全志tina-boot0-uboot

1BOOT0阶段

​ 机器从上电后先运行BROM中一段固定的代码,BROM会读取外部引脚来决定从何种介质来启动系统,BROM的作用是初始化储存启动系统的介质,如flash,emmc等。然后从启动介质中读取boot0代码到芯片内部的SRAM中,然后跳到boot0的boot0_entry.S(sunxi_spl/boot0/main/)。具体代码如下:

#include <config.h>
#include "asm/mode.h"
.globl _start
_start: b       reset

reset:
        mrs r0, cpsr @将cpsr寄存器的值读到r0寄存器中
        bic r0, r0, #ARMV7_MODE_MASK  @对模式位清0
        orr r0, r0, #ARMV7_SVC_MODE	  @设置模式为SVC
        @// After reset, ARM automaticly disables IRQ and FIQ, and runs in SVC mode.
        orr r0, r0, #( ARMV7_IRQ_MASK | ARMV7_FIQ_MASK )    
        bic r0, r0, #(1<<9)                                 @// set little-endian,ARM支持大小端,设置为小端,X86只支持小端
        msr cpsr_c, r0

        mrc     p15, 0, r0, c1, c0, 0		@读取协处理器CP15,一下功能主要是关闭I-cache,MMU,CAM等
#if defined(CONFIG_ARCH_SUN8IW10P1) || defined(CONFIG_ARCH_SUN3IW1P1)
        orr     r0, r0, #0x00002000     @ set bits 13 (--V-)
#else
        bic     r0, r0, #0x00002000     @ clear bits 13 (--V-)
#endif
        bic     r0, r0, #0x00000007     @ clear bits 2:0 (-CAM)
        orr     r0, r0, #0x00000800     @ set bit 11 (Z---) BTB
        bic     r0, r0, #0x00001000     @ clear bit 12 (I) I-cache
        mcr     p15, 0, r0, c1, c0, 0

        ldr sp, =CONFIG_BOOT0_STACK_BOTTOM   @设置sp指针,设置完后就可以跑C语音环境
        bl  clear_bss						 @清bss段
        bl  cpu_init_s						 @函数为空
        bl  main							 @跳到main,即boot0_main.c
  • CPSR器称为程序状态寄存器,CPSR中包含条件码标志、中断禁止位、当前处理器模式以及其他状态和控制信息。在每种异常模式下都有一个对用的程序状态寄存器SPSR。当异常出现时,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态,CPSR寄存器的每位的作用如下:

CPSR是32位寄存器,各位的状态:
31 30 29 28 27 25 26 24 23 20 19 16 15 10 9 8 7 6 5 4 0
N Z C V Q RESERVED J RESERVED GE[3:0] RESERVED E A I F T M[4:0]
N:计算结果 <0, N = 1
>= 0, N = 0
Z:当计算结果出现 0 时(一般用于比较指令相等时),Z = 1;否则 Z = 0
C:默认为1,不会自动置位
减法:需借位, C = 0; 无借位, C = 1
加法:有进位, C = 1; 无进位, C = 0
V:有溢出: V = 1; 无溢出: V = 0
Q:饱和:Q = 1; 不饱和:Q = 0
饱和:
0x80000000,…,-2,-1,0,1,2,…,0x7fffffff
0x80000000 的减法运算将变回 正数, <= 0x7fffffff
0x7fffffff 的加法运算将变回 负数, >= 0x80000000
饱和运算:
qadd(语法规则与add相同)
计算结果饱和到 0x7fffffff 或 0x80000000,即正数不会大于 0x7fffffff,负数不会小于 0x80000000
RESERVED:保留位,不用
J:当前执行的执行种类,java指令, J = 1
RESERVED:保留位,不用
GE[3:0]:保留指令(不用)
RESERVED:保留位,不用
E:数据存储用大端:E = 1;用小端:E = 0
住:在arm的CP15中修改其值,arm支持大小端,X86只支持小端
A:abort中止禁止位
禁止段错误发生,A = 1; 否则,A = 0
I:中断禁止位
I = 1,禁止IRQ请求
F:快速中断禁止位
F = 1,禁止FRQ请求
M[4:0]:存放工作模式编号
user: 0b10000
FIQ : 0b10001
IRQ : 0b10010
SVC : 0b10011
abt : 0b10111
und : 0b11011
sys : 0b11111

1.1 boot0_main.c函数分析

  • 此阶段主要完成的工作:

    • 1、初始化串口(sunxi_serial_init);

    • 2、sunxi_key_init()。初始化GPADC0和power按键。

    • 3、通过串口获取键盘的按键状态(set_debugmode_flag)用来判断是否进入烧录模式(按数字2进烧录模式)

    • 4、设置时钟频率(set_pll);

    • 5、读取芯片内部的某个rtc寄存器,主要用来判断是否进入烧录模式(rtc_region_probe_fel_flag);

    • 6、获取GPADC0 + POWERON中的是否有输入,如果有输入则在uboot阶段进烧录模式,(sunxi_key_read);

    • 7、初始化DRAM(init_DRAM),此部分代码未开放;

    • 8、建立MMU映射表(mmu_setup),此处可去除,boot0用不到mmu,直接屏蔽此函数即可,前面已关闭;

    • 9、读取boot_packge_nor.fex的头部信息,此头部信息包含了此固件打包了那几类镜像,比如(optee、dtb、uboot、scp(cpus))等,是从128个扇区开始的,即0~128个扇区装的是boot0的固件;

    • 10、通过读取出来的头部信息,把各个固件的镜像加载进DRAM内存中。(load_fip(&use_monitor));

    • 10、关闭MMU(mmu_turn_off),boot0阶段可不执行mmu_setup函数则此处可不用关闭,直接跳转到boot1或者kernel;

    • 11、跳转到u-boot执行, boot0_jmp_monitor()。可直接加载kernel来加快启动速度,或者直接把uboot镜像加载到重定位的位置直接运行

1.1.1 set_pll

​ CPU和各个总线的频率设置,此部分代码在uboot的源码目录中arch/arm/cpu/armv7/sun8iw16p1/spl/clock_spl.c:

