要分析boot启动流程,首先要找到程序入口地址,可以通过编译uboot生成u-boot.lds,通过查看链接脚本u-boot.lds知道入口点是 arch/arm/lib/vectors.S 文件中的_start。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)//代码当前入口点: _start, _start 在文件arch/arm/lib/vectors.S 中有定义
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {
  *(.__image_copy_start)//uboot 拷贝的首地址
  *(.vectors)//vectors 段保存中断向量表
  arch/arm/cpu/armv7/start.o (.text*)//将 arch/arm/cpu/armv7/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)//uboot 拷贝的结束地址
 }
 .rel_dyn_start :
 {
  *(.__rel_dyn_start)//.rel.dyn 段起始地址
 }
 .rel.dyn : {
  *(.rel*)
 }
 .rel_dyn_end :
 {
  *(.__rel_dyn_end)//.rel.dyn 段结束地址
 }
 .end :
 {
  *(.__end)
 }
 _image_binary_end = .;//镜像结束地址
 . = ALIGN(4096);
 .mmutable : {
  *(.mmutable)
 }
 .bss_start __rel_dyn_start (OVERLAY) : {
  KEEP(*(.__bss_start));//.bss 段起始地址
  __bss_base = .;
 }
 .bss __bss_base (OVERLAY) : {
  *(.bss*)
   . = ALIGN(4);
   __bss_limit = .;
 }
 .bss_end __bss_limit (OVERLAY) : {
  KEEP(*(.__bss_end));//.bss 段结束地址
 }
 .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.*) }
}

1. uboot启动总体流程

在这里插入图片描述

uboot启动主要分为两部分arch级初始化和板级初始化。下面具体分析各个初始化。

2. arch级初始化

arch级初始化
如上图所示,入口点是 arch/arm/lib/vectors.S 文件中的_start。_start 开始的是中断向量表,跳转到 reset 函数里面, reset 函数在 arch/arm/cpu/armv7/start.S 里面。 reset 函数跳转到了 save_boot_params 函数,save_boot_params 函数又跳转到 save_boot_params_ret 函数,该函数主要分为五个功能:
①设置CPU处于SVC模式,并且关闭FIQ和IRQ两个中断;
②设置向量表重定位;
③设置cp15寄存器(Cache,MMU,TLBs);
④配置关键寄存器和初始化
⑤跳转_main,进入板级初始化

2.1 cpu_init_crit

cpu_init_crit

cpu_init_crit 内部仅仅是调用了函数 lowlevel_init,该函数主要用于设置sp指针和r9寄存器。函数 lowlevel_init 在文件arch/arm/cpu/armv7/ lowlevel_init.S 中定义。
① 设置SP指向CONFIG_SYS_INIT_SP_ADDR=0X0091FF00,流程见上图。可结合下图自己分析
在这里插入图片描述
② 设置SP 8字节对齐
③ 设置gd(global data)大小为248B
④ sp地址(0x0091FE08)保存在r9寄存器中
在这里插入图片描述
⑤将ip和lr入栈
⑥ 跳转s_init//什么都没有做
⑦ 将入栈的ip和lr出栈,并将lr赋给pc

3. 板级初始化

在这里插入图片描述
_main主要分为六个部分,简单来说主要是把iROM中的程序拷贝至DDR,然后载重定向,最后初始化各种外设。

3.1 board_init_f_alloc_reserve函数和board_init_f_resrve函数

在这里插入图片描述

board_init_f_alloc_reserve函数主要用于留出早期malloc和gd(global_data)内存区域;board_init_f_init_reserve函数主要功能初始化gd。两个函数主要功能如上图所示:
① 设置指针地址0X0091FF00;
② 减去0X400,248B。留出早期malloc内存区域和gd内存区域。再保留8B,以使得16字节对齐;
③ 初始化gd(清零),再做16字节对齐,最终sp指向0X0091FB00;

3.2 board_init_f

board_init_f函数主要用于初始化DDR,定时器,完成代码拷贝等
① 初始化外设;
② 初始化gd各个成员变量
commmon/board_f.c
board_init_f函数中的initcall_run_list函数主要用于调用一系列函数,值保存在 init_sequence_f函数中。
在这里插入图片描述
initcall_run_list函数的具体主要功能如下:可结合上图分析

1、gd->num_len=_bss_end-_start//uboot image大小,即代码长度,0X878A8E74-0x87800000=0XA8EF4
2initf_malloc()//gd->malloc=CONFIG_SYS_MALLOC_F_LEN=0X400,内存池大小为0x400
3arch_cpu_init()//初始化架构相关的内容,CPU级别操作
4initf_dm()//驱动模型一些初始化
5board_early_init_f()//初始化串口的IO配置(I.MX6ULL)
6timer_init()//初始化定时器(Cortex-A7内核)14init_baud_rate()//根据环境变量baudrate设置gd->baudrate=11520024dram_init()//设置gd->ram_size=512MB 0X2000 0000B44set_up_addr()//设置地址gd->ram_zise=0X2000 0000;gd->ram_top=0XA0000000
(0X80000000+0X2000 0000);gd->relocadder=0XA0000000(重定位后最高地址)...
48、reserve_uboot//gd->mon_len=0X8EF4;gd->start_addr_sp=
0X9FF47000;gd->relocadder=0X9FF47000//uboot重定位后的起始地址
49、reserve_malloc//TOTAL_MALLOC_LEN=CONFIG_SYS_MALLOC_LEN(0X10000000B=16MB)+
CONFIG_ENV_SIZE(0X2000=8K)
50reserve_board()//留出板子bd所占的内存区
52reserve_global_data()//留出gd所占的内存区55、resreve_stacks//留出栈空间,gd->start_addr_sp-16,然后16字节对齐
…
最终sp=gd->start_addr_sp=0X9EF44E9061、setup_reloc//设置gd其他一些成员变量,供后面定位使用,并且将以前的gd拷贝到gd->new_gd处

