通常我们启动内核是使用bootm命令,所以这里就分析一下内核的启动流程。
基础知识
vmlinux、Image、uImage 和zImage
kernel镜像格式:vmlinux
vmlinuz是可引导的、可压缩的内核镜像,vm代表Virtual Memory.Linux支持虚拟内存,因此得名vmlinux它是由用户对内核源码编译得到,实质是elf格式的文件.也就是说,vmlinux是编译出来的最原始的内核文件,未压缩.这种格式的镜像文件多存放在PC机上
kernel镜像格式:Image
Image是经过objcopy处理的只包含二进制数据的内核代码,它已经不是elf格式了,但这种格式的内核镜像还没有经过压缩.
kernel镜像格式:zImage
zImage是ARM linux常用的一种压缩镜像文件,它是由vmlinux加上解压代码经gzip压缩而成,命令格式是#make zImage.这种格式的Linux镜像文件多存放在flash上.
kernel镜像格式:uImage
uImage是uboot专用的镜像文件,它是在zImage之前加上一个长度为0x40的头信息,在头信息内说明了该镜像文件的类型、加载 位置、生成时间、大小等信息.换句话说,若直接从uImage的0x40位置开始执行,则zImage和uImage没有任何区别.命令格式是#make uImage.这种格式的Linux镜像文件多存放在flash上.
kernel镜像格式:xipImage
这种格式的Linux镜像文件多存放在NorFlash上,且运行时不需要拷贝到内存SDRAM中,可以直接在NorFlash中运行.
可以让我们启动使用的内核有Image、zImage、uImage、xipImage 因为我们没norflash,所以我们只能使用前三种。
我们前面一直移植的是uboot,所以我们就加载uImage启动内核。
下面是image头的格式
typedef struct image_header {
__be32 ih_magic; /* Image Header Magic Number,魔数 */
__be32 ih_hcrc; /* Image Header CRC Checksum,整个64字节头的crc校验码 */
__be32 ih_time; /* Image Creation Timestamp,uImage的时间 */
__be32 ih_size; /* Image Data Size,zImage的字节数 */
__be32 ih_load; /* Data Load Address,uImage要加载的地址 */
__be32 ih_ep; /* Entry Point Address,zImage的入口位置 = lode + 64 */
__be32 ih_dcrc; /* Image Data CRC Checksum,整个zImage的crc校验码 */
uint8_t ih_os; /* Operating System,操作系统代码 */
uint8_t ih_arch; /* CPU architecture,芯片类型 */
uint8_t ih_type; /* Image Type, 镜像类型 */
uint8_t ih_comp; /* Compression Type,压缩类型 */
uint8_t ih_name[IH_NMLEN]; /* Image Name,32字节的名字 */
} image_header_t;
而bootm的作用就是根据uImage头的64个字节数据信息,来启动linux内核。
bootm命令的定义
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory", bootm_help_text
);
通常使用是bootm xxxxxxxx 或 bootm
bootm的实现
/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
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) { /* 假设我们传了一个参数"30008000" */
char *endp;
simple_strtoul(argv[0], &endp, 16); /* 为数字,endp = NULL */
/* 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); //images是一个比较重要的全局变量
}
这里记录一下BOOTM的状态,方便后面查找
下面列出bootm_headers的内容,等会一个一个填充
/*
* Legacy and FIT format headers used by do_bootm() and do_bootm_<os>()
* routines.
*/
typedef struct bootm_headers {
/*
* Legacy os image header, if it is a multi component image
* then boot_get_ramdisk() and get_fdt() will attempt to get
* data from second and third component accordingly.
*/
image_header_t *legacy_hdr_os; /* image header pointer */
image_header_t legacy_hdr_os_copy; /* header copy */
ulong legacy_hdr_valid;
#if IMAGE_ENABLE_FIT /* 没定义 */
const char *fit_uname_cfg; /* configuration node unit name */
void *fit_hdr_os; /* os FIT image header */
const char *fit_uname_os; /* os subimage node unit name */
int fit_noffset_os; /* os subimage node offset */
void *fit_hdr_rd; /* init ramdisk FIT image header */
const char *fit_uname_rd; /* init ramdisk subimage node unit name */
int fit_noffset_rd; /* init ramdisk subimage node offset */
void *fit_hdr_fdt; /* FDT blob FIT image header */
const char *fit_uname_fdt; /* FDT blob subimage node unit name */
int fit_noffset_fdt;/* FDT blob subimage node offset */
void *fit_hdr_setup; /* x86 setup FIT image header */
const char *fit_uname_setup; /* x86 setup subimage node name */
int fit_noffset_setup;/* x86 setup subimage node offset */
#endif
#ifndef USE_HOSTCC
image_info_t os; /* os image info */
ulong ep; /* entry point of OS */
ulong rd_start, rd_end;/* ramdisk start/end */
char *ft_addr; /* flat dev tree address */
ulong ft_len; /* length of flat device tree */
ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
#endif
int verify; /* getenv("verify")[0] != 'n' */
#define BOOTM_STATE_START (0x00000001)
#define BOOTM_STATE_FINDOS (0x00000002)
#define BOOTM_STATE_FINDOTHER (0x00000004)
#define BOOTM_STATE_LOADOS (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT (0x00000020)
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
#define BOOTM_STATE_OS_GO (0x00000400)
int state;
#ifdef CONFIG_LMB /* 没定义 */
struct lmb lmb; /* for memory mgmt */
#endif
} bootm_headers_t;
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; /* images->state之前没被赋值过,就是上面传下来的几个 */
/*
* Work through the states and see how far we get. We stop on
* any error.
*/
if (states & BOOTM_STATE_START) /* 被定义,要执行 */
ret = bootm_start(cmdtp, flag, argc, argv); /* 得到verify值,见1.1小节分析 */
if (!ret && (states & BOOTM_STATE_FINDOS)) /* 要执行 ,见1.2小节分析* /
ret = bootm_find_os(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOTHER)) /* 要执行,里面没什么实际操作 */
ret = bootm_find_other(cmdtp, flag, argc, argv);
/* Load the OS */
if (!ret && (states & BOOTM_STATE_LOADOS)) {
ulong load_end;
iflag = bootm_disable_interrupts(); /* 关中断 */
ret = bootm_load_os(images, &load_end, 0); /* 加载os,见1.3分析 */
if (ret == 0)
lmb_reserve(&images->lmb, images->os.load,
(load_end - images->os.load)); /* 空函数 */
else if (ret && ret != BOOTM_ERR_OVERLAP)
goto err;
else if (ret == BOOTM_ERR_OVERLAP)
ret = 0;
}
/* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH /* 没定义 */
if (!ret && (states & BOOTM_STATE_RAMDISK)) {
ulong rd_len = images->rd_end - images->rd_start;
ret = boot_ramdisk_high(&images->lmb, images->rd_start,
rd_len, &images->initrd_start, &images->initrd_end);
if (!ret) {
setenv_hex("initrd_start", images->initrd_start);
setenv_hex("initrd_end", images->initrd_end);
}
}
#endif
#if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB) /* 没定义 */
if (!ret && (states & BOOTM_STATE_FDT)) {
boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
&images->ft_len);
}
#endif
/* From now on, we need the OS boot function */
if (ret)
return ret;
boot_fn = bootm_os_get_boot_func(images->os.os); /* 得到我们要运行的os的函数,见1.4分析 */
need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO); /* 我们后三个os相关的都有 */
if (boot_fn == NULL && need_boot_fn) {
if (iflag)
enable_interrupts();
printf("ERROR: booting os '%s' (%d) is not supported\n",
genimg_get_os_name(images->os.os), images->os.os);
bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
return 1;
}
/* Call various other states that are not generally used */
if (!ret && (states & BOOTM_STATE_OS_CMDLINE)) /* 没有,不执行 */
ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_BD_T)) /* 没有,不执行 */
ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_PREP)) { /* 有 */
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY) /* 没定义 */
if (images->os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); /* 这里要执行了,见1.5分析,注意我们这里只传了一个BOOTM_STATE_OS_PREP ,1.5分析后只执行了boot_prep_linux后又回到这里向下继续执行*/
}
#ifdef CONFIG_TRACE /* 没定义 */
/* Pretend to run the OS, then run a user command */
if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
char *cmd_list = getenv("fakegocmd");
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
images, boot_fn);
if (!ret && cmd_list)
ret = run_command_list(cmd_list, -1, flag);
}
#endif
/* Check for unsupported subcommand. */
if (ret) { /* 上面的返回值是0,不执行 */
puts("subcommand not supported\n");
return ret;
}
/* Now run the OS! We hope this doesn't return */
if (!ret && (states & BOOTM_STATE_OS_GO)) /* 定义了,这里继续指行,见1.6分析,也是只传了一个参数BOOTM_STATE_OS_GO */
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);
/* Deal with any fallout */
err:
if (iflag)
enable_interrupts();
if (ret == BOOTM_ERR_UNIMPLEMENTED)
bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
else if (ret == BOOTM_ERR_RESET)
do_reset(cmdtp, flag, argc, argv);
return ret;
}
1.1、bootm_start
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
/* 把之前的全局变量images清0,包括state */
memset((void *)&images, 0, sizeof(images));
images.verify = getenv_yesno("verify"); /* 得到环境变量verify,定义的时候是n表示不对zImage进行crc校验,为y表示要校验 */
boot_start_lmb(&images); /* 没定义CONFIG_LMB,空函数 */
bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
images.state = BOOTM_STATE_START; /* 标记状态 */
return 0;
}
1.2、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,注释说的很明白得到image头信息,起始地址长度等 */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len); //os里面和imgae里面的一些信息我们已经标记了
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)) { /* 已经分析过返回值是IMAGE_FORMAT_LEGACY */
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY:
images.os.type = image_get_type(os_hdr); //填充os中的各种信息
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
#if IMAGE_ENABLE_FIT /* 没定义 */
case IMAGE_FORMAT_FIT:
......
break;
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE /* 没定义 */
case IMAGE_FORMAT_ANDROID:
images.os.type = IH_TYPE_KERNEL;
images.os.comp = IH_COMP_NONE;
images.os.os = IH_OS_LINUX;
images.os.end = android_image_get_end(os_hdr);
images.os.load = android_image_get_kload(os_hdr);
images.ep = images.os.load;
ep_found = true;
break;
#endif
default:
puts("ERROR: unknown image format type!\n");
return 1;
}
/* If we have a valid setup.bin, we will use that for entry (x86) */
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); /* 前面标记过有效了,这里对ep赋值0x30008040 */
#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;
}
/* 可以运行在任何地址的os,会直接使用当前地址作为os的load地址,我们不是那种内核 */
if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}
images.os.start = map_to_sysmem(os_hdr); /* os.start还是0x30008000 */
return 0;
}
1.2.1、boot_get_kernel
/* 这里的参数images就是全局变量images,os_data是images.os.image_start,这里的os_len是images.os.image_len */
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
/* 我们的参数是"30008000",所以argc = 1,第一个参数是argv[0],结果返回整数0x30008000函数见下面分析 */
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); /* 我们没dataflash,这里为空还是返回0x30008000 */
/* check image type, for FIT images get FIT kernel node */
*os_data = *os_len = 0; /* 这里先清空images.os里的两个参数 */
buf = map_sysmem(img_addr, 0); /* 强制类型转换buf = 0x30008000 */
switch (genimg_get_format(buf)) { /* buf传入这里还是0x30008000地址 */
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY: /* 魔数校验成功就是返回IMAGE_FORMAT_LEGACY */
printf("## Booting kernel from Legacy Image at %08lx ...\n",
img_addr); //打印信息见下图
hdr = image_get_kernel(img_addr, images->verify); /* 对image进行校验,并且打印出相关信息,该函数下面就分析 */
if (!hdr)
return NULL;
bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);
/* get os_data and os_len */
switch (image_get_type(hdr)) { /* 这边是得到os type类型,我们就是kernel */
case IH_TYPE_KERNEL:
case IH_TYPE_KERNEL_NOLOAD:
*os_data = image_get_data(hdr); /* 即0x30008000+0x40,这里的os_data就是os.image_start即得到,zImage当前在内存中的地址 */
*os_len = image_get_data_size(hdr); /* 0x40 + zImage大小,这里的os_len就是os.image_len即uImage的大小 */
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);
break;
default:
printf("Wrong Image Type for %s command\n",
cmdtp->name);
bootstage_error(BOOTSTAGE_ID_CHECK_IMAGETYPE);
return NULL;
}
/*
* copy image header to allow for image overwrites during
* kernel decompression.
*/
memmove(&images->legacy_hdr_os_copy, hdr,
sizeof(image_header_t)); /* 拷贝一份image_herfer_t,今后就直接用全局变量里面的了 */
/* save pointer to image header */
images->legacy_hdr_os = hdr; /* 0x30008000 */
images->legacy_hdr_valid = 1; /* 标记我们的hdr经过前面的校验,有效 */
bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);
break;
#endif
#if IMAGE_ENABLE_FIT /* 没定义,不管 */
case IMAGE_FORMAT_FIT:
os_noffset = fit_image_load(images, img_addr,
&fit_uname_kernel, &fit_uname_config,
IH_ARCH_DEFAULT, IH_TYPE_KERNEL,
BOOTSTAGE_ID_FIT_KERNEL_START,
FIT_LOAD_IGNORED, os_data, os_len);
if (os_noffset < 0)
return NULL;
images->fit_hdr_os = map_sysmem(img_addr, 0);
images->fit_uname_os = fit_uname_kernel;
images->fit_uname_cfg = fit_uname_config;
images->fit_noffset_os = os_noffset;
break;
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE /* 没定义,不管 */
case IMAGE_FORMAT_ANDROID:
printf("## Booting Android Image at 0x%08lx ...\n", img_addr);
if (android_image_get_kernel(buf, images->verify,
os_data, os_len))
return NULL;
break;
#endif
default:
printf("Wrong Image Format for %s command\n", cmdtp->name);
bootstage_error(BOOTSTAGE_ID_FIT_KERNEL_INFO);
return NULL;
}
debug(" kernel data at 0x%08lx, len = 0x%08lx (%ld)\n",
*os_data, *os_len, *os_len);
return buf; /* 返回值还是switch前面的0x30008000
}
我当初开发板自带的zImage,我使用uboot自带的mkinage工具生成的uImage
./mkimage -n "my kernel" -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -d zImage uImage
mkimage的使用方法百度介绍很多。
虽然我生成的uImage目前不能启动,但可以方便我们调试观察uboot中bootm的动作。这是一个2.6.35.7的内核。
static image_header_t *image_get_kernel(ulong img_addr, int verify)
{
image_header_t *hdr = (image_header_t *)img_addr;
if (!image_check_magic(hdr)) { /* 魔数前面校验过了,正常肯定能通过的 */
puts("Bad Magic Number\n");
bootstage_error(BOOTSTAGE_ID_CHECK_MAGIC);
return NULL;
}
bootstage_mark(BOOTSTAGE_ID_CHECK_HEADER);
if (!image_check_hcrc(hdr)) { /* 前64字节的crc,正常肯定能通过的 */
puts("Bad Header Checksum\n");
bootstage_error(BOOTSTAGE_ID_CHECK_HEADER);
return NULL;
}
bootstage_mark(BOOTSTAGE_ID_CHECK_CHECKSUM);
image_print_contents(hdr); /* 打印头信息 */
if (verify) { /* zimage比较大,所以通过这个verify环境变量来定义要不要校验 */
puts(" Verifying Checksum ... ");
if (!image_check_dcrc(hdr)) {
printf("Bad Data CRC\n");
bootstage_error(BOOTSTAGE_ID_CHECK_CHECKSUM);
return NULL;
}
puts("OK\n");
}
bootstage_mark(BOOTSTAGE_ID_CHECK_ARCH);
if (!image_check_target_arch(hdr)) { /* arch我们是arm,正常都是可以过的 */
printf("Unsupported Architecture 0x%x\n", image_get_arch(hdr));
bootstage_error(BOOTSTAGE_ID_CHECK_ARCH);
return NULL;
}
return hdr;
}
image_print_contents打印uImage的头信息,实际头信息键上图
void image_print_contents(const void *ptr)
{
const image_header_t *hdr = (const image_header_t *)ptr; /* 0x30008000 */
const char __maybe_unused *p;
p = IMAGE_INDENT_STRING;
printf("%sImage Name: %.*s\n", p, IH_NMLEN, image_get_name(hdr)); /* 打印hdr的name */
if (IMAGE_ENABLE_TIMESTAMP) {
printf("%sCreated: ", p);
genimg_print_time((time_t)image_get_time(hdr));
}
printf("%sImage Type: ", p); /* 打印image类型提示符 */
image_print_type(hdr); /* 打印实际image类型 */
printf("%sData Size: ", p); /* 打印zImaage的大小提示符 */
genimg_print_size(image_get_data_size(hdr)); /* 打印实际image大小 */
printf("%sLoad Address: %08x\n", p, image_get_load(hdr)); /* 加载地址 */
printf("%sEntry Point: %08x\n", p, image_get_ep(hdr)); /* 入口地址 */
if (image_check_type(hdr, IH_TYPE_MULTI) ||
image_check_type(hdr, IH_TYPE_SCRIPT)) {
int i;
ulong data, len;
ulong count = image_multi_count(hdr);
printf("%sContents:\n", p);
for (i = 0; i < count; i++) {
image_multi_getimg(hdr, i, &data, &len);
printf("%s Image %d: ", p, i);
genimg_print_size(len);
if (image_check_type(hdr, IH_TYPE_SCRIPT) && i > 0) {
/*
* the user may need to know offsets
* if planning to do something with
* multiple files
*/
printf("%s Offset = 0x%08lx\n", p, data);
}
}
} else if (image_check_type(hdr, IH_TYPE_FIRMWARE_IVT)) {
printf("HAB Blocks: 0x%08x 0x0000 0x%08x\n",
image_get_load(hdr) - image_get_header_size(),
image_get_size(hdr) + image_get_header_size()
- 0x1FE0);
}
}
可以看一下当前image的头信息
1.1.1.1、genimg_get_kernel_addr_fit
ulong genimg_get_kernel_addr_fit(char * const img_addr,
const char **fit_uname_config,
const char **fit_uname_kernel)
{
ulong kernel_addr;
/* find out kernel image address */
if (!img_addr) { /* 我们传的有值,所以进不来这里 */
/* ulong load_addr = CONFIG_SYS_LOAD_ADDR; 如果我们使用bootm不带参数,就会使用头文件默认的加载地址 */
kernel_addr = load_addr;
debug("* kernel: default image load address = 0x%08lx\n",
load_addr);
#if CONFIG_IS_ENABLED(FIT) /* 没定义 */
} else if (fit_parse_conf(img_addr, load_addr, &kernel_addr,
fit_uname_config)) {
debug("* kernel: config '%s' from image at 0x%08lx\n",
*fit_uname_config, kernel_addr);
} else if (fit_parse_subimage(img_addr, load_addr, &kernel_addr,
fit_uname_kernel)) {
debug("* kernel: subimage '%s' from image at 0x%08lx\n",
*fit_uname_kernel, kernel_addr);
#endif
} else {
kernel_addr = simple_strtoul(img_addr, NULL, 16); /* 把"30008000"字符串转换成16进制数 */
debug("* kernel: cmdline image address = 0x%08lx\n",
kernel_addr);
}
return kernel_addr;
}
1.2.1.2、genimg_get_format
/* 上面传过来的地址还是0x30008000 */
int genimg_get_format(const void *img_addr)
{
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
const image_header_t *hdr;
hdr = (const image_header_t *)img_addr;
if (image_check_magic(hdr)) /* 如果是uImage的话,0x30008000第一个位置放的是一个魔数 */
return IMAGE_FORMAT_LEGACY;
#endif
#if IMAGE_ENABLE_FIT || IMAGE_ENABLE_OF_LIBFDT
if (fdt_check_header(img_addr) == 0)
return IMAGE_FORMAT_FIT;
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE
if (android_image_check_header(img_addr) == 0)
return IMAGE_FORMAT_ANDROID;
#endif
return IMAGE_FORMAT_INVALID;
}
/* 得到传过来的0x30008000地址的第一个32位整数和IH_MAGIC比较,判断是不是魔数 */
static inline int image_check_magic(const image_header_t *hdr)
{
return (image_get_magic(hdr) == IH_MAGIC);
}
1.2.1.2.1、image_get_magic
#define image_get_hdr_l(f) \
static inline uint32_t image_get_##f(const image_header_t *hdr) \
{ \
return uimage_to_cpu(hdr->ih_##f); \
}
image_get_hdr_l(magic) /* image_get_magic */
image_get_hdr_l(hcrc) /* image_get_hcrc */
image_get_hdr_l(time) /* image_get_time */
image_get_hdr_l(size) /* image_get_size */
image_get_hdr_l(load) /* image_get_load */
image_get_hdr_l(ep) /* image_get_ep */
image_get_hdr_l(dcrc) /* image_get_dcrc */
前面已经分析过类似的宏,这里就不带入分析了,可以直接看出来,这个宏的作用就是通过传入的参数地址hdr
返回image_header_t 里面不同变量偏移地址的4字节数
注意:前面uImage中的image_hander是以大端格式存储的以下图的30008000为例来看,而我们arm中则是以小端格式存储,所以uimage_to_cpu函数还要对数值的高低字节进行调整。
32位数,调整大小端算法
#define uimage_to_cpu(x) be32_to_cpu(x)
# define be32_to_cpu(x) uswap_32(x)
#define uswap_32(x) \
((((x) & 0xff000000) >> 24) | \
(((x) & 0x00ff0000) >> 8) | \
(((x) & 0x0000ff00) << 8) | \
(((x) & 0x000000ff) << 24))
得到magic魔数为0x27051956,而系统定义的魔数也是0x27051956,所以只要是uImage,第一个魔数校验就可以成功。
#define IH_MAGIC 0x27051956 /* Image Magic Number */
下图标记一下头信息
1.3、bootm_load_os
先看一下图
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; /* 0x30008000 */
ulong blob_start = os.start; /* 0x30008000 */
ulong blob_end = os.end; /* 0x30008000 + uImage大小*/
ulong image_start = os.image_start; /* 0x30008040 */
ulong image_len = os.image_len; /* uImage大小 */
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);
return err;
}
flush_cache(load, ALIGN(*load_end - load, ARCH_DMA_MINALIGN));
debug(" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);
bootstage_mark(BOOTSTAGE_ID_KERNEL_LOADED);
/* 我们在uboot中没再压缩,所以不执行下面的 */
no_overlap = (os.comp == IH_COMP_NONE && load == image_start);
if (!no_overlap && (load < blob_end) && (*load_end > blob_start)) {
debug("images.os.start = 0x%lX, images.os.end = 0x%lx\n",
blob_start, blob_end);
debug("images.os.load = 0x%lx, load_end = 0x%lx\n", load,
*load_end);
/* Check what type of image this is. */
if (images->legacy_hdr_valid) {
if (image_get_type(&images->legacy_hdr_os_copy)
== IH_TYPE_MULTI)
puts("WARNING: legacy format multi component image overwritten\n");
return BOOTM_ERR_OVERLAP;
} else {
puts("ERROR: new format image overwritten - must RESET the board to recover\n");
bootstage_error(BOOTSTAGE_ID_OVERWRITTEN);
return BOOTM_ERR_RESET;
}
}
return 0;
}
1.3.1、bootm_decomp_image
1.3.2、bootm_decomp_image
err = bootm_decomp_image(os.comp, load, os.image_start, os.type,
load_buf, image_buf, image_len,
CONFIG_SYS_BOOTM_LEN, load_end); /* 解压缩 */
load_buf = 0x30008000 image_buf = 0x30008040
image_len = uImage长度 load_end = 0x30008000 + uImage
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); /* 我们这里不相等 */
/*
* 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) /* 一致,我门从flash或网络下载的地址和要运行的地址一致 */
break;
if (image_len <= unc_len) /* 不一致则要移动从image_buf地址移动image_len到load_buf地址 */
memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);
else
ret = 1;
break;
#ifdef CONFIG_GZIP
case IH_COMP_GZIP: {
ret = gunzip(load_buf, unc_len, image_buf, &image_len);
break;
}
#endif /* CONFIG_GZIP */
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2: {
/* 解压缩算法 */
........
break;
}
#endif /* CONFIG_BZIP2 */
#ifdef CONFIG_LZMA
case IH_COMP_LZMA: {
/* 解压缩算法 */
........
break;
}
#endif /* CONFIG_LZMA */
#ifdef CONFIG_LZO
case IH_COMP_LZO: {
/* 解压缩算法 */
........
break;
}
#endif /* CONFIG_LZO */
#ifdef CONFIG_LZ4
case IH_COMP_LZ4: {
/* 解压缩算法 */
........
break;
}
#endif /* CONFIG_LZ4 */
default:
printf("Unimplemented compression type %d\n", comp);
return BOOTM_ERR_UNIMPLEMENTED;
}
if (ret)
return handle_decomp_error(comp, image_len, unc_len, ret);
*load_end = load + image_len;
puts("OK\n");
return 0;
}
/* 我们的没有压缩,所以是IH_COMP_NONE,xip代表我们的加载地址和uImage地址刚好一致,否则loading代表要搬移 */
static void print_decomp_msg(int comp_type, int type, bool is_xip)
{
const char *name = genimg_get_type_name(type);
if (comp_type == IH_COMP_NONE)
printf(" %s %s ... ", is_xip ? "XIP" : "Loading", name);
else
printf(" Uncompressing %s ... ", name);
}
从上面的搬移我们可以看到,假如我们下载的uImage的地址不是uImage将来要运行的load地址,bootm命令会把我们下载的地址的uboot通过uImage的头信息的里面的load地址搬移到load地址。
1.4、bootm_os_get_boot_func
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]; /* 根据os类型,返回对应的函数指针 */
}
enum {
IH_OS_INVALID = 0, /* Invalid OS */
IH_OS_OPENBSD, /* OpenBSD */
IH_OS_NETBSD, /* NetBSD */
IH_OS_FREEBSD, /* FreeBSD */
IH_OS_4_4BSD, /* 4.4BSD */
IH_OS_LINUX, /* Linux */
IH_OS_SVR4, /* SVR4 */
IH_OS_ESIX, /* Esix */
.....
};
/* 函数指针数组见下图 */
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
[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
#ifdef CONFIG_LYNXKDI
[IH_OS_LYNXOS] = do_bootm_lynxkdi,
#endif
#ifdef CONFIG_BOOTM_RTEMS
[IH_OS_RTEMS] = do_bootm_rtems,
#endif
#if defined(CONFIG_BOOTM_OSE)
[IH_OS_OSE] = do_bootm_ose,
#endif
#if defined(CONFIG_BOOTM_PLAN9)
[IH_OS_PLAN9] = do_bootm_plan9,
#endif
#if defined(CONFIG_BOOTM_VXWORKS) && \
(defined(CONFIG_PPC) || defined(CONFIG_ARM))
[IH_OS_VXWORKS] = do_bootm_vxworks,
#endif
#if defined(CONFIG_CMD_ELF)
[IH_OS_QNX] = do_bootm_qnxelf,
#endif
#ifdef CONFIG_INTEGRITY
[IH_OS_INTEGRITY] = do_bootm_integrity,
#endif
#ifdef CONFIG_BOOTM_OPENRTOS
[IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
};
这里学到一点,数组可以通过指定下标,赋值,即如下
static boot_os_fn *boot_os[] = {
[IH_OS_LINUX] = do_bootm_linux,
}
static boot_os_fn *boot_os[] = {
[5] = do_bootm_linux,
}
1.5、boot_selected_os
int boot_selected_os(int argc, char * const argv[], int state,
bootm_headers_t *images, boot_os_fn *boot_fn)
{
arch_preboot_os(); /* 空函数 */
boot_fn(state, argc, argv, images); /* 执行do_bootm_linux */
/* Stand-alone may return when 'autostart' is 'no' */
if (images->os.type == IH_TYPE_STANDALONE ||
IS_ENABLED(CONFIG_SANDBOX) ||
state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
return 0;
bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
debug("\n## Control returned to monitor - resetting...\n");
return BOOTM_ERR_RESET;
}
1.6、do_bootm_linux
/* 前面的传参flag = BOOTM_STATE_OS_PREP*/
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
/* No need for those on ARM */
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
return -1;
if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images); /* 执行了一次 boot_prep_linux 直接退出*/
return 0;
}
if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
boot_jump_linux(images, flag);
return 0;
}
boot_prep_linux(images);
boot_jump_linux(images, flag);
return 0;
}
1.6.1、boot_prep_linux uboot的tag传参形式
static void boot_prep_linux(bootm_headers_t *images)
{
char *commandline = getenv("bootargs"); /* 得到bootargs环境变量 */
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
#ifdef CONFIG_OF_LIBFDT
debug("using: FDT\n");
if (image_setup_linux(images)) {
printf("FDT creation failed! hanging...");
hang();
}
#endif
} else if (BOOTM_ENABLE_TAGS) {
debug("using: ATAGS\n");
setup_start_tag(gd->bd); /* 设置起始头 */
if (BOOTM_ENABLE_SERIAL_TAG)
setup_serial_tag(¶ms); /* 设置串口信息 */
if (BOOTM_ENABLE_CMDLINE_TAG)
setup_commandline_tag(gd->bd, commandline); /* 命令行参数,很重要 */
if (BOOTM_ENABLE_REVISION_TAG)
setup_revision_tag(¶ms); /* 版本,不重要 */
if (BOOTM_ENABLE_MEMORY_TAGS)
setup_memory_tags(gd->bd); /* 内存信息,比较重要 */
if (BOOTM_ENABLE_INITRD_TAG) {
/*
* In boot_ramdisk_high(), it may relocate ramdisk to
* a specified location. And set images->initrd_start &
* images->initrd_end to relocated ramdisk's start/end
* addresses. So use them instead of images->rd_start &
* images->rd_end when possible.
*/
if (images->initrd_start && images->initrd_end) {
setup_initrd_tag(gd->bd, images->initrd_start,
images->initrd_end);
} else if (images->rd_start && images->rd_end) {
setup_initrd_tag(gd->bd, images->rd_start,
images->rd_end);
}
}
setup_board_tags(¶ms);
setup_end_tag(gd->bd); /* 结束tag */
} else {
printf("FDT and ATAGS support not compiled in - hanging\n");
hang();
}
}
/* tag的起始标记 */
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *)bd->bi_boot_params; /* 在uboot的后半部分的board_init被初始化 */
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);
}
int board_init(void)
{
/* Set Initial global variables */
gd->bd->bi_arch_number = MACH_TYPE_SMDKV210;
gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100; /* 0x30000100 */
return 0;
}
上面函数执行完后,会把uboot给linux内核的参数以tag的形式,依次放在0x30000100地址后面。将来只需要给linux内核传0x30000100地址就可以了。
1.7、跳转到linux内核
/* 参数flag = BOOTM_STATE_OS_GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64 /* 不是64位的 */
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) {
#ifdef CONFIG_ARMV8_PSCI
armv8_setup_psci();
#endif
do_nonsec_virt_switch();
update_os_arch_secondary_cores(images->os.arch);
#ifdef CONFIG_ARMV8_SWITCH_TO_EL1
armv8_switch_to_el2((u64)images->ft_addr, 0, 0,
(u64)switch_to_el1, ES_TO_AARCH64);
#else
if ((IH_ARCH_DEFAULT == IH_ARCH_ARM64) &&
(images->os.arch == IH_ARCH_ARM))
armv8_switch_to_el2(0, (u64)gd->bd->bi_arch_number,
(u64)images->ft_addr,
(u64)images->ep,
ES_TO_AARCH32);
else
armv8_switch_to_el2((u64)images->ft_addr, 0, 0,
images->ep,
ES_TO_AARCH64);
#endif
}
#else
unsigned long machid = gd->bd->bi_arch_number; /* board_init中MACH_TYPE_SMDKV210 */
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; /* 0x30008040 */
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); /* 启动Linux内核前的最后信息和清理 */
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) /* 没定义 */
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params; /* 0x30000100 */
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
kernel_entry(0, machid, r2); /* 跳到0x30008040执行,参数分别是0,SMDKV210的机器码,以及参数地址0x30000100 */
}
#endif
}
static void announce_and_cleanup(int fake)
{
printf("\nStarting kernel ...%s\n\n", fake ? /* 打印启动前的最后一个信息 */
"(fake run for tracing)" : "");
bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel");
#ifdef CONFIG_BOOTSTAGE_FDT /* 没定义 */
bootstage_fdt_add_report();
#endif
#ifdef CONFIG_BOOTSTAGE_REPORT /* 没定义 */
bootstage_report();
#endif
#ifdef CONFIG_USB_DEVICE /* 没定义 */
udc_disconnect();
#endif
board_quiesce_devices(); /* 空函数 */
cleanup_before_linux(); /* 清cache,关中断等 */
}
分析我自己手动生成放入uImage不能启动内核的原因。(用厂家提供的旧的1.3.4版本的uboot可以启动zImage,说明kernel没有问题)
1.没有设置命令行参数,比如启动了也不一定打印到这个串口2
设置一个我之前用过的,在smdkv210.h中定义,主要注释掉头文件里面原有的CONFIG_BOOTARGS
#define CONFIG_BOOTARGS "root=/dev/nfs nfsroot=192.168.0.107: \
/home/run/work/rootfs/rootfs ip=192.168.0.20:192.168.0.107:192.168.0.1:\
255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200"
发现仍然不能启动,故排除命令行问题
2.uImage本身不对
经过仔细分析源码发现,zImage的入口地址本应该是0x30008000(由kernel决定,一小节移植kernel时说明)
load地址是0x30008000,而load的时候我们是tftp uImage到0x30008000,所以在bootm命令中要把0x30008040地址后面的zImage搬移到0x30008000位置
同时,此时应该ep应该指向0x30008000,而不是0x30008040。而我们的ep设置的还是0x3008040,这就让跳转到zImage时不是从第一条指令开始运行,所以会跑飞。
接下来我们重新生成uImage再次尝试启动。
./mkimage -n "my kernel" -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage uImage
继续死掉
向前64字节,依然死掉
这一次是加载地址是XIP的,即不需要移动,说明我们分析的uImage比zImage前64字节,而zImge刚好在0x30008000就是XIP
实在没辙了重新编译了一次老的内核,用命令生成uImage
依然是死掉。
看来老的内核确实是有毒。
接一来,从下一节开始移植一个新的内核和文件系统,今后的驱动就用新的内核编写学习。
顺便测试一下新uboot配新内核能否启动。