[PATCH 10/13] images: add barebox FIT image target

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The generic barebox-dt-2nd.img depends on the user to fish out
the correct device tree from arch/${SRCARCH}/dts and to instruct
the preceding boot stage to pass that device tree to the barebox image.

To make this easier, especially with an eye towards using this as a
coreboot payload, let's have the barebox build system produce a FIT
image combining barebox-dt-2nd.img and all the enabled device trees.

As this introduces a python3 and python3-libfdt dependency that wasn't
there before, this is only built by default if the relevant CONFIG
option is enabled or if the target is explicitly built.

Signed-off-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx>
---
 Documentation/user/barebox.rst |  25 +--
 Makefile                       |   5 +
 arch/Kconfig                   |  17 ++
 images/Makefile                |   9 +
 scripts/Kbuild.include         |   6 +
 scripts/Makefile.lib           |  17 ++
 scripts/make_fit.py            | 331 +++++++++++++++++++++++++++++++++
 7 files changed, 392 insertions(+), 18 deletions(-)
 create mode 100755 scripts/make_fit.py

diff --git a/Documentation/user/barebox.rst b/Documentation/user/barebox.rst
index c6969ae3de1d..338c2f5d03c0 100644
--- a/Documentation/user/barebox.rst
+++ b/Documentation/user/barebox.rst
@@ -215,20 +215,10 @@ For example:
   U-Boot: bootz $kernel_addr - $fdt_addr # On 32-bit ARM
   U-Boot: booti $kernel_addr - $fdt_addr # for other platforms
 
-Another option is to generate a FIT image containing the generic DT image and a
-matching device tree with ``mkimage``:
-
-.. code-block:: console
-
-  sh: mkimage --architecture arm \
-      --os linux \
-      --type kernel \
-      --fit auto \
-      --load-address $kernel_addr_r \
-      --compression none \
-      --image images/barebox-dt-2nd.img \
-      --device-tree arch/${ARCH}/dts/my-board.dtb \
-      barebox-dt-2nd.fit
+The barebox build can also generate a FIT image combining ``barebox-dt-2nd.img``
+and all enabled device trees. This image requires python3 and python3-libfdt
+and is thus only built by default if ``CONFIG_BOARD_GENERIC_FIT`` is enabled
+or the FIT image target is explicitly invoked: ``make barebox.fit``.
 
 This FIT image can then be loaded by U-Boot and executed just like a regular
 Linux kernel:
@@ -238,10 +228,9 @@ Linux kernel:
   U-Boot: tftp $fit_addr barebox-dt-2nd.fit
   U-Boot: bootm $fit_addr
 
-Make sure that the address in ``$fit_addr`` is different from the
-``$kernel_addr_r`` passed to ``mkimage`` as the load address of the Kernel
-image. Otherwise U-Boot may attempt to overwrite the FIT image with the barebox
-image contained within.
+The FIT image has a kernel type of ``kernel_noload``, instructing the bootloader
+to ignore the load address. The first stage bootloader must thus either support
+``kernel_noload`` or always ignore load addresses.
 
 For non-DT enabled-bootloaders or other architectures, often the normal barebox
 binaries can also be used as they are designed to be startable second stage
diff --git a/Makefile b/Makefile
index 81a89d1c0ad4..9a2fdf0c68d2 100644
--- a/Makefile
+++ b/Makefile
@@ -1005,6 +1005,9 @@ barebox: $(BAREBOX_LDS) $(BAREBOX_OBJS) $(kallsyms.o) FORCE
 	$(call if_changed_rule,barebox__)
 	$(Q)rm -f .old_version
 
+barebox.fit: images/barebox-$(CONFIG_ARCH_LINUX_NAME).fit
+	$(Q)ln -fsn $< $@
+
 barebox.srec: barebox
 	$(OBJCOPY) -O srec $< $@
 
@@ -1110,6 +1113,7 @@ include/generated/utsrelease.h: include/config/kernel.release FORCE
 
 ifneq ($(wildcard $(srctree)/arch/$(SRCARCH)/dts/),)
 dtstree := arch/$(SRCARCH)/dts
+export dtstree
 endif
 
 ifneq ($(dtstree),)
