build_embed_linux_system

U-Boot启动执行分析

本篇主要以I.MX6ULL的”U-Boot 22.04”版本作为基础进行分析,关于如何分析启动流程,主要关注两个部分。

  1. 用于定义文件加载的u-boot.lds文件
  2. 生成的描述函数和文件位置的u-boot.map文件

本节目录如下。

dir_notes

在学习U-Boot前,先大致理解U-Boot目录功能。

目录 功能说明
api 用于支持uboot支持app运行的api接口定义
arch 包含了针对不同系统架构的特定代码,启动文件,执行库,目前主要涉及的arm,arm64和rsic-v等,开发中维护的设备树也在此目录下
board 包含了特定开发板的配置文件和初始化代码,在移植时主要实现部分的平台代码
boot 通常用于存放引导(Boot)代码和相关文件,这些文件对于系统的启动至关重要
cmd 包含了u-boot支持的命令行命令的代码,在此目录下可以扩展自定义命令
common 通用的代码,用于管理引导和启动过程的共用代码,执行启动时序
configs 配置文件,用于编译时的配置选项,管理U-Boot支持功能
disk 处理磁盘启动方式的代码
doc u-boot文档和说明
drivers 包含了各种外设的驱动程序,比如网络设备,存储设备等,如果希望自己实现驱动也添加到此目录
env 包含管理环境变量的相关代码和配置
examples 测试u-boot api的应用例程
fs 管理文件系统的相关代码,例如FAT,ext等文件系统的支持
include 包含了所有头文件的目录,定义u-boot中需要的头文件,数据结构和宏
lib 包含系统编译需要的各类库,例如算法库ae,crypto, bzip2, 安全库optee等
Licenses 相关的法律条款声明
net 处理网络相关的代码,主要用于支持tcp/ip协议
post 上电自检的相关代码
scripts 用于构建和配置u-boot的脚本文件
test 系统单元测试的相关目录
tools 用于系统编译的工具,处理中间代码或者生成最终文件

在移植时,主要修改的内容如下。

  1. arch目录下的设备树文件和平台管理文件,用于抽象描述硬件功能
  2. board目录下的平台文件,适配当前硬件
  3. configs目录下的配置文件,根据实际需求维护选项,后续也可通过”make menuconfig”进行二次修改
  4. driver目录下的部分驱动文件,涉及开发,修改和调试等
  5. inclue目录下的包含硬件定义的头文件

对于启动流程分析,首先可通过生成的uboot.map来分析, 另外本文中对于代码只解析跳转或者功能关键部分。如果有需要,可以去相应文件查看。

start

启动文件,路径为”arch/arm/cpu/armv7/start.S“,内部定义了起始地址,这里在链接文件中有说明,其中链接文件为uboot.lds。

# LD链接文件
ENTRY(_start)                                #定义程序的入口地址
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {
  *(.__image_copy_start)
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)
 }
 #.....
}

start.S主要执行启动相关的处理,其中reset即为上电复位的入口函数,主要实现流程如下所示。

1. 配置cachemmutlbs等信息
2. 配置时钟,存储,串口基础功能
3. 完成后,跳转到_main入口,进行下一步执行动作。

