前言uboot作为Linux驱动开发的 “三巨头” 之一,绝对是一座绕不开的大山。当然,即使不去细致了解uboot启动流程依旧不影响开发者对uboot的简单移植。但秉持着知其然知其所以然的学习态度,作者将给读者朋友细致化的过一遍uboot启动流程(考虑到硬件平台与uboot版本不一致,实际情况可能有些许出入)。

实验硬件:imx6ull;uboot版本:2016.03

想深挖uboot的启动流程就需要从uboot的链接脚本入手(程序的入口:程序执行的第一条指令被称为程序的入口,这个入口通常就是在链接脚本指定的),打开 arch/arm/cpu/armv7/u-boot.lds 这个文件(下载uboot源码后进行编译得到lds链接脚本):

#include <config.h>
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)    //当前程序的入口函数,定义在arch/arm/lib/vectors.S
SECTIONS
{
#if defined(CONFIG_ARMV7_SECURE_BASE) && defined(CONFIG_ARMV7_NONSEC)
    /DISCARD/ : { *(.rel._secure*) }
#endif
    . = 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.*) }
}

通过查看链接脚本u-boot.lds知道入口点是 arch/arm/lib/vectors.S 文件中的_start函数。

一、uboot总体启动流程

★作者将uboot的总体启动流程分为2部分:arch级初始化(架构)板级初始化

二、arch级初始化

如下图所示,uboot 程序的入口函数_start位于arch/arm/lib/vector.S文件中,_start 开始的是中断向量表,随后通过 b reset 函数跳转到 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,进入板级初始化

补充说明:_start 开始是中断向量表,这段话语部分读者可能存在疑惑,因为自己看 uboot 代码流程发现: _start 进入之后就紧跟着 b reset,并未出现定义中断向量表。其实,大部分开发板的中断向量表是固定定义在 0x0000000 开始的地址上的,比如 reset中断 的地址是 0x00000000

2.1 u-boot.lds链接代码

打开 arch/arm/cpu/armv7/u-boot.lds 这个文件, ENTRY 进入 _start ,代码具体如下:

#include <config.h>
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
#if defined(CONFIG_ARMV7_SECURE_BASE) && defined(CONFIG_ARMV7_NONSEC)
    /DISCARD/ : { *(.rel._secure*) }
#endif
    . = 0x00000000;

    . = ALIGN(4);
    .text :
    {
        *(.__image_copy_start)
        *(.vectors)
        CPUDIR/start.o (.text*)
        *(.text*)
    }

arch/arm/lib/vector.S 中寻找到 _start 定义,并且发现跳转到了 reset

#include <config.h>
.globl _start
    .section ".vectors", "ax"
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word   CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
    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
,,,,

2.2 reset

reset 函数存在于 arch/arm/cpu/armv7/start.s 文件中,而 reset 函数跳转到 save_boot_params 函数save_boot_params 函数又跳转到 save_boot_params_ret 中,具体如下:

//第一段:reset跳转到save_boot_params    
    .globl    reset
    .globl    save_boot_params_ret

reset:
    /* Allow the board to save important registers */
    b    save_boot_params
save_boot_params_ret:
    /*
     * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
     * except if in HYP mode already
     */
    mrs    r0, cpsr
    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

//第二段:save_boot_params跳转到save_boot_params_ret
    ENTRY(save_boot_params)
        b    save_boot_params_ret        @ back to my caller
    ENDPROC(save_boot_params)
        .weak    save_boot_params

2.3 cpsr和cp15 STCLR

reset 函数跳转到 save_boot_params_ret 函数后,具体如下:

save_boot_params_ret:
    /*
     * 关闭 FIQ 和 IRQ 中断,设置 CPU 处于 SVC 特权模式
     * except if in HYP mode already(除非已经处于HYP特权模式,比SVC低一点的特权)
     */
    mrs    r0, cpsr
    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

    /*
     *★SCTLR寄存器bit13清零(bit13控制中断向量表地址,0:可以重定位;1:默认为0xFFFF0000)将_start 
     *的数值写入该寄存器,也就是中断向量表的起始地址为0x87800000
     */
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
    /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
    mrc    p15, 0, r0, c1, c0, 0    @ Read CP15 SCTLR Register
    bic    r0, #CR_V        @ V = 0
    mcr    p15, 0, r0, c1, c0, 0    @ Write CP15 SCTLR Register

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

    /* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl    cpu_init_cp15
    bl    cpu_init_crit
#endif

    bl    _main

从上述代码能看出,代码包含5个部分:cpsr,cp15,cpu_init_cp15,cpu_init_crit_main

2.3.1 cpsr

/*
 * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
 * except if in HYP mode already
 */
