[IMX][UBoot] 10.启动流程 (6) - bootz 命令启动 Linux

发布于:2025-07-04 ⋅ 阅读:(21) ⋅ 点赞:(0)

文章链接

UBoot 启动流程 (1) - 基本流程

UBoot 启动流程 (2) - 平台前期初始化阶段 - board_init_f

UBoot 启动流程 (3) - UBoot 程序重定位 - relocate_code

UBoot 启动流程 (4) - 平台后期初始化阶段 - board_init_r

UBoot 启动流程 (5) - UBoot 运行阶段 - main_loop

UBoot 启动流程 (6) - bootz 命令启动 Linux


目录

1.系统镜像相关的数据结构

1.1.镜像的头部信息 - image_header_t

1.2.镜像的基本信息 - image_info_t

1.3.镜像文件在 uboot 中的描述 - bootm_headers_t

1.3.1.镜像头部信息

1.3.2.系统信息

1.3.3.引导状态

1.3.4.线性内存管理器 - LMB

2.booz 命令启动内核的流程 - do_bootz()

2.1.根据阶段执行对应的函数 - do_bootm_states()

2.2.images 初始化 - bootz_start()

2.2.1.初始化 images 和 LMB - bootm_start()

2.2.2.设置加载地址

2.2.3.检查 zImage 是否有效 - bootz_setup()

2.2.4.在 LMB 中为镜像文件预留空间 - lmb_reserve()

2.2.5.查找 RAMDisk 和设备树 - bootm_find_images()

2.3.禁用中断 - bootm_disable_interrupts()

2.4.准备运行环境并启动内核

2.4.1.查找 OS 对应的启动函数 - bootm_os_get_boot_func()

2.4.2.Linux 启动函数 - do_bootm_linux()

2.4.3.Linux 运行环境准备 - boot_prep_linux()

2.4.4.跳转至 Linux 内核运行 - boot_jump_linux()

3.启动过程概览


1.系统镜像相关的数据结构

  • image_header_t 类型的结构体,保存系统镜像的头部信息,如 CRC 校验码、操作系统类型等;

  • image_info_t 类型的结构体,保存系统镜像的基本信息,如系统镜像的起始地址,加载地址等;

  • bootm_headers_t 类型的结构体,保存系统镜像的所有信息 (包括镜像头部信息、镜像基本信息等),以及平台中涉及系统加载的相关信息 (如系统镜像在内存中的起始地址,设备树的加载地址等);

1.1.镜像的头部信息 - image_header_t

image_header_t 保存系统镜像的头部信息:

// include/image.h
typedef struct image_header {
    __be32      ih_magic;   /* Image Header Magic Number    */
    __be32      ih_hcrc;    /* Image Header CRC Checksum    */
    __be32      ih_time;    /* Image Creation Timestamp */
    __be32      ih_size;    /* Image Data Size      */
    __be32      ih_load;    /* Data  Load  Address      */
    __be32      ih_ep;      /* Entry Point Address      */
    __be32      ih_dcrc;    /* Image Data CRC Checksum  */
    uint8_t     ih_os;      /* Operating System     */
    uint8_t     ih_arch;    /* CPU architecture     */
    uint8_t     ih_type;    /* Image Type           */
    uint8_t     ih_comp;    /* Compression Type     */
    uint8_t     ih_name[IH_NMLEN];  /* Image Name       */
} image_header_t;

其成员的含义如下 (其中 __be32 表示大端序 uint32 数据类型):

  • ih_magic:镜像头部的魔数,根据该值判断文件是否为镜像文件;

  • ih_hcrc:镜像头部的 CRC 校验码,判断文件是否有效 (是否出错);

  • ih_time:该镜像文件的创建时间;

  • ih_size:整个系统镜像文件的大小;

  • ih_load:系统在内存中的加载地址 (程序存储起始地址);

  • ih_ep:系统在内存中的运行地址 (程序运行起始地址);

  • ih_dcrc:整个系统镜像文件的 CRC 校验码;

  • ih_os:系统代码,例如,Linux 系统的 ih_os 值为 0x05:

