[uboot] uboot start kernel articles (three) - uboot parsing uImage kernel information

一, description

From "[uboot] (番外篇) uboot boot kernel articles (two) - bootm jump To the process of the kernel" We know the process of bootm, the kernel information parsed from uImage is mainly implemented in bootm_find_os. Attention here, this information is mainly attached when the mkimage tool generates uImage, not the contents of the kernel native image. Because uImage has two types, Legacy-uImage and FIT-uImage, the format of the two types is different, so uboot parses the kernel information from these two uImage in different ways. These two types of parsing processes are described later.

二, Kernel information storage location

1, storage location

kernel information mainly includes two aspects:

  • kernel mirror information (including its loading address): placed in bootm_headers_t images -> image_info_t os
  • kernel entry address: put in bootm_headers_t images -> ulong ep

as follows (filter out some irrelevant parts) Include/image.h

typedef struct bootm_headers {
    image_info_t    os;     /* os image info */
    ulong       ep;     /* entry point of OS */
}

Therefore, the main purpose of bootm_find_os is to implement the members of image_info_t os and ulong ep in bootm_headers_t images.

2、image_info_t

image_info_t is used to describe the mirror information of the kernel. Includes header information, load address, kernel image address and length, and more. Its data structure is as follows: Include/image.h

typedef struct image_info {
    Ulong start, end; /* start/end of blob */ // include the start and end addresses of the entire kernel node within the additional node information
    Ulong image_start, image_len; /* start of image within blob, len of image */ // starting address of the kernel image, mirror length
    Ulong load; /* load addr for the image */ // image loading address
    Uint8_t comp, type, os; /* compression, type of image, os type */ // image compression format, image type, operating system type
    Uint8_t arch; /* CPU architecture */ // CPU architecture
} image_info_t; 

type corresponds to "kennel" and os corresponds to "linux".

Overview, the main purpose of our subsequent parsing of uImage is to fill image_info_t os and ulong ep. This sentence emphasizes several times.

三, Analysis of kernel information in Legacy-uImage The analysis of kernel information in

Legacy-uImage is relatively simple.

1, Generation of Legacy-uImage

First of all, you need to know how Legacy-uImage is generated. As follows:

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' // system
          -O ==> set operating system to 'os' // operating system
          -T ==> set image type to 'type' // image type
          -C ==> set compression type 'comp' // compression type
          -a ==> set load address to 'addr' (hex) // load address
          -e ==> set entry point to 'ep' (hex) // entry address
          -n ==> set image name to 'name' // image name, note that it cannot exceed 32B
          -d ==> use image data from 'datafile' // input file
          -x ==> set XIP (execute in place)

By the above, you can know that the kernel information is defined at this time (instead of the kernel native image). The generated uImage will contain a 64Byte format header, as follows:

[email protected]:boot$ od -tx1 -tc -Ax -N64 uImage
000000  27  05  19  56  5a  f3  f7  8e  58  45  0d  3d  00  17  cd  f8
         ' 005 031   V   Z 363 367 216   X   E  \r   =  \0 027 315 370
000010  20  00  80  00  20  00  80  40  e2  4b  43  b6  05  02  02  00
            \0 200  \0      \0 200   @ 342   K   C 266 005 002 002  \0
000020  4c  69  6e  75  78  5f  49  6d  61  67  65  00  00  00  00  00
         L   i   n   u   x   _   I   m   a   g   e  \0  \0  \0  \0  \0
000030  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
        \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0

So, uboot parsing Legacy-uImage is mainly to parse the contents of the 64Byte header.

2, Legacy-uImage header data structure

uboot uses struct image_header to correspond to the 64Byte header of this uImage.