mrs    r0, cpsr
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

cpsr给目标寄存器写入对应的数值(可以参考芯片手册),关闭 FIQ 和 IRQ,并且讲CPU设置为 SVC 特权模式。
为什么这样操作的原因
(1) 禁止 FIQ 和 IRQuboot 作为芯片上电的第一道程序是至关重要的,而中断是很危险的,它的等级太高了,可以轻松打断程序的正常运行。所以,为了保证uboot的正常运行,需要关闭部分中断。
(2) 设置 SVCuboot的运行避免不了对各类寄存器等操作,为了保证操作的正常,需要给CPU设置高特权模式。

2.3.2 cp15

/*
 * Setup vector:
 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
 * Continue to use ROM code vector only in OMAP4 spl)
 */
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
    /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
    mrc    p15, 0, r0, c1, c0, 0    @ Read CP15 SCTLR Register
    bic    r0, #CR_V        @ V = 0
    mcr    p15, 0, r0, c1, c0, 0    @ Write CP15 SCTLR Register

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

cp15 SCTLRSCTLR寄存器bit13清零(bit13控制中断向量表地址,0:可以重定位;1:默认为0xFFFF0000)将_start的数值写入该寄存器,也就是中断向量表的起始地址为0x87800000

2.4 cpu_init_cp15

cpu_init_cp15 存在于 arch/arm/cpu/armv7/start.s 文件中:

/*************************************************************************
 *
 * cpu_init_cp15
 *
 * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
 * CONFIG_SYS_ICACHE_OFF is defined.
 *
 *************************************************************************/
