👀u-boot版本说明
u-boot版本 | 备注 |
---|---|
4.1.15 | * |
一、bootz命令的操作流程
在u-boot的命令行下,当输入bootz命令后,u-boot将会进行怎样的操作流程?这一小节分析这个问题。
在u-boot源码中,通过U_BOOT_CMD
宏定义将命令与命令执行函数进行关联,从而在u-boot命令行中,可以通过输入bootz
命令来启动操作系统内核。
对于U_BOOT_CMD
这个命令注册宏替换的操作有些复杂,但是通过层层宏替换后,可以明确知道该宏替换完成后,本质是:定义一个cmd_tbl_s结构体类型的变量并初始化值,然后放在.u_boot_list
段中。如下图所示:
定义在文件:./include/command.h中
二、do_bootz()函数分析
通过前面分析,bootz实际的执行函数为do_bootz()
。该函数定义在./cmd/bootm.c文件中,如下代码所示:
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int ret;
/* Consume 'bootz' */
argc--; argv++;
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;
ret = do_bootm_states(cmdtp, flag, argc, argv,
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO,
&images, 1);
return ret;
}
三、bootz命令启动操作系统内核
在u-boot源码中,有一个重要的全局变量用于描述操作系统启动,那就是images
,该变量类型声明在./include/image.h文件中,变量定义在./cmd/bootm.c文件中。如下所示:
『images变量的定义』
『images类型的声明』
/*
* Legacy format image header,
* all data in network byte order (aka natural aka bigendian).
*/
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;
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;
/*
* 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 defined(CONFIG_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;
//声明一个在外部定义的bootm_headers_t类型的变量images
extern bootm_headers_t images;
bootz命令本质上会调用do_bootz函数进行操作,do_bootz函数的定义如下:
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int ret;
/* Consume 'bootz' */
argc--; argv++;
//1、bootz命令执行开始阶段的处理
if (bootz_start(cmdtp, flag, argc, argv, &images))
return 1;
/*
* We are doing the BOOTM_STATE_LOADOS state ourselves, so must
* disable interrupts ourselves
*/
//2、在BOOTM_STATE_LOADOS 状态下需要禁用中断
bootm_disable_interrupts();
//3、设置系统镜像为Linux
images.os.os = IH_OS_LINUX;
//4、调用do_bootm_states来执行不同的boot阶段,其中有:BOOTM_STATE_OS_PREP 、 BOOTM_STATE_OS_FAKE_GO 和 BOOTM_STATE_OS_GO三个阶段
ret = do_bootm_states(cmdtp, flag, argc, argv,
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO,
&images, 1);
return ret;
}
在do_bootz命令执行操作函数中,将执行4个连续的操作:
(1)bootz命令执行开始阶段的处理。 【bootz_start】
(2)在BOOTM_STATE_LOADOS 状态下禁用中断。
(3)设置系统镜像为Linux。
(4)调用do_bootm_states来执行不同的boot阶段,其中有:BOOTM_STATE_OS_PREP 、 BOOTM_STATE_OS_FAKE_GO >和 BOOTM_STATE_OS_GO三个阶段。【do_bootm_states】
『bootz_start函数定义』
/*
* zImage booting support
*/
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;
//1、执行bootm的BOOTM_STATE_START状态下的操作
ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
images, 1);
/* Setup Linux kernel zImage entry point */
//2、设置Linux内核zImage的入口点
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);
debug("* kernel: cmdline image address = 0x%08lx\n",
images->ep);
}
//3、【启动函数】bootz_setup函数是u-boot为特定平台预留的启动函数,不同的平台下需要自己实现
ret = bootz_setup(images->ep, &zi_start, &zi_end);
if (ret != 0)
return 1;
//4、预留逻辑内存Blocks
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.
*/
//5、查找和定位不同的ramdisk和设备树(dtb)文件
if (bootm_find_images(flag, argc, argv))
return 1;
#ifdef CONFIG_SECURE_BOOT
extern uint32_t authenticate_image(
uint32_t ddr_start, uint32_t image_size);
if (authenticate_image(images->ep, zi_end - zi_start) == 0) {
printf("Authenticate zImage Fail, Please check\n");
return 1;
}
#endif
return 0;
}
boot_start()函数需要进行五个连续操作:
(1)执行bootm的BOOTM_STATE_START状态下的操作。
(2)设置Linux内核zImage的入口点。
(3)【启动参数设置函数】bootz_setup函数是u-boot为特定平台预留的启动参数设置函数,不同的平台下需要不同实现。
(4)预留逻辑内存Blocks。
(5)查找和定位不同的ramdisk和设备树(dtb)文件。
『boot_setup函数定义』
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 //为ARM Linux系统魔术数
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;
printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image, *start,
*end);
return 0;
}
🔺【重磅角色】do_bootm_states
函数启动Linux内核过程。
在bootz_startup函数和do_bootz函数中都调用到了do_bootm_states函数,证明该函数的重要性。该函数的操作本质是:根据u-boot的不同状态来执行不同的操作。其中在u-boot中定义了以下一些阶段:
『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.
*/
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 */
}
/* 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
}
/* 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 defined(CONFIG_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;
//查找操作系统启动函数,该函数非常重要,在linux下,通过该函数查找到的启动函数操作实则是boot_fn = do_bootm_linux
boot_fn = bootm_os_get_boot_func(images->os.os);
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);
#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) {
puts("subcommand not supported\n");
return ret;
}
/* Now run the OS! We hope this doesn't return */
if (!ret && (states & 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;
}
do_bootm_state函数主要执行三个连续步骤:
(1)根据u-boot状态执行不同状态下的操作。
(2)查找对应操作系统的启动函数。【bootm_os_get_boot_func】
(3)启动已经选择好的操作系统。【boot_selected_os】
『bootm_os_get_boot_fun函数定义』
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];
}
bootm_os_get_boot_fun函数实则返回的是对应操作系统启动函数的函数指针,在u-boot源码中通过宏定义来选择对应的操作系统:
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
};
综上,如果定义了CONFIG_BOOTM_LINUX
宏,那么操作系统的启动函数则是:do_bootm_linux()
。接下来,将分析该函数。
『do_bootm_linux的定义』
从上图可见,该函数在不同的系统架构下都有定义,本文以ARM架构进行分析,函数定义如下:
/* Main Entry point for arm bootm implementation
*
* Modeled after the powerpc implementation
* DIFFERENCE: Instead of calling prep and go at the end
* they are called if subcommand is equal 0.
*/
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);
return 0;
}
do_bootm_linux主要涉及到两个连续操作:
(1)操作系统启动前的准备操作。【boot_prep_linux】
(2)启动跳转到linux。【boot_jump_linux】
『boot_prep_linux的定义』
/* Subcommand: PREP */
static void boot_prep_linux(bootm_headers_t *images)
{
char *commandline = getenv("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);
} else {
printf("FDT and ATAGS support not compiled in - hanging\n");
hang();
}
}
『boot_jump_linux()函数定义』:
/* Subcommand: GO */
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
/* 1、变量 machid 保存机器 ID,如果不使用设备树的话这个机器 ID 会被传递给 Linux
内核, Linux 内核会在自己的机器 ID 列表里面查找是否存在与 uboot 传递进来的 machid 相匹配的
项目,如果存在就证明Linux 内核支持这个机器,接着Linux内核就会启动。如果使用设备树的话这
个machid就无效了,设备树中有一个“兼容性”这个属性,Linux 内核会比较“兼容性”属性
的值(字符串)来查看是否支持这个机器。 */
unsigned long machid = gd->bd->bi_arch_number;
char *s;
/* 2、函数 kernel_entry本质上是一个函数指针,从名字“内核_进入”可知,说明该函数是进入Linux 内核的关键点,
该函数有三个参数:zero,arch,params,第一个参数 zero 同样为 0;第二个参数为机器 ID;第三个参数ATAGS
或者设备树(DTB)的首地址,ATAGS是传统的linux内核参数的传递方式,用于传递一些命令行信息的,如果使用设备树的话就要传递设备树(DTB)。*/
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
/* 3、获取 kernel_entry 函数,函数 kernel_entry 并不是在uboot源码中定义,而是在 Linux 内
核中定义的, Linux内核镜像文件的第一行代码就是函数 kernel_entry,而images->ep保存着Linux
内核镜像的起始地址,而起始地址保存的正是 Linux 内核第一行代码 */
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);
//4、调用函数 announce_and_cleanup 来打印一些信息并做一些清理工作
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
/* 5、调用 kernel_entry 函数进入 Linux 内核,此行将一去不复返,至此,进入Linux世界!!!*/
kernel_entry(0, machid, r2);
}
#endif
}}
#endif
}
四、总结
在u-boot引导启动linux内核的过程中,实则起着最关键作用的操作函数是do_bootm_linux()
,在该函数中完成了u-boot到linux内核的转换过程。