U-boot会给Linux Kernel传递很多参数,如:串口,RAM,videofb等。而Linux kernel也会读取和处理这些参数。两者之间通过struct tag来传递参数。
U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。
本文主要以U-boot传递RAM和Linux kernel读取RAM参数为例进行说明。
setenv bootcmd ' mmc rescan 0;fatload mmc 0 0x81000000 samplelogo_720p.bmp; logo on 0x81000000 0xA0000000 0x81600000 40 60;fatload mmc 0 0x81000000 uImage;bootm 0x81000000' setenv bootargs 'console=ttyO2,115200n8 rootwait rw mem=128M@0x80000000 mem=384M@0xc0000000 notifyk.vpssm3_sva=0xBEE00000 vram=20M root=/dev/nfs nfsroot=192.168.0.188:/home/m/rfs_816x/ ip=dhcp noinitrd' saveenv
1、u-boot给kernel传RAM参数
./common/cmd_bootm.c文件中(指Uboot的根目录),bootm命令对应的do_bootm函数,当分析uImage中信息发现OS是Linux时,调用./lib_arm/bootm.c文件中的do_bootm_linux函数来启动Linux kernel。
在do_bootm_linux函数中:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
......
#if defined (CONFIG_SETUP_MEMORY_TAGS) ||
defined (CONFIG_CMDLINE_TAG) ||
defined (CONFIG_INITRD_TAG) ||
defined (CONFIG_SERIAL_TAG) ||
defined (CONFIG_REVISION_TAG) ||
defined (CONFIG_LCD) ||
defined (CONFIG_VFD)
setup_start_tag (bd); //初始化tag结构体开始
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd); //设置RAM参数
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd); //初始化tag结构体结束
#endif
......
......
theKernel (0, machid, bd->bi_boot_params);
//传给Kernel的参数= (struct tag *)型的bd->bi_boot_params
//bd->bi_boot_params在board_init 函数中初始化,如对于at91rm9200,初始化在at91rm9200dk.c的board_init中进行:bd->bi_boot_params=PHYS_SDRAM + 0x100;
//这个地址也是所有taglist的首地址,见下面的setup_start_tag函数
}
对于setup_start_tag和setup_memory_tags函数说明如下。
函数setup_start_tag也在此文件中定义,如下:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;
//初始化(struct tag *)型的全局变量params为bd->bi_boot_params的地址,之后的setup tags相关函数如下面的setup_memory_tags就把其它tag的数据放在此地址的偏移地址上。
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
RAM相关参数在bootm.c中的函数setup_memory_tags中初始化:
static void setup_memory_tags (bd_t *bd)
{
int i;
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
} //初始化内存相关tag
}
2、Kernel读取U-boot传递的相关参数
对于Linux Kernel,ARM平台启动时,先执行arch/arm/kernel/head.S,此文件会调用arch/arm/kernel/head- common.S和arch/arm/mm/proc-arm920.S中的函数,并最后调用start_kernel:
......
b start_kernel
......
init/main.c中的start_kernel函数中会调用setup_arch函数来处理各种平台相关的动作,包括了u-boot传递过来参数的分析和保存:
start_kernel()
{
......
setup_arch(&command_line);
......
}
调用 setup_arch()函数进行与体系结构相关的第一个初始化工作; 对不同的体系结构来说该函数有不同的定义。对于 ARM 平台而言,该函数定义在arch/arm/kernel/Setup.c。它首先通过检测出来的处理器类型进行处理器内核的初始化,然后通过 bootmem_init()函数根据系统定义的 meminfo 结构进行内存结构的初始化,最后调用paging_init()开启 MMU,创建内核页表,映射所有的物理内存和 IO空间。 函数实现在 arch/arm/kernel/Setup.c 对内核参数的解析:
void __init setup_arch(char **cmdline_p) { struct tag *tags = (struct tag *)&init_tags; //定义了一个默认的内核参数列表 struct machine_desc *mdesc; char *from = default_command_line; unwind_init(); //本架构中没有用 setup_processor(); //汇编的CPU初始化部分 mdesc = setup_machine(machine_arch_type); machine_name = mdesc->name; if (mdesc->soft_reboot) reboot_setup("s"); if (__atags_pointer) tags = phys_to_virt(__atags_pointer); else if (mdesc->boot_params) tags = phys_to_virt(mdesc->boot_params); //由于MMU单元已打开,此处需要而boot_params是物理地址,需要转换成虚拟地址才能访问,因为此时CPU访问的都是虚拟地址 /* * If we have the old style parameters, convert them to * a tag list. */ //内核参数列表第一项必须是ATAG_CORE类型 if (tags->hdr.tag != ATAG_CORE) //如果不是,则需要转换成新的内核参数类型,新的内核参数类型用下面struct tag结构表示 convert_to_tag_list(tags); //此函数完成新旧参数结构转换 if (tags->hdr.tag != ATAG_CORE) //如果没有内核参数 tags = (struct tag *)&init_tags; //则选用默认的内核参数 if (mdesc->fixup) mdesc->fixup(mdesc, tags, &from, &meminfo); //用内核参数列表填充meminfo if (tags->hdr.tag == ATAG_CORE) { if (meminfo.nr_banks != 0) squash_mem_tags(tags); save_atags(tags); parse_tags(tags); //解析内核参数列表,然后调用内核参数列表的处理函数对这些参数进行处理。比如,如果列表为命令行,则最终会用parse_tag_cmdlin函数进行解析,这个函数用_tagtable编译连接到了内核里 }
//下面是记录内核代码的起始,结束虚拟地址 init_mm.start_code = (unsigned long) _text; init_mm.end_code = (unsigned long) _etext; init_mm.end_data = (unsigned long) _edata; init_mm.brk = (unsigned long) _end; //下面是对命令行的处理,刚才在参数列表处理parse_tag_cmdline函数已把命令行拷贝到了from空间 memcpy(boot_command_line, from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1] = '