u-boot启动流程分析

u-boot启动流程

u-boot移植框架

u-Boot移植框架

  • board,不用说了,板级,uboot使用dts后,这块代码应尽量简化
  • machine, SOC级,主要是一些外设
  • ARCH, 如arm(包含armv7和armv8)
  • CPU, 如armv8

各框架启动时的关系:
u-Boot框架启动关系.png

总体流程图

以 uboot armv7 imx6 32位芯片 启动流程为例

u-Boot总体流程图

u-boot:启动详细的代码调用流程
u-boot.lds:(arch/arm/cpu/u-boot.lds)
    |-->_start:(arch/arm/lib/vectors.S)
        |-->reset(arch/arm/cpu/armv7/start.S)    
            |-->save_boot_params(arch/arm/cpu/armv7/start.S)    /*将引导参数保存到内存中*/
                |-->save_boot_params_ret(arch/arm/cpu/armv7/start.S)
                    |-->cpu_init_cp15(arch/arm/cpu/armv7/start.S)  /*初始化*/
                    |-->cpu_init_crit(arch/arm/cpu/armv7/start.S)
                        |-->lowlevel_init(arch/arm/cpu/armv7/lowlevel_init.S)
                    |-->_main(arch/arm/lib/crt0.S)
                        |-->board_init_f_alloc_reserve(common/init/board_init.c)  /*为u-boot的gd结构体分配空间*/
                        |-->board_init_f_init_reserve(common/init/board_init.c)    /*将gd结构体清零*/
                        |-->board_init_f(common/board_f.c)
                            |-->initcall_run_list(include/initcall.h)    /*初始化序列函数*/
                                |-->init_sequence_f[](common/board_f.c)    /* 初始化序列函数数组 */
                                    |-->board_early_init_f(board/freescale/mx6ull_toto/mx6ull_toto.c)/*初始化串口的IO配置*/
                                    |-->timer_init(arch/arm/imx-common/timer.c)    /*初始化内核定时器,为uboot提供时钟节拍*/
                                    |-->init_baud_rate(common/board_f.c)        /*初始化波特率*/
                                    |-->serial_init(drivers/serial/serial.c)    /*初始化串口通信设置*/
                                    |-->console_init_f(common/console.c)        /*初始化控制台*/
                                    |-->...
                        |-->relocate_code(arch/arm/lib/relocate.S)    /*主要完成镜像拷贝和重定位*/
                        |-->relocate_vectors(arch/arm/lib/relocate.S)/*重定位向量表*/
                        |-->board_init_r(common/board_r.c)/*板级初始化*/
                            |-->initcall_run_list(include/initcall.h)/*初始化序列函数*/
                                |-->init_sequence_r[](common/board_f.c)/*序列函数*/
                                    |-->initr_reloc(common/board_r.c)    /*设置 gd->flags,标记重定位完成*/
                                    |-->serial_initialize(drivers/serial/serial-uclass.c)/*初始化串口*/
                                        |-->serial_init(drivers/serial/serial-uclass.c)     /*初始化串口*/
                                    |-->initr_mmc(common/board_r.c)                         /*初始化emmc*/
                                        |-->mmc_initialize(drivers/mmc/mmc.c)
                                            |-->mmc_do_preinit(drivers/mmc/mmc.c)
                                                |-->mmc_start_init(drivers/mmc/mmc.c)
                                    |-->console_init_r(common/console.c)                /*初始化控制台*/
                                    |-->interrupt_init(arch/arm/lib/interrupts.c)        /*初始化中断*/
                                    |-->initr_net(common/board_r.c)                        /*初始化网络设备*/
                                    |-->...
                                    |-->run_main_loop(common/board_r.c)/*主循环,处理命令*/
                                        |-->main_loop(common/main.c)
                                            |-->bootdelay_process(common/autoboot.c)    /*读取环境变量bootdelay和bootcmd的内容*/
                                            |-->autoboot_command(common/autoboot.c)        /*倒计时按下执行,没有操作执行bootcmd的参数*/
                                                |-->abortboot(common/autoboot.c)
                                                    |-->printf("Hit any key to stop autoboot: %2d ", bootdelay);
                                                    /*到这里就是我们看到uboot延时3s启动内核的地方*/
                                            |-->cli_loop(common/cli.c)    /*倒计时按下space键,执行用户输入命令*/

