程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Mini2440之uboot移植流程之linux内核启动分析(六)

在前面的章节关于u-boot的源码,以及u-boot的移植这一块我们介绍完了。接下来,我们应该开始进入第二个阶段,linux内核移植,以及驱动开发。

但是在这之前,我们遗漏了u-boot中的一个重要环节没有介绍,就是u-boot如何执行bootm命令,如何实现linux内核启动。

我们在《Mini440之uboot移植之源码分析命令解析(五) 》介绍过如果配置了CONFIG_BOOTCOMMAND宏:

#define CONFIG_BOOTCOMMAND "nand read 0x30000000 kernel; bootm 0x30000000" //bootcmd  

那么在执行autoboot_command函数的时候,将会执行bootcmd中保存的命令。

  • nand read 0x30000000 kernel:这里将Nand Flash kernel分区的内核镜像加载到地址0x30000000
  • bootm 0x30000000:启动linux内核;bootm这个命令用于启动一个内核镜像,这个镜像就是uImage文件,它会从uImage镜像文件的头部取得一些信息,这些信息包括:CPU架构、操作系统类型、文件类型、压缩方式、加载地址、运行的入口地址等;

一、内核镜像

前面我们说了u-boot启动linux内核,从Nand Flash内核分区中加载uImage镜像,那么什么是uImage镜像呢?说到这个我们就不得不聊一聊内核镜像的几种文件格式了。

1.1 介绍

linux内核编译之后一般会生成一下两个文件,一个是Image,一个是zImage,其中:

  • Image为内核镜像文件(可以直接在芯片上运行原生二进制文件);
  • zImage为内核的镜像压缩文件(但它不仅爱是一个压缩文件,在文件的开头部分内嵌有gzip解压缩代码)。

但是这两种镜像的格式并没有办法提供给u-boot的足够的信息来进行loadjump或者验证操作等等。因此,u-boot提供了mkimage工具,来将zImage制作为u-boot可以识别的格式,将生成的文件称之uImage

需要注意的是:这里并不是说u-boot一定不支持zImage镜像文件的启动,一般可以通过配置CONFIG_ZIMAGE_BOOT使u-boot支持zImage启动。

1.1.1 Legacy uImage

Legacy uImage它是在zImage之前加上了一个长度为64字节的头,说明这个内核的CPU架构、操作系统类型、文件类型、压缩方式、加载地址、运行的入口地址等。

1.1.2 FIT uImage

FIT uImage是在Legacy uImage的基础上,为了满足Linux Flattened Device Tree(FDT)的标准,而重新改进和定义出来的一种镜像文件格式;它一般将kernelfdtramdisk等等镜像打包到一个itb镜像文件中;u-boot只要获得了这个镜像文件,就可以得到kernelfdtramdisk等等镜像的具体信息和内容。

1.1.3 ImagezImageuImage发展历程

为什么会出现Legacy uImage ,然后又出现FIT uImage?

最开始出现的是Image,就一个普通的内核镜像。然后为了节省空间,有了zImage,进行了压缩可以节省空间。

u-boot启动一个Image或者zImage,还必须要给它传递一些参数;

  • 镜像文件的类型,如kernel imagedtb文件、ramdisk image等等?
  • 镜像文件需要放在内存的的哪个位置(加载地址)?
  • 镜像文件需要从内存哪个位置开始执行(入口地址)?
  • 镜像文件是否有压缩?
  • 镜像文件是否有一些完整性校验的信息(如CRC)?

这种方式的不足就在于,镜像本身没有带有这些参数的,用工具制作完镜像后,还需要另外再向u-boot提供这些参数,才能正常启动(就是比较麻烦)。

如果可以把这些参数,在制作镜像的时候就一起弄到镜像里面,然后u-boot一读取镜像,就马上可以知道这些参数了。不需要在制作好镜像之后再另外告诉u-boot这些参数。这种带有以上参数的镜像格式就是Legacy uImage

最后一个FIT uImage ,其主要目的是为了支持基于device treeunify kernel

1.2 Legacy uImage

1.2.1 配置

使能需要打开的宏:

CONFIG_IMAGE_FORMAT_LEGACY=y

注意,这个宏在自动生成的autoconf.mk中会自动配置,不需要额外配置。

1.2.2 制作

编译完u-boot之后,使用u-boot目录下tools/mkimage工具来制作uImage。命令如下:

mkimage -A arm -O linux -C none -T kernel -a 0x20008000 -e 0x20008040 -n Linux_Image -d zImage uImage 
 
各个参数意义如下
Usage: mkimage -l image
          -l ==> list image header information
       mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
          -A ==> set architecture to 'arch'  // 架构
          -O ==> set operating system to 'os' // 操作系统
          -T ==> set image type to 'type' // 镜像类型
          -C ==> set compression type 'comp' // 压缩类型
          -a ==> set load address to 'addr' (hex) // 加载地址
          -e ==> set entry point to 'ep' (hex) // 入口地址
          -n ==> set image name to 'name' // 镜像名称,注意不能超过32B
          -d ==> use image data from 'datafile' // 输入文件
          -x ==> set XIP (execute in place) 
1.2.3 启动内核

将生成的Legacy uImage下载到内存中,使用bootm <download addr>命令启动kernel

但是注意,如果使用Legacy uImage后面还需要根据实际情况决定是否需要传入ramdisk以及dtb的在内存中的地址,否则可能会导致bootm失败。

格式如下:

bootm    # 后面没有地址则使用CONFIG_SYS_LOAD_ADDR地址作为启动地址
bootm <Legacy uImage addr> <ramdisk addr> <dtb addr>        

1.3 FIT uImage

1.3.1 FIT介绍

FITflattened image tree的简称,它采用了device tree source filse(DTS)的语法,生成的image文件也和dtb文件类似(称做itb)。

其通过一定语法和格式将一些需要使用到的镜像(例如kerneldtb以及文件系统)组合到一起生成一个image file。其生成步骤如下图所示:

其中image source file(.its)device tree source file(.dts)类似,负责描述要生成的image file的信息。mkimagedtc工具,可以将.its文件以及对应的image data file,打包成一个image file