```c
# ------------------ reset函数 -------------------------
    .globl  reset

    #.....

#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
#ifdef CONFIG_CPU_V7A
    bl    cpu_init_cp15     # 配置CP15寄存器(包含cachemmutlbs)
#endif
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)
    bl    cpu_init_crit     #配置重要寄存器,包含pullmuxmemory
#endif
#endif

    bl    _main

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

lowlevel_init

lowlevel_init.S位于地址”arch/arm/cpu/armv7/lowlevel_init.S””, 主要功能为进行栈的初始化,用于C接口的调用,另外也进一步调用初始化硬件的接口。

.pushsection .text.lowlevel_init, "ax"
WEAK(lowlevel_init)

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr    sp, =CONFIG_SPL_STACK
#else
    ldr    sp, =CONFIG_SYS_INIT_SP_ADDR        # 将栈顶地址赋值给sp指针
#endif

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

crt0

crt0.S的地址为”arch/arm/lib/crt0.S“主要完成系统必要的初始化和C运行时的支持,其中_main也是系统的入口函数。

ENTRY(_main)

    /*
    * Set up initial C runtime environment and call board_init_f(0).
    */
    ldr     r0, =(CONFIG_SYS_INIT_SP_ADDR)

    bic     r0, r0, #7     /* 8-byte alignment for ABI compliance */
    mov     sp, r0
    bl     board_init_f_alloc_reserve          /* common/init/board_init.c 从top地址分配预留地址,通过CONFIG_SYS_MALLOC_F_LEN定义 */
    mov     sp, r0
    /* set up gd here, outside any C code */
    mov     r9, r0
    bl     board_init_f_init_reserve           /* common/init/board_init.c 实现内存布局 */

    mov     r0, #0                     
    bl     board_init_f                        /* common/board_f.c 板级序列初始化接口 */

/*
 * 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     r0, [r9, #GD_START_ADDR_SP]     /* sp = gd->start_addr_sp */
    bic     r0, r0, #7     /* 8-byte alignment for ABI compliance */
    mov     sp, r0
    ldr     r9, [r9, #GD_NEW_GD]          /* r9 <- gd->new_gd */

    adr     lr, here
    ldr     r0, [r9, #GD_RELOC_OFF]          /* r0 = gd->reloc_off */
    add     lr, lr, r0
    ldr     r0, [r9, #GD_RELOCADDR]          /* r0 = gd->relocaddr */
    b     relocate_code                                 

here:

    bl     relocate_vectors

    /* Set up final (full) environment */

    bl     c_runtime_cpu_setup     /* we still call old routine here */

    CLEAR_BSS                       

    bl coloured_LED_init                /* led初始化, __weak未定义 */
    bl red_led_on

    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov     r0, r9                      /* gd_t */
    ldr     r1, [r9, #GD_RELOCADDR]     /* dest_addr */

    /* call board_init_r */
    ldr     pc, =board_init_r           /* this is auto-relocated! */

board_init

board_init.c的地址为common/init/board_init.c, 主要实现内容排布,计算top地址,实例化内存管理指针gb,完成相应初始化,包含函数有board_init_f_alloc_reserve,board_init_f_init_reserve。

// top为栈顶的地址,申请global_data空间用于后续管理
ulong board_init_f_alloc_reserve(ulong top)
{
    /* Reserve early malloc arena */
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
    top -= CONFIG_VAL(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;
}

// 将上述申请的global_data的范围,进行初始化管理
void board_init_f_init_reserve(ulong base)
{
    struct global_data *gd_ptr;

    /*
     * 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 */
    memset(gd_ptr, '\0', sizeof(*gd));
    /* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)
    arch_setup_gd(gd_ptr);
#endif

    if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
        board_init_f_init_stack_protection_addr(base);

    /* 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 CONFIG_VAL(SYS_MALLOC_F_LEN)
    /* go down one 'early malloc arena' */
    gd->malloc_base = base;
#endif

    if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
        board_init_f_init_stack_protection();
}

board_f

board_f.c地址在common/board_f.c中,主要执行一系列初始化序列,调用函数为board_init_f,执行序列为init_sequence_f,具体实现功能如下。

static const init_fnc_t init_sequence_f[] = {
    setup_mon_len,              /* 配置用于监视的内存空间 */
#ifdef CONFIG_OF_CONTROL        /* 支持设备树的宏定义 */
    fdtdec_setup,               /* lib/fdtdec.c、解析设备树,并配置相应硬件,(获取memory容量也是在此文件,读取系统中memory节点)  */
#endif
#ifdef CONFIG_TRACE_EARLY
    trace_early_init,           /* 初始化跟踪系统,用于早期的系统监测 */
#endif
    initf_malloc,               /* common/dlmalloc.c, 用于申请系统堆,CONFIG_SYS_MALLOC_F_LEN定义系统堆大小 */
    log_init,                   /* common/log.c,初始化log链表,用于后续log调用 */
    initf_bootstage,            /* board_f.c,记录u-boot的运行阶段 */
    event_init,                 /* common/event.c, 定义u-boot支持的事件 */
    bloblist_maybe_init,        /* common/bloblist.c, 用于多阶段u-boot可以传输数据 */
    setup_spl_handoff,          /* board_f.c, 配合bloblist查询SPL传递到U-Boot的数据 */
#if defined(CONFIG_CONSOLE_RECORD_INIT_F)
    console_record_init,        /* common/console.c, 配合bloblist查询SPL传递到U-Boot的数据 */
#endif
    INITCALL_EVENT(EVT_FSP_INIT_F),
    arch_cpu_init,              /* 依赖cpu特性的配置 */
    mach_cpu_init,              /* soc/机器相关的cpu配置 */
    initf_dm,                   /* board_f.c, 初始化驱动程序模型 */
#if defined(CONFIG_BOARD_EARLY_INIT_F)
    board_early_init_f,         /* board_f.c, 早期初始化,初始化关键硬件 */
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
    /* get CPU and bus clocks according to the environment variable */
    get_clocks,                 /* 获取cpu和总线时钟 */
#endif
#if !defined(CONFIG_M68K) || (defined(CONFIG_M68K) && !defined(CONFIG_MCFTMR))
    timer_init,                 /* 初始化定时器 */
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
    board_postclk_init,
#endif
    env_init,                   /* env/env.c 初始化系统环境变量 */
    init_baud_rate,             /* 初始化波特率,根据环境变量或默认值 */
#ifndef CONFIG_ANDROID_AUTO_SUPPORT
    serial_init,                /* 串口通讯支持 */
#endif
    console_init_f,             /* common/console.c 第一阶段console初始化 */
    display_options,            /* board_f.c, 打印U-boot版本和编译信息,起始第一步 */
    display_text_info,          /* board_f.c, 打印U-Boot地址信息 */
    checkcpu,                   /* 检查cpu信息 */
#if defined(CONFIG_SYSRESET)
    print_resetinfo,            /* 查询复位信息 */
#endif
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,              /* 打印CPU信息和运行频率 */
#endif
#if defined(CONFIG_DTB_RESELECT)
    embedded_dtb_select,        /*  重新切换dtb支持 */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    show_board_info,             /* common/board_info.c,打印板级信息,设备树中的model配置 */
#endif
    INIT_FUNC_WATCHDOG_INIT     /*  初始化watchdog */
    INITCALL_EVENT(EVT_MISC_INIT_F),
    INIT_FUNC_WATCHDOG_RESET    /*  复位watchdog,避免复位 */
#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
    init_func_i2c,              /* 启动遗留的i2c子系统和驱动 */
#endif
    announce_dram_init,         /* 打印DRAM: */
    dram_init,                  /* 查询设备树,初始化sdram节点,加载设备树中父节点 */
#ifdef CONFIG_POST
    post_init_f,                /* 自检相关时钟初始化 */
#endif
    INIT_FUNC_WATCHDOG_RESET
#if defined(CFG_SYS_DRAM_TEST)
    testdram,                   /* 用于dram自检测试 */
#endif /* CFG_SYS_DRAM_TEST */
    INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_POST
    init_post,                  /* 上电自检初始化 */
#endif
    INIT_FUNC_WATCHDOG_RESET
    /*
     * Now that we have DRAM mapped and working, we can
     * relocate the code and continue running from DRAM.
     *
     * Reserve memory at end of RAM for (top down in that order):
     *  - area that won't get touched by U-Boot and Linux (optional)
     *  - kernel log buffer
     *  - protected RAM
     *  - LCD framebuffer
     *  - monitor code
     *  - board info struct
     */
    /* 后续代码功能为重新定位代码到SDRAM,然后跳转SDRAM运行 */
    setup_dest_addr,            
#ifdef CONFIG_OF_BOARD_FIXUP
    fix_fdt,
#endif
#ifdef CFG_PRAM
    reserve_pram,
#endif
    reserve_round_4k,                   
    setup_relocaddr_from_bloblist,
    arch_reserve_mmu,
    reserve_video,
    reserve_trace,
    reserve_uboot,
    reserve_malloc,
    reserve_board,
    reserve_global_data,
    reserve_fdt,
    reserve_bootstage,
    reserve_bloblist,
    reserve_arch,
    reserve_stacks,
    dram_init_banksize,
    show_dram_config,           /* 显示dram配置信息 */
    INIT_FUNC_WATCHDOG_RESET
    setup_bdinfo,               /* 配置板级信息 */
    display_new_sp,
    INIT_FUNC_WATCHDOG_RESET
    reloc_fdt,
    reloc_bootstage,
    reloc_bloblist,
    setup_reloc,
#if defined(CONFIG_X86) || defined(CONFIG_ARC)
    copy_uboot_to_ram,
    do_elf_reloc_fixups,
#endif
    clear_bss,
    /*
     * Deregister all cyclic functions before relocation, so that
     * gd->cyclic_list does not contain any references to pre-relocation
     * devices. Drivers will register their cyclic functions anew when the
     * devices are probed again.
     *
     * This should happen as late as possible so that the window where a
     * watchdog device is not serviced is as small as possible.
     */
    cyclic_unregister_all,
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)
    jump_to_copy,
#endif
    NULL,
};

void board_init_f(ulong boot_flags)
{
    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) && !CONFIG_IS_ENABLED(X86_64) && \
        !defined(CONFIG_ARC)
    /* NOTREACHED - jump_to_copy() does not return */
    hang();
#endif

    //返回到crt0.S,继续执行
}

next_chapter

返回目录

直接开始下一小节: Linux kernel移植