近日在开发uboot的过程中遇到_start_armboot阶段LED灯不亮,于是郁闷之余只好通过调试工具找问题。在调试过程中发现网络上的关于start.s的分析很多,不过都不是很详细。本人通过ADX调试uboot.bin,可惜都是汇编代码,只能硬着头皮分析。庆幸的是通过调试分析深刻地了解了启动过程,现在我详细的分析一下。本人还是初学,有什么分析不对的地方请高人指正。
我使用的是STR711,所以通过说s3c44b0的代码进行修改。下面的分析就基于s3c44b0的start.s(略有改动)。
代码分析:
.globl _start
_start: b reset
add pc, pc, #0x20000000
add pc, pc, #0x20000000
add pc, pc, #0x20000000
add pc, pc, #0x20000000
add pc, pc, #0x20000000
add pc, pc, #0x20000000
add pc, pc, #0x20000000
.balignl 16,0xdeadbeef
这是uboot中开始的一段代码,功能是实现异常中断向量跳转的设置。_start是
系统复位位置,在uboot.lds文件中指定,为0x00000000.系统复位后,b指令跳转
到reset出开始执行,从而进入第二阶段继续执行下面的add pc, pc, #0x0c0000000
用于跳转到sdram中的中断处理程序处。b指令的寻址空间是32M,而add的寻址空间是4G,reset同处在start.S中,因此只需b指令就可寻址,但另外6个中断处理函数在sdram中,因此需要用有较大寻址空间能力的指令进行跳转.。当一个中断发生时,ARM会强制把PC指针设置为对应中断类型地址处,该处的指令会跳转到自己的中断处理处继续执行。最后一句是数据对齐,不用太了解。
_TEXT_BASE:
.word TEXT_BASE
.globl _armboot_start
_armboot_start:
.word _start
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
这一段定义了_TEXT_BASE 这个是基地址,在config.mk中定义。_armboot_start注意这个变量和后面_start_armboot的区别。前者是其实就是给_start分配地址,后者则是为第二阶段的起始分配地址,也就是board.c中的start_armboot函数的地址。_bss_start数据段的起始地址,_bss_end数据段结尾的地址。
reset:
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0x13
msr cpsr,r0
这一段是将cpu配置为管理模式,cpsr具体的参数比较简单,就不说了。可以参考关于arm的书籍,基本都有说。
bl cpu_init_crit
跳转到cpu_init_crit段,具体代码如下:
cpu_init_crit:
/* disable watch dog 禁用看门狗电路*/
ldr r0, =WDG_CR
ldr r1, =0x0
str r1, [r0]
/* mask all IRQs by clearing all bits in the EIC_IER0*/
/*关闭除21位即TIME3之外的所有中断*/
ldr r1,=EIC_IER0
ldr r0, =0x00100000
str r0, [r1]
/*设置中断控制器,禁用FIQ中断,允许IRQ中断*/
ldr r1, =EIC_ICR
ldr r0, =0x01
str r0, [r1]
/* Set Clock Control Register */
ldr r1, =RCCU_CCR
ldrb r0, =800
strb r0, [r1]
/*设置PLL寄存器,设置倍频*/
ldr r1, =RCCU_PLL1CR
#if CONFIG_STR711_CLOCK_SPEED==24
ldr r0, =0x0051 /* 24MHz */
#else
# error CONFIG_STR711_CLOCK_SPEED undefined
#endif
str r0, [r1]
ldr r1,=RCCU_CCR
ldr r0, =0x0400
str r0, [r1]
mov pc, lr
以上代码主要是对看门狗、中断寄存器和PLL的设置,根据不同的芯片进行修改。这样cpu初始化完成,跳转回刚才的数据段。
bl lowlevel_init
跳转到lowlevel_init,该段在lowlevel_init.s中。主要是完成存储器的配置。由于我没有外部存储器,所以不用怎么修改。
relocate:
adr r0, _start
ldr r1, _TEXT_BASE
cmp r0, r1
beq stack_setup
该段首先将当前_start的地址传递到r0中,然后将基地址_TEXT_BASE传递到r1中,比较相等的话就跳入堆栈中,如果不相等继续执行下面的数据拷贝。
如果还在Flash中的话,这时的_TEXT_BASE为0x20000000而_start地址为0x00000000,所以不相等,进行数据拷贝(在下面详细介绍)。
如果在RAM中运行了,也就是数据已经拷贝到RAM中了,那这时的_start就变成了0x20000000,这样和_TEXT_BASE相等,不再进行数据拷贝,直接跳转到数据堆栈。
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2
add r2, r0, r2
copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble copy_loop
这两段由于关联性很大,所以放在一起讲有助理解。本段的作用主要是将flash中的数据拷贝到ram中。前一小段将_armboot_start也就是起始地址放入r2,数据段起始地址_bss_start放入r3,通过sub r2, r3, r2得到了_armboot_start段的大小,然后加上r0得到的就是_bss_start段实际的起始地址,也就是我们到拷贝到的目标地的那段起始地址。
下面一段就是通过ldmia和stmia进行拷贝,这两个指令的用法见书本。下面介绍一下过程:
开始时,r0=0x00000000,r1=0x00000000,r2=0x1FFDE000
然后每次比较r0和r2只要不相等就重复将r1开始的寄存器数据拷贝到r3-r10中,然后r0增加0x20,不段的拷贝,直到r0和r2相等,说明所有的都已拷贝完。
adr r0, real_vectors
add r2, r0, #1024
ldr r1, =0x20010000
add r1, r1, #0x08
vector_copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble vector_copy_loop
这一段是拷贝中断数据,过程和上面数据拷贝类似。其中需要注意的是0x20010000
,该参数需要根据自己的芯片进行修改,因为这个中断数据的拷贝起始地址,不能与前面的数据拷贝重叠,这个要非常小心,要先分析前面数据一共存放了多少空间。具体的数据在文件结尾:
real_vectors:
b reset
b undefined_instruction
b software_interrupt
b prefetch_abort
b data_abort
b not_used
b irq
b fiq
undefined_instruction:
mov r6, #3
b reset
software_interrupt:
mov r6, #4
b reset
prefetch_abort:
mov r6, #5
b reset
data_abort:
mov r6, #6
b reset
not_used:
/* we *should* never reach this */
mov r6, #7
b reset
irq:
mov r6, #8
b reset
fiq:
mov r6, #9
b reset
这些都是发生中断时的跳转语句,这里不用修改,所以就不解析了。
stack_setup:
ldr r0, _TEXT_BASE
sub r0, r0, #CFG_MALLOC_LEN
sub r0, r0, #CFG_GBL_DATA_SIZE
sub sp, r0, #12
该段就是在进行堆栈了,其中CFG_MALLOC_LEN、CFG_GBL_DATA_SIZE在include/config/mycoard.h中定义,需要根据不同的芯片进行修改。一般情况下ram空间很大的话就不用改了,基本没什么问题。
ldr pc, _start_armboot
_start_armboot: .word start_armboot
再来这两句将_start_armboot的地址载入pc,也就是跳转到该地址。那这个地址在哪定义呢?就是下面一句,这句的汇编代码是andcs r0,r0,r4,lsl #7 根据此得出跳转的_start_armboot地址。接下来就到board.c中执行c代码。
想要更好的理解start.s,需要大家亲自去调试,一定会有很大的收获的!