这里我们有必要对涉及到的这几类文件进行一个总结:

  • its文件 :image source file,类似于dts文件,负责描述要声称的image的的信息。需要自行进行构造;
  • itb文件 :最终得到的image文件,类似于dtb文件,也就是u-boot可以直接对其进行识别和解析的FIT uImage
  • mkimagemkimage则负责dtc的角色,用于通过解析its文件、获取对应的镜像,最终生成一个u-boot可以直接进行识别和解析的itb文件;
  • image data file :实际使用到的镜像文件;

mkimageits文件以及对应的image data file,打包成一个itb文件,也就是u-boot可以识别的image file(FIT uImage)。我们将这个文件下载到么内存中,使用bootm命令就可以执行了。

1.3.2 配置

使能需要打开的宏:

CONFIG_FIT=y
1.3.3 创建its文件

因为mkimage是根据its文件中的描述来打包镜像生成itb文件(FIT uImage),所以首先需要制作一个its文件,在its文件中描述需要被打包的镜像,主要是kernel镜像,dtb文件,ramdisk镜像。

关于its文件的语法,可以参考这篇博客《FIT介绍》。简单的例子如下:

展开查看详情
/*
 * U-Boot uImage source file for "X project"
 */
/dts-v1/;
 
/ {
    description = "U-Boot uImage source file for X project";
    #address-cells = <1>;
 
    images {
        kernel@tiny210 {
            description = "Unify(TODO) Linux kernel for project-x";
            data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/linux/arch/arm/boot/zImage");
            type = "kernel";
            arch = "arm";
            os = "linux";
            compression = "none";
            load = <0x20008000>;
            entry = <0x20008000>;
        };
        fdt@tiny210 {
            description = "Flattened Device Tree blob for project-x";
            data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/linux/arch/arm/boot/dts/s5pv210-tiny210.dtb");
            type = "flat_dt";
            arch = "arm";
            compression = "none";
        };
        ramdisk@tiny210 {
            description = "Ramdisk for project-x";
            data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/rootfs/initramfs.gz");
            type = "ramdisk";
            arch = "arm";
            os = "linux";
            compression = "gzip";
        };
    };
 
    configurations {
        default = "conf@tiny210";
        conf@tiny210 {
            description = "Boot Linux kernel with FDT blob";
            kernel = "kernel@tiny210";
            fdt = "fdt@tiny210";
            ramdisk = "ramdisk@tiny210";
        };
    };
};

注意,可以有多个kernel节点或者fdt节点等等,兼容性更强。同时,可以有多种configurations,来对kernelfdtramdisk来进行组合,使FIT-uImage可以兼容于多种板子,而无需重新进行编译烧写。

1.3.4 生成FIT uImage

生成的命令相对Legacy uImage较为简单,因为信息都在its文件里面描述了:

${UBOOT_OUT_DIR}/tools/mkimage -f ${UIMAGE_ITS_FILE} ${UIMAGE_ITB_FILE}

其中-f指定需要编译的source文件,并在后面指定需要生成的image文件(一般以.itb为后缀,例如u-boot.itb)。

1.3.5 启动内核

将生成的FIT uImage下载到内存某个地址,使用如下命令启动;

bootm                                   # 后面没有地址则使用CONFIG_SYS_LOAD_ADDR地址作为启动地址
bootm <FIT uImage addr>     
bootm <FIT uImage addr>:<image name>     # 单独加载FIT中<image name>镜像。
bootm <FIT uImage addr>#<config name>    # 使用载FIT中指定<config name>启动

u-boot会自动解析出FIT uImage中,kernelramdiskdtb的信息,使用起来相当方便。更多可以参考《U-Boot FDT Overlay FIT usage》。

1.4 其他

