Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

arm64: boot: Support Flat Image Tree

Add a script which produces a Flat Image Tree (FIT), a single file
containing the built kernel and associated devicetree files.
Compression defaults to gzip which gives a good balance of size and
performance.

The files compress from about 86MB to 24MB using this approach.

The FIT can be used by bootloaders which support it, such as U-Boot
and Linuxboot. It permits automatic selection of the correct
devicetree, matching the compatible string of the running board with
the closest compatible string in the FIT. There is no need for
filenames or other workarounds.

Add a 'make image.fit' build target for arm64, as well.

The FIT can be examined using 'dumpimage -l'.

This uses the 'dtbs-list' file but processes only .dtb files, ignoring
the overlay .dtbo files.

This features requires pylibfdt (use 'pip install libfdt'). It also
requires compression utilities for the algorithm being used. Supported
compression options are the same as the Image.xxx files. Use
FIT_COMPRESSION to select an algorithm other than gzip.

While FIT supports a ramdisk / initrd, no attempt is made to support
this here, since it must be built separately from the Linux build.

Signed-off-by: Simon Glass <sjg@chromium.org>
Acked-by: Masahiro Yamada <masahiroy@kernel.org>
Link: https://lore.kernel.org/r/20240329032836.141899-3-sjg@chromium.org
Signed-off-by: Will Deacon <will@kernel.org>

authored by

Simon Glass and committed by
Will Deacon
7a23b027 0dc1670b

+333 -3
+9
Documentation/process/changes.rst
··· 62 62 cpio any cpio --version 63 63 GNU tar 1.28 tar --version 64 64 gtags (optional) 6.6.5 gtags --version 65 + mkimage (optional) 2017.01 mkimage --version 65 66 ====================== =============== ======================================== 66 67 67 68 .. [#f1] Sphinx is needed only to build the Kernel documentation ··· 189 188 The kernel build requires GNU GLOBAL version 6.6.5 or later to generate 190 189 tag files through ``make gtags``. This is due to its use of the gtags 191 190 ``-C (--directory)`` flag. 191 + 192 + mkimage 193 + ------- 194 + 195 + This tool is used when building a Flat Image Tree (FIT), commonly used on ARM 196 + platforms. The tool is available via the ``u-boot-tools`` package or can be 197 + built from the U-Boot source code. See the instructions at 198 + https://docs.u-boot.org/en/latest/build/tools.html#building-tools-for-linux 192 199 193 200 System utilities 194 201 ****************
+7
MAINTAINERS
··· 3051 3051 N: zynq 3052 3052 N: xilinx 3053 3053 3054 + ARM64 FIT SUPPORT 3055 + M: Simon Glass <sjg@chromium.org> 3056 + L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) 3057 + S: Maintained 3058 + F: arch/arm64/boot/Makefile 3059 + F: scripts/make_fit.py 3060 + 3054 3061 ARM64 PORT (AARCH64 ARCHITECTURE) 3055 3062 M: Catalin Marinas <catalin.marinas@arm.com> 3056 3063 M: Will Deacon <will@kernel.org>
+5 -2
arch/arm64/Makefile
··· 154 154 # Default target when executing plain make 155 155 boot := arch/arm64/boot 156 156 157 - BOOT_TARGETS := Image vmlinuz.efi 157 + BOOT_TARGETS := Image vmlinuz.efi image.fit 158 158 159 159 PHONY += $(BOOT_TARGETS) 160 160 ··· 166 166 167 167 all: $(notdir $(KBUILD_IMAGE)) 168 168 169 - vmlinuz.efi: Image 169 + image.fit: dtbs 170 + 171 + vmlinuz.efi image.fit: Image 170 172 $(BOOT_TARGETS): vmlinux 171 173 $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@ 172 174 ··· 221 219 define archhelp 222 220 echo '* Image.gz - Compressed kernel image (arch/$(ARCH)/boot/Image.gz)' 223 221 echo ' Image - Uncompressed kernel image (arch/$(ARCH)/boot/Image)' 222 + echo ' image.fit - Flat Image Tree (arch/$(ARCH)/boot/image.fit)' 224 223 echo ' install - Install uncompressed kernel' 225 224 echo ' zinstall - Install compressed kernel' 226 225 echo ' Install using (your) ~/bin/installkernel or'
+1
arch/arm64/boot/.gitignore
··· 2 2 Image 3 3 Image.gz 4 4 vmlinuz* 5 + image.fit
+5 -1
arch/arm64/boot/Makefile
··· 16 16 17 17 OBJCOPYFLAGS_Image :=-O binary -R .note -R .note.gnu.build-id -R .comment -S 18 18 19 - targets := Image Image.bz2 Image.gz Image.lz4 Image.lzma Image.lzo Image.zst 19 + targets := Image Image.bz2 Image.gz Image.lz4 Image.lzma Image.lzo \ 20 + Image.zst image.fit 20 21 21 22 $(obj)/Image: vmlinux FORCE 22 23 $(call if_changed,objcopy) ··· 39 38 40 39 $(obj)/Image.zst: $(obj)/Image FORCE 41 40 $(call if_changed,zstd) 41 + 42 + $(obj)/image.fit: $(obj)/Image $(obj)/dts/dtbs-list FORCE 43 + $(call if_changed,fit) 42 44 43 45 EFI_ZBOOT_PAYLOAD := Image 44 46 EFI_ZBOOT_BFD_TARGET := elf64-littleaarch64
+16
scripts/Makefile.lib
··· 504 504 -a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \ 505 505 -n '$(UIMAGE_NAME)' -d $< $@ 506 506 507 + # Flat Image Tree (FIT) 508 + # This allows for packaging of a kernel and all devicetrees files, using 509 + # compression. 510 + # --------------------------------------------------------------------------- 511 + 512 + MAKE_FIT := $(srctree)/scripts/make_fit.py 513 + 514 + # Use this to override the compression algorithm 515 + FIT_COMPRESSION ?= gzip 516 + 517 + quiet_cmd_fit = FIT $@ 518 + cmd_fit = $(MAKE_FIT) -o $@ --arch $(UIMAGE_ARCH) --os linux \ 519 + --name '$(UIMAGE_NAME)' \ 520 + $(if $(findstring 1,$(KBUILD_VERBOSE)),-v) \ 521 + --compress $(FIT_COMPRESSION) -k $< @$(word 2,$^) 522 + 507 523 # XZ 508 524 # --------------------------------------------------------------------------- 509 525 # Use xzkern to compress the kernel image and xzmisc to compress other things.
+290
scripts/make_fit.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0+ 3 + # 4 + # Copyright 2024 Google LLC 5 + # Written by Simon Glass <sjg@chromium.org> 6 + # 7 + 8 + """Build a FIT containing a lot of devicetree files 9 + 10 + Usage: 11 + make_fit.py -A arm64 -n 'Linux-6.6' -O linux 12 + -o arch/arm64/boot/image.fit -k /tmp/kern/arch/arm64/boot/image.itk 13 + @arch/arm64/boot/dts/dtbs-list -E -c gzip 14 + 15 + Creates a FIT containing the supplied kernel and a set of devicetree files, 16 + either specified individually or listed in a file (with an '@' prefix). 17 + 18 + Use -E to generate an external FIT (where the data is placed after the 19 + FIT data structure). This allows parsing of the data without loading 20 + the entire FIT. 21 + 22 + Use -c to compress the data, using bzip2, gzip, lz4, lzma, lzo and 23 + zstd algorithms. 24 + 25 + The resulting FIT can be booted by bootloaders which support FIT, such 26 + as U-Boot, Linuxboot, Tianocore, etc. 27 + 28 + Note that this tool does not yet support adding a ramdisk / initrd. 29 + """ 30 + 31 + import argparse 32 + import collections 33 + import os 34 + import subprocess 35 + import sys 36 + import tempfile 37 + import time 38 + 39 + import libfdt 40 + 41 + 42 + # Tool extension and the name of the command-line tools 43 + CompTool = collections.namedtuple('CompTool', 'ext,tools') 44 + 45 + COMP_TOOLS = { 46 + 'bzip2': CompTool('.bz2', 'bzip2'), 47 + 'gzip': CompTool('.gz', 'pigz,gzip'), 48 + 'lz4': CompTool('.lz4', 'lz4'), 49 + 'lzma': CompTool('.lzma', 'lzma'), 50 + 'lzo': CompTool('.lzo', 'lzop'), 51 + 'zstd': CompTool('.zstd', 'zstd'), 52 + } 53 + 54 + 55 + def parse_args(): 56 + """Parse the program ArgumentParser 57 + 58 + Returns: 59 + Namespace object containing the arguments 60 + """ 61 + epilog = 'Build a FIT from a directory tree containing .dtb files' 62 + parser = argparse.ArgumentParser(epilog=epilog, fromfile_prefix_chars='@') 63 + parser.add_argument('-A', '--arch', type=str, required=True, 64 + help='Specifies the architecture') 65 + parser.add_argument('-c', '--compress', type=str, default='none', 66 + help='Specifies the compression') 67 + parser.add_argument('-E', '--external', action='store_true', 68 + help='Convert the FIT to use external data') 69 + parser.add_argument('-n', '--name', type=str, required=True, 70 + help='Specifies the name') 71 + parser.add_argument('-o', '--output', type=str, required=True, 72 + help='Specifies the output file (.fit)') 73 + parser.add_argument('-O', '--os', type=str, required=True, 74 + help='Specifies the operating system') 75 + parser.add_argument('-k', '--kernel', type=str, required=True, 76 + help='Specifies the (uncompressed) kernel input file (.itk)') 77 + parser.add_argument('-v', '--verbose', action='store_true', 78 + help='Enable verbose output') 79 + parser.add_argument('dtbs', type=str, nargs='*', 80 + help='Specifies the devicetree files to process') 81 + 82 + return parser.parse_args() 83 + 84 + 85 + def setup_fit(fsw, name): 86 + """Make a start on writing the FIT 87 + 88 + Outputs the root properties and the 'images' node 89 + 90 + Args: 91 + fsw (libfdt.FdtSw): Object to use for writing 92 + name (str): Name of kernel image 93 + """ 94 + fsw.INC_SIZE = 65536 95 + fsw.finish_reservemap() 96 + fsw.begin_node('') 97 + fsw.property_string('description', f'{name} with devicetree set') 98 + fsw.property_u32('#address-cells', 1) 99 + 100 + fsw.property_u32('timestamp', int(time.time())) 101 + fsw.begin_node('images') 102 + 103 + 104 + def write_kernel(fsw, data, args): 105 + """Write out the kernel image 106 + 107 + Writes a kernel node along with the required properties 108 + 109 + Args: 110 + fsw (libfdt.FdtSw): Object to use for writing 111 + data (bytes): Data to write (possibly compressed) 112 + args (Namespace): Contains necessary strings: 113 + arch: FIT architecture, e.g. 'arm64' 114 + fit_os: Operating Systems, e.g. 'linux' 115 + name: Name of OS, e.g. 'Linux-6.6.0-rc7' 116 + compress: Compression algorithm to use, e.g. 'gzip' 117 + """ 118 + with fsw.add_node('kernel'): 119 + fsw.property_string('description', args.name) 120 + fsw.property_string('type', 'kernel_noload') 121 + fsw.property_string('arch', args.arch) 122 + fsw.property_string('os', args.os) 123 + fsw.property_string('compression', args.compress) 124 + fsw.property('data', data) 125 + fsw.property_u32('load', 0) 126 + fsw.property_u32('entry', 0) 127 + 128 + 129 + def finish_fit(fsw, entries): 130 + """Finish the FIT ready for use 131 + 132 + Writes the /configurations node and subnodes 133 + 134 + Args: 135 + fsw (libfdt.FdtSw): Object to use for writing 136 + entries (list of tuple): List of configurations: 137 + str: Description of model 138 + str: Compatible stringlist 139 + """ 140 + fsw.end_node() 141 + seq = 0 142 + with fsw.add_node('configurations'): 143 + for model, compat in entries: 144 + seq += 1 145 + with fsw.add_node(f'conf-{seq}'): 146 + fsw.property('compatible', bytes(compat)) 147 + fsw.property_string('description', model) 148 + fsw.property_string('fdt', f'fdt-{seq}') 149 + fsw.property_string('kernel', 'kernel') 150 + fsw.end_node() 151 + 152 + 153 + def compress_data(inf, compress): 154 + """Compress data using a selected algorithm 155 + 156 + Args: 157 + inf (IOBase): Filename containing the data to compress 158 + compress (str): Compression algorithm, e.g. 'gzip' 159 + 160 + Return: 161 + bytes: Compressed data 162 + """ 163 + if compress == 'none': 164 + return inf.read() 165 + 166 + comp = COMP_TOOLS.get(compress) 167 + if not comp: 168 + raise ValueError(f"Unknown compression algorithm '{compress}'") 169 + 170 + with tempfile.NamedTemporaryFile() as comp_fname: 171 + with open(comp_fname.name, 'wb') as outf: 172 + done = False 173 + for tool in comp.tools.split(','): 174 + try: 175 + subprocess.call([tool, '-c'], stdin=inf, stdout=outf) 176 + done = True 177 + break 178 + except FileNotFoundError: 179 + pass 180 + if not done: 181 + raise ValueError(f'Missing tool(s): {comp.tools}\n') 182 + with open(comp_fname.name, 'rb') as compf: 183 + comp_data = compf.read() 184 + return comp_data 185 + 186 + 187 + def output_dtb(fsw, seq, fname, arch, compress): 188 + """Write out a single devicetree to the FIT 189 + 190 + Args: 191 + fsw (libfdt.FdtSw): Object to use for writing 192 + seq (int): Sequence number (1 for first) 193 + fmame (str): Filename containing the DTB 194 + arch: FIT architecture, e.g. 'arm64' 195 + compress (str): Compressed algorithm, e.g. 'gzip' 196 + 197 + Returns: 198 + tuple: 199 + str: Model name 200 + bytes: Compatible stringlist 201 + """ 202 + with fsw.add_node(f'fdt-{seq}'): 203 + # Get the compatible / model information 204 + with open(fname, 'rb') as inf: 205 + data = inf.read() 206 + fdt = libfdt.FdtRo(data) 207 + model = fdt.getprop(0, 'model').as_str() 208 + compat = fdt.getprop(0, 'compatible') 209 + 210 + fsw.property_string('description', model) 211 + fsw.property_string('type', 'flat_dt') 212 + fsw.property_string('arch', arch) 213 + fsw.property_string('compression', compress) 214 + fsw.property('compatible', bytes(compat)) 215 + 216 + with open(fname, 'rb') as inf: 217 + compressed = compress_data(inf, compress) 218 + fsw.property('data', compressed) 219 + return model, compat 220 + 221 + 222 + def build_fit(args): 223 + """Build the FIT from the provided files and arguments 224 + 225 + Args: 226 + args (Namespace): Program arguments 227 + 228 + Returns: 229 + tuple: 230 + bytes: FIT data 231 + int: Number of configurations generated 232 + size: Total uncompressed size of data 233 + """ 234 + seq = 0 235 + size = 0 236 + fsw = libfdt.FdtSw() 237 + setup_fit(fsw, args.name) 238 + entries = [] 239 + 240 + # Handle the kernel 241 + with open(args.kernel, 'rb') as inf: 242 + comp_data = compress_data(inf, args.compress) 243 + size += os.path.getsize(args.kernel) 244 + write_kernel(fsw, comp_data, args) 245 + 246 + for fname in args.dtbs: 247 + # Ignore overlay (.dtbo) files 248 + if os.path.splitext(fname)[1] == '.dtb': 249 + seq += 1 250 + size += os.path.getsize(fname) 251 + model, compat = output_dtb(fsw, seq, fname, args.arch, args.compress) 252 + entries.append([model, compat]) 253 + 254 + finish_fit(fsw, entries) 255 + 256 + # Include the kernel itself in the returned file count 257 + return fsw.as_fdt().as_bytearray(), seq + 1, size 258 + 259 + 260 + def run_make_fit(): 261 + """Run the tool's main logic""" 262 + args = parse_args() 263 + 264 + out_data, count, size = build_fit(args) 265 + with open(args.output, 'wb') as outf: 266 + outf.write(out_data) 267 + 268 + ext_fit_size = None 269 + if args.external: 270 + mkimage = os.environ.get('MKIMAGE', 'mkimage') 271 + subprocess.check_call([mkimage, '-E', '-F', args.output], 272 + stdout=subprocess.DEVNULL) 273 + 274 + with open(args.output, 'rb') as inf: 275 + data = inf.read() 276 + ext_fit = libfdt.FdtRo(data) 277 + ext_fit_size = ext_fit.totalsize() 278 + 279 + if args.verbose: 280 + comp_size = len(out_data) 281 + print(f'FIT size {comp_size:#x}/{comp_size / 1024 / 1024:.1f} MB', 282 + end='') 283 + if ext_fit_size: 284 + print(f', header {ext_fit_size:#x}/{ext_fit_size / 1024:.1f} KB', 285 + end='') 286 + print(f', {count} files, uncompressed {size / 1024 / 1024:.1f} MB') 287 + 288 + 289 + if __name__ == "__main__": 290 + sys.exit(run_make_fit())