U-boot启动内核
1.uImage
内核编译后会生成Image或者压缩的zImage,但这两种格式缺少足够信息供u-boot进行load、jump或验证操作;因此u-boot提供mkimage工具,用以生成u-boot可以识别的格式,称为uImage
1.1 Legacy-uImage类型
在kernel镜像的基础上,附加64Byte的信息在头部供u-boot使用
优点是实现简单,长度较小;
缺点是使用麻烦,需要在启动kernel命令中额外添加fdt、ramdisk等信息
1.2 FIT-uImage类型
类似FDT模式,把kernel,fdt,ramdisk等镜像打包到一个image file文件中,外加一些额外信息;u-boot获得这个image以后,就可以得到其中各镜像的具体信息和内容
优点是使用方便,兼容性好
缺点是长度较大,需要额外信息较长
2.Legacy-uImage
2.1 配置宏,使能
// 自动生成autoconf.mk时会自动使能,不需要额外配置
CONFIG_IMAGE_FORMAT_LEGACY=y
2.2 制作和使用
a) 制作
// mkimage工具目录
xingyanl@yocto:~$ ls -lrt uboot/tools/mkimage
-rwxrwxr-x 1 xingyanl xingyanl 164650 Dec 26 15:14 uboot/tools/mkimage
xingyanl@yocto:~$
xingyanl@yocto:~$ mkimage -A arm -O linux -C none -T kernel -a 0x20008000 -e 0x20008040 -n Linux_Image -d zImage uImage
参数含义:
Usage: mkimage -l image
-l ==> list image header information
mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch' // 体系
-O ==> set operating system to 'os' // 操作系统
-T ==> set image type to 'type' // 镜像类型
-C ==> set compression type 'comp' // 压缩类型
-a ==> set load address to 'addr' (hex) // 加载地址
-e ==> set entry point to 'ep' (hex) // 入口地址
-n ==> set image name to 'name' // 镜像名称,注意不能超过32B
-d ==> use image data from 'datafile' // 输入文件
-x ==> set XIP (execute in place)
b) 使用
// 使用bootm命令下载Legacy-uImage到指定位置
bootm Legacy-uImage加载地址 ramdisk加载地址 dtb加载地址
2.3 和zImage的区别
// 可得uImage比zImage多了64Byte数据
-rw-rw-rw- 1 hlos hlos 1560120 Dec 5 14:46 uImage
-rwxrwxrwx 1 hlos hlos 1560056 Nov 30 15:42 zImage*
// 通过od命令查看这64Byte的数据,并且加在了zImage的头部
xingyanl@yocto:~$ od -tx1 -tc -Ax -N64 uImage
000000 27 05 19 56 5a f3 f7 8e 58 45 0d 3d 00 17 cd f8
' 005 031 V Z 363 367 216 X E \r = \0 027 315 370
000010 20 00 80 00 20 00 80 40 e2 4b 43 b6 05 02 02 00
\0 200 \0 \0 200 @ 342 K C 266 005 002 002 \0
000020 4c 69 6e 75 78 5f 49 6d 61 67 65 00 00 00 00 00
L i n u x _ I m a g e \0 \0 \0 \0 \0
000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
\0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
2.4 格式头说明
// u-boot中使用image_header表示legacy-uImage的头部
typedef struct image_header {
__be32 ih_magic; // 魔数头27 05 19 56,用来校验是否是Legacy-uImage
__be32 ih_hcrc; // 头部的CRC校验值
__be32 ih_time; // 镜像创建的时间戳
__be32 ih_size; // 镜像数据长度
__be32 ih_load; // 加载地址
__be32 ih_ep; // 入口地址
__be32 ih_dcrc; // 镜像的CRC校验
uint8_t ih_os; // 操作系统类型
uint8_t ih_arch; // 体系
uint8_t ih_type; // 镜像类型
uint8_t ih_comp; // 压缩类型
uint8_t ih_name[IH_NMLEN]; // 镜像名
} image_header_t;
#define IH_NMLEN 32 // 镜像名最大长度
3. FIT-uImage
**FIT:flattened image tree,类似于FDT(flattened device tree)**的一种实现机制,它使用一定语法和格式把需要的镜像(如kernel,dtb等)组合到一起生成一个image文件
主要使用四个组件:
a) **its(image source file)文件:**类似于dts,负责描述image的信息,需要自行构造
b) **itb文件: **最终得到的image文件,类似于dtb文件,u-boot直接对其识别和解析
c) **mkimage:**负责dtc的角色,通过解析its生成itb
d) **image data file:**实际使用到的镜像文件
mkimage将its及对应的image data file打包成一个itb,即uboot可识别的image file(FIT-uImage); 把文件下载到memory中,使用bootm命令就可以执行了
3.1 配置宏,使能
// 需要配置使能
CONFIG_FIT=y
3.2 制作
3.2.1 its文件制作
// 制作its文件, 其中需要描述被打包的镜像, 主要是kernel镜像, dtb文件和ramdisk镜像
// 示例 [u-boot FIT image介绍](http://www.wowotech.net/u-boot/fit_image_overview.html)
/dts-v1/;
/ {
description = "U-Boot uImage source file for X project";
#address-cells = <1>;
images {
kernel@tiny210 {
description = "Unify(TODO) Linux kernel for project-x";
data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/linux/arch/arm/boot/zImage");
type = "kernel";
arch = "arm";
os = "linux";
compression = "none";
load = <0x20008000>;
entry = <0x20008000>;
};
fdt@tiny210 {
description = "Flattened Device Tree blob for project-x";
data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/linux/arch/arm/boot/dts/s5pv210-tiny210.dtb");
type = "flat_dt";
arch = "arm";
compression = "none";
};
ramdisk@tiny210 {
description = "Ramdisk for project-x";
data = /incbin/("/home/hlos/code/xys/temp/project-x/build/out/rootfs/initramfs.gz");
type = "ramdisk";
arch = "arm";
os = "linux";
compression = "gzip";
};
};
configurations {
default = "conf@tiny210";
conf@tiny210 {
description = "Boot Linux kernel with FDT blob";
kernel = "kernel@tiny210";
fdt = "fdt@tiny210";
ramdisk = "ramdisk@tiny210";
};
};
};
可以有多个kernel节点或者fdt节点等, 提高兼容性; 并且可以对kernel、fdt和ramdisk进行多种组合生成多种configurations, 使FIT-uImage可以兼容于多种板子,而无需重新进行编译烧写
3.2.2 itb文件制作
// 生成itb文件
xingyanl@yocto:~$ mkimage -f ${UIMAGE_ITS_FILE} ${UIMAGE_ITB_FILE}
3.3 使用
// 下载FIT-uImage到指定地址
xingyanl@yocto:~$ bootm "指定地址"
总结:FIT-uImage的格式类似于DTB,uboot通过解析FIT-uImage中的configurations节点,根据节点选择出需要的kernel、dtb、rootfs
1. bootm简要说明
宏定义使能:
CONFIG_BOOTM_LINUX=y
CONFIG_CMD_BOOTM=y
1.1 概要
bootm用于启动一个操作系统映像, 它会从映像文件的头部读取一些信息,如:映像文件的的cpu架构、操作系统类型、映像的类型、压缩方式、映像文件在内存中的加载地址、映像文件运行的入口地址、映像文件名等
1.2 使用方式
a) 对于Legacy-uImage
// 假设Legacy-uImage的加载地址是0x20008000,ramdisk的加载地址是0x21000000,fdt的加载地址是0x22000000
// 只加载kernel
bootm 0x20008000
// 加载kernel和ramdisk
bootm 0x20008000 0x21000000
// 加载kernel和fdt
bootm 0x20008000 - 0x22000000
// 加载kernel、ramdisk、fdt
bootm 0x20008000 0x21000000 0x22000000
b) 对于FIT-uImage
// 假设FIT-uImage的加载地址是0x30000000
// 启动内核
bootm 0x30000000
2. 执行流程
2.1 bootm函数
// 以bootm 0x20008000 0x21000000 0x22000000为例
// 通过U_BOOT_CMD找到对应命令do_bootm
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory", bootm_help_text
);
// cmdtp:_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[])
{
// 去掉bootm参数
....
// 此时argc=3
// argv[0]=0x20008000 , argv[1]=0x21000000, argv[2]=0x22000000
return do_bootm_states(cmdtp, flag, argc, argv, ...., &images, 1);
}
2.2 do_bootm_states函数
2.2.1 bootm_headers_t数据结构
bootm根据参数指向的镜像填充此结构体,然后使用结构体的内容填充kernel的启动信息,进行跳转
typedef struct bootm_headers {
image_header_t *legacy_hdr_os; // Legacy-uImage的镜像头
image_header_t legacy_hdr_os_copy; // Legacy-uImage的镜像头备份
ulong legacy_hdr_valid; // Legacy-uImage的镜像头是否存在的标记
#if IMAGE_ENABLE_FIT // 使能FIT
const char *fit_uname_cfg; // 配置节点名
void *fit_hdr_os; // FIT-uImage中kernel镜像头
const char *fit_uname_os; // FIT-uImage中kernel的节点名
int fit_noffset_os; // FIT-uImage中kernel的节点偏移
void *fit_hdr_rd; // FIT-uImage中ramdisk的镜像头
const char *fit_uname_rd; // FIT-uImage中ramdisk的节点名
int fit_noffset_rd; // FIT-uImage中ramdisk的节点偏移
void *fit_hdr_fdt; // FIT-uImage中FDT的镜像头
const char *fit_uname_fdt; // FIT-uImage中FDT的节点名
int fit_noffset_fdt; // FIT-uImage中FDT的节点偏移
#endif
image_info_t os; // 操作系统信息的结构体
ulong ep; // 操作系统的入口地址
ulong rd_start, rd_end; // ramdisk在内存上的起始地址和结束地址
char *ft_addr; // fdt在内存上的地址
ulong ft_len; // fdt在内存上的长度
int state; // 状态标识,用于标识对应的bootm需要做什么操作
} bootm_headers_t;
2.2.2 状态说明
do_bootm_states根据states来判断要执行的操作
// 开始执行bootm的一些准备动作
#define BOOTM_STATE_START (0x00000001)
// 查找操作系统镜像
#define BOOTM_STATE_FINDOS (0x00000002)
// 查找操作系统镜像外的其他镜像,比如FDT\ramdisk等等
#define BOOTM_STATE_FINDOTHER (0x00000004)
// 加载操作系统
#define BOOTM_STATE_LOADOS (0x00000008)
// 操作ramdisk
#define BOOTM_STATE_RAMDISK (0x00000010)
// 操作FDT
#define BOOTM_STATE_FDT (0x00000020)
// 操作commandline
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
// 跳转到操作系统的前的准备动作
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
// 伪跳转,一般都能直接跳转到kernel中去
#define BOOTM_STATE_OS_FAKE_GO (0x00000200)
// 跳转到kernel中去
#define BOOTM_STATE_OS_GO (0x00000400)
2.3 执行流程
// 根据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)
{
// *****************解析uImage***************************//
// 保存states到bootm_headers_t images内部
images->state |= states;
// BOOTM_STATE_START
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);
// BOOTM_STATE_FINDOS
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);
// BOOTM_STATE_FINDOTHER
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ret = bootm_find_other(cmdtp, flag, argc, argv);
argc = 0; /* consume the args */
}
// ********************解析uImage结束************************//
// BOOTM_STATE_LOADOS
if (!ret && (states & BOOTM_STATE_LOADOS)) {
ulong load_end;
iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, &load_end, 0);
}
// 是否需要重定向ramdinsk,do_bootm流程不需要
#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);
}
#endif
// 是否需要重定向fdt,do_bootm流程不需要
#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
// 获取OS启动函数, 如下:
boot_fn = bootm_os_get_boot_func(images->os.os);
// 跳转到OS前的准备动作,会直接调用启动函数,但是标识是BOOTM_STATE_OS_PREP
if (!ret && (states & BOOTM_STATE_OS_PREP))
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
// BOOTM_STATE_OS_GO标识,跳转到操作系统中,并且不应该再返回了
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);
}
// 根据OS类型获得到对应的操作函数
boot_os_fn *bootm_os_get_boot_func(int os)
{
return boot_os[os];
}
// 获取启动函数
static boot_os_fn *boot_os[] = {
// linux内核启动函数
[IH_OS_LINUX] = do_bootm_linux,
....
}
跳转到内核
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
// 实现跳转到linux前的准备动作
boot_prep_linux(images);
// 跳转到linux内核中
boot_jump_linux(images, flag);
}
// 主要任务是修复LMB,并把它填入到fdt中
// LMB是指Logical Memory blocks,用于表示内存保留区域,主要有fdt的区域,ramdisk区域等
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
if (image_setup_linux(images)) {
hang();
}
#endif
}
/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
// 从gd中获取machine-id
unsigned long machid = gd->bd->bi_arch_number;
// kernel入口函数,即kernel的入口地址,对应kernel的_start段地址
void (*kernel_entry)(int zero, int arch, uint params);
// 伪跳转,并不真正地跳转到kernel中
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
// 设置kernel的入口地址
kernel_entry = (void (*)(int, int, uint))images->ep;
// 把fdt的地址放人r2寄存器中
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
// 执行kernel_entry, 跳转到kernel内核中
kernel_entry(0, machid, r2);
}