更多关于内核镜像的介绍以及制作的内容参考:《Rockchip RK3399 - 移植linux 5.2.8`》。

此外有关bootm更多信息:

Usage:
bootm [addr [arg ...]]
    - boot application image stored in memory
        passing arguments 'arg ...'; when booting a Linux kernel,
        'arg' can be the address of an initrd image
        When booting a Linux kernel which requires a flat device-tree
        a third argument is required which is the address of the
        device-tree blob. To boot that kernel without an initrd image,
        use a '-' for the second argument. If you do not pass a third
        a bd_info struct will be passed instead

For the new multi component uImage format (FIT) addresses
        must be extended to include component or configuration unit name:
        addr:<subimg_uname> - direct component image specification
        addr#<conf_uname>   - configuration specification
        Use iminfo command to get the list of existing component
        images and configurations.

Sub-commands to do part of the bootm sequence.  The sub-commands must be
issued in the order below (it's ok to not issue all sub-commands):
        start [addr [arg ...]]
        loados  - load OS image
        ramdisk - relocate initrd, set env initrd_start/initrd_end
        fdt     - relocate flat device tree
        cmdline - OS specific command line processing/setup
        bdt     - OS specific bd_t processing
        prep    - OS specific prep before relocation or go
        go      - start OS

二、linux内核启动

2.1 autoboot_command

autoboot_command函数定义在common/autoboot.c文件中:

void autoboot_command(const char *s)
{
    debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

    if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
        run_command_list(s, -1, 0);
    }
}

如果在u-boot启动倒计时结束之前,没有按下任何键,将会执行那么将执行run_command_list,此函数会执行参数s指定的一系列命令,也就是bootcmd中配置中的命令,bootcmd中保存着默认的启动命令。

在默认环境变量default_environment中定义有:

#ifdef    CONFIG_BOOTCOMMAND
    "bootcmd="    CONFIG_BOOTCOMMAND        "\0"
#endif

2.2 do_bootm

由于要执行bootm命令,所以我们需要打开与bootm命令相关的文件进行分析,bootm命令定义在cmc/bootm.c文件中:

U_BOOT_CMD(
    bootm,    CONFIG_SYS_MAXARGS,    1,    do_bootm,
    "boot application image from memory", bootm_help_text
);

找到对应的do_bootm函数,去除无用的代码:

展开查看详情
/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    /* 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);
    }
  
    // 到这里参数中的bootm参数会被去掉
    return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
        BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
        BOOTM_STATE_LOADOS |
        BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
        BOOTM_STATE_OS_GO, &images, 1);
}

当执行bootm 0x30000000,函数入参:

  • 第一个参数是bootm命令结构体;
  • flag是命令标志位;
  • argv[0]='bootm"argv[1]="0x30000000"
  • argc=2

这里进入函数之后argc=1argv[0]=0x30000000

bootm的核心是do_bootm_states,以全局变量bootm_headers_t images作为do_bootm_states的参数,该变量在cmd/bootm.c文件中声明。

bootm_headers_t images;        /* pointers to os/initrd/fdt images */

bootm会根据参数以及参数指向的镜像来填充这个结构体里面的成员。 最终再使用这个结构体里面的信息来填充kernel启动信息并且到跳转到kernel中,下面详细说明这个函数。

三、do_bootm_states函数

3.1 函数声明

我们先来看一下这个函数的声明:

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
            int states, bootm_headers_t *images, int boot_progress);
3.2.1 bootm_headers_t

参数bootm_headers_t *images是一个复杂的数据结构,bootm_headers_t定义在include/image.h文件中:

展开查看详情
/*
 * Legacy and FIT format headers used by do_bootm() and do_bootm_<os>()
 * routines.
 */
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 IMAGE_ENABLE_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;

bootm_headers_t用于LegacyFIT方式镜像的启动,其中包括了os/initrd/fdt images的信息。我们这里大概介绍一下和这个结构体的成员变量:

Legacy uImage相关:

  • legacy_hdr_osLegacy uImage的镜像头;对应就是Legacy uImage加载到内存的起始地址;
  • legacy_hdr_os_copyLegacy uImage的镜像头备份;
  • legacy_hdr_valid:用于表示Legacy uImage的头部指针是否可用,也就是这是否是一个Legacy uImage

FIT uImage相关:

  • fit_uname_cfg:配置节点名;
  • fit_hdr_osFIT uImagekernel镜像头;对应就是FIT uImage加载到内存的的起始地址;
  • fit_uname_osFIT uImagekernel的节点名称;比如我们之前案例中的kernel@tiny210
  • fit_noffset_osFIT uImagekernel的节点偏移,直接代表了kernel节点;也就是说通过fit_hdr_osfit_noffset_os就可以获取到itb文件中描述的kernel节点的信息;
  • fit_hdr_rdFIT uImageramdisk的镜像头;
  • fit_uname_rdFIT uImageramdisk的节点名;
  • fit_noffset_rdFIT uImageramdisk的节点偏移;
  • fit_hdr_fdtFIT uImageFDT的镜像头;
  • fit_uname_fdtFIT uImageFDT的节点名;
  • fit_noffset_fdtFIT uImageFDT的节点偏移;

其它:

  • os:操作系统信息的结构体,包含os.loados.typeos.os等字段信息;
  • ep:操作系统的入口地址;
  • rd_startramdisk在内存上的起始地址;
  • rd_endramdisk在内存上的结束地址
  • ft_addrfdt在内存上的地址;
  • ft_lenfdt在内存上的长度;
  • verify:是否需要验证;
  • state:状态标识,用于标识对应的bootm需要做什么操作;

其中legacy_hdr_osimage_header_t类型。这个结构尤为重要,下面来介绍。

3.2.2 image_header_t

u-boot使用image_header_t来表示Legacy uImage的头部信息,定义在include/image.h文件:

/*
 * Legacy format image header,
 * all data in network byte order (aka natural aka bigendian).
 */
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;

比较重要的成员有:

  • ih_magic:镜像的魔数,用来校验是不是一个Legacy uImage;为0x27051956则表示是一个Legacy uImage

  • ih_size:数据镜像长度,和zImage长度一样,不包含头部信息;

  • ih_load:镜像的加载地址;和mkimage使用的参数一致;zImage解压之后得到Image会加载到这个地址上;

  • ih_ep:镜像的入口地址;和mkimage使用的参数一致;内核启动的入口地址;

  • ih_os:镜像的系统;

  • ih_type:镜像的类型;

3.2.3 状态说明

do_bootm_states的state参数是一大堆的标志宏,这些标志宏就是u-boot启动时需要的阶段,每个阶段都有一个宏来表示。

#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)

其中:

  • BOOTM_STATE_START :开始执行bootm的一些准备动作;
  • BOOTM_STATE_FINDOS :查找内核镜像,需要注意的是内核镜像有多种,比如ImagezImageuImage等,像Image内核镜像加载到内存,就可以直接运行,而像zImage这种是压缩之后的内核镜像文件,它在头部包含了解压缩代码,而uImagezImage之前又加了0x40的头信息,而u-boot引导的内核镜像文件一般为zImagebootz命令)和uImagebootm命令);
  • BOOTM_STATE_FINDOTHER :查找内核镜像外的其它镜像,比如FDTramdisk等;
  • BOOTM_STATE_LOADOS :查找到内核镜像后,解析加载的内核镜像的头信息,根据内核镜像的格式是zImageuImage还是设备树,有不同的处理逻辑,比uImage需要解压,最终会将vmlinux加载到images.os.load指向的内存空间;
  • BOOTM_STATE_RAMDISK:操作ramdisk
  • BOOTM_STATE_FDT :操作FDT
  • BOOTM_STATE_OS_CMDLINE :操作commandline
  • BOOTM_STATE_OS_BD_T :跳转到内核前的准备动作;
  • BOOTM_STATE_OS_PREP :执行跳转前的准备动作 ;
  • BOOTM_STATE_OS_FAKE_GO :伪跳转,一般都能直接跳转到kernel中去
  • BOOTM_STATE_OS_GO :设置启动参数,跳转到kernel所在的地址上 ;

上面划掉的可以先忽略掉。do_bootm_states根据states来判断要执行的操作。在这些流程中,起传递作用的是bootm_headers_t images这个数据结构,有些流程是解析内核镜像,往这个结构体里写数据。 而跳转的时候,则需要使用到这个结构体里面的数据。

3.3 do_bootm_states函数

do_bootm_states函数定义在cmd/bootm.c文件:

展开查看详情
/**
 * Execute selected states of the bootm command.
 *
 * Note the arguments to this state must be the first argument, Any 'bootm'
 * or sub-command arguments must have already been taken.
 *
 * Note that if states contains more than one flag it MUST contain
 * BOOTM_STATE_START, since this handles and consumes the command line args.
 *
 * Also note that aside from boot_os_fn functions and bootm_load_os no other
 * functions we store the return value of in 'ret' may use a negative return
 * value, without special handling.
 *
 * @param cmdtp        Pointer to bootm command table entry
 * @param flag        Command flags (CMD_FLAG_...)
 * @param argc        Number of subcommand arguments (0 = no arguments)
 * @param argv        Arguments
 * @param states    Mask containing states to run (BOOTM_STATE_...)
 * @param images    Image header information
 * @param boot_progress 1 to show boot progress, 0 to not do this
 * @return 0 if ok, something else on error. Some errors will cause this
 *    function to perform a reboot! If states contains BOOTM_STATE_OS_GO
 *    then the intent is to boot an OS, so this function will not return
 *    unless the image type is standalone.
 */
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_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);
        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));
        else if (ret && ret != BOOTM_ERR_OVERLAP)
            goto err;
        else if (ret == BOOTM_ERR_OVERLAP)
            ret = 0;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
        if (images->os.os == IH_OS_LINUX)
            fixup_silent_linux();
#endif
    }

    /* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH               // 不执行
    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) {
            setenv_hex("initrd_start", images->initrd_start);
            setenv_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 = 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 = getenv("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;
}

代码具体执行流程:

  • 初始化images->state |= states

  • states跟宏BOOTM_STATE_START进行与操作,通过执行bootm_start

  • states跟宏BOOTM_STATE_FINDOS进行与操作,通过执行bootm_find_os

  • states跟宏BOOTM_STATE_FINDOTHER进行与操作,通过执行bootm_find_other

  • states跟宏BOOTM_STATE_LOADOS进行与操作,通过关闭中断,执行bootm_load_os

  • states跟宏BOOTM_STATE_OS_PREP进行与操作,通过执行boot_fn

  • states跟宏BOOTM_STATE_OS_GO进行与操作,通过执行boot_selected_os

boot_selected_os,这函数里面就执行do_bootm_linux跳转到我们的内核去运行了,如无意外,到了这里一般情况下就不返回了。

我们根据状态参数,绘制出这个函数的执行流程:

3.3.1 bootm_start

boot_start函数定义在common/bootm.c文件:

static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,
               char * const argv[])
{
    memset((void *)&images, 0, sizeof(images));
    images.verify = getenv_yesno("verify");

    boot_start_lmb(&images);

    bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
    images.state = BOOTM_STATE_START;

    return 0;
}

代码具体执行流程:

  • 清空images结构体;
  • 获取环境遍历verify,并赋值给images.verify
  • 执行boot_start_lmb()初始化images.lmb
  • 执行bootstage_mark_name,记录启动阶段的名字;
  • 设置images.state = BOOTM_STATE_START
3.3.2 bootm_find_os

bootm_find_os函数用于获取操作系统相关的一些信息,例如类型、压缩方式,加载地址、入口地址等。

定义在common/bootm.c

展开查看详情
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
             char * const argv[])
{
    const void *os_hdr;
    bool ep_found = false;
    int ret;

    /* get kernel image header, start address and length */
    os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,      // 1. 获取到Legacy uImage的头部,并将其指针返回给os_hdr
            &images, &images.os.image_start, &images.os.image_len);
    if (images.os.image_len == 0) {
        puts("ERROR: can't get kernel image!\n");
        return 1;
    }

    /* get image parameters */
    switch (genimg_get_format(os_hdr)) {        // 2. 根据不同的内核镜像格式,如Android、Legacy uImage、FIT uImage,获取操作系统的信息
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
    case IMAGE_FORMAT_LEGACY:                   // Legacy uImage
        images.os.type = image_get_type(os_hdr);  // 从头部image_header中获得镜像类型并存储到images.os.type中
        images.os.comp = image_get_comp(os_hdr);  // 从头部image_header中获得压缩类型类型并存储到images.os.comp中
        images.os.os = image_get_os(os_hdr);      // 从头部image_header中获得操作系统类型并存储到images.os.os中

        images.os.end = image_get_image_end(os_hdr);  // 当前镜像的尾地址
        images.os.load = image_get_load(os_hdr);      // 从头部image_header中获得加载地址并存储到images.os.load中
        images.os.arch = image_get_arch(os_hdr);      // 从头部image_header中获得cpu体系结构类型并存储到images.os.arch中
        break;
#endif
#if IMAGE_ENABLE_FIT
    case IMAGE_FORMAT_FIT:                      // FIT uImage 
        if (fit_image_get_type(images.fit_hdr_os,  // 获取itb文件中kernel节点中的"type"属性
                       images.fit_noffset_os,
                       &images.os.type)) {
            puts("Can't get image type!\n");
            bootstage_error(BOOTSTAGE_ID_FIT_TYPE);
            return 1;
        }

        if (fit_image_get_comp(images.fit_hdr_os,  // 获取itb文件中kernel节点中的"comp"属性
                       images.fit_noffset_os,
                       &images.os.comp)) {
            puts("Can't get image compression!\n");
            bootstage_error(BOOTSTAGE_ID_FIT_COMPRESSION);
            return 1;
        }

        if (fit_image_get_os(images.fit_hdr_os, images.fit_noffset_os,  // 获取itb文件中kernel节点中的"os"属性
                     &images.os.os)) {
            puts("Can't get image OS!\n");
            bootstage_error(BOOTSTAGE_ID_FIT_OS);
            return 1;
        }

        if (fit_image_get_arch(images.fit_hdr_os,   // 获取itb文件中kernel节点中的"arch"属性
                       images.fit_noffset_os,
                       &images.os.arch)) {
            puts("Can't get image ARCH!\n");
            return 1;
        }

        images.os.end = fit_get_end(images.fit_hdr_os);

        if (fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os,  // 获取itb文件中kernel节点中的"load"属性
                       &images.os.load)) {
            puts("Can't get image load address!\n");
            bootstage_error(BOOTSTAGE_ID_FIT_LOADADDR);
            return 1;
        }
        break;
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE
    case IMAGE_FORMAT_ANDROID:        // Android
        images.os.type = IH_TYPE_KERNEL;
        images.os.comp = IH_COMP_NONE;
        images.os.os = IH_OS_LINUX;

        images.os.end = android_image_get_end(os_hdr);
        images.os.load = android_image_get_kload(os_hdr);
        images.ep = images.os.load;
        ep_found = true;
        break;
#endif
    default:
        puts("ERROR: unknown image format type!\n");
        return 1;
    }

    /* If we have a valid setup.bin, we will use that for entry (x86) */
    if (images.os.arch == IH_ARCH_I386 ||
        images.os.arch == IH_ARCH_X86_64) {
        ulong len;

        ret = boot_get_setup(&images, IH_ARCH_I386, &images.ep, &len);
        if (ret < 0 && ret != -ENOENT) {
            puts("Could not find a valid setup.bin for x86\n");
            return 1;
        }
        /* Kernel entry point is the setup.bin */
    } else if (images.legacy_hdr_valid) {    // Legacy uImage类型
        images.ep = image_get_ep(&images.legacy_hdr_os_copy);   // 从头部image_header中获得镜像的入口地址并存储到images.ep中
#if IMAGE_ENABLE_FIT
    } else if (images.fit_uname_os) {   // FIT uImage类型
        int ret;

        ret = fit_image_get_entry(images.fit_hdr_os,   // 获取itb文件中kernel节点中的"entry"属性
                      images.fit_noffset_os, &images.ep);
        if (ret) {
            puts("Can't get entry point property!\n");
            return 1;
        }
#endif
    } else if (!ep_found) {
        puts("Could not find kernel entry point!\n");
        return 1;
    }

    if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
        images.os.load = images.os.image_start;
        images.ep += images.os.load;
    }

    images.os.start = map_to_sysmem(os_hdr);

    return 0;
}

假设我们使用的内核镜像格式为Legacy uImage,那么代码具体执行流程如下。

3.3.2.1 boot_get_kernel

调用boot_get_kernel函数获取内核镜像在内存地址和大小:

展开查看详情
/**
 * boot_get_kernel - find kernel image
 * @os_data: pointer to a ulong variable, will hold os data start address
 * @os_len: pointer to a ulong variable, will hold os data length
 *
 * boot_get_kernel() tries to find a kernel image, verifies its integrity
 * and locates kernel data.
 *
 * returns:
 *     pointer to image header if valid image was found, plus kernel start
 *     address and length, otherwise NULL
 */
static const void *boot_get_kernel(cmd_tbl_t *cmdtp, int flag, int argc,
                                   char * const argv[], bootm_headers_t *images,
                                   ulong *os_data, ulong *os_len)
{
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
        image_header_t  *hdr;
#endif
        ulong           img_addr;
        const void *buf;
        const char      *fit_uname_config = NULL;
        const char      *fit_uname_kernel = NULL;
#if IMAGE_ENABLE_FIT
        int             os_noffset;
#endif
		// common/image.c, get the real kernel address and return 2 FIT strings
        img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
                                              &fit_uname_config,
                                              &fit_uname_kernel);

        bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);

        /* check image type, for FIT images get FIT kernel node */
        *os_data = *os_len = 0;
        buf = map_sysmem(img_addr, 0);
        switch (genimg_get_format(buf)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
        case IMAGE_FORMAT_LEGACY:
                printf("## Booting kernel from Legacy Image at %08lx ...\n",
                       img_addr);
                hdr = image_get_kernel(img_addr, images->verify);
                if (!hdr)
                        return NULL;
                bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);

                /* get os_data and os_len */
                switch (image_get_type(hdr)) {
                case IH_TYPE_KERNEL:
                case IH_TYPE_KERNEL_NOLOAD:
                        *os_data = image_get_data(hdr);
                        *os_len = image_get_data_size(hdr);
                        break;
                case IH_TYPE_MULTI:
                        image_multi_getimg(hdr, 0, os_data, os_len);
                        break;
                case IH_TYPE_STANDALONE:
                        *os_data = image_get_data(hdr);
                        *os_len = image_get_data_size(hdr);
                        break;
                default:
                        if (cmdtp)
                                printf("Wrong Image Type for %s command\n",
                                       cmdtp->name);
                        bootstage_error(BOOTSTAGE_ID_CHECK_IMAGETYPE);
                        return NULL;
                }

                /*
                 * copy image header to allow for image overwrites during
                 * kernel decompression.
                 */
                memmove(&images->legacy_hdr_os_copy, hdr,
                        sizeof(image_header_t));

                /* save pointer to image header */
                images->legacy_hdr_os = hdr;

                images->legacy_hdr_valid = 1;
                bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);
                break;
#endif
#if IMAGE_ENABLE_FIT
        case IMAGE_FORMAT_FIT:
                os_noffset = fit_image_load(images, img_addr,
                                &fit_uname_kernel, &fit_uname_config,
                                IH_ARCH_DEFAULT, IH_TYPE_KERNEL,
                                BOOTSTAGE_ID_FIT_KERNEL_START,
                                FIT_LOAD_IGNORED, os_data, os_len);
                if (os_noffset < 0)
                        return NULL;

                images->fit_hdr_os = map_sysmem(img_addr, 0);
                images->fit_uname_os = fit_uname_kernel;
                images->fit_uname_cfg = fit_uname_config;
                images->fit_noffset_os = os_noffset;
                break;
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE
        ......
#endif
        default:
                if (cmdtp)
                        printf("Wrong Image Format for %s command\n",
                               cmdtp->name);
                bootstage_error(BOOTSTAGE_ID_FIT_KERNEL_INFO);
                return NULL;
        }

        debug("   kernel data at 0x%08lx, len = 0x%08lx (%ld)\n",
              *os_data, *os_len, *os_len);

        return buf;
}

执行流程如下:

(1) 调用genimg_get_kernel_addr_fit获取内核镜像加载到内存的起始地址;

对于Legacy uImage就是uImage加载到内存的起始地址,也就是我们启动命令传入的0x30000000参数;

bootm 0x30000000

对于FIT uImage,解析启动命令,比如:

bootm <FIT uImage addr>#<config name>

调用fit_parse_conf 函数用于解析FIT配置字符串;

  • <addr>fit blob(不包含image)加载到内存的地址,并将其返回,即设置成img_addr的值;
  • <conf> 是启动内核抵用放入配置名称,并将其设置为fit_uname_config的值;

如果启动命令是bootm <FIT uImage addr>那么只会初始化img_addr的值,即fit blob(不包含image)加载到内存的地址;fit_uname_configfit_uname_kernel均为初始化;

(2) 调用genimg_get_format获取内核镜像格式:除了我们前面介绍的Legacy uImageFIT uImage、还有安卓等格式;

(3) 然后初始化images.os.image_start

  • 对于Legacy uImage,其值为内核镜像加载到内存的起始地址 + 镜像头64字节,即zImage加载到内存的起始地址;也就是0x30000040

  • 对于FIT uImage,其值为itb文件中kernel节点data属性指向的内核镜像文件在内存中的地址,一般也是zImage加载到内存的起始地址;

(5 )初始化images.os.image_len(即参数os_data ),内核镜像数据长度,即zImage的大小;

(6) 初始化images.legacy_hdr_os(即参数os_len ):指向0x30000000地址;

(7) 将images.legacy_hdr_os指向的前64个字节拷贝到images.legacy_hdr_os_copy;即位uImage的头部做了一个备份;

(8) 设置images.legacy_hdr_valid = 1; 说明uImage的头部指针指向的头部是可用的,这是一个Legacy uImage类型

(9) 如果是FIT uImage,还会初始化images中部分与fit相关的字段;

// 从fit_uname_config参数所指定的配置中加载kernel镜像, common/image-fit.c
// 这里是首先从FTI中获取configurations节点下名称为fit_uname_config(默认为conf)的子节点的偏移,
// 然后获取conf节点IH_TYPE_KERNEL(kernel)属性的值,一般为kernel = "kernel"
// 从FIT中获取images节点下名称为kernel的子节点的偏移,赋值为noffset变量
// ....
// 设置fit_uname_kernel为"kernel",即kernel = "kernel"中的值
// 返回noffset
os_noffset = fit_image_load(images, img_addr,
				&fit_uname_kernel, &fit_uname_config,
				IH_ARCH_DEFAULT, IH_TYPE_KERNEL,
				BOOTSTAGE_ID_FIT_KERNEL_START,
				FIT_LOAD_IGNORED, os_data, os_len);
if (os_noffset < 0)
		return NULL;

images->fit_hdr_os = map_sysmem(img_addr, 0);
images->fit_uname_os = fit_uname_kernel;
images->fit_uname_cfg = fit_uname_config;
images->fit_noffset_os = os_noffset;
3.3.2.2 genimg_get_format

根据内核镜像文件的类型,去获取到内核信息,并初始化images.os的各个成员。

对于Legacy uImage,初始化:

  • 内核镜像类型images.os.type;即images.legacy_hdr_os->ih_type
  • 内核压缩方式images.os.comp; 即images.legacy_hdr_os->ih_comp
  • 内核操作系统类型images.os.os;即images.legacy_hdr_os->ih_os
  • 内核镜像在内存的结束地址image.os.end
  • 内核要装载到内存的地址images.os.load;即images.legacy_hdr_os->ih_load
  • 内核的CPU体系架构images.os.arch;即images.legacy_hdr_os->ih_arch

对于FIT uImage,调用如下函数进行初始化:

  • 内核镜像类型images.os.type; 获取itb文件中kernel节点中的type属性;
  • 内核压缩方式images.os.comp; 获取itb文件中kernel节点中的comp属性;
  • 内核操作系统类型images.os.os;获取itb文件中kernel节点中的os属性;
  • 内核的CPU体系架构images.os.arch;获取itb文件中kernel节点中的arch属性;
  • 内核要装载到内存的地址images.os.load;获取itb文件中kernel节点中的load属性;
3.3.2.3 入口地址

最后从头部image_header中获得镜像入口地址并存储到images.ep,即images.legacy_hdr_os->ih_ep;其实就是内核的启动地址了;

这里要说明一下,现在Legacy uImage内核镜像文件所在的内存地址是0x30000000,而内核启动的内存地址是在images.ep成员所指向的地址;

3.3.3 bootm_find_other

查找内核镜像外的其它镜像,比如dtbramdisk等。

static int bootm_find_other(cmd_tbl_t *cmdtp, int flag, int argc,
                char * const argv[])
{
    if (((images.os.type == IH_TYPE_KERNEL) ||
         (images.os.type == IH_TYPE_KERNEL_NOLOAD) ||
         (images.os.type == IH_TYPE_MULTI)) &&
        (images.os.os == IH_OS_LINUX ||
         images.os.os == IH_OS_VXWORKS))
        return bootm_find_images(flag, argc, argv);

    return 0;
}

函数调用链如下:

->bootm_find_other     
	->bootm_find_images
		->boot_get_ramdisk        --获取images.rd_start和images.rd_end。
       ->fit_image_load       --从FIT镜像中获取ramdisk文件
     ->boot_get_fdt            --获取images.ft_addr和images.ft_len。
       ->boot_get_fdt_fit     --从FIT中获取dtb文件
          ->fit_image_load  --根据fdt镜像名获取dtb文件
3.3.4 bootm_load_os
static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
             int boot_progress)
{
    image_info_t os = images->os;
    ulong load = os.load;
    ulong blob_start = os.start;
    ulong blob_end = os.end;
    ulong image_start = os.image_start;
    ulong image_len = os.image_len;
    bool no_overlap;
    void *load_buf, *image_buf;
    int err;

    load_buf = map_sysmem(load, 0);                      // 获取zImage解压之后,得到的Image的存放地址
    image_buf = map_sysmem(os.image_start, image_len);   // 获取zImage镜像在内存中的地址
    err = bootm_decomp_image(os.comp, load, os.image_start, os.type,    // 重点,解压缩,重定位
                 load_buf, image_buf, image_len,
                 CONFIG_SYS_BOOTM_LEN, load_end);
    if (err) {
        bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);
        return err;
    }
    flush_cache(load, *load_end - load);

    debug("   kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);
    bootstage_mark(BOOTSTAGE_ID_KERNEL_LOADED);

    no_overlap = (os.comp == IH_COMP_NONE && load == image_start);

    if (!no_overlap && (load < blob_end) && (*load_end > blob_start)) {
        debug("images.os.start = 0x%lX, images.os.end = 0x%lx\n",
              blob_start, blob_end);
        debug("images.os.load = 0x%lx, load_end = 0x%lx\n", load,
              *load_end);

        /* Check what type of image this is. */
        if (images->legacy_hdr_valid) {
            if (image_get_type(&images->legacy_hdr_os_copy)
                    == IH_TYPE_MULTI)
                puts("WARNING: legacy format multi component image overwritten\n");
            return BOOTM_ERR_OVERLAP;
        } else {
            puts("ERROR: new format image overwritten - must RESET the board to recover\n");
            bootstage_error(BOOTSTAGE_ID_OVERWRITTEN);
            return BOOTM_ERR_RESET;
        }
    }

    return 0;
}

首先通过images.os.image_start获取zImage在内存的地址,然后通过images.os.load获取zImage解压之后Image的存放地址;

最后调用bootm_decomp_image解压zImage内核镜像文件,并将解压后的Image存放到images.os.load地址处,至此kernel镜像已经完成加载。

3.3.5 boot_fn

boot_fn的定义为:

boot_os_fn *boot_fn;

可以看出它是一个boot_os_fn类型的函数指针。它的定义为

/*
 *  Continue booting an OS image; caller already has:
 *  - copied image header to global variable `header'
 *  - checked header magic number, checksums (both header & image),
 *  - verified image architecture (PPC) and type (KERNEL or MULTI),
 *  - loaded (first part of) image to header load address,
 *  - disabled interrupts.
 *
 * @flag: Flags indicating what to do (BOOTM_STATE_...)
 * @argc: Number of arguments. Note that the arguments are shifted down
 *     so that 0 is the first argument not processed by U-Boot, and
 *     argc is adjusted accordingly. This avoids confusion as to how
 *     many arguments are available for the OS.
 * @images: Pointers to os/initrd/fdt
 * @return 1 on error. On success the OS boots so this function does
 * not return.
 */