typedef struct image_header {
    __be32 ih_magic; /* Image Header Magic Number */ // Magic number header used to check if it is a Legacy-uImage
    __be32 ih_hcrc; /* Image Header CRC Checksum */ // Header CRC check value
    __be32 ih_time; /* Image Creation Timestamp */ // Timestamp created by image
    __be32 ih_size; /* Image Data Size */ // Mirror data length
    __be32 ih_load; /* Data Load Address */ // load address
    __be32 ih_ep; /* Entry Point Address */ // Entry Address
    __be32 ih_dcrc; /* Image Data CRC Checksum */ // Mirrored CRC check
    Uint8_t ih_os; /* Operating System */ // Operating system type
    Uint8_t ih_arch; /* CPU architecture */ // system
    Uint8_t ih_type; /* Image Type */ // Mirror type
    Uint8_t ih_comp; /* Compression Type */ // Compression type
    Uint8_t ih_name[IH_NMLEN]; /* Image Name */ // Image name
} image_header_t;
#define IH_NMLEN 32 /* Image Name Length */

So, you only need to map the image_header pointer to the starting address of uImage, you can get the image_header entity. This header pointer image_header of uImage will be stored in bootm_headers as follows:

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 */ // head pointer
    Image_header_t legacy_hdr_os_copy; /* header copy */ // pointer to backup of header information
    Ulong legacy_hdr_valid; // Used to indicate whether the head pointer of Legacy-uImage is available, that is, whether this is a Legacy-uImage. 

3, the code flow for parsing the kernel information in Legacy-uImage

begins with the bootm_find_os entry. The code is as follows, filtering out the extraneous parts: Common/bootm.c

@static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
             Char * const argv[])
{
// The address of uImage corresponds to the parameter argv[0] passed in.
    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,
            &images, &images.os.image_start, &images.os.image_len);
        // Get the header of the Legacy-uImage by calling boot_get_kernel and return its pointer to os_hdr.
        // With images.os.image_start and images.os.image_len as their parameters, their values ​​are automatically set in boot_get_kernel

    /* get image parameters */
    Switch (genimg_get_format(os_hdr)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
    Case IMAGE_FORMAT_LEGACY:
        Images.os.type = image_get_type(os_hdr); // Get the image type from the header image_header and store it in images.os.type
        Images.os.comp = image_get_comp(os_hdr); // Get the compression type type from the header image_header and store it in images.os.comp
        Images.os.os = image_get_os(os_hdr); // Get the operating system type from the header image_header and store it in images.os.os

        Images.os.end = image_get_image_end(os_hdr); //Get the end address of uImage
        Images.os.load = image_get_load(os_hdr); // Get the load address from the header image_header and store it in images.os.load
        Images.os.arch = image_get_arch(os_hdr); // Get the cpu architecture type from the header image_header and store it in images.os.arch
        Break;
#endif
    If (images.os.arch == IH_ARCH_I386 ||
        Images.os.arch == IH_ARCH_X86_64) {
    } else if (images.legacy_hdr_valid) {
        Images.ep = image_get_ep(&images.legacy_hdr_os_copy); //Get the load address from the header image_header and store it in images.ep
        }
    Images.os.start = map_to_sysmem(os_hdr);

    Return 0;
}

Completed the implementation of the members of image_info_t os and ulong ep in bootm_headers_t images by the above code. The core of the code here is boot_get_kernel, which will determine the type of uImage, and the header image_header of Legacy-uImage, and associate image_header with bootm_headers.   而这里的代码的核心是boot_get_kernel,会实现uImage的类型的判断、和Legacy-uImage的头部image_header的设置,并且将image_header和bootm_headers进行关联。

4、boot_get_kernel

