Linux Audio

Check our new training course

Loading...
v6.13.7
  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
 10Usage:
 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
 15Creates a FIT containing the supplied kernel and a set of devicetree files,
 16either specified individually or listed in a file (with an '@' prefix).
 17
 18Use -E to generate an external FIT (where the data is placed after the
 19FIT data structure). This allows parsing of the data without loading
 20the entire FIT.
 21
 22Use -c to compress the data, using bzip2, gzip, lz4, lzma, lzo and
 23zstd algorithms.
 24
 25Use -D to decompose "composite" DTBs into their base components and
 26deduplicate the resulting base DTBs and DTB overlays. This requires the
 27DTBs to be sourced from the kernel build directory, as the implementation
 28looks at the .cmd files produced by the kernel build.
 29
 30The resulting FIT can be booted by bootloaders which support FIT, such
 31as U-Boot, Linuxboot, Tianocore, etc.
 32
 33Note that this tool does not yet support adding a ramdisk / initrd.
 34"""
 35
 36import argparse
 37import collections
 38import os
 39import subprocess
 40import sys
 41import tempfile
 42import time
 43
 44import libfdt
 45
 46
 47# Tool extension and the name of the command-line tools
 48CompTool = collections.namedtuple('CompTool', 'ext,tools')
 49
 50COMP_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
 60def parse_args():
 61    """Parse the program ArgumentParser
 62
 63    Returns:
 64        Namespace object containing the arguments
 65    """
 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')
 88
 89    return parser.parse_args()
 90
 91
 92def setup_fit(fsw, name):
 93    """Make a start on writing the FIT
 94
 95    Outputs the root properties and the 'images' node
 96
 97    Args:
 98        fsw (libfdt.FdtSw): Object to use for writing
 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} with devicetree set')
105    fsw.property_u32('#address-cells', 1)
106
107    fsw.property_u32('timestamp', int(time.time()))
108    fsw.begin_node('images')
109
110
111def write_kernel(fsw, data, args):
112    """Write out the kernel image
113
114    Writes a kernel node along with the required properties
115
116    Args:
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'
124    """
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)
134
135
136def finish_fit(fsw, entries):
137    """Finish the FIT ready for use
138
139    Writes the /configurations node and subnodes
140
141    Args:
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
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', 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')
157    fsw.end_node()
158
159
160def compress_data(inf, compress):
161    """Compress data using a selected algorithm
162
163    Args:
164        inf (IOBase): Filename containing the data to compress
165        compress (str): Compression algorithm, e.g. 'gzip'
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 algorithm '{compress}'")
176
177    with tempfile.NamedTemporaryFile() as comp_fname:
178        with open(comp_fname.name, 'wb') as outf:
179            done = False
180            for tool in comp.tools.split(','):
181                try:
182                    subprocess.call([tool, '-c'], stdin=inf, stdout=outf)
183                    done = True
184                    break
185                except FileNotFoundError:
186                    pass
187            if not done:
188                raise ValueError(f'Missing tool(s): {comp.tools}\n')
189            with open(comp_fname.name, 'rb') as compf:
190                comp_data = compf.read()
191    return comp_data
192
193
194def output_dtb(fsw, seq, fname, arch, compress):
195    """Write out a single devicetree to the FIT
196
197    Args:
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'
203    """
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)
209
210        with open(fname, 'rb') as inf:
211            compressed = compress_data(inf, compress)
212        fsw.property('data', compressed)
213
214
215def process_dtb(fname, args):
216    """Process an input DTB, decomposing it if requested and is possible
217
218    Args:
219        fname (str): Filename containing the DTB
220        args (Namespace): Program arguments
221    Returns:
222        tuple:
223            str: Model name string
224            str: Root compatible string
225            files: list of filenames corresponding to the DTB
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 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:
239            cmd = inf.read()
240
241        if 'scripts/dtc/fdtoverlay' in cmd:
242            # This depends on the structure of the composite DTB command
243            files = cmd.split()
244            files = files[files.index('-i') + 1:]
245        else:
246            files = [fname]
247    else:
248        files = [fname]
249
250    return (model, compat, files)
251
252def build_fit(args):
253    """Build the FIT from the provided files and arguments
254
255    Args:
256        args (Namespace): Program arguments
257
258    Returns:
259        tuple:
260            bytes: FIT data
261            int: Number of configurations generated
262            size: Total uncompressed size of data
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.compress)
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(fname, args)
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.arch, args.compress)
289                fdts[fn] = seq
290
291        files_seq = [fdts[fn] for fn in files]
292
293        entries.append([model, compat, files_seq])
294
295    finish_fit(fsw, entries)
296
297    # Include the kernel itself in the returned file count
298    return fsw.as_fdt().as_bytearray(), seq + 1, size
299
300
301def 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', 'mkimage')
312        subprocess.check_call([mkimage, '-E', '-F', args.output],
313                              stdout=subprocess.DEVNULL)
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_size / 1024 / 1024:.1f} MB',
323              end='')
324        if ext_fit_size:
325            print(f', header {ext_fit_size:#x}/{ext_fit_size / 1024:.1f} KB',
326                  end='')
327        print(f', {count} files, uncompressed {size / 1024 / 1024:.1f} MB')
328
329
330if __name__ == "__main__":
331    sys.exit(run_make_fit())
v6.13.7
  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
 10Usage:
 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
 15Creates a FIT containing the supplied kernel and a set of devicetree files,
 16either specified individually or listed in a file (with an '@' prefix).
 17
 18Use -E to generate an external FIT (where the data is placed after the
 19FIT data structure). This allows parsing of the data without loading
 20the entire FIT.
 21
 22Use -c to compress the data, using bzip2, gzip, lz4, lzma, lzo and
 23zstd algorithms.
 24
 25Use -D to decompose "composite" DTBs into their base components and
 26deduplicate the resulting base DTBs and DTB overlays. This requires the
 27DTBs to be sourced from the kernel build directory, as the implementation
 28looks at the .cmd files produced by the kernel build.
 29
 30The resulting FIT can be booted by bootloaders which support FIT, such
 31as U-Boot, Linuxboot, Tianocore, etc.
 32
 33Note that this tool does not yet support adding a ramdisk / initrd.
 34"""
 35
 36import argparse
 37import collections
 38import os
 39import subprocess
 40import sys
 41import tempfile
 42import time
 43
 44import libfdt
 45
 46
 47# Tool extension and the name of the command-line tools
 48CompTool = collections.namedtuple('CompTool', 'ext,tools')
 49
 50COMP_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
 60def parse_args():
 61    """Parse the program ArgumentParser
 62
 63    Returns:
 64        Namespace object containing the arguments
 65    """
 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')
 88
 89    return parser.parse_args()
 90
 91
 92def setup_fit(fsw, name):
 93    """Make a start on writing the FIT
 94
 95    Outputs the root properties and the 'images' node
 96
 97    Args:
 98        fsw (libfdt.FdtSw): Object to use for writing
 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} with devicetree set')
