[ARM Linux系统移植] bootz 启动 Linux 流程

在上一篇文章 [ARM Linux系统移植] U-Boot 启动流程 中,已经介绍了 u-boot 启动到命令行模式的流程。同时了解到,如果在倒计时之前没有进入命令行模式,则会执行 bootcmd 环境变量中的命令。而在 bootcmd 中就可以通过 bootz 命令启动 Linux。在此篇文章中就会记录 bootz 启动 Linux 的流程。

1. bootm_headers_t 结构体

首先了解一个重要的全局变量 images,它的类型为 bootm_headers_t 结构体。

cmd/bootm.c
  1. bootm_headers_t images;        /* pointers to os/initrd/fdt images */

bootm_headers_t 结构体的定义如下,先只关心其中几个:os 成员记录操作系统镜像相关信息;ep 成员为操作系统的入口地址,最终 u-boot 会跳转到这边,即进入操作系统的“领地”;ft_addrft_len 成员分别为设备树所在的地址和长度;state 成员记录当前引导操作系统的状态,第 352 至 362 行定义了可能的状态。

include/image.h
  1. typedef struct bootm_headers {
  2.     /*
  3.      * Legacy os image header, if it is a multi component image
  4.      * then boot_get_ramdisk() and get_fdt() will attempt to get
  5.      * data from second and third component accordingly.
  6.      */
  7.     image_header_t* legacy_hdr_os;     /* image header pointer */
  8.     image_header_t    legacy_hdr_os_copy;   /* header copy */
  9.     ulong        legacy_hdr_valid;
  10.  
  11. #if defined(CONFIG_FIT)
  12.     const char* fit_uname_cfg; /* configuration node unit name */
  13.  
  14.     void* fit_hdr_os; /* os FIT image header */
  15.     const char* fit_uname_os; /* os subimage node unit name */
  16.     int      fit_noffset_os;   /* os subimage node offset */
  17.  
  18.     void* fit_hdr_rd; /* init ramdisk FIT image header */
  19.     const char* fit_uname_rd; /* init ramdisk subimage node unit name */
  20.     int      fit_noffset_rd;   /* init ramdisk subimage node offset */
  21.  
  22.     void* fit_hdr_fdt;    /* FDT blob FIT image header */
  23.     const char* fit_uname_fdt; /* FDT blob subimage node unit name */
  24.     int      fit_noffset_fdt;/* FDT blob subimage node offset */
  25.  
  26.     void* fit_hdr_setup;  /* x86 setup FIT image header */
  27.     const char* fit_uname_setup; /* x86 setup subimage node name */
  28.     int      fit_noffset_setup;/* x86 setup subimage node offset */
  29. #endif
  30.  
  31. #ifndef USE_HOSTCC
  32.     image_info_t os;      /* os image info */
  33.     ulong        ep;      /* entry point of OS */
  34.  
  35.     ulong        rd_start, rd_end;/* ramdisk start/end */
  36.  
  37.     char* ft_addr;    /* flat dev tree address */
  38.     ulong        ft_len;      /* length of flat device tree */
  39.  
  40.     ulong        initrd_start;
  41.     ulong        initrd_end;
  42.     ulong        cmdline_start;
  43.     ulong        cmdline_end;
  44.     bd_t* kbd;
  45. #endif
  46.  
  47.     int      verify;      /* getenv("verify")[0] != 'n' */
  48.  
  49. #define  BOOTM_STATE_START (0x00000001)
  50. #define  BOOTM_STATE_FINDOS    (0x00000002)
  51. #define  BOOTM_STATE_FINDOTHER (0x00000004)
  52. #define  BOOTM_STATE_LOADOS    (0x00000008)
  53. #define  BOOTM_STATE_RAMDISK   (0x00000010)
  54. #define  BOOTM_STATE_FDT       (0x00000020)
  55. #define  BOOTM_STATE_OS_CMDLINE (0x00000040)
  56. #define  BOOTM_STATE_OS_BD_T   (0x00000080)
  57. #define  BOOTM_STATE_OS_PREP   (0x00000100)
  58. #define  BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
  59. #define  BOOTM_STATE_OS_GO (0x00000400)
  60.     int      state;
  61.  
  62. #ifdef CONFIG_LMB
  63.     struct lmb   lmb;     /* for memory mgmt */
  64. #endif
  65. } bootm_headers_t;

2. do_bootz 函数

[ARM Linux系统移植] U-Boot 启动流程 中,已经了解到命令对应的执行函数名,为 “do_命令名”。所以 bootz 命令对应 do_bootz 函数。do_bootz 函数在 cmd/bootm.c 文件中定义。