@@ -1319,6 +1323,7 @@ help:
 	@echo  '* barebox         - Build the barebox proper binary'
 ifdef CONFIG_PBL_IMAGE
 	@echo  '* images          - Build final prebootloader-prefixed images'
+	@echo  '* barebox.fit     - Build 2nd stage barebox with device trees FIT image'
 endif
 	@echo  '  dir/            - Build all files in dir and below'
 	@echo  '  dir/file.[ois]  - Build specified target only'
diff --git a/arch/Kconfig b/arch/Kconfig
index e1c08bcd7b99..aee5375dc70c 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -46,4 +46,21 @@ config BOARD_GENERIC_DT
 	  architecture and thus can be used anywhere that a Kernel image could be used.
 	  The image will be called images/barebox-dt-2nd.img
 
+config BOARD_GENERIC_FIT
+	depends on BOARD_GENERIC_DT
+	bool "Build generic device tree 2nd stage FIT image"
+	help
+	  This enables compilation of a generic FIT image that combines
+	  barebox-dt-2nd.img as well as all enabled device trees.
+	  This single image is the bootable from coreboot, barebox, or any other
+	  bootloader capable of booting a Linux kernel out of FIT images.
+	  The image will be called images/barebox-$(CONFIG_ARCH_LINUX_NAME).fit
+
+	  The image can be built manually, even without enabling this option
+	  by running make barebox.fit, which will create a barebox.fit symlink
+	  pointing at the built image.
+
+	  Note that this option requires python3 and its libfdt module to be
+	  installed on the build host.
+
 endmenu
diff --git a/images/Makefile b/images/Makefile
index e0e3e5c537cc..4e5cb693e40a 100644
--- a/images/Makefile
+++ b/images/Makefile
@@ -123,6 +123,10 @@ cmd_itb = $(CPP) $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) \
 $(obj)/%.itb: $(obj)/%.its FORCE
 	$(call if_changed,itb)
 
+.SECONDEXPANSION:
+$(obj)/%.fit: $(obj)/$$(FILE_$$(@F)) $(dtstree)/dtbs-list FORCE
+	$(call if_changed,fit)
+
 $(obj)/piggy.o: $(obj)/barebox.z FORCE
 
 $(obj)/sha_sum.o: $(obj)/barebox.sha.bin FORCE
@@ -187,6 +191,11 @@ pblb-$(CONFIG_BOARD_GENERIC_DT) += start_dt_2nd
 FILE_barebox-dt-2nd.img = start_dt_2nd.pblb
 image-$(CONFIG_BOARD_GENERIC_DT) += barebox-dt-2nd.img
 
+fit-image = barebox-$(call remove_quotes,$(CONFIG_ARCH_LINUX_NAME)).fit
+FILE_$(fit-image) = barebox-dt-2nd.img
+image-$(CONFIG_BOARD_GENERIC_FIT) += $(fit-image)
+targets += $(fit-name)
+
 ifdef CONFIG_ARM
 pblb-$(CONFIG_PBL_SINGLE_IMAGE) += start_pbl
 FILE_barebox.img = start_pbl.pblb
diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include
index 8c311b997e24..e905b5466371 100644
--- a/scripts/Kbuild.include
+++ b/scripts/Kbuild.include
@@ -60,6 +60,12 @@ escsq = $(subst $(squote),'\$(squote)',$1)
 # Quote a string to pass it to C files. foo => '"foo"'
 stringify = $(squote)$(quote)$1$(quote)$(squote)
 
+###
+# Remove the quotes in the argument
+define remove_quotes
+$(strip $(subst $(quote),,$(1)))
+endef
+
 ###
 # The path to Kbuild or Makefile. Kbuild has precedence over Makefile.
 kbuild-file = $(or $(wildcard $(src)/Kbuild),$(src)/Makefile)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 0dfb496777dd..4e8e1254833e 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -311,6 +311,23 @@ quiet_cmd_sha256bin ?= SHA-BIN $@
 quiet_cmd_sha256sum ?= SHA     $@
       cmd_sha256sum ?= sha256sum $2 > $@
 
