最全面的uboot启动过程分析

李肖遥 2023-05-13 22:08
    关注、星标公众号,直达精彩内容

来源:网络素材



1. U-Boot启动过程概述

  • U-Boot的启动过程分为两个阶段。

  • 第一阶段:主要是SOC内部的初始化,板级的初始化比较少,所以移植的修改量比较小。此阶段由汇编语言编写,代码主体分布在/uboot/cpu/s5pc11x/start.S和/uboot/board/samsung/x210/lowlevel_init.S中。

  • 第二阶段:主要是板级的初始化,SOC内部的初始化比较少,移植的修改量主要在此。此阶段由c语言编写,代码主体分布在/uboot/lib_arm/board.c中。

  • 红色字体部分和板级关系较大,是移植的重点修改部分。


2. U-Boot启动代码具体分析


2.1 第一阶段(/ubootcpu/s5pc11x/start.S)

(0)头文件包含

  • /uboot/include/config.h文件是在配置过程中中生成的(具体/uboot/mkconfig文件中实现),其中的内容就是“#include ”,即包含开发板的配置头文件。

  • version.h文件是uboot的版本信息文件,是在编译过程中生成的。具体的U-Boot的version信息可以在/uboot/Makefile中更改

  • U-Boot中的头文件包含其实都不是真正被包含的文件,它们大多是在配置编译阶段产生的符号链接或者是具有符号链接功能的头文件(/uboot/mkconfig中创建符号链接)。总之,它们的功能都类似于符号链接,目的是让uboot的源码更具灵活性和移植性。

     #include 
    #include

    #if defined(CONFIG_ENABLE_MMU)
    #include

    #endif
    #include

    (1)填充16字节的校验位

    • uboot.bin镜像在开头需要加16字节的检验头(详见 irom application note文件)。这一部分主要功能是在代码段最开始处放置16字节的填充位,即通过伪.word指令定义4个word(32位)的空间来在代码段最开始处占16字节。

    • 在此处只是占据16字节的空间,校验头的正确值在编译阶段通过mkv210image.c中计算并填充的。

    #if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
    .word
    0x2000
    .word
    0x0
    .word
    0x0
    .word
    0x0
    #endif

    (2)设置异常向量表

    • .globl是globl是把_start这个标号全局化,是编译器的操作,并不是汇编指令。_start代表程序start.S的入口。

    • 这段代码的功能是设置异常向量表。b reset 所处的位置是与异常向量表基地址偏移量为的0的地方,所以当复位异常发生时(开机也属于复位异常),CPU会自动跳转入异常表基址偏移量为0处执行复位异常程序,即跳转执行reset部分的代码。

    • 这部分代码只是构建了异常向量表,当每个异常向量指向的内容为空(除reset异常外)。因为U-Boot比较简单,只是作为引导程序,所以不需要很细致的处理各种异常。

    .globl _start  
    _start: b reset
    ldr pc, _undefined_instruction
    ldr pc, _software_interrupt
    ldr pc, _prefetch_abort
    ldr pc, _data_abort
    ldr pc, _not_used
    ldr pc, _irq
    ldr pc, _fiq

    (3)禁能IRQ和FIQ,使能SVC模式

    • 开机后程序从此时正式开始运行。

    • 程序上电之初,异常向量表未初始化,故先禁能IRQ(普通中断)和FIQ(快速中断)。

    • 使能SVC模式,即超级用户模式。SVC 模式,主要用于 SWI(软件中断)和 OS(操作系统)。这个模式有额外的特权,允许你进一步控制计算机。例如,你必须进入超级用户模式来读取一个插件(podule)。这不能在用户模式下完成。

    /*
    * the actual reset code
    */
    reset:
    /*
    * set the cpu to SVC32 mode and IRQ & FIQ disable
    */
    @;mrs r0,cpsr
    @;bic r0,r0,#
    0x1f
    @;orr r0,r0,#
    0xd3
    @;msr cpsr,r0
    msr cpsr_c, #
    0xd3 @ I & F disable, Mode: 0x13 - SVC

    (4)初始化相关全局变量

    • TEXT_BASE是U-Boot代码的链接地址,在/uboot/board/samsung/config.mk文件中定义,该文件在/uboot/Makefile->x210_sd_config段中生成。TEXT_BASE = 0xc3e00000

    _TEXT_BASE:
    .word TEXT_BASE
    • 设置U-Boot在DDR中的物理地址,即运行地址,U-Boot重定位将整个U-Boot拷贝至DDR中的_TEXT_PHY_BASE。CFG_PHY_UBOOT_BASE在开发板配置文件(/uboot/include/configs/x210_sd_config)中定义,为0x33e00000

    _TEXT_PHY_BASE:
    .word CFG_PHY_UBOOT_BASE
    • 在开启MMU之后,虚拟地址段0xc0000000-0xd0000000将被映射到物理地址段0x30000000-0x40000000。所以TEXT_BASE = 0xc3e00000对应的物理地址为TEXT_BASE = 0x33e00000,即U-Boot的链接地址与运行地址一致。

    (5)禁用Cache和MMU

    • caches是CPU的缓冲区,它的作用是存放常用的数据和指令,提高cpu与内存之间数据与指令的传输速率。

    • MMU是CPU的内存管理单元,它的作用是转换虚拟地址与物理地址。

    • 关闭caches的原因:上电初始,DDR未初始化,当CPU从cache中取数据时,可能导致数据预取异常。另一方面,当汇编指令读取缓存数据,而实际物理地址对应的数据发生变化,导致CPU不能获取最新的数据。在C语言中无需关闭caches,因为C语言中可以使用volatile关键字避免上述情况。

    • 关闭MMU的原因:U-Boot的作用是硬件初始化和引导操作系统,纯粹的初始化阶段,开启MMU会导致这个过程更复杂。

    cpu_init_crit:
    .................................................
    /*
    * disable MMU stuff and caches
    */
    mrc p15,
    0, r0, c1, c0, 0
    bic r0, r0, #
    0x00002000 @ clear bits 13 (--V-)
    bic r0, r0, #
    0x00000007 @ clear bits 2:0 (-CAM)
    orr r0, r0, #
    0x00000002 @ set bit 1 (--A-) Align
    orr r0, r0, #
    0x00000800 @ set bit 12 (Z---) BTB
    mcr p15,
    0, r0, c1, c0, 0

    (6)判断启动介质

    • 先从(PRO_ID_BASE+OMR_OFFSET)地址的寄存器读取启动介质信息,经过数据处理之后放入r2寄存器中。

    • 然后通过比较r2的值来判定启动介质,经过判断得到当前的启动介质为SD/MMC,在把BOOT_MMCSD宏写入r3中。

    • 最后将启动介质信息从寄存器r3中写入(INF_REG_BASE+INF_REG3_OFFSET)地址的寄存器中

    /* Read booting information */  
    ldr r0,
    =PRO_ID_BASE
    ldr r1, [r0,#OMR_OFFSET]
    bic r2, r1, #
    0xffffffc1
    ............................................

    /* SD/MMC BOOT */
       cmp r2, #
    0xc
       moveq r3, #BOOT_MMCSD
    .............................................
    ldr r0,
    =INF_REG_BASE
       str r3, [r0, #INF_REG3_OFFSET]

    (7)第一次初始化栈(SRAM中)

    • 第一次设置栈,由于不设置栈的话无法使用嵌套bl跳转指令,即双层函数调用,因为只有一个LR寄存器,而后面的lowlevel_init就有双层跳转,故这里开始设置SRAM中用的栈。

    • 这里栈设置的地址并没有按照s5pv210的推荐地址,将0xd0036000地址下12Bytes内存空间设为栈内存,只要保证该段内存不会被其他程序占用即可。

    ldr    sp, =0xd0036000 /* end of sram dedicated to u-boot */
    sub sp, sp, #
    12 /* set stack */
    mov fp, #
    0

    (8)跳转执行lowlevel_init部分

    /uboot/board/x210/lowlevel_init

    1)判断复位的类型

    • 复位分为多种情况,如冷启动、休眠唤醒等。

    • 判断复位类型的意义在于:冷启动需要重新初始化DDR,而休眠唤醒不需要再次初始化DDR。

    /* check reset status  */
    ldr r0,
    =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
    ldr r1, [r0]
    bic r1, r1, #
    0xfff6ffff
    cmp r1, #
    0x10000
    beq wakeup_reset_pre
    cmp r1, #
    0x80000
    beq wakeup_reset_from_didle

    2)关看门狗

    • U-Boot主要功能是初始化硬件资源和引导OS,基本不会出现程序跑飞的情况。因此,为避免喂狗的麻烦,等U-Boot一切就绪正常运行后,再打开看门狗。

    /* Disable Watchdog */
    ldr r0,
    =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
    mov r1, #
    0
    str r1, [r0]

    3)开发板供电锁存

    • 设置开发板的供电按键锁存功能。注意是哪个gpio,移植过程可能需要修改。

    /* PS_HOLD pin(GPH0_0) set to high 开发板供电上锁*/
    ldr r0,
    =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
    ldr r1, [r0]
    orr r1, r1, #
    0x300
    orr r1, r1, #
    0x1
    str r1, [r0]

    4)检测程序当前的执行位置(SRAM或DDR)

    • 本段的功能是检测当前代码的执行位置。判断是在SRAM中还是DDR中,即CPU是冷启动还是休眠唤醒复位,从而来决定是否要跳过后面的时钟、DDR的初始化代码。

    • bic是位清除指令,其功能是:将pc中的某些位清零(r0中为1的位清零),剩下一些特殊的bit位赋值给r1。

    • 比较链接地址和当前地址的特定位,即比较r1和r2。若比较链接地址和当前地址的特定位相等,说明当前代码处于DDR中(休眠唤醒),则跳转到标号1处执行后面的代码(即跳过时钟、DDR的初始化代码);否则,继续执行后续的时钟、DDR的初始化代码。

    ldr    r0, =0xff000fff
    bic r1, pc, r0
    /* r0 <- current base addr of code */
    ldr r2, _TEXT_BASE
    /* r1 <- original base addr in ram */
    bic r2, r2, r0
    /* r0 <- current base addr of code */
    cmp r1, r2
    /* compare r0, r1 */
    beq 1f
    /* r0 == r1 then skip sdram init */

    5)初始化时钟、DDR、串口

    • 跳转执行时钟初始化函数system_clock_init,然后返回。时钟的相关参数可以在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改,移植过程基本不需要需改该部分代码。

    • 跳转执行DDR初始化函数mem_ctrl_asm_init,然后返回。其中,DMC0,DMC1等设置很重要,直接和板子上内存的大小和分布有关,需要注意。DDR的相关参数可以在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改。

    • 跳转执行串口初始化函数uart_asm_init,成功执行后打印‘O’,然后返回。‘O’可作为调试的帮助。

    • 若定义了ONFIG_ONENAND,则初始化ONENAND;若定义了CONFIG_NAND,则初始化NAND。

    • 在返回start.S前打印了‘K’,与之前的‘O’组成“OK”,这是uboot的第一条打印信息,可以用来判断lowlevel_init是否正常运行。

    • 把之前保存在栈中的lr值弹出到pc中,来返回到start.S。

    /* init system clock */
    bl system_clock_init

    /* Memory initialize */
    bl mem_ctrl_asm_init

    1:
    /* for UART */
    bl uart_asm_init
    //初始化完成打印'O'

    bl tzpc_init
    //基本没用

    #if defined(CONFIG_ONENAND)
    bl onenandcon_init
    #endif

    #if defined(CONFIG_NAND)
    /* simple init for NAND */
    bl nand_asm_init
    #endif
    ..............................................
    /* Print 'K' */
    ldr r0,
    =ELFIN_UART_CONSOLE_BASE
    ldr r1,
    =0x4b4b4b4b
    str r1, [r0, #UTXH_OFFSET]

    pop {pc}

    (9)再次供电锁存

    • 从lowlevel_init中跳转回start.S中,再次进行开发板供电锁存设置(在lowlevel_init中已经设置过一次),可能为代码的冗余。注意引脚号。

    ldr    r0, =0xE010E81C       /* PS_HOLD_CONTROL register */
    ldr r1,
    =0x00005301 /* PS_HOLD output high */
    str r1, [r0]

    (10)第二次初始化栈(DDR中)

    • 为了即将执行的c程序做准备,这里开始第二次设置栈,设置于DDR中。这里将栈设置在_TEXT_PHY_BASE,即U-Boot在DDR中的真正物理地址(U-Boot运行地址)。由于栈是满减栈,所以紧挨着uboot放置也不会冲突。

    /* get ready to call C functions */
    ldr sp, _TEXT_PHY_BASE
    /* setup temp stack pointer */
    sub sp, sp, #
    12
    mov fp, #
    0 /* no previous frame, so fp=0 */

    (11)再次检测当前程序执行地址(SRAM或DDR)

    • 在lowlevel_init中,检测当前代码的执行位置。判断是在SRAM中还是DDR中,即cpu是冷启动还是休眠唤醒复位,从而来决定是否要跳过后面的时钟、DDR的初始化代码。

    • 在此处再次检测当前代码的执行位置,从而来决定是否要跳过重定位代码。

    ldr    r0, =0xff000fff
    bic r1, pc, r0
    /* r0 <- current base addr of code */
    ldr r2, _TEXT_BASE
    /* r1 <- original base addr in ram */
    bic r2, r2, r0
    /* r0 <- current base addr of code */
    cmp r1, r2
    /* compare r0, r1 */
    beq after_copy
    /* r0 == r1 then skip flash copy */

    (12)通过引脚来判断启动介质

    • 这一段区别于之前的启动介质判断,本段是通过引脚来判断启动介质的

    • 将0xD0037488地址的寄存器的值放入r0,该寄存器里的值可以判断BL1是从SD/MMC哪个通道启动的,将寄存器的值放入r1。这个寄存器的信息在irom applicationnote里有记载。

    • 将0xEB200000这个值放入r2,该值的表示是2号方式启动,并将寄存器的值和r2(0xEB200000)进行对比。如果相同则说明BL1是从SD卡通道2启动,跳转入标号mmcsd_boot处执行SD/MMC重定位相关代码(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy)。

    • 若不是从SD卡通道2启动,则读取之前存储的启动介质信息(INF_REG_BASE+INF_REG3_OFFSET地址的寄存器),该信息有上一次启动介质检测所得(见(6))。随后,跳转至启动介质相应的xxx_boot代码中执行重定位相关的代码(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy)。

    #if defined(CONFIG_EVT1)

    /* If BL1 was copied from SD/MMC CH2 */

    ldr r0,
    =0xD0037488
    ldr r1, [r0]
    ldr r2,
    =0xEB200000
    cmp r1, r2
    beq mmcsd_boot

    #endif

    ldr r0,
    =INF_REG_BASE
    ldr r1, [r0, #INF_REG3_OFFSET]
    cmp r1, #BOOT_NAND
    /* 0x0 => boot device is nand */
    beq nand_boot
    cmp r1, #BOOT_ONENAND
    /* 0x1 => boot device is onenand */
    beq onenand_boot
    cmp r1, #BOOT_MMCSD
    beq mmcsd_boot
    cmp r1, #BOOT_NOR
    beq nor_boot
    cmp r1, #BOOT_SEC_DEV
    beq mmcsd_boot

    nand_boot:
    mov r0, #
    0x1000
    bl copy_from_nand
    b after_copy

    onenand_boot:
    bl onenand_bl2_copy
    b after_copy

    mmcsd_boot:
    #if DELETE
    ldr sp, _TEXT_PHY_BASE
    sub sp, sp, #
    12
    mov fp, #
    0
    #endif
    bl movi_bl2_copy
    b after_copy

    nor_boot:
    bl read_hword
    b after_copy

    (13)进行U-Boot重定位

    • 具体代码在/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy中。

    • 定义一个函数指针类型copy_sd_mmc_to_mem。

    typedef u32(*copy_sd_mmc_to_mem) (u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);

    函数返回值

    u32代表函数执行成功与否

    u32 channel

    代表SD/MMC启动通道号(0-4)

    u32 start_block

    起始块地址

    u16 block_size

    复制的块的个数

    u32 *trg

    复制操作的目的地址,一般是DDR内的地址

    u32 init

    init一般给0,不用多管

    • 首先读取位于0xD0037488的寄存器值至变量ch中,再一次检查启动介质(SD卡的通道号)。

    • 然后0xD0037F98地址中的指针变量强制转换为copy_sd_mmc_to_mem类型,并赋值给copy_bl2。该指针变量指向一个函数,这个函数就是CPU的IROM中固化用来复制SD/mmc中的内容至任意地址的函数。

    • 最后,根据ch变量的值判断SD的通道号,并将整个U-Boot从相应的SD通道号中拷贝至DDR的指定地址(CFG_PHY_UBOOT_BASE = _TEXT_PHY_BASE = 0x33e00000)处。

    • MOVI_BL2_POS是烧录uboot时的扇区,MOVI_BL2_BLKCNT是uboot占的扇区数,具体的定义和计算都在/uboot/include/movi.h中。CFG_PHY_UBOOT_BASE为U-Boot的在DDR中的物理地址(运行地址),具体的定义和计算都在/uboot/include/configs/x210_sd.h中。

    void movi_bl2_copy(void){
    ulong ch;

    ch
    = *(volatile u32 *)(0xD003A508);

      copy_sd_mmc_to_mem copy_bl2
    = (copy_sd_mmc_to_mem) (*(u32 *) (0xD003E008));

    u32 ret;

      
    if (ch == 0xEB000000) //SD卡通道0

    {
        ret
    = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0);

    }
      else if (ch == 0xEB200000) //SD卡通道2
    {
    .................
    }
    }

    (14)建立虚拟地址映射表并开启MMU

    • 从movi_bl2_copy返回之后,将跳转至start.S->after_copy,建立虚拟地址映射表并开启MMU。

    bl      movi_bl2_copy
    b after_copy
    • TTB(TranslationTableBase),即转换表的基地址。转换表是MMU将虚拟地址映射为物理地址的凭据,建立整个虚拟地址映射的关键就是建立转换表,此表存放在内存中,工作时不需要软件干涉。

    • 只要将转换表TTB的基地址(_mmu_table_base)放入cp15的c2寄存器,MMU就能自动使用虚拟地址映射。_mmu_table_base定义在start.S其值为标号mmu_table,mmu_table在lowlevel_init中定义。

    • 最后通过设置cp15的c1寄存器来开启mmu,以实现虚拟地址映射和内存访问权限管理等功能。

    #if defined(CONFIG_ENABLE_MMU)

    /*使能域访问*/
    enable_mmu:
    ldr r5,
    =0x0000ffff
    mcr p15,
    0, r5, c3, c0, 0 @load domain access register

    /*将转换表ttb的基地址放入cp15的c2寄存器,mmu就能自动使用虚拟地址映射*/
    ldr r0, _mmu_table_base ldr r1,
    =CFG_PHY_UBOOT_BASE
    ldr r2,
    =0xfff00000
    bic r0, r0, r2
    orr r1, r0, r1
    mcr p15,
    0, r1, c2, c0, 0

    /*通过设置cp15的c1寄存器来开启mmu,以实现虚拟地址映射和内存访问权限管理等功能*/
    mmu_on:
    mrc p15,
    0, r0, c1, c0, 0
    orr r0, r0, #
    1
    mcr p15,
    0, r0, c1, c0, 0
    nop
    nop
    nop
    nop

    #endif
    • 详细的建表步骤在/uboot/board/x210/lowlevel_init。

    • 映射表中规定了内存映射和管理是以块为单位的,在ARM中支持3种块大小,细表1KB、粗表4KB、段1MB。真正的转换表就是由若干个转换表单元构成的,每个单元负责1个内存块,总体的转换表负责整个内存空间(0-4G,32位CPU)的映射。

    • 带参宏将FL_SECTION_ENTRY base,ap,d,c,b定义成一个word大小的特定值,这个特定值就是转换表的填充量。

    • 参数分析:参数base是映射出来的段地址的基地址,从第20位开始。20位的大小正好是1MB,故此映射表采用的是段式映射;ap是访问控制位,从第10位开始。d、c、b都是一些权限位。

    #ifdef CONFIG_ENABLE_MMU    
    #ifdef CONFIG_MCP_SINGLE

    .macro FL_SECTION_ENTRY
    base,ap,d,c,b //macro指令是汇编中宏定义
    .word (\base << 20) | (\ap << 10) | \
    (\d
    << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
    .endm
    //即结束宏定义


    .section .mmudata,
    "a"
    .align
    14
    .globl mmu_table

    mmu_table:
    .
    set __base,0 //设置变量__base值为
    /*
    .rept 0x100相当于for循环,一共循环0x100次,所以这一块代码创建了0x100(256)个转换表单元*/
    .rept 0x100
    /*利用刚才定义的带参宏创建转换表的内容,变量__base和3,0,0,0作为参数*/
    FL_SECTION_ENTRY __base,
    3,0,0,0
    .
    set __base,__base+1 //相当于base=base+1
    .endr //结束循环

    .........................................
    //后续继续建表
    • 由以上代码分析,得出整张转换表的设定如下。

    输入虚拟地址

    输出的物理地址

    长度

    0-10000000

    0-10000000

    256MB

    10000000-20000000

    0

    256MB

    20000000-60000000

    20000000-60000000

    1GB

    60000000-80000000

    0

    512MB

    80000000-b0000000

    80000000-b0000000

    768MB

    b0000000-c0000000

    b0000000-c0000000

    256MB

    c0000000-d0000000

    30000000-40000000

    256MB

    d-完

    d-完

    768MB

    • 由此可知,此表仅仅将c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存,其他的虚拟地址空间是原样映射的。所以uboot的链接地址(0xc3e00000)对应物理地址(0x33e00000),即U-Boot的链接地址和运行地址(CFG_PHY_UBOOT_BASE = 0x33e00000)一致。

    (15)第三次初始化栈(DDR中)

    • 第三次设置栈,仍然设在DDR中。虽然上一次已经在DDR中设置过,但是是紧挨着uboot存放的,位置不合理。

    • 所以本次将栈设置uboot链接地址上方2MB处,这个位置合理、紧凑、安全。

    stack_setup:
    #if defined(CONFIG_MEMORY_UPPER_CODE)
    ldr sp,
    =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000

    (16)清bss段

    • _bss_start和_bss_end是链接脚本中定义的。

    • 利用循环清零。

    clear_bss:
    ldr r0, _bss_start
    /* find start of bss segment */
    ldr r1, _bss_end
    /* stop here */
    mov r2, #
    0x00000000 /* clear */


    clbss_l:
    str r2, [r0]
    /* clear loop... */
    add r0, r0, #
    4
    cmp r0, r1
    ble clbss_l

    (17)远跳转至start_armboot(U-Boot第二阶段)

    • 从SRAM中远跳转到DDR中函数start_armboot处,start_armboot定义在根目录下/uboot/lib_arm/board.c中。

    • 至此,uboot第一阶段结束,进入第二阶段,至DDR处执行。

    ldr    pc, _start_armboot
    _start_armboot:
    .word start_armboot


    2.2 第二阶段(/uboot/lib_arm/board.c->start_armboot)

    (1)初始化全局变量

    1)定义一个函数指针类型。

    typedef int (init_fnc_t) (void);

    2)定义并初始化init_sequence数组

    定义并初始化一个全局数组init_sequence,其元素是指向init_fnc_t类型函数的指针,这些函数都是用来初始化各个功能的函数。这些函数的具体功能如下:

    • cpu_init:CPU初始化函数,但cpu初始化工作已在U-Boot的第一阶段完成,所以该函数为空。

    • board_init:开始板级初始化函数,配置网卡用到的GPIO、机器码、内存传参地址,并填充至gd结构体。

    • interrupt_init:定时器的初始化函数,和中断无关(应用,如bootdelay)。

    • env_init:环境变量的初始化函数,由于环境变量还没从启动介质中取到DDR中,故此处的初始化只是对DDR中的环境变量(U-Boot自带的环境变量)进行一个简单的判定,检测其是否可用。真正的初始化在start_armboot里面。U-Boot支持多种不同的启动介质(如norflash、nandflash、sd/mmc),而各种介质存取操作env的方法都是不同的,故U-Boot中包含在多个文件中包含了env_init函数。而此U-Boot的启动介质为SD/MMC,故当前的env_init函数在/uboot/common/Env_movi.c文件中。

    • init_baudrate:串口波特率初始化函数,设置串口波特率,并填充至gd结构体。具体的串口初始化工作已在U-Boot的第一阶段完成(lowlevel_init)。

    • serial_init:串口初始化函数,但具体的串口初始化工作已在U-Boot的第一阶段完成(lowlevel_init),所以该函数为空。

    • console_init_f:控制台初始化函数,名字中的_f表示这是第一阶段的初始化,由于第二阶段的初始化之前需要夹杂一些前提代码,故将在start_armboot执行。

    • display_banner:用来通过串口控制台显示U-Boot的版本信息(logo)。

    • print_cpuinfo:打印CPU的信息。

    • checkboard:确认开发板信息。即,打印出当前开发板的名称信息。

    • init_func_i2c:I2C初始化函数,可在开发板配置文件(/uboot/include/configs/x210_sd.h)中设置U-Boot是否使用I2C。

    • dram_init:DDR初始化函数,由于DDR硬件层面的初始化已在第一阶段完成,故此函数只是通过宏来获取DDR相关信息,并填充至gd结构体。DDR信息相关的宏在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改。

    • display_dram_config:打印bd中的DDR配置信息(由dram_init获取)

    init_fnc_t *init_sequence[] = {    
    cpu_init,
    /* basic cpu dependent setup */
    #if defined(CONFIG_SKIP_RELOCATE_UBOOT)
    reloc_init,
    /* Set the relocation done flag, mustdo this AFTER cpu_init()*/
    #endif
    board_init,
    /* basic board dependent setup */
    interrupt_init,
    /* set up exceptions */
    env_init,
    /* initialize environment */
    init_baudrate,
    /* initialze baudrate settings */
    serial_init,
    /* serial communications setup */
    console_init_f,
    /* stage 1 init of console */
    display_banner,
    /* say that we are here */
    #if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,
    /* display cpu info (and speed) */
    #endif
    #if defined(CONFIG_DISPLAY_BOARDINFO)
    checkboard,
    /* display board info */
    #endif
    #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    init_func_i2c,
    #endif
    dram_init,
    /* configure available RAM banks */
    display_dram_config,
    NULL,
    };

    3)初始化全局变量结构体gd_t、bd_t

    • 本段功能是初始化全局变量结构体。

    • gd_t是保存全集变量的结构体类型,在/uboot/include/asm-arm/Global_data.h文件中定义。

    typedef    struct    global_data {   
    bd_t
    *bd;//该指针指向bd_t结构体,其具体内容涉与板级硬件资源信息相关的全局变量
    unsigned long flags;
    unsigned
    long baudrate; //串口波特率
    unsigned long have_console;//标志位,是否使用控制台console
    unsigned long reloc_off; //重定位有关偏移量
    unsigned long env_addr; //环境变量结构体的偏移量
    usigned long env_valid; //标志位,表示内存中的环境变量能否使用
    unsigned long fb_base; //帧缓存基地址,和显示有关
    #ifdef CONFIG_VFD
    unsigned
    char vfd_type; /* display type */
    #endif
    void **jt; /* jump table *///基本无用
    } gd_t;
    • gd_t结构体中的bd指向一个bd_t结构体,bd_t结构体存放的是和开发板硬件相关的全局变量,在/uboot/include/asm-arm/U-Boot.h文件中定义。

    typedef struct bd_info {    
    int bi_baudrate; //串口波特率
    unsigned long bi_ip_addr; //IP地址
    unsigned char bi_enetaddr[6]; //MAC地址
    struct environment_s *bi_env;
    ulong bi_arch_number; //机器码
    ulong bi_boot_params; //U-Boot给内核传参的地址
    struct //DDR相关信息
    {
    ulong start;
    ulong size;
    }
    bi_dram[CONFIG_NR_DRAM_BANKS];
    #ifdef CONFIG_HAS_ETH1
    unsigned
    char bi_enet1addr[6];
    #endif
    } bd_t;
    • gd(globle data)是指向gd_t结构体的指针,在/uboot/include/asm-arm/Global_data.h文件中定义。

    /*register表示尽量让cpu放在寄存器中,以提高其读写速度;asm (“r8”)是指定放在寄存器的r8*/

    #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
    • 给gd结构体指针分配内存地址,以后可通过gd指针访问全局变量结构体gd_t。

    #ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */  

    ulong gd_base;

    gd_base
    = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);

    ...............

    gd
    = (gd_t*)gd_base;

    #endif
    • 为bd_t结构体分配内存空间,将gd下的一段内存空间分配给bd_t。

    • 清空gd_t、bd_t结构体。

    memset ((void*)gd, 0, sizeof (gd_t));
    gd
    ->bd = (bd_t*)((char*)gd - sizeof(bd_t));
    memset (gd
    ->bd, 0, sizeof (bd_t))

    (2)运行init_sequence数组中所有的初始化函数

    • 利用for循环遍历init_sequence数组,并执行初始化函数。若函数返回值为0,则说明初始化函数执行错误,挂起程序,进入死循环。

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) 
    {
      
    if ((*init_fnc_ptr)() != 0)
      {
        hang ();
      }
    }

    (3)初始化堆内存

    • 配置堆内存的起止地址、终止地址。

    #ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
    mem_malloc_init (CFG_UBOOT_BASE
    + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
    #else
    mem_malloc_init (_armboot_start
    - CFG_MALLOC_LEN);
    #endif

    (4)初始化外部存储设备

    • 若在开发板配置文件中配置过外部存储设备,则进行相应的初始化。

    #if defined(CONFIG_SMDKC110)

    #if defined(CONFIG_GENERIC_MMC)
    puts (
    "SD/MMC: ");
    mmc_exist
    = mmc_initialize(gd->bd);
    if (mmc_exist != 0)
    {
    puts (
    "0 MB\n");
    }
    #endif

    #if defined(CONFIG_MTD_ONENAND)
    puts(
    "OneNAND: ");
    onenand_init();
    /*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
    #else
    //puts("OneNAND: (FSR layer enabled)\n");
    #endif

    #if defined(CONFIG_CMD_NAND)
    puts(
    "NAND: ");
    nand_init();
    #endif

    #endif /* CONFIG_SMDKC110 */

    (5)环境变量的重定位

    • 环境变量的重定位,将环境变量从启动介质中读到DDR内,环境变量的位置是通过原始分区信息表中读到的。

    /* initialize environment */
    env_relocate ();

    (6)获取IP地址和MAC地址

    • 从重定位之后的环境变量中获取IP地址和MAC地址(以太网地址),放到bd中的全局变量内,以供使用。

    /* IP Address */
    gd
    ->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

    /* MAC Address */
    {
    int i;
    ulong reg;
    char *s, *e;
    char tmp[64];

    i
    = getenv_r ("ethaddr", tmp, sizeof (tmp));
    s
    = (i > 0) ? tmp : NULL;


    for (reg = 0; reg < 6; ++reg) {
    gd
    ->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
    if (s)
    s
    = (*e) ? e + 1 : e;
    }

    (7)其他函数

    devices_init ();     /* get the devices list going. *///设备驱动初始化,从linux中移植而来

    jumptable_init ();
    //跳转表初始化,其实没用

    console_init_r ();
    //控制台的第二部分的初始化,有实质性的功能
    enable_interrupts ();//开启中断,实质是一个空函数,U-Boot中并没有使用中断

    (8)进入main_loop循环

    • 至此,U-Boot启动第二阶段结束。即整个U-Boot启动完成,进入main_loop循环。若控制台有任意输入,则进入控制台命令解析-执行的循环。若无,则U-Boot将启动内核。

    for (;;) 
    {
    main_loop ();
    }

    至此,U-Boot启动完成。


    版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

    ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

    关注我的微信公众号,回复“加群”按规则加入技术交流群。


    点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

    李肖遥 公众号“技术让梦想更伟大”,作者:李肖遥,专注嵌入式,只推荐适合你的博文,干货,技术心得,与君共勉。
    评论
    • 随着消费者对汽车驾乘体验的要求不断攀升,汽车照明系统作为确保道路安全、提升驾驶体验以及实现车辆与环境交互的重要组成,日益受到业界的高度重视。近日,2024 DVN(上海)国际汽车照明研讨会圆满落幕。作为照明与传感创新的全球领导者,艾迈斯欧司朗受邀参与主题演讲,并现场展示了其多项前沿技术。本届研讨会汇聚来自全球各地400余名汽车、照明、光源及Tier 2供应商的专业人士及专家共聚一堂。在研讨会第一环节中,艾迈斯欧司朗系统解决方案工程副总裁 Joachim Reill以深厚的专业素养,主持该环节多位
      艾迈斯欧司朗 2025-01-16 20:51 136浏览
    • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
      Industio_触觉智能 2025-01-17 14:09 109浏览
    • 80,000人到访的国际大展上,艾迈斯欧司朗有哪些亮点?感未来,光无限。近日,在慕尼黑electronica 2024现场,ams OSRAM通过多款创新DEMO展示,以及数场前瞻洞察分享,全面展示自身融合传感器、发射器及集成电路技术,精准捕捉并呈现环境信息的卓越能力。同时,ams OSRAM通过展会期间与客户、用户等行业人士,以及媒体朋友的深度交流,向业界传达其以光电技术为笔、以创新为墨,书写智能未来的深度思考。electronica 2024electronica 2024构建了一个高度国际
      艾迈斯欧司朗 2025-01-16 20:45 177浏览
    • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
      华尔街科技眼 2025-01-17 10:44 194浏览
    •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
      MrCU204 2025-01-17 11:30 143浏览
    • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
      Industio_触觉智能 2025-01-20 11:04 99浏览
    • 电竞鼠标应用环境与客户需求电竞行业近年来发展迅速,「鼠标延迟」已成为决定游戏体验与比赛结果的关键因素。从技术角度来看,传统鼠标的延迟大约为20毫秒,入门级电竞鼠标通常为5毫秒,而高阶电竞鼠标的延迟可降低至仅2毫秒。这些差异看似微小,但在竞技激烈的游戏中,尤其在对反应和速度要求极高的场景中,每一毫秒的优化都可能带来致胜的优势。电竞比赛的普及促使玩家更加渴望降低鼠标延迟以提升竞技表现。他们希望通过精确的测试,了解不同操作系统与设定对延迟的具体影响,并寻求最佳配置方案来获得竞技优势。这样的需求推动市场
      百佳泰测试实验室 2025-01-16 15:45 285浏览
    • 百佳泰特为您整理2025年1月各大Logo的最新规格信息,本月有更新信息的logo有HDMI、Wi-Fi、Bluetooth、DisplayHDR、ClearMR、Intel EVO。HDMI®▶ 2025年1月6日,HDMI Forum, Inc. 宣布即将发布HDMI规范2.2版本。新规范将支持更高的分辨率和刷新率,并提供更多高质量选项。更快的96Gbps 带宽可满足数据密集型沉浸式和虚拟应用对传输的要求,如 AR/VR/MR、空间现实和光场显示,以及各种商业应用,如大型数字标牌、医疗成像和
      百佳泰测试实验室 2025-01-16 15:41 188浏览
    • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
      Industio_触觉智能 2025-01-17 14:14 69浏览
    • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
      wuliangu 2025-01-21 00:15 21浏览
    我要评论
    0
    点击右上角,分享到朋友圈 我知道啦
    请使用浏览器分享功能 我知道啦