FIT(1):基于FIT的镜像创建和解析/启动
关键词:FIT、mkimage、dtc、its、itb、bootm、initrd、sha256等等、
Kernel中引入的Device Trace概念,将配置信息放入dtb中。达到一个kernel,结合不同dtb适配多个平台。
FIT是Flattened Image Tree的意思,即将多个镜像通过dts语法编译生成一个镜像文件。
uboot支持编写its文件,通过mkimage和dtc创建kernel、ramdisk、dtb等等文件的打包镜像。
这里的dts文件通常命名为.its,输出的镜像文件通常命名.itb。
1 FIT相关工具
安装mkimage工具:
sudo apt install u-boot-tools
还需要安装dtc:
sudo apt-get install device-tree-compiler
mkimage调用dtc创建itb文件。
2 FIT镜像创建流程
创建FIT镜像的命令:
mkimage -f multi.its fit.itb
查看FIT镜像头:
mkimage -l fit.itb
its(image tree source)是创建FIT镜像的配置脚本,itb(flattened image tree blob)是FIT镜像。
下面介绍如何创建its文件。
2.1 its示例
下面结合实例介绍its编写规则,完整的编写规则参考《Flattened Image Tree (FIT) Format》:
/dts-v1/; / { description = "Various kernels, ramdisks and FDT blobs";--对本镜像的描述。 #address-cells = <1>;--下面load/entry属性的格式,1表示只有一个地址。 timestamp = <12399321>--镜像修改时间。
images {--镜像节点,必须包含一个子节点。 kernel-1 {--子节点名称。 description = "Linux ARM kernel";--子节点描述。 data = /incbin/("arch/arm/boot/zImage");--子节点二进制数据。 type = "kernel";--子节点类型为IH_TYPE_KERNEL,可以是"standalone", "kernel", "kernel_noload", "ramdisk", "firmware", "script","filesystem", "flat_dt"等。
arch = "arm";--架构名称,比如arm等。 os = "linux";--OS名称。 compression = "none";--对data使用的压缩算法。 load = <0x830000e8>;--本数据加载到的地址,注意地址要正确。 entry = <0x830000e8>;--镜像启动入口地址。Linux的load地址也是entry地址。 hash-1 {--使用md5进行哈希。哈希值存放于value属性中。 algo = "crc32";--支持的hash算法包括:crc32、md5、sha1、sha256。 }; hash-2 {--使用sha256进行哈希。 algo = "sha256"; }; }; ramdisk { description = "Linux ramdisk"; data = /incbin/("rootfs.cpio.gz"); type = "ramdisk";--数据类型为ramdisk,对应类型为IH_TYPE_RAMDISK。 arch = "arm"; os = "linux"; compression = "none";--注意这里的数据虽然被gzip压缩了,但是不需要uboot处理。所以uboot认为的数据压缩类型为none。 hash-1 { algo = "crc32"; }; }; kernel-fdt { description = "NAND FDT blob"; data = /incbin/("nand.dtb"); type = "flat_dt"; arch = "arm"; compression = "none";
load = <0x86F00000>; hash-1 { algo = "crc32"; }; }; ramdisk-fdt { description = "RAMDISK FDT blob"; data = /incbin/("ramdisk.dtb"); type = "flat_dt";--子镜像类型为flat_dt,对应类型为IH_TYPE_FLATDT。 arch = "arm"; compression = "none"; load = <0x86F00000>;--dtb加载到指定地址。 hash-1 { algo = "sha1"; }; }; }; configurations {--提供对上述镜像的组合,有默认配置和多种配置可选。 default = "kernel";--默认配置,bootm未指定特殊配置时使用。 kernel {--配置名称。加载zImage和nand.dtb,rootfs从NAND中获取。 description = "Boot Linux kernel with FDT blob";--配置说明。 kernel = "kernel"; fdt = "kernel-fdt"; }; ramdisk {--加载zImage和dtb,文件系统通过rootfs.cpio.gz解压作为ramdisk使用。 description = "Boot Linux kernel with FDT blob and ramdisk"; kernel = "kernel";--指向kernel类型镜像。 ramdisk = "ramdisk";--指向ramdisk类型镜像。 fdt = "ramdiskfdt";--指向flat_dt类型数据。 }; }; };
另外如果数据位于外部,通过data-offset和data-size指定。
3 FIT镜像解析流程
3.1 bootm命令使用
通过bootm命令可以解析FIT镜像并启动:
bootm--后面没有地址则使用CONFIG_SYS_LOAD_ADDR地址作为启动地址。
bootm <addr>--使用default配置启动。
bootm <addr>:<image name>--单独加载FIT中<image name>镜像。
bootm <addr>#<config name>--使用指定<config name>启动。
bootm还支持将整个流程拆解为如下步骤:
start [addr [arg ...]] loados - load OS image ramdisk - relocate initrd, set env initrd_start/initrd_end fdt - relocate flat device tree cmdline - OS specific command line processing/setup bdt - OS specific bd_t processing prep - OS specific prep before relocation or go go - start OS
3.2 bootm流程解析
下面解析bootm关于FIT的处理流程:
do_bootm
->do_bootm_states--根据states调用一系列函数进行处理。
->bootm_start
->bootm_find_os--加载OS镜像。
->boot_get_kernel
->fit_image_load--从指定或默认configuration中加载kernel镜像。
->genimg_get_format--对于IMAGE_FORMAT_FIT类型镜像,从FIT中获取kernel镜像。
->fit_image_get_type--images.os.type。
->fit_image_get_comp--images.os.comp。
->fit_image_get_os--images.os.os。
->fit_image_get_arch--images.os.arch。
->fit_image_get_load--images.os.load。
->fit_image_get_entry--images.ep。
->bootm_find_other--加载OS相关镜像,比如ramdisk和dtb等。
->bootm_find_images
->boot_get_ramdisk--获取images.rd_start和images.rd_end。
->fit_image_load--从FIT镜像中获取ramdisk文件。
->boot_get_fdt--获取images.ft_addr和images.ft_len。
->boot_get_fdt_fit--从FIT中获取dtb文件。
->fit_image_load--根据fdt镜像名获取dtb文件。
->bootm_load_os
->image_decomp--解压缩kernel镜像。至此kernel镜像已经完成加载。
->boot_ramdisk_high--根据initrd_high的值否则分配地址移动initrd内容。自此ramdisk内容就绪。
->boot_relocate_fdt--根据fdt_high的值否则分区地址移动fdt内容。至此fdt内容就绪。
->bootm_os_get_boot_func--根据os类型从boot_os[]获取启动函数,linux对应do_bootm_linux()。
->boot_selected_os
->do_bootm_linux
->boot_prep_linux
->image_setup_linux
->boot_fdt_add_mem_rsv_regions
->boot_relocate_fdt
->image_setup_libfdt--根据需要更新dtb文件内容。
->fdt_root
->fdt_chosen
->arch_fixup_fdt
->fdt_initrd--创建或更新linux,initrd-start和linux,initrd-end节点。
->boot_jump_linux
->kernel_entry(0, machid, r2)--kernel_entry即为kernel镜像的entry属性,machid是从环境变量machid获取,r2为dtb地址。
随后进入Linux kernel启动,dtb从r2获取,initrd已在dtb中指定。kernel启动解析dtb,然后解压initrd内容形成ramfs。
3.3 Linux initrd解析
内核从start_kernel开始执行C代码,对initrd的处理如下:
start_kernel
->setup_arch
->setup_arch
->setup_machine_fdt--参数__atags_pointer即为从r2获取的dtb地址。
->early_init_dt_scan_nodes
->early_init_dt_scan_chosen
->early_init_dt_check_for_initrd--从dtb中获取linux,initrd-start和linux,initrd-end的值。
->__early_init_dt_declare_initrd--将initrd的起始和结束地址赋给initrd_start和initrd_end。
->arch_call_rest_init
->rest_init
->kernel_init
->kernel_init_freeable
->do_basic_setup
->do_initcalls
->do_initcall_level
->populate_rootfs
->unpack_to_rootfs(__initramfs_start, __initramfs_size);--优先解压内部ramfs。如果initrd_start为0或者强制使用initramfs,则使用initramfs。
->unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start)--其次解压从dtb传入的外部initrd。
4 小结
综上整个FIT方案包括如下部分:
- 编写its文件。
- 通过mkimage+dtc生成FIT镜像itb文件。
- uboot启动时通过bootm解析FIT镜像。
- 跳转到Linux执行镜像;如果是存在ramdisk,则作为initrd在Linux启动过程中解析创建ramfs。
FIT还有一个功能是对镜像内容进行校验,后续会继续分析基于FIT的校验和验签流程。