ARCH级上电过程

上电后的ARCH级初始化过程在 arch/arm/cpu/armv7/start.S 文件里

总结
①初始化中断向量表
②进入SVC模式,关FIQ,IRQ
③设置中断向量表偏移VBAR
④初始化CP15,关MMU,ICACHE等
⑤sram内存布局,设置SP指针

ARCH级上电过程

初始化中断向量表

由于imx6dl芯片属于armv7架构,在arch/arm/cpu/目录下,通过分析链接脚本u-boot.lds代码段.text可知:可执行程序的入口是_start

image-20250122172557138

start 位于 arch/arm/lib/vectors.S 文件中:

_start:
    .globl  _reset
    .globl    _undefined_instruction
    .globl    _software_interrupt
    .globl    _prefetch_abort
    .globl    _data_abort
    .globl    _not_used
    .globl    _irq
    .globl    _fiq

从函数入口_start可以看到,入口的指令存放的就是中断向量表,0地址偏移存放reset, 0x4地址存放_undefined_instruction,0x8地址存放_software_interrupt… 即一上电就执行偏移量为0地址的reset中断_

CPU上电最开始进入_start,进而进入reset函数,该函数定义在arch/arm/cpu/armv7/start.S 里面。

reset:
    /* Allow the board to save important registers */
    b    save_boot_params

进一步进入save_boot_params ->save_boot_params_ret。

WEAK(save_boot_params)
    ...
    b    save_boot_params_ret        @ back to my caller
ENDPROC(save_boot_params)

函数调用关系如下:reset->save_boot_params->save_boot_params_ret。

进入SVC模式,关闭FIQ 和 IRQ中断

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           @读取cpsr寄存器
    and    r1, r0, #0x1f       @ 对r0和0x1a进行与运算,目的是取出cpu模式。
    teq    r1, #0x1a          @ 比较 r1 和 0X1a 的值 
    bicne    r0, r0, #0x1f  @ 如果 r1 和 0X1A 不相等,也就是 CPU 不处于 Hyp 模式的话就将 r0 寄存器的低5位清零,清除模式位
    orrne    r0, r0, #0x13    @如果处理器不处于 Hyp 模式的话就将 r0 的寄存器的值与 0x13 进行或运算, 0x13=0b10011,也就是设置处理器进入 SVC 模式
    orr    r0, r0, #0xc0        @ 0xC0或运算,那么 r0 寄存器此时的值就是 0xD3,cpsr 的 I 为和 F 位分别控制 IRQ 和 FIQ 这两个中断的开关,设置为 1 就关闭了 FIQ 和 IRQ。 
    msr    cpsr,r0                @ r0写入cpsr

1-1

1-2

设置中断向量表偏移VBAR

该过程把CP15 SCTLR Register读出来,对bit13清0,再写SCTLR Register 进去。

#if !CONFIG_IS_ENABLED(SYS_NO_VECTOR_TABLE)
/*
 *   cp15配置,设置中断向量表偏移VBAR
 */
    /* 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

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

如下图是SCTLR 寄存器:bit13 为 V 位,此位是向量表控制位,当为 0 的时候向量表基地址 为 0X00000000,软件可以重定位向量表。为 1 的时候向量表基地址为 0XFFFF0000,软件不能 重定位向量表。
这里将 V 清零,目的就是为了接下来的向量表重定位
1-3

关MMU,ICACHE

cpu_init_cp15就是初始化协处理器寄存器CP15, 比如关闭MMU, ICACHE等。都是一些CP15协寄存器操作指令。

/* the mask ROM code should have PLL and others stable */
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
#ifdef CONFIG_CPU_V7A
    bl    cpu_init_cp15       /* cp15配置,关MMU,ICACHE */
