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

    这一篇开始做重定位,至于为何要做重定位,以及如何实现重定位,可以参考下面这两篇博文:

    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.重定位,一般就是给位置相关的转跳加上一个重定位偏移地址!

    以下是实现,该源文件在arch\arm\lib\relocate.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变量的重定位!

    下面是图片的功能演示:

嵌入式Linux学习:u-boot源码分析(7)--AM335X系列的2014.10版_第1张图片

    以上就完成了重定位,自此以后,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中各个函数的具体实现,为后期实现各种形式的内核调试和加载做准备!




你可能感兴趣的:(嵌入式Linux--uboot)