嵌入式Linux学习:u-boot源码分析(7)--AM335X系列的2014.10版

2019-07-12 19:04发布

    这一篇开始做重定位,至于为何要做重定位,以及如何实现重定位,可以参考下面这两篇博文:     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中各个函数的具体实现,为后期实现各种形式的内核调试和加载做准备!