下面我们就以引导linux内核的命令bootm为例,说一下u-boot到linux过渡及参数传递的整个过程。
common/Cmd_bootm.c:
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory/n",
"[addr [arg ...]]/n - boot application image stored in memory/n"
"/tpassing arguments 'arg ...'; when booting a Linux kernel,/n"
"/t'arg' can be the address of an initrd image/n"
);
从注释中可以看到这个命令的作用是从memory中引导application,我们这里就是引导linux内核,前提就是linux kernel已经在memory中了。这条命令的处理函数就是do_bootm()。
common/Cmd_bootm.c:
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
………
/*看到了吧,u-boot是如何支持多个操作系统引导的*/
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify); /*linux引导的函数*/
break;
case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#ifdef CONFIG_LYNXKDI
case IH_OS_LYNXOS:
do_bootm_lynxkdi (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#if (CONFIG_COMMANDS & CFG_CMD_ELF)
case IH_OS_VXWORKS:
do_bootm_vxworks (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_QNX:
do_bootm_qnxelf (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif /* CFG_CMD_ELF */
#ifdef CONFIG_ARTOS
case IH_OS_ARTOS:
do_bootm_artos (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif
}
………
}
do_bootm()函数很大,上面没有全部列出来, 但是大部分代码都很简单,就是做一些检测,看是否是正确的image,做的检测有:magic number, crc校验,体系结构是否正确等等,如果是压缩的还会先解压缩(这里的压缩和linux编译出来的压缩没关系,应该是application是否被gzip等压缩过的概念),最后如果是linux kernel则会调用do_bootm_linux()。
我们一段段的分析do_bootm_linux()
lib_arm/Armlinux.c:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
DECLARE_GLOBAL_DATA_PTR;
ulong len = 0, checksum;
ulong initrd_start, initrd_end;
ulong data;
void (*theKernel)(int zero, int arch, uint params); /*定义一个函数指针*/
image_header_t *hdr = &header; /*linux image的头*/
bd_t *bd = gd->bd; /*用来存放传递给linux kernel参数的地址*/
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);/*指向linux kernel的入口*/
………
}
继续
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
………
/*
* Check if there is an initrd image
*/
if (argc >= 3) { /*存在initrd image*/
SHOW_BOOT_PROGRESS (9);
addr = simple_strtoul (argv[2], NULL, 16); /*获取地址*/
printf ("## Loading Ramdisk Image at %08lx .../n", addr);
/* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (addr, sizeof (image_header_t),
(char *) &header);
} else
#endif
memcpy (&header, (char *) addr,
sizeof (image_header_t)); /*获取initrd的头*/
if (ntohl (hdr->ih_magic) != IH_MAGIC) { /*合法性检测*/
printf ("Bad Magic Number/n");
SHOW_BOOT_PROGRESS (-10);
do_reset (cmdtp, flag, argc, argv);
}
data = (ulong) & header;
len = sizeof (image_header_t);
checksum = ntohl (hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (char *) data, len) != checksum) {/*合法性检测*/
printf ("Bad Header Checksum/n");
SHOW_BOOT_PROGRESS (-11);
do_reset (cmdtp, flag, argc, argv);
}
SHOW_BOOT_PROGRESS (10);
print_image_hdr (hdr);
data = addr + sizeof (image_header_t);
len = ntohl (hdr->ih_size);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
data = CFG_LOAD_ADDR;
}
#endif
if (verify) { /*合法性检测*/
ulong csum = 0;
printf (" Verifying Checksum ... ");
csum = crc32 (0, (char *) data, len);
if (csum != ntohl (hdr->ih_dcrc)) {
printf ("Bad Data CRC/n");
SHOW_BOOT_PROGRESS (-12);
do_reset (cmdtp, flag, argc, argv);
}
printf ("OK/n");
}
SHOW_BOOT_PROGRESS (11);
if ((hdr->ih_os != IH_OS_LINUX) ||
(hdr->ih_arch != IH_CPU_ARM) ||
(hdr->ih_type != IH_TYPE_RAMDISK)) {/*合法性检测*/
printf ("No Linux ARM Ramdisk Image/n");
SHOW_BOOT_PROGRESS (-13);
do_reset (cmdtp, flag, argc, argv);
}
#if defined(CONFIG_B2) || defined(CONFIG_EVB4510)
/*
*we need to copy the ramdisk to SRAM to let Linux boot
*/
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */
/*
* Now check if we have a multifile image
*/
} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
ulong tail = ntohl (len_ptr[0]) % 4;
int i;
SHOW_BOOT_PROGRESS (13);
/* skip kernel length and terminator */
data = (ulong) (&len_ptr[2]);
/* skip any additional image length fields */
for (i = 1; len_ptr[i]; ++i)
data += 4;
/* add kernel length, and align */
data += ntohl (len_ptr[0]);
if (tail) {
data += 4 - tail;
}
len = ntohl (len_ptr[1]);
} else {
/*
* no initrd image
*/
SHOW_BOOT_PROGRESS (14);
len = data = 0;
}
………
}
上面的代码主要是检查是否有initrd image,如有则记住它在内存的地址。
继续
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
………
if (data) { /*保存initrd的地址*/
initrd_start = data;
initrd_end = initrd_start + len;
} else {
initrd_start = 0;
initrd_end = 0;
}
SHOW_BOOT_PROGRESS (15);
debug ("## Transferring control to Linux (at address %08lx) .../n",
(ulong) theKernel);
/*下面这些实际上就是设置好参数*/
#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_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);
#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);
#endif
/* we assume that the kernel is in place */
printf ("/nStarting kernel .../n/n");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux (); /*进入linux前,为linux做好准备*/
/*正式跳转到linux kernel*/
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
上面这部分就是为进入linux准备好环境,然后就正式把控制权交给linux kernel。
我们以setup_serial_tag为例看下是如何设置参数的
lib_arm/Armlinux.c:
void setup_serial_tag (struct tag **tmp)
{
struct tag *params = *tmp;
struct tag_serialnr serialnr;
void get_board_serial(struct tag_serialnr *serialnr);
get_board_serial(&serialnr);
params->hdr.tag = ATAG_SERIAL;
params->hdr.size = tag_size (tag_serialnr);
params->u.serialnr.low = serialnr.low;
params->u.serialnr.high= serialnr.high;
params = tag_next (params);
*tmp = params;
}
上面这个函数传进来的参数是¶ms,而params在setup_start_tag()里已经被初始化为
params = (struct tag *) bd->bi_boot_params;
因此该函数对params里变量的赋值实际上就是在对bd->bi_boot_params下赋值,因此bi_boot_params地址处存放了所有相关的参数。最后这个地址被传给了linux kernel。
接下来看cleanup_before_linux ()
cpu/arm920t/cpu.c:
int cleanup_before_linux (void)
{
/*
* this function is called just before we call linux
* it prepares the processor for linux
*
* we turn off caches etc ...
*/
unsigned long i;
disable_interrupts (); /*关中断*/
/* turn off I/D-cache */
/*关掉数据和指令的cache*/
asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i));
i &= ~(C1_DC | C1_IC);
asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i));
/* flush I/D-cache */ /*复位数据和指令的cache*/
i = 0;
asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));
return (0);
}
呵呵,有人要问了为什么要做这个操作,其实这是linux kernel规定的,请看:
arch/arm/kernel/head.S:
/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr.
*
* This code is mostly position independent, so if you link the kernel at
* 0xc0008000, you call this at __pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for the complete list of machine
* numbers for r1.
*
* We're trying to keep crap to a minimum; DO NOT add any machine specific
* crap here - that's what the boot loader (or in extreme, well justified
* circumstances, zImage) is for.
*/
分析到这里整个u-boot的流程大概就分析完了, 至于
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
它是如何按什么顺序把参数放入哪个寄存器的,就请自行分析吧,可以参考APCS规范。
有时间在分析linux启动顺序, 这样应该可以有个衔接。