4.2 u-boot源码分析 --- 启动第一阶段
分析代码当然要从上电后执行的第一条指令开始看起咯, 那第一条指令在哪呢? 还是以smdk2410为例,我们看它的链接脚本:
board/smsk2410/u-boot.lds:
……
ENTRY(_start) //入口地址
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text) //呵呵, 这个就是启动后执行的第一个文件
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
……
}
由这个文件可知第一个执行的文件是cpu/arm920t/start.S,那第一条指令就在这里了。我们看这个文件:
cpu/arm920t/start.S:
.globl _start
_start: b reset //复位向量
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq //中断向量
ldr pc, _fiq //快速中断向量
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
2410的CPU规定开机后的PC寄存器地址为0,即从0地址开始执行指令,因此我们必须把我们的程序放在正确的地址处才能正常开机。 ARM核也规定启动地址处的32个字节必须存放异常向量跳转表,里面保存有中断,异常等的处理函数地址。当系统产生中断时,必定会跳到这里来开始处理中断。具体可参考ARM方面的书籍。
由u-boot.lds可知入口地址为_start, 即开机后从_start处开始执行指令。所以第一条指令就是:
b reset //跳转到reset处进行复位处理
cpu/arm920t/start.S:
/*
* the actual reset code
*/
reset:
/*
* 让处理器进入SVC32模式,这样可以拥有特权操作,参考ARM书籍
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
/* turn off the watchdog */
//CPU上操作watchdog相关的寄存器地址,可参考CPU的datasheet,这里用到的地址都是实地址,
//因为还没为MMU等部件进行初始化,也没切换操作模式呢。
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0] //关闭watchdog,具体寄存器含义可参考CPU手册
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0] //关闭所有的中断
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0] //关闭所有的中断
# endif
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
//设置HCLK为FCLK/2, PCLK为FCLK/4, FCLK为CPU产生clock,HCLK为AHB总线上的设备产生//clock, PCLK为APB总线上的设备产生clock,具体参考s3c2410的datasheet
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
//做系统相关的重要初始化,这些初始化代码只在系统重起的时候执行,
// CONFIG_SKIP_LOWLEVEL_INIT 可以看README.
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //可以先看这段代码在转回来接着看后面的复位过程。
#endif
//内存配置完后,可以进行重定位操作了
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* 重定位u-boot到RAM中*/
adr r0, _start /* r0 = flash中的代码的起始地址*/
ldr r1, _TEXT_BASE /* r1= 代码在RAM中的起始地址 */
cmp r0, r1 /* 看是否u-boot就在RAM中运行*/
beq stack_setup /*如果在RAM中则无需重定位*/
/*开始重定位,即把u-boot从flash中搬到RAM中去运行*/
ldr r2, _armboot_start /*r2 = flash中代码的起始地址,看_armboot_start的定义*/
ldr r3, _bss_start /*r3 = bss段的起始地址,_bss_start可在u-boot.lds中查看。*/
sub r2, r3, r2 /* r2 = 需要重定位的字节数*/
add r2, r0, r2 /* r2 = flash中RO,RW内容的结束地址 */
//开始把代码从flash中搬运到RAM中
copy_loop:
ldmia r0!, {r3-r10} /*获取从r0开始的代码,存入r3—r10*/
stmia r1!, {r3-r10} /*把r3—r10的内容存入r1所在位置,即RAM中*/
cmp r0, r2 /*copy所有代码 */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
/*设置栈地址*/
stack_setup:
ldr r0, _TEXT_BASE /*upper 128 KiB: relocated uboot*/
sub r0, r0, #CFG_MALLOC_LEN /*malloc分配内存的区域,大小以板子的配置而定,smdk2410
的在include/configs/smdk2410.h中定义*/
sub r0, r0, #CFG_GBL_DATA_SIZE /* 存放bdinfo的区域,定义同上*/
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) //保留中断所需的区域
#endif
sub sp, r0, #12 /* 保留 12字节给 abort-stack, 并设好堆栈*/
//bss段内容清0
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
#if 0
/* try doing this stuff after the relocation */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff
ldr r0, =INTMR
str r1, [r0]
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
/* END stuff after relocation */
#endif
ldr pc, _start_armboot //跳转到_start_armboot处执行。
_start_armboot: .word start_armboot
总结reset这块代码,主要完成了一下几个部分:
1. 重要部分的初始化工作,如禁止中断,关闭watchdog,初始化memory控制器等
2. 重定位boot loader到ram
3. 设置好堆栈
4. 跳转到第2阶段执行
完成这些后,此时内存的分布情况如下:
这个图代表的是u-boot自己在内存的情况,和上面的图不一样,这里的_TEXT_BASE就是0x33F8’0000
接着看CPU_init_critical
cpu/arm920t/start.S:
/*
*************************************************************************
*
* CPU_init_critical registers
* 设置cache,TLB,MMU等寄存器
* 设置内存操作的时序
*
*************************************************************************
*/
cpu_init_crit:
/*
* flush v4 I/D caches
*/
/*使cache和TLB无效, 可以参考data sheet*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* 使指令cache和数据cache无效 */
mcr p15, 0, r0, c8, c7, 0 /* 使TLB无效 */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0 /*读出c1控制寄存器的值*/
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM),小端对齐,关闭数据cache,关
//闭错误检测,关闭MMU
orr r0, r0, #0x00000002 @ set bit 2 (A) Align, 使能错误检测
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache, 使能指令cache
mcr p15, 0, r0, c1, c0, 0 /*设置c1控制寄存器*/
/*可以参考data sheet*/
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
//在把u-boot重定位到RAM前,我们必须先把RAM的时序设置好,内存时序是依板子而定的,//所以这里的初始化应该由我们提供,一般在我们的板子所在目录下有个lowlevel_init.S来负//责这件事情。特定板子的目录还记得吗, 呵呵回到上面在看看。
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr //类似于函数返回
cpu_init_crit主要是使能了instruction cache,关闭了MMU等部件,但是好像在u-boot后面的代码里没有看见打开MMU的操作,我猜测可能是留到了OS启动的时候再打开了吧,data cache在第二阶段的board_init下被使能。
接着看lowlevel_init。以smdk2410位例
board/smdk2410/lowlevel_init.S
_TEXT_BASE:
.word TEXT_BASE
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
// 内存控制器的配置, 配置完后就可以使用内存了
ldr r0, =SMRDATA //在下面定义
ldr r1, _TEXT_BASE
sub r0, r0, r1 //???
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4 //设置内存配置寄存器,可以对着datasheet来看这里的设置,包括时
//序位宽等等, 使用一个循环来配置所有的寄存器
cmp r2, r0
bne 0b
/* everything is fine now */
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
这部分代码主要是设置memory的时序,位宽等参数