Uboot启动内核关键步骤--AARCH64架构

518 阅读2分钟

do_bootm_states--真正的启动内核步骤

传入的flag参数BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO。do_bootm_states会根据传入的标识位的不同,选择性执行相应函数。

1.通过 函数 bootm_os_get_boot_func 来查找系统启动函数,参数 images->os.os 就是系统类型,根据这 个系统类型来选择对应的启动函数,在 do_bootz 中设置 images.os.os= IH_OS_LINUX。函数返 回值就是找到的系统启动函数,这里找到的 Linux 系统启动函数为 do_bootm_linux。因此 boot_fn=do_bootm_linux,后面执行 boot_fn 函数的地方实际上是执行的 do_bootm_linux 函数。

2.处理 BOOTM_STATE_OS_PREP 状态,调用函数 do_bootm_linux,do_bootm_linux 也是调用 boot_prep_linux 来完成具体的处理过程。boot_prep_linux 主要用于处理环境变量 bootargs,bootargs 保存着传递给 Linux kernel 的参数。

3.调用函数 boot_selected_os 启动 Linux 内核,此函数第 4 个参数为 Linux 系统镜 像头,第 5 个参数就是 Linux 系统启动函数 do_bootm_linux。

int do_bootm_states(struct cmd_tbl *cmdtp, int flag, int argc,            char *const argv[], int states, bootm_headers_t *images,            int boot_progress){    boot_os_fn *boot_fn;    ulong iflag = 0;    int ret = 0, need_boot_fn;​    images->state |= states;​    /* From now on, we need the OS boot function */    if (ret)        return ret;    boot_fn = bootm_os_get_boot_func(images->os.os);    need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |            BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |            BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);    if (boot_fn == NULL && need_boot_fn) {        if (iflag)            enable_interrupts();        printf("ERROR: booting os '%s' (%d) is not supported\n",               genimg_get_os_name(images->os.os), images->os.os);        bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);        return 1;    }​    if (!ret && (states & BOOTM_STATE_OS_PREP)) {        ret = bootm_process_cmdline_env(images->os.os == IH_OS_LINUX);        ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);    }​    /* 启动内核,调用do_bootm_linux ,传入BOOTM_STATE_OS_GO */    if (!ret && (states & BOOTM_STATE_OS_GO))        ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,                images, boot_fn);      return ret;}

do_bootm_linux

由于我们在do_bootm_states最后传入的标识位是BOOTM_STATE_OS_GO,因此这里直接boot_jump_linux跳转到内核。

int do_bootm_linux(int flag, int argc, char *const argv[],           bootm_headers_t *images){​    if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {        boot_jump_linux(images, flag);        return 0;    }​    boot_prep_linux(images);    boot_jump_linux(images, flag);    return 0;}

boot_jump_linux--从uboot跳转到kernel

入口是启动参数传入的image entry,它是Linux 内 核定义的,Linux 内核镜像文件的第一行代码就是函数 kernel_entry,而 images->ep 保存着 Linux 内核镜像的起始地址,起始地址保存的正是 Linux 内核第一行代码!

static void boot_jump_linux(bootm_headers_t *images, int flag){#ifdef CONFIG_ARM64    void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,            void *res2);    int fake = (flag & BOOTM_STATE_OS_FAKE_GO);​    debug("## Transferring control to Linux (at address %lx)...\n",        (ulong) kernel_entry);    bootstage_mark(BOOTSTAGE_ID_RUN_OS);​    announce_and_cleanup(fake);​    if (!fake) {#ifdef CONFIG_ARMV8_PSCI        armv8_setup_psci();#endif        do_nonsec_virt_switch();​        update_os_arch_secondary_cores(images->os.arch);//进入异常状态#ifdef CONFIG_ARMV8_SWITCH_TO_EL1        armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,                    (u64)switch_to_el1, ES_TO_AARCH64);#else        if ((IH_ARCH_DEFAULT == IH_ARCH_ARM64) &&            (images->os.arch == IH_ARCH_ARM))            armv8_switch_to_el2(0, (u64)gd->bd->bi_arch_number,                        (u64)images->ft_addr, 0,                        (u64)images->ep,                        ES_TO_AARCH32);        else      //跳转到内核            armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,                        images->ep,                        ES_TO_AARCH64);#endif    }#else​#endif}

armv8_switch_to_el2分析

switch_el读取当前的异常等级,el3对于label1,其余异常对应label0,这里会进入label0,比较传入的x5寄存器(c传入的ES_TO_AARCH64)值,如果是aarch64架构,就跳转到label2。在label2中,c代码传入的images->ep即kernel的入口存储在x4寄存器,所以这里会跳转到x4进入内核栈,images->ft_addr存在x0中。

ENTRY(armv8_switch_to_el2)    switch_el x6, 1f, 0f, 0f0:    cmp x5, #ES_TO_AARCH64    b.eq 2f    /*     * When loading 32-bit kernel, it will jump     * to secure firmware again, and never return.     */    bl armv8_el2_to_aarch322:    /*     * x4 is kernel entry point or switch_to_el1     * if CONFIG_ARMV8_SWITCH_TO_EL1 is defined.   * When running in EL2 now, jump to the     * address saved in x4.     */    br x41:  armv8_switch_to_el2_m x4, x5, x6ENDPROC(armv8_switch_to_el2)

总结,aarch64架构中uboot到kernel的跳转是通过armv8_switch_to_el2实现的!