// include/image.h
/*
 * Operating System Codes
 */
#define IH_OS_INVALID       0   /* Invalid OS */
#define IH_OS_OPENBSD       1   /* OpenBSD    */
...
#define IH_OS_LINUX         5   /* Linux      */
...
#define IH_OS_QNX           16  /* QNX        */
...
#define IH_OS_OPENRTOS      24  /* OpenRTOS   */
  • ih_arch:CPU 的架构代码,例如,ARM 架构 SoC 的 ih_arch 为 0x02:

// include/image.h
/*
 * CPU Architecture Codes (supported by Linux)
 */
#define IH_ARCH_INVALID     0   /* Invalid CPU  */
...
#define IH_ARCH_ARM         2   /* ARM      */
#define IH_ARCH_I386        3   /* Intel x86    */
...
#define IH_ARCH_MIPS        5   /* MIPS     */
...
#define IH_ARCH_ARM64       22  /* ARM64    */
...
#define IH_ARCH_X86_64      24  /* AMD x86_64, Intel and Via */
  • ih_type:镜像类型,例如系统镜像、Boot 镜像等:

// include/image.h
/*
 * Image Types
 *
 * "Standalone Programs" are directly runnable in the environment
 *  provided by U-Boot; it is expected that (if they behave
 *  well) you can continue to work in U-Boot after return from
 *  the Standalone Program.
 * "OS Kernel Images" are usually images of some Embedded OS which
 *  will take over control completely. Usually these programs
 *  will install their own set of exception handlers, device
 *  drivers, set up the MMU, etc. - this means, that you cannot
 *  expect to re-enter U-Boot except by resetting the CPU.
 * "RAMDisk Images" are more or less just data blocks, and their
 *  parameters (address, size) are passed to an OS kernel that is
 *  being started.
 * "Multi-File Images" contain several images, typically an OS
 *  (Linux) kernel image and one or more data images like
 *  RAMDisks. This construct is useful for instance when you want
 *  to boot over the network using BOOTP etc., where the boot
 *  server provides just a single image file, but you want to get
 *  for instance an OS kernel and a RAMDisk image.
 *
 *  "Multi-File Images" start with a list of image sizes, each
 *  image size (in bytes) specified by an "uint32_t" in network
 *  byte order. This list is terminated by an "(uint32_t)0".
 *  Immediately after the terminating 0 follow the images, one by
 *  one, all aligned on "uint32_t" boundaries (size rounded up to
 *  a multiple of 4 bytes - except for the last file).
 *
 * "Firmware Images" are binary images containing firmware (like
 *  U-Boot or FPGA images) which usually will be programmed to
 *  flash memory.
 *
 * "Script files" are command sequences that will be executed by
 *  U-Boot's command interpreter; this feature is especially
 *  useful when you configure U-Boot to use a real shell (hush)
 *  as command interpreter (=> Shell Scripts).
 */
#define IH_TYPE_INVALID     0   /* Invalid Image        */
#define IH_TYPE_STANDALONE  1   /* Standalone Program       */
#define IH_TYPE_KERNEL      2   /* OS Kernel Image      */
...
#define IH_TYPE_RKIMAGE     23  /* Rockchip Boot Image      */
#define IH_TYPE_RKSD        24  /* Rockchip SD card     */
#define IH_TYPE_RKSPI       25  /* Rockchip SPI image       */
#define IH_TYPE_ZYNQIMAGE   26  /* Xilinx Zynq Boot Image */
#define IH_TYPE_COUNT       27  /* Number of image types */
  • ih_comp:文件压缩类型:

// include/image.h
/*
 * Compression Types
 */
