在分析启动代码之前先看一下S3C2440的NAND启动:
在配置NAND启动模式之后,S3C2440上电会先将NAND中的0x0 - 0x1000共4096字节的数据拷贝到位于Bank0中的Boot Internal SRAM上
Bank0如下图:
连接脚本在board/smdk2440/u-boot.lds中,如下
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
cpu/arm920t/s3c24x0/nand_read.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
.text为代码段,可以看出cpu/arm920t/start.o在代码段的最前面,所以会先执行start.o中的代码
连接完成后的镜像文件的前4K如下
cpu/arm920t/start.o(.text)
.text 0x33f80000 0x4e0 cpu/arm920t/start.o
0x33f80050 IRQ_STACK_START
0x33f80048 _bss_start
0x33f8004c _bss_end
0x33f80044 _armboot_start
0x33f80000 _start
0x33f80054 FIQ_STACK_START
cpu/arm920t/s3c24x0/nand_read.o(.text)
.text 0x33f804e0 0x1b8 cpu/arm920t/s3c24x0/nand_read.o
0x33f804e0 nand_read_ll
*(.text)
.text 0x33f80698 0x64 board/smdk2440/libsmdk2440.a(lowlevel_init.o)
0x33f8069c lowlevel_init
.text 0x33f806fc 0x280 cpu/arm920t/libarm920t.a(interrupts.o)
0x33f80934 do_fiq
0x33f80880 do_undefined_instruction
0x33f80744 show_regs
0x33f80958 do_irq
0x33f80728 bad_mode
0x33f808c8 do_prefetch_abort
0x33f8070c disable_interrupts
0x33f80910 do_not_used
0x33f808ec do_data_abort
0x33f808a4 do_software_interrupt
0x33f806fc enable_interrupts
.text 0x33f8097c 0x250 cpu/arm920t/s3c24x0/libs3c24x0.a(interrupts.o)
0x33f80aa4 set_timer
0x33f80a20 reset_timer
0x33f8097c interrupt_init
0x33f80ba0 get_tbclk
0x33f80a90 get_timer
0x33f809f0 reset_timer_masked
0x33f80a24 get_timer_masked
0x33f80ab4 udelay
0x33f80b10 udelay_masked
0x33f80bac reset_cpu
0x33f80b8c get_ticks
.text 0x33f80bcc 0x150 cpu/arm920t/s3c24x0/libs3c24x0.a(speed.o)
0x33f80c4c get_HCLK
0x33f80cec get_PCLK
0x33f80c44 get_FCLK
0x33f80d14 get_UCLK
.text 0x33f80d1c 0x1e8 cpu/arm920t/s3c24x0/libs3c24x0.a(cmd_s3c24xx.o)
0x33f80d8c do_s3c24xx
.text 0x33f80f04 0xdc cpu/arm920t/s3c24x0/libs3c24x0.a(serial.o)
0x33f80f04 serial_setbrg
0x33f80fa8 serial_tstc
0x33f80f80 serial_putc
0x33f80f58 serial_init
0x33f80fb8 serial_puts
0x33f80f68 serial_getc
.text 0x33f80fe0 0x140 lib_arm/libarm.a(_divsi3.o)
0x33f80fe0 __divsi3
如何设置从0x33f80000开始呢?~这是链接的时候指定的
在根目录下面的config.mk中有下面一句
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
关键就是其中的-Ttext $(TEXT_BASE),这句指明了代码段的起始地址
而TEXT_BASE在board/smdk2440/config.mk中定义 TEXT_BASE = 0x33F8 0000
为什么是0x33F8 0000呢?~
这是将NAND中Uboot拷贝到RAM中的起始地址,所以在代码拷贝到RAM之前不能使用绝对地址来寻址数据,只能用相对地址
在以下将用虚拟地址来指Uboot在RAM中的地址,也就是0x33F8 0000
现在来看代码cpu/arm920t/start.S
_start: ;异常处理向量表
b start_code
ldr pc, _undefined_instruction ;未定义指令异常:0x00000004
ldr pc, _software_interrupt ;软中断异常:0x00000008
ldr pc, _prefetch_abort ;预取异常:0x0000000C
ldr pc, _data_abort ;数据异常:0x00000010
ldr pc, _not_used ;未使用:0x00000014
ldr pc, _irq ;外部中断请求IRQ:0x00000018
ldr pc, _fiq ;快束中断请求FIQ:0x0000001C
b start_code在虚拟地址0x33F8 0000处 , 拷贝到Boot Internal SRAM后则位于0x0处,所以b start_code是第一条执行的指令,
start_code在cpu/arm920t/start.S中
代码如下:
//读取CPSR寄存器的内容到R0
mrs r0,cpsr
//清除R0中的0 - 4 这5个位后保存到R0中
//也就是清除用户模式位
bic r0,r0,#0x1f
//置R0的0 1 4 6 7 位为真
//也就是选择SVC模式,同时IRQ和FIQ被禁止,处理器处于ARM状态
//关闭中断和快速中断
orr r0,r0,#0xd3
//将R0中的值保存到CPSR上
msr cpsr,r0
# define pWTCON 0x53000000 ;看门狗控制寄存器WTCON
# define INTMSK 0x4A000008 ;中断屏蔽寄存器INTMSK
# define INTSUBMSK 0x4A00001C ;辅助中断屏蔽寄存器,由于外设中断源太多,要用此寄存器屏蔽剩余的中断源
# define LOCKTIME 0x4c000000 ;PLL锁定时间计数寄存器
# define MPLLCON 0x4c000004 ;主时钟锁相环控制寄存器
# define UPLLCON 0x4c000008
# define CLKDIVN 0x4C000014 ;时钟分频寄存器/* clock divisor register */
# define INTSUBMSK_val 0xffff
# define MPLLCON_val ((184 << 12) + (2 << 4) + 2) /*406M*/
# define UPLLCON_val ((60 << 12) + (4 << 4) + 2) /* 47M */
# define CLKDIVN_val 7 /* FCLK:HCLK:PCLK = 1:3:6 */
# define CAMDIVN 0x4C000018
//取得看门狗寄存器的地址
ldr r0, =pWTCON
//将R1寄存器清0
mov r1, #0x0
//将看门狗寄存器清0,即将看门狗禁止,包括定时器定时,溢出中断及溢出复位等
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
//设R1寄存器为0xFFFF FFFF
mov r1, #0xffffffff
//读取中断屏蔽寄存器的地址
ldr r0, =INTMSK
//将中断屏蔽寄存器中的位全设1,屏蔽所有中断
str r1, [r0]
//# define INTSUBMSK_val 0xffff
//设R1寄存器为0xFFFF
ldr r1, =INTSUBMSK_val
//读取辅助中断屏蔽寄存器的地址
ldr r0, =INTSUBMSK
//将辅助中断屏蔽寄中的11个中断信号屏蔽掉,本人觉得INTSUBMS_val应设成7ff
str r1, [r0]
//# define LOCKTIME 0x4c000000
//读取PLL锁频计数器寄存器地址到R0中
ldr r0,=LOCKTIME
//将R1设为0x00FF FFFF
ldr r1,=0xffffff
//M_LTIME为最大的0xFFF
//U_LTIME为最大的0xFFF
str r1,[r0] ;0xfff=4096>1800,远远满足锁定要求
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
//# define CLKDIVN 0x4C000014 /* clock divisor register */
//读取时钟分频寄存器的地址
ldr r0, =CLKDIVN
//# define CLKDIVN_val 7 /* FCLK:HCLK:PCLK = 1:3:6 */
//将R1设为0x7
mov r1, #CLKDIVN_va
//PDIVN - 1: PCLK has the clock same as the HCLK/2.
//HDIVN - 11 : HCLK = FCLK/3 when CAMDIVN[8] = 0.
// HCLK = FCLK/6 when CAMDIVN[8] = 1.
str r1, [r0]
/* Make sure we get FCLK:HCLK:PCLK = 1:3:6 */
//# define CAMDIVN 0x4C000018
//读取摄像头时钟分频寄存器的地址
ldr r0, =CAMDIVN
//将R1设为0
mov r1, #0
//将摄像头时钟分频寄存器清0
str r1, [r0]
/* Clock asynchronous mode */
//MRC p15, 0, Rd, c1, c0, 0 ; read control register
//读取控制寄存器中的值到R1中
mrc p15, 0, r1, c1, c0, 0 ;将协处理器p15的寄存器c1和c0的值传到arm处理器的R1寄存器中
//31 iA bit Asynchronous clock select
//30 nF bit notFastBus select
orr r1, r1, #0xc0000000 ;将最高两位置1
//MCR p15, 0, Rd, c1, c0, 0 ; write control register
//将R1中的值写到控制寄存器中
mcr p15, 0, r1, c1, c0, 0 将arm的寄存器R1的32位数据传到协处理器p15的两个16位寄存器c1和c0
//# define UPLLCON 0x4c000008
//读取UPLL设置寄存器的地址到R0中
ldr r0,=UPLLCON
//# define UPLLCON_val ((60 << 12) + (4 << 4) + 2) /* 47M */
ldr r1,=UPLLCON_val
//将R1中的值写入UPLL设置寄存器中
str r1,[r0]
//ARM920T为5级流水线,需要至少5个周期来让指令生效
nop
nop
nop
nop
nop
nop
nop
nop
//读取MPLL设置寄存器的地址到R0中
ldr r0,=MPLLCON
//# define MPLLCON_val ((184 << 12) + (2 << 4) + 2) /*406M*/
ldr r1,=MPLLCON_val
//将R1中的值写入MPLL设置寄存器中
str r1,[r0]
#define GPJCON 0x560000D0
#define GPJDAT 0x560000D4
#define GPJUP 0x560000D8
//跳转到cpu_init_crit处执行
//并将下一条指令的地址写入LR寄存器中
bl cpu_init_crit
cpu_init_crit在cpu/arm920t/start.S中
代码如下:
cpu_init_crit:
/*
* flush v4 I/D caches
*/
//将R0寄存器置0
mov r0, #0
//Invalidate ICache and DCache SBZ MCR p15,0,Rd,c7,c7,0
//禁止指令和数据cache
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
//Invalidate TLB(s) SBZ MCR p15,0,Rd,c8,c7,0
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
//MRC p15, 0, Rd, c1, c0, 0 ; read control register
mrc p15, 0, r0, c1, c0, 0
//清除[8] [9] [13] 这3个位
//8 - System protection
//9 - ROM protection
//13 - Base location of exception registers - 0 = Low addresses = 0x00000000.
bic r0, r0, #0x00002300 // clear bits 13, 9:8 (--V- --RS)
//清除[0] [1] [2] [7] 这4个位
// 0 - MMU enable - 0 = MMU disabled.
// 1 - Alignment fault enable - 0 = Fault checking disabled.
// 2 - DCache enable - 0 = DCache disabled.
// 7 - Endianness - 0 = Little-endian operation.
bic r0, r0, #0x00000087 // clear bits 7, 2:0 (B--- -CAM)
//设置位[1]为真
// 1 - Alignment fault enable - 1 = Fault checking enabled.
orr r0, r0, #0x00000002 // set bit 2 (A) Align
//设置位[12]为真
//12 - ICache enable - 1 = ICache enabled.
orr r0, r0, #0x00001000 // set bit 12 (I) I-Cache
//MCR p15, 0, Rd, c1, c0, 0 ; write control register
mcr p15, 0, r0, c1, c0, 0
//将返回地址保存到IP中
mov ip, lr
//跳转到lowlevel_init中执行
bl lowlevel_init
cpu_init_crit在cpu/arm920t/start.S中
代码如下:
.globl lowlevel_init
//读取下面标号为SMRDATA处的地址到R0中
ldr r0, =SMRDATA
//读取上面标号为_TEXT_BASE处的地址内容到R1中
//也就是取得TEXT_BASE的值到R1中
ldr r1, _TEXT_BASE
//计算SMRDATA的相对地址保存到R0中
//SMRDATA为虚拟地址,而TEXT_BASE为虚拟地址的起始地址
//而现在Uboot的起始地址并不为虚拟地址
//TEXT_BASE为0x33F8 0000,SMRDATA为0x33F8 06C8
//而现在程序运行在起始地址为0x0000 0000的地方
//所以需要计算以0x0000 0000为标准的相对地址
sub r0, r0, r1
//取得带宽与等待状态控制寄存器地址到R1中
ldr r1, =BWSCON /* Bus Width Status Controller */
//一共需要设置13个寄存器,每个寄存器4字节
add r2, r0, #13*4
0:
//读取R0所指的项的值到R3中后R0自加4字节
ldr r3, [r0], #4
//将R3中的值保存到R1所指的地址中后R1自加4字节
str r3, [r1], #4
//比较R0和R2是否相等,相等则说明13个寄存器全部设置完毕
cmp r2, r0
//不等则跳转到上面标号为0处的地址继续执行
bne 0b
//跳回到返回地址中继续执行
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA:
.word(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
执行mov pc, lr后将返回到cpu_init_crit中
剩下来还有2条指令
//恢复返回地址到LR
mov lr, ip
//跳转到返回地址
mov pc, lr
执行完毕之后将返回到start_code中执行接下来的代码
代码如下:
//#define GPJCON 0x560000D0
//取得J端口控制寄存器的地址到R0中
LDR R0, = GPJCON
//将R1设置为0x1 5555
LDR R1, = 0x15555
//将R1中的值保存到J端口控制寄存器
//GPJ0 - 01 - Output
//GPJ1 - 01 - Output
//GPJ2 - 01 - Output
//GPJ3 - 01 - Output
//GPJ4 - 01 - Output
STR R1, [R0]
//#define GPJUP 0x560000D8
//取得J端口上拉功能寄存器的地址到R0中
LDR R0, = GPJUP
//将R1设置为0x1F
LDR R1, = 0x1f
//将R1中的值保存到J端口上拉功能寄存器
//禁止GPJ0 - GPJ4的上拉功能
STR R1, [R0]
//#define GPJDAT 0x560000D4
//取得J端口数据寄存器的地址到R0中
LDR R0, = GPJDAT
//将R1设为0x0
LDR R1, = 0x00
//将R1中的值保存到J端口数据寄存器
//将J端口数据寄存器清0
STR R1, [R0]
//下面是NAND数据拷贝过程
//relocate:
copy_myself:
//#define S3C2440_NAND_BASE 0x4E000000
//取得Nand Flash设置寄存器的地址
mov r1, #S3C2440_NAND_BASE
//将R2设为0xFFF0
ldr r2, =0xfff0 // initial value tacls=3,rph0=7,rph1=7
//#define oNFCONF 0x00
//读取Nand Flash设置寄存器中的值到R3中
ldr r3, [r1, #oNFCONF]
//将R3或上R2后保存到R3中
orr r3, r3, r2
//将R3中的值保存到Nand Flash设置寄存器中
//TWRPH0 - 111 - Duration = HCLK * (TWRPH0 + 1)
//TACLS - 11 - Duration = HCLK * TACLS
str r3, [r1, #oNFCONF]
//#define oNFCONT 0x04
//读取Nand Flash控制寄存器中的值到R3中
ldr r3, [r1, #oNFCONT]
//将R3的[0]位置1
orr r3, r3, #1 // enable nand controller
//将R3中的值保存到Nand Flash控制寄存器中
//Mode - 1:Nand Flash Controller Enable
str r3, [r1, #oNFCONT]
//读取虚拟起始地址到R0中
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
//预留malloc所需要的空间
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
//预留bdinfo所需要的空间
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
//预留中断和快速中断向量表空间
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
//预留12字节给中断栈
sub sp, r0, #12 /* leave 3 words for abort-stack */
// copy u-boot to RAM
//读取虚拟起始地址到R0中,作为目标地址
ldr r0, _TEXT_BASE
//将R1设为0,作为源地址
mov r1, #0x0
//将UBOOT大小的值保存在R2中,作为数据大小
mov r2, #CFG_UBOOT_SIZE
//跳转到nand_read_ll处执行
//并将下一条指令的地址保存在LR中
bl nand_read_ll
nand_read_ll的原型为
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
之前设置的R0 R1 R2为它的3个参数
R0 - buf
R1 - start_addr
R2 - size
nand_read_ll的代码在cpu/arm920t/s3c24x0/nand_read.c中
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
//检测源地址和大小是否在NandFlash的边界上
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK))
//不在边界上则返回-1表示出错
return -1; /* invalid alignment */
/* chip Enable */
// #define nand_select() (NFCONT &= ~(1 << 1))
//置NAND Flash控制寄存器中除Reg_nCE外所有的位为1
//Reg_nCE - NAND FLASH Memory nFCE signal control
//0 - Force nFCE to low (Enable chip select)
nand_select();
// #define nand_clear_RnB() (NFSTAT |= (1 << 2))
//置NAND Flash操作状态寄存器中的RnB_TransDetect位为1
//When RnB low to high transition is occurred, this value set and issue interrupt if enabled.
//To clear this value write '1'
//1: RnB transition is detected
nand_clear_RnB();
for (i=0; i<10; i++);
//从源地址的首地址开始历便所要拷贝的数据大小
for (i=start_addr; i < (start_addr + size);)
{
//检测地址是否在NAND Flash的边界上
if (start_addr % NAND_BLOCK_SIZE == 0)
{
//检测是否为坏块
if (is_bad_block(i))
{
/* Bad block */
//向后延伸一个存储块
i += NAND_BLOCK_SIZE;
size += NAND_BLOCK_SIZE;
//跳到下一块
continue;
}
}
j = nand_read_page_ll(buf, i);
//指向下一块
i += j;
buf += j;
// LED_FLASH();
}
/* chip Disable */
// #define nand_deselect() (NFCONT |= (1 << 1))
//置Reg_nCE位为1
//NAND Flash Memory nFCE signal control
//1: Force nFCE to High(Disable chip select)
nand_deselect();
return 0;
}
nand_read_ll将Uboot从NAND中拷贝到RAM中
拷贝完成后将返回到start_code
接下来的代码如下:
//检测R0是否为0,R0为nand_read_ll的返回值
tst r0, #0x0
//为0则说明无错,跳转到ok_nand_read处执行
beq ok_nand_read
ok_nand_read:
//将R0设为0
mov r0, #0
//ldr r1, =0x33f00000
//将R1设为虚拟地址起始处
ldr r1, _TEXT_BASE
//检测0x400个字节
mov r2, #0x400 // 4 bytes * 1024 = 4K-bytes
go_next:
//读取R0处地址的数据到R3中
//然后R0自加4字节
ldr r3, [r0], #4
//读取R1处地址的数据到R4中
//然后R1自加4字节
ldr r4, [r1], #4
//比较R3和R4的数据是否相等
//也就是检测Boot Internal SRAM和RAM中的数据是否相等
//以保证数据无错
teq r3, r4
//不等则跳转到notmatch
bne notmatch
//相等则R2自减4
subs r2, r2, #4
//当R2为0则跳转到done_nand_read
beq done_nand_read
//R2不为0则跳转回go_next继续检测
bne go_next
done_nand_read:
LDR R0, = GPJDAT
LDR R1, = 0x2
STR R1, [R0]
stack_setup:
//读取虚拟起始地址到R0中
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
//预留malloc所需要的空间
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
//预留bdinfo所需要的空间
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
//预留中断和快速中断向量表空间
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
//预留12字节给中断栈
sub sp, r0, #12 /* leave 3 words for abort-stack */
clear_bss:
//读取BSS段的起始地址
ldr r0, _bss_start /* find start of bss segment */
//读取BSS段的结束地址
ldr r1, _bss_end /* stop here */
//将R2设为0x0
mov r2, #0x00000000 /* clear */
clbss_l:
//将R2中的值保存在R0所指的地址
str r2, [r0] /* clear loop... */
//R0自加4字节
add r0, r0, #4
//比较R0和R1是否相等
cmp r0, r1
//不等则说明清0还没结束
ble clbss_l
LDR R0, = GPJDAT
LDR R1, = 0x1
STR R1, [R0]
//跳转到start_armboot处执行
ldr pc, _start_armboot
_start_armboot: .word start_armboot
这里start_armboot是一个绝对地址,在朗成所修改的这个Uboot中为0x33F8 13F4
执行ldr pc, _start_armboot之后将会跳到RAM中的绝对地址继续执行
整理了一个流程图,分为3个存储器:
1 Boot Internal SRAM , 接在BANK0,起始地址为0x0
2 RAM , 接在BANK6,起始地址为0x3000 0000
3 NAND FLASH,为单独寻址
流程如下图:
红字为流程序号:
1. 首先将NAND FLASH中的前0x1000字节内容拷贝到Boot Internal SRAM中
2. 从Boot Internal SRAM的0x0地址处开始执行指令
3. 将Uboot从Flash拷贝到RAM中
4. 执行ldr pc, _start_armboot
从Boot Internal SRAM中跳转到RAM中的绝对地址0x33F8 13F4处继续执行
在配置NAND启动模式之后,S3C2440上电会先将NAND中的0x0 - 0x1000共4096字节的数据拷贝到位于Bank0中的Boot Internal SRAM上
Bank0如下图:
可以看出Boot Internal SRAM为4KB大小,也正
是因为Boot Internal SRAM只有4KB大小,所以只能从NAND中拷贝4K的内容 = 3= 这个Boot Internal SRAM是配置为NAND FLASH启动模式才有的
连接脚本在board/smdk2440/u-boot.lds中,如下
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
cpu/arm920t/s3c24x0/nand_read.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
.text为代码段,可以看出cpu/arm920t/start.o在代码段的最前面,所以会先执行start.o中的代码
连接完成后的镜像文件的前4K如下
cpu/arm920t/start.o(.text)
.text 0x33f80000 0x4e0 cpu/arm920t/start.o
0x33f80050 IRQ_STACK_START
0x33f80048 _bss_start
0x33f8004c _bss_end
0x33f80044 _armboot_start
0x33f80000 _start
0x33f80054 FIQ_STACK_START
cpu/arm920t/s3c24x0/nand_read.o(.text)
.text 0x33f804e0 0x1b8 cpu/arm920t/s3c24x0/nand_read.o
0x33f804e0 nand_read_ll
*(.text)
.text 0x33f80698 0x64 board/smdk2440/libsmdk2440.a(lowlevel_init.o)
0x33f8069c lowlevel_init
.text 0x33f806fc 0x280 cpu/arm920t/libarm920t.a(interrupts.o)
0x33f80934 do_fiq
0x33f80880 do_undefined_instruction
0x33f80744 show_regs
0x33f80958 do_irq
0x33f80728 bad_mode
0x33f808c8 do_prefetch_abort
0x33f8070c disable_interrupts
0x33f80910 do_not_used
0x33f808ec do_data_abort
0x33f808a4 do_software_interrupt
0x33f806fc enable_interrupts
.text 0x33f8097c 0x250 cpu/arm920t/s3c24x0/libs3c24x0.a(interrupts.o)
0x33f80aa4 set_timer
0x33f80a20 reset_timer
0x33f8097c interrupt_init
0x33f80ba0 get_tbclk
0x33f80a90 get_timer
0x33f809f0 reset_timer_masked
0x33f80a24 get_timer_masked
0x33f80ab4 udelay
0x33f80b10 udelay_masked
0x33f80bac reset_cpu
0x33f80b8c get_ticks
.text 0x33f80bcc 0x150 cpu/arm920t/s3c24x0/libs3c24x0.a(speed.o)
0x33f80c4c get_HCLK
0x33f80cec get_PCLK
0x33f80c44 get_FCLK
0x33f80d14 get_UCLK
.text 0x33f80d1c 0x1e8 cpu/arm920t/s3c24x0/libs3c24x0.a(cmd_s3c24xx.o)
0x33f80d8c do_s3c24xx
.text 0x33f80f04 0xdc cpu/arm920t/s3c24x0/libs3c24x0.a(serial.o)
0x33f80f04 serial_setbrg
0x33f80fa8 serial_tstc
0x33f80f80 serial_putc
0x33f80f58 serial_init
0x33f80fb8 serial_puts
0x33f80f68 serial_getc
.text 0x33f80fe0 0x140 lib_arm/libarm.a(_divsi3.o)
0x33f80fe0 __divsi3
如何设置从0x33f80000开始呢?~这是链接的时候指定的
在根目录下面的config.mk中有下面一句
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
关键就是其中的-Ttext $(TEXT_BASE),这句指明了代码段的起始地址
而TEXT_BASE在board/smdk2440/config.mk中定义 TEXT_BASE = 0x33F8 0000
为什么是0x33F8 0000呢?~
这是将NAND中Uboot拷贝到RAM中的起始地址,所以在代码拷贝到RAM之前不能使用绝对地址来寻址数据,只能用相对地址
在以下将用虚拟地址来指Uboot在RAM中的地址,也就是0x33F8 0000
现在来看代码cpu/arm920t/start.S
_start: ;异常处理向量表
b start_code
ldr pc, _undefined_instruction ;未定义指令异常:0x00000004
ldr pc, _software_interrupt ;软中断异常:0x00000008
ldr pc, _prefetch_abort ;预取异常:0x0000000C
ldr pc, _data_abort ;数据异常:0x00000010
ldr pc, _not_used ;未使用:0x00000014
ldr pc, _irq ;外部中断请求IRQ:0x00000018
ldr pc, _fiq ;快束中断请求FIQ:0x0000001C
b start_code在虚拟地址0x33F8 0000处 , 拷贝到Boot Internal SRAM后则位于0x0处,所以b start_code是第一条执行的指令,
start_code在cpu/arm920t/start.S中
代码如下:
//读取CPSR寄存器的内容到R0
mrs r0,cpsr
//清除R0中的0 - 4 这5个位后保存到R0中
//也就是清除用户模式位
bic r0,r0,#0x1f
//置R0的0 1 4 6 7 位为真
//也就是选择SVC模式,同时IRQ和FIQ被禁止,处理器处于ARM状态
//关闭中断和快速中断
orr r0,r0,#0xd3
//将R0中的值保存到CPSR上
msr cpsr,r0
# define pWTCON 0x53000000 ;看门狗控制寄存器WTCON
# define INTMSK 0x4A000008 ;中断屏蔽寄存器INTMSK
# define INTSUBMSK 0x4A00001C ;辅助中断屏蔽寄存器,由于外设中断源太多,要用此寄存器屏蔽剩余的中断源
# define LOCKTIME 0x4c000000 ;PLL锁定时间计数寄存器
# define MPLLCON 0x4c000004 ;主时钟锁相环控制寄存器
# define UPLLCON 0x4c000008
# define CLKDIVN 0x4C000014 ;时钟分频寄存器/* clock divisor register */
# define INTSUBMSK_val 0xffff
# define MPLLCON_val ((184 << 12) + (2 << 4) + 2) /*406M*/
# define UPLLCON_val ((60 << 12) + (4 << 4) + 2) /* 47M */
# define CLKDIVN_val 7 /* FCLK:HCLK:PCLK = 1:3:6 */
# define CAMDIVN 0x4C000018
//取得看门狗寄存器的地址
ldr r0, =pWTCON
//将R1寄存器清0
mov r1, #0x0
//将看门狗寄存器清0,即将看门狗禁止,包括定时器定时,溢出中断及溢出复位等
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
//设R1寄存器为0xFFFF FFFF
mov r1, #0xffffffff
//读取中断屏蔽寄存器的地址
ldr r0, =INTMSK
//将中断屏蔽寄存器中的位全设1,屏蔽所有中断
str r1, [r0]
//# define INTSUBMSK_val 0xffff
//设R1寄存器为0xFFFF
ldr r1, =INTSUBMSK_val
//读取辅助中断屏蔽寄存器的地址
ldr r0, =INTSUBMSK
//将辅助中断屏蔽寄中的11个中断信号屏蔽掉,本人觉得INTSUBMS_val应设成7ff
str r1, [r0]
//# define LOCKTIME 0x4c000000
//读取PLL锁频计数器寄存器地址到R0中
ldr r0,=LOCKTIME
//将R1设为0x00FF FFFF
ldr r1,=0xffffff
//M_LTIME为最大的0xFFF
//U_LTIME为最大的0xFFF
str r1,[r0] ;0xfff=4096>1800,远远满足锁定要求
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
//# define CLKDIVN 0x4C000014 /* clock divisor register */
//读取时钟分频寄存器的地址
ldr r0, =CLKDIVN
//# define CLKDIVN_val 7 /* FCLK:HCLK:PCLK = 1:3:6 */
//将R1设为0x7
mov r1, #CLKDIVN_va
//PDIVN - 1: PCLK has the clock same as the HCLK/2.
//HDIVN - 11 : HCLK = FCLK/3 when CAMDIVN[8] = 0.
// HCLK = FCLK/6 when CAMDIVN[8] = 1.
str r1, [r0]
/* Make sure we get FCLK:HCLK:PCLK = 1:3:6 */
//# define CAMDIVN 0x4C000018
//读取摄像头时钟分频寄存器的地址
ldr r0, =CAMDIVN
//将R1设为0
mov r1, #0
//将摄像头时钟分频寄存器清0
str r1, [r0]
/* Clock asynchronous mode */
//MRC p15, 0, Rd, c1, c0, 0 ; read control register
//读取控制寄存器中的值到R1中
mrc p15, 0, r1, c1, c0, 0 ;将协处理器p15的寄存器c1和c0的值传到arm处理器的R1寄存器中
//31 iA bit Asynchronous clock select
//30 nF bit notFastBus select
orr r1, r1, #0xc0000000 ;将最高两位置1
//MCR p15, 0, Rd, c1, c0, 0 ; write control register
//将R1中的值写到控制寄存器中
mcr p15, 0, r1, c1, c0, 0 将arm的寄存器R1的32位数据传到协处理器p15的两个16位寄存器c1和c0
//# define UPLLCON 0x4c000008
//读取UPLL设置寄存器的地址到R0中
ldr r0,=UPLLCON
//# define UPLLCON_val ((60 << 12) + (4 << 4) + 2) /* 47M */
ldr r1,=UPLLCON_val
//将R1中的值写入UPLL设置寄存器中
str r1,[r0]
//ARM920T为5级流水线,需要至少5个周期来让指令生效
nop
nop
nop
nop
nop
nop
nop
nop
//读取MPLL设置寄存器的地址到R0中
ldr r0,=MPLLCON
//# define MPLLCON_val ((184 << 12) + (2 << 4) + 2) /*406M*/
ldr r1,=MPLLCON_val
//将R1中的值写入MPLL设置寄存器中
str r1,[r0]
#define GPJCON 0x560000D0
#define GPJDAT 0x560000D4
#define GPJUP 0x560000D8
//跳转到cpu_init_crit处执行
//并将下一条指令的地址写入LR寄存器中
bl cpu_init_crit
cpu_init_crit在cpu/arm920t/start.S中
代码如下:
cpu_init_crit:
/*
* flush v4 I/D caches
*/
//将R0寄存器置0
mov r0, #0
//Invalidate ICache and DCache SBZ MCR p15,0,Rd,c7,c7,0
//禁止指令和数据cache
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
//Invalidate TLB(s) SBZ MCR p15,0,Rd,c8,c7,0
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
//MRC p15, 0, Rd, c1, c0, 0 ; read control register
mrc p15, 0, r0, c1, c0, 0
//清除[8] [9] [13] 这3个位
//8 - System protection
//9 - ROM protection
//13 - Base location of exception registers - 0 = Low addresses = 0x00000000.
bic r0, r0, #0x00002300 // clear bits 13, 9:8 (--V- --RS)
//清除[0] [1] [2] [7] 这4个位
// 0 - MMU enable - 0 = MMU disabled.
// 1 - Alignment fault enable - 0 = Fault checking disabled.
// 2 - DCache enable - 0 = DCache disabled.
// 7 - Endianness - 0 = Little-endian operation.
bic r0, r0, #0x00000087 // clear bits 7, 2:0 (B--- -CAM)
//设置位[1]为真
// 1 - Alignment fault enable - 1 = Fault checking enabled.
orr r0, r0, #0x00000002 // set bit 2 (A) Align
//设置位[12]为真
//12 - ICache enable - 1 = ICache enabled.
orr r0, r0, #0x00001000 // set bit 12 (I) I-Cache
//MCR p15, 0, Rd, c1, c0, 0 ; write control register
mcr p15, 0, r0, c1, c0, 0
//将返回地址保存到IP中
mov ip, lr
//跳转到lowlevel_init中执行
bl lowlevel_init
cpu_init_crit在cpu/arm920t/start.S中
代码如下:
.globl lowlevel_init
//读取下面标号为SMRDATA处的地址到R0中
ldr r0, =SMRDATA
//读取上面标号为_TEXT_BASE处的地址内容到R1中
//也就是取得TEXT_BASE的值到R1中
ldr r1, _TEXT_BASE
//计算SMRDATA的相对地址保存到R0中
//SMRDATA为虚拟地址,而TEXT_BASE为虚拟地址的起始地址
//而现在Uboot的起始地址并不为虚拟地址
//TEXT_BASE为0x33F8 0000,SMRDATA为0x33F8 06C8
//而现在程序运行在起始地址为0x0000 0000的地方
//所以需要计算以0x0000 0000为标准的相对地址
sub r0, r0, r1
//取得带宽与等待状态控制寄存器地址到R1中
ldr r1, =BWSCON /* Bus Width Status Controller */
//一共需要设置13个寄存器,每个寄存器4字节
add r2, r0, #13*4
0:
//读取R0所指的项的值到R3中后R0自加4字节
ldr r3, [r0], #4
//将R3中的值保存到R1所指的地址中后R1自加4字节
str r3, [r1], #4
//比较R0和R2是否相等,相等则说明13个寄存器全部设置完毕
cmp r2, r0
//不等则跳转到上面标号为0处的地址继续执行
bne 0b
//跳回到返回地址中继续执行
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA:
.word(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
执行mov pc, lr后将返回到cpu_init_crit中
剩下来还有2条指令
//恢复返回地址到LR
mov lr, ip
//跳转到返回地址
mov pc, lr
执行完毕之后将返回到start_code中执行接下来的代码
代码如下:
//#define GPJCON 0x560000D0
//取得J端口控制寄存器的地址到R0中
LDR R0, = GPJCON
//将R1设置为0x1 5555
LDR R1, = 0x15555
//将R1中的值保存到J端口控制寄存器
//GPJ0 - 01 - Output
//GPJ1 - 01 - Output
//GPJ2 - 01 - Output
//GPJ3 - 01 - Output
//GPJ4 - 01 - Output
STR R1, [R0]
//#define GPJUP 0x560000D8
//取得J端口上拉功能寄存器的地址到R0中
LDR R0, = GPJUP
//将R1设置为0x1F
LDR R1, = 0x1f
//将R1中的值保存到J端口上拉功能寄存器
//禁止GPJ0 - GPJ4的上拉功能
STR R1, [R0]
//#define GPJDAT 0x560000D4
//取得J端口数据寄存器的地址到R0中
LDR R0, = GPJDAT
//将R1设为0x0
LDR R1, = 0x00
//将R1中的值保存到J端口数据寄存器
//将J端口数据寄存器清0
STR R1, [R0]
//下面是NAND数据拷贝过程
//relocate:
copy_myself:
//#define S3C2440_NAND_BASE 0x4E000000
//取得Nand Flash设置寄存器的地址
mov r1, #S3C2440_NAND_BASE
//将R2设为0xFFF0
ldr r2, =0xfff0 // initial value tacls=3,rph0=7,rph1=7
//#define oNFCONF 0x00
//读取Nand Flash设置寄存器中的值到R3中
ldr r3, [r1, #oNFCONF]
//将R3或上R2后保存到R3中
orr r3, r3, r2
//将R3中的值保存到Nand Flash设置寄存器中
//TWRPH0 - 111 - Duration = HCLK * (TWRPH0 + 1)
//TACLS - 11 - Duration = HCLK * TACLS
str r3, [r1, #oNFCONF]
//#define oNFCONT 0x04
//读取Nand Flash控制寄存器中的值到R3中
ldr r3, [r1, #oNFCONT]
//将R3的[0]位置1
orr r3, r3, #1 // enable nand controller
//将R3中的值保存到Nand Flash控制寄存器中
//Mode - 1:Nand Flash Controller Enable
str r3, [r1, #oNFCONT]
//读取虚拟起始地址到R0中
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
//预留malloc所需要的空间
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
//预留bdinfo所需要的空间
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
//预留中断和快速中断向量表空间
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
//预留12字节给中断栈
sub sp, r0, #12 /* leave 3 words for abort-stack */
// copy u-boot to RAM
//读取虚拟起始地址到R0中,作为目标地址
ldr r0, _TEXT_BASE
//将R1设为0,作为源地址
mov r1, #0x0
//将UBOOT大小的值保存在R2中,作为数据大小
mov r2, #CFG_UBOOT_SIZE
//跳转到nand_read_ll处执行
//并将下一条指令的地址保存在LR中
bl nand_read_ll
nand_read_ll的原型为
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
之前设置的R0 R1 R2为它的3个参数
R0 - buf
R1 - start_addr
R2 - size
nand_read_ll的代码在cpu/arm920t/s3c24x0/nand_read.c中
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
//检测源地址和大小是否在NandFlash的边界上
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK))
//不在边界上则返回-1表示出错
return -1; /* invalid alignment */
/* chip Enable */
// #define nand_select() (NFCONT &= ~(1 << 1))
//置NAND Flash控制寄存器中除Reg_nCE外所有的位为1
//Reg_nCE - NAND FLASH Memory nFCE signal control
//0 - Force nFCE to low (Enable chip select)
nand_select();
// #define nand_clear_RnB() (NFSTAT |= (1 << 2))
//置NAND Flash操作状态寄存器中的RnB_TransDetect位为1
//When RnB low to high transition is occurred, this value set and issue interrupt if enabled.
//To clear this value write '1'
//1: RnB transition is detected
nand_clear_RnB();
for (i=0; i<10; i++);
//从源地址的首地址开始历便所要拷贝的数据大小
for (i=start_addr; i < (start_addr + size);)
{
//检测地址是否在NAND Flash的边界上
if (start_addr % NAND_BLOCK_SIZE == 0)
{
//检测是否为坏块
if (is_bad_block(i))
{
/* Bad block */
//向后延伸一个存储块
i += NAND_BLOCK_SIZE;
size += NAND_BLOCK_SIZE;
//跳到下一块
continue;
}
}
j = nand_read_page_ll(buf, i);
//指向下一块
i += j;
buf += j;
// LED_FLASH();
}
/* chip Disable */
// #define nand_deselect() (NFCONT |= (1 << 1))
//置Reg_nCE位为1
//NAND Flash Memory nFCE signal control
//1: Force nFCE to High(Disable chip select)
nand_deselect();
return 0;
}
nand_read_ll将Uboot从NAND中拷贝到RAM中
拷贝完成后将返回到start_code
接下来的代码如下:
//检测R0是否为0,R0为nand_read_ll的返回值
tst r0, #0x0
//为0则说明无错,跳转到ok_nand_read处执行
beq ok_nand_read
ok_nand_read:
//将R0设为0
mov r0, #0
//ldr r1, =0x33f00000
//将R1设为虚拟地址起始处
ldr r1, _TEXT_BASE
//检测0x400个字节
mov r2, #0x400 // 4 bytes * 1024 = 4K-bytes
go_next:
//读取R0处地址的数据到R3中
//然后R0自加4字节
ldr r3, [r0], #4
//读取R1处地址的数据到R4中
//然后R1自加4字节
ldr r4, [r1], #4
//比较R3和R4的数据是否相等
//也就是检测Boot Internal SRAM和RAM中的数据是否相等
//以保证数据无错
teq r3, r4
//不等则跳转到notmatch
bne notmatch
//相等则R2自减4
subs r2, r2, #4
//当R2为0则跳转到done_nand_read
beq done_nand_read
//R2不为0则跳转回go_next继续检测
bne go_next
done_nand_read:
LDR R0, = GPJDAT
LDR R1, = 0x2
STR R1, [R0]
stack_setup:
//读取虚拟起始地址到R0中
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
//预留malloc所需要的空间
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
//预留bdinfo所需要的空间
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
//预留中断和快速中断向量表空间
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
//预留12字节给中断栈
sub sp, r0, #12 /* leave 3 words for abort-stack */
clear_bss:
//读取BSS段的起始地址
ldr r0, _bss_start /* find start of bss segment */
//读取BSS段的结束地址
ldr r1, _bss_end /* stop here */
//将R2设为0x0
mov r2, #0x00000000 /* clear */
clbss_l:
//将R2中的值保存在R0所指的地址
str r2, [r0] /* clear loop... */
//R0自加4字节
add r0, r0, #4
//比较R0和R1是否相等
cmp r0, r1
//不等则说明清0还没结束
ble clbss_l
LDR R0, = GPJDAT
LDR R1, = 0x1
STR R1, [R0]
//跳转到start_armboot处执行
ldr pc, _start_armboot
_start_armboot: .word start_armboot
这里start_armboot是一个绝对地址,在朗成所修改的这个Uboot中为0x33F8 13F4
执行ldr pc, _start_armboot之后将会跳到RAM中的绝对地址继续执行
整理了一个流程图,分为3个存储器:
1 Boot Internal SRAM , 接在BANK0,起始地址为0x0
2 RAM , 接在BANK6,起始地址为0x3000 0000
3 NAND FLASH,为单独寻址
流程如下图:
红字为流程序号:
1. 首先将NAND FLASH中的前0x1000字节内容拷贝到Boot Internal SRAM中
2. 从Boot Internal SRAM的0x0地址处开始执行指令
3. 将Uboot从Flash拷贝到RAM中
4. 执行ldr pc, _start_armboot
从Boot Internal SRAM中跳转到RAM中的绝对地址0x33F8 13F4处继续执行