IMX6ULL-uboot启动内核流程分析(4)

uboot版本2016.03

bootz命令加载linux内核流程分析

bootz命令在启动内核的过程中会一直伴随一个全局变量images,images是一个bootm_headers_t形式的结构体,结构体的定义在文件/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;

#if defined(CONFIG_FIT)
    const char    *fit_uname_cfg;    /* configuration node unit name */

    void        *fit_hdr_os;    /* os FIT image header */
    const char    *fit_uname_os;    /* os subimage node unit name */
    int        fit_noffset_os;    /* os subimage node offset */

    void        *fit_hdr_rd;    /* init ramdisk FIT image header */
    const char    *fit_uname_rd;    /* init ramdisk subimage node unit name */
    int        fit_noffset_rd;    /* init ramdisk subimage node offset */

    void        *fit_hdr_fdt;    /* FDT blob FIT image header */
    const char    *fit_uname_fdt;    /* FDT blob subimage node unit name */
    int        fit_noffset_fdt;/* FDT blob subimage node offset */

    void        *fit_hdr_setup;    /* x86 setup FIT image header */
    const char    *fit_uname_setup; /* x86 setup subimage node name */
    int        fit_noffset_setup;/* x86 setup subimage node offset */
#endif

#ifndef USE_HOSTCC
    image_info_t    os;        /* os image info */
    ulong        ep;        /* entry point of OS */

    ulong        rd_start, rd_end;/* ramdisk start/end */

    char        *ft_addr;    /* flat dev tree address */
    ulong        ft_len;        /* length of flat device tree */

    ulong        initrd_start;
    ulong        initrd_end;
    ulong        cmdline_start;
    ulong        cmdline_end;
    bd_t        *kbd;
#endif

    int        verify;        /* getenv("verify")[0] != 'n' */

#define    BOOTM_STATE_START    (0x00000001)
#define    BOOTM_STATE_FINDOS    (0x00000002)
#define    BOOTM_STATE_FINDOTHER    (0x00000004)
#define    BOOTM_STATE_LOADOS    (0x00000008)
#define    BOOTM_STATE_RAMDISK    (0x00000010)
#define    BOOTM_STATE_FDT        (0x00000020)
#define    BOOTM_STATE_OS_CMDLINE    (0x00000040)
#define    BOOTM_STATE_OS_BD_T    (0x00000080)
#define    BOOTM_STATE_OS_PREP    (0x00000100)
#define    BOOTM_STATE_OS_FAKE_GO    (0x00000200)    /* 'Almost' run the OS */
#define    BOOTM_STATE_OS_GO    (0x00000400)
    int        state;

#ifdef CONFIG_LMB
    struct lmb    lmb;        /* for memory mgmt */
#endif
} bootm_headers_t;

第32行,定义了一个image_info_t类型的成员变量os,通过此变量描述系统的镜像信息。定义如下:

typedef struct image_info {
    ulong start, end; /* blob 开始和结束位置*/
    ulong image_start, image_len; /* 镜像起始地址(包括 blob)和长度 */
    ulong load; /* 系统镜像加载地址*/
    uint8_t comp, type, os; /* 镜像压缩、类型, OS 类型 */
    uint8_t arch; /* CPU 架构 */
} image_info_t;

第49~59行,宏定义用来描述uboot启动内核的不同阶段。

do_bootz 函数

bootz命令对应调用的函数就是do_bootz函数,函数定义在/cmd/bootm.c中,如下:

int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    int ret;

    /* Consume 'bootz' */
    argc--; argv++;

    if (bootz_start(cmdtp, flag, argc, argv, &images))
        return 1;

    /*
     * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
     * disable interrupts ourselves
     */
    bootm_disable_interrupts();

    images.os.os = IH_OS_LINUX;
    ret = do_bootm_states(cmdtp, flag, argc, argv,
                  BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
                  BOOTM_STATE_OS_GO,
                  &images, 1);

    return ret;
}

第6行,相当于把命令传参的boot命令去掉。

第8行,调用了bootz_start函数。

第15行,调用了bootm_disable_interrupts函数,关闭了中断。

第17行,设置了要启动内核的类型为LINUX。

第18行,调用了do_bootm_states函数执行boot的不同阶段。

bootz_start函数