ENTRY(cpu_init_cp15)
    /*
     * Invalidate L1 I/D
     */
    mov    r0, #0            @ set up for MCR
    mcr    p15, 0, r0, c8, c7, 0    @ invalidate TLBs  /*禁止从TLB中取地址描述符,也就是禁止虚拟地址到物理地址的转换,因为刚开始操作的都是物理寄存器!*/
    mcr    p15, 0, r0, c7, c5, 0    @ invalidate icache     /*关闭指令cache*/
    mcr    p15, 0, r0, c7, c5, 6    @ invalidate BP array    /*关闭分支预测*/
    mcr p15, 0, r0, c7, c10, 4    @ DSB     /*多核cpu之间进行数据同步*/
    mcr p15, 0, r0, c7, c5, 4    @ ISB /*进行指令同步,放弃流水线中已经取到的指令,重新取指令*/


    /*
     * 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 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
#endif
    mcr    p15, 0, r0, c1, c0, 0

#ifdef CONFIG_ARM_ERRATA_716044
    mrc    p15, 0, r0, c1, c0, 0    @ read system control register
    orr    r0, r0, #1 << 11    @ set bit #11
    mcr    p15, 0, r0, c1, c0, 0    @ write system control register
#endif

#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))
    mrc    p15, 0, r0, c15, c0, 1    @ read diagnostic register
    orr    r0, r0, #1 << 4        @ set bit #4
    mcr    p15, 0, r0, c15, c0, 1    @ write diagnostic register
#endif

#ifdef CONFIG_ARM_ERRATA_743622
    mrc    p15, 0, r0, c15, c0, 1    @ read diagnostic register
    orr    r0, r0, #1 << 6        @ set bit #6
    mcr    p15, 0, r0, c15, c0, 1    @ write diagnostic register
#endif

#ifdef CONFIG_ARM_ERRATA_751472
    mrc    p15, 0, r0, c15, c0, 1    @ read diagnostic register
    orr    r0, r0, #1 << 11    @ set bit #11
    mcr    p15, 0, r0, c15, c0, 1    @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_761320
    mrc    p15, 0, r0, c15, c0, 1    @ read diagnostic register
    orr    r0, r0, #1 << 21    @ set bit #21
    mcr    p15, 0, r0, c15, c0, 1    @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_845369
    mrc    p15, 0, r0, c15, c0, 1    @ read diagnostic register
    orr    r0, r0, #1 << 22    @ set bit #22
    mcr    p15, 0, r0, c15, c0, 1    @ write diagnostic register
#endif

    mov    r5, lr            @ Store my Caller
    mrc    p15, 0, r1, c0, c0, 0    @ r1 has Read Main ID Register (MIDR)
    mov    r3, r1, lsr #20        @ get variant field
    and    r3, r3, #0xf        @ r3 has CPU variant
    and    r4, r1, #0xf        @ r4 has CPU revision
    mov    r2, r3, lsl #4        @ shift variant field for combined value
    orr    r2, r4, r2        @ r2 has combined CPU variant + revision
#ifdef CONFIG_ARM_ERRATA_798870
    cmp    r2, #0x30        @ Applies to lower than R3p0
    bge    skip_errata_798870      @ skip if not affected rev
    cmp    r2, #0x20        @ Applies to including and above R2p0
    blt    skip_errata_798870      @ skip if not affected rev
    mrc    p15, 1, r0, c15, c0, 0  @ read l2 aux ctrl reg
    orr    r0, r0, #1 << 7         @ Enable hazard-detect timeout
    push    {r1-r5}            @ Save the cpu info registers
    bl    v7_arch_cp15_set_l2aux_ctrl
    isb                @ Recommended ISB after l2actlr update
    pop    {r1-r5}            @ Restore the cpu info - fall through
skip_errata_798870:
#endif

#ifdef CONFIG_ARM_ERRATA_801819
    cmp    r2, #0x24        @ Applies to lt including R2p4
    bgt    skip_errata_801819      @ skip if not affected rev
    cmp    r2, #0x20        @ Applies to including and above R2p0
    blt    skip_errata_801819      @ skip if not affected rev
    mrc    p15, 0, r0, c0, c0, 6    @ pick up REVIDR reg
    and    r0, r0, #1 << 3        @ check REVIDR[3]
    cmp    r0, #1 << 3
    beq    skip_errata_801819    @ skip erratum if REVIDR[3] is set

    mrc    p15, 0, r0, c1, c0, 1    @ read auxilary control register
    orr    r0, r0, #3 << 27    @ Disables streaming. All write-allocate
                    @ lines allocate in the L1 or L2 cache.
    orr    r0, r0, #3 << 25    @ Disables streaming. All write-allocate
                    @ lines allocate in the L1 cache.
    push    {r1-r5}            @ Save the cpu info registers
    bl    v7_arch_cp15_set_acr
    pop    {r1-r5}            @ Restore the cpu info - fall through
skip_errata_801819:
#endif

#ifdef CONFIG_ARM_ERRATA_454179
    cmp    r2, #0x21        @ Only on < r2p1
    bge    skip_errata_454179

    mrc    p15, 0, r0, c1, c0, 1    @ Read ACR
    orr    r0, r0, #(0x3 << 6)    @ Set DBSM(BIT7) and IBE(BIT6) bits
    push    {r1-r5}            @ Save the cpu info registers
    bl    v7_arch_cp15_set_acr
    pop    {r1-r5}            @ Restore the cpu info - fall through

skip_errata_454179:
#endif

#ifdef CONFIG_ARM_ERRATA_430973
    cmp    r2, #0x21        @ Only on < r2p1
    bge    skip_errata_430973

    mrc    p15, 0, r0, c1, c0, 1    @ Read ACR
    orr    r0, r0, #(0x1 << 6)    @ Set IBE bit
    push    {r1-r5}            @ Save the cpu info registers
    bl    v7_arch_cp15_set_acr
    pop    {r1-r5}            @ Restore the cpu info - fall through

skip_errata_430973:
#endif

#ifdef CONFIG_ARM_ERRATA_621766
    cmp    r2, #0x21        @ Only on < r2p1
    bge    skip_errata_621766

    mrc    p15, 0, r0, c1, c0, 1    @ Read ACR
    orr    r0, r0, #(0x1 << 5)    @ Set L1NEON bit
    push    {r1-r5}            @ Save the cpu info registers
    bl    v7_arch_cp15_set_acr
    pop    {r1-r5}            @ Restore the cpu info - fall through

skip_errata_621766:
#endif

    mov    pc, r5            @ back to my caller
ENDPROC(cpu_init_cp15)

cpu_init_cp15关闭MMU和Cache
为什么这样操作的原因
(1) 关闭MMU因为MMU是把虚拟地址转化为物理地址得作用而我们现在是要设置控制寄存器,而控制寄存器本来就是实地址(物理地址),再使能MMU,不就是多此一举了吗?
(2) 关闭cachecache和MMU是通过CP15管理的,刚上电的时候,CPU还不能管理他们。所以上电的时候MMU必须关闭,指令cache可关闭,可不关闭,但数据cache一定要关闭否则可能导致刚开始的代码里面,去取数据的时候,从cache里面取,而这时候RAM中数据还没有cache过来,导致数据预取异常。

2.5 cpu_init_crit

cpu_init_crit 存在于 arch/arm/cpu/armv7/start.s 文件中,cpu_init_crit 代码比较简单:b lowlevel_init。所以,直接去研究 lowlevel_init 函数,lowlevel_init 函数存在 arch/arm/cpu/armv7/lowlevel_init.S 文件中:

ENTRY(lowlevel_init)
    /*
     * Setup a temporary stack. Global data is not available yet.
     */
    ldr    sp, =CONFIG_SYS_INIT_SP_ADDR
    bic    sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
    mov    r9, #0
