arm-linux之uboot向内核传递参数

抛开uboot不谈,先看看uboot给内核传递的参数是什么样的东西,在arch/arm/kernel/setup.h文件中的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;

               /*

        * Marvell specific

      */

              struct tag_mv_uboot     mv_uboot;

             // board info

              struct tag_board_info board_info;

              } u;

};

Uboot传递给内核的参数,都是一些对一个个的设备参数的描述,用于对内核进行相应的初始化,参数的具体内容暂时无需关心,有个大致印象就行,下面一步步看内核是怎么接收uboot参数的,在setup_arch函数中首先定义struct tag *型指针变量tags

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

tags是重点,它就是内核接收uboot参数的东西!

init_tags是个全局静态变量,就在本文件(arch/arm/kernel/setup.c)定义如下:

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, PHYS_OFFSET },

       { 0, ATAG_NONE }

};

在定义结构体init_tags的同时声明了静态全局变量init_tags记住这个静态全局变量不重要,继续往下看,还在setup_arch函数中接下来几行:

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传递,else if说明是由内核部分的代码(即代码写死,不是从uboot),这个是重点:

先看看这个__atags_pointer是什么:

__atags_pointer,定义在汇编文件arch/arm/kernel/head-common.S__switch_data子程序(可参考“ARM架构内核启动分析-head.S(1.4stext分析之打开MMU并跳到start kernel”一文)),在内核代码源头stext运行前,由arm寄存器R2保存要传递给内核的参数的地址,当stext运行到子程序__switch_data时,定义变量__atags_pointer保存这个地址,即__atags_pointer保存了uboot要传递给内核的参数的地址,所以这里让tags获取__atags_pointer的转换后的虚拟地址,即可访问。

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

一般来说还是从__atags_pointer传递,即从uboot传递的几率比较大,我手头这个marvell设备就是如此,毕竟在内核代码的machine_desc变量写死本质还是uboot里存放这些参数的物理地址,一旦uboot里这些参数的物理地址变动,同时还要改machine_desc变量的这个值,不如自动传递方便。

搞明白了参数的传递,下面看内核代码如何使用这些参数,接着setup_arch函数往下看,如下:

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

       convert_to_tag_list(tags);

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

       tags = (struct tag *)&init_tags;

结合本文最前面的struct tags结构体,它的第一个成员如下:

struct tag_header hdr

记住,struct tag_header的成员tag,如果不等于宏ATAG_CORE的值(arch/arm/kernel/setup.h定义),说明是旧式的参数,需要转换成新格式的参数,所以调用函数convert_to_tag_list;如果转换后依然是旧格式的,那么就没法使用这个参数了,改为使用默认参数,就是本文开始时描述的那个不重要的init_tags静态全局变量。

convert_to_tag_list这个函数内容可以不看,因为一个正常的uboot是不会传递旧格式的参数,这里重在理解道理即可。

后面的fixup部分,其实可以不关注了,fixup用于内核代码固定的写死meminfo,而不是由uboot传递参数配置meminfo,应该说很少有使用fixup成员写死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肯定为0,继续往下看,save_atags将把tags里的内容拷贝给全局变量atags_copy,重点是下面的parse_tags

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

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 = .;

可见是卡住了“.taglist.init”段的全部内容,那这个段里是什么东西呢?在这里,arch/include/asm/setup.h文件中,有这么些内容:

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

#define __tagtable(tag, fn) \

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

第一行的意思是:宏__tag,定义为__used __attribute__((__section__(".taglist.init")))

第二、三行的意思是:定义宏__tagtable(tag, fn)static struct tagtable __tagtable_##fn __tag = { tag, fn },意思是:在".taglist.init"段中,创建struct tagtable的静态变量__tagtable_##fn(fn是什么由参数指定,后面的__tag起变量描述符的作用,它真正指定了这个静态变量是在".taglist.init"段中链接),并赋初值,赋的值就是宏函数定义时的参数tagfn

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

arch/arm/mach-XXX/core.carch/arm/mm/init.carch/arm/kernel/setup.c文件中,定义了很多__tagtable(XXXXXX)这样的宏,这些宏干什么的?就是解析uboot传递给内核的参数用的(当然不仅它们解析,后面还有别的代码解析),现在回到parse_tag函数,应该很好理解了,它对传递进来的参数,利用__tagtable_begin__tagtable_end,使用所有定义的__tagtable(tag, fn)对其进行遍历,一旦发现__tagtable(tag, fn)tag和传递进来的参数的参数头的tag(struct tags_headertag成员)一样,就用该__tagtable(tag, fn)fn即解析函数进行解析,这里的marvell设备的实现一共定义了11个这样的__tagtable(tag, fn),就不一列出了,这里具体描述两个比较重要的:

parse_tag_mem32

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

parse_tag_cmdline

它把uboot传递的“命令行参数”赋给静态全局变量default_command_line,这个变量是干什么用的?它很重要,先看看这里的marvell设备的情况:

ttyS0,115200 ubi.mtd=3 root=ubi0:rootfs rootfstype=ubifs mtdparts=nand_flash:0x200000@0x0(uboot)ro,0x200000@0x200000(env)rw,0x500000@0x400000(kernel0)ro,0x2800000@0x900000(rootfs0)ro,0x500000@0x3100000(kernel1)ro,0x2800000@0x3600000(rootfs1)ro,0x2200000@0x5e00000(config)rw rw)

想必会明白这个default_command_line是干什么的了,这里只是拷贝到这个变量中去,后面还有其他代码进一步解析它。

看完这一部分,接下来是另一部分的解析,接着setup_arch函数往下看:

parse_cmdline(cmdline_p, from);

form指针变量在setup_arch函数一开始就指向了default_command_line,到这应该能感觉到现在要解析default_command_line了,看parse_cmdline的实现:

无需仔细看里边关于用空格定位每一个参数等细节,重点是看它是由谁来解析的,可以看到两个变量__early_begin__early_end,和上面类似,故伎重演,在vmlinux.lds.S中它俩卡住了“.early_param.init”段,这个段所链接的内容同样是在arch/arm/kernel/setup.h中规定,道理完全一样具体就不描述了,最终是由__early_param宏解析。具体都在解析些什么内容即解析后如何操作,也都是一些和设备参数相关的诸如内存、缓存等等的信息,在这里也不详细描述了。

总之,对于uboot向内核传递参数,需要理解的一个是内核对uboot所传参数的接收、解析的机制和方法,另外需要了解下所解析和操作的内容,尤其对于一些重要参数典型如内存参数的解析和操作需要细致理解。

你可能感兴趣的:(水滴石穿)