Uboot引导操作系统
5. boot os
uboot 主要用来引导 linux , 一般情况下使用下列命令就可以启动嵌入式 linux :
echo load ramdisk
cp.b $ramdisk_addr 2000000 400000
echo load uImage
cp.b $kernel_addr 1000000 500000
echo load dtb
cp.b $dts_addr 3000000 4000
echo booting
bootm 0x1000000 0x2000000 0x3000000
上面的命令通过 cp
将 linux 系统的 ramdisk 、 设备树 、 kernel 从 flash 拷贝到内存,然后调用 bootm
命令启动系统。
或者,直接输入命令 boot
也可以启动系统,而实际上输入 boot 后 uboot 会自动执行上述命令。
5.1. 引导系统的命令
和引导系统有关的命令主要有 boot
、bootd
、bootm
、bootz
、bootvx
、bootp
go
,以及加载系统文件的命令 cp
、tftp
、fatload
、extload
、run
等。
以下是如要的 boot*
命令
-
boot
和bootd
等价,后者是以前的命令名,现在存在的唯一意义就是向后兼容。boot
命令是通过函数do_bootd()
实现的 :int do_bootd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { return run_command(getenv("bootcmd"), flag); }
如代码所示,
boot
实际上就是运行 shell 变量 bootcmd 所代表的一串命令,比如:bootcmd=run findfdt; run init_console; run envboot; run distro_bootcmd
boot
启动系统时只需要在 shell 输入命令本身即可,或者等待 uboot 自己调用boot
(uboot 默认会执行 boot 启动 linux)。 -
bootm
从内存引导系统,启动系统大多依赖这条命令(以及命令go
),bootm
启动 linux 时一般需要三个参数 : kernel 地址、ramdisk 地址和设备树地址,比如:bootm $kernel_addr $ramdisk_addr $dtb_addr
-
bootz
用来启动内存中的 zImage linux 系统镜像。参数于bootm
相同
5.2. 引导系统的过程
boot
命令本身很简单,其实现依赖于bootm
。bootm
和bootz
引导 linux 都主要依赖函数do_bootm_states()
。
bootm 的实现(p1020 和 zynq 使用了这种方式):
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
...
/* determine if we have a sub command */
argc--; argv++;
if (argc > 0) {
char *endp;
simple_strtoul(argv[0], &endp, 16);
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);
}
bootz 的实现(beaglebone black 采用了这种方式):
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
...
if (bootz_start(cmdtp, flag, argc, argv, &images))
return 1;
...
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);
...
}
可以看出 bootz
和 bootm
要引导系统最终都由 do_bootm_states()
完成。 do_bootm()
里的 do_bootm_subcommand()
只是用来处理一些 bootm
的字命令。do_bootz()
中的 bootz_start()
也只是为最后启动操作系统做一些检查和预备工作。
do_bootm_subcommand()
的实现很简单只是执行对应的命令函数,然后标记一下启动的过程。
static int do_bootm_subcommand(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
int ret = 0;
long state;
cmd_tbl_t *c;
c = find_cmd_tbl(argv[0], &cmd_bootm_sub[0], ARRAY_SIZE(cmd_bootm_sub));
argc--; argv++;
if (c) {
state = (long)c->cmd;
if (state == BOOTM_STATE_START)
state |= BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER;
} else {
/* Unrecognized command */
return CMD_RET_USAGE;
}
if (((state & BOOTM_STATE_START) != BOOTM_STATE_START) &&
images.state >= state) {
printf("Trying to execute a command out of order\n");
return CMD_RET_USAGE;
}
ret = do_bootm_states(cmdtp, flag, argc, argv, state, &images, 0);
return ret;
}
bootz_start()
会设置好 zImage 的入口,加载 ramdisk 、设备树和其他一下琐碎到内存。
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;
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);
debug("* kernel: cmdline image address = 0x%08lx\n",
images->ep);
}
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.
*/
if (bootm_find_images(flag, argc, argv))
return 1;
return 0;
}
bootm_find_images()
加载 ramdisk 和设备树等文件到内存:
int bootm_find_images(int flag, int argc, char * const argv[])
{
int ret;
/* find ramdisk */
ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
&images.rd_start, &images.rd_end);
...
#if IMAGE_ENABLE_OF_LIBFDT
/* find flattened device tree */
ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
&images.ft_addr, &images.ft_len);
if (ret) {
puts("Could not find a valid device tree\n");
return 1;
}
set_working_fdt_addr((ulong)images.ft_addr);
#endif
...
return 0;
}
do_bootm_states()
/**
* Execute selected states of the bootm command.
*
* Note the arguments to this state must be the first argument, Any 'bootm'
* or sub-command arguments must have already been taken.
*
* Note that if states contains more than one flag it MUST contain
* BOOTM_STATE_START, since this handles and consumes the command line args.
*
* Also note that aside from boot_os_fn functions and bootm_load_os no other
* functions we store the return value of in 'ret' may use a negative return
* value, without special handling.
*
* @param cmdtp Pointer to bootm command table entry
* @param flag Command flags (CMD_FLAG_...)
* @param argc Number of subcommand arguments (0 = no arguments)
* @param argv Arguments
* @param states Mask containing states to run (BOOTM_STATE_...)
* @param images Image header information
* @param boot_progress 1 to show boot progress, 0 to not do this
* @return 0 if ok, something else on error. Some errors will cause this
* function to perform a reboot! If states contains BOOTM_STATE_OS_GO
* then the intent is to boot an OS, so this function will not return
* unless the image type is standalone.
*/
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 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);
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;
}
从注释就可以看出该函数要启动 Linux 需要的参数:启动命令(cmdtp,argv)、linux 镜像头(images)以及启动过程和状态(flag,states,boot_progress)。
states 用来判断引导系统进行到了哪一步,不同阶段有不同的分支代码要执行。如代码中的 ret = bootm_start(cmdtp, flag, argc, argv);
(设置之后获取到的内核镜像的大小和位置) , ret = bootm_find_os(cmdtp, flag, argc, argv);
(获取要引导的内核镜像) 等。
分支 if (!ret && (states & BOOTM_STATE_LOADOS)) { ... }
会将内核解压、读取到内存之前设定位置。接下来就是加载 ramdisk 和 设备树。如果上面几步都执行正确,那么接下来就是根据不同的内核类型获取不同系统引导函数:
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);
执行系统引导函数时还要根据 states 的内容给 boot_fn
传入不同的参数。在最后启动、进入 linux 之前,如果还要进行追踪,可以调用 getenv()
和 run_command_list()
获取并执行命令(比如执行 fakegocmd 进行追踪)。最后 uboot 执行函数 boot_selected_os()
启动操作系统,其实最终调用的还是 boot_fn()
,不过传入的参数与之前不同而已。
boot_fn()
实际启动系统的函数 boot_fn()
实际上有一组函数,在 do_bootm_states()
中通过 bootm_os_get_boot_func()
获取:
boot_fn = bootm_os_get_boot_func(images->os.os);
boot_os_fn *bootm_os_get_boot_func(int os)
{
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
[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
};
从代码可以看出 uboot 可以支持引导多种操作系统,比如 Linux 、 NetBSD 、 VxWorks 、 OpenRTOS 等。以引导 Linux 的 do_bootm_linux()
为例。
do_bootm_linux()
是区分架构的,比如 arm 、powerpc 、 mips 、 x86 等。
1 F f do_bootm_linux arch/arc/lib/bootm.c
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
2 F f do_bootm_linux arch/arm/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[],
3 F f do_bootm_linux arch/avr32/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images)
4 F f do_bootm_linux arch/blackfin/lib/boot.c
int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images)
5 F f do_bootm_linux arch/m68k/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images)
6 F f do_bootm_linux arch/microblaze/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[],
7 F f do_bootm_linux arch/mips/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[],
8 F f do_bootm_linux arch/nds32/lib/bootm.c
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
9 F f do_bootm_linux arch/nios2/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images)
10 F f do_bootm_linux arch/openrisc/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[],
11 F f do_bootm_linux arch/powerpc/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images)
12 F f do_bootm_linux arch/sandbox/lib/bootm.c
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
13 F f do_bootm_linux arch/sh/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images)
14 F f do_bootm_linux arch/sparc/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t * images)
15 F f do_bootm_linux arch/x86/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[],
只看 arm 版的 do_bootm_linux()
的代码:
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;
}
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);
} else {
printf("FDT and ATAGS support not compiled in - hanging\n");
hang();
}
}
int image_setup_linux(bootm_headers_t *images)
{
ulong of_size = images->ft_len;
char **of_flat_tree = &images->ft_addr;
ulong *initrd_start = &images->initrd_start;
ulong *initrd_end = &images->initrd_end;
struct lmb *lmb = &images->lmb;
ulong rd_len;
int ret;
if (IMAGE_ENABLE_OF_LIBFDT)
boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);
if (IMAGE_BOOT_GET_CMDLINE) {
ret = boot_get_cmdline(lmb, &images->cmdline_start,
&images->cmdline_end);
if (ret) {
puts("ERROR with allocation of cmdline\n");
return ret;
}
}
if (IMAGE_ENABLE_RAMDISK_HIGH) {
rd_len = images->rd_end - images->rd_start;
ret = boot_ramdisk_high(lmb, images->rd_start, rd_len,
initrd_start, initrd_end);
if (ret)
return ret;
}
if (IMAGE_ENABLE_OF_LIBFDT) {
ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);
if (ret)
return ret;
}
if (IMAGE_ENABLE_OF_LIBFDT && of_size) {
ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb);
if (ret)
return ret;
}
return 0;
}
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
kernel_entry(0, machid, r2);
}
#endif
}
boot_prep_linux()
和 image_setup_linux()
用来做一些正式启动前的处理工作(此处待补充), boot_jump_linux()
用来生成进入系统的入口点(kernel_entry
),而入口点的信息都是之前函数处理好的,此处只是进行一个跳转而已:
kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
void *res2))images->ep;
...
if (!fake) {
do_nonsec_virt_switch();
kernel_entry(images->ft_addr, NULL, NULL, NULL);
}
到此就要进入 linux 了,结束了 uboot 的代码,arm 版的 boot_fn()
结束。
kernel_entry
的实现
在 uboot 中执行了 kernel_entry()
就要进入 Linux 了。
...
kernel_entry(images->ft_addr, NULL, NULL, NULL);
...
这在一般的 bootloader 中很常见,对于一般的系统来说,kernel_entry()
实际上指向了对应操作系统的入口函数的地址,比如 main()
,根据需要或许还要传入一些参数,执行 main()
时就已经进入到 操作系统的代码了,操作系统接管 CPU 开始了它自己的初始化、配置等工作。