编译环境:ubuntu9.10
uboot版本:u-boot-2012.04.01
开发平台:mini2440
u-boot-2012.04.01启动流程:
1. 设置为管理模式
2. 关闭看门狗
3. 关中断
4. 设置时钟分频比
5. 跳转到cpu_init_crit执行
a. 禁止MMU、关闭caches
b. 跳入到lowlevel_init执行
*. 初始化内存控制器
6. 设置堆栈,为跳入board_init_f做准备
a. 跳入board_init_f执行
*. board_init_f函数中进行了各种初始化,并重新设置了堆栈,为重定位代码做准备
*. 调用relocate_code(addr_sp, id, addr)进行代码重定位
7. 清bss段
8. 跳转到第二阶段board_init_r执行
自此,Uboot第一阶段执行完毕。
我们知道重定位代码是在board_init_f函数中调用relocate_code(addr_sp, id, addr)函数完成的。relocate_code是用汇编实现的,根据ATPCS约定addr_sp、id、addr分别存放在r0,r1,r2寄存器中。那么这个三个参数的值是多少呢?这需要分析在relocate_code之前的代码。board_init_f是用C语言编写的,从汇编中调用C函数,必须首先设置好堆栈。
/* Set stackpointer in internal RAM tocall board_init_f */
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)//sp =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7//清除sp低3位,也就是8字节对齐
ldr r0, 0x00000000//r0 = 0
bl board_init_f
其中宏定义CONFIG_SYS_INIT_SP_ADDR于文件include/configs/smdk2410.h中定义,原型为:
#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE+ 0x1000 - \
GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1
#define PHYS_SDRAM_1 0x30000000
GENERATED_GBL_DATA_SIZE暂时未找到定义处,但它是全局数据gd_t结构体的大小。
跳入board_init_f之前内存分布如下:
接下来需要找出addr和addr_sp的值是什么,代码摘抄如下:
addr =CONFIG_SYS_SDRAM_BASE + gd->ram_size; // 1. addr = 0x3400,0000 addr -= (4096 * 4); // 2. 16k保留给TLB, addr = 0x33ff,c000 addr &= ~(0x10000 - 1); // 3. 低16位清零,64k对齐,addr = 0x33ff,0000 addr &= ~(4096 - 1); // 4. 4k对齐,addr = 0x33ff,0000 addr -= gd->mon_len; // 5. gd->mon_len = _bss_end_ofs, bss_end_ofs定义为__bss_end__ -_start,表示整 个Uboot代码大小 addr &= ~(4096 - 1); // 6. 4k对齐
addr_sp =addr - TOTAL_MALLOC_LEN; // 7. 留出大小为TOTAL_MALLOC_LEN内存,分配给堆 addr_sp -= sizeof (bd_t); // 8. 留出bd_t大小的内存 bd = (bd_t *) addr_sp; // 9. bd指向此时add_sp addr_sp -= sizeof (gd_t); // 10. 留出gd_t大小的内存 id = (gd_t *) addr_sp; // 11. gd指向此时add_sp addr_sp -= 12; // 12. 留出12Bytes空间 addr_sp &= ~0x07; // 13. addr_sp 8字节对齐
另外board_init_f代码后面有memcpy(id, (void *)gd, sizeof(gd_t)),该代码会把之前设置过的gd全局变量(保存在调用board_init_f之前设置的栈中,也就是上图所示红色SP所指位置)原原本本拷贝到id所指向的内存中。此外,gd全局变量还包含有指向bd变量的指针,代码为gd->bd = bd。
知道addr_sp、id、addr三个参数的含义后,接下来分析relocate_code函数的具体过程。
relocate_code: mov r4, r0 /* 保存addr_sp到r4 */ mov r5, r1 /* 保存gd地址到r5,addr指向uboot在内存中的存放地址 */ mov r6, r2 /* 保存addr地址到r6,即需要拷贝到的位置 */ /* Set up the stack */ stack_setup: mov sp, r4 /* sp = addr_sp, 即栈指针 */ adr r0, _start /* r0 = 0x0000,0000,即r0指向代码段起始地址 */ cmp r0, r6 /* 判断代码是否需要拷贝 */ beq clear_bss /* 不需要拷贝,则跳转到clear_bss */ mov r1, r6 /* r1为uboot需要拷贝到的位置,即addr */ ldr r3, _bss_start_ofs // r3为除bss段外的代码大小,或者称为偏移量,定义为__bss_start - _start add r2, r0, r3 /* r2等于拷贝前代码的结束地址 */ copy_loop: ldmia r0!, {r9-r10} /* copy from source address [r0] */ stmia r1!, {r9-r10} /* copy to target address [r1] */ cmp r0, r2 /* until source end address [r2] */ blo copy_loop /* BLO指令:小于(无符号数) */
上面的汇编实现了把代码从nor flash(不支持nand flash,因为nand flash不能像内存一样读)中。该版本的uboot重定位和之前版本的重定位的区别是,该uboot的链接地址不是通过_TEXT_BASE指定的,而是根据编译后uboot文件大小来确定的。所以,在上面代码执行完毕后,实际重定位的工作并未全部完成,还需要重定位诸如.rel.dyn、.dynsym等各种段,这部分留待下次分析。