#define IH_COMP_NONE        0   /* No    Compression Used   */
#define IH_COMP_GZIP        1   /* gzip  Compression Used   */
#define IH_COMP_BZIP2       2   /* bzip2 Compression Used   */
#define IH_COMP_LZMA        3   /* lzma  Compression Used   */
#define IH_COMP_LZO         4   /* lzo   Compression Used   */
#define IH_COMP_LZ4         5   /* lz4   Compression Used   */
  • ih_name[IH_NMLEN]:镜像名称,名称最大长度为 32 个字符 (包含 31 个有效字符和 1 个结束符 \0):

// include/image.h
#define IH_NMLEN        32  /* Image Name Length        */

1.2.镜像的基本信息 - image_info_t

image_info_t 类型的结构体中,包含了系统镜像的基本信息,如起始地址、所支持的 CPU 架构等:

// include/image.h
typedef struct image_info {
    ulong       start, end;     /* start/end of blob */
    ulong       image_start, image_len; /* start of image within blob, len of image */
    ulong       load;           /* load addr for the image */
    uint8_t     comp, type, os; /* compression, type of image, os type */
    uint8_t     arch;           /* CPU architecture */
} image_info_t;

其各个成员的含义如下:

  • start, end:镜像文件在存储介质中的起始地址和结束地址;

  • image_start, image_len:系统程序在文件中的起始地址和长度;

  • load:系统程序在内存中的加载地址 (程序的起始地址,如 0x80008000 等);

  • comp:系统镜像的压缩格式;

  • type:镜像类型,例如系统镜像、Boot 镜像等:

  • os:系统类型,如 Linux、Windows 等;

  • arch:系统对应的架构,如 ARM、MIPS 等;

1.3.镜像文件在 uboot 中的描述 - bootm_headers_t

uboot 中使用 bootm_headers_t 类型的变量 images 描述镜像文件 (如 zImage、uImage 等):

// include/image.h
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;
...
    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;
...
    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;
...
    struct lmb  lmb;        /* for memory mgmt */
...
} bootm_headers_t;
-----------------------------------------------------------------------------

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

1.3.1.镜像头部信息

  • *legacy_hdr_os 为镜像头部信息的指针;

  • legacy_hdr_os_copy 为镜像头部信息的副本;

  • legacy_hdr_valid 标记镜像头部信息是否有效;

