FIT vs legacy image format
U-Boot supports several image formats when booting a kernel. However, a Linux system usually need multiple files for booting. Such files may be the kernel itself, an initrd and a device tree blob.
A typical embedded Linux system have all these files in at least two-three different configurations. It's not uncommon to have a
- Default configuration
- Rescue configuration
- Development configuration
- Production configuration
- ...
Only these four configurations may end up with unmanageable amount of different files. Maybe the devicetree is shared between two configurations. Maybe the initrd is... Or maybe the..... you got the point. It has a fairly good chance to end up quite messy.
This is a problem with the old legacy formats and is somehow addressed in the FIT (Flattened Image Tree) format.
Lets first look at the old formats before we get into FIT.
zImage
The most well known format for the Linux kernel is the zImage. The zImage contains a small header followed by a self-extracting code and finally the payload itself. U-Boot support this format by the bootz command.
Image layout
zImage format |
---|
Header |
Decompressing code |
Compressed Data |
uImage
When talking about U-Boot, the zImage is usually encapsulated in a file called uImage created with the mkimage utility. Beside image data, the uImage also contains information such as OS type, loader information, compression type and so on. Both data and header is checksumed with CRC32.
The uImage format does also support multiple images. The zImage, initrd and devicetree blob may therefor be included in one single monolithic uImage. Drawbacks with this monolith is that it has no flexible indexing, no hash integrity and no support for security at all.
Image layout
uImage format |
---|
Header |
Header checksum |
Data size |
Data load address |
Entry point address |
Data CRC |
OS, CPU |
Image type |
Compression type |
Image name |
Image data |
FIT
The FIT (Flattened Image Tree) has been around for a while but is for unknown reason rarely used in real systems based on my experience. FIT is a tree structure like Device Tree (before they change format to YAML: https://lwn.net/Articles/730217/) and handles images of various types.
These images is then used in something called configurations. One image may be a used in several configurations.
This format has many benefits compared to the other:
- Better solutions for multi component images
- Multiple kernels (productions, feature, debug, rescue...)
- Multiple devicetrees
- Multiple initrds
- Better hash integrity of images. Supports different hash algorithms like
- SHA1
- SHA256
- MD5
- Support signed images
- Only boot verified images
- Detect malware
Image Tree Source
The file describing the structure is called .its (Image Tree Source). Here is an example of a .its file :
1 /dts-v1/;
2
3 / {
4 description = "Marcus FIT test";
5 #address-cells = <1>;
6
7 images {
8 kernel@1 {
9 description = "My default kenel";
10 data = /incbin/("./zImage");
11 type = "kernel";
12 arch = "arm";
13 os = "linux";
14 compression = "none";
15 load = <0x83800000>;
16 entry = <0x83800000>;
17 hash@1 {
18 algo = "md5";
19 };
20 };
21
22 kernel@2 {
23 description = "Rescue image";
24 data = /incbin/("./zImage");
25 type = "kernel";
26 arch = "arm";
27 os = "linux";
28 compression = "none";
29 load = <0x83800000>;
30 entry = <0x83800000>;
31 hash@1 {
32 algo = "crc32";
33 };
34 };
35
36 fdt@1 {
37 description = "FDT for my cool board";
38 data = /incbin/("./devicetree.dtb");
39 type = "flat_dt";
40 arch = "arm";
41 compression = "none";
42 hash@1 {
43 algo = "crc32";
44 };
45 };
46
47
48 };
49
50 configurations {
51 default = "config@1";
52
53 config@1 {
54 description = "Default configuration";
55 kernel = "kernel@1";
56 fdt = "fdt@1";
57 };
58
59 config@2 {
60 description = "Rescue configuration";
61 kernel = "kernel@2";
62 fdt = "fdt@1";
63 };
64
65 };
66 };
This .its file has two kernel images (default and rescue) and one FDT image. Note that the default kernel is hashed with md5 and the other with crc32. It's also possible to use several hash-functions per image.
The ITS file does also specifies two configuration, Default and Rescue. The two configurations are using different kernels (well, it's the same kernel in this case since both are pointing to ./zImage...) but share the same FDT.
As you can see, it's easy to build up new configurations on demand.
The .its file is then passed to mkimage to generate an itb (Image Tree Blob):
1mkimage -f kernel.its kernel.itb
which is bootable from U-Boot.
Look at the howto [1] in the U-Boot source code for more examples on how a .its-file can look like. It also contains examples on signed images which is worth a look.
Boot from U-Boot
To boot from U-Boot, use the bootm command and specify the physical address for the FIT image and the configuration you want to use.
Example on booting config@1 from the .its above:
bootm 0x80800000#config@1
Full example with bootlog:
U-Boot 2015.04 (Oct 05 2017 - 14:25:09) CPU: Freescale i.MX6UL rev1.0 at 396 MHz CPU: Temperature 42 C Reset cause: POR Board: Marcus Cool board fuse address == 21bc400 serialnr-low : e1fe012a serialnr-high : 243211d4 Watchdog enabled I2C: ready DRAM: 512 MiB Using default environment In: serial Out: serial Err: serial Net: FEC1 Boot from USB for mfgtools Use default environment for mfgtools Run bootcmd_mfg: run mfgtool_args;bootz ${loadaddr} - ${fdt_addr}; Hit any key to stop autoboot: 0 => bootm 0x80800000#config@1 ## Loading kernel from FIT Image at 80800000 ... Using 'config@1' configuration Trying 'kernel@1' kernel subimage Description: My cool kernel Type: Kernel Image Compression: uncompressed Data Start: 0x808000d8 Data Size: 7175328 Bytes = 6.8 MiB Architecture: ARM OS: Linux Load Address: 0x83800000 Entry Point: 0x83800000 Hash algo: crc32 Hash value: f236d022 Verifying Hash Integrity ... crc32+ OK ## Loading fdt from FIT Image at 80800000 ... Using 'config@1' configuration Trying 'fdt@1' fdt subimage Description: FDT for my cool board Type: Flat Device Tree Compression: uncompressed Data Start: 0x80ed7e4c Data Size: 27122 Bytes = 26.5 KiB Architecture: ARM Hash algo: crc32 Hash value: 1837f127 Verifying Hash Integrity ... crc32+ OK Booting using the fdt blob at 0x80ed7e4c Loading Kernel Image ... OK Loading Device Tree to 9ef86000, end 9ef8f9f1 ... OK Starting kernel ...
One thing to think about
Since this FIT image tends to grow in size, it's a good idea to set CONFIG_SYS_BOOTM_LEN in the U-Boot configuration.
- CONFIG_SYS_BOOTM_LEN: Normally compressed uImages are limited to an uncompressed size of 8 MBytes. If this is not enough, you can define CONFIG_SYS_BOOTM_LEN in your board config file to adjust this setting to your needs.
Conclusion
I advocate using the FIT format because it solves a lot of problems that can get really tricky. Just the bonus of programming one image in production instead of many is a huge advantage. The configurations are well defined and there is no chance you will boot a kernel image with an incompatible device tree or whatever.
Use FIT images should be part of the bring-up-board activity, not the last thing todo before closing a project. The "Use seperate kernel/dts/initrds for now and then move on to FIT when the platform is stable" does not work. It simple not gonna happend, and it's a mistake.