void set_pll(void)
{
        printf("set pll start\n");
        set_circuits_analog();
        set_cpu_step();		/*此函数设置不合理,对PLL_CPUX_TUN_REG进行了设置*/
        set_pll_cpux_axi(); /*设置CPUX的频率为408M,AXI的频率为136M*/
        set_pll_periph0();	/*设置AHB1和AHB2的时钟源选择和设置PLL*/
        if (0)
                set_pll_ahb_for_secure();
        else
                set_ahb();/*设置PLL6:AHB1:APB1 = 600M:200M:100M,PLL6:AHB3 = 600M:200M*/
        set_apb();		  /*PLL6:APB1 = 600M:100M*/
        set_pll_dma();	  /*DMA的频率用的是MBUS的时钟源*/
        set_pll_mbus();   /*set mbus=400M*/

        printf("set pll end\n");
        return ;
}

  • set_cpu_step() :根据芯片手册建议,此函数应不执行,使用默认值即可,故建议关闭此函数

    static void set_cpu_step(void)
    {
            u32 reg_val;
            reg_val = readl(SUNXI_CCM_BASE + 0x400);
            reg_val &= (~(0x7 << 28));
            reg_val |= (0x01 << 28);
            writel(reg_val, SUNXI_CCM_BASE + 0x400);
    }
    
    

    对PLL_CPUX_TUN_REG进行了设置,先对2830位清0,再设置28位为1,但是根据芯片手册上的建议,最好设置2830位为默认值0x4,要不然会影响到稳定性。故个人觉得此函数可不执行,让PLL_CPUX_TUN_REG寄存器保持默认值。

  • set_pll_cpux_axi:主要设置cpu axi amba的频率

    void set_pll_cpux_axi(void)
    {
            __u32 reg_val;
    
            /*select CPUX  clock src: OSC24M, AXI divide ratio is 3, system apb clk ratio is 4*/
            writel((0<<24) | (3<<8) | (2<<0), CCMU_CPUX_AXI_CFG_REG);
            __usdelay(1);
    
            /* set default val: clk is 408M  , PLL_OUTPUT= 24M*N/(M*P)*/
            writel((0x0a001000), CCMU_PLL_CPUX_CTRL_REG);
    
            /* lock enable */
            reg_val = readl(CCMU_PLL_CPUX_CTRL_REG);
            reg_val |= (1<<29);
            writel(reg_val, CCMU_PLL_CPUX_CTRL_REG);
    
            /* enable pll */
            reg_val = readl(CCMU_PLL_CPUX_CTRL_REG);
            reg_val |=  (1<<31);
            writel(reg_val, CCMU_PLL_CPUX_CTRL_REG);
    
            /* wait PLL_CPUX stable */
    #ifndef FPGA_PLATFORM
            while (!(readl(CCMU_PLL_CPUX_CTRL_REG) & (0x1 << 28)))
                    ;
            __usdelay(1);
    #endif
            /* lock disable,可以不做此步操作 */
            reg_val = readl(CCMU_PLL_CPUX_CTRL_REG);
            reg_val &= ~(1<<29);
            writel(reg_val, CCMU_PLL_CPUX_CTRL_REG);
    
            /* set and change cpu clk src to PLL_CPUX,  PLL_CPUX:AXI0 = 408M:136M,AXI是一种AMBA的总线协议 */
            reg_val = readl(CCMU_CPUX_AXI_CFG_REG);
            reg_val &=  ~(0x03 << 24);
            reg_val |=  (0x03 << 24);
            writel(reg_val, CCMU_CPUX_AXI_CFG_REG);
            __usdelay(1);
    }
    

    ​ cpu时钟频率设置,先选择OSC24M作为CPUX和AXI的时钟输入源,然后设置N、M、P因子,PLL_C0_CPUX= 24M*N/(M*P)

1.1.2 boot0_jmp_monitor

void boot0_jmp_monitor(void)
{
        unsigned int optee_entry = OPTEE_BASE;
        unsigned int uboot_entry = CONFIG_SYS_TEXT_BASE;

        mmu_turn_off();

        asm volatile ("mov lr, %0" : : "r" (uboot_entry) : "memory");
        asm volatile ("bx          %0" : : "r" (optee_entry) : "memory");/*直接通过bx命令跳到optee处执行,然后再由optee执行uboot部分*/
}

*** 优化措施 : 提高CPU、nor_flash频率,开4线

1、在boot0阶段,cpu=408MHZ,spi nor flash开4线,nor_flash=40MHZ读boot_package_nor.fex,没开之前所耗时间:326 - 214 = 112ms,开之后246 - 215 = 31ms,减少80ms

2、boot0阶段cpu=408MHZ,开4线load kernel的时间,nor_flash=40MHZ:137ms

​ 注意事项:因没有了uboot,不会把dram的起始地址为大小加到dtb中,所以在dts中的memory中写出dram的起始地址和大小,如图:

如果不传递dram的起始地址和大小,则内核起来在访问内存的时候会出错,大小设置不对也会导致内存访问出错问题。v536公版的dram起始地址为0x40000000,大小为256M。

因没有的uboot,需要在dtb中传递cmdline,所以需要在设备树中增加cmdline的节点。

2 optee

​ 此部分不进行分析,主要涉及对uboot镜像的安全认证和电源管理模块

3 uboot