+# Flat Image Tree (FIT)
+# This allows for packaging of barebox and all devicetrees files, using
+# compression.
+# ---------------------------------------------------------------------------
+
+MAKE_FIT := $(srctree)/scripts/make_fit.py
+
+# Use this to override the compression algorithm of the DTBs
+FIT_COMPRESSION ?= none
+
+quiet_cmd_fit = FIT     $@
+      cmd_fit = $(MAKE_FIT) -o $@ --arch $(CONFIG_ARCH_MKIMAGE_NAME) --os linux \
+		--name 'barebox-$(KERNELRELEASE)' \
+		$(if $(findstring 1,$(KBUILD_VERBOSE)),-v) \
+		$(if $(FIT_DECOMPOSE_DTBS),--decompose-dtbs) \
+		--dtb-compress $(FIT_COMPRESSION) -k $< @$(word 2,$^)
+
 # Decompressor for barebox proper binary when using PBL
 # ---------------------------------------------------------------------------
 
diff --git a/scripts/make_fit.py b/scripts/make_fit.py
new file mode 100755
index 000000000000..075b7c258ff2
--- /dev/null
+++ b/scripts/make_fit.py
@@ -0,0 +1,331 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2024 Google LLC
+# Written by Simon Glass <sjg@xxxxxxxxxxxx>
+#
+
+"""Build a FIT containing a lot of devicetree files
+
+Usage:
+    make_fit.py -A arm64 -n 'Linux-6.6' -O linux
+        -o arch/arm64/boot/image.fit -k /tmp/kern/arch/arm64/boot/image.itk
+        @arch/arm64/boot/dts/dtbs-list -E -c gzip
+
+Creates a FIT containing the supplied kernel and a set of devicetree files,
+either specified individually or listed in a file (with an '@' prefix).
+
+Use -E to generate an external FIT (where the data is placed after the
+FIT data structure). This allows parsing of the data without loading
+the entire FIT.
+
+Use -c to compress the data, using bzip2, gzip, lz4, lzma, lzo and
+zstd algorithms.
+
+Use -D to decompose "composite" DTBs into their base components and
+deduplicate the resulting base DTBs and DTB overlays. This requires the
+DTBs to be sourced from the kernel build directory, as the implementation
+looks at the .cmd files produced by the kernel build.
+
+The resulting FIT can be booted by bootloaders which support FIT, such
+as U-Boot, Linuxboot, Tianocore, etc.
+
+Note that this tool does not yet support adding a ramdisk / initrd.
+"""
+
+import argparse
+import collections
+import os
+import subprocess
+import sys
+import tempfile
+import time
+
+import libfdt
+
+
+# Tool extension and the name of the command-line tools
+CompTool = collections.namedtuple('CompTool', 'ext,tools')
+
+COMP_TOOLS = {
+    'bzip2': CompTool('.bz2', 'bzip2'),
+    'gzip': CompTool('.gz', 'pigz,gzip'),
+    'lz4': CompTool('.lz4', 'lz4'),
+    'lzma': CompTool('.lzma', 'lzma'),
+    'lzo': CompTool('.lzo', 'lzop'),
+    'zstd': CompTool('.zstd', 'zstd'),
+}
+
+
+def parse_args():
+    """Parse the program ArgumentParser
+
+    Returns:
+        Namespace object containing the arguments
+    """
+    epilog = 'Build a FIT from a directory tree containing .dtb files'
+    parser = argparse.ArgumentParser(epilog=epilog, fromfile_prefix_chars='@')
+    parser.add_argument('-A', '--arch', type=str, required=True,
+          help='Specifies the architecture')
+    parser.add_argument('--dtb-compress', type=str, default='none',
+          help='Specifies the compression of the DTBs')
+    parser.add_argument('-D', '--decompose-dtbs', action='store_true',
+          help='Decompose composite DTBs into base DTB and overlays')
+    parser.add_argument('-E', '--external', action='store_true',
+          help='Convert the FIT to use external data')
+    parser.add_argument('-n', '--name', type=str, required=True,
+          help='Specifies the name')
+    parser.add_argument('-o', '--output', type=str, required=True,
+          help='Specifies the output file (.fit)')
+    parser.add_argument('-O', '--os', type=str, required=True,
+          help='Specifies the operating system')
+    parser.add_argument('-k', '--kernel', type=str, required=True,
+          help='Specifies the (uncompressed) kernel input file (.itk)')
+    parser.add_argument('-v', '--verbose', action='store_true',
+                        help='Enable verbose output')
+    parser.add_argument('dtbs', type=str, nargs='*',
+          help='Specifies the devicetree files to process')
+
+    return parser.parse_args()
+
+
+def setup_fit(fsw, name):
+    """Make a start on writing the FIT
+
+    Outputs the root properties and the 'images' node
+
+    Args:
+        fsw (libfdt.FdtSw): Object to use for writing
+        name (str): Name of kernel image
+    """
+    fsw.INC_SIZE = 65536
+    fsw.finish_reservemap()
+    fsw.begin_node('')
+    fsw.property_string('description', f'{name} with devicetree set')
+    fsw.property_u32('#address-cells', 1)
+
+    fsw.property_u32('timestamp', int(time.time()))
+    fsw.begin_node('images')
+
+
+def write_kernel(fsw, data, args):
+    """Write out the kernel image
+
+    Writes a kernel node along with the required properties
+
+    Args:
+        fsw (libfdt.FdtSw): Object to use for writing
+        data (bytes): Data to write (possibly compressed)
+        args (Namespace): Contains necessary strings:
+            arch: FIT architecture, e.g. 'arm64'
+            fit_os: Operating Systems, e.g. 'linux'
+            name: Name of OS, e.g. 'Linux-6.6.0-rc7'
+            compress: Compression algorithm to use, e.g. 'gzip'
+    """
+    with fsw.add_node('kernel'):
+        fsw.property_string('description', args.name)
+        fsw.property_string('type', 'kernel_noload')
+        fsw.property_string('arch', args.arch)
+        fsw.property_string('os', args.os)
+        fsw.property_string('compression', 'none')
+        fsw.property('data', data)
+        fsw.property_u32('load', 0)
+        fsw.property_u32('entry', 0)
+
+
+def finish_fit(fsw, entries):
+    """Finish the FIT ready for use
+
+    Writes the /configurations node and subnodes
+
+    Args:
+        fsw (libfdt.FdtSw): Object to use for writing
+        entries (list of tuple): List of configurations:
+            str: Description of model
+            str: Compatible stringlist
+    """
+    fsw.end_node()
+    seq = 0
+    with fsw.add_node('configurations'):
+        for model, compat, files in entries:
+            seq += 1
+            with fsw.add_node(f'conf-{seq}'):
+                fsw.property('compatible', bytes(compat))
+                fsw.property_string('description', model)
+                fsw.property('fdt', bytes(''.join(f'fdt-{x}\x00' for x in files), "ascii"))
+                fsw.property_string('kernel', 'kernel')
+    fsw.end_node()
+
+
+def compress_data(inf, compress):
+    """Compress data using a selected algorithm
+
+    Args:
+        inf (IOBase): Filename containing the data to compress
+        compress (str): Compression algorithm, e.g. 'gzip'
+
+    Return:
+        bytes: Compressed data
+    """
+    if compress == 'none':
+        return inf.read()
+
+    comp = COMP_TOOLS.get(compress)
+    if not comp:
+        raise ValueError(f"Unknown compression algorithm '{compress}'")
+
+    with tempfile.NamedTemporaryFile() as comp_fname:
+        with open(comp_fname.name, 'wb') as outf:
+            done = False
+            for tool in comp.tools.split(','):
+                try:
+                    subprocess.call([tool, '-c'], stdin=inf, stdout=outf)
+                    done = True
+                    break
+                except FileNotFoundError:
+                    pass
+            if not done:
+                raise ValueError(f'Missing tool(s): {comp.tools}\n')
+            with open(comp_fname.name, 'rb') as compf:
+                comp_data = compf.read()
+    return comp_data
+
+
+def output_dtb(fsw, seq, fname, arch, compress):
+    """Write out a single devicetree to the FIT
+
+    Args:
+        fsw (libfdt.FdtSw): Object to use for writing
+        seq (int): Sequence number (1 for first)
+        fname (str): Filename containing the DTB
+        arch: FIT architecture, e.g. 'arm64'
+        compress (str): Compressed algorithm, e.g. 'gzip'
+    """
+    with fsw.add_node(f'fdt-{seq}'):
+        fsw.property_string('description', os.path.basename(fname))
+        fsw.property_string('type', 'flat_dt')
+        fsw.property_string('arch', arch)
+        fsw.property_string('compression', compress)
+
+        with open(fname, 'rb') as inf:
+            compressed = compress_data(inf, compress)
+        fsw.property('data', compressed)
+
+
+def process_dtb(fname, args):
+    """Process an input DTB, decomposing it if requested and is possible
+
+    Args:
+        fname (str): Filename containing the DTB
+        args (Namespace): Program arguments
+    Returns:
+        tuple:
+            str: Model name string
+            str: Root compatible string
+            files: list of filenames corresponding to the DTB
+    """
+    # Get the compatible / model information
+    with open(fname, 'rb') as inf:
+        data = inf.read()
+    fdt = libfdt.FdtRo(data)
+    model = fdt.getprop(0, 'model').as_str()
+    compat = fdt.getprop(0, 'compatible')
+
+    if args.decompose_dtbs:
+        # Check if the DTB needs to be decomposed
+        path, basename = os.path.split(fname)
+        cmd_fname = os.path.join(path, f'.{basename}.cmd')
+        with open(cmd_fname, 'r', encoding='ascii') as inf:
+            cmd = inf.read()
+
+        if 'scripts/dtc/fdtoverlay' in cmd:
+            # This depends on the structure of the composite DTB command
+            files = cmd.split()
+            files = files[files.index('-i') + 1:]
+        else:
+            files = [fname]
+    else:
+        files = [fname]
+
+    return (model, compat, files)
+
+def build_fit(args):
+    """Build the FIT from the provided files and arguments
+
+    Args:
+        args (Namespace): Program arguments
+
+    Returns:
+        tuple:
+            bytes: FIT data
+            int: Number of configurations generated
+            size: Total uncompressed size of data
+    """
+    seq = 0
+    size = 0
+    fsw = libfdt.FdtSw()
+    setup_fit(fsw, args.name)
+    entries = []
+    fdts = {}
+
+    # Handle the kernel
+    with open(args.kernel, 'rb') as inf:
+        comp_data = compress_data(inf, 'none')
+    size += os.path.getsize(args.kernel)
+    write_kernel(fsw, comp_data, args)
+
+    for fname in args.dtbs:
+        # Ignore non-DTB (*.dtb) files
+        if os.path.splitext(fname)[1] != '.dtb':
+            continue
+
+        (model, compat, files) = process_dtb(fname, args)
+
+        for fn in files:
+            if fn not in fdts:
+                seq += 1
+                size += os.path.getsize(fn)
+                output_dtb(fsw, seq, fn, args.arch, args.dtb_compress)
+                fdts[fn] = seq
+
+        files_seq = [fdts[fn] for fn in files]
+
+        entries.append([model, compat, files_seq])
+
+    finish_fit(fsw, entries)
+
+    # Include the kernel itself in the returned file count
+    return fsw.as_fdt().as_bytearray(), seq + 1, size
+
+
+def run_make_fit():
+    """Run the tool's main logic"""
+    args = parse_args()
+
+    out_data, count, size = build_fit(args)
+    with open(args.output, 'wb') as outf:
+        outf.write(out_data)
+
+    ext_fit_size = None
+    if args.external:
+        mkimage = os.environ.get('MKIMAGE', 'mkimage')
+        subprocess.check_call([mkimage, '-E', '-F', args.output],
+                              stdout=subprocess.DEVNULL)
+
+        with open(args.output, 'rb') as inf:
+            data = inf.read()
+        ext_fit = libfdt.FdtRo(data)
+        ext_fit_size = ext_fit.totalsize()
+
+    if args.verbose:
+        comp_size = len(out_data)
+        print(f'FIT size {comp_size:#x}/{comp_size / 1024 / 1024:.1f} MB',
+              end='')
+        if ext_fit_size:
+            print(f', header {ext_fit_size:#x}/{ext_fit_size / 1024:.1f} KB',
+                  end='')
+        print(f', {count} files, uncompressed {size / 1024 / 1024:.1f} MB')
+
+
+if __name__ == "__main__":
+    sys.exit(run_make_fit())
-- 
2.39.5





[Index of Archives]     [Linux Embedded]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux