u-boot是一种bootloader,它其实就是一段单机程序,在系统上电时自动执行,初始化硬件设备,准备好软件环境,就是为了达到其终极目的——启动内核。
本文记录了以u-boot启动运行在ARM上的Linux为例,拷贝内核镜像文件到SDRAM后,调用do_bootm的过程。话不多说,先上软件流程图:
一、内核镜像文件的检查
内核镜像文件拷贝到SDRAM上之后,需要对镜像文件进行检查,包括Image Magic Number,镜像文件头CRC,内核内容CRC,是否支持当前的CPU,是否需要解压,将内核内容拷贝到内核启动地址。这一些列的操作都是通过common/cmd_bootm.c中的do_bootm()函数来实现的。
@cmd_tbl_t *cmdtp: do_bootm的命令结构体指针
@argc: 参数个数,以”bootm 0x30007FC0”为例,argc = 2
@argv:存放参数,argv[0] = “bootm”, argv[1] = “0x30007FC0”
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong iflag;
ulong addr;
ulong data, len, checksum;
ulong *len_ptr;
uint unc_len = CFG_BOOTM_LEN;
int i, verify;
char *name, *s;
int (*appl)(int, char *[]);
image_header_t *hdr = &header;
// 检索环境变量"verify",若检索成功则verify = 0,否则为1
s = getenv ("verify");
verify = (s && (*s == 'n')) ? 0 : 1;
// 若参数个数小于2,表明没有传入内核镜像加载地址,则使用默认的加载地址load_addr
if (argc < 2) {
addr = load_addr;
} else {
addr = simple_strtoul(argv[1], NULL, 16);
}
SHOW_BOOT_PROGRESS (1);
printf ("## Booting image at %08lx ...\n", addr);
// 拷贝镜像文件的文件头到header。
memmove (&header, (char *)addr, sizeof(image_header_t));
// check the Image Magic Number
if (ntohl(hdr->ih_magic) != IH_MAGIC) {
{
puts ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-1);
return 1;
}
}
SHOW_BOOT_PROGRESS (2);
// 将header的地址值赋给data,长度赋给len
data = (ulong)&header;
len = sizeof(image_header_t);
// 读取镜像文件头hcrc的值
checksum = ntohl(hdr->ih_hcrc);
hdr->ih_hcrc = 0;
// 对镜像文件头做CRC校验。
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
SHOW_BOOT_PROGRESS (3);
/* for multi-file images we need the data part, too */
// 显示镜像文件头信息。
print_image_hdr ((image_header_t *)addr);
// 计算内核的入口地址值,赋给data,内核的大小赋给len
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");
}
SHOW_BOOT_PROGRESS (4);
// len_ptr指向内核的入口地址。
len_ptr = (ulong *)data;
if (hdr->ih_arch != IH_CPU_ARM)
{
printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
SHOW_BOOT_PROGRESS (-4);
return 1;
}
SHOW_BOOT_PROGRESS (5);
// 获取镜像文件的类型,若是内核则 name = "Kernel Image";
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
name = "Standalone Application";
/* A second argument overwrites the load address */
if (argc > 2) {
hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
}
break;
case IH_TYPE_KERNEL:
name = "Kernel Image";
break;
case IH_TYPE_MULTI:
name = "Multi-File Image";
len = ntohl(len_ptr[0]);
/* OS kernel is always the first image */
data += 8; /* kernel_len + terminator */
for (i=1; len_ptr[i]; ++i)
data += 4;
break;
default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
SHOW_BOOT_PROGRESS (-5);
return 1;
}
SHOW_BOOT_PROGRESS (6);
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts();
// 判断镜像文件是否为压缩文件,若为压缩文件,解压缩。
switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == data) {
printf (" XIP %s ... ", name);
} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data;
printf (" Loading %s ... ", name);
while (l > 0) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;
case IH_COMP_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;
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2:
printf (" Uncompressing %s ... ", name);
/*
* If we've got less than 4 MB of malloc() space,
* use slower decompression algorithm which requires
* at most 2300 KB of memory.
*/
i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
&unc_len, (char *)data, len,
CFG_MALLOC_LEN < (4096 * 1024), 0);
if (i != BZ_OK) {
printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
SHOW_BOOT_PROGRESS (-6);
udelay(100000);
do_reset (cmdtp, flag, argc, argv);
}
break;
#endif /* CONFIG_BZIP2 */
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", hdr->ih_comp);
SHOW_BOOT_PROGRESS (-7);
return 1;
}
puts ("OK\n");
SHOW_BOOT_PROGRESS (7);
// 对于内核镜像文件,在这里直接跳到下一步操作。
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
if (iflag)
enable_interrupts();
/* load (and uncompress), but don't start if "autostart"
* is set to "no"
*/
if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
char buf[32];
sprintf(buf, "%lX", len);
setenv("filesize", buf);
return 0;
}
appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
(*appl)(argc-1, &argv[1]);
return 0;
case IH_TYPE_KERNEL:
case IH_TYPE_MULTI:
/* handled below */
break;
default:
if (iflag)
enable_interrupts();
printf ("Can't boot image type %d\n", hdr->ih_type);
SHOW_BOOT_PROGRESS (-8);
return 1;
}
SHOW_BOOT_PROGRESS (8);
// 到了这里说明镜像文件是内核文件,判断是什么类型的内核,然后调用相应的启动函数,这里是Linux OS,所以调用do_bootm_linux
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
// 若bootm传入的命令参数为"bootm 0x30007FC0",则
// cmdtp = store the address where the cmd_bootm struct table.
// flag = 0
// argc = 2
// argv[0] = "bootm"
// argv[1] = "0x30007FC0"
// addr = 0x30007FC0
// len_ptr = 0x30008000
// verify = 1
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
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
}
SHOW_BOOT_PROGRESS (-9);
#ifdef DEBUG
puts ("\n## Control returned to monitor - resetting...\n");
do_reset (cmdtp, flag, argc, argv);
#endif
return 1;
}
二、达成终极目标——启动内核
在确认镜像文件无误,拷贝内核到调用入口地址处(如果需要的话)后,调用do_bootm_linux(),设置u-boot传给内核的参数并为启动内核做一些初始化,包括关闭中断,关闭MMU,关闭数据cache,设置CPU为SVC模式,设置R0~R2寄存器的值,最终跳到内核入口地址调用内核。
由于u-boot和内核的交互是单向的,传递参数的办法只有一个:u-boot将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。
// 若bootm传入的命令参数为"bootm 0x30007FC0",则
// cmdtp = store the address where the cmd_bootm struct table.
// flag = 0
// argc = 2
// argv[0] = "bootm"
// argv[1] = "0x30007FC0"
// addr = 0x30007FC0
// len_ptr = 0x30008000
// verify = 1
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],ulong addr, ulong *len_ptr, int verify)
{
ulong len = 0, checksum;
ulong initrd_start, initrd_end;
ulong data;
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
bd_t *bd = gd->bd;
#ifdef CONFIG_CMDLINE_TAG
// 获取OS的启动参数: 若 bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
// 则commandline = "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"
char *commandline = getenv ("bootargs");
#endif
// theKernel指向内核的入口地址
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
/*
* no initrd image
*/
SHOW_BOOT_PROGRESS (14);
len = data = 0;
#ifdef DEBUG
if (!data) {
printf ("No initrd\n");
}
#endif
if (data) {
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);
// 设置传递给内核的参数 TLV格式
#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 (); // cancled by www.100ask.net
}
#endif
// 在调用内核之前,做一些必要的初始化。
cleanup_before_linux ();
// 通过入参,设置CPU寄存器
// R0 = 0
// R1 = 机器类型ID
// R2 = 启动参数标记列表在RAM中起始基地址
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
// 设置起始TAG参数,参数列表的其实地址为 bd->bi_boot_params
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);
}
// 关中断,关流水线,清cache
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 */
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 */
i = 0;
asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));
return (0);
}