cmd/bootm.c
  1. int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
  2. {
  3.     int ret;
  4.  
  5.     /* Consume 'bootz' */
  6.     argc--; argv++;
  7.  
  8.     if (bootz_start(cmdtp, flag, argc, argv, &images))
  9.         return 1;
  10.  
  11.     /*
  12.      * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
  13.      * disable interrupts ourselves
  14.      */
  15.     bootm_disable_interrupts();
  16.  
  17.     images.os.os = IH_OS_LINUX;
  18.     ret = do_bootm_states(cmdtp, flag, argc, argv,
  19.                   BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
  20.                   BOOTM_STATE_OS_GO,
  21.                   &images, 1);
  22.  
  23.     return ret;
  24. }

如下所示,首先整体看一下 do_bootz 函数的大致流程,后续会继续分析。

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 函数

common/bootm.c
  1. static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
  2.             char * const argv[], bootm_headers_t *images)
  3. {
  4.     int ret;
  5.     ulong zi_start, zi_end;
  6.  
  7.     ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
  8.                   images, 1);
  9.  
  10.     /* Setup Linux kernel zImage entry point */
  11.     if (!argc) {
  12.         images->ep = load_addr;
  13.         debug("*  kernel: default image load address = 0x%08lx\n",
  14.                 load_addr);
  15.     } else {
  16.         images->ep = simple_strtoul(argv[0], NULL16);
  17.         debug("*  kernel: cmdline image address = 0x%08lx\n",
  18.             images->ep);
  19.     }
  20.  
  21.     ret = bootz_setup(images->ep, &zi_start, &zi_end);
  22.     if (ret != 0)
  23.         return 1;
  24.     ……
  25.     /*
  26.      * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
  27.      * have a header that provide this informaiton.
  28.      */
  29.     if (bootm_find_images(flag, argc, argv))
  30.         return 1;
  31.     ……
  32. }

bootz_start 函数中首先执行的是 do_bootm_states 函数(第 584 行)。do_bootm_states 函数有点类似状态机,只对当前的状态给予相应的处理,此时传递的状态为 BOOTM_STATE_START

BOOTM_STATE_START 状态对应的处理函数为 bootm_start。其中对 images 全局变量进行清理初始化,并对 verify 等成员进行初始化,这边不继续贴出相关代码。

common/bootm.c
  1. int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
  2.             int states, bootm_headers_t *images, int boot_progress)
  3. {
  4.     ……
  5.     if (states & BOOTM_STATE_START)
  6.         ret = bootm_start(cmdtp, flag, argc, argv);
  7.     ……
  8. }

bootz_start 函数中第 587 至 596 行设置 imagesep 成员:如果参数中指定了则使用参数指定的值,否则使用默认值。

bootz_start 函数中第 598 行调用 bootz_setup 函数。主要的作用是解析 zImage 的头部信息,如果不满足“魔数”要求就终止后续的操作,满足的话就记录镜像的起始和结束的地址。

arch/arm/lib/bootm.c
  1. int bootz_setup(ulong image, ulong *start, ulong *end)
  2. {
  3.     struct zimage_header *zi;
  4.  
  5.     zi = (struct zimage_header *)map_sysmem(image, 0);
  6.     if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
  7.         puts("Bad Linux ARM zImage magic!\n");
  8.         return 1;
  9.     }
  10.  
  11.     *start = zi->zi_start;
  12.     *end = zi->zi_end;
  13.  
  14.     printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image, *start,
  15.           *end);
  16.  
  17.     return 0;
  18. }

bootz_start 函数中第 608 行调用 bootm_find_images 函数。bootm_find_images 函数主要用于获取和记录 ramdisk 和设备树相关信息。由于实验用开发板没有用到 ramdisk,所以这边只摘录第 237 至 246 行:获取和记录设备树的地址(ft_addr)和长度(ft_len)。

common/bootm.c
  1. int bootm_find_images(int flag, int argc, char * const argv[])
  2. {
  3.     ……
  4. #if defined(CONFIG_OF_LIBFDT)
  5.     /* find flattened device tree */
  6.     ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
  7.                &images.ft_addr, &images.ft_len);
  8.     if (ret) {
  9.         puts("Could not find a valid device tree\n");
  10.         return 1;
  11.     }
  12.     set_working_fdt_addr((ulong)images.ft_addr);
  13. #endif
  14.     ……
  15. }

2.2 do_bootm_states 函数