/*
 * zImage booting support
 */
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
            char * const argv[], bootm_headers_t *images)
{
    int ret;
    ulong zi_start, zi_end;

    ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
                  images, 1);

    /* Setup Linux kernel zImage entry point */
    if (!argc) {
        images->ep = load_addr;
        debug("*  kernel: default image load address = 0x%08lx\n",
                load_addr);
    } else {
        images->ep = simple_strtoul(argv[0], NULL, 16);
        debug("*  kernel: cmdline image address = 0x%08lx\n",
            images->ep);
    }

    ret = bootz_setup(images->ep, &zi_start, &zi_end);
    if (ret != 0)
        return 1;

    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.
     */
    if (bootm_find_images(flag, argc, argv))
        return 1;

#ifdef CONFIG_SECURE_BOOT
    extern uint32_t authenticate_image(
            uint32_t ddr_start, uint32_t image_size);
    if (authenticate_image(images->ep, zi_end - zi_start) == 0) {
        printf("Authenticate zImage Fail, Please check\n");
        return 1;
    }
#endif
    return 0;
}

bootz_start函数的作用是完成对uboot启动zImage镜像文件的支持工作的。

第10行,调用do_bootm_states函数完成执行BOOTM_STATE_START阶段。

第18~22行,设置images的成员变量ep即系统镜像的入口地址,并打印出地址。

第24行,调用bootz_setup函数,该函数较为简单,在该函数中获取了镜像文件的zimage_header类型的文件头,并判断当前镜像文件是否为ARM的LINUX镜像。并将镜像文件的入口地址和结束地址分别保存到变量zi_startzi_end中。

第34行,调用bootm_find_images函数,设置了images->ftaddr(设备树的起始地址)和images->ft_len(设备树的长度)。

do_bootm_states函数

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
            int states, bootm_headers_t *images, int boot_progress)
{
    boot_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);


    /*****省略部分代码*****/


    /* 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;
    }

    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;
}

do_bootm_states函数会跟根据参数states,实现uboot启动内核不同阶段的代码。

第14行处理了BOOTM_STATE_START阶段。在该阶段调用了bootm_start函数,完成了images内存清空和images.verify成员变量设置,并将images.state设置为BOOTM_STATE_START

第24行,通过函数bootm_os_get_boot_func查找系统的启动函数,参数images->os.os就是在do_bootz函数中设置的IH_OS_LINUX。至此boot_fn这个函数指针指向的就是LINUX的启动函数do_bootm_linux。

第37行,调用do_bootm_linux函数处理BOOTM_STATE_OS_PREP状态,do_bootm_linux函数如下:

int do_bootm_linux(int flag, int argc, char * const argv[],
           bootm_headers_t *images)
{
    /* No need for those on ARM */
    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
        return -1;

    if (flag & BOOTM_STATE_OS_PREP) {
        boot_prep_linux(images);
        return 0;
    }

    if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
        boot_jump_linux(images, flag);
        return 0;
    }

    boot_prep_linux(images);
    boot_jump_linux(images, flag);
    return 0;
}

在该函数中调用boot_prep_linux处理BOOTM_STATE_OS_PREP状态。在boot_prep_linux函数中主要处理uboot用于启动linux的环境变量bootargs。

第42行,是处理BOOTM_STATE_OS_FAKE_GO状态,由于没有定义宏因此不会执行。

第59行,是处理BOOTM_STATE_OS_GO状态,即开始启动内核,调用boot_selected_os函数->调用boot_fn(do_bootm_linux)函数->boot_jump_linux函数,函数定义如下:

static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    /*省略64位代码*/

    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) {
#ifdef CONFIG_ARMV7_NONSEC
        if (armv7_boot_nonsec()) {
            armv7_init_nonsec();
            secure_ram_addr(_do_nonsec_entry)(kernel_entry,
                              0, machid, r2);
        } else
#endif
            kernel_entry(0, machid, r2);
    }
#endif
}

第5行,定义了变量machid,不采用设备树此变量uboot会传递给linux内核,内核会在机器ID表中查找是否支持该机器。如果采用设备树,该变量则无效。

第7行,定义了一个函数指针kernel_entry,该函数有3个参数,第一个参数为0,第二个参数是机器ID,第三个参数为ATAGS 或者设备树(DTB)首地址。

第11行,获取kernel_entry函数,该函数为linux内核定义,是linux镜像的第一行代码。

第27行,如果使用设备树将r2寄存器的值赋值为设备树首地址。

第30行,不适用设备树,r2就是bootargs参数首地址。

最后第40行,调用kernel_entry函数启动linux内核。

至此uboot所有工作都已经结束!

猜你喜欢

转载自blog.csdn.net/qq_42174306/article/details/128857389