• 海思uboot启动流程详细分析(转)


    第一阶段 start.S

    首先我们可以在u-boot.lds中看到ENTRY(_start),即指定了入口_start_start也就是整个start.S的最开始;

    1. reset

    archarmcpuarmv8hi3559av100中的start.S

    注意x30在ARMV8中代表lr寄存器

    reset:
        /*
         * Could be EL3/EL2/EL1, Initial State:
         * Little Endian, MMU Disabled, i/dCache Disabled
         */
        adr    x0, vectors
        switch_el x1, 3f, 2f, 1f
    3:  msr    vbar_el3, x0
        mrs    x0, scr_el3
        orr    x0, x0, #0xf             /* SCR_EL3.NS|IRQ|FIQ|EA */
        msr    scr_el3, x0
        msr    cptr_el3, xzr            /* Enable FP/SIMD */
    #ifdef COUNTER_FREQUENCY
        ldr    x0, =COUNTER_FREQUENCY
        msr    cntfrq_el0, x0           /* Initialize CNTFRQ */
    #endif
        b      0f
    2:  msr    vbar_el2, x0
        mov    x0, #0x33ff
        msr    cptr_el2, x0             /* Enable FP/SIMD */
        b      0f
    1:  msr    vbar_el1, x0
        mov    x0, #3 << 20
        msr    cpacr_el1, x0            /* Enable FP/SIMD */
    0:
        /*
         * Cache/BPB/TLB Invalidate
         * i-cache is invalidated before enabled in icache_enable()
         * tlb is invalidated before mmu is enabled in dcache_enable()
         * d-cache is invalidated before enabled in dcache_enable()
         */
    
        /*
         *  read system register REG_SC_GEN2
         *  check if ziju flag
         */
        ldr    x0, =SYS_CTRL_REG_BASE
        ldr    w1, [x0, #REG_SC_GEN2]
        ldr    w2, =0x7a696a75         /* magic for "ziju" */
        cmp    w1, w2
        bne    normal_start_flow
        mov    x1, sp                  /* save sp */
        str    w1, [x0, #REG_SC_GEN2]  /* clear ziju flag */

    adr x0, vectors,其中的vectors代表了异常向量表

    主要做了如下事情:

    1)reset SCTRL寄存器

    具体可参考reset_sctrl函数,由CONFIG_SYS_RESET_SCTRL控制,一般不需要打开。该配置项的解释如下:

    Reset the SCTRL register at the very beginning of execution to avoid interference from stale mappings set up by early firmware/loaders/etc. http://lists.denx.de/pipermail/u-boot/2015-April/211147.html

    2)根据当前的EL级别,配置中断向量、MMU、Endian、i/d Cache等。

    3)配置ARM的勘误表

    具体可参考apply_core_errata函数,由CONFIG_ARM_ERRATA_XXX控制,在项目的初期,可以不打开,后续根据实际情况打开)。

    2. normal_start_flow流程

    这里是正常启动流程

    normal_start_flow:
        /* set stack for C code  */
        ldr    x0, =(CONFIG_SYS_INIT_SP_ADDR)
        bic    sp, x0, #0xf            /* 16-byte alignment for ABI compliance */
    
        bl      uart_early_init
        adr     x0, Str_SystemSartup
        bl      uart_early_puts
        ldr  x0, =0x1202008c
        ldr w0, [x0]
        bl uart_early_put_hex
        /* enable I-Cache  */
        bl     icache_enable

    1)设置代码的堆栈 2.)跳转到uart_early_init

    因为uart_early_init是全局的伪汇编指令(在uart.S中定义),所以在start.S中也可以使用到

    3)声明一个字符串Str_SystemSartup 4)使能icache

    因为bne normal_start_flow是不跳转回来的,所以会继续向下执行

    3. running_addr_check流程

    判断是否进入not_ddr_init中,不需要DDR初始化,直接copy到DDR中

    check_boot_mode:
        ldr    x0, =SYS_CTRL_REG_BASE
        ldr    w0, [x0, #REG_SYSSTAT]
        lsr    w6, w0, #4
        and    w6, w6, #0x3
        cmp    w6, #BOOT_FROM_EMMC  //判断是不是EMMC启动
        bne    ufs_boot //如果不是,则进入ufs_boot

    4. ziju_flow流程

    自举模式从这里我可以推断出,芯片的启动分为两种,一种是自举模式也就是本地的spiflash或nand或emmc等启动,另一种就是pcie启动模式。不同启动模式对应不同的启动流程。但不同启动模式代码是相互交织的,需要分清楚!

    1) 初始化PLL和DDRC控制器和管脚复用情况。

    /* init PLL/DDRC/pin mux/... */
        ldr r0, _blank_zone_start
        ldr r1, _TEXT_BASE
        sub r0, r0, r1
        ldr r1, =RAM_START_ADRS
        add r0, r0, r1
        mov r1, #0x0                 /* flags: 0->normal 1->pm */
        bl  init_registers           /* init PLL/DDRC/... */

    bl init_registers这个函数是初始化一些寄存器,这些寄存器分了很多,包括中断、网络、哈希功能形式的寄存器,初始化的意思就是给一个值,但这值一般没什么意义,具体的寄存器,后面会再进行配置!

    2) start_ddr_training

    /* DDR training:DR布线,完全按等长约束就没有ddr training的说法。 当布线去掉等长约束或放宽约束条件,就要做ddr training,以保证时序的完整性,使信号的建立&保持时间窗口一致。ddr training是调整Addr/Cmd信号对CLK,DQ信号对DQS的延时。由于没做等长约束,信号有长,有短,就会导致信号有快,慢之差(信号在1000mil走线耗时约160~180ps,相对FR-4的板材),ddr training就是找到一套参数,使信号的建立与保持时间充足。并保存且写到配置中。*/

    3) pcie_slave_boot

    5. jump_to_ddr

    自举模式省略了一些PCIE判断的情况的解释,我也没怎么看懂

    jump_to_ddr:
        adr    x0, _start_armboot
        ldr    x30,[x0]
        ret

    开始进入跳转到C语言阶段

    总结

    1. 关cache,关mmu,SVC模式
    2. 检测是不是自举模式还是pcie启动,也包括是冷启动还是热启动
    3. 串口初始化
    4. DDR初始化和DDR training
    5. 正常启动时,会检测启动方式,对代码进行相应的拷贝,重定位
    6. 设置堆栈
    7. 清bss段
    8. 跳转到第二阶段,即C语言阶段

    1. 第二个start.S

    start_armboot开始,在startup.c中有包含#include <config.h>

    在config.h中:

    /* Automatically generated - do not edit */
    #define CONFIG_BOARDDIR board/hisilicon/hi3559av100
    #include <config_defaults.h>
    #include <config_uncmd_spl.h>
    #include <configs/hi3559av100.h>
    #include <asm/config.h>
    #include <config_fallbacks.h>

    hi3559av100.h中:

    #define CONFIG_SYS_TEXT_BASE            0x48800000

    hi3559av100.h中,看到了CONFIG_SYS_TEXT_BASE的宏

    /* CONFIG_SYS_TEXT_BASE needs to align with where ATF loads bl33.bin */
    #define CONFIG_SYS_TEXT_BASE        0x48800000

    查看u-boot.map在这里又来到了上一层的start.S中来,所以可以知道这两个是由两个文件组成的,一个是u-boot.bin和reg_info.bin,就是说两个不同的start.S的流程来合成一个最终的u-boot-hi3559av100.bin

    这个便是正常的流程了:

    本文将结合u-boot的“board—>machine—>arch—>cpu”框架,介绍u-boot中平台相关部分的启动流程。并通过对启动流程的简单分析,掌握u-boot移植的基本方法。

    2. 多平台架构

    这些问题的本质,是软件工程中的抽象和封装,以最简洁、最高效的方式,实现尽可能多的功能。u-boot作为一个跨平台、跨设备的bootloader,同样会面临这些问题。它的解决方案,就是“board—>machine—>arch—>cpu”框架,如下:

    基于图片1的架构,u-boot和平台有关的初始化流程,显得比较直观、清晰:

    1)u-boot启动后,会先执行CPU(如armv8)的初始化代码。 2)CPU相关的代码,会调用ARCH的公共代码(如arch/arm)。 3)ARCH的公共代码,在适当的时候,调用board有关的接口。u-boot的功能逻辑,大多是由common代码实现,部分和平台有关的部分,则由公共代码声明,由board代码实现。 4)board代码在需要的时候,会调用machine(arch/arm/mach-xxx)提供的接口,实现特定的功能。因此machine的定位是提供一些基础的代码支持,不会直接参与到u-boot的功能逻辑中。

    3. 平台相关部分的启动流程分析

    本文先不涉及u-boot和平台相关的Kconfig/Makefile部分,以ARM64为例,假定u-boot首先从“arch/arm/cpu/armv8/start.S”的_start接口开始执行。因此我们从_start开始分析。

    3.1 _start

    _start是u-boot启动后的第一个执行地址,对armv8来说,它只是简单的跳转到reset处执行,如下:

    .globl  _start
    _start:
        b   reset

    3.2 reset

    reset:
        /* Allow the board to save important registers */
        b   save_boot_params
    .globl  save_boot_params_ret
    save_boot_params_ret:
    
    #ifdef CONFIG_SYS_RESET_SCTRL
        bl reset_sctrl
    #endif
        /*
         * Could be EL3/EL2/EL1, Initial State:
         * Little Endian, MMU Disabled, i/dCache Disabled
         */
        adr x0, vectors
        switch_el x1, 3f, 2f, 1f
    3:  msr vbar_el3, x0
        mrs x0, scr_el3
        orr x0, x0, #0xf            /* SCR_EL3.NS|IRQ|FIQ|EA */
        msr scr_el3, x0
        msr cptr_el3, xzr           /* Enable FP/SIMD */
    #ifdef COUNTER_FREQUENCY
        ldr x0, =COUNTER_FREQUENCY
        msr cntfrq_el0, x0          /* Initialize CNTFRQ */
    #endif
        b   0f
    2:  msr vbar_el2, x0
        mov x0, #0x33ff
        msr cptr_el2, x0            /* Enable FP/SIMD */
        b   0f
    1:  msr vbar_el1, x0
        mov x0, #3 << 20
        msr cpacr_el1, x0           /* Enable FP/SIMD */
    0:
    
        /* Apply ARM core specific erratas */
        bl  apply_core_errata
    
        /*
         * Cache/BPB/TLB Invalidate
         * i-cache is invalidated before enabled in icache_enable()
         * tlb is invalidated before mmu is enabled in dcache_enable()
         * d-cache is invalidated before enabled in dcache_enable()
         */
    
        /* Processor specific initialization */
        bl  lowlevel_init

    1)reset SCTRL寄存器

    具体可参考reset_sctrl函数,由CONFIG_SYS_RESET_SCTRL控制,一般不需要打开。该配置项的解释如下:

    Reset the SCTRL register at the very beginning of execution to avoid interference from stale mappings set up by early firmware/loaders/etc. http://lists.denx.de/pipermail/u-boot/2015-April/211147.html

    2)根据当前的EL级别,配置中断向量、MMU、Endian、i/d Cache等。

    3)配置ARM的勘误表

    具体可参考apply_core_errata函数,由CONFIG_ARM_ERRATA_XXX控制,在项目的初期,可以不打开,后续根据实际情况打开)。

    就是ARM有一些bug,但可以通过软件的方法绕过去,由u-boot的代码注释可知,应该只有Cortex-A57才有。具体什么bug,我也没有去研究

    4)调用lowlevel_init的功能解释如下(具体可参考u-boot的readme文档):

    • purpose: essential init to permit execution to reach board_init_f()

    - no global_data or BSS - there is no stack (ARMv7 may have one but it will soon be removed) - must not set up SDRAM or use console - must only do the bare minimum to allow execution to continue to board_init_f() - this is almost never needed - return normally from this function

    海思的和原生uboot代码的start.S其实就是增加以下内容

    5)如果是多CPU的场景,处理其它的CPU的boot

    多CPU功能由CONFIG_ARMV8_MULTIENTRY控制,不需要打开。

    6)跳转到arm公共的_main中执行

    ARM64平台的_main位于crt0_64.S文件中,具体请参考下面的描述。

    3.3 _main

    crt0是C-runtime Startup Code的简称,意思就是运行C代码之前的准备工作。关于_main函数,crt0_64.S中有非常详细的注释(这一点要给u-boot点100个赞!),大家可以参考。该函数的定义如下:

    ENTRY(_main)
    
    /*
     * Set up initial C runtime environment and call board_init_f(0).
     */
    #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
        ldr x0, =(CONFIG_SPL_STACK)
    #else
        ldr x0, =(CONFIG_SYS_INIT_SP_ADDR)
    #endif
        bic sp, x0, #0xf/* 16-byte alignment for ABI compliance */
        mov x0, sp
        bl  board_init_f_alloc_reserve
        mov sp, x0
        /* set up gd here, outside any C code */
        mov x18, x0
        bl  board_init_f_init_reserve
    
        mov x0, #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 x0, [x18, #GD_START_ADDR_SP]/* x0 <- gd-="">start_addr_sp */
        bic sp, x0, #0xf/* 16-byte alignment for ABI compliance */
        ldr x18, [x18, #GD_BD]      /* x18 <- gd-="">bd */
        sub x18, x18, #GD_SIZE      /* new GD is below bd */
    
        adr lr, relocation_return
        ldr x9, [x18, #GD_RELOC_OFF]    /* x9 <- gd-="">reloc_off */
        add lr, lr, x9/* new return address after relocation */
        ldr x0, [x18, #GD_RELOCADDR]    /* x0 <- gd-="">relocaddr */
        b   relocate_code
    
    relocation_return:
    
    /*
     * Set up final (full) environment
     */
        bl  c_runtime_cpu_setup /* still call old routine */
    
    /* TODO: For SPL, call spl_relocate_stack_gd() to alloc stack relocation */
    
    /*
     * Clear BSS section
     */
        ldr x0, =__bss_start    /* this is auto-relocated! */
        ldr x1, =__bss_end  /* this is auto-relocated! */
        mov x2, #0
    clear_loop:
        str x2, [x0]
        add x0, x0, #8
        cmp x0, x1
        b.lo    clear_loop
    
        /* call board_init_r(gd_t *id, ulong dest_addr) */
        mov x0, x18         /* gd_t */
        ldr x1, [x18, #GD_RELOCADDR]    /* dest_addr */
        b   board_init_r        /* PC relative jump */
    
        /* NOTREACHED - board_init_r() does not return */
    
    #endif /* !CONFIG_SPL_BUILD */
    
    ENDPROC(_main)

    功能可总结为(大部分翻译自crt0_64.S中的注释):

    1)设置C代码的运行环境,为调用board_init_f接口做准备。包括:

    a)设置堆栈(C代码的函数调用,堆栈是必须的)。如果当前的编译是SPL(由CONFIG_SPL_BUILD定义),可单独定义堆栈基址(CONFIG_SPL_STACK),否则,通过CONFIG_SYS_INIT_SP_ADDR定义堆栈基址。
    
    b)调用board_init_f_alloc_reserve接口,从堆栈开始的地方,为u-boot中大名鼎鼎的GD ('global data') 数据结构,分配空间。
    
    c)调用board_init_f_init_reserve接口,对GD进行初始化。

    2)调用board_init_f函数,完成一些前期的初始化工作,例如:

    a)点亮一个Debug用的LED灯,表示u-boot已经活了。
    
    b)初始化DRAM、DDR等system范围的RAM等。
    
    c)计算后续代码需要使用的一些参数,包括relocation destination、the future stack、the future GD location等。

    注5:关于u-boot的relocation操作,后续会有专门的文章介绍。

    3)如果当前是SPL(由CONFIG_SPL_BUILD控制),则_main函数结束,直接返回。如果是正常的u-boot,则继续执行后续的动作。

    4)根据board_init_f指定的参数,执行u-boot的relocation操作。

    5)清除BBS段。

    6)调用board_init_r函数,执行后续的初始化操作(已经不再本文的讨论范围了,具体请参考后续的分析文章)。

    4. 总结

    4.1 SPL功能

    SPL是Secondary Program Loader的简称,之所以称作secondary,是相对于ROM code来说的。SPL是u-boot中独立的一个代码分支,由CONFIG_SPL_BUILD配置项控制,是为了在正常的u-boot image之外,提供一个独立的、小size的SPL image,通常用于那些SRAM比较小(或者其它限制)、无法直接装载并运行整个u-boot的平台。

    如果使用了SPL功能,u-boot的启动流程通常是:

    ROM code加载SPL并运行;
    
    SPL进行必要的初始化之后,加载u-boot并运行;
    
    u-boot进行后续的操作。

    因此,如果使用SPL功能,需要尽可能的减少SPL的代码量,以减小它的size。

    4.2 配置项总结

    经过第3章的流程分析,我们可以总结出和“平台相关部分的启动流程”有关的配置项,记录如下:

    CONFIG_SYS_RESET_SCTRL,控制是否在启动的时候reset SCTRL寄存器,一般不需要打开;
    
    CONFIG_ARM_ERRATA_XXX,控制ARM core的勘误信息,一般不需要打开;
    
    CONFIG_GICV2、CONFIG_GICV3,控制GIC的版本,用到的时候再说明;
    
    CONFIG_ARMV8_MULTIENTRY,控制是否在u-boot中使用多CPU,一般不需要;
    
    CONFIG_SPL_BUILD,是否是能SPL的编译,需要的话可以打开;
    
    CONFIG_SPL_STACK,如果配置了CONFIG_SPL_BUILD,是否为SPL image配置单独的stack(SP基址),如果需要,通过




    1. 前言

    介绍u-boot启动流程中和具体版型(board)有关的部分,也即board_init_f/board_init_r所代表的、board有关初始化过程。该过程将持续u-boot的整个生命周期,直到main_loop(即传说中的命令行)。

    注1:由于u-boot后初始化过程,基本上涉及到了所有的软件模块,因此本文不能一一分析,基本原则就是捡看着顺眼的、熟的下手了~。

    2. Generic Board

    u-boot的基本策略,就是声明一系列的API(如low_level_init、board_init_f、board_init_r等等),并在u-boot的核心逻辑中调用它们。平台的移植和开发者,所需要做的,就是根据实际情况,实现它们。

    与此同时,为了减少开发的工作量,u-boot为大部分API提供了通用实现(一般通过CONFIG配置项或者若定义去控制是否编译)。以board_init_f和board_init_r两个板级的初始化接口为例,u-boot分别在common/board_f.c和common/board_r.c两个文件中提供了通用实现。查看common/Makefile可知:

    # boards
    obj-y += board_f.o
    obj-y += board_r.o

    可以看到这两个文件都会调用到;

    3. _main

    _main函数位于crt0_64.S中:

    1)设置初始的堆栈

    基址由CONFIG_SYS_INIT_SP_ADDR定义。

    2)分配global data所需的空间

    将堆栈16 bits对齐之后,调用board_init_f_alloc_reserve接口,从堆栈开始的地方,为u-boot的global data(struct global_data)分配空间。如下:

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

    需要注意的是,如果定义了CONFIG_SYS_MALLOC_F_LEN,则会先预留出early malloc所需的空间。

    3)初始化global data

    global data的空间分配后,调用board_init_f_init_reserve,初始化global data。所谓的初始化,无非就是一些清零操作,不过有几个地方需要注意:

    1)如果不是ARM平台(!CONFIG_ARM),则可以调用arch_setup_gd接口,进行arch级别的设置。当然,前提是,对应的arch应该实现这个接口。 2)如果定义了CONFIG_SYS_MALLOC_F,则会初始化gd->malloc_base。

    4)执行前置的(front)初始化操作

    调用board_init_f接口,执行前置的初始化操作,会再后面的章节详细说明。

    5)执行relocation操作,后面会详细说明。

    6)清除BBS段

    7)执行后置的(rear)初始化操作

    调用board_init_r接口,执行前置的初始化操作,会再后面的章节详细说明。

    4. global data介绍以及背后的思考

    4.1 背景知识

    我们看看board_init_r函数:

    在uboot的common文件下有一个board_r.c文件: u-boot是一个bootloader,有些情况下,它可能位于系统的只读存储器(ROM或者flash)中,并从那里开始执行。 因此,这种情况下,在u-boot执行的前期(在将自己copy到可读写的存储器之前),它所在的存储空间,是不可写的,这会有两个问题: 1)堆栈无法使用,无法执行函数调用,也即C环境不可用。 2)没有data段(或者正确初始化的data段)可用,不同函数或者代码之间,无法通过全局变量的形式共享数据。

    对于问题1,通常的解决方案是:

    u-boot运行起来之后,在那些不需要执行任何初始化动作即可使用的、可读写的存储区域,开辟一段堆栈(stack)空间。 一般来说,大部分的平台(如很多ARM平台),都有自己的SRAM,可用作堆栈空间。如果实在不行,也有可借用CPU的data cache的方法(不再过多说明)。

    对于问题2,解决方案要稍微复杂一些:

    首先,对于开发者来说,在u-boot被拷贝到可读写的RAM(这个动作称作relocation)之前,永远不要使用全局变量。 其次,在relocation之前,不同模块之间,确实有通过全局变量的形式传递数据的需求。怎么办?这就是global data需要解决的事情。

    4.2 global data

    为了在relocation前通过全局变量的形式传递数据,u-boot设计了一个巧妙的方法:

    1)定义一个struct global_data类型的数据结构,里面保存了各色各样需要传递的数据

    该数据结构的具体内容,后面用到的时候再一个一个解释,这里不再详细介绍。具体可参考:include/asm-generic/global_data.h

    2)堆栈配置好之后,在堆栈开始的位置,为struct global_data预留空间(可参考第3章中相关的说明),并将开始地址(就是一个struct global_data指针)保存在一个寄存器中,后续的传递,都是通过保存在寄存器中的指针实现

    对arm64平台来说,该指针保存在了X18寄存器中,如下:

    bl      board_init_f_alloc_reserve 
    mov     sp, x0 
    /* set up gd here, outside any C code */ 
    mov     x18, x0 
    bl      board_init_f_init_reserve

    上面board_init_f_alloc_reserve的返回值(x0)就是global data的指针。

    /* arch/arm/include/asm/global_data.h */
    
    #ifdef __clang__
    
    #define DECLARE_GLOBAL_DATA_PTR 
    #define gd      get_gd()
    
    static inline gd_t *get_gd(void) 
    { 
            gd_t *gd_ptr;
    
    #ifdef CONFIG_ARM64 
            … 
            __asm__ volatile("mov %0, x18
    " : "=r" (gd_ptr)); 
    #else 
           … 
    }
    
    #else
    
    #ifdef CONFIG_ARM64 
    #define DECLARE_GLOBAL_DATA_PTR         register volatile gd_t *gd asm ("x18") 
    #else 
    … 
    #endif

    5. 前置的板级初始化操作

    global data准备好之后,u-boot会执行前置的板级初始化动作,即board_init_f。所谓的前置的初始化动作,主要是relocation之前的初始化操作,也就是说:

    执行board_init_f的时候,u-boot很有可能还在只读的存储器中。大家记住这一点就可以了! 注3:大家可能会觉得这里的f(front?)和r(rear?)的命名有点奇怪,我猜这个软件工程师应该是车迷,是不是借用了前驱和后驱的概念?不得而知啊。

    对于ARM等平台来说,u-boot提供了一个通用的board_init_f接口,该接口使用u-boot惯用的设计思路:

    u-boot将需要在board_init_f中初始化的内容,抽象为一系列API。这些API由u-boot声明,由平台的开发者根据实际情况实现。具体可参考本章后续的描述。

    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();
    }

    5.1 board_init_f

    位于common/board_f.c中的board_init_f接口的实现非常简单,如下(省略了一些无用代码):

    void board_init_f(ulong boot_flags) 
    { 
    …
    
            gd->flags = boot_flags; 
            gd->have_console = 0;
    
            if (initcall_run_list(init_sequence_f)) 
                    hang();
    … 
    }

    对global data进行简单的初始化之后,调用位于init_sequence_f数组中的各种初始化API,进行各式各样的初始化动作。后面将会简单介绍一些和ARM平台有关的、和平台的移植工作有关的、比较重要的API。其它API,大家可以参考source code自行理解。

    5.2 fdtdec_setup

    #ifdef CONFIG_OF_CONTROL 
            fdtdec_setup, 
    #endif

    如果打开了CONFIG_OF_CONTROL,则调用fdtdec_setup,配置gd->fdt_blob指针(即device tree所在的存储位置)。对ARM平台来说,u-boot的Makefile会通过连接脚本,将dtb文件打包到u-boot image的“__dtb_dt_begin”位置处,因此不需要特别关心。

    5.3 trace_early_init

    #ifdef CONFIG_TRACE 
            trace_early_init, 
    #endif

    由CONFIG_TRACE配置项控制,暂且不用关注,后面用到的时候再分析。

    5.4 initf_malloc

    如果定义了CONFIG_SYS_MALLOC_F_LEN,则调用initf_malloc,初始化malloc有关的global data,如gd->malloc_limit、gd->malloc_ptr。

    5.5 arch_cpu_init

    cpu级别的初始化操作,可以在需要的时候由CPU有关的code实现。

    5.6 initf_dm

    driver model有关的初始化操作。如果定义了CONFIG_DM,则调用dm_init_and_scan初始化并扫描系统所有的device。如果定义了CONFIG_TIMER_EARLY,调用dm_timer_init初始化driver model所需的timer。

    5.7 board_early_init_f

    #if defined(CONFIG_BOARD_EARLY_INIT_F) 
            board_early_init_f, 
    #endif

    如果定义CONFIG_BOARD_EARLY_INIT_F,则调用board_early_init_f接口,执行板级的early初始化。平台的开发者可以根据需要,实现board_early_init_f接口,以完成特定的功能。

    5.8 timer_init

    初始化系统的timer。

    该接口应该由平台或者板级的代码实现,初始化成功后,u-boot会通过其它的API获取当前的timestamp,后面用到的时候再详细介绍。

    5.9 get_clocks

    获取当前CPU和BUS的时钟频率,并保存在global data中:

    gd->cpu_clk
    
    gd->bus_clk

    5.10 env_init

    初始化环境变量有关的逻辑,不需要特别关注。

    5.11 init_baud_rate

    gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);

    获取当前使用串口波特率,可以有两个途径(优先级从高到低):从"baudrate"中获取;从CONFIG_BAUDRATE配置项获取。

    5.12 serial_init

    初始化serial,包括u-boot serial core以及具体的serial driver。该函数执行后,系统的串口(特别是用于控制台的)已经可用。

    5.13 console_init_f

    /* Called before relocation - use serial functions */ 
    int console_init_f(void) 
    { 
            gd->have_console = 1;
    
    #ifdef CONFIG_SILENT_CONSOLE 
            if (getenv("silent") != NULL) 
                    gd->flags |= GD_FLG_SILENT; 
    #endif
    
            print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);
    
            return 0; 
    }

    初始化系统的控制台,之后串口输出可用。大家可留意CONFIG_SILENT_CONSOLE配置项,如果使能,可以通过“silent”环境变量,控制u-boot的控制台是否输出。

    5.14 fdtdec_prepare_fdt

    #ifdef CONFIG_OF_CONTROL 
            fdtdec_prepare_fdt, 
    #endif

    如果定义了CONFIG_OF_CONTROL,调用fdtdec_prepare_fdt接口,准备device tree有关的内容。后续device tree的分析文章会详细介绍。

    5.15 display_options/display_text_info/print_cpuinfo/show_board_info

    通过控制台,显示一些信息,可用于debug。

    5.16 misc_init_f

    #if defined(CONFIG_MISC_INIT_F) 
            misc_init_f, 
    #endif

    如果使能了CONFIG_MISC_INIT_F,则调用misc_init_f执行misc driver有关的初始化。

    5.17 init_func_i2c

    #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C) 
            init_func_i2c, 
    #endif

    如果使能了CONFIG_HARD_I2C或者CONFIG_SYS_I2C,则调用init_func_i2c执行i2c driver有关的初始化。

    5.18 init_func_spi

    #if defined(CONFIG_HARD_SPI) 
            init_func_spi, 
    #endif

    如果使能了CONFIG_HARD_SPI,则调用init_func_spi执行spi driver有关的初始化。

    5.19 announce_dram_init

    宣布我们要进行DDR的初始化动作了(其实就是一行打印)。

    5.20 dram_init

    #if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) ||  
                    defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) 
            dram_init,              /* configure available RAM banks */ 
    #endif

    调用dram_init接口,初始化系统的DDR。dram_init应该由平台相关的代码实现。

    如果DDR在SPL中已经初始化过了,则不需要重新初始化,只需要把DDR信息保存在global data中即可,例如:

    gd->ram_size = …

    5.21 testdram

    #if defined(CONFIG_SYS_DRAM_TEST) 
            testdram, 
    #endif /* CONFIG_SYS_DRAM_TEST */

    如果定义了CONFIG_SYS_DRAM_TEST,则会调用testdram执行DDR的测试操作。可以在开发阶段打开,系统稳定后关闭。

    5.22 DRAM空间的分配

    DRAM初始化完成后,就可以着手规划u-boot需要使用的部分,如下图:

    总结如下:

    1)考虑到后续的kernel是在RAM的低端位置解压缩并执行的,为了避免麻烦,u-boot将使用DRAM的顶端地址,即gd->ram_top所代表的位置。其中gd->ram_top是由setup_dest_addr函数配置的。 2)u-boot所使用的DRAM,主要分为三类:各种特殊功能所需的空间,如log buffer、MMU page table、LCD fb buffer、trace buffer、等等;u-boot的代码段、数据段、BSS段所占用的空间(就是u-boot relocate之后的执行空间),由gd->relocaddr标示;堆栈空间,从gd->start_addr_sp处递减。 3)特殊功能以及u-boot所需空间,是由reserve_xxx系列函数保留的,具体可参考source code,这里不再详细分析。 4)reserve空间分配完毕后,堆栈紧随其后,递减即可。

    5.23 setup_dram_config

    调用dram_init_banksize接口(由具体的平台代码实现),初始化DDR的bank信息。

    5.24 reloc_fdt

    如果没有定义CONFIG_OF_EMBED,则先将device tree拷贝到图片1 new_fdt所在的位置,也就是device tree的relocation操作。

    5.25 setup_reloc

    计算relocation有关的信息,主要是 gd->reloc_off,计算公式如下:

    gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;

    其中CONFIG_SYS_TEXT_BASE是u-boot relocation之前在(只读)memory的位置(也是编译时指定的位置),gd->relocaddr是relocation之后的位置,因此gd->reloc_off代表u-boot relocation操作之后的偏移量,后面relocation时会用到。

    同时,该函数顺便把global data拷贝到了图片1所示的“new global data”处,其实就是global data的relocation。

    6. u-boot的relocation

    前面讲过,u-boot是有可能在只读的memory中启动的。简单起见,u-boot假定所有的启动都是这样,因此u-boot的启动逻辑,都是针对这种情况设计的。在这种情况下,基于如下考虑:

    1)只读memory中执行,代码需要小心编写(不能使用全局变量,等等)。

    2)只读memory执行速度通常比较慢。

    u-boot需要在某一个时间点,将自己从“只读memory”中,拷贝到可读写的memory(如SDRAM,后面统称RAM,注意和SRAM区分,不要理解错了)中继续执行,这就是relocation(重定位)操作。

    relocation的时间点,可以是“系统可读写memory始化完成之后“的任何时间点。根据u-boot当前的代码逻辑,是在board_init_f执行完成之后,因为board_init_f中完成了很多relocation有关的准备动作,具体可参考第5章的描述。

    u-boot relocation的代码如下(以arm64为例):

    /* https://github.com/wowotechX/u-boot/blob/x_integration/arch/arm/lib/crt0_64.S */
    
            ldr     x0, [x18, #GD_START_ADDR_SP]    /* x0 <- gd->start_addr_sp */ 
            bic     sp, x0, #0xf    /* 16-byte alignment for ABI compliance */ 
            ldr     x18, [x18, #GD_BD]              /* x18 <- gd->bd */ 
            sub     x18, x18, #GD_SIZE              /* new GD is below bd */
    
            adr     lr, relocation_return 
            ldr     x9, [x18, #GD_RELOC_OFF]        /* x9 <- gd->reloc_off */ 
            add     lr, lr, x9      /* new return address after relocation */ 
            ldr     x0, [x18, #GD_RELOCADDR]        /* x0 <- gd->relocaddr */ 
            b       relocate_code
    
    relocation_return:

    逻辑比较简单:

    1)从global data中取出relocation之后的堆栈基址,16-byte对齐后,保存到sp中。

    2)将新的global data的指针,保存在x18寄存器中。

    3)计算relocation之后的执行地址(relocation_return处),计算的方法就是当前的relocation_return位置加上gd->reloc_off。

    4)以relocation的目的地址(gd->relocaddr)为参数,调用relocate_code执行实际的relocation动作,就是将u-boot的代码段、data段、bss段等数据,拷贝到新的位置(gd->relocaddr)。

    7. 后置的板级初始化操作

    relocate完成之后,真正的C运行环境才算建立了起来,接下来会执行“后置的板级初始化操作”,即board_init_r函数。board_init_r和board_init_f的设计思路基本一样,也有一个很长的初始化序列----init_sequence_r,该序列中包含如下的初始化函数(逻辑比较简单,这里不再涉及细节,权当列出index吧):

    注5:老规矩,红色字体标注的函数是比较重要的函数。

    1)initr_trace,初始化并使能u-boot的tracing system,涉及的配置项有CONFIG_TRACE。

    2)initr_reloc,设置relocation完成的标志。

    3)initr_caches,使能dcache、icache等,涉及的配置项有CONFIG_ARM。

    4)initr_malloc,malloc有关的初始化。

    5)initr_dm,relocate之后,重新初始化DM,涉及的配置项有CONFIG_DM。

    6)board_init,具体的板级初始化,需要由board代码根据需要实现,涉及的配置项有CONFIG_ARM。

    7)set_cpu_clk_info,Initialize clock framework,涉及的配置项有CONFIG_CLOCKS。

    8)initr_serial,重新初始化串口(不太明白什么意思)。

    9)initr_announce,宣布已经在RAM中执行,会打印relocate后的地址。

    10)board_early_init_r,由板级代码实现,涉及的配置项有CONFIG_BOARD_EARLY_INIT_R。

    11)arch_early_init_r,由arch代码实现,涉及的配置项有CONFIG_ARCH_EARLY_INIT_R。

    12)power_init_board,板级的power init代码,由板级代码实现,例如hold住power。

    13)initr_flash、initr_nand、initr_onenand、initr_mmc、initr_dataflash,各种flash设备的初始化。

    14)initr_env,环境变量有关的初始化。

    15)initr_secondary_cpu,初始化其它的CPU core。

    16)stdio_add_devices,各种输入输出设备的初始化,如LCD driver等。

    17)interrupt_init,中断有关的初始化。

    18)initr_enable_interrupts,使能系统的中断,涉及的配置项有CONFIG_ARM(ARM平台u-boot实在开中断的情况下运行的)。

    19)initr_status_led,状态指示LED的初始化,涉及的配置项有CONFIG_STATUS_LED、STATUS_LED_BOOT。

    20)initr_ethaddr,Ethernet的初始化,涉及的配置项有CONFIG_CMD_NET。

    21)board_late_init,由板级代码实现,涉及的配置项有CONFIG_BOARD_LATE_INIT。

    22)等等…

    23)run_main_loop/main_loop,执行到main_loop,开始命令行操作。

     
  • 相关阅读:
    Debian常用操作
    debian-11.0.0-amd64
    CentOS-8.4.2105-x86_64
    第20章: 二进制方式部署K8S1.20高可用集群
    天狮集团云函数实践:自定义业务逻辑实现跨境电商全球直播
    峰值利用率80%+,视频云离线转码自研上云TKE实践
    腾讯首个CNCF沙箱开源项目
    斗鱼直播云原生实践之注册中心篇
    Superedge的新特性和未来之路
    腾讯云与 Grafana Labs 达成深度合作, 推出全新 Grafana 托管服务
  • 原文地址:https://www.cnblogs.com/erhu-67786482/p/13959624.html
Copyright © 2020-2023  润新知