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