#endif

sram内存布局

​ cpu_init_crit 实际是进入 lowlevel_init , lowlevel_init 在 arch/arm/cpu/armv7/lowlevel_init.S 文件里

代码看出即 在内部sram中设置SP指针, 有SPL默认配置 和 soc厂家自带配置 两种方式去设置SP指针,为c语言的运行配置环境。

arch/arm/cpu/armv7/start.S文件

#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)
    bl    cpu_init_crit       /* lowlevel_init() 配置 */
#endif
...

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)

...
#endif
    bl    _main       /* 进入板级初始化 */

/*------------------------------------------------------------------------------*/

arch/arm/cpu/armv7/lowlevel_init.S 文件

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr    sp, =CONFIG_SPL_STACK
#else
    ldr    sp, =SYS_INIT_SP_ADDR
#endif
    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

板级上电过程

板级初始化过程在 arch/arm/lib/crt0.S 文件里

板级上电过程

堆栈、GD、early malloc空间的分配

ulong board_init_f_alloc_reserve(ulong top)
{
    /* Reserve early malloc arena */
#ifndef CFG_MALLOC_F_ADDR
#if CONFIG_IS_ENABLED(SYS_MALLOC_F)
    top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
#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指针),该函数主要是留出早期的 malloc 内存区域和 gd 内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400( 在 文 件 include/generated/autoconf.h 中定义, CONFIG_SYS_MALLOC_F也同样定义成1) , sizeof(struct global_data)=248(GD_SIZE 值)。rounddown是一个向下对齐的函数。

堆栈、GD、early malloc空间的初始化

board_init_f_init_reserve该函数主要 初始化gd,gd内存清0

芯片级外设(uart,timer,console等)初始化和DRAM划分

board_init_f 此函数定义在文件 common/board_f.c,该函数非常重要,主要功能如下:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分 分配好内存位置和大小,比如 gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。

代码重定位

relocate_code 代码段重定位, rel.dyn重定位

向量表重定位

relocate_vectors 对中断向量表进行重定位

进行板级(flash,网卡,i2c,sdio等)的外设初始化

前面board_init_f初始化了芯片级外设(uart,Timer,console等)并且进行了DRAM划分,为代码重定位做准备。接下来board_init_r进行板级的外设初始化。
init_sequence_r 定义在文件 common/board_r.c,负责初始化各个硬件外设,比如串口、flash、网卡、i2c、sdio等等

预启动linux过程

预启动linux过程

void main_loop(void)
{
    const char *s;

    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); //调用 bootstage_mark_name 函数,打印出启动进度。

    if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
        env_set("ver", version_string);  /* set version variable */ //设置版本号环境变量

    cli_init();    //命令行初始化

    if (IS_ENABLED(CONFIG_USE_PREBOOT))
        run_preboot_environment_command();   //preboot处理

    if (IS_ENABLED(CONFIG_UPDATE_TFTP))
        update_tftp(0UL, NULL, NULL);    //tftp处理

    if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK_EARLY)) {
        /* efi_init_early() already called */
        if (efi_init_obj_list() == EFI_SUCCESS)
            efi_launch_capsules();
    }

    process_button_cmds();

    s = bootdelay_process();
    if (cli_process_fdt(&s))
        cli_secure_boot_cmd(s);

    autoboot_command(s);         //自动启动linux

    /* if standard boot if enabled, assume that it will be able to boot */
    if (IS_ENABLED(CONFIG_BOOTSTD_PROG)) {
        int ret;

        ret = bootstd_prog_boot();
        printf("Standard boot failed (err=%dE)\n", ret);
        panic("Failed to boot");
    }

    cli_loop();   //若停在uboot,循环处理命令 

    panic("No CLI available");
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

×

喜欢就点赞,疼爱就打赏