分类: LINUX
2012-10-15 14:51:50
Uboot和kernel之间有时候需要参数传递,比如:uboot中通过i2c读取产品id,如果kernel中也需要根据产品id区分型号,那么就会产生一个问题:uboot中可以通过给订好的i2c接口读取,但kernel中没有做好的接口,尤其是驱动程序中通过i2c读取信息,这就要自己在kernel中实现i2c的接口,这样可能会与i2c驱动冲突,引起很多不确定的问题。所以通过uboot把产品型号这个参数传到kernel中,就解决了这个问题。本文以uboot向kernel传递产品id这个参数来示例。
u-boot会给Linux Kernel传递很多参数,而Linux kernel也会读取和处理这些参数。两者之间通过struct tag来传递参数。U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。
1、u-boot
./common/cmd_bootm.c文件中,bootm命令对应的do_bootm函数,当分析uImage中信息发现OS是Linux时,调用./lib_arm/bootm.c文件中的do_bootm_linux函数来启动Linux kernel。
在do_bootm_linux函数中
以下是简化后的函数
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number;//获取产品型号
void (*theKernel)(int zero, int arch, uint params);
char *commandline = getenv ("bootargs");//获取命令行参数
theKernel = (void (*)(int, int, uint))images->ep;//获取kernel入口点地址
s = getenv ("machid");
setup_start_tag (bd);//设置tag起始标志
setup_serial_tag (¶ms);//设置串口参数
setup_revision_tag (¶ms);设置版本参数
//user add
setup_product_tag(¶ms);设置产品id参数,这个参数时用户自家添加的
//user end
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_videolfb_tag ((gd_t *) gd);
setup_end_tag (bd);//设置tags结束标志
theKernel (0, machid, bd->bi_boot_params);
/* does not return */
return 1;
}
对上面这个函数有如下解释:
首先,uboot把要传递的参数放到内存中的一个指定的地址,kernel需要从这个地址中取出参数,在board_init()函数中,这个参数 bd->bi_boot_params被赋值为:
#define LINUX_BOOT_PARAM_ADDR 0x80000100
gd->bd->bi_boot_params = LINUX_BOOT_PARAM_ADDR;
最终theKernel (0, machid, bd->bi_boot_params);通过第三个参数传递给kernel,因为theKernel被赋值为kernel入口点,所以这个函数就是启动kernel并且传递三个参数
static struct tag *setup_start_tag(struct tag *params)
{
params->hdr.tag = ATAG_CORE;//此标志表示这是tag的开始
params->hdr.size = tag_size(tag_core);//占用空间大小
params->u.core.flags = 0;
params->u.core.pagesize = 4096;
params->u.core.rootdev = 0;
return tag_next(params);
}
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size)) //指向下一个参数的开始地址
其次,添加自己的参数:
void setup_product_tag(struct tag **in_params)
{
params->hdr.tag = ATAG_G_PRODUCT;//参数类型的标志
params->hdr.size = tag_size (tag_g_product);//占用空间大小
params->u.productid.product_id = g_product;//设置产品id, g_product中存着从i2c读出的产品型号
params = tag_next (params);//指向下一个参数地址,这样就把自己的参数存到了tag列表中了,即实现了uboot的参数传递
}
定义自己的参数类型和数据结构,自己添加的数据类型和结构需要在kernel中也对应添加
#define ATAG_G_PRODUCT 0x5441000c
struct tag_g_product {
u32 product_id;
};
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;
struct tag_acorn acorn;
struct tag_memclk memclk;
//usr add
struct tag_g_product productid;//添加自己的数据类型
//end add
} u;
}
设置结束tag,表明参数已经结束了
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;//结束标记
params->hdr.size = 0;
}
2、对应的kernel中:
在setup.c中的setup_arch()函数中调用了parse_tags(tags);这个函数,此函数完成解析参数列表中的参数并调用对应的处理函数。
遍历每个tag,然后调用parse_tag()函数
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);
}
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++)//遍历tag链表
if (tag->hdr.tag == t->tag) {//判断标志
t->parse(tag);//调用相应的解析函数
break;
}
return t < &__tagtable_end;
}
//把自己的处理函数和数据结构添加到tag列表中
static int __init parse_tag_product_id(const struct tag *tag)//对应的处理函数
{
g_product = tag->u.productid.product_id;//把tags中的产品id赋给kernel中的全局变量
return 0;
}
__tagtable(ATAG_G_PRODUCT, parse_tag_product_id);
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn } //把自定义的标记和对应的处理函数添加到结构体中
#define __tag __used __attribute__((__section__(".taglist.init")))//把生成的tag编译到taglist.init段中,
__tagtable_begin,定义在arch/arm/vmlinux.lds,vmlinux.lds这是编译器用的文件
__arch_info_begin = .;
*(.arch.info)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist)
__tagtable_end = .;
*(.data.init)
. = ALIGN(16);
由此可见,编译器链接脚本把tag列表开始位置__tagtable_begin 指向了.taglist开始,就是我们通过__tagtable()宏生成的列表,结束位置__tagtable_end指向了.taglist的下一个段,所以parse_tag()这个函数中就是在遍历这个列表
//usr add 用户自己定义的类型和数据结构,应该与uboot中的定义一致
#define ATAG_G_PRODUCT 0x5441000c
struct tag_g_product {
u32 product_id;
};
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;
struct tag_acorn acorn;
struct tag_memclk memclk;
//usr add
struct tag_g_product productid;
//usr add
} u;
};