105    fsw.property_u32('#address-cells', 1)
106
107    fsw.property_u32('timestamp', int(time.time()))
108    fsw.begin_node('images')
109
110
111def write_kernel(fsw, data, args):
112    """Write out the kernel image
113
114    Writes a kernel node along with the required properties
115
116    Args:
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'
124    """
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)
134
135
136def finish_fit(fsw, entries):
137    """Finish the FIT ready for use
138
139    Writes the /configurations node and subnodes
140
141    Args:
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
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', 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')
157    fsw.end_node()
158
159
160def compress_data(inf, compress):
161    """Compress data using a selected algorithm
162
163    Args:
164        inf (IOBase): Filename containing the data to compress
165        compress (str): Compression algorithm, e.g. 'gzip'
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 algorithm '{compress}'")
176
177    with tempfile.NamedTemporaryFile() as comp_fname:
178        with open(comp_fname.name, 'wb') as outf:
179            done = False
180            for tool in comp.tools.split(','):
181                try:
182                    subprocess.call([tool, '-c'], stdin=inf, stdout=outf)
183                    done = True
184                    break
185                except FileNotFoundError:
186                    pass
187            if not done:
188                raise ValueError(f'Missing tool(s): {comp.tools}\n')
189            with open(comp_fname.name, 'rb') as compf:
190                comp_data = compf.read()
191    return comp_data
192
193
194def output_dtb(fsw, seq, fname, arch, compress):
195    """Write out a single devicetree to the FIT
196
197    Args:
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'
203    """
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)
209
210        with open(fname, 'rb') as inf:
211            compressed = compress_data(inf, compress)
212        fsw.property('data', compressed)
213
214
215def process_dtb(fname, args):
216    """Process an input DTB, decomposing it if requested and is possible
217
218    Args:
219        fname (str): Filename containing the DTB
220        args (Namespace): Program arguments
221    Returns:
222        tuple:
223            str: Model name string
224            str: Root compatible string
225            files: list of filenames corresponding to the DTB
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 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:
239            cmd = inf.read()
240
241        if 'scripts/dtc/fdtoverlay' in cmd:
242            # This depends on the structure of the composite DTB command
243            files = cmd.split()
244            files = files[files.index('-i') + 1:]
245        else:
246            files = [fname]
247    else:
248        files = [fname]
249
250    return (model, compat, files)
251
252def build_fit(args):
253    """Build the FIT from the provided files and arguments
254
255    Args:
256        args (Namespace): Program arguments
257
258    Returns:
259        tuple:
260            bytes: FIT data
261            int: Number of configurations generated
262            size: Total uncompressed size of data
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.compress)
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(fname, args)
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.arch, args.compress)
289                fdts[fn] = seq
290
291        files_seq = [fdts[fn] for fn in files]
292
293        entries.append([model, compat, files_seq])
294
295    finish_fit(fsw, entries)
296
297    # Include the kernel itself in the returned file count
298    return fsw.as_fdt().as_bytearray(), seq + 1, size
299
300
301def 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', 'mkimage')
312        subprocess.check_call([mkimage, '-E', '-F', args.output],
313                              stdout=subprocess.DEVNULL)
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_size / 1024 / 1024:.1f} MB',
323              end='')
324        if ext_fit_size:
325            print(f', header {ext_fit_size:#x}/{ext_fit_size / 1024:.1f} KB',
326                  end='')
327        print(f', {count} files, uncompressed {size / 1024 / 1024:.1f} MB')
328
329
330if __name__ == "__main__":
331    sys.exit(run_make_fit())