common/bootm.c
  1. int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
  2.             int states, bootm_headers_t *images, int boot_progress)
  3. {
  4.     ……
  5.     boot_fn = bootm_os_get_boot_func(images->os.os);
  6.     ……
  7.     if (!ret && (states & BOOTM_STATE_OS_PREP))
  8.         ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
  9.     ……
  10.     if (!ret && (states & BOOTM_STATE_OS_GO))
  11.         ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
  12.                 images, boot_fn);
  13.     ……
  14. }

现在我们回到 do_bootz 函数中,调用完 bootz_start 后,会再次调用 do_bootm_states 函数。并在此之前会将操作系统的类型设置为 Linux(第 638 行)。此次 do_bootm_statesbootz_start 中的状态不同,此时的状态为 BOOTM_STATE_OS_PREPBOOTM_STATE_OS_FAKE_GOBOOTM_STATE_OS_GO 的组合。

首先会调用 bootm_os_get_boot_func 函数,获取对应系统的启动函数,可以看到这边 Linux 系统对应的启动函数为 do_bootm_linux

common/bootm_os.c
  1. boot_os_fn *bootm_os_get_boot_func(int os)
  2. {
  3. #ifdef CONFIG_NEEDS_MANUAL_RELOC
  4.     static bool relocated;
  5.  
  6.     if (!relocated) {
  7.         int i;
  8.  
  9.         /* relocate boot function table */
  10.         for (i = 0; i < ARRAY_SIZE(boot_os); i++)
  11.             if (boot_os[i] != NULL)
  12.                 boot_os[i] += gd->reloc_off;
  13.  
  14.         relocated = true;
  15.     }
  16. #endif
  17.     return boot_os[os];
  18. }
  1. static boot_os_fn *boot_os[] = {
  2.     ……
  3.     [IH_OS_LINUX] = do_bootm_linux,
  4.     ……
  5. };

do_bootm_linux 函数也按状态进行区分调用,可以看到以上按 BOOTM_STATE_OS_PREP 状态调用了一下,且在 boot_selected_os 中以 BOOTM_STATE_OS_GO 状态调用了一次。

common/bootm_os.c
  1. int boot_selected_os(int argc, char * const argv[]int state,
  2.              bootm_headers_t *images, boot_os_fn *boot_fn)
  3. {
  4.     arch_preboot_os();
  5.     boot_fn(state, argc, argv, images);
  6.  
  7.     /* Stand-alone may return when 'autostart' is 'no' */
  8.     if (images->os.type == IH_TYPE_STANDALONE ||
  9.         state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
  10.         return 0;
  11.     bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
  12. #ifdef DEBUG
  13.     puts("\n## Control returned to monitor - resetting...\n");
  14. #endif
  15.     return BOOTM_ERR_RESET;
  16. }

do_bootm_linux 中的 BOOTM_STATE_OS_PREP 状态对应 boot_prep_linux 函数,看名字是启动 Linux 之前的准备,这边就先不细看了。这边主要看 boot_selected_os 中调用的 boot_jump_linux 函数。

arch/arm/lib/bootm.c
  1. int do_bootm_linux(int flag, int argc, char * const argv[],
  2.            bootm_headers_t *images)
  3. {
  4.     /* No need for those on ARM */
  5.     if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
  6.         return -1;
  7.  
  8.     if (flag & BOOTM_STATE_OS_PREP) {
  9.         boot_prep_linux(images);
  10.         return 0;
  11.     }
  12.  
  13.     if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
  14.         boot_jump_linux(images, flag);
  15.         return 0;
  16.     }
  17.  
  18.     boot_prep_linux(images);
  19.     boot_jump_linux(images, flag);
  20.     return 0;
  21. }

boot_jump_linux 的部分内容如下,重点关注第 277 行和第 286 行。第 277 行将之前的入口点地址作为函数指针,传递的三个参数分别为 0、machid 和 设备树地址。第 286 行跳转到入口点,至此完成了 Linux 的启动!

arch/arm/lib/bootm.c
  1. static void boot_jump_linux(bootm_headers_t *images, int flag)
  2. {
  3.     ……
  4.     unsigned long machid = gd->bd->bi_arch_number;
  5.     ……
  6.     kernel_entry = (void (*)(intintuint))images->ep;
  7.     ……
  8.     if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
  9.         r2 = (unsigned long)images->ft_addr;
  10.     else
  11.         r2 = gd->bd->bi_boot_params;
  12.  
  13.     if (!fake) {
  14.         ……
  15.             kernel_entry(0, machid, r2);
  16.     }
  17.     ……
  18. }

3. 总结

本篇如果再抛去一些细节,可以定义 u-boot 的 “使命” 就是解析指定 Image,并跳转到入口点。