突然想起来,在移植linux的时候,linux中无法打印出信息的问题,满天下找问题,为了确保uboot没有问题,把uboot的引导过程分析了下:
一般会用bootm 引导uImage,uImage是在zImage的基础上,加了一个64字节的头,里面记载了linux内核的一些基本信息,来帮助uboot 加载linux 系统,当时最纠结的一个问题是,我的加载地址是0x31000000,为什么执行kernel的时候跳转到0x30108000这个地址去了,后来发现这个地址就是uImage的头里面传给uboot的,0x30108000这个地址是linux kernel的编译地址,是zImage自解压程序的起始地址,同时也是自解压程序后面真正kernel的起始地址,自解压程序会把kernel 解压到0x30108000这个地址,这边就分析一下在uboot 里面是如何把zImage 复制到这个问题,并启动kernel的。
执行bootm命令,就是执行cmd/bootm.c文件中的:
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
static int relocated = 0;
if (!relocated) {
int i;
/* relocate names of sub-command table */
for (i = 0; i < ARRAY_SIZE(cmd_bootm_sub); i++)
cmd_bootm_sub[i].name += gd->reloc_off;
relocated = 1;
}
#endif
/* determine if we have a sub command */
argc--; argv++;
if (argc > 0) {
char *endp;
simple_strtoul(argv[0], &endp, 16);
/* endp pointing to NULL means that argv[0] was just a
* valid number, pass it along to the normal bootm processing
*
* If endp is ':' or '#' assume a FIT identifier so pass
* along for normal processing.
*
* Right now we assume the first arg should never be '-'
*/
if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
return do_bootm_subcommand(cmdtp, flag, argc, argv);
}
return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
BOOTM_STATE_LOADOS |
#if defined(CONFIG_PPC) || defined(CONFIG_MIPS)
BOOTM_STATE_OS_CMDLINE |
#endif
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO, &images, 1);
}
这边执行最后一行的do_bootm_states,并且带入了很多标志位。
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress)
{
boot_os_fn *boot_fn;
ulong iflag = 0;
int ret = 0, need_boot_fn;
images->state |= states;
/*
* Work through the states and see how far we get. We stop on
* any error.
*/
printf("do_bootm_states \n");
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ret = bootm_find_other(cmdtp, flag, argc, argv);
argc = 0; /* consume the args */
}
在bootm中,关注bootm_find_os函数,进去可以看到:
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
const void *os_hdr;
bool ep_found = false;
int ret;
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len);
if (images.os.image_len == 0) {
puts("ERROR: can't get kernel image!\n");
return 1;
}
/* get image parameters */
switch (genimg_get_format(os_hdr)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY:
images.os.type = image_get_type(os_hdr);
images.os.comp = image_get_comp(os_hdr);
images.os.os = image_get_os(os_hdr);
images.os.end = image_get_image_end(os_hdr);
images.os.load = image_get_load(os_hdr);
images.os.arch = image_get_arch(os_hdr);
break;
#endif
首先是在boot_get_kernel 里面,这个里面比较重要的就是images这个结构体的一些参数,后面把原来的image加载地址,需要加载的位置等都记录进去,比较重要。这边image是IMAGE_FORMAT_LEGACY类型的,所以images.os.load = image_get_load(os_hdr);会被执行到,os_hdr是原先一开始加载的0x31000000地址,这边有64字节的头,把头里面的内核zImage的开始执行的地址赋值给images.os.load,第一次代码拷贝就放到这边,地址是0x30108000,进去看一下boot_get_kernel :
static const void *boot_get_kernel(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], bootm_headers_t *images,
ulong *os_data, ulong *os_len)
{
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
image_header_t *hdr;
#endif
ulong img_addr;
const void *buf;
const char *fit_uname_config = NULL;
const char *fit_uname_kernel = NULL;
#if IMAGE_ENABLE_FIT
int os_noffset;
#endif
img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0],
&fit_uname_config,
&fit_uname_kernel);
bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);
/* copy from dataflash if needed */
img_addr = genimg_get_image(img_addr);
/* check image type, for FIT images get FIT kernel node */
*os_data = *os_len = 0;
buf = map_sysmem(img_addr, 0);
switch (genimg_get_format(buf)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY:
printf("## Booting kernel from Legacy Image at %08lx ...\n",
img_addr);
hdr = image_get_kernel(img_addr, images->verify);
if (!hdr)
return NULL;
bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);
/* get os_data and os_len */
switch (image_get_type(hdr)) {
case IH_TYPE_KERNEL:
case IH_TYPE_KERNEL_NOLOAD:
*os_data = image_get_data(hdr);
*os_len = image_get_data_size(hdr);
break;
case IH_TYPE_MULTI:
image_multi_getimg(hdr, 0, os_data, os_len);
break;
case IH_TYPE_STANDALONE:
*os_data = image_get_data(hdr);
*os_len = image_get_data_size(hdr);
函数刚开始从我们环境变量中设置的load_addr中去取uImage的初始地址,放入img_addr中
然后通过hdr = image_get_kernel(img_addr, images->verify);
*os_data = image_get_data(hdr);
取出uImage 后面的zImage的起始地址,其实就是偏移64个字节,由于在boot_get_kernel中传入的os_data就是images.os.image_start,所以images.os.image_start就等于zImage的起始地址,为0x31000040,
在do_bootm_states中继续执行bootm_load_os,copy zImage到0x30801000:
static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
int boot_progress)
{
image_info_t os = images->os;
ulong load = os.load;
ulong blob_start = os.start;
ulong blob_end = os.end;
ulong image_start = os.image_start;
ulong image_len = os.image_len;
bool no_overlap;
void *load_buf, *image_buf;
int err;
load_buf = map_sysmem(load, 0);
image_buf = map_sysmem(os.image_start, image_len);
err = bootm_decomp_image(os.comp, load, os.image_start, os.type,
load_buf, image_buf, image_len,
CONFIG_SYS_BOOTM_LEN, load_end);
if (err) {
bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);
这边load_buf就是copy的目的地址0x30801000,由images.os.load传入,image_buf是copy的起始地址,0x31000040,由images.os.image_start传入,接着在bootm_decomp_image中执行memmove_wd 完成代码的搬移:
int bootm_decomp_image(int comp, ulong load, ulong image_start, int type,
void *load_buf, void *image_buf, ulong image_len,
uint unc_len, ulong *load_end)
{
int ret = 0;
*load_end = load;
print_decomp_msg(comp, type, load == image_start);
debug("comp=%d \n",comp);
debug("load_buf=%x \n",load_buf);
debug("image_buf=%x \n",image_buf);
/*
* Load the image to the right place, decompressing if needed. After
* this, image_len will be set to the number of uncompressed bytes
* loaded, ret will be non-zero on error.
*/
switch (comp) {
case IH_COMP_NONE:
if (load == image_start)
break;
if (image_len <= unc_len){
debug("load image \n");
memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);
}
else
回到do_bootm_states中,通过bootm_os_get_boot_func函数,在boot_os中找到引导linux的函数,初始化boot_fn
boot_os_fn *bootm_os_get_boot_func(int os)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
static bool relocated;
if (!relocated) {
int i;
/* relocate boot function table */
for (i = 0; i < ARRAY_SIZE(boot_os); i++)
if (boot_os[i] != NULL)
boot_os[i] += gd->reloc_off;
relocated = true;
}
#endif
return boot_os[os];
}
static boot_os_fn *boot_os[] = {
[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
#endif
#ifdef CONFIG_BOOTM_NETBSD
在do_bootm_states中执行 boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);其实是执行do_bootm_linux,接着执行boot_jump_linux:
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64
void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,
void *res2);
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
void *res2))images->ep;
debug("## Transferring control to Linux (at address %lx)...\n",
(ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);
if (!fake) {
do_nonsec_virt_switch();
kernel_entry(images->ft_addr, NULL, NULL, NULL);
}
#else
unsigned long machid = gd->bd->bi_arch_number;
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(int, int, uint))images->ep;
s = getenv("machid");
if (s) {
if (strict_strtoul(s, 16, &machid) < 0) {
debug("strict_strtoul failed!\n");
return;
}
printf("Using machid 0x%lx from environment\n", machid);
}
debug("## Transferring control to Linux (at address %08lx)" \
"...\n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
if (!fake) {
#ifdef CONFIG_ARMV7_NONSEC
if (armv7_boot_nonsec()) {
armv7_init_nonsec();
secure_ram_addr(_do_nonsec_entry)(kernel_entry,
0, machid, r2);
} else
#endif
debug("machid =%x \n",machid);
kernel_entry(0, machid, r2);
}
#endif
}
可以看到最后执行了kernel_entry(0, machid, r2);,传入了machid和boot的参数,这个kernel_entry是把kernel_entry = (void (*)(int, int, uint))images->ep;这个地址强制转化为指针函数,那么images->ep是个什么地址,可以回到之前boot_get_kernel函数中:
memmove(&images->legacy_hdr_os_copy, hdr,
sizeof(image_header_t));
/* save pointer to image header */
images->legacy_hdr_os = hdr;
images->legacy_hdr_valid = 1;
bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);
复制hdr到images->legacy_hdr_os_copy,其实hdr就是uImage 的头部64字节,里面包含kernel的入口地址0x30108000,然后置位images->legacy_hdr_valid,接着在bootm_find_os函数中:
if (images.os.arch == IH_ARCH_I386 ||
images.os.arch == IH_ARCH_X86_64) {
ulong len;
ret = boot_get_setup(&images, IH_ARCH_I386, &images.ep, &len);
if (ret < 0 && ret != -ENOENT) {
puts("Could not find a valid setup.bin for x86\n");
return 1;
}
/* Kernel entry point is the setup.bin */
} else if (images.legacy_hdr_valid) {
images.ep = image_get_ep(&images.legacy_hdr_os_copy);
#if IMAGE_ENABLE_FIT
} else if (images.fit_uname_os) {
int ret;
ret = fit_image_get_entry(images.fit_hdr_os,
images.fit_noffset_os, &images.ep);
if (ret) {
puts("Can't get entry point property!\n");
return 1;
}
#endif
} else if (!ep_found) {
puts("Could not find kernel entry point!\n");
return 1;
}
可以看到images.ep = image_get_ep(&images.legacy_hdr_os_copy);从64字节的头部中取出entry的地址,放入ep中,所以images.ep地址就是0x30108000,也是zImage的开始地址,uboot跳转到这个地址去执行以后就不再返回,完成使命。