先从链接文件u-boot.lds开始入手,链接文件定义了程序编译之后的整个链接过程,这样我们就可以找出整个链接后执行了哪个程序的第一句汇编代码,先来分析uboot源码目录下的u-boot.lds链接脚本,源码如下:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x43100000; /*程序的执行起始地址为0x43100000,也是uboot镜像的加载地址*/
 . = ALIGN(4);
 .head :
 {
  *(.__image_copy_start)	/*拷贝uboot镜像的开始地址*/
  arch/arm/cpu/armv7/spare_head.o (.data*)
 }
        .hash :
        {
  arch/arm/cpu/armv7/uboot_hash.o(.data*)
        }
 . = ALIGN(4);
 .text :			/*代码段起始位置*/
 {
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)/*第一个汇编代码在arch/arm/cpu/armv7/start.S中*/
  *(.text*)							/*剩下的所有代码段*/
 }
 . = ALIGN(4);
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }	/*readonly data段*/
 . = ALIGN(4);
 .data : {		/*所有的readonly data*/
  *(.data*)
 }
 . = ALIGN(4);
 . = .;
 . = ALIGN(4);
 .u_boot_list : {
  KEEP(*(SORT(.u_boot_list*)));
 }
 . = ALIGN(4);
 .image_copy_end :		/*拷贝uboot镜像的结束地址*/
 {
  *(.__image_copy_end)
 }
 .rel_dyn_start :
 {
  *(.__rel_dyn_start)
 }
 .rel.dyn : {
  *(.rel*)
 }
 .rel_dyn_end :
 .end :
 {
  *(.__end)
 }
 _image_binary_end = .;
 . = ALIGN(4096);
 .mmutable : {
  *(.mmutable)
 }
 .bss_start __rel_dyn_start (OVERLAY) : {
  KEEP(*(.__bss_start));	/*bss data段*/
  __bss_base = .;			/*bss data开始地址*/
 }
 .bss __bss_base (OVERLAY) : {
  *(.bss*)
   . = ALIGN(4);
   __bss_limit = .;
 }
 .bss_end __bss_limit (OVERLAY) : {
  KEEP(*(.__bss_end));		/*bss data结束地址*/
 }
 .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.*) }
}

3.1 start.S分析

​ 由链接脚本我们知道uboot执行的第一句汇编文件是在arch/arm/cpu/armv7/start.S文件中,如下:

.globl  _TEXT_BASE
_TEXT_BASE:
        .word   CONFIG_SYS_TEXT_BASE	@CONFIG_SYS_TEXT_BASE= 0x43100000 
        .globl  reset
reset:
        bl      save_boot_params		@此函数为空
        /*
         * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
         * except if in HYP mode already
         */
        mrs     r0, cpsr				@读取arm的程序主状态寄存器
        and     r1, r0, #0x1f           @ mask mode bits
        teq     r1, #0x1a               @ test for HYP mode
        bicne   r0, r0, #0x1f           @ clear all mode bits
        orrne   r0, r0, #0x13           @ set SVC mode
        orr     r0, r0, #0xc0           @ disable FIQ and IRQ
        msr     cpsr,r0					@set cpu SVC MODE,disable FIQ and IRQ

#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
        /* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
        mrc     p15, 0, r0, c1, c0, 0   @ Read CP15 SCTRL Register
        bic     r0, #CR_V               @ V = 0
        mcr     p15, 0, r0, c1, c0, 0   @ Write CP15 SCTRL Register

        /* Set vector address in CP15 VBAR register */
        ldr     r0, =_start				
        mcr     p15, 0, r0, c12, c0, 0  @Set VBAR
#endif

            
 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
        bl      cpu_init_cp15		@bl为带跳转指令,会把当前的pc赋值给lr寄存器,然后可以返回继续执行
        bl      cpu_init_crit		@bl为带跳转指令,会把当前的pc赋值给lr寄存器,然后可以返回继续执行
#endif

        bl      _main				
  • 1、关闭IRQ、FIQ,设置CPU处于SVC模式
  • 2、设置VBAR异常向量入口地址为_start的标号处,当发生异常时,跳到此标号处执行。
    • 根据arm手册异常向量地址的入口为0x00000000或者0xffff0000然而片内内存中开始地址0x00000000为ROM区,开始地址不可写,处理器厂家知道这样设计有些问题,所以在片内内存开辟了一块新的区域专门用于放置异常向量表,这样做话我们就需要将异常向量表的地址重新定位到厂家的那块内存。通过设置VBAR寄存器,当发生异常以后ARM处理器会自动从VBAR寄存器训导异常向量表的地址,并跳转到该处执行。

3.2 cpu_init_cp15 函数分析

ENTRY(cpu_init_cp15)
        /*
         * Invalidate L1 I/D
         */
        mov     r0, #0                  @ set up for MCR
        mcr     p15, 0, r0, c8, c7, 0   @ invalidate TLBs  		//失效TLBS
        mcr     p15, 0, r0, c7, c5, 0   @ invalidate icache  	//失效icache
        mcr     p15, 0, r0, c7, c5, 6   @ invalidate BP array 	//失效分支预测
        mcr     p15, 0, r0, c7, c10, 4  @ DSB					//数据同步屏障
        mcr     p15, 0, r0, c7, c5, 4   @ ISB					//指令同步屏障

        /*
         * disable MMU stuff and caches
         */
        mrc     p15, 0, r0, c1, c0, 0							//将SCTLR的值赋值给r0
        bic     r0, r0, #0x00002000     @ clear bits 13 (--V-)	//清零第13位,异常向量映射到0x00000000
        bic     r0, r0, #0x00000007     @ clear bits 2:0 (-CAM) //失效dcache,失效对齐检查,禁用mmu
        orr     r0, r0, #0x00000002     @ set bit 1 (--A-) Align //使能对其检查模式
        orr     r0, r0, #0x00000800     @ set bit 11 (Z---) BTB  //使能分支预测
#ifdef CONFIG_SYS_ICACHE_OFF									//该宏未被定义
        bic     r0, r0, #0x00001000     @ clear bit 12 (I) I-cache	
#else
        orr     r0, r0, #0x00001000     @ set bit 12 (I) I-cache	//开启icache
@endif 
        mcr     p15, 0, r0, c1, c0, 0							//将r0的值写入SCTLR寄存器
        mov     pc, lr                  @ back to my caller		//跳回调用cpu_init_cp15的地方
ENDPROC(cpu_init_cp15)