Analyze the core function of the uImage header. The code is as follows, filtering out the extraneous parts: Common/bootm.c

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;


    Img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
                          &fit_uname_config,
                          &fit_uname_kernel);
        // Because the kernel address argv[0] is a string type, it will be converted to an address type (long)

    / * Check image type, for FIT images get FIT kernel node * /
    *os_data = *os_len = 0;
    Buf = map_sysmem(img_addr, 0);
        / / Map to the physical address, because the MMU is not normally open in uboot, so img_addr generally does not change here

    Switch (genimg_get_format(buf)) {
        // The type of uImage will be judged based on the magic number here, assuming that it is judged to be the Legacy-uImage type.

#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
    Case IMAGE_FORMAT_LEGACY:
        printf ( "## Booting kernel from Legacy Image at% 08lx ... \ n",
               Img_addr);
                // this will print out the log "## Booting kernel from Legacy Image at 0x20008000"

        Hdr = image_get_kernel(img_addr, images->verify);
        If (!hdr)
            Return NULL;
                // Here the head pointer hdr is set to img_addr, then the magic number is determined, and the CRC value is correct.
               // Through this step, the head pointer of Legacy-uImage has been successfully set, and its content is determined accordingly! ! !

        /* 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);
                        // Get the address of the kernel image through image_header, stored in os_data, which is in images.os.image_start
            *os_len = image_get_data_size(hdr);
                        / / Get the length of the kernel image through image_header, stored in os_len, which is images.os.image_len
            Break;
        }

        Memmove(&images->legacy_hdr_os_copy, hdr,
            Sizeof(image_header_t)); // Make a backup of the uImage header
        Images->legacy_hdr_os = hdr; // store the uImage header pointer to images->legacy_hdr_os
        Images->legacy_hdr_valid = 1; // Indicates that the head pointed to by uImage is available, which is a Legacy-uImage type
        Break;
#endif
    }
    Return buf;
}

四, FIT-uImage kernel information analysis

1, principle simple introduction

flattened image tree, similar to FDT (flattened device tree) an implementation mechanism. It combines some of the images that need to be used (such as kernel, dtb, and file system) with a certain syntax and format to generate an image file. and the kernel image is also a node in the FIT's configure, and its information is described by the attributes in the node.  和乌boot's job is to extract the corresponding kernel node from FIT, get the corresponding properties in the node, and get the kernel information. The way is quite similar to FDT. Get the kernel information and fill it in image_info_t os and ulong ep in bootm_headers_t images.

2, generate description

/ {
    Images {
        [email protected] {
            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"; // Mirror type is kernel
            Arch = "arm"; // system
            Os = "linux"; // operating system
            Compression = "none"; // compression type
            Load = <0x20008000>; // load address
            Entry = <0x20008040>; // entry address
        };
    Configuration {
        Default = "[email protected]";
        [email protected] {
            Description = "Boot Linux kernel with FDT blob";
            Kernel = "[email protected]"; // Specify the information described by the kernel for the node "[email protected]"
        };
    }; 

It can be seen that the kernel information is described in the "[email protected]" node. Note that even the kernel image is used as the attribute "data" of this node. So the principle of uboot parsing the kernel information in FIT-uImage is:

  1. parsing the configuration node from the itb (FIT-uImage) file
  2. Get the path (offset) of the node of the kernel to be used from the configurations
  3. Get various properties from the kernel node, these properties are the kernel information.
  4. Include the image of the kernel is also in the data attribute.

3, the relationship between data structure description

  • and struct bootm_headers The kernel in FIT-uImage is described in the form of nodes, and its nodes also have their own headers. Its node information is stored in struct bootm_headers as follows:
Typedef struct bootm_headers {
#if IMAGE_ENABLE_FIT
    Const char *fit_uname_cfg; /* configuration node unit name */ // configuration node name

    Void *fit_hdr_os; /* os FIT image header */ // The header of itb, the corresponding address of FIT-uImage
    Const char *fit_uname_os; /* os subimage node unit name */ // name of the kernel node
    Int fit_noffset_os; /* os subimage node offset */ // The node offset of the kernel node directly represents the kernel node
} bootm_headers_t;

4, the code flow for parsing the kernel information in FIT-uImage

begins with the bootm_find_os entry. The code is as follows, filtering out the extraneous parts: Common/bootm.c

