底软驱动 | U-boot启动内核

U-boot启动内核

整理自uboot启动kernel篇(一)——Legacy-uImage & FIT-uImage

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);

}
  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TrustZone_Hcoco

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值