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, 0f
0:
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_aarch32
2:
/*
* 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 x4
1: armv8_switch_to_el2_m x4, x5, x6
ENDPROC(armv8_switch_to_el2)

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