From: Ahmad Fatoum <a.fatoum@pengutronix.de>
To: barebox@lists.infradead.org
Cc: Felix Singer <felixsinger@posteo.net>,
Ahmad Fatoum <a.fatoum@pengutronix.de>
Subject: [PATCH 10/13] images: add barebox FIT image target
Date: Sun, 12 Jan 2025 09:34:29 +0100 [thread overview]
Message-ID: <20250112083432.320215-11-a.fatoum@pengutronix.de> (raw)
In-Reply-To: <20250112083432.320215-1-a.fatoum@pengutronix.de>
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@pengutronix.de>
---
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@chromium.org>
+#
+
+"""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
next prev parent reply other threads:[~2025-01-12 8:39 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-01-12 8:34 [PATCH 00/13] " Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 01/13] ARM: dts: add device trees for the QEMU Virt machine Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 02/13] treewide: collect the name of all board device trees Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 03/13] kbuild: collect available device trees in dtbs-list Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 04/13] scripts: add new scripts_dtc target Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 05/13] kbuild: restrict dtbs target to enabled DTs by default Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 06/13] kbuild: improve make help description Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 07/13] kbuild: allow dependency on any file in images/ Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 08/13] arch: maintain Linux kernel and mkimage ARCH mapping in Kconfig Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 09/13] arch: make BOARD_GENERIC_DT a user-selectable option across archs Ahmad Fatoum
2025-01-12 8:34 ` Ahmad Fatoum [this message]
2025-01-17 8:32 ` [PATCH 10/13] images: add barebox FIT image target Sascha Hauer
2025-01-17 9:03 ` Ahmad Fatoum
2025-01-20 7:19 ` Sascha Hauer
2025-01-12 8:34 ` [PATCH 11/13] MAKEALL: rename target in symbols to more appropiate defconfig Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 12/13] MAKEALL: add support for building arbitrary targets Ahmad Fatoum
2025-01-12 8:34 ` [PATCH 13/13] ci: container: add python3-libfdt dependency for barebox.fit Ahmad Fatoum
2025-01-14 8:18 ` [PATCH 00/13] images: add barebox FIT image target Sascha Hauer
2025-01-21 8:07 ` (subset) " Sascha Hauer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250112083432.320215-11-a.fatoum@pengutronix.de \
--to=a.fatoum@pengutronix.de \
--cc=barebox@lists.infradead.org \
--cc=felixsinger@posteo.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox