u-boot 加载 kernel 的流程分析。
image重要结构体头文件
// include/image.h
*
* Legacy and FIT format headers used by do_bootm() and do_bootm_<os>()
* routines.
*/
// 这是正bootm 头部结构体
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;
// image 头部结构体
typedef struct image_header {
__be32 ih_magic; /* Image Header Magic Number */
__be32 ih_hcrc; /* Image Header CRC Checksum */
__be32 ih_time; /* Image Creation Timestamp */
__be32 ih_size; /* Image Data Size */
__be32 ih_load; /* Data Load Address */
__be32 ih_ep; /* Entry Point Address */
__be32 ih_dcrc; /* Image Data CRC Checksum */
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 */
} image_header_t;
// image 信息结构体
typedef struct image_info {
ulong start, end; /* start/end of blob */
ulong image_start, image_len; /* start of image within blob, len of image */
ulong load; /* load addr for the image */
uint8_t comp, type, os; /* compression, type of image, os type */
uint8_t arch; /* CPU architecture */
} image_info_t;
* #### 1. 这里分析的话从启动脚本开始分析
* #### 启动脚本写在 `include/configs/am335x_sbc7109.h`
```sh
#define CONFIG_BOOTCOMMAND \
"run mmcboot;" \
"setenv mmcdev 1; " \
"setenv bootpart 1:2; " \
"run mmcboot;"
# 这里我们看到,他主要调用的是 mmcboot
# mmcboot 主要有如下操作
"mmcboot=mmc dev ${mmcdev}; " \
"if mmc rescan; then " \
"echo SD/MMC found on device ${mmcdev};" \
"if run loadbootenv; then " \ # 加载配置文件 uEnv.txt
"echo Loaded environment from ${bootenv};" \
"run importbootenv;" \ # 加载环境变量
"fi;" \
"if test -n $uenvcmd; then " \
"echo Running uenvcmd ...;" \
"run uenvcmd;" \ # 这个上次有记录,是boot 命令
"fi;" \
"if run loadimage; then " \ # 执行加载命令
"run mmcloados;" \ # ----> 这个在后面
"fi;" \
"fi;\0" \
# loadbootenv
"mmcdev=0\0" \ # 开始为零,如果第一次mmcboot 不行第二次就可以设置为1
"bootenv=uEnv.txt\0" \
"loadbootenv=load mmc ${mmcdev} ${loadaddr} ${bootenv}\0" \
# importbootenv
"importbootenv=echo Importing environment from mmc ...; " \
"env import -t $loadaddr $filesize\0" \
# loadimage
"loadimage=load mmc ${bootpart} ${loadaddr} ${bootdir}/${bootfile}\0" \
# 到最后的 ---->
# mmcloados
"mmcloados=run mmcargs; " \
"if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \
"if run loadfdt; then " \
"bootz ${loadaddr} - ${fdtaddr}; " \ #最后执行的是这里
"else " \
"if test ${boot_fdt} = try; then " \
"bootz; " \
"else " \
"echo WARN: Cannot load the DT; " \
"fi; " \
"fi; " \
"else " \
"bootz; " \
"fi;\0" \
# mmcargs
"mmcargs=setenv bootargs console=${console} " \
"${optargs} " \
"${mtdparts} " \
"root=${mmcroot} " \
"rootfstype=${mmcrootfstype}\0" \
# loadfdt
"loadfdt=load mmc ${bootpart} ${fdtaddr} ${bootdir}/${fdtfile}\0" \
# 最后执行
"bootz ${loadaddr} - ${fdtaddr}; " \
# 结合下面的, 也就是 bootz 0x82000000 - 0x88000000
# ---> 接下面 bootz 命令 ,这里可以跳到 2.
# 这里变量的定义最后我在 include/configs/ti_armv7_common.h 找到
#define DEFAULT_LINUX_BOOT_ENV \
"loadaddr=0x82000000\0" \
"kernel_addr_r=0x82000000\0" \
"fdtaddr=0x88000000\0" \
"fdt_addr_r=0x88000000\0" \
"rdaddr=0x88080000\0" \
"ramdisk_addr_r=0x88080000\0" \
"scriptaddr=0x80000000\0" \
"pxefile_addr_r=0x80100000\0" \
"bootm_size=0x10000000\0"
# 这个宏被 CONFIG_EXTRA_ENV_SETTINGS 包含
</br>
* #### 2. `bootz` 命令分析
```c
// bootz 命令
// cmd/bootm.c
bootm_headers_t images; /* pointers to os/initrd/fdt images */
// bootz 命令
U_BOOT_CMD(
bootz, CONFIG_SYS_MAXARGS, 1, do_bootz,
"boot Linux zImage image from memory", bootz_help_text
);
// bootz 调用的是 do_bootz
// 接上面分析的 bootz 0x82000000 - 0x88000000
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int ret;
/* Consume 'bootz' */
argc--; argv++;
// ----> 这里可以跳到 2.1
if (bootz_start(cmdtp, flag, argc, argv, &images))
return 1;
/*
* We are doing the BOOTM_STATE_LOADOS state ourselves, so must
* disable interrupts ourselves
*/
bootm_disable_interrupts();
images.os.os = IH_OS_LINUX; // 这个变量有关下面获取kernel 的进入函数
// 在这里调用了 do_bootm_states()
// 状态是 BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO
ret = do_bootm_states(cmdtp, flag, argc, argv, // ----> 这里可以跳到 2.2
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO,
&images, 1);
return ret;
}
* #### 2.1 `do_bootz` 首先会执行 `bootz_statrt` 初始化一些东西
```c
// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], bootm_headers_t *images)
{
int ret;
ulong zi_start, zi_end;
// 第一次执行 do_bootm_states, 状态是 BOOTM_STATE_START
// 这里可以跳到 2.2
ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
images, 1);
/* Setup Linux kernel zImage entry point */
if (!argc) {
images->ep = load_addr;
debug("* kernel: default image load address = 0x%08lx\n",
load_addr);
} else {
images->ep = simple_strtoul(argv[0], NULL, 16);
// 在这里,参数不为0, 所以 image->ep == 0x82000000, 这个在后面被调用
debug("* kernel: cmdline image address = 0x%08lx\n",
images->ep);
}
// 这里把 0x8200000 作为第一个参数传进去,然后就有相关 kernel的显示
// 这里跳到 2.1.1
ret = bootz_setup(images->ep, &zi_start, &zi_end);
if (ret != 0)
return 1;
lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
/*
* Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
* have a header that provide this informaiton.
*/ // kernel, dts的获取就是在这个函数内进行的。
if (bootm_find_images(flag, argc, argv))
return 1;
return 0;
}
* #### 2.1.1 bootz_setup
```c
struct zimage_header {
uint32_t code[9];
uint32_t zi_magic;
uint32_t zi_start;
uint32_t zi_end;
};
#define LINUX_ARM_ZIMAGE_MAGIC 0x016f2818
int bootz_setup(ulong image, ulong *start, ulong *end)
{
struct zimage_header *zi;
zi = (struct zimage_header *)map_sysmem(image, 0);
if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
puts("Bad Linux ARM zImage magic!\n");
return 1;
}
*start = zi->zi_start;
*end = zi->zi_end;
// 这里打印的信息
// Kernel image @ 0x82000000 [ 0x000000 - 0x417248 ]
printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image, *start,
*end);
return 0;
}
* #### 2.2 回到`do_bootz`, 再进去一次 `do_bootm_states`
* #### 这里开始就是有关怎么一步步跳入kernel 的流程分析
```c
// common/bootm.c
// 这个函数最主要的功能是获取kernel的启动函数,执行寻找设备树,并进入执行 kernel启动函数
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.
*/
if (states & BOOTM_STATE_START) // 如果有开始状态,执行bootm_start
ret = bootm_start(cmdtp, flag, argc, argv); ---> 这里跳到 2.2.1
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 */
}
/* Load the OS */
if (!ret && (states & BOOTM_STATE_LOADOS)) {
ulong load_end;
iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, &load_end, 0);
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;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
if (images->os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
}
// ... ...
/* From now on, we need the OS boot function */
if (ret)
return ret;
// 获取进入kernel 的函数, 上面可以获悉,参数是 IH_OS_LINUX
boot_fn = bootm_os_get_boot_func(images->os.os); // ---> 请跳到 2.2.2
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);
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))
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
// ... ...
/* Now run the OS! We hope this doesn't return */
if (!ret && (states & BOOTM_STATE_OS_GO)) // 执行进去kernel,如果有返回则出错
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO, // ----> 请跳到 2.2.3
images, boot_fn);
// ... ... 成功则不执行下面的,失败则执行后面的函数
}
</br>
* #### 2.2.1 看到上面的 `bootm_start`, 第一次,状态为 `BOOTM_STATE_START`
* #### 我们需要调用 `bootm_start(cmdtp, flag, argc, argv); `
```c
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{ // 初始化清空 images 结构体的空间
memset((void *)&images, 0, sizeof(images));
images.verify = getenv_yesno("verify"); // 这个我设置为 n 了
boot_start_lmb(&images); // 暂时没有搞懂
bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start"); // 空
images.state = BOOTM_STATE_START; // 设置了一个状态
return 0;
}
* #### 2.2.2 跳回上面的`do_bootm_states`,看到`bootm_os_get_boot_func`
* #### 获取kernel 的加载函数方式如下:
```c
// common/bootm_os.c 获取kernel 加载函数
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];
}
```c
// common/bootm_os.c
static boot_os_fn *boot_os[] = {
[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux, // 执行这个, 可以看 2.2.4
#endif
// ... ...
}
* #### 2.2.3 我们先回到上面的 `do_bootm_states` 函数,跟到最后面的 `boot_selected_os`
```c
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);
// 这里调用进入kernel 的函数
// 也就是上面返回的 do_bootm_linux()
/* Stand-alone may return when 'autostart' is 'no' */
if (images->os.type == IH_TYPE_STANDALONE ||
state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
return 0;
bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
#ifdef DEBUG
puts("\n## Control returned to monitor - resetting...\n");
#endif
return BOOTM_ERR_RESET;
}
* #### 2.2.4 do_bootm_linux
```c
// 参考 2.2.2 最后的返回值,执行的是这个
// arch/arc/lib/bootm.c // kernel 的加载函数长这个样子
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);
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); // 这里跳入Linux --> 2.2.5
return 0;
}
2.2.5 boot_jump_linux
// arch/arc/lib/bootm.c
/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
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);
// 这个在前面被分析出来是 0x82000000
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;
// ... ...
// ... ...
// 这里进入kernel 不再返回
kernel_entry(0, machid, r2);
// ... ...
}