u-boot 与 kernel 间的参数传递

do_bootm_linux函数中,用到函数char *commandline = getenv ("bootargs") 来从环境变量中获取命令参数,下面就来分析一下参数的传递过程:

 

首先要讲一个重要的结构体:

struct tag {

    struct tag_header hdr;

    union {

        struct tag_core core;

        struct tag_mem_range mem_range;

        struct tag_cmdline cmdline;

        struct tag_clock clock;

        struct tag_ethernet ethernet;

    } u;

};

在这个结构体重集合了传递的绝大多数命令参数。

 

函数do_bootm_linux中的部分代码:

int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images) 

{ 

#ifdef CONFIG_CMDLINE_TAG//如果定义了这个宏,将会把bootargs传给内核。 

    char *commandline = getenv ("bootargs");//获取bootargs环境变量。 

#endif 

    theKernel = (void (*)(int, int, uint))images->ep;//镜像的入口地址处也就是需要执行的函数入口 

    s = getenv ("machid"); 

    if (s) { 

        machid = simple_strtoul (s, NULL, 16); 

        printf ("Using machid 0x%x from environment/n", machid); 

    } 

    show_boot_progress (15); 

    debug ("## Transferring control to Linux (at address %08lx) .../n", 

           (ulong) theKernel); 

//下面将会初始化传给内核的标签(tag),这些标签里包含了必要的环境变量和参数。 

#if defined (CONFIG_SETUP_MEMORY_TAGS) || / 

    defined (CONFIG_CMDLINE_TAG) ||

    defined (CONFIG_INITRD_TAG) ||

    defined (CONFIG_SERIAL_TAG) ||

    defined (CONFIG_REVISION_TAG) 

    setup_start_tag (bd); 

#ifdef CONFIG_SETUP_MEMORY_TAGS 

    setup_memory_tags (bd);//内存有关的定义 

#endif 

#ifdef CONFIG_CMDLINE_TAG 

    setup_commandline_tag (bd, commandline);//bootargs传给tag 

#endif 

#ifdef CONFIG_INITRD_TAG 

    if (images->rd_start && images->rd_end)//没有ramdisk所以不会执行这个路径 

        setup_initrd_tag (bd, images->rd_start, images->rd_end); 

#endif 

    setup_end_tag (bd); 

#endif 

   ...

} 

 

*********************************************************************************************

 

首先看下函数setup_start_tag

static void setup_start_tag (bd_t *bd)

{

    params = (struct tag *) bd->bi_boot_params;

    //board_init 中有一句:gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;这个设置参数存放的位置:  #define PHYS_SDRAM_1     CSD0_DDR_BASE_ADDR -->> #define CSD0_DDR_BASE_ADDR              MMDC0_ARB_BASE_ADDR -->> #define MMDC0_ARB_BASE_ADDR             0x10000000

    //初始化(struct tag *)型的全局变量paramsbd->bi_boot_params的地址,之后的setup tags相关函数如下面的setup_memory_tags就把其它tag的数据放在此地址的偏移地址上。

    params->hdr.tag = ATAG_CORE;

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

    ...

}

 

/* The list must start with an ATAG_CORE node */

#define ATAG_CORE   0x54410001

 

tag方式传参

(1)struct tagtag是一个数据结构,在ubootlinux kernel中都有定义tag数据机构,而且定义是一样的。

(2)tag_headertag_xxxtag_header中有这个tagsize和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。

(3)tag_starttag_endkernel接收到的传参是若干个tag构成的,这些tagtag_start起始,到tag_end结束。

 

*********************************************************************************************

 

RAM 相关参数在 bootm.c 中的函数 setup_memory_tags 中初始化:

static void setup_memory_tags (bd_t *bd)

{

    int i;

    for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {  //:#define CONFIG_NR_DRAM_BANKS   1

        params->hdr.tag = ATAG_MEM;   //#define ATAG_MEM    0x54410002

        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;   //这些变量都是在struct bd_info

        params = tag_next(params);  //初始化内存相关tag

    }

}

 

*********************************************************************************************

