乐趣区

关于linux编程:Linux编程入门正点原子Linux驱动开发指南学习2021W27

(3)U-Boot 启动流程详解

  1. lowlevel_init 函数详解

函数 lowlevel_init 在文件 arch/arm/cpu/armv7/lowlevel_init.S 中定义

#include <asm-offsets.h>
#include <config.h>
#include <linux/linkage.h>

ENTRY(lowlevel_init)
    /*
     * Setup a temporary stack. Global data is not available yet.
     */
    ldr    sp, =CONFIG_SYS_INIT_SP_ADDR       /* 宏定义在 include/configs/mx6ullevk.h sp 指向 0X91FF00,这属于 IMX6UL/IMX6ULL 的外部 ram*/
    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          /* GD_SIZE  = 248,定义在 generic-asm-offsets.h*/
    bic    sp, sp, #7    /* 八字节对齐 */
    mov    r9, sp         /*SP = 0X0091FF00-248=0X0091FE08*/
#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     /* 跳转到 s_init,实现初始化 */
    pop    {ip, pc}   /* 弹出堆栈,并将 lr 的值返回给 PC*/
ENDPROC(lowlevel_init)
  1. s_init 函数详解

上一节的初始化跳转到 s_init,该函数在定义在 arch/arm/cpu/armv7/mx6/soc.c 的 808 行。

void s_init(void)
{struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;
    struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
    u32 mask480;
    u32 mask528;
    u32 reg, periph1, periph2;

    if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) ||
        is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL))
        return;        // 判断到 CPU 类型是 MXC_CPU_MX6ULL,返回
        
    /* 省略。。。*/
}

函数返回到 cpu_init_crit,而后又返回到 save_boot_params_ret,继续执行 _main 函数。

  1. _main 函数详解

_main 函数在文件 arch/arm/lib/crt0.S 中

/*
 * entry point of crt0 sequence
 */

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)          // 设置 sp = 0X0091FF00
#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  /* 此函数有一个参数,参数为 r0 中的值, 留出晚期的 malloc 内存区域和 gd 内存区域,该函数返回 top=0X0091FA00。该函数定义在 common/init/board_init.c*/
    mov    sp, r0   /* 将 board_init_f_alloc_reserve 返回值 0X0091FA00 放入堆栈指针 */
    /* set up gd here, outside any C code */
    mov    r9, r0  /* r9 寄存器寄存着全局变量 gd 的地址 */
    bl    board_init_f_init_reserve  /* 此函数用于初始化 gd,同时设置 gd->malloc_base=0X0091FB00,这个也就是 early malloc 的起始地址。该函数定义在 common/init/board_init.c -68*/

    mov    r0, #0
    bl    board_init_f /* 初始化 DDR,定时器,实现代码拷贝等等, 定义在 common/board_f.c -1037*/

#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.
 */
/*
从新设置环境 (sp 和 gd)、获取 gd->start_addr_sp 的值赋给 sp,在函数 board_init_f
中会初始化 gd 的所有成员变量,其中 gd->start_addr_sp=0X9EF44E90,所以这里相当于设置 sp=gd->start_addr_sp=0X9EF44E90。0X9EF44E90 是 DDR 中的地址,阐明新的 sp 和 gd 将会寄存到 DDR 中,而不是外部的 RAM 了。GD_START_ADDR_SP=64,*/
    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  GD_BD=0,计算出新的 gd 的地位,并赋给 r9*/
    sub    r9, r9, #GD_SIZE        /* new GD is below bd 新的 gd 在 bd 上面,所以 r9 减去 gd 的大小就是新的 gd 的地位,获取到新的 gd 的地位当前赋值给 r9。*/

    adr    lr, here  /* 设置 lr 寄存器为 here,这样前面执行其余函数返回的时候就返回到了第 122 行的 here 地位处 */
    ldr    r0, [r9, #GD_RELOC_OFF]        /* r0 = gd->reloc_off  GD_RELOC_OFF=68*/
    
/*lr 寄存器的值加上 r0 寄存器的值,从新赋值给 lr 寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的中央去 ( 当初的 uboot 寄存的起始地址为 0X87800000,上面要
将 uboot 拷贝到 DDR 最初面的地址空间出,将 0X87800000 开始的内存空进去 ),其中就包含
here,因而 lr 中的 here 要应用重定位后的地位。*/
    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  读取 gd->relocaddr 的值赋给 r0 寄存器,此时 r0 寄存器就保留着 uboot 要拷贝的目标地址,为 0X9FF47000。GD_RELOCADDR=48*/
    b    relocate_code  /* 此函数负责将 uboot 拷贝到新的中央去,此函数定义在文件 arch/arm/lib/relocate.S -67*/
here:
/*
 * now relocate vectors
 */

    bl    relocate_vectors /* 对中断向量表做重定位, arch/arm/lib/relocate.S 
 -16*/

/* Set up final (full) environment */

    bl    c_runtime_cpu_setup    /* we still call old routine here arch/arm/cpu/armv7/start.S -77*/
#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                                    /*147-159, 革除 BSS 段 */

#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 设置 board_init_r 第一个参数 */
    ldr    r1, [r9, #GD_RELOCADDR]    /* dest_addr 设置 board_init_r 第二个参数 */
    /* 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!  common/board_r.c -997*/
#endif
    /* we should not return here. */
#endif

ENDPROC(_main)
  1. board_init_f 函数详解

board_init_f 函数次要有两个工作:
①、初始化一系列外设,比方串口、定时器,或者打印一些音讯等。
②、初始化 gd 的各个成员变量,uboot 会将本人重定位到 DRAM 最初面的地址区域,也就是将本人拷贝到 DRAM 最初面的内存区域中。这么做的目标是给 Linux 腾出空间,避免 Linux kernel 笼罩掉 uboot,将 DRAM 后面的区域残缺的空进去。在拷贝之前必定要给 uboot 各局部调配好内存地位和大小,比方 gd 应该寄存到哪个地位,malloc 内存池应该寄存到哪个地位等等。这些信息都保留在 gd 的成员变量中,因而要对 gd 的这些成员变量做初始化。最终造成一个残缺的内存“调配图”,在前面重定位 uboot 的时候就会用到这个内存“调配图”。

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;
    
/*initcall_run_list() 函数负责顺次调用 init_sequence_f 中的初始化函数,这个函数我只能说是稍微了解,要让我写,我是写不出的 */
    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();}

用于初始化调用函数的原型如下,

int initcall_run_list(const init_fnc_t init_sequence[])
{
    const init_fnc_t *init_fnc_ptr;

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        unsigned long reloc_ofs = 0;
        int ret;

        if (gd->flags & GD_FLG_RELOC)
            reloc_ofs = gd->reloc_off;
#ifdef CONFIG_EFI_APP
        reloc_ofs = (unsigned long)image_base;
#endif
        debug("initcall: %p", (char *)*init_fnc_ptr - reloc_ofs);
        if (gd->flags & GD_FLG_RELOC)
            debug("(relocated to %p)\n", (char *)*init_fnc_ptr);
        else
            debug("\n");
        ret = (*init_fnc_ptr)();
        if (ret) {printf("initcall sequence %p failed at call %p (err=%d)\n",
                   init_sequence,
                   (char *)*init_fnc_ptr - reloc_ofs, ret);
            return -1;
        }
    }
    return 0;
}

