2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright 2024 Google LLC
5 # Written by Simon Glass <sjg@chromium.org>
8 """Build a FIT containing a lot of devicetree files
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
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).
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
22 Use -c to compress the data, using bzip2, gzip, lz4, lzma, lzo and
25 Use -D to decompose "composite" DTBs into their base components and
26 deduplicate the resulting base DTBs and DTB overlays. This requires the
27 DTBs to be sourced from the kernel build directory, as the implementation
28 looks at the .cmd files produced by the kernel build.
30 The resulting FIT can be booted by bootloaders which support FIT, such
31 as U-Boot, Linuxboot, Tianocore, etc.
33 Note that this tool does not yet support adding a ramdisk / initrd.
47 # Tool extension and the name of the command-line tools
48 CompTool
= collections
.namedtuple('CompTool', 'ext,tools')
51 'bzip2': CompTool('.bz2', 'bzip2'),
52 'gzip': CompTool('.gz', 'pigz,gzip'),
53 'lz4': CompTool('.lz4', 'lz4'),
54 'lzma': CompTool('.lzma', 'lzma'),
55 'lzo': CompTool('.lzo', 'lzop'),
56 'zstd': CompTool('.zstd', 'zstd'),
61 """Parse the program ArgumentParser
64 Namespace object containing the arguments
66 epilog
= 'Build a FIT from a directory tree containing .dtb files'
67 parser
= argparse
.ArgumentParser(epilog
=epilog
, fromfile_prefix_chars
='@')
68 parser
.add_argument('-A', '--arch', type=str, required
=True,
69 help='Specifies the architecture')
70 parser
.add_argument('-c', '--compress', type=str, default
='none',
71 help='Specifies the compression')
72 parser
.add_argument('-D', '--decompose-dtbs', action
='store_true',
73 help='Decompose composite DTBs into base DTB and overlays')
74 parser
.add_argument('-E', '--external', action
='store_true',
75 help='Convert the FIT to use external data')
76 parser
.add_argument('-n', '--name', type=str, required
=True,
77 help='Specifies the name')
78 parser
.add_argument('-o', '--output', type=str, required
=True,
79 help='Specifies the output file (.fit)')
80 parser
.add_argument('-O', '--os', type=str, required
=True,
81 help='Specifies the operating system')
82 parser
.add_argument('-k', '--kernel', type=str, required
=True,
83 help='Specifies the (uncompressed) kernel input file (.itk)')
84 parser
.add_argument('-v', '--verbose', action
='store_true',
85 help='Enable verbose output')
86 parser
.add_argument('dtbs', type=str, nargs
='*',
87 help='Specifies the devicetree files to process')
89 return parser
.parse_args()
92 def setup_fit(fsw
, name
):
93 """Make a start on writing the FIT
95 Outputs the root properties and the 'images' node
98 fsw (libfdt.FdtSw): Object to use for writing
99 name (str): Name of kernel image
102 fsw
.finish_reservemap()
104 fsw
.property_string('description', f
'{name} with devicetree set')
105 fsw
.property_u32('#address-cells', 1)
107 fsw
.property_u32('timestamp', int(time
.time()))
108 fsw
.begin_node('images')
111 def write_kernel(fsw
, data
, args
):
112 """Write out the kernel image
114 Writes a kernel node along with the required properties
117 fsw (libfdt.FdtSw): Object to use for writing
118 data (bytes): Data to write (possibly compressed)
119 args (Namespace): Contains necessary strings:
120 arch: FIT architecture, e.g. 'arm64'
121 fit_os: Operating Systems, e.g. 'linux'
122 name: Name of OS, e.g. 'Linux-6.6.0-rc7'
123 compress: Compression algorithm to use, e.g. 'gzip'
125 with fsw
.add_node('kernel'):
126 fsw
.property_string('description', args
.name
)
127 fsw
.property_string('type', 'kernel_noload')
128 fsw
.property_string('arch', args
.arch
)
129 fsw
.property_string('os', args
.os
)
130 fsw
.property_string('compression', args
.compress
)
131 fsw
.property('data', data
)
132 fsw
.property_u32('load', 0)
133 fsw
.property_u32('entry', 0)
136 def finish_fit(fsw
, entries
):
137 """Finish the FIT ready for use
139 Writes the /configurations node and subnodes
142 fsw (libfdt.FdtSw): Object to use for writing
143 entries (list of tuple): List of configurations:
144 str: Description of model
145 str: Compatible stringlist
149 with fsw
.add_node('configurations'):
150 for model
, compat
, files
in entries
:
152 with fsw
.add_node(f
'conf-{seq}'):
153 fsw
.property('compatible', bytes(compat
))
154 fsw
.property_string('description', model
)
155 fsw
.property('fdt', bytes(''.join(f
'fdt-{x}\x00' for x
in files
), "ascii"))
156 fsw
.property_string('kernel', 'kernel')
160 def compress_data(inf
, compress
):
161 """Compress data using a selected algorithm
164 inf (IOBase): Filename containing the data to compress
165 compress (str): Compression algorithm, e.g. 'gzip'
168 bytes: Compressed data
170 if compress
== 'none':
173 comp
= COMP_TOOLS
.get(compress
)
175 raise ValueError(f
"Unknown compression algorithm '{compress}'")
177 with tempfile
.NamedTemporaryFile() as comp_fname
:
178 with
open(comp_fname
.name
, 'wb') as outf
:
180 for tool
in comp
.tools
.split(','):
182 subprocess
.call([tool
, '-c'], stdin
=inf
, stdout
=outf
)
185 except FileNotFoundError
:
188 raise ValueError(f
'Missing tool(s): {comp.tools}\n')
189 with
open(comp_fname
.name
, 'rb') as compf
:
190 comp_data
= compf
.read()
194 def output_dtb(fsw
, seq
, fname
, arch
, compress
):
195 """Write out a single devicetree to the FIT
198 fsw (libfdt.FdtSw): Object to use for writing
199 seq (int): Sequence number (1 for first)
200 fname (str): Filename containing the DTB
201 arch: FIT architecture, e.g. 'arm64'
202 compress (str): Compressed algorithm, e.g. 'gzip'
204 with fsw
.add_node(f
'fdt-{seq}'):
205 fsw
.property_string('description', os
.path
.basename(fname
))
206 fsw
.property_string('type', 'flat_dt')
207 fsw
.property_string('arch', arch
)
208 fsw
.property_string('compression', compress
)
210 with
open(fname
, 'rb') as inf
:
211 compressed
= compress_data(inf
, compress
)
212 fsw
.property('data', compressed
)
215 def process_dtb(fname
, args
):
216 """Process an input DTB, decomposing it if requested and is possible
219 fname (str): Filename containing the DTB
220 args (Namespace): Program arguments
223 str: Model name string
224 str: Root compatible string
225 files: list of filenames corresponding to the DTB
227 # Get the compatible / model information
228 with
open(fname
, 'rb') as inf
:
230 fdt
= libfdt
.FdtRo(data
)
231 model
= fdt
.getprop(0, 'model').as_str()
232 compat
= fdt
.getprop(0, 'compatible')
234 if args
.decompose_dtbs
:
235 # Check if the DTB needs to be decomposed
236 path
, basename
= os
.path
.split(fname
)
237 cmd_fname
= os
.path
.join(path
, f
'.{basename}.cmd')
238 with
open(cmd_fname
, 'r', encoding
='ascii') as inf
:
241 if 'scripts/dtc/fdtoverlay' in cmd
:
242 # This depends on the structure of the composite DTB command
244 files
= files
[files
.index('-i') + 1:]
250 return (model
, compat
, files
)
253 """Build the FIT from the provided files and arguments
256 args (Namespace): Program arguments
261 int: Number of configurations generated
262 size: Total uncompressed size of data
267 setup_fit(fsw
, args
.name
)
272 with
open(args
.kernel
, 'rb') as inf
:
273 comp_data
= compress_data(inf
, args
.compress
)
274 size
+= os
.path
.getsize(args
.kernel
)
275 write_kernel(fsw
, comp_data
, args
)
277 for fname
in args
.dtbs
:
278 # Ignore non-DTB (*.dtb) files
279 if os
.path
.splitext(fname
)[1] != '.dtb':
282 (model
, compat
, files
) = process_dtb(fname
, args
)
287 size
+= os
.path
.getsize(fn
)
288 output_dtb(fsw
, seq
, fn
, args
.arch
, args
.compress
)
291 files_seq
= [fdts
[fn
] for fn
in files
]
293 entries
.append([model
, compat
, files_seq
])
295 finish_fit(fsw
, entries
)
297 # Include the kernel itself in the returned file count
298 return fsw
.as_fdt().as_bytearray(), seq
+ 1, size
302 """Run the tool's main logic"""
305 out_data
, count
, size
= build_fit(args
)
306 with
open(args
.output
, 'wb') as outf
:
311 mkimage
= os
.environ
.get('MKIMAGE', 'mkimage')
312 subprocess
.check_call([mkimage
, '-E', '-F', args
.output
],
313 stdout
=subprocess
.DEVNULL
)
315 with
open(args
.output
, 'rb') as inf
:
317 ext_fit
= libfdt
.FdtRo(data
)
318 ext_fit_size
= ext_fit
.totalsize()
321 comp_size
= len(out_data
)
322 print(f
'FIT size {comp_size:#x}/{comp_size / 1024 / 1024:.1f} MB',
325 print(f
', header {ext_fit_size:#x}/{ext_fit_size / 1024:.1f} KB',
327 print(f
', {count} files, uncompressed {size / 1024 / 1024:.1f} MB')
330 if __name__
== "__main__":
331 sys
.exit(run_make_fit())