一、bootm说明
bootm这个命令用于启动一个操作系统映像。它会从映像文件的头部取得一些信息,这些信息包括:映像文件的基于的cpu架构、其操作系统类型、映像的类型、压缩方式、映像文件在内存中的加载地址、映像文件运行的入口地址、映像文件名等。
紧接着bootm将映像加载到指定的地址,如果需要的话,还会解压映像并传递必要有参数给内核,最后跳到入口地址进入内核。
这里的描述参考(http://blog.chinaunix.net/uid-20799298-id-99666.html)
需要打开的宏
CONFIG_BOOTM_LINUX=y
CONFIG_CMD_BOOTM=y
二、bootm使用方式
在《uboot启动kernel篇(一)——Legacy-uImage & FIT-uImage》中我们知道了uImage有两种格式。
- Legacy-uImage
对于Legacy-uImage,我们需要另外加载ramdisk和fdt到RAM上面。
执行的命令如下
假设Legacy-uImage的加载地址是0x20008000,ramdisk的加载地址是0x21000000,fdt的加载地址是0x22000000
(1) 只加载kernel的情况下
bootm 0x20008000
(2) 加载kernel和ramdisk
bootm 0x20008000 0x21000000
(3) 加载kernel和fdt
bootm 0x20008000 - 0x22000000
(4) 加载kernel、ramdisk、fdt
bootm 0x20008000 0x21000000 0x22000000
- FIT-uImage
对于FIT-uImage,kernel镜像、ramdisk镜像和fdt都已经打包到FIT-uImage的镜像中了。
执行的命令如下
假设FIT-uImage的加载地址是0x30000000,启动kernel的命令如下:
bootm 0x30000000
三、bootm执行流程
建议先参考《[uboot] (第六章)uboot流程——命令行模式以及命令处理介绍》。
- 对应U_BOOT_CMD
我们找到bootm命令对应的U_BOOT_CMD如下:
cmd/bootm.c
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory", bootm_help_text
);
当执行‘bootm 0x20008000 0x21000000 0x22000000’
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
cmdtp:传递的是bootm的命令表项指针,也就是_u_boot_list_2_cmd_2_bootm的指针
argc=4
argv[0]="bootm", argv[1]=0x20008000, arv[2]=0x21000000, argv[3]=0x22000000
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
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 |
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO, &images, 1);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
所以bootm的核心是do_bootm_states,以全局变量bootm_headers_t images作为do_bootm_states的参数。
下面详细说明这个函数。
四、do_bootm_states软件流程
1、数据结构说明
- bootm_headers_t
bootm_headers_t用来表示bootm启动kernel的一些信息的结构体,其中包括了os/initrd/fdt images的信息。
bootm会根据参数以及参数指向的镜像来填充这个结构题里面的成员。
最终再使用这个结构体里面的信息来填充kernel启动信息并且到跳转到kernel中。
在uboot中使用了一个全局的bootm_headers_t images。
typedef struct bootm_headers {
image_header_t *legacy_hdr_os;
image_header_t legacy_hdr_os_copy;
ulong legacy_hdr_valid;
#if IMAGE_ENABLE_FIT
const char *fit_uname_cfg;
void *fit_hdr_os;
const char *fit_uname_os;
int fit_noffset_os;
void *fit_hdr_rd;
const char *fit_uname_rd;
int fit_noffset_rd;
void *fit_hdr_fdt;
const char *fit_uname_fdt;
int fit_noffset_fdt;
#endif
image_info_t os;
ulong ep;
ulong rd_start, rd_end;
char *ft_addr;
ulong ft_len;
ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
int verify;
int state;
#ifdef CONFIG_LMB
struct lmb lmb;
#endif
} bootm_headers_t;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
2、状态说明
- BOOTM_STATE_START
#define BOOTM_STATE_START (0x00000001)
开始执行bootm的一些准备动作。 - BOOTM_STATE_FINDOS
#define BOOTM_STATE_FINDOS (0x00000002)
查找操作系统镜像 - BOOTM_STATE_FINDOTHER
#define BOOTM_STATE_FINDOTHER (0x00000004)
查找操作系统镜像外的其他镜像,比如FDT\ramdisk等等 - BOOTM_STATE_LOADOS
#define BOOTM_STATE_LOADOS (0x00000008)
加载操作系统 - BOOTM_STATE_RAMDISK
#define BOOTM_STATE_RAMDISK (0x00000010)
操作ramdisk - BOOTM_STATE_FDT
#define BOOTM_STATE_FDT (0x00000020)
操作FDT - BOOTM_STATE_OS_CMDLINE
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
操作commandline - BOOTM_STATE_OS_BD_T
#define BOOTM_STATE_OS_BD_T (0x00000080) - BOOTM_STATE_OS_PREP
#define BOOTM_STATE_OS_PREP (0x00000100)
跳转到操作系统的前的准备动作 - BOOTM_STATE_OS_FAKE_GO
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* ‘Almost’ run the OS */
伪跳转,一般都能直接跳转到kernel中去 - BOOTM_STATE_OS_GO
#define BOOTM_STATE_OS_GO (0x00000400)
跳转到kernel中去
3、软件流程说明
do_bootm_states根据states来判断要执行的操作。
- 主要流程简单说明如下:
- bootm的准备动作
BOOTM_STATE_START - 获取kernel信息
BOOTM_STATE_FINDOS - 获取ramdisk和fdt的信息
BOOTM_STATE_FINDOTHER - 加载kernel到对应的位置上(有可能已经就在这个位置上了)
BOOTM_STATE_LOADOS - 重定向ramdisk和fdt(不一定需要)
BOOTM_STATE_RAMDISK、BOOTM_STATE_FDT - 执行跳转前的准备动作
BOOTM_STATE_OS_PREP - 设置启动参数,跳转到kernel所在的地址上
BOOTM_STATE_OS_GO
在这些流程中,起传递作用的是bootm_headers_t images这个数据结构,有些流程是解析镜像,往这个结构体里写数据。
而跳转的时候,则需要使用到这个结构体里面的数据。
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;
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;
}
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
}
#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
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;
}
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);
if (ret) {
puts("subcommand not supported\n");
return ret;
}
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);
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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
主要用到如下依次几个函数来实现:
- bootm_start
- bootm_find_os
- bootm_find_other
- bootm_load_os
- bootm_os_get_boot_func
- boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
- boot_selected_os
- boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);
4、bootm_start & bootm_find_os & bootm_find_other
主要负责解析环境变量、参数、uImage,来填充bootm_headers_t images这个数据结构。
最终目的是实现bootm_headers_t images中的这几个成员:
typedef struct bootm_headers {
image_info_t os;
ulong ep;
ulong rd_start, rd_end;
char *ft_addr;
ulong ft_len;
ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
int verify;
#ifdef CONFIG_LMB
struct lmb lmb;
#endif
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- bootm_start
实现verify和lmb - bootm_find_os
实现os和ep。
也就是说,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员。
放在《uboot启动kernel篇(三)——解析uImage的kernel镜像》中具体说明。 - bootm_find_other
实现rd_start, rd_end,ft_addr和initrd_end。
也就是说,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员。
放在《uboot启动kernel篇(四)——解析uImage的fdt》和《uboot启动kernel篇(五)——解析uImage的ramdisk》中具体说明。
5、bootm_load_os
简单说明一下,在bootm_load_os中,会对kernel镜像进行load到对应的位置上,并且如果kernel镜像是被mkimage压缩过的,那么会先经过解压之后再进行load。(这里要注意,这里的压缩和Image压缩成zImage并不是同一个,而是uboot在Image或者zImage的基础上进行的压缩!!!)
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
结果上述步骤之后,kernel镜像就被load到对应位置上了。
6、bootm_os_get_boot_func
bootm_os_get_boot_func用于获取到对应操作系统的启动函数,被存储到boot_fn 中。
如下:
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_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[] = {
...
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
#endif
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
可以看出最终启动linux的核心函数是do_bootm_linux。
另外几个函数最终也是调用到boot_fn,对应linux也就是do_bootm_linux,所以这里不在说明了。
下面继续说明一下do_bootm_linux的流程
五、do_bootm_linux
arch/arm/lib/bootm.c
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
boot_prep_linux用于实现跳转到linux前的准备动作。
而boot_jump_linux用于跳转到linux中。
都是以全局变量bootm_headers_t images为参数,这样就可以直接获取到前面步骤中得到的kernel镜像、ramdisk以及fdt的信息了。
- boot_prep_linux
首先要说明一下LMB的概念。LMB是指logical memory blocks,主要是用于表示内存的保留区域,主要有fdt的区域,ramdisk的区域等等。
boot_prep_linux主要的目的是修正LMB,并把LMB填入到fdt中。
实现如下:
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
}
这里没有深入学习image_setup_linux,等后续有需要的话再进行深入。
- boot_jump_linux
以arm为例:
arch/arm/lib/bootm.c
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);
kernel_entry = (void (*)(int, int, uint))images->ep;
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) {
kernel_entry(0, machid, r2);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
到这里,经过kernel_entry之后就跳转到kernel环境中了。有兴趣可以看一下kernel的启动流程。
================================================================================
kernel启动流程文章
[kernel 启动流程] 前篇——vmlinux.lds分析
[kernel 启动流程] (第一章)概述
[kernel 启动流程] (第二章)第一阶段之——设置SVC、关闭中断
[kernel 启动流程] (第三章)第一阶段之——proc info的获取
[kernel 启动流程] (第四章)第一阶段之——dtb的验证
[kernel 启动流程] (第五章)第一阶段之——临时内核页表的创建
[kernel 启动流程] (第六章)第一阶段之——打开MMU
[kernel 启动流程] (第七章)第一阶段之——跳转到start_kernel