typedef int boot_os_fn(int flag, int argc, char * const argv[],
            bootm_headers_t *images);

extern boot_os_fn do_bootm_linux;

然后boot_fndo_bootm函数中被赋值为:

boot_fn = bootm_os_get_boot_func(images->os.os);

bootm_os_get_boot_func函数返回的是boot_os[os]boot_os是一个函数指针数组,定义在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,
#endif
#ifdef CONFIG_BOOTM_NETBSD
    [IH_OS_NETBSD] = do_bootm_netbsd,
#endif
#ifdef CONFIG_LYNXKDI
    [IH_OS_LYNXOS] = do_bootm_lynxkdi,
#endif
#ifdef CONFIG_BOOTM_RTEMS
    [IH_OS_RTEMS] = do_bootm_rtems,
#endif
#if defined(CONFIG_BOOTM_OSE)
    [IH_OS_OSE] = do_bootm_ose,
#endif
#if defined(CONFIG_BOOTM_PLAN9)
    [IH_OS_PLAN9] = do_bootm_plan9,
#endif
#if defined(CONFIG_BOOTM_VXWORKS) && \
    (defined(CONFIG_PPC) || defined(CONFIG_ARM))
    [IH_OS_VXWORKS] = do_bootm_vxworks,
#endif
#if defined(CONFIG_CMD_ELF)
    [IH_OS_QNX] = do_bootm_qnxelf,
