005@ arm-linux之uboot向内核传递参数(setup_arch分析)

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 子程序
( 可参考“ 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所传参数的接收、解析的机制和方法,

另外需要了解下所解析和操作的内容,尤其对于一些重要参数典型如内存参数的解析和操作需要细致理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值