被调用的函数列表中有太多行,很多宏定义决定了某些函数是否调用,把这个去除后的函数如下:

static init_fnc_t init_sequence_f[] = {
    setup_mon_len,             /* 设置 gd 的 mon_len 成员变量 */
    initf_malloc,              /* 初始化 malloc 内存池大小和 malloc 指针 */
    initf_console_record,      /* 未定义,返回 0 */
    arch_cpu_init,           /* basic arch cpu dependent setup */
    initf_dm,                  /* 驱动模型的一些初始化, 未定义 */
    arch_cpu_init_dm,          /* 未定义 */
    mark_bootstage,           /* need timer, go after init dm 做标记 board_init_f*/
    board_early_init_f,        /*I.MX6ULL 用来初始化串口的 IO 配置 */
    timer_init,           /* initialize timer */
    board_postclk_init,        /* I.MX6ULL to Set VDDSOC to 1.175V*/
    get_clocks, 
    env_init,          /* initialize environment */
    init_baud_rate,        /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,        /* stage 1 init of console 初始控制台,设置仅一个控制台 */
    display_options,    /* say that we are here 输入版本信息 UBoot 日期和工夫 */
    display_text_info,    /* show debugging info if required 若开启 debug,会输入 text_base、bss_start、bss_end*/
    print_cpuinfo,        /* display cpu info (and speed) */
    show_board_info,
    INIT_FUNC_WATCHDOG_INIT     /* 空函数 */
    INIT_FUNC_WATCHDOG_RESET    /* 空函数 */
    init_func_i2c,
    announce_dram_init,
    dram_init,        /* configure available RAM banks 设置 gd->ram_size */
    post_init_f,             /* 上电复位自检 */
    testdram,                /* 空函数 */
    setup_dest_addr,         /* 设置目标地址,设置 gd->ram_size,gd->ram_top,gd->relocaddr 这三个的值 */
    reserve_round_4k,        /*reserve_round_4k 函 数 用 于 对 gd->relocaddr 做 4KB 对 齐,因 为 gd->relocaddr=0XA0000000,曾经是 4K 对齐了,所以调整后不变。*/
    reserve_mmu,            /* 留出 MMU 的 TLB 表的地位,调配 MMU 的 TLB 表内存当前会对 gd->relocaddr 做 64K 字节对齐 */
    reserve_trace,          /* 留出跟踪调试的内存,I.MX6ULL 没有用到 */ 
    reserve_uboot,          /* 留出重定位后的 uboot 所占用的内存区域,uboot 所占用大小由 gd->mon_len 所指定,留出 uboot 的空间当前还要对 gd->relocaddr 做 4K 字节对齐,并且从新设置 gd->start_addr_sp*/
    reserve_malloc,
    reserve_board,
    setup_machine,         /* 老版本应用,新设施没有用 */
    reserve_global_data,   /* 保留出 gd_t 的内存区域 */
    reserve_fdt,           /* 留出设施树相干的内存区域 */
    reserve_arch,          /* 空函数 */
    reserve_stacks,        /* 留出栈空间,16 字节对齐 */
    setup_dram_config,     /* 设置 DRAM,通知 linux DRAM 的起始地址和大小 */
    show_dram_config,      /* 显示 DRAM 信息 */
    display_new_sp,        /* 开启 debug 后,显示 gd->start_addr_sp*/
    reloc_fdt,             /* 重定位 fdt,没有用到 */
    setup_reloc,           /* 设置 gd 的其余一些成员变量,供前面重定位的时候应用,并且将以前的 gd 拷贝到 gd->new_gd 处。*/
}
退出移动版