Flattened Image Tree (FIT) with Yocto

Posted by Marcus Folkesson on Tuesday, May 14, 2024

Flattened Image Tree (FIT) with Yocto

Long time ago, I wrota a post [1] that compared the legacy Image format against Flattened Image Tree Format (FIT) [2] and highlighted the benefits of using it. The benefits is still valid and FIT images is my preferred way to boot a Linux kernel. Dispite that, I almost never see that FIT images is used in examples nor Board Support Packages (BSPs).

So this post is mostly to give some more attention to the FIT images because I think it deserves it. The post will somehow overlap with the previous one, but my intension is to be a little more hands-on this time.

Even if creating a FIT image in Yocto is just two lines away, I think it is good to know what a FIT image really is - so we start from there.

Recap from the previous post

An embedded Linux system often needs multiple files in order to boot. Such files includes the kernel image, a Flattened Device Tree (FDT), Trusted Execution Environment (TEE) firmware, an Initrd image ... the list could be long.

FIT let you bundle all those components into one single image and create configurations compounded of a subset of these components. This way you may use the same kernel image with different FDT:s for different hardware configurations, all handled by a single FIT image.

/media/fit.png

What is a FIT image?

FIT image is essentially a Flattened Device Tree (FDT) with embedded binaries. You are probably already familiar with the Device Tree Source (DTS) format as it is heavily used in several big projects (e.g. Linux Kernel, U-Boot, Zephyr, ...).

The source file for FIT images is called Image Tree Source (ITS), compare to DTS for Device Tree Source, and use the same tools (libfdt library and Device Tree Compiler (dtc) ) as the other of the projects mention above.

What does a ITS file look like?

You will see the same tree structure with the same format you are used to when working with dts files:

/dts-v1/;

/ {
        description = "iMX8 FIT image";
        #address-cells = <1>;

        images {
                kernel {
                        description = "Kernel";
                        data = /incbin/("Image");
                        type = "kernel";
                        arch = "arm64";
                        os = "linux";
                        compression = "none";
                        load = <0x40480000>;
                        entry = <0x40480000>;
                        hash {
                                algo = "sha1";
                        };
                };
                fdt@1 {
                        description = "Devicetree for Board XXX";
                        data = /incbin/("imx8-board-XXX.dtb");
                        type = "flat_dt";
                        arch = "arm64";
                        compression = "none";
                        load = <0x43000000>;
                        entry = <0x43000000>;
                        hash {
                                algo = "sha1";
                        };
                };
                fdt@2 {
                        description = "Devicetree for Board YYY";
                        data = /incbin/("imx8-board-YYY.dtb");
                        type = "flat_dt";
                        arch = "arm64";
                        compression = "none";
                        load = <0x43000000>;
                        entry = <0x43000000>;
                        hash {
                                algo = "sha1";
                        };
                };
                initrd {
                        description = "Initrd";
                        data = /incbin/("core-image-minimal-custom-board.ext2.gz");
                        type = "ramdisk";
                        arch = "arm64";
                        os = "linux";
                        compression = "none";
                        hash {
                                algo = "sha1";
                        };
                };
        };

        configurations {
                default = "boardXXX";
                boardXXX {
                        description = "Standard Boot for board XXX";
                        kernel = "kernel";
                        fdt = "fdt@1";
                        ramdisk = "initrd";
                        hash {
                                algo = "sha1";
                        };
                };

                boardYYY {
                        description = "Standard Boot for board YYY";
                        kernel = "kernel";
                        fdt = "fdt@2";
                        ramdisk = "initrd";
                        hash {
                                algo = "sha1";
                        };
                };
        };

};              

All nodes is well described in the documentation [3], but we may take a closer look at the configurations node:

configurations {
	default = "boardXXX";
	boardXXX {
		description = "Standard Boot for board XXX";
		kernel = "kernel";
		fdt = "fdt@1";
		ramdisk = "initrd";
		hash {
			algo = "sha1";
		};
	};

	boardYYY {
		description = "Standard Boot for board YYY";
		kernel = "kernel";
		fdt = "fdt@2";
		ramdisk = "initrd";
		hash {
			algo = "sha1";
		};
	};

};              

A configuration is a group of related components that will be extracted during boot. In the example above, there are two configurations, boardXXX and boardYYY (I'm always suprised at how good I am at picking good names).

These two configurations share the same kernel and initrd, but uses different FDTs.

Supported image types

FIT supports many different types and U-boot does only make use of a few of them. That doesn't stop you from embedding the firmware for your coprocessor and writing the logic to flash it yourself. The advantage is that all firmware is collected in a single image and the versions are coherent with each other.

libfdt makes it convenient to handle the FIT image in your application.

Type is one of the mandatory attribute for images in the ITS file and specify the type of the image.

Supported image types are:

Sub-image type Meaning
aisimage Davinci AIS image
atmelimage ATMEL ROM-Boot Image
copro Coprocessor Image}
fdt_legacy legacy Image with Flat Device Tree
filesystem Filesystem Image
firmware Firmware
firmware_ivt Firmware with HABv4 IVT }
flat_dt Flat Device Tree
fpga FPGA Image }
gpimage TI Keystone SPL Image
imx8image NXP i.MX8 Boot Image
imx8mimage NXP i.MX8M Boot Image
imximage Freescale i.MX Boot Image
kernel Kernel Image
kernel_noload Kernel Image (no loading done)
kwbimage Kirkwood Boot Image
lpc32xximage LPC32XX Boot Image
mtk_image MediaTek BootROM loadable Image }
multi Multi-File Image
mxsimage Freescale MXS Boot Image
omapimage TI OMAP SPL With GP CH
pblimage Freescale PBL Boot Image
pmmc TI Power Management Micro-Controller Firmware
ramdisk RAMDisk Image
rkimage Rockchip Boot Image }
rksd Rockchip SD Boot Image }
rkspi Rockchip SPI Boot Image }
script Script
socfpgaimage Altera SoCFPGA CV/AV preloader
socfpgaimage_v1 Altera SoCFPGA A10 preloader
spkgimage Renesas SPKG Image }
standalone Standalone Program
stm32image STMicroelectronics STM32 Image }
sunxi_egon Allwinner eGON Boot Image }
sunxi_toc0 Allwinner TOC0 Boot Image }
tee Trusted Execution Environment Image
ublimage Davinci UBL image
vybridimage Vybrid Boot Image
x86_setup x86 setup.bin
zynqimage Xilinx Zynq Boot Image }
zynqmpbif Xilinx ZynqMP Boot Image (bif) }
zynqmpimage Xilinx ZynqMP Boot Image }

Working with FIT images

Besides the Device Tree Compiler (dtc), all tools that you need to manipulate a FIT image are typically available in the u-boot-tools package you probably find in your Linux distribution.

mkimage is used to create the image and dumpimage is used to inspect and dump content of a FIT image.

Inspect a FIT image

Use dumpimage with the -l option to list the content of a FIT image.

See example below:

 1$ dumpimage -l ./fitImage
 2FIT description: Kernel fitImage for test Distro/5.15.87+gitAUTOINC+0eb4504bd3/test-imx8mp
 3	Created:         Wed Feb  8 16:32:47 2023
 4Image 0 (kernel-1)
 5	Description:  Linux kernel
 6	Created:      Wed Feb  8 16:32:47 2023
 7	Type:         Kernel Image
 8	Compression:  gzip compressed
 9	Data Size:    11880575 Bytes = 11602.12 KiB = 11.33 MiB