最终uboot重定位后偏移为0X18747000(0X9FF47000-0X87800000),新的gd首地址0X9EF44EB8,新的sp首地址0X9EF44E90

3.3 relocate_code

relocate_code函数主要用于代码拷贝,在relocate_code函数之前还有语句ldr r0,[r9,#GD_RELOCADDR],即r0=gd-> relocaddr= 0X9FF47000,uboot重定位后的首地址。
relocate_code函数在arch/arm/lib/relocate.S中,下面结合代码分析该函数

1、ldr r1,=__image_copy_start//r1=0X8780000源地址起始地址
2、subs r4, r0, r1//r4=0X9FF47000-0X87800000=0X18747000 偏移
3、ldr r2, =__image_copy_end//r2=0X8785dc6c源地址结束地址
4、copy_loop: //拷贝,将uboot从源地址0X8780000拷贝至0X9FF47000
	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

注意:直接将uboot从0X87800000拷贝至其他地方后,函数调用、全局变量引用可能会出问题。uboot采用位置无关码来处理该类问题(简单说采用相对地址寻址,而不是采用绝对地址寻址,并且重定位后需要将Label+offset)。在使用 ld 进行链接的时候使用选项”- pie” 生成位置无关的可执行文件。具体为.rel.dyn段。


5/*
	 * 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

第5段的程序分析如下:

1、r2=__rel_dyn_start,也就是.rel.dyn 段的起始地址。
2、r3=__rel_dyn_end,也就是.rel.dyn 段的终止地址。
3、从.rel.dyn 段起始地址开始,每次读取两个 4 字节的数据存放到 r0 和 r1 寄存器中, r0 存放低 4 字节的数据,也就是 Label 地址; r1 存放高 4 字节的数据,也就是 Label 标志。
4、r1 中给的值与 0xff 进行与运算,其实就是取 r1 的低 8 位。
5、判断 r1 中的值是否等于 23(0X17)//0X17就是判断是否是Label
6、如果 r1 不等于 23 的话就说明不是描述 Label 的,执行函数 fixnext ,否则的话继续执行下面的代码。
7、r0 保存着 Label 值, r4 保存着重定位后的地址偏移, r0+r4 就得到了重定位后的Label值。此时 r0 保存着重定位后的 Label 值。
8、读取重定位后 Label 所保存的变量地址,此时这个变量地址还是重定位前的地址,将得到的值放到 r1 寄存器中。
9、 r1+r4 即可得到重定位后的变量地址 。
10、重定位后的变量地址写入到重定位后的 Label 中。
11、比较 r2 和 r3,查看.rel.dyn 段重定位是否完成。
12、如果 r2 和 r3 不相等,说明.rel.dyn 重定位还未完成,因此跳到 fixloop 继续重定位 .rel.dyn 段。

3.3 relocate_vectors

relocate_vectors函数主要用于重定位向量表。relocate_vectors函数位于arch/arm/lib/relocate.S中,用于设置VBAR寄存器为重定位后的中断向量表起始地址

3.4 board_init_r

board_init_r函数与board_init_f函数类似,用于初始化一系列外设。 board_init_r函数位于commmon/board_r.c中,实际上
board_init_f函数并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数board_init_r 函数来完成。
board_init_r函数中含有init_sequence_r函数,该函数用于初始化序列。而init_sequence_r函数又含有run_main_loop函数,用于进入uboot命令模式或启动linux内核。run_main_loop函数中最重要的是main_loop函数,该函数位于common/main.c中。下面分析main_loop函数

3.4.1 main_loop()

该函数主要功能如下:

1、打印启动进度
2、设置环境变量
3cli_init()//初始化hush shell相关变量
4run_preboot_environment_command()//获取环境变量prebooot的内容,preboot是一些预启动命令,一般不使用该环境变量
5bootdelay_process()//获取bootdelay的值,然后保存到stored_bootdelay全局变量里面,获取bootcmd环境变量值,并且将其返回
6autoboot_command(bootcmd)
	---> abortboot(stored_bootdelay)//参数为bootdelay,该函数用于处理倒计时
		---> abortboot_normal(bootdelay)//参数为bootdelay,该函数用于处理倒计时
7cli_loop()//uboot命令处理函数 common/cli.c
	---> parse_file_outer()//common/cli_hush.c 
		---> rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);//hush shell 的命令解释器,负责接收命令行输入,然后解析并执行相应的命令
			---> parse_stream()//命令解析
			---> run_list()//运行命令
				---> run_list_real()
					---> run_pipe_real()
						---> 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。
							---> find_cmd()//从.u_boot_list段里查找命令,当找到对应的命令以cmd_tlb_t类型返回
							---> cmd_call()//cmdtp->cmd,直接引用cmd成员变量		

至此,uboot启动流程分析完毕,最后附上启动流程完整图
在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