// include/image.h
typedef struct bootm_headers {
    image_header_t  *legacy_hdr_os;      /* image header pointer */
    image_header_t   legacy_hdr_os_copy; /* header copy */
    ulong            legacy_hdr_valid;
    ...

1.3.2.系统信息

  • os 为系统镜像的基本信息;

  • ep 为系统程序在镜像文件中的起始地址;

  • rd_start, rd_end 分别为虚拟磁盘 RAMDisk 在内存中的起始地址和结束地址;

  • *ft_addr 为设备树的起始地址;

  • ft_len 为设备树的长度;

  • initrd_start 为初始化 RAMDisk 的起始地址;

  • initrd_end 为初始化 RAMDisk 的结束地址;

  • cmdline_start 为 Linux 启动命令行的起始地址;

  • cmdline_end 为 Linux 启动命令行的结束地址;

  • *kbd 为开发板的硬件信息 bd 的指针;

  • verify 为校验使能标志,若环境变量 verify 不为 n 则启用校验;

// include/image.h
typedef struct bootm_headers {
    ...
    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;
...
    int     verify;     /* getenv("verify")[0] != 'n' */

1.3.3.引导状态

  • BOOTM_STATE_xxx 宏定义内核的引导状态,即内核加载过程中的各个阶段;

  • state 为内核当前的引导状态;

// include/image.h
typedef struct bootm_headers {
    ...
#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;

1.3.4.线性内存管理器 - LMB

  • lmb (Logical Memory Blocks) 为线性内存块管理器,用于动态内存分配:

    • memory 为可用的内存区域;

    • reserved 为预留内存区域,如设备映射内存、启动代码所占用的内存;

// include/lmb.h
struct lmb {
    struct lmb_region memory;
    struct lmb_region reserved;
};
-------------------------------------------

// include/image.h
typedef struct bootm_headers {
    ...
    struct lmb  lmb;        /* for memory mgmt */

2.booz 命令启动内核的流程 - do_bootz()

bootz 命令用于启动 Linux 内核,其对应的执行函数为 do_bootz(),包含如下几个阶段:

  • bootz_start() 初始化 images 结构体,设置系统的加载地址,并为系统镜像预留内存;

  • bootm_disable_interrupts() 在启动过程中禁用所有中断;

  • 准备内核的执行环境并启动内核;

// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    ...
    // 初始化 images 结构体
    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(); 

    // 标记需要启动的系统类型为 Linux
    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;
}

2.1.根据阶段执行对应的函数 - do_bootm_states()

内核的加载过程分为多个阶段,do_bootm_states() 根据阶段标志 states 调用对应的函数:

// common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
            int states, bootm_headers_t *images, int boot_progress)
{
    ...
    /* 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);
        argc = 0;   /* consume the args */
    }

    /* Load the OS */
    // 加载内核
    if (!ret && (states & BOOTM_STATE_LOADOS)) {
        ulong load_end;
        iflag = bootm_disable_interrupts();
        ret = bootm_load_os(images, &load_end, 0);
        if (ret == 0)
            lmb_reserve(&images->lmb, images->os.load,
                    (load_end - images->os.load));
        ...
    }

    /* Relocate the ramdisk */
    // RAMDisk 重定位
    if (!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 && (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);
    }

    // 查找内核对应的启动函数
    /* From now on, we need the OS boot function */
    ...
    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) {
        ...
    }

    // 执行内核对应的启动函数
    /* 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 = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);

...

    // 运行内核
    /* 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);
                |--> boot_fn(state, argc, argv, images);

    // 启用中断,执行复位
    /* 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;
}

2.2.images 初始化 - bootz_start()

uboot 使用 bootm_headers_t 类型的结构体 images 描述镜像文件,内核启动前,先调用 bootz_start() 初始化 images 结构体以及运行环境 (分配内存、查找设备树等):

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
            char * const argv[], bootm_headers_t *images)
{
    ...
    // 执行初始化阶段 BOOTM_STATE_START
    ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START, images, 1);

    /* Setup Linux kernel zImage entry point */
    // 设置 OS 在内存中的起始地址 0x80800000
    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);
    ...

    // 在内存中为镜像文件预留空间
    lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);

    /*
     * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
     * have a header that provide this informaiton.
     */
     // 查找 RAMDisk、设备树等其他模块
    if (bootm_find_images(flag, argc, argv))
        return 1;
    ...

    return 0;
}

2.2.1.初始化 images 和 LMB - bootm_start()

do_bootm_states() 调用 BOOTM_STATE_START 阶段对应的函数 bootm_start():

  • 调用 memset() 清空 images 结构体;

  • boot_start_lmb() 初始化 LMB,并创建一个 dummy lmb,同时在内存中分配内存;

  • bootstage_mark_name() 记录当前阶段的相关信息;

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
            char * const argv[], bootm_headers_t *images)
{
    ...
    // 执行初始化阶段 BOOTM_STATE_START
    ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START, images, 1);
-------------------------------------------------------------------------------   

// common/bootm.c
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);
-------------------------------------------------------------------------------