调用getenv ("bootargs")函数获得命令行参数并让commandline指向它,然后调用setup_commandline_tag函数将命令行参数放到tag参数例表

static void setup_commandline_tag (bd_t *bd, char *commandline)

{ // commandline就是需要传递的bootargs

    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;   //#define ATAG_CMDLINE    0x54410009

    params->hdr.size =

        (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;

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

    params = tag_next (params);

}

当内核启动的时候就会去读这些参数。

 

*********************************************************************************************

 

以下是内核部分

*********************************************************************************************

 

执行到setup_end_tag(bd)的时候,uboot向内核传参结束,下面就开始是内核来接受这些参数,并进行解析,以用来接下来的启动过程。

b   start_kernel  -->> asmlinkage void __init start_kernel(void) -->> setup_arch(&command_line) -->> mdesc = setup_machine_tags(machine_arch_type) -->> parse_tags(tags)//开始解析所有的参数

 

void __init setup_arch(char **cmdline_p)

{

     /*__atags_pointeruboot传递的参数地址*/

    mdesc = setup_machine_fdt(__atags_pointer);

    /*由于参数非设备树结构,返回NULL*/

    if (!mdesc)

        mdesc = setup_machine_tags(machine_arch_type);

    ...

    init_mm.start_code = (unsigned long) _text; // 记录kernel代码段的虚拟启始地址

    init_mm.end_code   = (unsigned long) _etext; // 记录kernel代码段的虚拟结束地址

    init_mm.end_data   = (unsigned long) _edata; // 记录kernel数据段的虚拟结束地址

    init_mm.brk       = (unsigned long) _end; // brk指向kernel全部段的虚拟结束地址

    *cmdline_p = boot_command_line; // boot_command_line的地址保存到start_kernel局部指针变量cmdline_p

   ...

}

 

nr= machid=MACH_TYPE_MX6Q_SABRESD=3980uboot传递过来的,内核中的MACH_TYPE_MX6Q_SABRESD

/include/generated/mach-types.h中定义,这个文件是动态产生的。

/*

 * This was automagically generated from arch/arm/tools/mach-types!

 * Do NOT edit

 */

#ifndef __ASM_ARM_MACH_TYPE_H

#define __ASM_ARM_MACH_TYPE_H

 

#ifndef __ASSEMBLY__

/* The type of machine we're running on */

extern unsigned int __machine_arch_type;

#endif

#define MACH_TYPE_MX6Q_SABRESD     3980

#endif

 

static struct machine_desc * __init setup_machine_tags(unsigned int nr)

{

    init_tags.mem.start = PHYS_OFFSET; //#define PHYS_OFFSET    PLAT_PHYS_OFFSET -->> define PLAT_PHYS_OFFSET   MX6_PHYS_OFFSET  -->> #define MX6_PHYS_OFFSET   UL(0x10000000) tag在内存中开始的地址

    /* 在受支持的机器列表中定位 */

    for_each_machine_desc(p)

    /*#define for_each_machine_desc(p)          \

    for (p = __arch_info_begin; p < __arch_info_end; p++)

    __arch_info_beginmachine_desc存放的位置,通过属性定义,*/

        if (nr == p->nr) {

            printk("Machine: %s\n", p->name);

//内核参数打印:Machine: Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board

            mdesc = p;

            break;

        }

    if (__atags_pointer)

        tags = phys_to_virt(__atags_pointer);

    else if (mdesc->boot_params) {

            tags = phys_to_virt(mdesc->boot_params);

        }

    //这里根据情况判断tags接收uboot参数的来源,if中说明是uboot传递来的参数,elseif说明是由内核部分写死的

    if (tags->hdr.tag != ATAG_CORE) {

        tags = (struct tag *)&init_tags;

    }

    if (mdesc->fixup)

        mdesc->fixup(mdesc, tags, &from, &meminfo);

   

    if (tags->hdr.tag == ATAG_CORE) {

        if (meminfo.nr_banks != 0)

            squash_mem_tags(tags);

        save_atags(tags);

        parse_tags(tags);

    }

    /*全局变量meminfo在这时候还没有被初始化,其用于指示物理内存bank个数的成员nr_banks肯定为0save_atags将把tags里的内容拷贝给全局变量atags_copy*/

    /* parse_early_param needs a boot_command_line */

    strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

    return mdesc;

}

 

*********************************************************************************************

 

#define for_each_machine_desc(p)            \

    for (p = __arch_info_begin; p < __arch_info_end; p++)

__arch_info_beginmachine_desc存放的位置,通过属性定义,

 

setup_machine_tags函数一开始首先定义struct tag *型指针变量tags:

struct tag *tags = (struct tag *)&init_tags;

tags就是内核接收uboot参数的。

static struct init_tags {

    struct tag_header hdr1;

    struct tag_core   core;

    struct tag_header hdr2;

    struct tag_mem32  mem;

    struct tag_header hdr3;

} init_tags __initdata = {

    { tag_size(tag_core), ATAG_CORE },

    { 1, PAGE_SIZE, 0xff },

    { tag_size(tag_mem32), ATAG_MEM },

    { MEM_SIZE },

    { 0, ATAG_NONE }

};

 

__atags_pointer定义是在文件arch/arm/kernel/head-common.s

    .long   __atags_pointer         @ r6

在内核代码源头stext运行前,由arm寄存器R6保存要传递给内核的参数的地址,当stext运行到子程序 __switch_data时,定义变量__atags_pointer保存这个地址,即__atags_pointer保存了uboot要传递给内核的参数的地址,所以这里让tags获取__atags_pointer的转换后的虚拟地址,即可访问。

有的时候,可能不需要从uboot传递参数到内核,也就是说这些参数写死在内核里而不是在uboot里,那么就可以写死,写死是写死在machine_desc变量的boot_params成员,可以把地址值赋给这个成员,即可访问。

 

*********************************************************************************************

 

接下来是函数parse_tags(tags)

parse_tags函数的实现,必须要搞懂tags指针变量里面的内容是什么了,tags指针变量实际上指向了多个的struct tags型变量,观察struct tags结构体即可发现,它是一个struct tags_header加一个联合的结构。再看parse_tags的实现,它就是对每一个它指向的struct tags型变量调用函数parse_tag,这个函数实际解析struct tags型变量。

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

}

 

