Android bootm启动过程

作者 by adtxl / 2022-03-24 / 暂无评论 / 385 个足迹

从uboot中启动的最后一个命令是使用bootm启动kernel,格式如下

bootm $loadaddr $loadaddr $fdtaddr

其中,$loadaddr表示boot image在ram中的加载地址,$fdtaddr表示dtb在ram中的加载地址。

bootm命令用于启动操作系统映像;bootm命令从映像文件的头部获取信息;包括:映像文件的CPU架构、操作系统类型、映像类型、压缩方式、映像文件在内存中的加载地址、映像文件运行的入口地址、映像文件名等;

bootm将映像文件加载到指定的地址,解压映像并传递必要的内核启动参数给内核,最后跳转到入口地址进入内核,开始执行内核;

bootm命令执行有三个步骤:

  1. 解压Linux内核映像并将其加载到RAM中
  2. 将ramdisk映像加载到RAM中
  3. 将控制权交给内核,并向内核传递ramdisk在RAM中的位置和大小等信息

将映像文件加载到RAM中时,需要确保映像文件的加载地址,不能与内核的位置有重叠;

本文研究下具体是如何启动的。

bootm命令的使用方法如下:

=> bootm --help
bootm - boot application image from memory

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

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_info processing
    prep    - OS specific prep before relocation or go
    go      - start OS

1. do_bootm

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

do_bootm函数如下,会先对sub commands进行处理,主要处理流程在do_bootm_subcommand,

// cmd/bootm.c
/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
    static int relocated = 0;

    if (!relocated) {
        int i;

        /* relocate names of sub-command table */
        for (i = 0; i < ARRAY_SIZE(cmd_bootm_sub); i++)
            cmd_bootm_sub[i].name += gd->reloc_off;

        relocated = 1;
    }
#endif

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

    return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
        BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
        BOOTM_STATE_LOADOS |
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
        BOOTM_STATE_RAMDISK |
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_MIPS)
        BOOTM_STATE_OS_CMDLINE |
#endif
        BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
        BOOTM_STATE_OS_GO, &images, 1);
}

do_bootm_states()函数操作,是由参数states来决定的;

// include/image.h
#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)

do_bootm_states()函数的参数images,用来表示bootm命令启动kernel的一些信息,包含了指向os、initrd、fdt的映像信息;是全局变量:

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

2. do_bootm_states

do_bootm_states函数如下:

/**
 * 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(struct cmd_tbl *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);

    /* Load the OS */
    if (!ret && (states & BOOTM_STATE_LOADOS)) {
        iflag = bootm_disable_interrupts();
        ret = bootm_load_os(images, 0);
        if (ret && ret != BOOTM_ERR_OVERLAP)
            goto err;
        else if (ret == BOOTM_ERR_OVERLAP)
            ret = 0;
    }

    /* 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) {
            env_set_hex("initrd_start", images->initrd_start);
            env_set_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)) {
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
        if (images->os.os == IH_OS_LINUX)
            fixup_silent_linux();
#endif
        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 = env_get("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;
}

其中获取Android boot.img中的kernel流程如下,boot_find_os()-->boot_get_kernel()-->android_image_get_kernel()

/**
 * android_image_get_kernel() - processes kernel part of Android boot images
 * @hdr:    Pointer to image header, which is at the start
 *          of the image.
 * @verify: Checksum verification flag. Currently unimplemented.
 * @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.
 *
 * This function returns the os image's start address and length. Also,
 * it appends the kernel command line to the bootargs env variable.
 *
 * Return: Zero, os start address and length on success,
 *      otherwise on failure.
 */
