uboot的relocate功能代码是分析uboot的必经之路,非常巧妙,也非常经典,当然理解起来也有些麻烦:
.globl _start
_start: b reset
......
......
......
......
......
_TEXT_BASE:
.word TEXT_BASE
.globl _armboot_start
_armboot_start:
.word _start
reset:
......
......
......
......
......
relocate: /* 重新搬迁加载 U-Boot 到 RAM */
adr r0, _start /* adr命令将标志_start基于PC的相对地址存放到r0中 */
ldr r1, _TEXT_BASE /* 将标志_TEXT_BASE的内容放到r1中,其值在上面用.word 赋值了,这个很巧妙,很规范 */
cmp r0, r1 /* 比较r0与r1,就能够判断当前是在flash中运行,还是在RAM中运行*/
beq stack_setup //如果r0 = r1,表示是在内存中,属于调试模式,就直接进行堆栈初始化了
ldr r2, _armboot_start //_armboot_start用.word 在上面定义了,就是_start的地址,赋值给r2
ldr r3, _bss_start //将_bss_start地址赋值给r3,其实就是u-boot的结尾
sub r2, r3, r2 // r2 = r3 - r2,其实就是要搬迁的 uboot代码的总长度(bss - start)
add r2, r0, r2 /* r2 = r0 + r2,这里就计算出了要搬迁的uboot代码结尾的 地址,跟上面的长度是两码事儿*/
copy_loop: //执行循环复制
ldmia r0!, {r3-r10} /* ldmia 批量读取,从首地址r0,批量读8个字,共32个字节到r3~r10,ro!表示自动递增 */
stmia r1!, {r3-r10} /* stmia 批量写内存,将r3~r10 批量写入到首地址r1中,由上分析,r1就是执行了内存首地址,自递增*/
cmp r0, r2 /* 判断 r0 首地址,是否与 末地址相等重合 */
ble copy_loop //如果不等,表示没有复制完,重新跳转到copy_loop,相等则下一步
......
......
......
......
......
...... //各种堆栈初始化
ldr pc, _start_armboot //跳入到第二阶段,执行_start_armboot函数,这个时候由于已经将uboot搬迁到内存中,所以下面的
//所有命令已经是在RAM中运行了。
_start_armboot: .word start_armboot
总结:这里面有两种理解方式,
(1)cpu在上电重启后,代码是在Nor Flash中,我们总认为程序代码只有在RAM中才能执行,所以就不明白,为什么上电后,程序代码还能运行,这个概念是有问题的,Nor Flash或Nand Flash虽然是外部flash,虽然相对于RAM执行速度慢,但是还是可以执行代码的,此时的程序执行速度可以理解为“走起来”,所以需要尽可能的先去初始化内存等必要外围,然后立即将uboot代码搬迁到RAM中,再从RAM中进行重新运行。这样程序就能“跑起来”。
(2)CPU中有一块自带的SRAM,大小为4Kb,CPU在设计时,在上电后,会自动将Flash中前4KB的代码搬迁到这4Kb的内存中,然后程序也能在这块“小内存”中快速执行那些特别紧迫,特别必要的初始化动作,然后尽快把uboot搬迁到 外部RAM中,让程序能够大步跑起来。这里的 4Kb小内存,有人成为垫脚石,反正就是类似的意思。
最后,这里所有的实现,都用了adr命令的巧妙,它能自动的根据当前PC的地址,找到对应的标号,相当于反推判断当前程序是在RAM中运行,还是在Flash中运行,以便后面进行判断,然后进行copy动作。