if条件语句中调用parse_tag函数:

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;

}

 

*********************************************************************************************

 

extern struct tagtable __tagtable_begin, __tagtable_end;,发现这两个是在链接脚本vmlinux.lds.S中定义的,并且是卡住某一代码段便于给C函数调用。

        __tagtable_begin = .;

            *(.taglist.init)

        __tagtable_end = .;

       

(arch/include/asm/setup.h)     

#define __tag __used __attribute__((__section__(".taglist.init")))

#define __tagtable(tag, fn) \

static struct tagtable __tagtable_##fn __tag = { tag, fn }

__tagtable(tag, fn)形式定义的宏,实际是在".taglist.init"段中,创建struct tagtable的静态变量,并赋初值给这个变量,赋的值就是这个宏的两个参数tagfn.

所以,parse_tag函数对传递进来的参数,利用__tagtable_begin__tagtable_end,使用所有定义的__tagtable(tag, fn)对其进行遍历,一旦发现__tagtable(tag, fn)tag和传递进来的参数的参数头的tag(struct tags_headertag成员)一样,就用该__tagtable(tag, fn)fn即解析函数进行解析。

 

在内核中用__tagtable(tag,fn)定义的函数:

Init.c (arch\arm\mm):__tagtable(ATAG_INITRD, parse_tag_initrd);

Init.c (arch\arm\mm):__tagtable(ATAG_INITRD2, parse_tag_initrd2);

Setup.c (arch\arm\kernel):__tagtable(ATAG_CORE, parse_tag_core);

Setup.c (arch\arm\kernel):__tagtable(ATAG_MEM, parse_tag_mem32);

Setup.c (arch\arm\kernel):__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);

Setup.c (arch\arm\kernel):__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);

Setup.c (arch\arm\kernel):__tagtable(ATAG_SERIAL, parse_tag_serialnr);

Setup.c (arch\arm\kernel):__tagtable(ATAG_REVISION, parse_tag_revision);