// common/bootm.c
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,
               char * const argv[])
{
    // 清空 images 结构体
    memset((void *)&images, 0, sizeof(images));
    
    // 检查环境变量 verify,确定是否需要对镜像文件进行校验
    images.verify = getenv_yesno("verify"); 
    
    // 初始化 LMB
    boot_start_lmb(&images);
        |--> lmb_init(&images->lmb); // 将 LMB 的地址、大小等全部置 0,计数值初始化为 1
        |--> lmb_add(&images->lmb, (phys_addr_t)mem_start, mem_size); // 分配一块 lmb
        |--> arch_lmb_reserve(&images->lmb);
            |--> sp = get_sp(); // 获取当前的 SP 指针
            |--> sp -= 4096; // 4K 字节对齐
            |--> lmb_reserve(lmb, sp, ...); // 分配栈空间 
        |--> board_lmb_reserve(&images->lmb); // 未实现该方法

    // 记录当前阶段的相关信息
    bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
    images.state = BOOTM_STATE_START;

    return 0;
}

2.2.2.设置加载地址

用户可以在命令行中指定 OS 的加载地址,若未指定则使用 load_addr 中保存的默认地址:

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
            char * const argv[], bootm_headers_t *images)
{
    ...
    // 设置 OS 在内存中的起始地址
    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);
    }

2.2.3.检查 zImage 是否有效 - bootz_setup()

  • 使用 map_sysmem() 将镜像地址映射为可访问的指针 (获取镜像文件的头部信息);

  • 检查魔数是否为 Linux 镜像文件;

  • 打印镜像文件的起始地址和结束地址;

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
            char * const argv[], bootm_headers_t *images)
{
    ...
    // 检查镜像文件是否有效
    ret = bootz_setup(images->ep, &zi_start, &zi_end);
--------------------------------------------------------------

// arch/arm/lib/bootm.c
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) { // 检查是否为 Linux 镜像文件
        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;
}

2.2.4.在 LMB 中为镜像文件预留空间 - lmb_reserve()

调用 lmb_reserve() 为镜像文件预留内存空间 (分配 LMB):

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
            char * const argv[], bootm_headers_t *images)
{
    ...
    // 在内存中为镜像文件预留空间
    lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
----------------------------------------------------------------

// lib/lmb.c
long lmb_reserve(struct lmb *lmb, phys_addr_t base, phys_size_t size)
{
    struct lmb_region *_rgn = &(lmb->reserved);

    return lmb_add_region(_rgn, base, size); // 分配一个 LMB
}

2.2.5.查找 RAMDisk 和设备树 - bootm_find_images()

  • boot_get_ramdisk() 查找 RAMDisk (IMX 未使用);

  • boot_get_fdt() 查找设备树,并设置设备树文件的起始地址和长度;

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
            char * const argv[], bootm_headers_t *images)
{
    ...
     // 查找 RAMDisk、设备树等其他模块
    if (bootm_find_images(flag, argc, argv))
        return 1;
-----------------------------------------------------------

// common/bootm.c
int bootm_find_images(int flag, int argc, char * const argv[])
{
    int ret;

    /* find ramdisk */
    ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
                   &images.rd_start, &images.rd_end);
    if (ret) {
        puts("Ramdisk image is corrupt or invalid\n");
        return 1;
    }

#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);
           |--> ... // 在镜像文件中查找设备树
           |--> *of_flat_tree = fdt_blob; // 将设备树的地址保存至 images.ft_addr
           |--> *of_size = fdt_totalsize(fdt_blob); // 将设备树的长度保存至 images.ft_len
    ...
    // 为设备树分配内存,并将设备树在内存中的起始地址,保存至环境变量 fdtaddr 中
    set_working_fdt_addr((ulong)images.ft_addr);
        |--> buf = map_sysmem(addr, 0);
        |--> setenv_hex("fdtaddr", addr);
#endif

...

    return 0;
}

2.3.禁用中断 - bootm_disable_interrupts()

内核加载过程中需要禁用中断,同时禁用以太网和 USB:

// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    ...
     // 内核启动过程中禁用中断
    bootm_disable_interrupts(); 
------------------------------------------------------------------------