#endif
#ifdef CONFIG_INTEGRITY
    [IH_OS_INTEGRITY] = do_bootm_integrity,
#endif
#ifdef CONFIG_BOOTM_OPENRTOS
    [IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
};

u-boot支持的操作操作系统类型宏在include/config_defaults.h文件中定义:

/* Support bootm-ing different OSes */
#define CONFIG_BOOTM_LINUX 1
#define CONFIG_BOOTM_NETBSD 1
#define CONFIG_BOOTM_PLAN9 1
#define CONFIG_BOOTM_RTEMS 1
#define CONFIG_BOOTM_VXWORKS 1

我们的内核是linux操作系统,因此boot_fn指向的是do_bootm_linux

3.3.6 boot_selected_os
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函数,这里将会执行该函数,进行linux内核的启动。

四、do_bootm_linux函数

do_bootm_linux函数在不同的硬件平台有不同的实现,我们这里查看的ARM架构的实现代码,代码位于arch/arm/lib/bootm.c文件:

/* Main Entry point for arm bootm implementation
 *
 * Modeled after the powerpc implementation
 * DIFFERENCE: Instead of calling prep and go at the end
 * they are called if subcommand is equal 0.
 */
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;
}

我们一点一点分析这里函数的执行流程,其流程大致如下:

