U-boot最主要的功能是引导OS,目前对Linux支持的相对比较好,“引导”的意义不仅仅是拷贝内核,运行内核,还要给内核kernel传递板子的相关参数,打个比方,u-boot相当于是一名专业功底深厚的 “接待员”,他会先初始化好一些外围设备,比如说串口,SDRAM、Nand Flash、MMC等,初始化这些 外围设备也是为了最终迎接Kernel的到来,比如初始化串口,自然是为了调试打印,初始化SDRAM是为了运行u-boot 本身,是u-boot的 运行的根本,初始化Nand Flash和MMC主要是为了搬迁u-boot和kernel,试想,kernel都是烧写在Nand Flash的,自然需要对应的驱动来读写Nand flash。
u-boot在引导kernel的同时,还需要给kernel传递一些参数,这就相当于交代一些关键信息,比如这块板子的内存起始地址、内存大小、串口信息、root的类型等等,kernel是个“大拿”,不要想着直接去修改kernel 代码来实现这些功能,这不现实,也不科学,所以最好是按照kernel的接口协议,传递板子信息参数给kernel。
u-boot的最后一句代码如下:
theKernel (0, machid, bd->bi_boot_params);
这条代码就是引导内核kernel的终极代码,向内核传递了3个参数,分别是0,machid、bd->bi_boot_params,去掉那个“0”其实就2个参数,写到这里你可以能怀疑,难道就这两个参数吗?答案是也 对也不对,对的是,确实只有这两个参数,不对的是,这个bd->bi_boot_params是个指针,也就是是个地址,它指向了所有 传递给内核的 参数,指针这个 东西就没谱了, 后面可以延伸n多个地址,也就是n个参数。
kernel规定,如果想要传递参数给kernel,就需要将参数保存在struct tag类型的数据结构里,可以有n 多个,tag的数据 结构定义如下:
///include/asm-arm/setup.h
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;
} u;
};
//其中tag_header的定义如下:
struct tag_header {
u32 size;
u32 tag;
};
从上面的定义可知,tag_header作为“头”,表示了这个tag的类型和大小,而后面的“共同体”代表了 几种不同的定义,共同体的作用是为了找“ 共同”,然后取最大空间公约数,上面的所有tag类型,并不是要全部赋值传递给kernel,而是根据移值板子的配置(mx28_evk.h),比如最常用,也是最基本的几种tag:tag_core、tag_mem32、tag_cmdline,假设 我们就只传递这几种tag,那么我们就需要在thekernel函数之前对这几种tag 进行初始化,去掉那个复杂的共同体表示方式,这三种tag的内容就如下:
//tag_core
struct tag {
struct tag_header hdr;
struct tag_core core;
};
//tag_mem32
struct tag {
struct tag_header hdr;
struct tag_core core;
};
//tag_cmdline
struct tag {
struct tag_header hdr;
struct tag_cmdline cmdline;
};
我们只需要把这3个tag顺序依次的存放到前面说的bd->bi_boot_params开始地址处即可。而且linux规定,tag列表的第一项必须是tag_core,最后一项必须是ATAG_NENE,自然我们也要遵从这个“协议规定”,这没什么好说的,不遵守,linux就不认。我们来看下这3个tag,u-boot是如何初始化的。
前面讲到thekernel是最后一条代码,那么初始化tag必然也是要在这条代码之前的,我们在/lib_arm/bootm.c文件中可以找到引导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);
//环境变量-命令行参数bootargs获取
char *commandline = getenv ("bootargs");
if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
return 1;
theKernel = (void (*)(int, int, uint))images->ep;
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
//安装tag_core
setup_start_tag (bd);
//安装tag_mem32
setup_memory_tags (bd);
//安装tag_cmdline
setup_commandline_tag (bd, commandline);
//tag操作结束
setup_end_tag (bd);
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
cleanup_before_linux ();
theKernel (0, machid, bd->bi_boot_params);
/* does not return */
return 1;
}
从上面的代码可以知道,总共操作了4种tag,分别为tag_core、tag_mem32、tag_cmdline、tag end(就是结束),
我们来分别看这个4中tag的操作函数:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;
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);
}
#ifdef CONFIG_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);
}
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */
static void setup_commandline_tag (bd_t *bd, char *commandline)
{
char *p;
if (!commandline)
return;
/* eat leading white space */
for (p = commandline; *p == ' '; p++);
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
}
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
从上面的 代码及顺序就可以很好的验证之前的分析,先 从ATAG_CORE开始,以ATAG_NONE结束,简单分析可知:
(1)ATAG_CORE:将bd->bi_boot_params赋值到tag_core,以便告诉内核从哪里开始找参数。
(2)ATAG_MEM:主要是告诉内核sdram的大小和开始地址。
(3)ATAG_CMDLINE:命令行参数,bootargs的赋值,这也是最重要的参数,kernel根据这个命令行参数来进行配置,这一点会再专门写一篇文章来分析。
(4)ATAG_NONE:结束,所以tag类型为ATAG_NONE,大小为0.