arm-linux之uboot向内核传递参数(setup_arch分析)
抛开
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
,并且对其结构体中的5个形参做了初始化赋值,第一个形参是最关键的,就是struct tag_header hdr1;
记住这个静态全局变量不重要
,继续往下看,还在
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
子程序
在内核代码源头
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
宏解析。
具体都在解析些什么内容即解析后如何操作,也都是一些和设备参数相关的诸如内存、缓存等等的信息,在这里也不详细描述了。