uboot向kernel的传参机制——bootm与tags

最近阅读代码学习了uboot boot kernel的过程以及uboot如何传参给kernel,记录下来,与大家共享:

U-boot版本:2014.4

Kernel版本:3.4.55

一 uboot 如何启动 kernel

1 do_bootm

uboot下使用bootm命令启动内核镜像文件uImage,uImage是在zImage头添加了64字节的镜像信息供uboot解析使用,具体这64字节头的内容,我们在分析bootm命令的时候就会一一说到,那直接来看bootm命令。

在common/cmd_bootm.c中

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
    static int relocated = 0;

    if (!relocated) {
        int i;

        /* relocate boot function table */
        for (i = 0; i < ARRAY_SIZE(boot_os); i++)
            if (boot_os[i] != NULL)
                boot_os[i] += gd->reloc_off;

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

数组boot_os是bootm最后阶段启动kernel时调用的函数数组,CONFIG_NEEDS_MANUAL_RELOC中的代码含义是将boot_os函数都进行偏移(uboot启动中会将整个code拷贝到靠近sdram顶端的位置执行),

但是boot_os函数在uboot relocate时已经都拷贝了,所以感觉没必要在进行relocate。这个宏因此没有定义,直接走下面。

新版uboot对于boot kernel实现了一个类似状态机的机制,将整个过程分成很多个阶段,uboot将每个阶段称为subcommand,

核心函数是do_bootm_states,需要执行哪个阶段,就在do_bootm_states最后一个参数添加那个宏定义,如: BOOTM_STATE_START

do_bootm_subcommand是按照bootm参数来指定运行某一个阶段,也就是某一个subcommand

对于正常的uImage,bootm加tftp的load地址就可以。

2 do_bootm_states

这样会走到最后函数do_bootm_states,那就来看看核心函数do_bootm_states

static 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);

参数中需要注意bootm_headers_t *images,这个参数用来存储由image头64字节获取到的的基本信息。由do_bootm传来的该参数是images,是一个全局的静态变量。

首先将states存储在images的state中,因为states中有BOOTM_STATE_START,调用bootm_start.

3 第一阶段:bootm_start

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

获取verify,bootstage_mark_name标志当前状态为bootm start(bootstage_mark_name可以用于无串口调试,在其中实现LED控制)。

boot_start_lmb暂时还没弄明白,以后再搞清楚。

最后修改images.state为bootm start。

bootm_start主要工作是清空images,标志当前状态为bootm start。

4 第二阶段:bootm_find_os

由bootm_start返回后,do_bootm传了BOOTM_STATE_FINDOS,所以进入函数bootm_find_os

static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
             char * const argv[])
{
    const void *os_hdr;

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

调用boot_get_kernel,函数较长,首先是获取image的load地址,如果bootm有参数,就是img_addr,之后如下:

    bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);

    /* copy from dataflash if needed */
    img_addr = genimg_get_image(img_addr);

    /* check image type, for FIT images get FIT kernel node */
    *os_data = *os_len = 0;
    buf = map_sysmem(img_addr, 0);

首先标志当前状态,然后调用genimg_get_image,该函数会检查当前的img_addr是否在sdram中,如果是在flash中,则拷贝到sdram中CONFIG_SYS_LOAD_ADDR处,修改img_addr为该地址。

这里说明我们的image可以在flash中用bootm直接起

