注:本文是学习朱老师课程整理的笔记,基于uboot-1.3.4和s5pc11x分析。
前部分的分析见uboot之start.s分析1
/* Go setup Memory and board specific bits prior to relocation.*/
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
sub sp, sp, #12 /* set stack */
mov fp, #0
bl lowlevel_init /* go setup pll,mux,memory */
这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。
在调用函数前初始化栈,主要原因是BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时,第一层的返回地址就丢了。
关于lowlevel_init函数的分析见start.s之lowlevel_init分析
/* To hold max8698 output before releasing power on switch,
* set PS_HOLD signal to high
*/
ldr r0, =0xE010E81C /* PS_HOLD_CONTROL register */
ldr r1, =0x00005301 /* PS_HOLD output high */
str r1, [r0]
在lowlevel_init中其实已经设置过供电锁存。
/* get ready to call C functions */
ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
之前在调用lowlevel_init程序前设置过1次栈,那时候因为DDR尚未初始化,因此程序执行都是在SRAM中,所以在SRAM中分配了一部分内存作为栈。
本次设置栈是因为DDR已经被初始化了,因此要把栈挪移到DDR中,所以要重新设置栈;这里实际设置的栈的地址是33E00000,刚好在uboot的代码段的下面紧挨着。
为什么放在uboot代码的下面呢?因为arm是满减栈,放在下面就不会覆盖掉uboot的代码。
为什么要再次设置栈?DDR已经初始化了,已经有大片内存可以用了,没必要再把栈放在SRAM中,栈放在那里要注意不能使用过多的栈否则栈会溢出,我们及时将栈迁移到DDR中也是为了尽可能避免栈使用时候的小心翼翼。
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq after_copy /* r0 == r1 then skip flash copy */
在lowlevel_init中判断过一次重定位,这里再次用相同的代码判断运行地址是在SRAM中还是DDR中,不过本次判断的目的不同,上次判断是为了决定是否要执行初始化时钟和DDR的代码,这次判断是为了决定是否进行uboot的relocate。
冷启动时,当前情况是uboot的前一部分(16kb或者8kb),开机自动从SD卡加载到SRAM中运行,uboot的第二部分(其实第二部分是整个uboot)还躺在SD卡的某个扇区开头的N个扇区中。
此时uboot的第一阶段已经即将结束了,结束之前要把第二部分加载到DDR中链接地址处(33e00000),这个加载过程就叫重定位。
/* SD/MMC BOOT */
cmp r2, #0xc
moveq r3, #BOOT_MMCSD
……
/*将#BOOT_MMCSD写入了INF_REG3寄存器中存储着*/
ldr r0, =INF_REG_BASE
str r3, [r0, #INF_REG3_OFFSET]
……
/*再将INF_REG3_OFFSET读出来,再和#BOOT_MMCSD去比较,确定是从MMCSD启动*/
ldr r0, =INF_REG_BASE
ldr r1, [r0, #INF_REG3_OFFSET]
cmp r1, #BOOT_MMCSD
beq mmcsd_boot
bl movi_bl2_copy
真正的重定位是通过调用movi_bl2_copy函数完成的,在uboot/cpu/s5pc11x/movi.c中是一个C语言的函数
ch = *(volatile u32 *)(0xD0037488);
copy_sd_mmc_to_mem copy_bl2 =
(copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));
……
if (ch == 0xEB000000) {
ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
……
if (ch == 0xEB200000) {
ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
0xD0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为0xEB000000;从SD2通道启动时,这个值为0xEB200000。
copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0);
分析参数:
2:表示通道2;
MOVI_BL2_POS:是uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot时烧录的位置相同;
MOVI_BL2_BLKCNT:是uboot的长度占用的扇区数;
CFG_PHY_UBOOT_BASE:是重定位时将uboot的第二部分复制到DDR中的起始地址(33E00000)。
after_copy:
#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
/* enable domain access */
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0 @load domain access register
cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们通过mrc和mcr指令来访问这些寄存器。所谓的操作cp协处理器其实就是操作cp15的这些寄存器。
c3寄存器在mmu中的作用是控制域访问。域访问是和MMU的访问控制有关的。
/* Set the TTB register */
ldr r0, _mmu_table_base
ldr r1, =CFG_PHY_UBOOT_BASE
ldr r2, =0xfff00000
bic r0, r0, r2
orr r1, r0, r1
mcr p15, 0, r1, c2, c0, 0
转换表放置在内存中的,放置时要求起始地址在内存中要xx位对齐。转换表不需要软件去干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时会自动去查转换表。
关于_mmu_table_base所指向的内容见lowlevel_init之mmu_table分析
/* Enable the MMU */
mmu_on:
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1
mcr p15, 0, r0, c1, c0, 0
cp15的c1寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。开启MMU之后上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。
MMU就是memory management unit,内存管理单元。MMU实际上是SoC中一个硬件单元,它的主要功能就是实现虚拟地址到物理地址的映射。
MMU在CP15协处理器中进行控制,也就是说要操控MMU进行虚拟地址映射,方法就是对cp15协处理器的寄存器进行编程。
关于地址映射
cache的工作和虚拟地址映射有关系。
cache是快速缓存,意思就是比CPU慢但是比DDR块。CPU嫌DDR太慢了,于是乎把一些DDR中常用的内容事先读取缓存在cache中,然后CPU每次需要找东西时先在cache中找。如果cache中有就直接用cache中的;如果cache中没有才会去DDR中寻找。
关于MMU的分析见arm MMU详解
再次设置栈
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#if defined(CONFIG_USE_IRQ)
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
#endif
这次设置栈还是在DDR中,之前虽然已经在DDR中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。
我们实际将栈设置在uboot起始地址上方2MB处,这样安全的栈空间是:2MB-uboot大小-0x1000=1.8MB左右。这个空间既没有太浪费内存,又足够安全。
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
bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
start_armboot是在uboot/lib_arm/board.c中。
这是一个C语言实现的函数。这个函数就是uboot的第二阶段。这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处。
远跳转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。
到此uboot启动第一阶段的代码分析完毕。
总结:uboot的第一阶段做了哪些工作
(1)构建异常向量表
(2)设置CPU为SVC模式
(3)关看门狗
(4)开发板供电置锁
(5)时钟初始化
(6)DDR初始化
(7)串口初始化并打印”OK”
(8)重定位
(9)建立映射表并开启MMU
(10)跳转到第二阶段
第二阶段代码分析见uboot之start_armboot分析1