分享

uboot启动流程详解

 新用户0118F7lQ 2023-07-07 发布于内蒙古

要分析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/arm/lib/vectors.S 文件中的start。start 开始的是中断向量表,跳转到 reset 函数里面,reset 函数在 arch/arm/cpu/armv7/start.S 里面。reset 函数跳转到了 savebootparams 函数,savebootparams 函数又跳转到 savebootparams_ret 函数,该函数主要分为五个功能:

①设置CPU处于SVC模式,并且关闭FIQ和IRQ两个中断;
②设置向量表重定位;
③设置cp15寄存器(Cache,MMU,TLBs);
④配置关键寄存器和初始化
⑤跳转_main,进入板级初始化

2.1 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=0XA8EF42、initf_malloc()//gd->malloc=CONFIG_SYS_MALLOC_F_LEN=0X400,内存池大小为0x4003、arch_cpu_init()//初始化架构相关的内容,CPU级别操作4、 initf_dm()//驱动模型一些初始化5、 board_early_init_f()//初始化串口的IO配置(I.MX6ULL)6、 timer_init()//初始化定时器(Cortex-A7内核)14、 init_baud_rate()//根据环境变量baudrate设置gd->baudrate=11520024、dram_init()//设置gd->ram_size=512MB 0X2000 0000B44、set_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)50、reserve_board()//留出板子bd所占的内存区52、reserve_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就是判断是否是Label6、如果 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.4 relocate_vectors

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

3.5 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.5.1 main_loop()

该函数主要功能如下:

1、打印启动进度2、设置环境变量3、cli_init()//初始化hush shell相关变量4、run_preboot_environment_command()//获取环境变量prebooot的内容,preboot是一些预启动命令,一般不使用该环境变量5、bootdelay_process()//获取bootdelay的值,然后保存到stored_bootdelay全局变量里面,获取bootcmd环境变量值,并且将其返回6、autoboot_command(bootcmd)  ---> abortboot(stored_bootdelay)//参数为bootdelay,该函数用于处理倒计时    ---> abortboot_normal(bootdelay)//参数为bootdelay,该函数用于处理倒计时7、cli_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启动流程分析完毕,最后附上启动流程完整图

图片

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约