map_sysmem为空函数,buf即为img_addr。

    switch (genimg_get_format(buf)) {
    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:
            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;

首先来说明一下image header的格式,在代码中由image_header_t代表,如下:

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;

genimg_get_format检查img header的头4个字节,代表image的类型,有2种,legacy和FIT,这里使用的legacy,头4个字节为0x27051956。

image_get_kernel则会来计算header的crc是否正确,然后获取image的type,根据type来获取os的len和data起始地址。

最后将hdr的数据拷贝到images的legacy_hdr_os_copy,防止kernel image在解压是覆盖掉hdr数据,保存hdr指针到legacy_hdr_os中,置位legacy_hdr_valid。

从boot_get_kernel中返回到bootm_find_os,继续往下:

    switch (genimg_get_format(os_hdr)) {
    case IMAGE_FORMAT_LEGACY:
        images.os.type = image_get_type(os_hdr);
        images.os.comp = image_get_comp(os_hdr);
        images.os.os = image_get_os(os_hdr);

        images.os.end = image_get_image_end(os_hdr);
        images.os.load = image_get_load(os_hdr);

根据hdr获取os的type,comp,os,end,load addr。

    /* find kernel entry point */
    if (images.legacy_hdr_valid) {
        images.ep = image_get_ep(&images.legacy_hdr_os_copy);
    } else {
        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 = (ulong)os_hdr;

获取os的start。

到这里bootm_find_os就结束了,主要工作是根据image的hdr来做crc,获取一些基本的os信息到images结构体中。

回到do_bootm_states中接下来调用bootm_find_other,

5 第三阶段:bootm_find_other

该函数大体看一下,对于legacy类型的image,获取查询是否有ramdisk,此处我们没有用单独的ramdisk,ramdisk是直接编译到kernel image中的。

回到do_bootm_states中接下来会调用bootm_load_os。

6 第四阶段: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;
    uint8_t comp = os.comp;
    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;
    __maybe_unused uint unc_len = CONFIG_SYS_BOOTM_LEN;
    int no_overlap = 0;
    void *load_buf, *image_buf;
#if defined(CONFIG_LZMA) || defined(CONFIG_LZO)
    int ret;
#endif /* defined(CONFIG_LZMA) || defined(CONFIG_LZO) */

    const char *type_name = genimg_get_type_name(os.type);

    load_buf = map_sysmem(load, unc_len);
    image_buf = map_sysmem(image_start, image_len);
    switch (comp) {
    case IH_COMP_NONE:
        if (load == blob_start || load == image_start) {
            printf("   XIP %s ... ", type_name);
            no_overlap = 1;
        } else {
            printf("   Loading %s ... ", type_name);
            memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);
        }
        *load_end = load + image_len;
        break;
#ifdef CONFIG_GZIP
    case IH_COMP_GZIP:
        printf("   Uncompressing %s ... ", type_name);
        if (gunzip(load_buf, unc_len, image_buf, &image_len) != 0) {
            puts("GUNZIP: uncompress, out-of-mem or overwrite "
                "error - must RESET board to recover\n");
            if (boot_progress)
                bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);
            return BOOTM_ERR_RESET;
        }

        *load_end = load + image_len;
        break;
#endif /* CONFIG_GZIP */

load_buf是之前find_os是根据hdr获取的load addr,image_buf是find_os获取的image的开始地址(去掉64字节头)。

之后则是根据hdr的comp类型来解压拷贝image到load addr上。

这里就需要注意,kernel选项的压缩格式必须在uboot下打开相应的解压缩支持,或者就不进行压缩

这里还有一点,load addr与image add是否可以重叠,看代码感觉是可以重叠的,还需要实际测试一下。

回到do_bootm_states,接下来根据os从boot_os数组中获取到了相应的os boot func,这里是linux,则是do_bootm_linux。后面代码如下:

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

这时do_bootm最后的代码,如果正常,boot kernel之后就不应该回来了。states中定义了BOOTM_STATE_OS_PREP(对于mips处理器会使用BOOTM_STATE_OS_CMDLINE),调用do_bootm_linux,如下:

int do_bootm_linux(int flag, int argc, char *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;
}

do_bootm_linux实现跟do_bootm类似,也是根据flag分阶段运行subcommand,这里会调到boot_prep_linux。

7 第五阶段:boot_prep_linux

该函数作用是为启动后的kernel准备参数,这个函数我们在第三部分uboot如何传参给kernel再仔细分析一下

boot_prep_linux完成返回到do_bootm_states后接下来就是最后一步了。执行boot_selected_os调用do_bootm_linux,flag为BOOTM_STATE_OS_GO,则调用boot_jump_linux

8 第六阶段:boot_jump_linux

    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) {
        strict_strtoul(s, 16, &machid);
        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);

boot_jump_linux主体函数如上

获取gd->bd->bi_arch_number为machid,如果有env则用env的machid,kernel_entry为之前由hdr获取的ep,也就是内核的入口地址。

fake为0,直接调用kernel_entry,参数1为0,参数2为machid,参数3为bi_boot_params。

这之后就进入了kernel的执行流程启动,就不会再回到uboot

这整个boot过程中bootm_images_t一直作为对image信息的全局存储结构。

三 uboot如何传参给kernel

uboot下的传参机制就直接来分析boot_prep_linux函数就可以了,如下:

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(¶ms);
        if (BOOTM_ENABLE_CMDLINE_TAG)
            setup_commandline_tag(gd->bd, commandline);
        if (BOOTM_ENABLE_REVISION_TAG)
            setup_revision_tag(¶ms);
        if (BOOTM_ENABLE_MEMORY_TAGS)
            setup_memory_tags(gd->bd);
        if (BOOTM_ENABLE_INITRD_TAG) {
            if (images->rd_start && images->rd_end) {
                setup_initrd_tag(gd->bd, images->rd_start,
                         images->rd_end);
            }
        }
        setup_board_tags(¶ms);
        setup_end_tag(gd->bd);
    } else {
        printf("FDT and ATAGS support not compiled in - hanging\n");
        hang();
    }
    do_nonsec_virt_switch();
}