4.1 boot_prep_linux

首先先执行BOOTM_STATE_OS_PREP的代码,这里调用的是boot_prep_linux,函数位于arch/arm/lib/bootm.c,这个函数跟内核传递参数有关系,为内核设置启动参数,u-boot向内核传递参数就是在这里做的准备:

/* Subcommand: PREP */
static void boot_prep_linux(bootm_headers_t *images)
{
    char *commandline = getenv("bootargs");

    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {   // 不执行
#ifdef CONFIG_OF_LIBFDT
        debug("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(&params);
        if (BOOTM_ENABLE_CMDLINE_TAG)      // 执行
            setup_commandline_tag(gd->bd, commandline);
        if (BOOTM_ENABLE_REVISION_TAG)   // 不执行
            setup_revision_tag(&params);
        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(&params);
        setup_end_tag(gd->bd);
    } else {
        printf("FDT and ATAGS support not compiled in - hanging\n");
        hang();
    }
}

这里先调用char *commandline = getenv("bootargs");从u-boot的环境变量中获取到我们传入的启动参数,比如上一节我们提到的启动参数:

noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

如果使用了设备树的话,这里将会调用image_setup_linux函数,该函数会在设备树的choosen节点下添加bootargs属性,其属性的内容就是bootargs环境变量的值。

不过这里我们没有使用设备树,因此走的是设置各种tag的逻辑,关于这一块具体内容可以看《Mini2440BootLoader简单实现》第3.3节内容,下面是一个tag在内存的分布示意图,图中地址只供参考,实际上:

  • u-bootgd->bd:指向地址空间为CONFIG_SYS_SDRAM_BASE + PHYS_SDRAM_1_SIZE - uboot大小 - 64kb - 4MB - sizeof(bd_t):因此第一个tag地址位于gd->bd->bi_boot_params,而不是图中的0x30000100
  • kernel地址位于0x30000000,而不是图中的0x30008000
4.1.1 struct tag

struct tag的结构如下:

struct tag {
    struct tag_header hdr;
    union {
        struct tag_core        core;
        struct tag_mem32    mem;
        struct tag_videotext    videotext;
        struct tag_ramdisk    ramdisk;
        struct tag_initrd    initrd;
        struct tag_serialnr    serialnr;
        struct tag_revision    revision;
        struct tag_videolfb    videolfb;
        struct tag_cmdline    cmdline;

        /*
         * Acorn specific
         */
        struct tag_acorn    acorn;

        /*
         * DC21285 specific
         */
        struct tag_memclk    memclk;
    } u;
};
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE    0x00000000

struct tag_header {
    u32 size;
    u32 tag;
};

/* The list must start with an ATAG_CORE node */
#define ATAG_CORE    0x54410001

struct tag_core {
    u32 flags;        /* bit 0 = read-only */
    u32 pagesize;
    u32 rootdev;
};

/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM    0x54410002

struct tag_mem32 {
    u32    size;
    u32    start;    /* physical start address */
};

...

tag结构包括taghdr和各种类型的tag_* ;hdr来标志当前的tag是哪种类型。

4.1.2 setup_start_tag

调用setup_start_tag设置启动要用到的tag,在这里有一个全局静态变量static struct tag *paramsbd->bi_boot_params的值赋给它:

static void setup_start_tag (bd_t *bd)
{
    params = (struct tag *)bd->bi_boot_params;

    params->hdr.tag = ATAG_CORE;
    params->hdr.size = tag_size (tag_core);

    params->u.core.flags = 0;
    params->u.core.pagesize = 0;
    params->u.core.rootdev = 0;

    params = tag_next (params);
}

setup_start_tag是初始化了第一个tag,类型为tag_core。 最后调用tag_next跳到第一个tag末尾,为下一个tag赋值做准备。

4.1.3 setup_commandline_tag

在头文件smdk2410.h配置了:

#define CONFIG_CMDLINE_TAG    /* enable passing of ATAGs */
#define CONFIG_SETUP_MEMORY_TAGS
#define CONFIG_INITRD_TAG

因此,会执行setup_commandline_tagsetup_memory_tagssetup_initrd_tag(与ramdisk相关,这里我们没有使用)。

setup_commandline_tag代码如下:

static void setup_commandline_tag(bd_t *bd, char *commandline)
{
    char *p;

    if (!commandline)
        return;

    /* eat leading white space */
    for (p = commandline; *p == ' '; p++);

    /* skip non-existent command lines so the kernel will still
     * use its default command line.
     */
    if (*p == '\0')
        return;

    params->hdr.tag = ATAG_CMDLINE;
    params->hdr.size =
        (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;

    strcpy (params->u.cmdline.cmdline, p);

    params = tag_next (params);
}

可以看出,这里调用了strcpy来赋值字符串,赋值的字符串正是,函数开头使用getenv获取的启动参数bootargs字符串。

4.1.4 setup_memory_tags

setup_memory_tags代码如下:

static void setup_memory_tags(bd_t *bd)
{
    int i;

    for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {   // 1
        params->hdr.tag = ATAG_MEM;
        params->hdr.size = tag_size (tag_mem32);

        params->u.mem.start = bd->bi_dram[i].start;
        params->u.mem.size = bd->bi_dram[i].size;

        params = tag_next (params);
    }
}

如果有多片内存ram,会循环为每一片的ram设置一个tag

4.1.5 setup_board_tags

setup_board_tags,这个是板级实现,如果没有实现则跳过:

__weak void setup_board_tags(struct tag **in_params) {}

setup_end_tag最后将最末尾的tag设置为ATAG_NONE,标志tag结束:

static void setup_end_tag(bd_t *bd)
{
    params->hdr.tag = ATAG_NONE;
    params->hdr.size = 0;
}

由此可知我们的启动参数params是一片连续的内存,这片内存有很多个tag,我们通过调用不同的程序来设置这些tag

4.2 boot_jump_linux

然后执行BOOTM_STATE_OS_GO里的代码,调用boot_jump_linux函数,位于arch/arm/lib/bootm.c。该函数会将tag的首地址也就是gd->bd->bi_boot_params传给内核,并跳转到内核地址、启动内核,让内核解析这些tag

/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned 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;   // 指定为内核入口地址

    s = getenv("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;
    else
        r2 = gd->bd->bi_boot_params;

    if (!fake) {
         kernel_entry(0, machid, r2);
    }
}

主要流程:

  • 获取gd->bd->bi_arch_numbermachid,如果有env则用envmachid
  • 设置内核入口地址kernel_entry,将images->ep赋值给它作为跳转到内核执行的入口;
  • r2设置为为gd->bd->bi_boot_params,也就是tag启动参数的起始地址;
  • kernel_entry传入其余相关参数并执行kernel_entry启动;

关于machid的值可以参考《内核解析U-boot传入的machid》,S3C2440默认传入的是362

参考文章

[1] 七,移植linux-3.19内核

[2] [uboot] uboot启动kernel篇(二)——bootm跳转到kernel的流程

[3] linux驱动之uboot启动过程及参数传递

[4] S5PV210-uboot解析(五)-do_bootm函数分析

[5] Linux内核镜像格式

[6] [uboot] uboot启动kernel篇(三)——uboot解析uImagekernel信息

posted @ 2022-01-17 23:22  大奥特曼打小怪兽  阅读(1177)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步