​ 此函数主要通过配置CP15协处理器的SCTLR寄存器来关闭dcache、BP array、MMU。

  • 为什么dcache在uboot的开始阶段一定要关闭呢,dcache是CPU内部的数据缓存,在CPU刚上电的时候,也就是进入到uboot的时候,dcache是一定要关闭的,如果不关闭的时候,CPU取数据就会从dcache中取,而这时dram中的数据并没有读取出来存到dcache中。
  • MMU在使用之前需要进行一系列的初始化,并且初始化过程复杂,并且uboot阶段暂时用不到,所以可以关闭它。
  • BP array :分支预测。条件分支指令通常具有两路后续执行分支。即不采取跳转,顺序执行后面紧挨JMP的指令;以及采取跳转,到另一块程序内存去执行那里的指令。是否条件跳转,只有在该分支指令在指令流水线中通过了执行阶段才能确定。如果没有分支预测器,处理器将会等待分支指令通过了指令流水线的执行阶段,才把下一条指令送入流水线的第一个阶段——取指令阶段或者将后续流水线全部清空。这种技术叫做流水线停顿、流水线冒泡

3.3 cpu_init_crit函数分析

ENTRY(cpu_init_crit)
        /*
         * Jump to board specific initialization...
         * The Mask ROM will have already initialized
         * basic memory. Go here to bump up clock rate and handle
         * wake up conditions.
         */
        b       lowlevel_init           @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)

​ 此函数直接跳转到 lowlevel_init函数执行,所在目录文件为arch/arm/cpu/armv7/lowlevel_init.S:

ENTRY(lowlevel_init)
        /*
         * Setup a temporary stack
         */
        ldr sp, =CONFIG_SYS_INIT_SP_ADDR @设置sp堆栈指针,堆栈的指针向下增长(递减),为c语言环境做准备,0x37EB0,堆顶以上的地址预留了gdata的空间
												@ CONFIG_SYS_INIT_SP_ADDR = CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET
                                                @ CONFIG_SYS_INIT_RAM_ADDR = 0x20000 
                                                @ CONFIG_SYS_INIT_SP_OFFSET = CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE
                                                @ CONFIG_SYS_INIT_RAM_SIZE = 0x18000
                                                @ GENERATED_GBL_DATA_SIZE = 336
        bic     sp, sp, #7 /* 8-byte alignment for ABI compliance */	@将堆栈地址8字节对齐

        sub     sp, sp, #GD_SIZE				@把gd_t gdata __attribute__ ((section(".data")))的地址存储在r9寄存器中
        bic     sp, sp, #7
        mov     r9, sp
#endif
        /*
         * Save the old lr(passed in ip) and the current lr to stack
         */
        push    {ip, lr}		@把lr入栈,并把lr存储到ip寄存器中

        /*
         * go setup pll, mux, memory
         */
        bl      s_init			@跳转到s_init处执行
        pop     {ip, pc}		@把ip弹出赋值给pc,即返回到调用lowlevel_init的地方,后续就会去执行bl _main
ENDPROC(lowlevel_init)

​ 在分析此函数之前,先讲解一些概念,堆栈的概念,堆栈是用来保存临时数据、局部变量和中断/调用子程序程序的返回地址。程序中栈主要是用来存储函数中的局部变量以及保存寄存器参数的,如果你用了操作系统,栈中还可能存储当前进线程的上下文。设置栈大小的一个原则是,保证栈不会下溢出到数据空间或程序空间.CPU在运行程序时,会自动的使用堆栈,所以堆栈指针SP就必须要在调用C程序前设定。

  • 堆栈位置的设置

    • 开始将堆栈指针设置在内部RAM,是因为不是每个板上都有外部RAM,而且外部RAM的大小也不相同,而且如果是SDRAM,还需要初始化,在内部RAM开始运行的一般是一个小的引导程序,基本上不怎么使用堆栈,因此将堆栈设置在内部RAM(V536内部RAM的起始地址为0x38000,大小为132k=0x21000),全志V536用的SDRAM的大小为256M,从0x40000000到0x50000000,当程序在片内SRAM运行的时候,sp的值可设置为0x58000,当程序在SDRAM内运行的时候sp设置为0x50000000,当然当程序在内部SRAM运行,若已经初始化SDRAM,此时也可以将堆栈指针设置为0x50000000。

栈的整体作用:

  • 保存现场(在linux系统中尤为突出和重要)
    • 当从一个函数调到另一个函数时,指当前用到的某一些寄存器的值要保存下来,比如r0,r1的值,可以通过入栈的操作保存下来,如果不保存这些寄存器的值,当你跳转到执行完子函数后,再跳转回来,r0,r1的寄存器值就很大可能被修改,而更重要的一点时lr寄存器保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量;
  • 传递参数 : 汇编代码调用 C 函数时,需传递参数,特别当汇编要传递的参数超过4各时,就会用到堆栈中的参数
    • C 语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些 C 语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。一种情况是,本身传递的参数不多于 4 个,就可以通过寄存器 r0~r3 传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。另一种情况是,参数多于 4 个时,寄存器不够用,就得用栈了。
  • 保存临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量;
    • 包括函数的非静态局部变量以及编译器自动生成的其他临时变量。

lowlevel_init函数先开始设置好sp指针,即堆栈,设置的值为 = 0x20000 + 0x18000 - 336 = 0x37EB0,即内部SRAM最高地址处减去gdata长度的大小,预留gdata的空间大小,是不希望有其他的因素修改到gdata的数据,而堆栈设置到SRAM的最高地址处是不希望产生下溢出。准备好堆栈后,就可以运行C语音环境了,则直接跳转到s_init函数运行

3.3.1 s_init函数分析

​ 函数所在木目录文件为 arch/arm/cpu/armv7/sunxi-common/board.c,此函数只是关闭看门狗,函数退出后执行bl _main函数:

void s_init(void)
{
	watchdog_disable();
}

3.4 main函数分析

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWJYev4E-1626501560867)(boot0-uboot流程分析.assets/image-20210303110754960.png)]

