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 9 10 Usage: 11 make_fit.py -A arm64 -n 'Linux-6.6' -O lin 12 -o arch/arm64/boot/image.fit -k /tmp/k 13 @arch/arm64/boot/dts/dtbs-list -E -c g 14 15 Creates a FIT containing the supplied kernel a 16 either specified individually or listed in a f 17 18 Use -E to generate an external FIT (where the 19 FIT data structure). This allows parsing of th 20 the entire FIT. 21 22 Use -c to compress the data, using bzip2, gzip 23 zstd algorithms. 24 25 Use -D to decompose "composite" DTBs into thei 26 deduplicate the resulting base DTBs and DTB ov 27 DTBs to be sourced from the kernel build direc 28 looks at the .cmd files produced by the kernel 29 30 The resulting FIT can be booted by bootloaders 31 as U-Boot, Linuxboot, Tianocore, etc. 32 33 Note that this tool does not yet support addin 34 """ 35 36 import argparse 37 import collections 38 import os 39 import subprocess 40 import sys 41 import tempfile 42 import time 43 44 import libfdt 45 46 47 # Tool extension and the name of the command-l 48 CompTool = collections.namedtuple('CompTool', 49 50 COMP_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'), 57 } 58 59 60 def parse_args(): 61 """Parse the program ArgumentParser 62 63 Returns: 64 Namespace object containing the argume 65 """ 66 epilog = 'Build a FIT from a directory tre 67 parser = argparse.ArgumentParser(epilog=ep 68 parser.add_argument('-A', '--arch', type=s 69 help='Specifies the architecture') 70 parser.add_argument('-c', '--compress', ty 71 help='Specifies the compression') 72 parser.add_argument('-D', '--decompose-dtb 73 help='Decompose composite DTBs into 74 parser.add_argument('-E', '--external', ac 75 help='Convert the FIT to use externa 76 parser.add_argument('-n', '--name', type=s 77 help='Specifies the name') 78 parser.add_argument('-o', '--output', type 79 help='Specifies the output file (.fi 80 parser.add_argument('-O', '--os', type=str 81 help='Specifies the operating system 82 parser.add_argument('-k', '--kernel', type 83 help='Specifies the (uncompressed) k 84 parser.add_argument('-v', '--verbose', act 85 help='Enable verbose o 86 parser.add_argument('dtbs', type=str, narg 87 help='Specifies the devicetree files 88 89 return parser.parse_args() 90 91 92 def setup_fit(fsw, name): 93 """Make a start on writing the FIT 94 95 Outputs the root properties and the 'image 96 97 Args: 98 fsw (libfdt.FdtSw): Object to use for 99 name (str): Name of kernel image 100 """ 101 fsw.INC_SIZE = 65536 102 fsw.finish_reservemap() 103 fsw.begin_node('') 104 fsw.property_string('description', f'{name 105 fsw.property_u32('#address-cells', 1) 106 107 fsw.property_u32('timestamp', int(time.tim 108 fsw.begin_node('images') 109 110 111 def write_kernel(fsw, data, args): 112 """Write out the kernel image 113 114 Writes a kernel node along with the requir 115 116 Args: 117 fsw (libfdt.FdtSw): Object to use for 118 data (bytes): Data to write (possibly 119 args (Namespace): Contains necessary s 120 arch: FIT architecture, e.g. 'arm6 121 fit_os: Operating Systems, e.g. 'l 122 name: Name of OS, e.g. 'Linux-6.6. 123 compress: Compression algorithm to 124 """ 125 with fsw.add_node('kernel'): 126 fsw.property_string('description', arg 127 fsw.property_string('type', 'kernel_no 128 fsw.property_string('arch', args.arch) 129 fsw.property_string('os', args.os) 130 fsw.property_string('compression', arg 131 fsw.property('data', data) 132 fsw.property_u32('load', 0) 133 fsw.property_u32('entry', 0) 134 135 136 def finish_fit(fsw, entries): 137 """Finish the FIT ready for use 138 139 Writes the /configurations node and subnod 140 141 Args: 142 fsw (libfdt.FdtSw): Object to use for 143 entries (list of tuple): List of confi 144 str: Description of model 145 str: Compatible stringlist 146 """ 147 fsw.end_node() 148 seq = 0 149 with fsw.add_node('configurations'): 150 for model, compat, files in entries: 151 seq += 1 152 with fsw.add_node(f'conf-{seq}'): 153 fsw.property('compatible', byt 154 fsw.property_string('descripti 155 fsw.property('fdt', bytes(''.j 156 fsw.property_string('kernel', 157 fsw.end_node() 158 159 160 def compress_data(inf, compress): 161 """Compress data using a selected algorith 162 163 Args: 164 inf (IOBase): Filename containing the 165 compress (str): Compression algorithm, 166 167 Return: 168 bytes: Compressed data 169 """ 170 if compress == 'none': 171 return inf.read() 172 173 comp = COMP_TOOLS.get(compress) 174 if not comp: 175 raise ValueError(f"Unknown compression 176 177 with tempfile.NamedTemporaryFile() as comp 178 with open(comp_fname.name, 'wb') as ou 179 done = False 180 for tool in comp.tools.split(','): 181 try: 182 subprocess.call([tool, '-c 183 done = True 184 break 185 except FileNotFoundError: 186 pass 187 if not done: 188 raise ValueError(f'Missing too 189 with open(comp_fname.name, 'rb') a 190 comp_data = compf.read() 191 return comp_data 192 193 194 def output_dtb(fsw, seq, fname, arch, compress 195 """Write out a single devicetree to the FI 196 197 Args: 198 fsw (libfdt.FdtSw): Object to use for 199 seq (int): Sequence number (1 for firs 200 fname (str): Filename containing the D 201 arch: FIT architecture, e.g. 'arm64' 202 compress (str): Compressed algorithm, 203 """ 204 with fsw.add_node(f'fdt-{seq}'): 205 fsw.property_string('description', os. 206 fsw.property_string('type', 'flat_dt') 207 fsw.property_string('arch', arch) 208 fsw.property_string('compression', com 209 210 with open(fname, 'rb') as inf: 211 compressed = compress_data(inf, co 212 fsw.property('data', compressed) 213 214 215 def process_dtb(fname, args): 216 """Process an input DTB, decomposing it if 217 218 Args: 219 fname (str): Filename containing the D 220 args (Namespace): Program arguments 221 Returns: 222 tuple: 223 str: Model name string 224 str: Root compatible string 225 files: list of filenames correspon 226 """ 227 # Get the compatible / model information 228 with open(fname, 'rb') as inf: 229 data = inf.read() 230 fdt = libfdt.FdtRo(data) 231 model = fdt.getprop(0, 'model').as_str() 232 compat = fdt.getprop(0, 'compatible') 233 234 if args.decompose_dtbs: 235 # Check if the DTB needs to be decompo 236 path, basename = os.path.split(fname) 237 cmd_fname = os.path.join(path, f'.{bas 238 with open(cmd_fname, 'r', encoding='as 239 cmd = inf.read() 240 241 if 'scripts/dtc/fdtoverlay' in cmd: 242 # This depends on the structure of 243 files = cmd.split() 244 files = files[files.index('-i') + 245 else: 246 files = [fname] 247 else: 248 files = [fname] 249 250 return (model, compat, files) 251 252 def build_fit(args): 253 """Build the FIT from the provided files a 254 255 Args: 256 args (Namespace): Program arguments 257 258 Returns: 259 tuple: 260 bytes: FIT data 261 int: Number of configurations gene 262 size: Total uncompressed size of d 263 """ 264 seq = 0 265 size = 0 266 fsw = libfdt.FdtSw() 267 setup_fit(fsw, args.name) 268 entries = [] 269 fdts = {} 270 271 # Handle the kernel 272 with open(args.kernel, 'rb') as inf: 273 comp_data = compress_data(inf, args.co 274 size += os.path.getsize(args.kernel) 275 write_kernel(fsw, comp_data, args) 276 277 for fname in args.dtbs: 278 # Ignore non-DTB (*.dtb) files 279 if os.path.splitext(fname)[1] != '.dtb 280 continue 281 282 (model, compat, files) = process_dtb(f 283 284 for fn in files: 285 if fn not in fdts: 286 seq += 1 287 size += os.path.getsize(fn) 288 output_dtb(fsw, seq, fn, args. 289 fdts[fn] = seq 290 291 files_seq = [fdts[fn] for fn in files] 292 293 entries.append([model, compat, files_s 294 295 finish_fit(fsw, entries) 296 297 # Include the kernel itself in the returne 298 return fsw.as_fdt().as_bytearray(), seq + 299 300 301 def run_make_fit(): 302 """Run the tool's main logic""" 303 args = parse_args() 304 305 out_data, count, size = build_fit(args) 306 with open(args.output, 'wb') as outf: 307 outf.write(out_data) 308 309 ext_fit_size = None 310 if args.external: 311 mkimage = os.environ.get('MKIMAGE', 'm 312 subprocess.check_call([mkimage, '-E', 313 stdout=subproces 314 315 with open(args.output, 'rb') as inf: 316 data = inf.read() 317 ext_fit = libfdt.FdtRo(data) 318 ext_fit_size = ext_fit.totalsize() 319 320 if args.verbose: 321 comp_size = len(out_data) 322 print(f'FIT size {comp_size:#x}/{comp_ 323 end='') 324 if ext_fit_size: 325 print(f', header {ext_fit_size:#x} 326 end='') 327 print(f', {count} files, uncompressed 328 329 330 if __name__ == "__main__": 331 sys.exit(run_make_fit())
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.