u-boot启动内幕

一口Linux 2023-12-24 22:09

点击左上方蓝色“一口Linux”,选择“设为星标

第一时间看干货文章 

【干货】嵌入式驱动工程师学习路线
【干货】一个可以写到简历的基于Linux物联网综合项目
【干货】Linux嵌入式知识点-思维导图-免费获取



 1

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



    一口Linux 


    关注,回复【1024】海量Linux资料赠送

    精彩文章合集

    文章推荐

    【专辑】ARM
    【专辑】粉丝问答
    【专辑】所有原创
    专辑linux入门
    专辑计算机网络
    专辑Linux驱动
    【干货】嵌入式驱动工程师学习路线
    【干货】Linux嵌入式所有知识点-思维导图



    一口Linux 写点代码,写点人生!
    评论
    • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
      虹科Pico汽车示波器 2025-01-08 16:51 86浏览
    • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
      华普微HOPERF 2025-01-06 17:23 211浏览
    • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
      hai.qin_651820742 2025-01-07 14:52 113浏览
    • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
      知白 2025-01-07 15:02 145浏览
    • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
      优思学院 2025-01-08 14:54 80浏览
    • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
      GIRtina 2025-01-07 11:02 127浏览
    •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
      晶台光耦 2025-01-08 16:03 75浏览
    • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
      华普微HOPERF 2025-01-06 15:29 172浏览
    • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
      Industio_触觉智能 2025-01-08 00:06 100浏览
    • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
      丙丁先生 2025-01-07 09:25 122浏览
    我要评论
    0
    点击右上角,分享到朋友圈 我知道啦
    请使用浏览器分享功能 我知道啦