接着上一篇内容,本篇我们来分析main_loop的主要内容:
main_loop主要功能有两点:
1.没有进入调试模式的话,初始化一些参数,直接跳转到内核入口处执行
2.进入调试模式,执行串口输入的命令
s = getenv ("bootcmd");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
{
printf("Booting Linux ...\n");
run_command (s, 0);
}
}
上述代码从环境变量中获取bootcmd的命令,如下
#define CONFIG_BOOTCOMMAND "nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0"
这里我们分析下bootm命令,从cmd_bootm.c中我们能够找到bootm命令对应的函数为do_bootm。
下面重点分析下这个函数。
if (argc < 2) {
addr = load_addr;
} else {
/*获取uImage版本在内存中的地址*/
addr = simple_strtoul(argv[1], NULL, 16);/*bootm 0x30007FC0,argv[1]代表kernel开始的地址*/
}
SHOW_BOOT_PROGRESS (1);
printf ("## Booting image at %08lx ...\n", addr);
/* Copy header so we can blank CRC field for re-calculation */
memmove (&header, (char *)addr, sizeof(image_header_t));/*直接得到头部*/
if (ntohl(hdr->ih_magic) != IH_MAGIC) { /*版本魔数不对*/
{
puts ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-1);
return 1;
}
}
data = (ulong)&header;/*内核地址*/
len = sizeof(image_header_t);/*版本头部*/
checksum = ntohl(hdr->ih_hcrc);/*网络序到主机序--计算版本头部的CRC*/
hdr->ih_hcrc = 0;
/*计算CRC校验码并判断*/
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
data = addr + sizeof(image_header_t);/*计算正式版本的地址*/
len = ntohl(hdr->ih_size); /*得到版本的大小*/
/*计算版本的CRC*/
if (verify) {
puts (" Verifying Checksum ... ");
if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-3);
return 1;
}
puts ("OK\n");
}
switch (hdr->ih_comp) {
.
......
.
case IH_COMP_GZIP:/*如果是压缩方式GZIP,解压版本*/
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
(uchar *)data, &len) != 0) {
puts ("GUNZIP ERROR - must RESET board to recover\n");
SHOW_BOOT_PROGRESS (-6);
do_reset (cmdtp, flag, argc, argv);
}
break;
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);/*addr 代表LOAD地址,len_ptr 版本数据正式开始的地方*/
void (*theKernel)(int zero, int arch, uint params);
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
/*得到内核的入口地址*/
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
上面获取bootargs环境变量,定义内核入口函数theKernel,函数的地址在版本头ih_ep中指明,值得注意的是参数为int,int,uint最后一个参数并不是指针类型
#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);
/*设置标记列表中的参数*/
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
setup_end_tag (bd);
#endif
目前boot向内核传递参数的常用方法便是标记列表法:
每个标记列表的结构体如下:
struct tag {
struct tag_header hdr;
union {
/*这里定义联合体,分别可以是tag_mem32,cmd_line*/
struct tag_core core;
struct tag_mem32 mem;
......
struct tag_cmdline cmdline;
......
} u;
};
struct tag_header {
u32 size; /*整个tag的大小*/
u32 tag; /*tag的类型*/
};
整个标记列表以ATAG_CORE开始,以ATAG_NONE结束,中间放入我们需要传递的参数
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);
/**/
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
这里分析下setup_memory_tags参数,这个参数用于向内核传递RAM的起始地址和大小
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);
}
CONFIG_NR_DRAM_BANKS为DRAM数目,每一个DRAM要单独定义一个TAG
紧接着调用内核入口函数
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
其中bd->bi_arch_number为ARCH_ID 在board_init中说明。至此uboot完成其任务,将控制权交给内核,退出了“”舞台”。后面几篇我会再补充一些uboot钟用到的关键知识点,完善uboot的体系