// common/bootm.c
ulong bootm_disable_interrupts(void)
{
    ulong iflag;

    /*
     * We have reached the point of no return: we are going to
     * overwrite all exception vector code, so we cannot easily
     * recover from any failures any more...
     */
    iflag = disable_interrupts();
    
... // 禁用以太网和 USB

    return iflag;
}
------------------------------------------------------------------------

// arch/arm/lib/interrupts.c
int disable_interrupts (void)
{
    unsigned long old,temp;

    __asm__ __volatile__("mrs %0, cpsr\n"
                 "orr %1, %0, #0xc0\n"
                 "msr cpsr_c, %1"
                 : "=r" (old), "=r" (temp)
                 :
                 : "memory");

    return (old & 0x80) == 0;
}

2.4.准备运行环境并启动内核

分别调用 BOOTM_STATE_OS_PREPBOOTM_STATE_OS_FAKE_GOBOOTM_STATE_OS_GO 阶段对应的函数:

// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    ...
    // 标记需要启动的系统类型为 Linux
    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);

2.4.1.查找 OS 对应的启动函数 - bootm_os_get_boot_func()

不同操作系统所使用的启动函数不同,启动函数定义在 boot_os_fn 类型的结构体数组 boot_os 中,Linux 系统对应的启动函数为 do_bootm_linux():

// common/bootm_os.c
static boot_os_fn *boot_os[] = {
    [IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
    [IH_OS_LINUX] = do_bootm_linux, // Linux 系统对应的启动函数
#endif
#ifdef CONFIG_BOOTM_NETBSD
    [IH_OS_NETBSD] = do_bootm_netbsd,
#endif
...
#ifdef CONFIG_BOOTM_OPENRTOS
    [IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
};

do_bootm_states() 启动内核时,会调用 bootm_os_get_boot_func() 查找系统对应的启动函数:

// common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
            int states, bootm_headers_t *images, int boot_progress)
{
    ...
    /* From now on, we need the OS boot function */
    ...
    boot_fn = bootm_os_get_boot_func(images->os.os); // 查找系统对应的启动函数
                |--> return boot_os[os]; // boot_fn = do_bootm_linux()

    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);
    ...
    /* Call various other states that are not generally used */
    ...
    // 执行 do_bootm_linux()
    if (!ret && (states & BOOTM_STATE_OS_PREP))
        ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);

do_bootz() 启动 Linux 内核时,设置了 BOOTM_STATE_OS_PREP 标志,表示需要启动的系统类型为 Linux,因此会执行 do_bootm_linux()

2.4.2.Linux 启动函数 - do_bootm_linux()

检查启动阶段标志,并执行对应的系统环境准备函数 boot_prep_linux() 或运行函数 boot_jump_linux():

// arch/arm/lib/bootm.c
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;
    }

    // 运行 Linux 内核
    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;
}

2.4.3.Linux 运行环境准备 - boot_prep_linux()

处理环境变量 bootargs,其中保存了传递给 Linux 内核的启动参数:

// arch/arm/lib/bootm.c
static void boot_prep_linux(bootm_headers_t *images)
{
    // 从环境变量 bootargs 中读取 Linux 的启动命令
    char *commandline = getenv("bootargs");

    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) { // 检查是否使用设备树
        debug("using: FDT\n");
        if (image_setup_linux(images)) { // 准备启动环境
            printf("FDT creation failed! hanging...");
            hang();
        }
    } else if (BOOTM_ENABLE_TAGS) {
        ...
    } else {
        printf("FDT and ATAGS support not compiled in - hanging\n");
        hang();
    }
}

若配置使用设备树,则调用 image_setup_linux() 准备启动环境,包括调整设备树、分配存储 cmdline 的内存、处理 Ramdisk 以及设置启动参数:

  • 将 LMB 中的内存区域添加至设备树;

  • 在内核中为 cmdline 分配内存;

  • 重定位 RAMDisk;

  • 重定位设备树;

  • 设置设备树中内存相关的节点;

