[ARM Linux系统移植] bootz 启动 Linux 流程
在上一篇文章 [ARM Linux系统移植] U-Boot 启动流程 中,已经介绍了 u-boot 启动到命令行模式的流程。同时了解到,如果在倒计时之前没有进入命令行模式,则会执行 bootcmd 环境变量中的命令。而在 bootcmd 中就可以通过 bootz 命令启动 Linux。在此篇文章中就会记录 bootz 启动 Linux 的流程。
1. bootm_headers_t 结构体
首先了解一个重要的全局变量 images,它的类型为 bootm_headers_t 结构体。
- bootm_headers_t images; /* pointers to os/initrd/fdt images */
bootm_headers_t 结构体的定义如下,先只关心其中几个:os 成员记录操作系统镜像相关信息;ep 成员为操作系统的入口地址,最终 u-boot 会跳转到这边,即进入操作系统的“领地”;ft_addr 和 ft_len 成员分别为设备树所在的地址和长度;state 成员记录当前引导操作系统的状态,第 352 至 362 行定义了可能的状态。
- typedef struct bootm_headers {
- /*
- * Legacy os image header, if it is a multi component image
- * then boot_get_ramdisk() and get_fdt() will attempt to get
- * data from second and third component accordingly.
- */
- image_header_t* legacy_hdr_os; /* image header pointer */
- image_header_t legacy_hdr_os_copy; /* header copy */
- ulong legacy_hdr_valid;
- #if defined(CONFIG_FIT)
- const char* fit_uname_cfg; /* configuration node unit name */
- void* fit_hdr_os; /* os FIT image header */
- const char* fit_uname_os; /* os subimage node unit name */
- int fit_noffset_os; /* os subimage node offset */
- void* fit_hdr_rd; /* init ramdisk FIT image header */
- const char* fit_uname_rd; /* init ramdisk subimage node unit name */
- int fit_noffset_rd; /* init ramdisk subimage node offset */
- void* fit_hdr_fdt; /* FDT blob FIT image header */
- const char* fit_uname_fdt; /* FDT blob subimage node unit name */
- int fit_noffset_fdt;/* FDT blob subimage node offset */
- void* fit_hdr_setup; /* x86 setup FIT image header */
- const char* fit_uname_setup; /* x86 setup subimage node name */
- int fit_noffset_setup;/* x86 setup subimage node offset */
- #endif
- #ifndef USE_HOSTCC
- image_info_t os; /* os image info */
- ulong ep; /* entry point of OS */
- ulong rd_start, rd_end;/* ramdisk start/end */
- char* ft_addr; /* flat dev tree address */
- ulong ft_len; /* length of flat device tree */
- ulong initrd_start;
- ulong initrd_end;
- ulong cmdline_start;
- ulong cmdline_end;
- bd_t* kbd;
- #endif
- int verify; /* getenv("verify")[0] != 'n' */
- #define BOOTM_STATE_START (0x00000001)
- #define BOOTM_STATE_FINDOS (0x00000002)
- #define BOOTM_STATE_FINDOTHER (0x00000004)
- #define BOOTM_STATE_LOADOS (0x00000008)
- #define BOOTM_STATE_RAMDISK (0x00000010)
- #define BOOTM_STATE_FDT (0x00000020)
- #define BOOTM_STATE_OS_CMDLINE (0x00000040)
- #define BOOTM_STATE_OS_BD_T (0x00000080)
- #define BOOTM_STATE_OS_PREP (0x00000100)
- #define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
- #define BOOTM_STATE_OS_GO (0x00000400)
- int state;
- #ifdef CONFIG_LMB
- struct lmb lmb; /* for memory mgmt */
- #endif
- } bootm_headers_t;
2. do_bootz 函数
在 [ARM Linux系统移植] U-Boot 启动流程 中,已经了解到命令对应的执行函数名,为 “do_命令名”。所以 bootz 命令对应 do_bootz 函数。do_bootz 函数在 cmd/bootm.c 文件中定义。
- int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
- {
- int ret;
- /* Consume 'bootz' */
- argc--; argv++;
- if (bootz_start(cmdtp, flag, argc, argv, &images))
- return 1;
- /*
- * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
- * disable interrupts ourselves
- */
- bootm_disable_interrupts();
- images.os.os = IH_OS_LINUX;
- ret = do_bootm_states(cmdtp, flag, argc, argv,
- BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
- BOOTM_STATE_OS_GO,
- &images, 1);
- return ret;
- }
如下所示,首先整体看一下 do_bootz 函数的大致流程,后续会继续分析。
- └─do_bootz
- ├─bootz_start
- │ ├─do_bootm_states
- │ │ └─bootm_start
- │ ├─bootz_setup
- │ └─bootm_find_images
- │ └─boot_get_fdt
- └─do_bootm_states
- ├─bootm_os_get_boot_func
- ├─do_bootm_linux
- │ └─boot_prep_linux
- └─boot_selected_os
- └─do_bootm_linux
- └─boot_jump_linux
2.1 bootz_start 函数
- static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
- char * const argv[], bootm_headers_t *images)
- {
- int ret;
- ulong zi_start, zi_end;
- ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
- images, 1);
- /* Setup Linux kernel zImage entry point */
- if (!argc) {
- images->ep = load_addr;
- debug("* kernel: default image load address = 0x%08lx\n",
- load_addr);
- } else {
- images->ep = simple_strtoul(argv[0], NULL, 16);
- debug("* kernel: cmdline image address = 0x%08lx\n",
- images->ep);
- }
- ret = bootz_setup(images->ep, &zi_start, &zi_end);
- if (ret != 0)
- return 1;
- ……
- /*
- * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
- * have a header that provide this informaiton.
- */
- if (bootm_find_images(flag, argc, argv))
- return 1;
- ……
- }
bootz_start 函数中首先执行的是 do_bootm_states 函数(第 584 行)。do_bootm_states 函数有点类似状态机,只对当前的状态给予相应的处理,此时传递的状态为 BOOTM_STATE_START。
BOOTM_STATE_START 状态对应的处理函数为 bootm_start。其中对 images 全局变量进行清理初始化,并对 verify 等成员进行初始化,这边不继续贴出相关代码。
- int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
- int states, bootm_headers_t *images, int boot_progress)
- {
- ……
- if (states & BOOTM_STATE_START)
- ret = bootm_start(cmdtp, flag, argc, argv);
- ……
- }
bootz_start 函数中第 587 至 596 行设置 images 的 ep 成员:如果参数中指定了则使用参数指定的值,否则使用默认值。
bootz_start 函数中第 598 行调用 bootz_setup 函数。主要的作用是解析 zImage 的头部信息,如果不满足“魔数”要求就终止后续的操作,满足的话就记录镜像的起始和结束的地址。
- int bootz_setup(ulong image, ulong *start, ulong *end)
- {
- struct zimage_header *zi;
- zi = (struct zimage_header *)map_sysmem(image, 0);
- if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
- puts("Bad Linux ARM zImage magic!\n");
- return 1;
- }
- *start = zi->zi_start;
- *end = zi->zi_end;
- printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image, *start,
- *end);
- return 0;
- }
bootz_start 函数中第 608 行调用 bootm_find_images 函数。bootm_find_images 函数主要用于获取和记录 ramdisk 和设备树相关信息。由于实验用开发板没有用到 ramdisk,所以这边只摘录第 237 至 246 行:获取和记录设备树的地址(ft_addr)和长度(ft_len)。
- int bootm_find_images(int flag, int argc, char * const argv[])
- {
- ……
- #if defined(CONFIG_OF_LIBFDT)
- /* find flattened device tree */
- ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
- &images.ft_addr, &images.ft_len);
- if (ret) {
- puts("Could not find a valid device tree\n");
- return 1;
- }
- set_working_fdt_addr((ulong)images.ft_addr);
- #endif
- ……
- }
2.2 do_bootm_states 函数
- int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
- int states, bootm_headers_t *images, int boot_progress)
- {
- ……
- boot_fn = bootm_os_get_boot_func(images->os.os);
- ……
- if (!ret && (states & BOOTM_STATE_OS_PREP))
- ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
- ……
- if (!ret && (states & BOOTM_STATE_OS_GO))
- ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
- images, boot_fn);
- ……
- }
现在我们回到 do_bootz 函数中,调用完 bootz_start 后,会再次调用 do_bootm_states 函数。并在此之前会将操作系统的类型设置为 Linux(第 638 行)。此次 do_bootm_states 和 bootz_start 中的状态不同,此时的状态为 BOOTM_STATE_OS_PREP、BOOTM_STATE_OS_FAKE_GO 和 BOOTM_STATE_OS_GO 的组合。
首先会调用 bootm_os_get_boot_func 函数,获取对应系统的启动函数,可以看到这边 Linux 系统对应的启动函数为 do_bootm_linux。
- boot_os_fn *bootm_os_get_boot_func(int os)
- {
- #ifdef CONFIG_NEEDS_MANUAL_RELOC
- static bool relocated;
- if (!relocated) {
- int i;
- /* relocate boot function table */
- for (i = 0; i < ARRAY_SIZE(boot_os); i++)
- if (boot_os[i] != NULL)
- boot_os[i] += gd->reloc_off;
- relocated = true;
- }
- #endif
- return boot_os[os];
- }
- static boot_os_fn *boot_os[] = {
- ……
- [IH_OS_LINUX] = do_bootm_linux,
- ……
- };
do_bootm_linux 函数也按状态进行区分调用,可以看到以上按 BOOTM_STATE_OS_PREP 状态调用了一下,且在 boot_selected_os 中以 BOOTM_STATE_OS_GO 状态调用了一次。
- int boot_selected_os(int argc, char * const argv[], int state,
- bootm_headers_t *images, boot_os_fn *boot_fn)
- {
- arch_preboot_os();
- boot_fn(state, argc, argv, images);
- /* Stand-alone may return when 'autostart' is 'no' */
- if (images->os.type == IH_TYPE_STANDALONE ||
- state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
- return 0;
- bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
- #ifdef DEBUG
- puts("\n## Control returned to monitor - resetting...\n");
- #endif
- return BOOTM_ERR_RESET;
- }
do_bootm_linux 中的 BOOTM_STATE_OS_PREP 状态对应 boot_prep_linux 函数,看名字是启动 Linux 之前的准备,这边就先不细看了。这边主要看 boot_selected_os 中调用的 boot_jump_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_jump_linux 的部分内容如下,重点关注第 277 行和第 286 行。第 277 行将之前的入口点地址作为函数指针,传递的三个参数分别为 0、machid 和 设备树地址。第 286 行跳转到入口点,至此完成了 Linux 的启动!
- static void boot_jump_linux(bootm_headers_t *images, int flag)
- {
- ……
- unsigned long machid = gd->bd->bi_arch_number;
- ……
- kernel_entry = (void (*)(int, int, uint))images->ep;
- ……
- if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
- r2 = (unsigned long)images->ft_addr;
- else
- r2 = gd->bd->bi_boot_params;
- if (!fake) {
- ……
- kernel_entry(0, machid, r2);
- }
- ……
- }
3. 总结
本篇如果再抛去一些细节,可以定义 u-boot 的 “使命” 就是解析指定 Image,并跳转到入口点。