@static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
             Char * const argv[])
{
// The address of uImage corresponds to the parameter argv[0] passed in.
    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,
            &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 the header of itb (FIT-uImage) by calling boot_get_kernel, stored in os_hdr
        // With images.os.image_start and images.os.image_len as their parameters, their values ​​are automatically set in boot_get_kernel
        // At the same time, the information about the kernel node in itb is set in the boot_get_kernel. fit_hdr_os, fit_uname_os, and fit_noffset_os
        // Continue to follow up

    /* get image parameters */
    Switch (genimg_get_format(os_hdr)) {
#if IMAGE_ENABLE_FIT
    Case IMAGE_FORMAT_FIT:
        If (fit_image_get_type(images.fit_hdr_os,
                       Images.fit_noffset_os,
                       &images.os.type)) {
            Return 1;
        }
                // Call fit_image_get_type to parse the "type" attribute from the kerne node of itb and store it in images.os.type.

        If (fit_image_get_comp(images.fit_hdr_os,
                       Images.fit_noffset_os,
                       &images.os.comp)) {
            Return 1;
        }
                // Call fit_image_get_comp to parse the "comp" attribute from the kerne node of itb and store it in images.os.comp.

        If (fit_image_get_os(images.fit_hdr_os, images.fit_noffset_os,
                     &images.os.os)) {
            Return 1;
        }
                // Call fit_image_get_comp to parse the "os" attribute from the kerne node of itb and store it in images.os.os.

        If (fit_image_get_arch(images.fit_hdr_os,
                       Images.fit_noffset_os,
                       &images.os.arch)) {
            Return 1;
        }
                // Call fit_image_get_comp to parse the "arch" attribute from the kerne node of itb and store it in images.os.arch.

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

        If (fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os,
                       &images.os.load)) {
            Return 1;
        }
                // Call fit_image_get_comp to parse the "load" attribute from the kerne node of itb and store it in images.os.load.
        Break;
#endif
    If (images.os.arch == IH_ARCH_I386 ||
        Images.os.arch == IH_ARCH_X86_64) {
...
#if IMAGE_ENABLE_FIT
    } else if (images.fit_uname_os) {
        Int ret;

        Ret = fit_image_get_entry(images.fit_hdr_os,
                      Images.fit_noffset_os, &images.ep);
                // Call fit_image_get_comp to parse the "ep" attribute from the kerne node of itb and store it in images.os.ep.
        If (ret) {
            Puts("Can't get entry point property!\n");
            Return 1;
        }
#endif
    }

Completed the implementation of the members of image_info_t os and ulong ep in bootm_headers_t images by the above code. The core of the code here is boot_get_kernel, which will determine the type of uImage, and the setting of the header information of FIT-uImage, and associate the node information of the FIT-uImage kernel with bootm_headers.

4、boot_get_kernel

Analyze the core function of the uImage header. The code is as follows, filtering out the extraneous parts: Common/bootm.c

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

    Img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
                          &fit_uname_config,
                          &fit_uname_kernel);
        // Because the kernel address argv[0] is a string type, it will be converted to an address type (long)

    / * Check image type, for FIT images get FIT kernel node * /
    *os_data = *os_len = 0;
    Buf = map_sysmem(img_addr, 0);
        / / Map to the physical address, because the MMU is not normally open in uboot, so img_addr generally does not change here

    Switch (genimg_get_format(buf)) {
        // Here we will judge the type of uImage based on the magic number, assuming that it is judged to be a FIT-uImage type.

#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);
                / / In the fit_image_load will find the node specified by IH_TYPE_KERNEL
                // Corresponding to kernel = "[email protected]"; the specified node, returning its node offset, and the address and length of the value of the data attribute (representing the kernel image)
                // set to os_data and os_len, which is images.os.image_start and images.os.image_len
                // specific reference code
        If (os_noffset < 0)
            Return NULL;

        Images->fit_hdr_os = map_sysmem(img_addr, 0);
                // Set the header of itb, which corresponds to the starting address of FIT-uImage
        Images->fit_uname_os = fit_uname_kernel;
                / / Set the name of the kernel node
        Images->fit_uname_cfg = fit_uname_config;
                / / Set the configuration node name
        Images->fit_noffset_os = os_noffset;
                / / Set the node offset of the kernel node, directly represents the kernel node
        Break;
#endif
    Return buf;
} 

By the above code, you get the address of itb and the node offset of the kernel in itb (FIT-uImage), similar to the operation of fdt, you can pass the address of these two itb and itb (FIT- uImage) The node offset of the kernel to get the properties of the kernel node, which is some of our contents in the above four and three.

到这里, uboot bootm command, the work of extracting kernel information from uImage is completed.