#else
    /*
     * Set up global data for boards that still need it. This will be
     * removed soon.
     */
#ifdef CONFIG_SPL_BUILD
    ldr    r9, =gdata
#else
    sub    sp, sp, #GD_SIZE
    bic    sp, sp, #7
    mov    r9, sp
#endif
#endif
    /*
     * Save the old lr(passed in ip) and the current lr to stack
     */
    push    {ip, lr}
    /*
     * Call the very early init function. This should do only the
     * absolute bare minimum to get started. It should not:
     *
     * - set up DRAM
     * - use global_data
     * - clear BSS
     * - try to start a console
     *
     * For boards with SPL this should be empty since SPL can do all of
     * this init in the SPL board_init_f() function which is called
     * immediately after this.
     */
    bl    s_init
    pop    {ip, pc}
ENDPROC(lowlevel_init)

考虑到 cpu_init_crit 函数是 arch 级初始化中重要的函数,所以,作者这里详解给大家分析一下。

设置SP指向CONFIG_SYS_INIT_SP_ADDR=0X0091FF00,流程见上图。可结合下图自己分析

设置SP 8字节对齐
设置gd(global data)大小为248B
sp地址(0x0091FE08)保存在r9寄存器中

将ip和lr入栈
跳转s_init //对于IMX6ULL而言,什么都没有做(空函数)
将入栈的ip和lr出栈,并将lr赋给pc

三、板级初始化

_main 函数存在于 arch/arm/lib/crt0.S 中,_main 包含六个部分,简单来说主要是把ROM中的程序拷贝至DDR(加快uboot的运行),然后载重定向,最后初始化各种外设。_mian 函数具体如下:

ENTRY(_main)

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr    sp, =(CONFIG_SPL_STACK)
#else
    ldr    sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M)    /* v7M forbids using SP as BIC destination */
    mov    r3, sp
    bic    r3, r3, #7
    mov    sp, r3
#else
    bic    sp, sp, #7    /* 8-byte alignment for ABI compliance */
#endif
    mov    r0, sp
    bl    board_init_f_alloc_reserve
    mov    sp, r0
    /* set up gd here, outside any C code */
    mov    r9, r0
    bl    board_init_f_init_reserve

    mov    r0, #0
    bl    board_init_f

#if ! defined(CONFIG_SPL_BUILD)