10	Architecture: AArch64
11	OS:           Linux
12	Load Address: 0x40480000
13	Entry Point:  0x40480000
14	Hash algo:    sha256
15	Hash value:   794491ba95d20ba1337d61b5b12d04a406168360072ec55c4c22ee3b4a24439c
16Image 1 (fdt-test-imx8mp.dtb)
17	Description:  Flattened Device Tree blob
18	Created:      Wed Feb  8 16:32:47 2023
19	Type:         Flat Device Tree
20	Compression:  uncompressed
21	Data Size:    54918 Bytes = 53.63 KiB = 0.05 MiB
22	Architecture: AArch64
23	Hash algo:    sha256
24	Hash value:   4b37b6507a6e6f755bb701156f08274f7060062c03793a219de46ac4be8e2014
25Image 2 (ramdisk-1)
26	Description:  core-image-readonly-rootfs-overlay-initramfs
27	Created:      Wed Feb  8 16:32:47 2023
28	Type:         RAMDisk Image
29	Compression:  uncompressed
30	Data Size:    4305043 Bytes = 4204.14 KiB = 4.11 MiB
31	Architecture: AArch64
32	OS:           Linux
33	Load Address: unavailable
34	Entry Point:  unavailable
35	Hash algo:    sha256
36	Hash value:   7fa455d2d5a9a6692e1aac67c3683ae795fb6dfe910ca06ee1607c797daa3b51
37	Default Configuration: 'conf-test-imx8mp.dtb'
38Configuration 0 (conf-test-imx8mp.dtb)
39	Description:  1 Linux kernel, FDT blob, ramdisk
40	Kernel:       kernel-1
41	Init Ramdisk: ramdisk-1
42	FDT:          fdt-test-imx8mp.dtb
43	Hash algo:    sha256
44	Hash value:   unavailable

This FIT image is generated by a Yocto build using the kernel-fitimage [4] class.

Extract a component from a FIT image

dumpimage is also used to extract any component from the FIT image. E.g.

 1$ dumpimage -T flat_dt -p 1 -o devicetree.dtb  ./fitImage
 2Extracted:
 3Image 1 (fdt-test-imx8mp.dtb)
 4	Description:  Flattened Device Tree blob
 5	Created:      Wed Feb  8 16:32:47 2023
 6	Type:         Flat Device Tree
 7	Compression:  uncompressed
 8	Data Size:    54918 Bytes = 53.63 KiB = 0.05 MiB
 9	Architecture: AArch64
10	Hash algo:    sha256
11	Hash value:   4b37b6507a6e6f755bb701156f08274f7060062c03793a219de46ac4be8e2014

Create a FIT image

There is no magic in creating a FIT image. Just provide an ITS file to mkimage and get back a FIT.

1$ mkimage -f image.its image.fit

FIT support in Yocto

FIT images are supported in Yocto by the kernel-fitimage class [4].

The kernel-fitimage class will create and ITS structure and populate it with the components provided by the machine/local.conf files.

Unfortunately, the current implementation comes with some limitations though. It does only support one kernel image, an optional U-boot script, an optional initramfs, an optional RAM disk and any number of device trees. [5]. So it will not cover all the cases that I wrote about above, but at least multiple device trees are supported and that is the most essential.

Support for multiple initramfs files is on my wishlist though. It would make it possible to, for example, have the initramfs generated by meta-swupdate [6] to support an "update mode" in the same FIT image..

Anyway.

To create a FIT image, include kernel-fitimage to the KERNEL_CLASSES variable and include fitimage to either KERNEL_IMAGETYPE, KERNEL_ALT_IMAGETYPE or KERNEL_IMAGETYPES. In other words, put these lines into your local.conf:

KERNEL_CLASSES += "kernel-fitimage"
KERNEL_IMAGETYPES += " fitImage "

The kernel-fitimage class will take care of the rest.

There are also a few other variables that may be set depending on what you want to generate. E.g.

INITRAMFS_IMAGE = "core-image-readonly-rootfs-overlay-initramfs"
FIT_SUPPORTED_INITRAMFS_FSTYPES = " cpio.gz "

If you would like to have the FIT image contain an initramfs. The documentation [5] describes this very well.