抛开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.4、stext分析之打开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"段中链接),并赋初值,赋的值就是宏函数定义时的参数tag和fn。
说白了就是,以__tagtable(tag, fn)形式定义的宏,实际是在".taglist.init"段中,创建struct tagtable的静态变量,并赋初值给这个变量,赋的值就是这个宏的两个参数tag和fn。
在arch/arm/mach-XXX/core.c、arch/arm/mm/init.c、arch/arm/kernel/setup.c文件中,定义了很多__tagtable(XXX,XXX)这样的宏,这些宏干什么的?就是解析uboot传递给内核的参数用的(当然不仅它们解析,后面还有别的代码解析),现在回到parse_tag函数,应该很好理解了,它对传递进来的参数,利用__tagtable_begin和__tagtable_end,使用所有定义的__tagtable(tag, fn)对其进行遍历,一旦发现__tagtable(tag, fn)的tag和传递进来的参数的参数头的tag(即struct tags_header的tag成员)一样,就用该__tagtable(tag, fn)的fn即解析函数进行解析,这里的marvell设备的实现一共定义了11个这样的__tagtable(tag, fn),就不一列出了,这里具体描述两个比较重要的:
parse_tag_mem32:
这个是初始化meminfo即让内核了解设备的物理内存情况的,它将调用函数arm_add_memory,参数就是uboot传递的相应参数的mem结构,包括start成员和size成员即物理内存起始地址和内存大小,arm_add_memory这个函数比较简单,它把这两个参数写进meminfo的bank中,并更新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所传参数的接收、解析的机制和方法,另外需要了解下所解析和操作的内容,尤其对于一些重要参数典型如内存参数的解析和操作需要细致理解。