Setup.c (arch\arm\kernel):__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

 

下面就挑两个函数:

static int __init parse_tag_mem32(const struct tag *tag)

{

    return arm_add_memory(tag->u.mem.start, tag->u.mem.size);

}

__tagtable(ATAG_MEM, parse_tag_mem32);

这个函数是初始化meminfo,让设备了解物理内存的情况,它将调用函数arm_add_memory,参数就是uboot传递的相应参数的mem结构,包括start成员和size成员即物理内存起始地址和内存大小,arm_add_memory这个函数把这两个参数写进meminfobank中,并更新benk个数值nr_banks。对于很多小型arm嵌入式应用,一般只有一个物理内存,也就只调用该函数一次。重中之重的meminfo就是在这里初始化的。

 

第二个函数:

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

它把uboot传递的命令行参数赋给静态全局变量default_command_line.

/*Kernel command line: console=ttymxc1,115200 video=mxcfb0:dev=lcd,AT070-WVGA,if=RGB24,bpp=32 video=mxcfb1:dev=hdmi,1920x1080M@60,if=RGB24,bpp=32 ldb=sin0 audio_codec=wm8960-24M video=mxcfb2:off calibrate=Y fec_mac=1E:ED:19:27:1A:B3 ip=none root=/dev/mmcblk0p1 rootwait*/

 

*********************************************************************************************

 

重新回到函数setup_machine_tags中,最后的代码:

setup_machine_tags函数一开始的地方有这样的定义:

    char *from = default_command_line;

/* parse_early_param needs a boot_command_line */

    strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

此句是将default_command_line复制给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;

setup.c文件开始的地方: cmd_line被定义全局变量,

static char __initdata cmd_line[COMMAND_LINE_SIZE];

所以到函数parse_early_param() 中:

/* Arch code calls this early on, or if not, just before other parsing. */

void __init parse_early_param(void)

{

    static __initdata int done = 0;

    static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];

 

    if (done)

        return;

 

    /* All fall through to do_early_param. */

    strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);

    //boot_command_line的值又给了tmp_cmdline

    parse_early_options(tmp_cmdline);

    done = 1;

}

接下来追踪到:

void __init parse_early_options(char *cmdline)

{

    parse_args("early options", cmdline, NULL, 0, do_early_param);

}

现在要关注的是parse_args函数中的参数:do_early_param

static int __init do_early_param(char *param, char *val)

{

    const struct obs_kernel_param *p;

    for (p = __setup_start; p < __setup_end; p++)

        ...

}

for循环语句中:__setup_start __setup_end,它们的定义是在链接脚本vmlinux.lds.Sinclude/asm-generic

VMLINUX_SYMBOL(__setup_start) = .;          \

        *(.init.setup)                      \

        VMLINUX_SYMBOL(__setup_end) = .;

这个定义和刚刚讲的__tagtable那个类似。

(include/linux/init.h)

#define __setup_param(str, unique_id, fn, early)            \

    static const char __setup_str_##unique_id[] __initconst \

        __aligned(1) = str; \

    static struct obs_kernel_param __setup_##unique_id  \

        __used __section(.init.setup)           \

        __attribute__((aligned((sizeof(long)))))    \

        = { __setup_str_##unique_id, fn, early }

 

#define __setup(str, fn)                    \

    __setup_param(str, fn, fn, 0)

/* NOTE: fn is as per module_param, not __setup!  Emits warning if fn

 * returns non-zero. */

#define early_param(str, fn)                    \

    __setup_param(str, fn, fn, 1)

这两个宏都会调用__setup_param,在这个宏里面有个结构体:

struct obs_kernel_param {

    const char *str;

    int (*setup_func)(char *);

    int early;

};

__setupearly_param的区别就是用early标记。

 

early_param相关函数:

Main.c (init):early_param("debug", debug_kernel);

Main.c (init):early_param("quiet", quiet_kernel);

Main.c (init):early_param("loglevel", loglevel);

 

函数的调用过程:parse_early_param-->parse_early_options-->parse_args-->do_early_param

 

*********************************************************************************************

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_38022972/article/details/81583143