目录
- 前言
- 流程图
- autoboot_command
- run_command_list
- do_bootm
- do_bootm_states
- do_bootm_linux
- boot_prep_linux
- boot_jump_linux
前言
本文在u-boot启动流程分析这篇文章的基础上,简要梳理uboot启动linux kernel的流程。
流程图
其中,
- autoboot_command位于uboot/common/autoboot.c
- run_command_list位于uboot/common/cli.c
- do_bootm位于uboot/cmd/bootm.c
- do_bootm_states位于uboot/common/bootm.c
- do_bootm_linux位于uboot/arch/arm/lib/bootm.c
- boot_prep_linux位于uboot/arch/arm/lib/bootm.c
- boot_jump_linux位于uboot/arch/arm/lib/bootm.c
autoboot_command
void autoboot_command(const char *s)
{debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");if (s && (stored_bootdelay == -2 ||(stored_bootdelay != -1 && !abortboot(stored_bootdelay)))) {bool lock;int prev;lock = IS_ENABLED(CONFIG_AUTOBOOT_KEYED) &&!IS_ENABLED(CONFIG_AUTOBOOT_KEYED_CTRLC);if (lock)prev = disable_ctrlc(1); /* disable Ctrl-C checking */run_command_list(s, -1, 0);if (lock)disable_ctrlc(prev); /* restore Ctrl-C checking */}if (IS_ENABLED(CONFIG_USE_AUTOBOOT_MENUKEY) &&menukey == AUTOBOOT_MENUKEY) {s = env_get("menucmd");if (s)run_command_list(s, -1, 0);}
}
其中,abortboot会等待一段时间timeout(由环境变量bootdelay设定),
如果有按键被按下,则会返回1,此时将停止启动linux kernel,返回命令行。如果在timeout后依然无按键被按下,则继续执行启动linux kernel的命令。
run_command_list
run_command_list调用do_bootm的流程实现的关键点:
在上面的代码中,将"bootm"和do_bootm这个函数绑定。
U_BOOT_CMD的定义在uboot/include/command.h中,可以自行查看源代码。
do_bootm
int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
#ifdef CONFIG_NEEDS_MANUAL_RELOCstatic int relocated = 0;if (!relocated) {int i;/* relocate names of sub-command table */for (i = 0; i < ARRAY_SIZE(cmd_bootm_sub); i++)cmd_bootm_sub[i].name += gd->reloc_off;relocated = 1;}
#endif/* determine if we have a sub command */argc--; argv++;if (argc > 0) {char *endp;simple_strtoul(argv[0], &endp, 16);/* endp pointing to NULL means that argv[0] was just a* valid number, pass it along to the normal bootm processing** If endp is ':' or '#' assume a FIT identifier so pass* along for normal processing.** Right now we assume the first arg should never be '-'*/if ((*endp != 0) && (*endp != ':') && (*endp != '#'))return do_bootm_subcommand(cmdtp, flag, argc, argv);}return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |BOOTM_STATE_LOADOS |
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGHBOOTM_STATE_RAMDISK |
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_MIPS)BOOTM_STATE_OS_CMDLINE |
#endifBOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |BOOTM_STATE_OS_GO, &images, 1);
}
这个函数最核心的部分就是调用do_bootm_states。
do_bootm_states
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;/** Work through the states and see how far we get. We stop on* any error.*/if (states & BOOTM_STATE_START)ret = bootm_start(cmdtp, flag, argc, argv);if (!ret && (states & BOOTM_STATE_FINDOS))ret = bootm_find_os(cmdtp, flag, argc, argv);if (!ret && (states & BOOTM_STATE_FINDOTHER))ret = bootm_find_other(cmdtp, flag, argc, argv);/* Load the OS */if (!ret && (states & BOOTM_STATE_LOADOS)) {iflag = bootm_disable_interrupts();ret = bootm_load_os(images, 0);if (ret && ret != BOOTM_ERR_OVERLAP)goto err;else if (ret == BOOTM_ERR_OVERLAP)ret = 0;}/* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGHif (!ret && (states & BOOTM_STATE_RAMDISK)) {ulong rd_len = images->rd_end - images->rd_start;ret = boot_ramdisk_high(&images->lmb, images->rd_start,rd_len, &images->initrd_start, &images->initrd_end);if (!ret) {env_set_hex("initrd_start", images->initrd_start);env_set_hex("initrd_end", images->initrd_end);}}
#endif
#if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB)if (!ret && (states & BOOTM_STATE_FDT)) {boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,&images->ft_len);}
#endif/* 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;}/* Call various other states that are not generally used */if (!ret && (states & BOOTM_STATE_OS_CMDLINE))ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);if (!ret && (states & BOOTM_STATE_OS_BD_T))ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);if (!ret && (states & BOOTM_STATE_OS_PREP)) {ret = bootm_process_cmdline_env(images->os.os == IH_OS_LINUX);if (ret) {printf("Cmdline setup failed (err=%d)\n", ret);ret = CMD_RET_FAILURE;goto err;}ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);}#ifdef CONFIG_TRACE/* Pretend to run the OS, then run a user command */if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {char *cmd_list = env_get("fakegocmd");ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,images, boot_fn);if (!ret && cmd_list)ret = run_command_list(cmd_list, -1, flag);}
#endif/* Check for unsupported subcommand. */if (ret) {puts("subcommand not supported\n");return ret;}/* Now run the OS! We hope this doesn't return */if (!ret && (states & BOOTM_STATE_OS_GO))ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);/* Deal with any fallout */
err:if (iflag)enable_interrupts();if (ret == BOOTM_ERR_UNIMPLEMENTED)bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);else if (ret == BOOTM_ERR_RESET)do_reset(cmdtp, flag, argc, argv);return ret;
}
该函数的核心部分:
- bootm_load_os将kernel镜像加载到内存中。
- 调用do_bootm_linux启动kernel。
do_bootm_linux
int do_bootm_linux(int flag, int argc, char *const argv[],bootm_headers_t *images)
{/* No need for those on ARM */if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)return -1;if (flag & BOOTM_STATE_OS_PREP) {boot_prep_linux(images);return 0;}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_prep_linux负责准备传递给kernel的参数。
- boot_jump_linux负责跳转执行kernel代码。
boot_prep_linux
static void boot_prep_linux(bootm_headers_t *images)
{char *commandline = env_get("bootargs");if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
#ifdef CONFIG_OF_LIBFDTdebug("using: FDT\n");if (image_setup_linux(images)) {printf("FDT creation failed! hanging...");hang();}
#endif} else if (BOOTM_ENABLE_TAGS) {debug("using: ATAGS\n");setup_start_tag(gd->bd);if (BOOTM_ENABLE_SERIAL_TAG)setup_serial_tag(¶ms);if (BOOTM_ENABLE_CMDLINE_TAG)setup_commandline_tag(gd->bd, commandline);if (BOOTM_ENABLE_REVISION_TAG)setup_revision_tag(¶ms);if (BOOTM_ENABLE_MEMORY_TAGS)setup_memory_tags(gd->bd);if (BOOTM_ENABLE_INITRD_TAG) {/** In boot_ramdisk_high(), it may relocate ramdisk to* a specified location. And set images->initrd_start &* images->initrd_end to relocated ramdisk's start/end* addresses. So use them instead of images->rd_start &* images->rd_end when possible.*/if (images->initrd_start && images->initrd_end) {setup_initrd_tag(gd->bd, images->initrd_start,images->initrd_end);} else if (images->rd_start && images->rd_end) {setup_initrd_tag(gd->bd, images->rd_start,images->rd_end);}}setup_board_tags(¶ms);setup_end_tag(gd->bd);} else {printf("FDT and ATAGS support not compiled in - hanging\n");hang();}board_prep_linux(images);
}
可以看到,u-boot使用的是tag的方式传参。共计分为以下几类tag:
- setup_serial_tag设定与板子序列号(64位)相关的参数。
- setup_commandline_tag设置命令行启动参数,参数来自环境变量"bootargs"。
- setup_revision_tag 设置修订版本。
- setup_memory_tags设置内存区块相关参数。
- setup_initrd_tag设置ramdisk相关的参数。
注:以上参数并非都是必要的。
boot_jump_linux
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,void *res2);int fake = (flag & BOOTM_STATE_OS_FAKE_GO);kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,void *res2))images->ep;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_PSCIarmv8_setup_psci();
#endifdo_nonsec_virt_switch();update_os_arch_secondary_cores(images->os.arch);#ifdef CONFIG_ARMV8_SWITCH_TO_EL1armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,(u64)switch_to_el1, ES_TO_AARCH64);
#elseif ((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);elsearmv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,images->ep,ES_TO_AARCH64);
#endif}
#elseunsigned long machid = gd->bd->bi_arch_number;char *s;void (*kernel_entry)(int zero, int arch, uint params);unsigned long r2;int fake = (flag & BOOTM_STATE_OS_FAKE_GO);kernel_entry = (void (*)(int, int, uint))images->ep;
#ifdef CONFIG_CPU_V7Mulong addr = (ulong)kernel_entry | 1;kernel_entry = (void *)addr;
#endifs = env_get("machid");if (s) {if (strict_strtoul(s, 16, &machid) < 0) {debug("strict_strtoul failed!\n");return;}printf("Using machid 0x%lx from environment\n", machid);}debug("## Transferring control to Linux (at address %08lx)" \"...\n", (ulong) kernel_entry);bootstage_mark(BOOTSTAGE_ID_RUN_OS);announce_and_cleanup(fake);if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)r2 = (unsigned long)images->ft_addr;elser2 = gd->bd->bi_boot_params;if (!fake) {
#ifdef CONFIG_ARMV7_NONSECif (armv7_boot_nonsec()) {armv7_init_nonsec();secure_ram_addr(_do_nonsec_entry)(kernel_entry,0, machid, r2);} else
#endifkernel_entry(0, machid, r2);}
#endif
}
-
r2 = gd->bd->bi_boot_params; 是将TAG列表所在的内存地址赋值给通用寄存器R2。
-
kernel_entry(0, machid, r2); 跳转执行kernel的代码(自此uboot的使命完成,生命周期结束)。
综上,Uboot传递给linux kernel的参数是通过R2传递的。当linux kernel启动后,会从R2中拿到TAG列表的地址,然后将TAG参数解析出来使用。