(系统移植)4 U-boot启动流程

资料来源正点原子嵌入式Linux

目录

链接脚本lds

Uboot启动流程

程序调用图

board_init_f:

relocate_code

relocate_vectors

board_init_r

run_main_loop

cmd_process


链接脚本lds

u-boot.lds中给出了U-boot的总结架构。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {
  *(.__image_copy_start)  //0X87800000
  *(.vectors)             //0X87800000
  arch/arm/cpu/armv7/start.o (.text*) //将start.s编译出来的代码放在这里
  *(.text*)               //text段结束
 }
 . = ALIGN(4);
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 . = ALIGN(4);
 .data : {
  *(.data*)
 }
 . = ALIGN(4);
 . = .;
 . = ALIGN(4);
 .u_boot_list : {
  KEEP(*(SORT(.u_boot_list*)));
 }
 . = ALIGN(4);
 .image_copy_end :
 {
  *(.__image_copy_end)
 }
 .rel_dyn_start :
 {
  *(.__rel_dyn_start)
 }
 .rel.dyn : {
  *(.rel*)
 }
 .rel_dyn_end :
 {
  *(.__rel_dyn_end)
 }
 .end :
 {
  *(.__end)
 }
 _image_binary_end = .;
 . = ALIGN(4096);
 .mmutable : {
  *(.mmutable)
 }
 .bss_start __rel_dyn_start (OVERLAY) : {
  KEEP(*(.__bss_start));
  __bss_base = .;
 }
 .bss __bss_base (OVERLAY) : {
  *(.bss*)
   . = ALIGN(4);
   __bss_limit = .;
 }
 .bss_end __bss_limit (OVERLAY) : {
  KEEP(*(.__bss_end));
 }
 .dynsym _image_binary_end : { *(.dynsym) }
 .dynbss : { *(.dynbss) }
 .dynstr : { *(.dynstr*) }
 .dynamic : { *(.dynamic*) }
 .plt : { *(.plt*) }
 .interp : { *(.interp*) }
 .gnu.hash : { *(.gnu.hash) }
 .gnu : { *(.gnu*) }
 .ARM.exidx : { *(.ARM.exidx*) }
 .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}

可以看出,启动从_start开始。

Uboot启动流程

程序调用图

(系统移植)4 U-boot启动流程_第1张图片

 

接下来介绍main调用的board_init_f、 relocate_code、relocate_vectors 和 board_init_r四个主要函数

 

board_init_f:

board_init_f主要对DDR进行初始化,并且在DDR中分配好了重定位代码各个部分的地址区域。

DDR地址从0x8000000到0XA0000000,共计0x20000000,512MB。

Uboot要将Uboot代码重定位到DDR区域最后的部分,留出前面的位置(开始存放在0x87800000),内存分配好后的分布图:

(系统移植)4 U-boot启动流程_第2张图片

uboot 重定位后的偏移为 0X18747000,重定位后的新地址为0X9FF4700,新的 gd 首地址为 0X9EF44EB8,最终的 sp 为 0X9EF44E90。
 

 

relocate_code

relocate_code 函数是用于代码拷贝的。
拷贝方式:

ldr	r1, =__image_copy_start	/* r1 <- SRC &__image_copy_start */
	subs	r4, r0, r1		/* r4=r0-r1=0X9FF47000-0X87800000=0X18747000,因此 r4 保存偏移量r4 <- relocation offset */
	beq	relocate_done		/* skip relocation */
	ldr	r2, =__image_copy_end	/*r2=0x878668f4   r2 <- SRC &__image_copy_end */
/*将uboot代码段拷贝到目标地址,从__image_copy_start到__image_copy_end */
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

再拷贝代码时候,有些代码调用其他地址的函数,变量,那么这些变量函数的地址变化了,调用不是会出错吗?所以,为了解决这一问题,引出了rel.dyn 段。

首先来看一个简单函数:

 static int rel_a = 0;

void rel_test(void)
 {
 rel_a = 100;
 printf("rel_test\r\n");
 }

该函数是在u-boot的board_init()初始化中引用。查看反汇编:

1 87804184 :
2 87804184: e59f300c ldr r3, [pc, #12] ; 87804198 
3 87804188: e3a02064 mov r2, #100 ; 0x64
4 8780418c: e59f0008 ldr r0, [pc, #8] ; 8780419c 
5 87804190: e5832000 str r2, [r3]
6 87804194: ea00d668 b 87839b3c 
7 87804198: 8785da50 ;  instruction: 0x8785da50
8 8780419c: 878426a2 strhi r2, [r4, r2, lsr #13]
9
10 878041a0 :
11 878041a0: e92d4010 push {r4, lr}
12 878041a4: ebfffff6 bl 87804184 
13
14 ......
15
16 8785da50 :
17 8785da50: 00000000 andeq r0, r0, r0

可以看到board_init在调用rel_test时利用了bl跳转指令(12行),bl是相对跳转指令,也就是说bl跳转到后面的地址时是根据偏移来跳转的。现在bl指令所在是地址878041a4,跳转到87804184,bl是根据两个地址差值进行跳转。如果878041a4变化了,那后面的跳转地址也会跟着变化。

再来看变量引用,rel_test引用变量rel_a

r3=pc+8+12=[0x87804198](ARM中pc的值是当前位置+8)=8785da50,8785da50正是rel_a的地址。

r2=100

----

将100存入地址8785da50中变量rel_a中。

可以看出,函数 rel_test 对变量 rel_a 的访问没有直接进行,而是使用了一个第三方偏移地址 0X87804198,专业术语叫做 Label。

UBOOT重定位以后,偏移地址0x18747000,那么上述代码的地址变为:

1 ‭9FF4B184‬ :
2 ‭9FF4B184‬: e59f300c ldr r3, [pc, #12] ; 87804198 
3 ‭9FF4B188‬: e3a02064 mov r2, #100 ; 0x64
4 ‭9FF4B18c‬: e59f0008 ldr r0, [pc, #8] ; 8780419c 
5 ‭9FF4B190‬: e5832000 str r2, [r3]
6 ‭9FF4B194: ea00d668 b 87839b3c 
7 ‭9FF4B198: 8785da50 ;  instruction: 0x8785da50
8 ‭9FF4B19c: 878426a2 strhi r2, [r4, r2, lsr #13]
9
10 ‭9FF4B1A0‬ :
11 ‭9FF4B1A4: e92d4010 push {r4, lr}
12 ‭9FF4B1A8: ebfffff6 bl 87804184 
13
14 ......
15
16 ‭9FFA4A50‬ :
17 ‭9FFA4A54: 00000000 andeq r0, r0, r0


Label的地址变为0X9FF4B184+8+12=0X9FF4B198,但是这时候只是地址变化了,Label中的值,也就是rel_a的地址并没有变化。Label中的值应该是9FFA4A50才对,也就是原来的Label值8785da50+偏移地址18747000.

可以看出, uboot 对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码(bl,label),在使用 ld 进行链接的时候使用选项“ -pie”生成位置无关的可执行文件。-pie会生成rel.dyn段。
对应的反汇编:

1 Disassembly of section .rel.dyn:
2 
3 8785da44 <__rel_dyn_end-0x8ba0>:
4 8785da44: 87800020 strhi r0, [r0, r0, lsr #32]
5 8785da48: 00000017 andeq r0, r0, r7, lsl r0
6 ......
7 8785dfb4: 87804198 ;  instruction: 0x87804198
8 8785dfb8: 00000017 andeq r0, r0, r7, lsl r0

先来看一下.rel.dyn 段的格式,类似第 7 行和第 8 行这样的是一组,也就是两个 4 字节数据为一组。高 4 字节是 Label 地址标识 0X17,低 4 字节就是 Label 的地址,首先判断 Label 地址标识是否正确,也就是判断高 4 字节是否为 0X17,如果是的话高 4 字节就是 Label 值。第 7 行值为 0X87804198,第 8 行为 0X00000017,说明第 7 行的 0X87804198 是个 Label,这个正是示例代码  中存放变量 rel_a 地址的那个 Label地址。前面给出的解决方案是Label的值加上偏移地址就可以了,所以在重定位rel.dyn段时,代码如下:

	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}		/*每次读取4字节数据存放到r0(Label地址),r1(Label标志) */
	and	r1, r1, #0xff   //取低8位
	cmp	r1, #23			/* 判断r1是不是0x17 */
	bne	fixnext			//如果不等,则不是label。

	/* 如果相等,则是label,需要加上偏移*/
	add	r0, r0, r4   //r0=r0+r4偏移量=重定位后的Label地址0X87804198+0X18747000=0X9FF4B198
	ldr	r1, [r0] //读取重定位后labal中的labal值,也就是还没有定位的rel_a的地址0X8785DA50
	add	r1, r1, r4//label值加上偏移量, rel_a 重 定 位 后 的0X8785DA50+0X18747000=0X9FFA4A50
	str	r1, [r0]//重新写入r0=0X9FF4B198
fixnext:
	cmp	r2, r3  //比较r2,r3看rel.dyn是否重定位完成
	blo	fixloop //不相等,跳回去继续执行

 


relocate_vectors

重定位中断向量表

imx6ull的中断向量表重定位只需要向VBAR寄存器中,VBAR是c15协寄存器中目标设置为CRn=c12, opc1=0, CRm=c0,opc2=0。

	ldr	r0, [r9, #GD_RELOCADDR]	/* r0 = gd->relocaddr */
	mcr     p15, 0, r0, c12, c0, 0  /* 将重定位的地址写到VBAR寄存器*/

 

board_init_r

board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r 来完成的
 

 

run_main_loop

boot 启动以后会进入 3 秒倒计时,如果在 3 秒倒计时结束之前按下按下回车键,那么就会进入 uboot 的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux 内核 , 这 个 功 能 就 是 由 run_main_loop 函 数 来 完 成 的 。

run_main_loop

              -> main_loop

                     -> bootdelay_process 获取bootdelay的值,然后保存到stored_bootdelay

全局变量里面,获取bootcmd环境变量值,并且将其

返回      

-> autoboot_command 参数是bootcmd的值。

       -> abortboot 参数为boot delay,此函数会处理倒计时

              -> abortboot_normal 参数为boot delay,此函数会处理倒计时

-> cli_loop  uboot命令模式处理函数。

       -> parse_file_outer

              -> parse_stream_outer

                     -> parse_stream 解析输入的字符,得到命令

                     -> run_list 运行命令

                            -> run_list_real

                                   -> run_pipe_real

                                          -> cmd_process  处理命令,也就是执行命令

cmd_process

cmd_process用来处理命令。

Uboot使用U_BOOT_CMD来定义一个命令。CONFIG_CMD_XXX来使能uboot中的某个命令。 U_BOOT_CMD最终是定义了一个cmd_tbl_t类型的变量,所有的命令最终都是存放在.u_boot_list段里面。cmd_tbl_t的cmd成员变量就是具体的命令执行函数,命令执行函数都是do_xxx。

       cmd_process

              ->find_cmd  从.u_boot_list段里面查找命令,当找到对应的命令以后以返回值的

形式给出,为cmd_tbl_t类型

              ->cmd_call

                     ->cmdtp->cmd  直接引用cmd成员变量

 

bootz启动linux

(系统移植)4 U-boot启动流程_第3张图片

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