首先获取出环境变量bootargs,这就是要传递给kernel的参数。

在配置文件中定义了CONFIG_CMDLINE_TAG以及CONFIG_SETUP_MEMORY_TAGS,根据arch/arm/include/asm/bootm.h,则会定义BOOTM_ENABLE_TAGS,首先调用setup_start_tag,如下:

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

params是一个全局静态变量用来存储要传给kernel的参数,这里bd->bi_boot_params的值赋给params,因此bi_boot_params需要进行初始化,从而将params放在一个合理的内存区域。

这里params为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;
};

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

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

回到boot_prep_linux,接下来调用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);
}

该函数设置第二个tag的hdr.tag为ATAG_CMDLINE,然后拷贝cmdline到tags的cmdline结构体中,跳到下一个tag。

回到boot_prep_linux,调用setup_memory_tag,如下:

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

    for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
        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);
    }
}

过程类似,将第三个tag设为ATAG_MEM,将mem的start,size保存在此处,如果有多片ram(CONFIG_NR_DRAM_BANKS > 1),则将下一个tag保存下一片ram的信息,依次类推。

回到boot_prep_linux中,调用setup_board_tags,这个函数是__weak属性,我们可以在自己的板级文件中去实现来保存跟板子相关的参数,如果没有实现,则是空函数。

最后调用setup_end_tags,如下:

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

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

这样整个参数的准备就结束了,最后在调用boot_jump_linux时会将tags的首地址也就是bi_boot_params传给kernel,供kernel来解析这些tag,kernel如何解析看第四部分kenrel如何找到并解析参数

总结一下,uboot将参数以tag数组的形式布局在内存的某一个地址,每个tag代表一种类型的参数,首尾tag标志开始和结束,首地址传给kernel供其解析。

四 kernel如何找到并解析参数

uboot在调用boot_jump_linux时最后kernel_entry(0, machid, r2);

按照二进制规范eabi,machid存在寄存器r1,r2即tag的首地址存在寄存器r2.

查看kernel的入口函数,在arch/arm/kernel/head.S,中可以看到如下一段汇编:

    /*
     * r1 = machine no, r2 = atags or dtb,
     * r8 = phys_offset, r9 = cpuid, r10 = procinfo
     */
    bl  __vet_atags

可以看出kernel刚启动会调用__vet_atags来处理uboot传来的参数,如下:

__vet_atags:
    tst r2, #0x3            @ aligned?
    bne 1f

    ldr r5, [r2, #0]
#ifdef CONFIG_OF_FLATTREE
    ldr r6, =OF_DT_MAGIC        @ is it a DTB?
    cmp r5, r6
    beq 2f
#endif
    cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?
    cmpne   r5, #ATAG_CORE_SIZE_EMPTY
    bne 1f
    ldr r5, [r2, #4]
    ldr r6, =ATAG_CORE
    cmp r5, r6
    bne 1f

2:  mov pc, lr              @ atag/dtb pointer is ok

1:  mov r2, #0
    mov pc, lr
ENDPROC(__vet_atags)

主要是对tag进行了一个简单的校验,查看tag头4个字节(tag_core的size)和第二个4字节(tag_core的type)。

之后对参数的真正分析处理是在start_kernel的setup_arch中,在arch/arm/kernel/setup.c中,如下:

void __init setup_arch(char **cmdline_p)
{
    struct machine_desc *mdesc;

    setup_processor();
    mdesc = setup_machine_fdt(__atags_pointer);
    if (!mdesc)
        mdesc = setup_machine_tags(machine_arch_type);
    machine_desc = mdesc;
    machine_name = mdesc->name;

#ifdef CONFIG_ZONE_DMA
    if (mdesc->dma_zone_size) {
        extern unsigned long arm_dma_zone_size;
        arm_dma_zone_size = mdesc->dma_zone_size;
    }
#endif
    if (mdesc->restart_mode)
        reboot_setup(&mdesc->restart_mode);

    init_mm.start_code = (unsigned long) _text;
    init_mm.end_code   = (unsigned long) _etext;
    init_mm.end_data   = (unsigned long) _edata;
    init_mm.brk    = (unsigned long) _end;

    /* populate cmd_line too for later use, preserving boot_command_line */
    strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    *cmdline_p = cmd_line;

    parse_early_param();

关键函数是setup_machine_tags,如下:

static struct machine_desc * __init setup_machine_tags(unsigned int nr)
{
    struct tag *tags = (struct tag *)&init_tags;
    struct machine_desc *mdesc = NULL, *p;
    char *from = default_command_line;
。。。。
    if (__atags_pointer)
        tags = phys_to_virt(__atags_pointer);
    else if (mdesc->atag_offset)
        tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);

。。。。。
    if (tags->hdr.tag == ATAG_CORE) {
        if (meminfo.nr_banks != 0)
            squash_mem_tags(tags);
        save_atags(tags);
        parse_tags(tags);
    }

    /* parse_early_param needs a boot_command_line */
    strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
。。。
}

首先回去获取tags的首地址,如果收个tag是ATAG_CORE类型,则会调用save_atags拷贝一份tags,最后调用parse_tags来分析这个tag list,如下:

static int __init parse_tag(const struct tag *tag)
{
    extern struct tagtable __tagtable_begin, __tagtable_end;
    struct tagtable *t;

    for (t = &__tagtable_begin; t < &__tagtable_end; t++)
        if (tag->hdr.tag == t->tag) {
            t->parse(tag);
            break;
        }

    return t < &__tagtable_end;
}   

/*
 * Parse all tags in the list, checking both the global and architecture
 * specific tag tables.
 */
static void __init parse_tags(const struct tag *t)
{
    for (; t->hdr.size; t = tag_next(t))
        if (!parse_tag(t))
            printk(KERN_WARNING
                "Ignoring unrecognised tag 0x%08x\n",
                t->hdr.tag);
}

遍历tags list,找到在tagstable中匹配的处理函数(hdr.tag一致),来处理响应的tag。

这个tagtable的处理函数是在调用__tagtable来注册的,如下:

static int __init parse_tag_cmdline(const struct tag *tag)
{
#if defined(CONFIG_CMDLINE_EXTEND)
    strlcat(default_command_line, " ", COMMAND_LINE_SIZE);
    strlcat(default_command_line, tag->u.cmdline.cmdline,
        COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
    pr_warning("Ignoring tag cmdline (using the default kernel command line)\n");
#else
    strlcpy(default_command_line, tag->u.cmdline.cmdline,
        COMMAND_LINE_SIZE);
#endif
    return 0;
}

__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

看这个对cmdline类型的tag的处理,就是将tag中的cmdline拷贝到default_command_line中。还有其他如mem类型的参数也会注册这个处理函数,来匹配处理响应的tag。这里就先以cmdline的tag为例。

这样遍历并处理完tags list之后回到setup_machine_tags,将from(即default_command_line)中的cmdline拷贝到boot_command_line,

最后返回到setup_arch中,

    /* populate cmd_line too for later use, preserving boot_command_line */
    strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    *cmdline_p = cmd_line;

    parse_early_param();

将boot_command_line拷贝到start_kernel给setup_arch的cmdline_p中,这里中间拷贝的boot_command_line是给parse_early_param来做一个早期的参数分析的。

到这里kernel就完全接收并分析完成了uboot传过来的args。

简单的讲,uboot利用函数指针及传参规范,它将

l   R0: 0x0

l   R1: 机器号

l   R2: 参数地址

三个参数传递给内核。

其中,R2寄存器传递的是一个指针,这个指针指向一个TAG区域。

UBOOT和Linux内核之间正是通过这个扩展了的TAG区域来进行复杂参数的传递,如 command line,文件系统信息等等,用户也可以扩展这个TAG来进行更多参数的传递。TAG区域的首地址,正是R2的值。

uboot向kernel的传参机制——bootm与tags,布布扣,bubuko.com

时间: 2024-07-30 10:20:45

uboot向kernel的传参机制——bootm与tags的相关文章

Java函数的传参机制

一 最近的一个项目,里面各种返回void,参数用引用类型,那叫一个熟和多,但是也把我绕糊涂了. 我就打算好好理一理java的传参机制,整理一番 二 很多人一听Java的传参,那一定会脱口而出,java都是值传递的.恭喜各位,答对了. 那么,传的是什么值? 如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,这个跟之前所谈的传值是一样的.如果在函数中改变了副本的值不会改变原始的值. 如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地

uboot移植之tag传参

1:什么是tag (1)tag是一种数据结构,用于uboot向内核传递参数,一个tag代表了一个参数,uboot在向kernel传参时,就是通过一个个tag来实现的. (2)每个tag都有一个tag_header(一个结构体),包含了tag的类型和大小,然后tag中剩余的部分tag_xxx来当作传参的tag 2:kernel如何知道tag的起始地址. uboot启动内核的最后通过theKernel (0, machid, bd->bi_boot_params);来启动内核,这三个参数,第一个固定

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

一.bootm说明 bootm这个命令用于启动一个操作系统映像.它会从映像文件的头部取得一些信息,这些信息包括:映像文件的基于的cpu架构.其操作系统类型.映像的类型.压缩方式.映像文件在内存中的加载地址.映像文件运行的入口地址.映像文件名等. 紧接着bootm将映像加载到指定的地址,如果需要的话,还会解压映像并传递必要有参数给内核,最后跳到入口地址进入内核. 这里的描述参考(http://blog.chinaunix.net/uid-20799298-id-99666.html) 需要打开的宏

UU-boot和kernel的传参

bd_info 1 typedefstruct bd_info { 2 int bi_baudrate;/* serial console baudrate */ 3 unsignedlong bi_ip_addr;/* IP Address */ 4 struct environment_s *bi_env; 5 ulong bi_arch_number;/* unique id for this board */ 6 ulong bi_boot_params;/* where this bo

WebApi 接口参数不再困惑:传参详解

转自:http://www.cnblogs.com/landeanfen/p/5337072.html 阅读目录 一.get请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.“怪异”的get请求 二.post请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.后台发送请求参数的传递 三.put请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 四.delete请求 五.总结 正文 前言:还记得刚使用WebApi那会儿,被它的传参机制折腾了好久,查阅了半天资料.如

浅谈 c++/java/javascript 之传参

本文主要梳理了几种语言的传参机制,即关于 传值.传引用 之争 最近开始学node.js搭后端服务器时,碰到这样一句话 java只有一种传参机制就是传值 javascript其大部分语法规范取自于JAVA语法规范, 那么这种句话也适用于它,于是也有  javascript只有一种传参机制就是传值 为了理解这句话,我从个人感觉较为接近底层的语言c++写一些测试,代码如下 #include<iostream> using namespace std; class Test { public: int

C#进阶系列——WebApi接口传参不再困惑:传参详解

前言:还记得刚使用WebApi那会儿,被它的传参机制折腾了好久,查阅了半天资料.如今,使用WebApi也有段时间了,今天就记录下API接口传参的一些方式方法,算是一个笔记,也希望能帮初学者少走弯路.本篇针对初初使用WebApi的同学们,比较基础,有兴趣的且看看. 本篇打算通过get.post.put.delete四种请求方式分别谈谈基础类型(包括int/string/datetime等).实体.数组等类型的参数如何传递. 一.get请求 对于取数据,我们使用最多的应该就是get请求了吧.下面通过

uboot给内核传参的方式——tag

一:tag是什么? tag 是一个数据结构:stract tag  这种数据格式在uboot和kernel是一样的,也就是说uboot在启动的时候将需要传给kernel的参数放在了DDR的某个地址处,而存放格式就是tag格式,然后kernel就会到这个地址去读取这些参数,读取方式也是按tag格式去读取的. 简单来说tag就像是一个数组一样,是一块连续的内存,里面存放了uboot需要传递给kernel的参数信息,我们通过定义一个指针params(static struct tag *params;

嵌入式 uboot引导kernel,kernel引导fs【转】

转自:http://www.cnblogs.com/lidabo/p/5383934.html#3639633 1.uboot引导kernel: u-boot中有个bootm命令,它可以引导内存中的应用程序映像(Kernel),bootm命令对应 common/cmd_bootm.c中的do_bootm()函数,此函数实现下面几个功能: 1)读flash中的内核映像文件 2)解压内核 3)校验内核 4)跳到内核执行(调用do_bootm_linux()函数) { 1.Stage1 start.S