这一篇开始做重定位,至于为何要做重定位,以及如何实现重定位,可以参考下面这两篇博文:
http://blog.csdn.net/u012176730/article/details/53940113
http://blog.csdn.net/u012176730/article/details/53941412
这里简单的在解释下为何要做重定位:
1. 因为在编译、连接阶段,用户实际上是不知道uboot程序最终会被运行在SDRAM的哪一个区域内的,也就是所谓的实际运行地址并未可知。但是为了不失一般性,一般会在编译、连接的时候暂时确定运行地址为SDRAM的低位,即将TEXT_BASE设定为SDRAM的低位位置。这样在SPL阶段的最后,会将uboot程序从mmc或者其他flash中拷贝到SDRAM的低位位置(由TEXT_BASE确定),然后开始运行uboot程序。
2. 但是以上运行地址并不是一个最优化的手段。为了最大化的利用SDRAM的空间,一般会在SDRAM的高位人为的划分出一块区域作为uboot以及其他数据的存放地点,那么问题就来了,在编译和连接时确定的TEXT_BASE会和高位uboot区域不一样,这将会导致位置相关的程序好变量都不能够正确执行
3. 所以要做重定位,所以重定位的目的就是,让一段代码能够在SDRAM的任何位置运行
所以重定位一般包含两个阶段:
1. 拷贝,就是将源数据拷贝到目标区域,在这里就是将uboot的代码段从SDRAM的低位拷贝到SDRAM的高位
2.重定位,一般就是给位置相关的转跳加上一个重定位偏移地址!
以下是实现,该源文件在archarmlib
elocate.S中定义:
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start *///wlg: r0 now is the address of begining of uboot in sdram
subs r4, r0, r1 /* r4 <- relocation offset */ //wlg: if the beginning of ld address is eaque to r0
beq relocate_done /* skip relocation */ //wlg: skip the relocation
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */ //wlg: otherwise, copy the data and do relocate
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop //wlg: complete copying the data to SDRAM, we also need relocate!
/*
* fix .rel.dyn relocations
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop
relocate_done:
#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif
/* ARMv4- don't know bx lr but the assembler fails to see that */
#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif
ENDPROC(relocate_code)
一般看注释就明白了,需要注意的就是__rel_dyn_start和__rel_dyn_end两个地址变量。这两个地址指向了一段数据区域,这个数据区域中保存着所有地址相关变量的地址。假如说我们定义了一个全局变量a,而a的地址就是0x0010。那么通过访问0x0010就能够寻址找到变量a。假如此时a变量被移动到了0x0040这个地方,那么很明显原来的地址0x0010已经失效,需要在0x0010的基础上加上offset,即加上0x0030.所以重定位的意义就是给所有的地址相关的变量加上这个offset,使得寻址能够正常访问。那么另一个问题就是,a的地址0x0010本身也是一个指针数据,它本身也被保存某个区域中内存中,假如这个地址就是0x1010(随意定义的)。那么很明显,我们要访问a,就必须要先知道a的地址,那么a的地址0x0010就被保存在0x1010中。那么访问流程应该是:
1. cpu先访问0x1010里的数据,获得a的指针是0x0010;
2. cpu再访问0x0010,最终实现了访问变量a本身,可以对数据a进行读写。
所以这里有两个关键的地址,地址0x1010(a指针变量保存的地址)和地址0010(a指针变量所指向的地址,就是a变量的保存地址).
假如我们将代码拷贝到了另一个区域,使得此时的a的地址是0x0040(新a变量的实际保存地址),同时指向a的新指针也被拷贝到了0x1040(“a”指针变量保存的地址,a加了引号是因为这个时候的a指针仍是指向旧地址,0x0010)。
所以重定位的目的就是为了修改上面0x1040里面所保存的a指针变量,使其加上一个offset,让其由0x0010变成0x0040,,也就是让其指向新的a变量!
而 __rel_dyn_start和__rel_dyn_end里面保存了所有指针变量的地址,就是保存了类似0x1010这类数据。
所以重定位实际就是如下步骤:
1. 从__rel_dyn_start和__rel_dyn_end里取出0x1010(数据,保存到r0。同时这里还取出了r1,r1里面的数据作为判断数据(是否等于23),来决定r0里的数据是否需要重定位,这里加入时需要重定位的)
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
2. 将0x1010加上offset,得到0x1040(数据,保存在r0)
add r0, r0, r4
3. 根据0x1040(将r0其作为地址)可得到新指针值为0x0010(数据,仍指向旧a变量,保存在r1)
ldr r1, [r0]
4. 将0x0010(数据,仍指向旧a变量)加上offset,得到0x0040(数据,指向新a变量,保存在r1)
add r1, r1, r4
5. 将0x0040保存到0x1040(将r1保存到r0为地址的内存里)
str r1, [r0]
6. 完成a变量的重定位!
下面是图片的功能演示:
以上就完成了重定位,自此以后,uboot就可以运行在SDRAM的高位。还记得上一篇的here嘛?我们已经here的地址(SDRAM低位的here地址)放置到了ldr中,并已经给其加上了offset,此时的ldr里面保存的就是here在SDRAM高位的地址。所以重定位的最后是一句:
bx lr
实际上就是为了实现:接下来运行在SDRAM高位里的here(因为前面已经完成了copy,在SDRAM高位的uboot区域也有了uboot的程序备份)
回到here,其之后的代码展示如下:
here:
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
ldr r0, =__bss_start /* this is auto-relocated! */
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
clbss_l:cmp r0, r1 /* while not at end of BSS */
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
bl coloured_LED_init
bl red_led_on
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */ /*wlg: r0 and r1 will be parament of board_init_r*/
ldr pc, =board_init_r /* this is auto-relocated! wlg: /arch/arm/lib/board.c, run on SDRAM*/
/* we should not return here. */
回到here后,先后执行了以下功能:
1. c_runtime_cpu_setup
2. 清除SDRAM高位的uboot区域中的bss部分为0。注意_bss_start已经被重定位!
3. LED灯的操作
4. 将r9(指向SDRAM高位的gd_t区域)复制给r0,作为C语言函数board_init_r的第一个变量
5. 将SDRAM高位中的gd_t结构体中的dest_addr赋值给r1,作为C语言函数board_init_r第二个变量。该变量实际指向了uboot区域的开始
6. 最后开始跳到C语言函数board_init_r,其在arch/arm/lib/board.c
这个函数将会和硬件相关,其主要完成硬件的初始化,包括键盘、网络、串口、显示等等。
这个函数包含了太多的硬件功能,可能会在以后的博客中慢慢介绍,然后函数跳到了main_loop()函数中,开始倒计时,在加载内核的镜像。这部分也后再后期慢慢丰富起来
以后的博客会将注意力从uboot源代码的分析专注到board_init_r中各个函数的具体实现,为后期实现各种形式的内核调试和加载做准备!