由搜索可知,_main函数在arch/arm/lib/crt0.S中:

	ldr	sp, =(CONFIG_SYS_INIT_SP_ADDR)
    bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */
	sub	sp, sp, #GD_SIZE	/* allocate one GD above SP */
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */
	mov	r9, sp		/* GD is above SP */						@这些已经在前面设置过了,个人认为可以不用再设置
        
	mov	r0, #0
	bl	board_init_f
        
    ldr	sp, [r9, #GD_START_ADDR_SP]	/* sp = gd->start_addr_sp */ @r9存储了gdata的再SRAM中的首地址
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */
	ldr	r9, [r9, #GD_BD]		/* r9 = gd->bd */				@
	sub	r9, r9, #GD_SIZE		/* new GD is below bd */

	adr	lr, here
	ldr	r0, [r9, #GD_RELOC_OFF]		/* r0 = gd->reloc_off */
	add	lr, lr, r0
	ldr	r0, [r9, #GD_RELOCADDR]		/* r0 = gd->relocaddr */
	b	relocate_code
        
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 */
	ldr	pc, =board_init_r	/* this is auto-relocated! */

	/* we should not return here. */

3.4.1 board_init_f函数分析

common/board_f.c此函数主要完成了以下几件事件:

  • 给global_data这个结构体初始化
  • 初始化串口、设置波特率、置gd->have_console = 1,dram已在boot0初始化,此处只是获取dram的大小并打印出来
  • 电源和cpu、和其他总线的频率设置
  • 规划dram的内存空间,uboot镜像要重定位到高地址,给kernel留出低端地址
  • 获取dtb,并重新加载到规划的dram空间中
void board_init_f(ulong boot_flags)
{

#if defined(CONFIG_ARCH_SUN8IW6P1)|defined(CONFIG_ARCH_SUN8IW11P1)
	memset((void *)CONFIG_SYS_SRAM_BASE, 0, 4*1024);
#endif

#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
	/*
	 * For some archtectures, global data is initialized and used before
	 * calling this function. The data should be preserved. For others,
	 * CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
	 * here to host global data until relocation.
	 */
	gd_t data;

	gd = &data;	/*gd为全局变量,首地址存储到r9寄存器中*/

	/*
	 * Clear global data before it is accessed at debug print
	 * in initcall_run_list. Otherwise the debug print probably
	 * get the wrong vaule of gd->have_console.
	 */
	zero_global_data();	/*对gd全局变量清0*/
#endif

	gd->flags = boot_flags;	/*boot_flags = 0,传入的r0 = 0*/
	gd->have_console = 0;
	gd->debug_mode = 1;

	if (initcall_run_list(init_sequence_f))
	{
		//hang();
		sunxi_board_run_fel_eraly();
	}

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)
	/* NOTREACHED - jump_to_copy() does not return */
	hang();
#endif
}

​ 接下来分析init_sequence数组中执行的一系列函数:

init_fnc_t *init_sequence[] = {
    #ifdef CONFIG_SANDBOX
	setup_ram_buf,
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS)
	timer_init,		/* initialize timer */
#endif
	init_baud_rate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	console_init_f,		/* stage 1 init of console */
	script_init,		/*暂未清楚此函数的用处,大概是初始化可以获取sys_config.fex中的配置*/

	setup_mon_len,		@time = 0.391/*获取uboot镜像的长度,gd->mon_len = (ulong)&__bss_end - (ulong)_TEXT_BASE;*/
	setup_fdt,			@time = 0.391/*获取dtb并Copy到新的内存地址gd->fdt_blob =(void*)CONFIG_SUNXI_FDT_ADDR = 0x44000000;*/
	trace_early_init,	/*空函数return 0*/
    arch_cpu_init,		/*空函数return 0*/
# ifdef CONFIG_OF_CONTROL
    find_fdt,			/*铜鼓环境变量获取到fdt,V536不是通过此方式,可去掉此函数,evn_init也没有初始化,故获取不到*/
#endif        
    mark_bootstage,		@time = 0.393	/*标记此时处于board_init_f阶段*/    
    env_init,			   				/*获取环境变量的地址,并给gd->env_valid = 1;gd->env_addr = (ulong)&(env_ptr->data);*/
    fdtdec_prepare_fdt, @time = 0.393	/*此处只是检查dtb的头部合法性,并不是把dtb解析成device_node*/
    display_options,	@time = 0.393 	/* say that we are here ,打印u-boot的版本号*/
	display_text_info,	/* show debugging info if required ,打印出u-boot的commit提交号*/
	sunxi_probe_securemode,	/*涉及安全启动的校验,gd->securemode = SUNXI_NORMAL_MODE;gd->bootfile_mode = SUNXI_BOOT_FILE_PKG;*/
    print_cpuinfo,			/*打印CPU的信息*/
        
    INIT_FUNC_WATCHDOG_INIT		/*未定义CONFIG_WATCHDOG,此处不开看门狗*/
#if defined(CONFIG_MISC_INIT_F)	/*未定义*/
	misc_init_f,
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)	/*未定义,不初始化I2C*/
	init_func_i2c,
#endif
#if defined(CONFIG_HARD_SPI)	/*未定义,不初始化spi*/
	init_func_spi,
#endif
	smc_init,					@time = 0.414 /*和安全有关,此处不分析*/
	init_func_pmubus,			@time = 0.416 /*电源和频率的设置*/
	power_source_init,			@time = 0.420/*电源设置相关,设置到cpus*/
	check_update_key,			@time = 0.433/*从串口获取key的输入,看是否要进去烧录模式*/
	announce_dram_init,    		@time = 0.435/*只是输出puts("DRAM:  ");字符串*/
   	dram_init,					@time = 0.439/*在boot0阶段已初始化,此处只是赋值gd->ram_size = dram_size * 1024 * 1024;并打印出DRAM的大小*/
        
    	/*
	 * Now that we have DRAM mapped and working, we can
	 * relocate the code and continue running from DRAM.
	 *
	 * Reserve memory at end of RAM for (top down in that order):
	 *  - area that won't get touched by U-Boot and Linux (optional)
	 *  - kernel log buffer
	 *  - protected RAM
	 *  - LCD framebuffer
	 *  - monitor code
	 *  - board info struct
	 */
	setup_dest_addr,	@time = 0.442/*把dram的高地址赋值给gd->ram_top=0x50000000,因为后续kernel要泡在dram的低端地址,如u-boot需放在高地址处
						 * gd->relocaddr = gd->ram_top;
						*/
     /*接下来是规划DRAM的内存空间分布*/   
#if defined(CONFIG_SUNXI_LOGBUFFER)	/*未定义*/
	reserve_logbuffer,			/* reserve kernel log buffer: SUNXI_DISPLAY_FRAME_BUFFER_SIZE = 0x10000000/16M*/
#endif    
#ifdef CONFIG_SUNXI_DEBUG_BUF		/*定义此宏*/
	reserve_debugbuffer,		@time = 0.444/*CONFIG_SUNXI_DEBUG_BUF_SIZE   (1 * 1024 * 1024)*/
#endif
    
    reserve_round_4k,			/*gd->relocaddr &= ~(4096 - 1);4kb对齐*/

    #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
		defined(CONFIG_ARM)
	reserve_mmu,				@time = 0.447/*留出MMU的page映射区,gd->arch.tlb_size = PGTABLE_SIZE(4096*4)留出4K存放TLB页表;页表64K对齐*/
    
#ifdef CONFIG_LCD				/*未定义*/
	reserve_lcd,
#endif
    reserve_trace,			@time =0.450
#if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) \
		&& !defined(CONFIG_ARM) && !defined(CONFIG_X86)
	reserve_video,				/*不执行*/
#endif    
     reserve_uboot,		@time = 0.452/*留出u-boot的内存空间,gd->relocaddr -= gd->mon_len;gd->relocaddr &= ~(4096 - 1);
     					 * gd->start_addr_sp = gd->relocaddr;
     					 */
        
    reserve_malloc,		@time = 0.455/*留出堆的内存空间*/
	reserve_board, 		@time = 0.457/*留出bd结构体的内存*/
      
   	setup_machine,		@time = 0.459	/*为空函数*/
	reserve_global_data,	@time = 0.462/*留出global_data的大小,gd->start_addr_sp -= sizeof(gd_t);
							 *gd->new_gd = (gd_t *)map_sysmem(gd->start_addr_sp, sizeof(gd_t))
							 */
	reserve_fdt,			@time = 0.465/*为dtb留出两倍长度的大小,并更新gd->new_fdt的为dtb的首地址*/
	reserve_sysconfig,		@time = 0.467/*预留soc_config的大小*/
	reserve_parameter,		@time = 0.470/*未定义直接返回0*/
	reserve_stacks,			@time = 0.473/*设置中断异常堆栈,gd->start_addr_sp -= 16;
										  *gd->start_addr_sp &= ~0xf;gd->irq_sp = gd->start_addr_sp;*/
	setup_dram_config,		@time = 0.475/*已在boot0阶段初始化dram,gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
							 * gd->bd->bi_dram[0].size = gd->ram_size;
							 */
    display_new_sp,			@time = 0.478	/*打印出在DRAM中的sp堆栈指针,要开启DEBUG*/
    reloc_fdt,				@time = 0.480	/*拷贝dtb到新的gd->new_fdt地址处*/
	reloc_sysconfig,		@time = 0.484
	reloc_parameter,		@time = 0.485
	setup_reloc,			@time = 0.493	/*拷贝全局gd到gd->new_gd处,并计算出将要拷贝uboot的偏移地址
							 *gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
							 */
    
};

执行完之后global_data结构体中部分变量已给赋值,已不按顺序给出,如下:

global_data
bd_t *bd0x4e1fffa8
flags0
baudrate115200
cpu_clk912Mhz
bus_clk304Mhz
pci_clk0
mem_clk400Mhz
fb_base
have_console1
env_addrdefault_environment[0]
env_valid1
relocaddr0x4feaf000
ram_size256M
mon_len__bss_end - _start + head_align_size
start_addr_sp0x4e1c3ec0
reloc_off
new_gdgd->new_gd
fdt_blob__dtb_dt_begin
new_fdt0x4e1d1ee0
fdt_size94144 Bytes

到这里此时board_init_f函数已经执行完了,可以看出uboot现在空间划分是从顶端往下进行的。内存图分布如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cQ6OMu4Q-1626501560869)(boot0-uboot流程分析.assets/image-20210303211535133.png)]

  • 此时board_init_f函数已经执行完了,此时会返回到_main函数,执行一段汇编函数,如下:
    ldr	sp, [r9, #GD_START_ADDR_SP]	/* sp = gd->start_addr_sp */ @r9存储了全局变量gd的首地址,然后通过偏移来重新赋值堆栈地址,SPL阶段的sp堆栈																 @指针是设置在SRAM内存中,而此时则切换到DRAM中
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */
	ldr	r9, [r9, #GD_BD]		/* r9 = gd->bd */				@更新gd->bd的结构体所在的首地址
	sub	r9, r9, #GD_SIZE		/* new GD is below bd */

	adr	lr, here				@把here标记处的地址赋值给lr寄存器			
	ldr	r0, [r9, #GD_RELOC_OFF]		/* r0 = gd->reloc_off ,把uboot相对于未拷贝前的偏移赋值给r0寄存器*/	
	add	lr, lr, r0					/*lr + 偏移地址后赋值给lr*/
	ldr	r0, [r9, #GD_RELOCADDR]		/* r0 = gd->relocaddr ,*/
	b	relocate_code
  • 首先更新sp,并且将sp 8字节对齐,方便后面函数开辟栈能对齐,

  • 获取gd->bd地址到r9寄存器中,在board_init_f中gd->bd已经更新为新分配的bd了,下一条汇编将r9减掉bd的size,这样就获取到了board_init_f中新分配的gd了!

  • 把here标记处的地址赋值给lr寄存器,然后加上新地址偏移量给lr,,则是code relocate后的新here了,relocate_code返回条转到lr,则是新位置的here!

  • r0中保存了需要重定位后的uboot_code的新地址,跳转到relocate_code函数执行

3.4.2 relocate_code函数分析

ENTRY(relocate_code)
	ldr	r1, =__image_copy_start	/* r1 <- SRC &__image_copy_start */
	subs	r4, r0, r1		/* r4 <- relocation offset */
	beq	relocate_done		/* skip relocation ,如果r0 - r1 = 0,即不需要重定位,则跳转到relocate_done执行*/
	ldr	r2, =__image_copy_end	/* r2 <- SRC &__image_copy_end */

copy_loop:
	ldmia	r1!, {r10-r11}		/* copy from source address [r1],拷贝r1寄存器中的地址处的2个4byte数据到r10,r11寄存器中,r1自动加8    */
	stmia	r0!, {r10-r11}		/* copy to   target address [r0],    */
	cmp	r1, r2			/* until source end address [r2],比较r1和r2的地址是否相等,    */
	blo	copy_loop		/*r1和r2不相等则继续拷贝*/

	/*
	 * fix .rel.dyn relocations,完成rel_dyn_data的拷贝,与位置无关码有关,百度即可
	 */
	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?,label标签地址处的值为0x17 = 23 */
	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

	bx        lr		/*lr存储的是here标号处的地址*/
#endif

ENDPROC(relocate_code)

3.4.3 here标号处的函数分析

here:
/* Set up final (full) environment */

	bl	c_runtime_cpu_setup	/* we still call old routine here,主要关闭I/Dcache,重新设置VBAR为_start的标号地址处 */

	ldr	r0, =__bss_start	/* this is auto-relocated! */
	ldr	r1, =__bss_end		/* this is auto-relocated! */

	mov	r2, #0x00000000		/* prepare zero to clear BSS,对BSS段清0 *	
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

​ 执行完后,接下来分别调用了coloured_LED_init以及red_led_on,很多开发板都会有led指示灯,这里可以实现上电指示灯亮,最后r0赋值gd指针,r1赋值relocaddr,进入最后的board_init_r

	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 */
	ldr	pc, =board_init_r	/* this is auto-relocated! */croot

接着就是执行board_init_r函数了:

3.4.2 board_init_r函数分析

​ 部分地方有删减,只保留满足条件的执行部分:

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
    
#ifdef CONFIG_USE_IRQ
	intr_vector_fix();		/*Uboot重定位后,需要重新给异常向量设置新的地址,原理:旧的异常地址加上gd->reloc_off*/
    
    	if (initcall_run_list(init_sequence_r))	/*功能函数的实现都在init_sequence_r数组里所有的函数*/
		hang();

	/* NOTREACHED - run_main_loop() does not return */
	hang();

​ 接下来就分析init_sequence_r中都完成了哪些功能初始化,此时到了这就是一些板级初始化,视产品需求来完成相应的功能开发,主要是驱动开发,数组源码如下:

init_fnc_t init_sequence_r[] = {
	initr_trace,			/*未定义*/
	initr_reloc,			/*通过环境变量'fdtaddr'来给gd->fdt_blob赋值,V536未设置'fdtaddr'的值*/
	initr_serial,			/*这里面会初始化一系列定义的串口,假如你的机器有4个串口它就可以帮我初始化4个,然后再设置默认的串口作为输出端口*/
#ifdef CONFIG_ARM
	initr_caches,			/*使能I/Dcache*/
	board_init,				/* Setup chipselects */   
        
    initr_reloc_global_data,	/*获取uboot镜像的大小赋值给monitor_flash_len = _end - __image_copy_start*/
	initr_announce,				/*debug打印出uboot在dram中的位置*/
	show_platform_info,			/*空函数*/
	initr_malloc,				/*对board_init_f分配的malloc和noncache_malloc进行清0*/
	script_init,   				/*看名字主要是对一些脚本的初始化,此处未能理解,大概是获取sys_config.fex中的配置*/
        
    bootstage_relocate,			/*只是赋值一个字符串给某个数组赋值,暂不清楚其作用*/
	power_init_board,			/*获取sys_config.fex属性未[charger0]的信息,为后续初始化axp做准备*/
	stdio_init,					/*设置串口的标准输入输出的device*/
	initr_jumptable,			/*给gd->jt分配内存gd->jt = malloc(XF_MAX * sizeof(void *))*/
	console_init_r,				/* fully init console as a device,最终完成stdio和stderr的设置初始化*/
	interrupt_init,				/*主要设置IRQ的sp指针和FIRQ的堆栈和设置gic处于monitor mode模式(安全验证模式)*/
#if defined(CONFIG_ARM) || defined(CONFIG_x86)
	initr_enable_interrupts,	/*使能中断,此处直接汇编写cpsr寄存器的值*/
        
#ifdef CONFIG_SUNXI
	platform_dma_init,			/*DMA的初始化*/
	sunxi_led_init,				/*点LED灯*/
        
#ifdef CONFIG_AUTO_UPDATE		/*未定义*/
	auto_update_check,
#endif
    
    initr_single_core,			/*有关hdmi和flash信息的获取,初始化nor_flash*/
    sunxi_burn_key,				/*未定义CONFIG_SUNXI_KEY_BURN,故此处在uboot阶段没有按键烧录,可加打印确认是否进入*/
    initr_env,					/*先从fdt的根节点看是否有'/config'节点,如果没有则从flash中的env分区获取,即env-4.9.cfg*/
	initr_sunxi_base,			/*主要是fastboot和cmdline环境变量的赋值准备fdt给Kernel*/
        
#ifndef CONFIG_SUNXI_MULITCORE_BOOT
#ifndef CONFIG_SUNXI_MODULE_AXP		/*定义了此宏*/
	sunxi_show_logo,
#endif    
    PowerCheck,					/*电源和电池的检查,从bootlogo分区加载bootlogo.jpg图片到sdram的低端内存0x40000000位置*/
        
#if defined(CONFIG_SUNXI_MULITCORE_BOOT) && defined(ENABLE_ADVERT_PICTURE)	/*未定义*/
	sunxi_load_logo_multiboot,
#endif
    
    usb_net_init,				/*和usb_net有关,此处并未定义*/
	run_main_loop,				/*进入uboot_cmd命令行模式,main_loop*/
}

​ 我们分析一下run_main_loop函数:

static int run_main_loop(void)
{
	pr_msg("inter uboot shell\n");
	/* main_loop() can return to retry autoboot, if so just run it again */
	for (;;)
		main_loop();
	return 0;
}

​ 直接运行main_loop,根据注释,如果需要自动启动,可以再次运行main_loop函数:

void main_loop(void)
{
	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");	/*标记已跑到main_lopp阶段*/

#ifndef CONFIG_SYS_GENERIC_BOARD	/*定义了此宏,不打印以下信息*/
	puts("Warning: Your board does not use generic board. Please read\n");
	puts("doc/README.generic-board and take action. Boards not\n");
	puts("upgraded by the late 2014 may break or be removed.\n");
#endif

	modem_init();			/*未定义CONFIG_MODEM_SUPPORT宏,故为空函数*/
#ifdef CONFIG_VERSION_VARIABLE
	setenv("ver", version_string);  /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

	cli_init();					/*涉及uboot的hash一个variables结构体赋值*/

	run_preboot_environment_command();/*去执行环境变量'preboot'的命令*/

#if defined(CONFIG_UPDATE_TFTP)		/*tftp网络传输有关,这个uboot没有初始化网络*/
	update_tftp(0UL);
#endif /* CONFIG_UPDATE_TFTP */

	s = bootdelay_process();		/*获取到环境变量'bootcmd'的值并返回,把'bootdelay'赋值给stored_bootdelay*/
	//if (cli_process_fdt(&s))
	//	cli_secure_boot_cmd(s);

	autoboot_command(s);			/*如果设置了bootdelay的值,则延时bootdelay的值,然后执行bootcmd命令,如果在bootdelay倒计时,按下了按键
									 *则直接结束推出,不执行bootcmd的命令,接着执行cli_loop函数 
									 */

	cli_loop();						/*此函数会解析从键盘输入命令,并执行,不进行深入分析*/
}

  • run_preboot_environment_command函数分析,如下:

    static void run_preboot_environment_command(void)
    {
    #ifdef CONFIG_PREBOOT		/*如果定义此宏,则从环境变量'preboot'获取命令*/
    	char *p;
    	p = getenv("preboot");
    	if (p != NULL) {
    # ifdef CONFIG_AUTOBOOT_KEYED
    		int prev = disable_ctrlc(1);	/* disable Control C checking,只是把ctrlc_disabled = 1,并把1返回给prev */
    # endif
    
    		run_command_list(p, -1, 0);		/*此处则是去执行从'preboot'获取到的命令*/
    
    # ifdef CONFIG_AUTOBOOT_KEYED
    		disable_ctrlc(prev);	/* restore Control C checking,把prev赋值给ctrlc_disabled, */
    # endif
    	}
    #endif /* CONFIG_PREBOOT */
    }
    
  • bootdelay_process函数分析,如下

    const char *bootdelay_process(void)
    {
    	char *s;
    	int bootdelay;
    #ifdef CONFIG_BOOTCOUNT_LIMIT
    	unsigned long bootcount = 0;
    	unsigned long bootlimit = 0;
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    
    #ifdef CONFIG_BOOTCOUNT_LIMIT
    	bootcount = bootcount_load();
    	bootcount++;
    	bootcount_store(bootcount);
    	setenv_ulong("bootcount", bootcount);
    	bootlimit = getenv_ulong("bootlimit", 10, 0);
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    
        /*如果环境变量设置了'bootdalay的值,则获取该值并转换为10进制的数赋值给变量bootdelay'*/
    	s = getenv("bootdelay");
    	bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
    
    #ifdef CONFIG_OF_CONTROL	/*从dtb中获取Bootdelay的值*/
    //	bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay",
    //			bootdelay);
    #endif
    
    	debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
        
    #if defined(CONFIG_MENU_SHOW)			/*未定义*/
    	bootdelay = menu_show(bootdelay);
    #endif
    //	bootretry_init_cmd_timeout();		/*获取或者设置'bootretry'的值*/
    
    #ifdef CONFIG_POST						/*未定义*/
    	if (gd->flags & GD_FLG_POSTFAIL) {
    		s = getenv("failbootcmd");
    	} else
    #endif /* CONFIG_POST */
    #ifdef CONFIG_BOOTCOUNT_LIMIT			/*未定义*/
    	if (bootlimit && (bootcount > bootlimit)) {
    		printf("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
    		       (unsigned)bootlimit);
    		s = getenv("altbootcmd");
    	} else
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    		s = getenv("bootcmd");		/*获取环境变量'bootcmd'的值*/
    
    //	process_fdt_options(gd->fdt_blob);	
    	stored_bootdelay = bootdelay;	/*把bootdelay的值存在stored_bootdelay变量中*/
    
    	return s;
    }
    
  • autoboot_command(s)函数分析,源码如下:

    void autoboot_command(const char *s)
    {
    	debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
    	/*如果设置了bootdelay的值,bootcmd能获取到,则abortboot函数会计时bootdelay返回0,如果按键了键盘的任何按键则直接推出*/
    	if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
    #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    		int prev = disable_ctrlc(1);	/* disable Control C checking */
    #endif
    
    		run_command_list(s, -1, 0);	/*运行bootcmd的命令*/
    
    #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    		disable_ctrlc(prev);	/* restore Control C checking */
    #endif
    	}
    /*未定义此宏,只要执行menucomd的命令*/
    #ifdef CONFIG_MENUKEY
    	if (menukey == CONFIG_MENUKEY) {
    		s = getenv("menucmd");
    		if (s)
    			run_command_list(s, -1, 0);
    	}
    #endif /* CONFIG_MENUKEY */
    }
    
    

    ​ 到这就回去执行bootcmd的命令,一般是把内核从flash中读到内存中,然后用bootm命令来启动内核,因为此文档的目的是为了优化启动分析,就不去分析bootm命令的执行过程。

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值