/*
 * Set up intermediate environment (new sp and gd) and call
 * relocate_code(addr_moni). Trick here is that we'll return
 * 'here' but relocated.
 */

    ldr    sp, [r9, #GD_START_ADDR_SP]    /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M)    /* v7M forbids using SP as BIC destination */
    mov    r3, sp
    bic    r3, r3, #7
    mov    sp, r3
#else
    bic    sp, sp, #7    /* 8-byte alignment for ABI compliance */
#endif
    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
#if defined(CONFIG_CPU_V7M)
    orr    lr, #1                /* As required by Thumb-only */
#endif
    ldr    r0, [r9, #GD_RELOCADDR]        /* r0 = gd->relocaddr */
    b    relocate_code
here:
/*
 * now relocate vectors
 */

    bl    relocate_vectors

/* Set up final (full) environment */

    bl    c_runtime_cpu_setup    /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD
    /* Use a DRAM stack for the rest of SPL, if requested */
    bl    spl_relocate_stack_gd
    cmp    r0, #0
    movne    sp, r0
    movne    r9, r0
# endif
    ldr    r0, =__bss_start    /* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
    ldr    r3, =__bss_end        /* this is auto-relocated! */
    mov    r1, #0x00000000        /* prepare zero to clear BSS */

    subs    r2, r3, r0        /* r2 = memset len */
    bl    memset
#else
    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 */
#if defined(CONFIG_CPU_V7M)
    itt    lo
#endif
    strlo    r2, [r0]        /* clear 32-bit BSS word */
    addlo    r0, r0, #4        /* move to next */
    blo    clbss_l
#endif

#if ! defined(CONFIG_SPL_BUILD)
    bl coloured_LED_init
    bl red_led_on
#endif
    /* 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 */
#if defined(CONFIG_SYS_THUMB_BUILD)
    ldr    lr, =board_init_r    /* this is auto-relocated! */
    bx    lr
#else
    ldr    pc, =board_init_r    /* this is auto-relocated! */
#endif
    /* we should not return here. */
#endif

ENDPROC(_main)

3.1 board_init_f_alloc_reserve

board_init_f_alloc_reserve 函数位于 common/init/board_init.c 文件中,具体如下:

ulong board_init_f_alloc_reserve(ulong top)
{
    /* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
    top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
    /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
    top = rounddown(top-sizeof(struct global_data), 16);

    return top;
}

board_init_f_alloc_reserve 函数目的:留出早期的 malloc 内存区域和 gd 内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400( 在文件 include/generated/autoconf.h 中定义 ) ,sizeof(struct global_data)=248(GD_SIZE 值),完成以后的内存分布如图所示(top返回地址为:0x0091FA00):

3.2 board_init_f_init_reserve

board_init_f_init_reserve 函数同样位于common/init/board_init.c 文件中,具体如下:

void board_init_f_init_reserve(ulong base)
{
    struct global_data *gd_ptr;
#ifndef _USE_MEMCPY
    int *ptr;
#endif

    /*
     * clear GD entirely and set it up.
     * Use gd_ptr, as gd may not be properly set yet.
     */

    gd_ptr = (struct global_data *)base;
    /* zero the area */
#ifdef _USE_MEMCPY
    memset(gd_ptr, '\0', sizeof(*gd));
#else
    for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )
        *ptr++ = 0;
#endif
    /* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)
    arch_setup_gd(gd_ptr);
#endif
    /* next alloc will be higher by one GD plus 16-byte alignment */
    base += roundup(sizeof(struct global_data), 16);

    /*
     * record early malloc arena start.
     * Use gd as it is now properly set for all architectures.
     */

#if defined(CONFIG_SYS_MALLOC_F)
    /* go down one 'early malloc arena' */
    gd->malloc_base = base;
    /* next alloc will be higher by one 'early malloc arena' size */
    base += CONFIG_SYS_MALLOC_F_LEN;
#endif
}

board_init_f_init_reserve 函数目的:初始化gd(全局变量)

3.3 board_init_f

board_init_f 函数定义在文件 common/board_f.c 中,具体代码如下:

void board_init_f(ulong boot_flags)
{
#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;

    /*
     * 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();
#endif

    gd->flags = boot_flags;
    gd->have_console = 0;

    if (initcall_run_list(init_sequence_f))
        hang();

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

    /* Light up LED1 */
    imx6_light_up_led1();
}

board_init_f 函数主要有两个工作
1、 初始化一系列外设,比如串口、定时器,或者打印一些消息等。
2、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就
是将自己拷贝到 DRAM 最后面的内存区域中。

board_init_f 函数中的 initcall_run_list 函数主要用于调用一系列函数,值保存在 init_sequence_f 函数中。

initcall_run_list 函数的具体主要功能如下(可结合上图分析):

1、gd->num_len=_bss_end-_start//uboot image大小,即代码长度,0X878A8E74-0x87800000=0XA8EF4
2、initf_malloc()//gd->malloc=CONFIG_SYS_MALLOC_F_LEN=0X400,内存池大小为0x400
3、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=115200
…
24、dram_init()//设置gd->ram_size=512MB 0X2000 0000B
…
44、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=0X9EF44E90
…
61、setup_reloc//设置gd其他一些成员变量,供后面定位使用,并且将以前的gd拷贝到gd->new_gd处

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

3.4 relocate_code

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

ENTRY(relocate_code)    
    ldr r1,=__image_copy_start//r1=0X8780000源地址起始地址
    subs r4, r0, r1//r4=0X9FF47000-0X87800000=0X18747000 偏移
    beq    relocate_done        /* skip relocation */
    ldr r2, =__image_copy_end//r2=0X8785dc6c源地址结束地址

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

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

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

    /* ARMv4- don't know bx lr but the assembler fails to see that */

#ifdef __ARM_ARCH_4__
    mov    pc, lr
#else
    bx    lr
#endif

ENDPROC(relocate_code)

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

核心代码:
    /*
     * 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

核心代码程序分析如下

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.5 relocate_vector

relocate_vectors 函数同样位于 arch/arm/lib/relocate.S 中,relocate_vectors 函数的目的:重定位中断向量表(设置VBAR寄存器为重定位后的中断向量表起始地址)

ENTRY(relocate_vectors)

#ifdef CONFIG_CPU_V7M
    /*
     * On ARMv7-M we only have to write the new vector address
     * to VTOR register.
     */
    ldr    r0, [r9, #GD_RELOCADDR]    /* r0 = gd->relocaddr */
    ldr    r1, =V7M_SCB_BASE
    str    r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR
    /*
     * If the ARM processor has the security extensions,
     * use VBAR to relocate the exception vectors.
     */
    ldr    r0, [r9, #GD_RELOCADDR]    /* r0 = gd->relocaddr */
    mcr     p15, 0, r0, c12, c0, 0  /* Set VBAR */
#else
    /*
     * Copy the relocated exception vectors to the
     * correct address
     * CP15 c1 V bit gives us the location of the vectors:
     * 0x00000000 or 0xFFFF0000.
     */
    ldr    r0, [r9, #GD_RELOCADDR]    /* r0 = gd->relocaddr */
    mrc    p15, 0, r2, c1, c0, 0    /* V bit (bit[13]) in CP15 c1 */
    ands    r2, r2, #(1 << 13)
    ldreq    r1, =0x00000000        /* If V=0 */
    ldrne    r1, =0xFFFF0000        /* If V=1 */
    ldmia    r0!, {r2-r8,r10}
    stmia    r1!, {r2-r8,r10}
    ldmia    r0!, {r2-r8,r10}
    stmia    r1!, {r2-r8,r10}
#endif
#endif
    bx    lr

ENDPROC(relocate_vectors)

3.6 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 中。

board_init_r 函数如下:

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
    int i;
#endif

#ifdef CONFIG_AVR32
    mmu_init_r(dest_addr);
#endif

#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
    gd = new_gd;
#endif

#ifdef CONFIG_NEEDS_MANUAL_RELOC
    for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
        init_sequence_r[i] += gd->reloc_off;
#endif

    if (initcall_run_list(init_sequence_r))
        hang();

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

上段代码中关键的是 initcall_run_list 函数,调用 initcall_run_list 函数来执行初始化序列 init_sequence_rinit_sequence_r 是一个函数集合, init_sequence_r 也定义在文件 common/board_r.c 中,由于 init_sequence_f 的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的 init_sequence_r 定义如下:

1 init_fnc_t init_sequence_r[] = {
2 initr_trace,
3 initr_reloc,
4 initr_caches,
5 initr_reloc_global_data,
6 initr_barrier,
7 initr_malloc,
8 initr_console_record,
9 bootstage_relocate,
10 initr_bootstage,
11 board_init, /* Setup chipselects */
12 stdio_init_tables,
13 initr_serial,
14 initr_announce,
15 INIT_FUNC_WATCHDOG_RESET
16 INIT_FUNC_WATCHDOG_RESET
17 INIT_FUNC_WATCHDOG_RESET
18 power_init_board,
19 initr_flash,
20 INIT_FUNC_WATCHDOG_RESET
21 initr_nand,
22 initr_mmc,
23 initr_env,
24 INIT_FUNC_WATCHDOG_RESET
25 initr_secondary_cpu,
26 INIT_FUNC_WATCHDOG_RESET
27 stdio_add_devices,
28 initr_jumptable,
29 console_init_r, /* fully init console as a device */
30 INIT_FUNC_WATCHDOG_RESET
31 interrupt_init,
32 initr_enable_interrupts,
33 initr_ethaddr,
34 board_late_init,
35 INIT_FUNC_WATCHDOG_RESET
36 INIT_FUNC_WATCHDOG_RESET
37 INIT_FUNC_WATCHDOG_RESET
38 initr_net,
39 INIT_FUNC_WATCHDOG_RESET
40 run_main_loop,
41 };

第 2 行, initr_trace 函数,如果定义了宏 CONFIG_TRACE 的话就会调用函数 trace_init,初始化和调试跟踪有关的内容。
第 3 行, initr_reloc 函数用于设置 gd->flags,标记重定位完成。
第 4 行, initr_caches 函数用于初始化 cache,使能 cache。
第 5 行, initr_reloc_global_data 函数,初始化重定位后 gd 的一些成员变量。
第 6 行, initr_barrier 函数, I.MX6ULL 未用到。
第 7 行, initr_malloc 函数,初始化 malloc。
第 8 行, initr_console_record 函数,初始化控制台相关的内容, I.MX6ULL 未用到,空函数。
第 9 行, bootstage_relocate 函数,启动状态重定位。
第 10 行, initr_bootstage 函数,初始化 bootstage 什么的。
第 11 行, board_init 函数,板级初始化,包括 74XX 芯片, I2C、 FEC、 USB 和 QSPI 等。
这里执行的是 mx6ull_alientek_emmc.c 文件中的 board_init 函数。
第 12 行, stdio_init_tables 函数, stdio 相关初始化。
第 13 行, initr_serial 函数,初始化串口。
第 14 行, initr_announce 函数,与调试有关,通知已经在 RAM 中运行。
第 18 行, power_init_board 函数,初始化电源芯片
第 19 行, initr_flash 函数,对于 I.MX6ULL 而言,没有定义宏 CONFIG_SYS_NO_FLASH的话函数 initr_flash 才有效。但是 mx6_common.h 中定义了宏 CONFIG_SYS_NO_FLASH,所以此函数无效。
第 21 行, initr_nand 函数,初始化 NAND,如果使用 NAND 版本核心板的话就会初始化NAND。
第 22 行, initr_mmc 函数,初始化 EMMC,如果使用 EMMC 版本核心板的话就会初始化EMMC
第 23 行, initr_env 函数,初始化环境变量。
第 25 行, initr_secondary_cpu 函数,初始化其他 CPU 核, I.MX6ULL 只有一个核,因此此函数没用。
第 27 行, stdio_add_devices 函数,各种输入输出设备的初始化,如 LCD driver
第 28 行, initr_jumptable 函数,初始化跳转表。
第 29 行 , console_init_r 函 数 , 控 制 台 初 始 化 , 初 始 化 完 成 以 后 此 函 数 会 调 用stdio_print_current_devices 函数来打印出当前的控制台设备
第 31 行, interrupt_init 函数,初始化中断。
第 32 行, initr_enable_interrupts 函数,使能中断。
第 33 行, initr_ethaddr 函数,初始化网络地址,也就是获取 MAC 地址。读取环境变量“ethaddr”的值。
第 34 行, board_late_init 函数,板子后续初始化,如果环境变量存储在 EMMC 或者 SD 卡中的话此函数会调用 board_late_mmc_env_init 函数初始化 EMMC/SD。会切换到正在时候用的 emmc 设备
第 38 行 , initr_net 函 数 , 初 始 化 网 络 设 备
第 40 行, run_main_loop 行,主循环,处理命令。

run_main_loop 函数中最重要的是 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启动流程的详细解读。

四、uboot启动流程总结

uboot启动流程总结

uboot的语言构成:10%的汇编语言;90%的C语言
uboot的启动特性:稳定性;速度
uboot的简化版启动流程:
1、设置状态寄存器 cpsr ,使CPU进入 SVC 特权模式,并且禁止 FIQ 和 IRQ;
2、关闭看门狗、中断、MMU、Cache;
3、初始化部分寄存器和外设(时钟、串口、Flash、内存);
4、自搬移uboot到内存中运行;
5、设置栈空间并初始化global_data;
6、剩余大部分硬件的初始化;
7、搬移Linux内核到内存;