int android_image_get_kernel(const struct andr_img_hdr *hdr, int verify,
                 ulong *os_data, ulong *os_len)
{
    u32 kernel_addr = android_image_get_kernel_addr(hdr);
    uboot_set_rot();
#ifdef CONFIG_TRANSFER_IMGVERSION
    /* os_version = ver << 11 | lvl */
    u32 os_ver = hdr->os_version >> 11;
    u32 os_lvl = hdr->os_version & ((1U << 11) - 1);
    u32 android_version =
        (((os_ver >> 14) & 0x7F) * 100 + ((os_ver >> 7) & 0x7F)) * 100
        + (os_ver & 0x7F);
    u32 android_patchlevel = ((os_lvl >> 4) + 2000) * 100 + (os_lvl & 0x0F);
    extern void optee_transfer_imgversion(unsigned long a0, unsigned long a1,
    unsigned long a2, unsigned long a3,
    unsigned long a4, unsigned long a5,
    unsigned long a6, unsigned long a7,
    struct arm_smccc_res *res);
    struct arm_smccc_res res;
    optee_transfer_imgversion(OPTEE_SMC_CALL_TRANSFER_IMGVERSION,
        (unsigned long)android_version,
        (unsigned long)android_patchlevel, 0, 0, 0, 0, 0, &res);
#endif
    const struct image_header *ihdr = (const struct image_header *)
        ((uintptr_t)hdr + hdr->page_size);

    /*
     * Not all Android tools use the id field for signing the image with
     * sha1 (or anything) so we don't check it. It is not obvious that the
     * string is null terminated so we take care of this.
     */
    strncpy(andr_tmp_str, hdr->name, ANDR_BOOT_NAME_SIZE);
    andr_tmp_str[ANDR_BOOT_NAME_SIZE] = '\0';
    if (strlen(andr_tmp_str))
        printf("Android's image name: %s\n", andr_tmp_str);

    printf("Kernel load addr 0x%08x size %u KiB\n",
           kernel_addr, DIV_ROUND_UP(hdr->kernel_size, 1024));

    int len = 0;
    if (*hdr->cmdline) {
        printf("Kernel command line: %s\n", hdr->cmdline);
        len += strlen(hdr->cmdline);
    }

    char *bootargs = env_get("bootargs");
    if (bootargs)
        len += strlen(bootargs);

    int add_Factory_Flag = false;
    if(is_Factory_Mode()) {
        add_Factory_Flag = true;
        printf("is facotry mode add flag to cmdline \n");
        len = len + strlen(" i_isFactory=true");
    }

    char *newbootargs = malloc(len + 2);
    if (!newbootargs) {
        puts("Error: malloc in android_image_get_kernel failed!\n");
        return -ENOMEM;
    }
    *newbootargs = '\0';

    if (bootargs) {
        printf("bootargs: %s\n", bootargs);
        strcpy(newbootargs, bootargs);
        strcat(newbootargs, " ");
    }
    if (*hdr->cmdline)
        strcat(newbootargs, hdr->cmdline);

    if(add_Factory_Flag) {
        strcat(newbootargs, " i_isFactory=true");
        printf("add factory flag newbootargs: [%s]\n", newbootargs);
    }

    env_set("bootargs", newbootargs);

#ifdef ESWIN_FCT_RES_APP
    if(need_reserve_app() && parse_misc_for_wipedata()) {
        printf("newbootargs: [%s]\n", newbootargs);
        const char *match = NULL;
        char *fixbootargs;
        match = strstr(newbootargs, "androidboot.selinux=enforcing");
        if(match) {
            printf("wipe_data and fix selinux to permissive\n");
            fixbootargs = malloc(len + 4);
            if(!fixbootargs)
                goto malloc_fixbootargs_fail;
            memset(fixbootargs,'\0',len+4);
            strncpy(fixbootargs, newbootargs, match - newbootargs);
            strcat(fixbootargs, "androidboot.selinux=permissive");
            strcat(fixbootargs, match + strlen("androidboot.selinux=enforcing"));
        } else {
            printf("wipe_data and add selinux permissive\n");
            fixbootargs = malloc(len + 2 + strlen( "androidboot.selinux=permissive"));
            *fixbootargs = '\0';
            if(!fixbootargs)
                goto malloc_fixbootargs_fail;
            strcpy(fixbootargs, newbootargs);
            strcat(fixbootargs, " androidboot.selinux=permissive");
        }
        printf("fixbootargs: [%s]\n", fixbootargs);
        env_set("bootargs", fixbootargs);
malloc_fixbootargs_fail:
        printf("wipe_data parse done\n");
    }
#endif

    if (os_data) {
        if (image_get_magic(ihdr) == IH_MAGIC) {
            *os_data = image_get_data(ihdr);
        } else {
            *os_data = (ulong)hdr;
            *os_data += hdr->page_size;
        }
    }
    if (os_len) {
        if (image_get_magic(ihdr) == IH_MAGIC)
            *os_len = image_get_data_size(ihdr);
        else
            *os_len = hdr->kernel_size;
    }
    return 0;
}

获取ramdisk的流程如下,bootm_find_other()-->bootm_find_images()--boot_get_randisk()

获取dtb的流程如下,bootm_find_other()-->bootm_find_images()--boot_get_fdt()

独特见解