// common/image.c
int image_setup_linux(bootm_headers_t *images)
{
    ulong of_size = images->ft_len; // 获取设备树的长度
    char **of_flat_tree = &images->ft_addr; // 获取设备树在镜像文件中的起始地址
    ulong *initrd_start = &images->initrd_start; // RAMDisk 的起始地址
    ulong *initrd_end = &images->initrd_end; // RAMDisk 的结束地址
    struct lmb *lmb = &images->lmb; // LMB
    ulong rd_len;
    int ret;

    // 将 LMB 中预留的内存区域,添加到设备树的 /memory 节点和 /reserved-memory 节点,
    // 使内核可以知道需要预留这些区域
    if (IMAGE_ENABLE_OF_LIBFDT)
        boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);

    // 在内核中分配内存,用于存储 cmdline
    if (IMAGE_BOOT_GET_CMDLINE) {
        ret = boot_get_cmdline(lmb, &images->cmdline_start, &images->cmdline_end);
        if (ret) {
            puts("ERROR with allocation of cmdline\n");
            return ret;
        }
    }

    // 将 RAMDisk 重定位至高地址的内存中,避免和内核文件冲突
    if (IMAGE_ENABLE_RAMDISK_HIGH) {
        rd_len = images->rd_end - images->rd_start;
        ret = boot_ramdisk_high(lmb, images->rd_start, rd_len,
                initrd_start, initrd_end);
        if (ret)
            return ret;
    }

    // 如果当前设备树的位置不合适,例如,与内核加载区域冲突,
    // 则使用 LMB 分配新的内存区域并复制设备树
    if (IMAGE_ENABLE_OF_LIBFDT) {
        ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);
        if (ret)
            return ret;
    }

    // 设置设备树中的 bootargs 节点,更新 /memory 节点的内存范围,添加预留内存的相关描述
    if (IMAGE_ENABLE_OF_LIBFDT && of_size) {
        ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb);
        if (ret)
            return ret;
    }

    return 0;
}

2.4.4.跳转至 Linux 内核运行 - boot_jump_linux()

查找 Linux 内核的运行函数 kernel_entry (Linux 系统的第一个函数,类似于 _start) 并执行

内核运行前,会调用 announce_and_cleanup() 打印启动信息并清除之前的缓存 (I-Cache 和 D-Cache):

// arch/arm/lib/bootm.c
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64
    ...
#else
    unsigned long machid = gd->bd->bi_arch_number; // 开发板的硬件 ID (使用设备树时无效)
    char *s;
    void (*kernel_entry)(int zero, int arch, uint params);
    unsigned long r2;
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

    // 运行 Linux 内核的入口
    kernel_entry = (void (*)(int, int, uint))images->ep;

    ...
    // 输出调试信息,表示即将开始 Linux 内核的运行阶段
    debug("## Transferring control to Linux (at address %08lx)" \
        "...\n", (ulong) kernel_entry);

    // 记录当前的运行阶段
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    
    // 打印启动信息:starting kernel ... 并初始化缓存 (清理掉原来的缓存)
    announce_and_cleanup(fake);
        |--> cleanup_before_linux(); // 清除 I-Cache 和 D-Cache

    // 获取设备树的地址
    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
        r2 = (unsigned long)images->ft_addr;
    else
        r2 = gd->bd->bi_boot_params;

    ...
            // 运行内核
            kernel_entry(0, machid, r2);
    ...
}

因为后续将要运行 Linux 内核,之前 uboot 所使用的数据缓存与指令缓存不再需要,调用 announce_and_cleanup() 将其清除,最后执行 kernel_entry() 运行 Linux 内核,其第二个参数 r2 为设备树的地址或者启动命令 bootargs

kernel_entry() 由操作系统定义,是其运行的第一个函数,类似 uboot 的 _start() 函数

3.启动过程概览

综上,uboot 启动 Linux 内核的过程大致如下图所示: