From: Douglas Anderson <dianders@chromium.org>
To: Devicetree-compiler@vger.kernel.org,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Konrad Dybcio <konradybcio@kernel.org>
Cc: "Brian Norris" <briannorris@chromium.org>,
"Chen-Yu Tsai" <wenst@chromium.org>,
"André Draszik" <andre.draszik@linaro.org>,
Devicetree@vger.kernel.org, "Simon Glass" <sjg@chromium.org>,
"David Gibson" <david@gibson.dropbear.id.au>,
"Tudor Ambarus" <tudor.ambarus@linaro.org>,
linux-kernel@vger.kernel.org,
"Julius Werner" <jwerner@chromium.org>,
"Saravana Kannan" <saravanak@kernel.org>,
"Peter Griffin" <peter.griffin@linaro.org>,
"Douglas Anderson" <dianders@chromium.org>
Subject: [PATCH v2 10/10] fdtfactor: Add compiled FIT image factoring support
Date: Wed, 28 Oct 2026 14:46:59 -0700 [thread overview]
Message-ID: <20261028215600.1713475-11-dianders@chromium.org> (raw)
In-Reply-To: <20261028215600.1713475-1-dianders@chromium.org>
Bootloaders such as depthcharge on Chromebooks and U-Boot use Flattened
Image Tree (FIT) images to bundle a kernel image with multiple board FDT
blobs (`fdt-1`, `fdt-2`, ...) and per-board configuration nodes.
Add `-F`/`--fit`, `--outfile`, and `--single-base-dtb` options to
`fdtfactor` to factor a compiled FIT image directly:
- Parse non-overlay `type = "flat_dt"` nodes under `/images`,
decompressing their `data` blobs according to their `compression`
property (`none`, `lz4`, `lzma`, `xz`, `gzip`).
- Reject FIT images containing `signature*` subnodes under `/images/*`
or `/configurations/*`, as modifying FDT blobs and configurations
would invalidate cryptographic signatures.
- Partition the `flat_dt` blobs into compatible factor groups (by
compression, architecture, FDT header fields, and node-path overlap)
so a single FIT image containing multiple board/SoC families produces
a separate `fdt-base-1`, `fdt-base-2`, etc. for each family.
- Support `--single-base-dtb` to force all `flat_dt` blobs into a single
base DTB regardless of node-path overlap, failing only if their FDT
headers, architecture, or compression differ.
- Insert each `fdt-base-N` node into `/images`, replace each factored
`fdt-*` node's `data` with its compressed `.dtbo` overlay, recompute
any `hash*` subnode digests, and prepend `fdt-base-N` to each matching
configuration's `fdt` string list while preserving all other
configuration properties (`description`, `compatible`, etc.,
populating `compatible` from the board DTB's root node when omitted)
and any pre-existing overlay entries.
Add uncompressed, LZ4-compressed multi-family, signed-FIT rejection, and
`--single-base-dtb` FIT image test cases to the `fdtfactor` test suite.
Assisted-by: Gemini:gemini-pro-prerelease
Signed-off-by: Douglas Anderson <dianders@chromium.org>
---
Changes in v2:
- Rename -f/--fit and -F/--fit-out to -F/--fit and --outfile and clarify
FIT mode in the --help description (David Gibson, Simon Glass)
- Reject signed FIT images with signature* subnodes (Simon Glass)
- Use '<base-node-name>.dtb' for FIT base FDT descriptions instead of
common-prefix heuristics (Simon Glass)
- Compare FIT group node overlap against the group's common node set
instead of only the first entry (Simon Glass)
- Document in README.md that U-Boot's boot_get_fdt_fit() currently
requires FIT overlays to be uncompressed (Simon Glass)
README.md | 17 ++
fdtfactor | 588 +++++++++++++++++++++++++++++++++++--
tests/Makefile.tests | 2 +-
tests/fdtfactor_lz4.its | 109 +++++++
tests/fdtfactor_other1.dts | 33 +++
tests/fdtfactor_other2.dts | 33 +++
tests/fdtfactor_test.its | 109 +++++++
tests/run_tests.sh | 158 +++++++++-
8 files changed, 1018 insertions(+), 31 deletions(-)
create mode 100644 tests/fdtfactor_lz4.its
create mode 100644 tests/fdtfactor_other1.dts
create mode 100644 tests/fdtfactor_other2.dts
create mode 100644 tests/fdtfactor_test.its
diff --git a/README.md b/README.md
index 6b5ad32f474b..17f7563cffa5 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ Basic usage:
```
$ fdtfactor -o out_dir board1.dtb board2.dtb board3.dtb
+$ fdtfactor -F image.fit --outfile image.factored.fit
```
Options:
@@ -123,6 +124,22 @@ Options:
* `-o`, `--outdir`: Output directory for generated `.dtbo` files and
`--base-dtb` (default: alongside each input `.dtb` for `.dtbo` files,
current directory for `--base-dtb`).
+* `-F`, `--fit`: Factor `/images/fdt-*` blobs inside a compiled FIT image
+ (creating `/images/fdt-base-1`, `/images/fdt-base-2`, ... per compatible
+ board/SoC family grouped by FDT headers and `>= 50%` node-path overlap,
+ converting each `/images/fdt-N` node into a `.dtbo` overlay, updating
+ `/configurations/conf-*` `fdt` and `compatible` properties, and
+ recalculating `/images/*/hash-*` digests). Each factored base DTB and
+ `.dtbo` overlay preserves the `compression` setting of its `/images/fdt-*`
+ node (note: while bootloaders such as depthcharge decompress compressed
+ FIT overlays, U-Boot's `boot_get_fdt_fit()` currently only decompresses
+ the base DTB and requires FIT overlays to be uncompressed). Signed FIT
+ images with `signature*` subnodes are rejected because modifying FDT blobs
+ and configurations would invalidate cryptographic signatures.
+* `--outfile`: Output FIT image path when `--fit` is used (default:
+ overwrite `--fit` in place).
+* `--single-base-dtb`: Force a single `/images/fdt-base-1` across all FDTs
+ in `--fit` mode rather than grouping by node-path overlap.
* `-S`, `--strip-local-fixups`: Omit `/__local_fixups__` from the generated
`base.dtb` and final reconstructed device trees. By default, `fdtfactor`
preserves `/__local_fixups__` on `base.dtb` so that when `fdt_overlay_apply()`
diff --git a/fdtfactor b/fdtfactor
index ab261765bb29..f07389c07d02 100755
--- a/fdtfactor
+++ b/fdtfactor
@@ -8,16 +8,25 @@ properties shared across the inputs into a single common `base.dtb` and emits a
small per-board `.dtbo` overlay for each input. Applying a board's `.dtbo` to
`base.dtb` (e.g., via `fdtoverlay` or `fdt_overlay_apply()`) reconstructs the
original board DTB.
+
+Alternatively, `fdtfactor -F <image.fit>` can rewrite a compiled U-Boot /
+ChromeOS Flattened Image Tree (FIT) in place (or to `--outfile`), factoring its
+embedded `/images/fdt-*` blobs into base DTBs (`fdt-base-1`, `fdt-base-2`, ...)
+and overlays while updating `/configurations/conf-*` `fdt` and `compatible`
+properties and recalculating `/images/*/hash-*` digests.
"""
import argparse
from collections import Counter
+import copy
import gzip
+import hashlib
import lzma
import os
import struct
import subprocess
import sys
+import zlib
import libfdt
@@ -107,17 +116,24 @@ def _run_lz4(args, input_bytes, action):
return res.stdout
-def decompress_blob(raw_bytes, filename=''):
+def decompress_blob(raw_bytes, filename='', comp=None):
"""Decompress a DTB blob if compressed, returning `(raw_bytes, comp)`.
Args:
- raw_bytes: Input FDT `bytes` (compressed or uncompressed).
- filename: Optional filename for error reporting and extension fallback.
+ raw_bytes: Input file or property `bytes` (compressed or uncompressed).
+ filename: Optional filename or node description for error reporting and
+ extension fallback.
+ comp: Optional explicit compression type (`'none'`, `'lz4'`,
+ `'lz4_legacy'`, `'lzma'`, `'xz'`, `'gzip'`), or `None` to
+ auto-detect via `detect_compression()`.
Returns:
Tuple of `(uncompressed_bytes, resolved_compression_type)`.
"""
- comp = detect_compression(raw_bytes, filename)
+ if not comp:
+ comp = detect_compression(raw_bytes, filename)
+ elif comp == 'lz4' and raw_bytes.startswith(LZ4_LEGACY_MAGIC):
+ comp = 'lz4_legacy'
if comp == 'none':
return raw_bytes, 'none'
if comp in ('lz4', 'lz4_legacy'):
@@ -238,6 +254,10 @@ def _is_dtbo_overlay(fdt):
)
+class OverlayBlobError(ValueError):
+ """Raised when an input FDT blob is already a `.dtbo` overlay."""
+
+
class ParsedDtb:
"""Complete input .dtb blob with phandle-normalized tree and FDT metadata.
@@ -258,17 +278,24 @@ class ParsedDtb:
independent of numeric phandle numbering.
"""
- def __init__(self, filename):
- """Parse an input FDT file into a normalized NodeTree.
+ def __init__(self, filename, file_bytes=None, comp=None):
+ """Parse an input FDT file or blob into a normalized NodeTree.
Args:
- filename: Path to the input .dtb (or compressed .dtb.*) file.
+ filename: File path (or display name when `file_bytes` is given).
+ file_bytes: Optional raw (compressed or uncompressed) FDT bytes; if
+ None, read from `filename`.
+ comp: Optional explicit compression algorithm name, or None to
+ auto-detect via `decompress_blob()`.
"""
self.filename = filename
- with open(filename, 'rb') as f:
- file_bytes = f.read()
+ if file_bytes is None:
+ with open(filename, 'rb') as f:
+ file_bytes = f.read()
self.compressed_size = len(file_bytes)
- raw_bytes, self.compression = decompress_blob(file_bytes, filename)
+ raw_bytes, self.compression = decompress_blob(
+ file_bytes, filename, comp=comp
+ )
self.raw_bytes = raw_bytes
try:
@@ -278,7 +305,7 @@ class ParsedDtb:
raise ValueError(f'{filename}: Invalid FDT blob ({exc})') from exc
if _is_dtbo_overlay(fdt):
- raise ValueError(
+ raise OverlayBlobError(
f'{filename}: Input is already a DTB overlay (.dtbo)'
)
@@ -849,6 +876,456 @@ def create_base_dtb_and_overlays(dtbs, strip_local_fixups=False):
return base_dtb_blob, dtbo_blobs
+# ---------------------------------------------------------------------------
+# Compiled FIT image factoring (targeting depthcharge / coreboot / U-Boot)
+# ---------------------------------------------------------------------------
+
+def _compute_fit_hash(algo_name, data_bytes):
+ """Compute a binary hash digest for a FIT image `hash*` subnode.
+
+ Args:
+ algo_name: Hash algorithm name (e.g., `'crc32'`, `'sha1'`, `'sha256'`).
+ data_bytes: Input `bytes` to hash.
+
+ Returns:
+ Binary hash digest `bytes`.
+ """
+ if algo_name == 'crc32':
+ return struct.pack('>I', zlib.crc32(data_bytes) & 0xffffffff)
+ h = hashlib.new(algo_name)
+ h.update(data_bytes)
+ return h.digest()
+
+
+def _update_fit_hashes(img_node):
+ """Recompute `value` properties on any FIT `hash*` subnodes of `img_node`.
+
+ Args:
+ img_node: `NodeTree` for a `/images/*` subnode whose `hash*` child
+ nodes should be updated in place from `img_node.props['data']`.
+ """
+ data_bytes = img_node.props.get('data')
+ if data_bytes is None:
+ return
+ for sub_name, sub_node in img_node.children.items():
+ if not sub_name.startswith('hash'):
+ continue
+ if 'algo' not in sub_node.props:
+ raise ValueError(
+ f'FIT hash subnode {sub_name!r} lacks an algo property'
+ )
+ algo = sub_node.get_str('algo')
+ sub_node.props['value'] = _compute_fit_hash(algo, data_bytes)
+
+
+def _is_dtb_filename_desc(desc):
+ """Return True if `desc` looks like a `.dtb` filename.
+
+ Args:
+ desc: Description string from a FIT `/images/fdt-*` node.
+
+ Returns:
+ True if `desc` has no spaces and ends with `.dtb` (optionally followed
+ by a recognized compression suffix), False otherwise.
+ """
+ if not desc or ' ' in desc:
+ return False
+ _, stem, desc_comp = split_dtb_filename(desc)
+ return os.path.basename(desc) == f'{stem}.dtb{COMP_TO_EXT[desc_comp]}'
+
+
+def group_fit_dtbs(fdt_entries):
+ """Partition FIT `flat_dt` entries into compatible factor groups.
+
+ DTBs are grouped by `(compression, arch, load, entry, boot_cpuid_phys,
+ mem_rsv)` and node-path overlap (>= 50%) against the group's common node
+ set so that a single FIT image containing multiple board/SoC families
+ produces one base DTB per family.
+
+ Args:
+ fdt_entries: Sequence of FIT `flat_dt` entry dicts from
+ `_extract_fit_fdt_entries()`.
+
+ Returns:
+ List of entry-dict lists, one per compatible group.
+ """
+ groups = []
+ for entry in fdt_entries:
+ dtb = entry['dtb']
+ header_key = (
+ dtb.compression,
+ *(entry['node'].props.get(p) for p in ('arch', 'load', 'entry')),
+ dtb.boot_cpuid_phys,
+ dtb.mem_rsv,
+ )
+ entry_nodes = {path for path, _ in dtb.root.walk()}
+
+ placed = False
+ for grp in groups:
+ if grp['key'] != header_key:
+ continue
+ common_nodes = grp['common_nodes']
+ union_len = len(entry_nodes | common_nodes)
+ overlap = (
+ len(entry_nodes & common_nodes) / union_len
+ if union_len
+ else 1.0
+ )
+ if overlap >= 0.5:
+ grp['common_nodes'] &= entry_nodes
+ grp['entries'].append(entry)
+ placed = True
+ break
+
+ if not placed:
+ groups.append({
+ 'key': header_key,
+ 'common_nodes': set(entry_nodes),
+ 'entries': [entry],
+ })
+
+ return [grp['entries'] for grp in groups]
+
+
+def _extract_fit_fdt_entries(fit_in_path, images_node, configs_node):
+ """Extract and parse non-overlay `flat_dt` entries from `/images`.
+
+ Args:
+ fit_in_path: Input FIT image path (for error reporting).
+ images_node: `NodeTree` for the FIT `/images` node.
+ configs_node: `NodeTree` for the FIT `/configurations` node.
+
+ Returns:
+ List of dicts (`{'name', 'node', 'dtb'}`) for all non-overlay
+ `flat_dt` image subnodes.
+ """
+ if any(name.startswith('fdt-base-') for name in images_node.children):
+ raise ValueError(
+ f'{fit_in_path}: FIT image already contains fdt-base-* node(s)'
+ )
+
+ for conf_name, conf_node in configs_node.children.items():
+ for sub_name in conf_node.children:
+ if sub_name.startswith(('signature', 'hash')):
+ raise ValueError(
+ f'{fit_in_path}: /configurations/{conf_name}/{sub_name} '
+ f'is not supported; sign the FIT image after factoring'
+ )
+
+ fdt_entries = []
+ for img_name, img_node in images_node.children.items():
+ if ('data-offset' in img_node.props or
+ 'data-position' in img_node.props):
+ raise ValueError(
+ f'{fit_in_path}: /images/{img_name} uses external data '
+ f'(data-offset/data-position), which is not supported'
+ )
+ for sub_name in img_node.children:
+ if sub_name.startswith('signature'):
+ raise ValueError(
+ f'{fit_in_path}: /images/{img_name}/{sub_name} is not '
+ f'supported; sign the FIT image after factoring'
+ )
+ img_type = img_node.get_str('type') if 'type' in img_node.props else ''
+ if img_type != 'flat_dt':
+ continue
+ raw_data = img_node.props.get('data')
+ if raw_data is None:
+ raise ValueError(
+ f'{fit_in_path}: /images/{img_name} lacks a data property'
+ )
+ node_label = f'{fit_in_path}:/images/{img_name}'
+ comp_prop = (
+ img_node.get_str('compression')
+ if 'compression' in img_node.props
+ else 'none'
+ ) or 'none'
+ try:
+ parsed_dtb = ParsedDtb(
+ node_label, file_bytes=raw_data, comp=comp_prop
+ )
+ except OverlayBlobError:
+ continue
+
+ fdt_entries.append({
+ 'name': img_name,
+ 'node': img_node,
+ 'dtb': parsed_dtb,
+ })
+
+ if len(fdt_entries) < 2:
+ raise ValueError(
+ 'FIT image must contain at least two non-overlay flat_dt '
+ 'nodes to factor'
+ )
+
+ return fdt_entries
+
+
+def _partition_fit_fdt_entries(fdt_entries, single_base_dtb):
+ """Partition `fdt_entries` into factorable groups and validate constraints.
+
+ Args:
+ fdt_entries: List of FIT `flat_dt` entry dicts from
+ `_extract_fit_fdt_entries()`.
+ single_base_dtb: If True, require all `fdt_entries` to share a single
+ base DTB group rather than grouping by node-path overlap.
+
+ Returns:
+ List of factorable entry-dict groups (each containing at least 2
+ entries).
+ """
+ if single_base_dtb:
+ ref_entry = fdt_entries[0]
+ for entry in fdt_entries[1:]:
+ if entry['dtb'].compression != ref_entry['dtb'].compression:
+ raise ValueError(
+ f'--single-base-dtb requires all FIT flat_dt nodes to use '
+ f'the same compression: {ref_entry["name"]} has '
+ f'{ref_entry["dtb"].compression!r} vs {entry["name"]} '
+ f'{entry["dtb"].compression!r}'
+ )
+ for prop in ('arch', 'load', 'entry'):
+ if (entry['node'].props.get(prop) !=
+ ref_entry['node'].props.get(prop)):
+ raise ValueError(
+ f'--single-base-dtb requires all FIT flat_dt nodes to '
+ f'use the same {prop} property: {ref_entry["name"]} vs '
+ f'{entry["name"]}'
+ )
+ return [fdt_entries]
+
+ groups = group_fit_dtbs(fdt_entries)
+ factorable_groups = [g for g in groups if len(g) >= 2]
+ if not factorable_groups:
+ raise ValueError(
+ 'FIT image must contain at least two compatible '
+ 'flat_dt nodes to factor'
+ )
+ for g in groups:
+ if len(g) == 1:
+ print(
+ f'Note: FIT flat_dt node {g[0]["name"]} has no '
+ f'compatible peers and will be left unfactored',
+ file=sys.stderr,
+ )
+ return factorable_groups
+
+
+def _factor_fit_group(entries, base_dtb_node_name, strip_local_fixups):
+ """Factor one compatible group of FIT `flat_dt` entries.
+
+ Args:
+ entries: List of FIT `flat_dt` entry dicts in this compatible group.
+ base_dtb_node_name: Name for the new `/images/<base_dtb_node_name>`
+ node (e.g., `'fdt-base-1'`).
+ strip_local_fixups: If True, omit `/__local_fixups__` from the
+ generated base DTB.
+
+ Returns:
+ Tuple of `(base_dtb_node, stat_args)` where `base_dtb_node` is the
+ constructed `NodeTree` for `/images/<base_dtb_node_name>` and
+ `stat_args` is an argument tuple for `_print_dtb_stats()`.
+ """
+ group_dtbs = [e['dtb'] for e in entries]
+ comp = group_dtbs[0].compression
+
+ base_dtb_blob, dtbo_blobs = create_base_dtb_and_overlays(
+ group_dtbs,
+ strip_local_fixups=strip_local_fixups,
+ )
+
+ base_dtb_comp_bytes = compress_blob(
+ base_dtb_blob, comp, filename=f'/images/{base_dtb_node_name}'
+ )
+ base_dtb_node = copy.deepcopy(entries[0]['node'])
+ for prop in ('phandle', 'compatible'):
+ base_dtb_node.props.pop(prop, None)
+ base_dtb_node.children = {
+ k: v
+ for k, v in base_dtb_node.children.items()
+ if k.startswith('hash')
+ }
+ for sub_node in base_dtb_node.children.values():
+ sub_node.props.pop('phandle', None)
+ if 'description' in base_dtb_node.props:
+ base_dtb_node.set_str(
+ 'description',
+ f'{base_dtb_node_name}.dtb{COMP_TO_EXT[comp]}',
+ )
+ base_dtb_node.props['data'] = base_dtb_comp_bytes
+ if 'compression' in base_dtb_node.props or comp != 'none':
+ comp_prop = 'lz4' if comp == 'lz4_legacy' else comp
+ base_dtb_node.set_str('compression', comp_prop)
+ _update_fit_hashes(base_dtb_node)
+
+ dtbo_comp_sizes = []
+ for entry, dtbo_blob in zip(entries, dtbo_blobs):
+ img_node = entry['node']
+ dtbo_comp_bytes = compress_blob(
+ dtbo_blob, comp, filename=f'/images/{entry["name"]}'
+ )
+ dtbo_comp_sizes.append(len(dtbo_comp_bytes))
+
+ if 'description' in img_node.props:
+ old_desc = img_node.get_str('description')
+ if _is_dtb_filename_desc(old_desc):
+ img_node.set_str(
+ 'description',
+ get_dtbo_output_path(old_desc, None, comp=comp),
+ )
+
+ # Strip 'load' and 'entry' from overlay image nodes so bootloaders do
+ # not load overlays at the base FDT address and overwrite the base FDT.
+ for prop in ('load', 'entry'):
+ img_node.props.pop(prop, None)
+
+ img_node.props['data'] = dtbo_comp_bytes
+ _update_fit_hashes(img_node)
+
+ stat_args = (
+ group_dtbs,
+ base_dtb_blob,
+ dtbo_blobs,
+ comp,
+ len(base_dtb_comp_bytes),
+ dtbo_comp_sizes,
+ f'[{base_dtb_node_name}] ',
+ )
+ return base_dtb_node, stat_args
+
+
+def _update_fit_images_and_configs(images_node, configs_node,
+ insert_before_first, fdt_to_base_dtb):
+ """Insert `fdt-base-*` nodes into `/images` and update `/configurations`.
+
+ Args:
+ images_node: `NodeTree` for `/images` (mutated in place).
+ configs_node: `NodeTree` for `/configurations` (mutated in place).
+ insert_before_first: Dict mapping the first `fdt-*` subnode name of
+ each group to `(base_dtb_node_name, base_dtb_node)`.
+ fdt_to_base_dtb: Dict mapping each factored `fdt-*` subnode name to
+ `(base_dtb_node_name, orig_parsed_dtb)`.
+ """
+ new_images_children = {}
+ for img_name, img_node in images_node.children.items():
+ if img_name in insert_before_first:
+ b_name, b_node = insert_before_first[img_name]
+ new_images_children[b_name] = b_node
+ new_images_children[img_name] = img_node
+ images_node.children = new_images_children
+
+ for conf_name, conf_node in configs_node.children.items():
+ if 'fdt' not in conf_node.props:
+ continue
+ fdt_list = conf_node.get_stringlist('fdt')
+ if not fdt_list:
+ continue
+ primary_fdt = fdt_list[0]
+ for extra_fdt in fdt_list[1:]:
+ if extra_fdt in fdt_to_base_dtb:
+ raise ValueError(
+ f'/configurations/{conf_name} lists factored flat_dt '
+ f'{extra_fdt!r} after the primary FDT'
+ )
+ if primary_fdt in fdt_to_base_dtb:
+ base_dtb_name, orig_dtb = fdt_to_base_dtb[primary_fdt]
+ conf_node.set_stringlist('fdt', [base_dtb_name] + fdt_list)
+ # If the configuration omitted 'compatible' (relying on the
+ # bootloader reading root 'compatible' from fdt_list[0]), copy the
+ # board's root 'compatible' property onto the configuration node
+ # now that fdt_list[0] is the shared base DTB.
+ if 'compatible' not in conf_node.props:
+ root_compat = orig_dtb.root.props.get('compatible')
+ if root_compat is not None:
+ conf_node.props['compatible'] = root_compat
+
+
+def factor_fit_image(fit_in_path, fit_out_path, strip_local_fixups=False,
+ single_base_dtb=False, stats=False):
+ """Factor all `flat_dt` images in a compiled FIT image into base + overlays.
+
+ Args:
+ fit_in_path: Path to the input compiled FIT image.
+ fit_out_path: Path where the factored FIT image should be written.
+ strip_local_fixups: If True, omit `/__local_fixups__` from generated
+ base DTBs.
+ single_base_dtb: If True, force all `flat_dt` nodes into a single
+ `fdt-base-1` base DTB.
+ stats: If True, print per-group and overall FIT size savings.
+ """
+ with open(fit_in_path, 'rb') as f:
+ orig_fit_bytes = f.read()
+
+ try:
+ orig_fit_fdt = libfdt.FdtRo(orig_fit_bytes)
+ libfdt.check_err(
+ libfdt.fdt_check_full(orig_fit_fdt._fdt, len(orig_fit_bytes))
+ )
+ except libfdt.FdtException as exc:
+ raise ValueError(f'{fit_in_path}: Invalid FDT blob ({exc})') from exc
+
+ fit_root = libfdt.NodeTree.unflatten(orig_fit_fdt)
+ images_node = fit_root.children.get('images')
+ configs_node = fit_root.children.get('configurations')
+ if images_node is None or configs_node is None:
+ raise ValueError(
+ f'{fit_in_path}: FIT image must contain /images and '
+ f'/configurations nodes'
+ )
+
+ fdt_entries = _extract_fit_fdt_entries(
+ fit_in_path, images_node, configs_node
+ )
+ factorable_groups = _partition_fit_fdt_entries(
+ fdt_entries, single_base_dtb
+ )
+
+ fdt_to_base_dtb = {}
+ insert_before_first = {}
+ group_stats = []
+
+ for idx, entries in enumerate(factorable_groups, 1):
+ base_dtb_node_name = f'fdt-base-{idx}'
+ base_dtb_node, stat_args = _factor_fit_group(
+ entries, base_dtb_node_name, strip_local_fixups
+ )
+ insert_before_first[entries[0]['name']] = (
+ base_dtb_node_name, base_dtb_node
+ )
+ for entry in entries:
+ fdt_to_base_dtb[entry['name']] = (base_dtb_node_name, entry['dtb'])
+ group_stats.append(stat_args)
+
+ _update_fit_images_and_configs(
+ images_node, configs_node, insert_before_first, fdt_to_base_dtb
+ )
+
+ new_fit_bytes = fit_root.flatten(
+ mem_rsv=orig_fit_fdt.get_mem_rsv_list(),
+ boot_cpuid_phys=orig_fit_fdt.boot_cpuid_phys(),
+ )
+
+ out_dir = os.path.dirname(fit_out_path)
+ if out_dir:
+ os.makedirs(out_dir, exist_ok=True)
+ with open(fit_out_path, 'wb') as f:
+ f.write(new_fit_bytes)
+
+ if stats:
+ for stat_args in group_stats:
+ _print_dtb_stats(*stat_args)
+ fit_saved = len(orig_fit_bytes) - len(new_fit_bytes)
+ fit_pct = (
+ (fit_saved / len(orig_fit_bytes) * 100.0) if orig_fit_bytes else 0.0
+ )
+ print(
+ f'FIT image total: {len(orig_fit_bytes):,} B -> '
+ f'{len(new_fit_bytes):,} B '
+ f'(saved {fit_saved:,} B / {fit_pct:.1f}%)'
+ )
+
+
def get_dtbo_output_path(dtb_path, outdir, comp='none'):
"""Determine the output `.dtbo[.comp]` file path for an input `.dtb` path.
@@ -881,13 +1358,15 @@ def parse_args(argv=None):
parser = argparse.ArgumentParser(
description=(
'Split a set of compiled device tree blobs (.dtb, or compressed '
- '.dtb.lz4/.xz/.lzma/.gz) into a single common base .dtb and '
- 'per-board overlay blobs (.dtbo).'
+ '.dtb.lz4/.xz/.lzma/.gz) into a common base .dtb and per-board '
+ 'overlay blobs (.dtbo), or update a compiled FIT image '
+ '(-F/--fit) in-place (or to --outfile) by factoring its '
+ '/images/fdt-* blobs into base DTBs and overlays.'
)
)
parser.add_argument(
'-b', '--base-dtb',
- default='base.dtb',
+ default=None,
metavar='FILENAME',
help=(
'Output common base .dtb (or .dtb.lz4/.xz/.lzma/.gz) filename '
@@ -914,6 +1393,23 @@ def parse_args(argv=None):
'for --base-dtb)'
),
)
+ parser.add_argument(
+ '-F', '--fit',
+ default=None,
+ metavar='FIT_IMAGE',
+ help=(
+ 'Input compiled FIT image (.fit / .itb) to factor (updated '
+ 'in-place unless --outfile is specified)'
+ ),
+ )
+ parser.add_argument(
+ '--outfile',
+ default=None,
+ metavar='FIT_OUT',
+ help=(
+ 'Output path for the factored FIT image (default: overwrite --fit)'
+ ),
+ )
parser.add_argument(
'-S', '--strip-local-fixups',
action='store_true',
@@ -922,6 +1418,15 @@ def parse_args(argv=None):
'reconstructed device trees'
),
)
+ parser.add_argument(
+ '--single-base-dtb',
+ action='store_true',
+ help=(
+ 'Force all flat_dt nodes in a FIT image (-F/--fit) to be '
+ 'factored into a single fdt-base-1 base DTB, failing only if '
+ 'headers, arch, or compression differ'
+ ),
+ )
parser.add_argument(
'-s', '--stats',
action='store_true',
@@ -929,26 +1434,40 @@ def parse_args(argv=None):
)
parser.add_argument(
'dtbs',
- nargs='+',
+ nargs='*',
metavar='DTB',
help=(
- 'Input .dtb (or compressed .dtb.lz4/.xz/.lzma/.gz) files (at '
- 'least two required)'
+ 'Input .dtb (or compressed .dtb.lz4/.xz/.lzma/.gz) files (when '
+ '--fit is not used)'
),
)
args = parser.parse_args(argv)
- if os.path.dirname(args.base_dtb):
- parser.error(
- '--base-dtb must be a filename without directory components; '
- 'use -o/--outdir to specify the output directory'
- )
- if len(args.dtbs) < 2:
- parser.error('at least two input .dtb files are required')
+ if args.outfile and not args.fit:
+ parser.error('--outfile requires -F/--fit')
+ if args.single_base_dtb and not args.fit:
+ parser.error('--single-base-dtb requires -F/--fit')
+ if args.fit:
+ if (args.dtbs or args.base_dtb or args.outdir or
+ args.base_compression != 'auto'):
+ parser.error(
+ '-F/--fit cannot be combined with -b/--base-dtb, '
+ '--base-compression, -o/--outdir, or positional DTB arguments'
+ )
+ else:
+ if args.base_dtb is None:
+ args.base_dtb = 'base.dtb'
+ if os.path.dirname(args.base_dtb):
+ parser.error(
+ '--base-dtb must be a filename without directory components; '
+ 'use -o/--outdir to specify the output directory'
+ )
+ if len(args.dtbs) < 2:
+ parser.error('at least two input .dtb files are required')
return args
def _print_dtb_stats(dtbs, base_dtb_blob, dtbo_blobs, base_dtb_comp='none',
- base_dtb_csize=None, dtbo_csizes=None):
+ base_dtb_csize=None, dtbo_csizes=None, prefix=''):
"""Print uncompressed and compressed size savings summary.
Args:
@@ -958,6 +1477,7 @@ def _print_dtb_stats(dtbs, base_dtb_blob, dtbo_blobs, base_dtb_comp='none',
base_dtb_comp: Compression format of the base DTB output.
base_dtb_csize: Byte size of the compressed base DTB output.
dtbo_csizes: Sequence of byte sizes of the compressed `.dtbo` outputs.
+ prefix: Optional line prefix (e.g., `'[fdt-base-1] '`).
"""
orig_uncomp = sum(len(d.raw_bytes) for d in dtbs)
base_dtb_uncomp = len(base_dtb_blob)
@@ -966,7 +1486,7 @@ def _print_dtb_stats(dtbs, base_dtb_blob, dtbo_blobs, base_dtb_comp='none',
saved_uncomp = orig_uncomp - new_uncomp
pct_uncomp = (saved_uncomp / orig_uncomp * 100.0) if orig_uncomp else 0.0
print(
- f'Uncompressed DTBs: {len(dtbs)} files ({orig_uncomp:,} B) -> '
+ f'{prefix}Uncompressed DTBs: {len(dtbs)} files ({orig_uncomp:,} B) -> '
f'base {base_dtb_uncomp:,} B + {len(dtbo_blobs)} overlays '
f'{dtbo_uncomp:,} B = {new_uncomp:,} B '
f'(saved {saved_uncomp:,} B / {pct_uncomp:.1f}%)'
@@ -975,9 +1495,9 @@ def _print_dtb_stats(dtbs, base_dtb_blob, dtbo_blobs, base_dtb_comp='none',
if any(c != 'none' for c in all_comps):
comp_label = '/'.join(sorted(all_comps))
hdr = (
- f'Compressed ({comp_label}):'
+ f'{prefix}Compressed ({comp_label}):'
if len(all_comps) == 1
- else f'On-disk ({comp_label}): '
+ else f'{prefix}On-disk ({comp_label}): '
)
orig_comp = sum(d.compressed_size for d in dtbs)
dtbo_csize = sum(dtbo_csizes)
@@ -1086,7 +1606,17 @@ def main(argv=None):
"""
args = parse_args(argv)
try:
- factor_dtb_files(args)
+ if args.fit:
+ fit_out = args.outfile if args.outfile else args.fit
+ factor_fit_image(
+ args.fit,
+ fit_out,
+ strip_local_fixups=args.strip_local_fixups,
+ single_base_dtb=args.single_base_dtb,
+ stats=args.stats,
+ )
+ else:
+ factor_dtb_files(args)
except (OSError, ValueError, RuntimeError, libfdt.FdtException) as exc:
sys.exit(f'Error: {exc}')
return 0
diff --git a/tests/Makefile.tests b/tests/Makefile.tests
index 300f244f41c5..a89bcde1f038 100644
--- a/tests/Makefile.tests
+++ b/tests/Makefile.tests
@@ -53,7 +53,7 @@ TESTS_DEPFILES = $(TESTS:%=%.d) \
$(addprefix $(TESTS_PREFIX),testutils.d)
TESTS_CLEANFILES_L = $(STD_CLEANFILES) \
- *.dtb *.test.dtbo *.test.dts *.test.dt.yaml *.dtsv1 \
+ *.dtb *.test.dtbo *.test.dts *.test.dt.yaml *.dtsv1 *.test.fit \
*.test.dtb.gz *.test.dtbo.gz *.test.dtb.xz *.test.dtbo.xz \
*.test.dtb.lzma *.test.dtbo.lzma *.test.dtb.lz4 *.test.dtbo.lz4 \
tmp.* *.bak \
diff --git a/tests/fdtfactor_lz4.its b/tests/fdtfactor_lz4.its
new file mode 100644
index 000000000000..dc2b345d90e5
--- /dev/null
+++ b/tests/fdtfactor_lz4.its
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/dts-v1/;
+
+/ {
+ description = "Test LZ4 FIT image for fdtfactor";
+ #address-cells = <1>;
+
+ images {
+ kernel-1 {
+ description = "Unavailable";
+ data = /incbin/("fdtfactor_board1.test.dtb.lz4");
+ type = "kernel_noload";
+ arch = "arm64";
+ os = "linux";
+ compression = "lz4";
+ load = <0>;
+ entry = <0>;
+ };
+ fdt-1 {
+ description = "fdtfactor_board1.test.dtb.lz4";
+ data = /incbin/("fdtfactor_board1.test.dtb.lz4");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "lz4";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-2 {
+ description = "fdtfactor_board2.test.dtb.lz4";
+ data = /incbin/("fdtfactor_board2.test.dtb.lz4");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "lz4";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-3 {
+ description = "fdtfactor_board3.test.dtb.lz4";
+ data = /incbin/("fdtfactor_board3.test.dtb.lz4");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "lz4";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-4 {
+ description = "fdtfactor_other1.test.dtb.lz4";
+ data = /incbin/("fdtfactor_other1.test.dtb.lz4");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "lz4";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-5 {
+ description = "fdtfactor_other2.test.dtb.lz4";
+ data = /incbin/("fdtfactor_other2.test.dtb.lz4");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "lz4";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-ovl-1 {
+ description = "overlay_overlay_phandle.test.dtb.lz4";
+ data = /incbin/("overlay_overlay_phandle.test.dtb.lz4");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "lz4";
+ };
+ };
+ configurations {
+ default = "conf-1";
+ conf-1 {
+ description = "board-meta-1";
+ kernel = "kernel-1";
+ fdt = "fdt-1";
+ compatible = "vendor,board1", "vendor,common";
+ };
+ conf-2 {
+ description = "board-meta-2";
+ kernel = "kernel-1";
+ fdt = "fdt-2";
+ };
+ conf-3 {
+ description = "board-meta-3";
+ kernel = "kernel-1";
+ fdt = "fdt-3", "fdt-ovl-1";
+ compatible = "vendor,board3", "vendor,common";
+ };
+ conf-4 {
+ description = "board-meta-4";
+ kernel = "kernel-1";
+ fdt = "fdt-4";
+ compatible = "vendor,other1", "vendor,other";
+ };
+ conf-5 {
+ description = "board-meta-5";
+ kernel = "kernel-1";
+ fdt = "fdt-5";
+ compatible = "vendor,other2", "vendor,other";
+ };
+ };
+};
diff --git a/tests/fdtfactor_other1.dts b/tests/fdtfactor_other1.dts
new file mode 100644
index 000000000000..d6257d1fd29d
--- /dev/null
+++ b/tests/fdtfactor_other1.dts
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/dts-v1/;
+
+/memreserve/ 0xdeadbeef00000000 0x100000;
+
+/ {
+ compatible = "vendor,other1", "vendor,other";
+ model = "other1";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ osn1: other-subnode@1 {
+ compatible = "other-subnode1";
+ reg = <1>;
+ prop-ref = <&osn1 0x10 &ossn1 0x20>;
+
+ ossn1: other-subsubnode {
+ compatible = "other-subsubnode";
+ prop-int = <0x1111>;
+ };
+ };
+
+ osn2: other-subnode@2 {
+ reg = <2>;
+ prop-phandle = <&osn2>;
+ };
+
+ o1_node: other1-node {
+ prop-str = "other1";
+ ref-base = <&osn1>;
+ ref-self = <&o1_node>;
+ };
+};
diff --git a/tests/fdtfactor_other2.dts b/tests/fdtfactor_other2.dts
new file mode 100644
index 000000000000..2d97f3595e3c
--- /dev/null
+++ b/tests/fdtfactor_other2.dts
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/dts-v1/;
+
+/memreserve/ 0xdeadbeef00000000 0x100000;
+
+/ {
+ compatible = "vendor,other2", "vendor,other";
+ model = "other2";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ osn1: other-subnode@1 {
+ compatible = "other-subnode1";
+ reg = <1>;
+ prop-ref = <&osn1 0x10 &ossn1 0x20>;
+
+ ossn1: other-subsubnode {
+ compatible = "other-subsubnode";
+ prop-int = <0x2222>;
+ };
+ };
+
+ osn2: other-subnode@2 {
+ reg = <2>;
+ prop-phandle = <&osn2>;
+ };
+
+ o2_node: other2-node {
+ prop-str = "other2";
+ ref-base = <&ossn1>;
+ ref-self = <&o2_node>;
+ };
+};
diff --git a/tests/fdtfactor_test.its b/tests/fdtfactor_test.its
new file mode 100644
index 000000000000..bf3b9ef8c439
--- /dev/null
+++ b/tests/fdtfactor_test.its
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/dts-v1/;
+
+/ {
+ description = "Test FIT image for fdtfactor";
+ #address-cells = <1>;
+
+ images {
+ kernel-1 {
+ description = "Unavailable";
+ data = /incbin/("fdtfactor_board1.test.dtb");
+ type = "kernel_noload";
+ arch = "arm64";
+ os = "linux";
+ compression = "none";
+ load = <0>;
+ entry = <0>;
+ };
+ fdt-1 {
+ description = "fdtfactor_board1.test.dtb";
+ data = /incbin/("fdtfactor_board1.test.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-2 {
+ description = "fdtfactor_board2.test.dtb";
+ data = /incbin/("fdtfactor_board2.test.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-3 {
+ description = "fdtfactor_board3.test.dtb";
+ data = /incbin/("fdtfactor_board3.test.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-4 {
+ description = "fdtfactor_other1.test.dtb";
+ data = /incbin/("fdtfactor_other1.test.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-5 {
+ description = "fdtfactor_other2.test.dtb";
+ data = /incbin/("fdtfactor_other2.test.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha1";
+ };
+ };
+ fdt-ovl-1 {
+ description = "overlay_overlay_phandle.test.dtb";
+ data = /incbin/("overlay_overlay_phandle.test.dtb");
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ };
+ };
+ configurations {
+ default = "conf-1";
+ conf-1 {
+ description = "board-meta-1";
+ kernel = "kernel-1";
+ fdt = "fdt-1";
+ compatible = "vendor,board1", "vendor,common";
+ };
+ conf-2 {
+ description = "board-meta-2";
+ kernel = "kernel-1";
+ fdt = "fdt-2";
+ };
+ conf-3 {
+ description = "board-meta-3";
+ kernel = "kernel-1";
+ fdt = "fdt-3", "fdt-ovl-1";
+ compatible = "vendor,board3", "vendor,common";
+ };
+ conf-4 {
+ description = "board-meta-4";
+ kernel = "kernel-1";
+ fdt = "fdt-4";
+ compatible = "vendor,other1", "vendor,other";
+ };
+ conf-5 {
+ description = "board-meta-5";
+ kernel = "kernel-1";
+ fdt = "fdt-5";
+ compatible = "vendor,other2", "vendor,other";
+ };
+ };
+};
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index b1a8188a9264..fb95ff6fbd25 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -1279,6 +1279,162 @@ fdtfactor_tests () {
run_test dtbs_equal_unordered fdtfactor_board1.test.dtb \
fdtfactor_base_comp.test.dir/merged1.test.dtb
fi
+
+ # FIT image factoring (uncompressed and compressed, multi-group by node
+ # overlap)
+ # fdtget -t r appends a trailing newline; strip that last byte.
+ fdtget_raw () {
+ $DTGET -t r "$1" "$2" "$3" > "$4.tmp"
+ head -c $(($($STATSZ "$4.tmp") - 1)) "$4.tmp" > "$4"
+ rm -f "$4.tmp"
+ }
+ run_dtc_test -b 2 -@ -L -I dts -O dtb -o fdtfactor_other1.test.dtb \
+ "$SRCDIR/fdtfactor_other1.dts"
+ run_dtc_test -b 2 -@ -L -I dts -O dtb -o fdtfactor_other2.test.dtb \
+ "$SRCDIR/fdtfactor_other2.dts"
+ run_dtc_test -@ -I dts -O dtb -o overlay_overlay_phandle.test.dtb \
+ "$SRCDIR/overlay_overlay_phandle.dts"
+
+ run_dtc_test -i . -I dts -O dtb -o fdtfactor.test.fit \
+ "$SRCDIR/fdtfactor_test.its"
+ cp fdtfactor.test.fit fdtfactor_orig.test.fit
+ run_wrap_test $PYTHON $FDTFACTOR -s -F fdtfactor.test.fit
+ run_fdtget_test "fdt-base-1.dtb" fdtfactor.test.fit \
+ /images/fdt-base-1 description
+ run_fdtget_test "fdtfactor_board1.test.dtbo" fdtfactor.test.fit \
+ /images/fdt-1 description
+ run_fdtget_test "fdt-base-2.dtb" fdtfactor.test.fit \
+ /images/fdt-base-2 description
+ run_fdtget_test "fdtfactor_other1.test.dtbo" fdtfactor.test.fit \
+ /images/fdt-4 description
+ run_fdtget_test "fdt-base-1 fdt-1" fdtfactor.test.fit \
+ /configurations/conf-1 fdt
+ run_fdtget_test "fdt-base-1 fdt-2" fdtfactor.test.fit \
+ /configurations/conf-2 fdt
+ run_fdtget_test "fdt-base-1 fdt-3 fdt-ovl-1" fdtfactor.test.fit \
+ /configurations/conf-3 fdt
+ run_fdtget_test "fdt-base-2 fdt-4" fdtfactor.test.fit \
+ /configurations/conf-4 fdt
+ run_fdtget_test "fdt-base-2 fdt-5" fdtfactor.test.fit \
+ /configurations/conf-5 fdt
+ # conf-1 already had an explicit compatible property; conf-2 did not, so
+ # fdtfactor should populate it from fdt-2's root compatible property.
+ run_fdtget_test "vendor,board1 vendor,common" fdtfactor.test.fit \
+ /configurations/conf-1 compatible
+ run_fdtget_test "test_fdtfactor" fdtfactor.test.fit \
+ /configurations/conf-2 compatible
+ run_fdtget_test "vendor,other1 vendor,other" fdtfactor.test.fit \
+ /configurations/conf-4 compatible
+ # Verify that hash-1/value was populated for fdt-base-1, fdt-base-2, and
+ # updated for fdt-1.
+ base1_hash=$($DTGET -t bx fdtfactor.test.fit \
+ /images/fdt-base-1/hash-1 value)
+ base2_hash=$($DTGET -t bx fdtfactor.test.fit \
+ /images/fdt-base-2/hash-1 value)
+ fdt1_hash=$($DTGET -t bx fdtfactor.test.fit \
+ /images/fdt-1/hash-1 value)
+ if [ -z "$base1_hash" ] || [ -z "$base2_hash" ] || \
+ [ "$fdt1_hash" = "0 0 0 0 0 0 0 0" ]; then
+ FAIL "FIT hash-1 values were not updated"
+ fi
+ # Extract factored base DTBs and overlays from the FIT and verify all 5
+ # boards with fdtoverlay + dtbs_equal_unordered.
+ fdtget_raw fdtfactor.test.fit /images/fdt-base-1 data fit_base1.test.dtb
+ for i in 1 2 3; do
+ fdtget_raw fdtfactor.test.fit /images/fdt-${i} data \
+ fit_fdt${i}.test.dtbo
+ run_wrap_test $FDTOVERLAY -i fit_base1.test.dtb \
+ -o fit_merged${i}.test.dtb fit_fdt${i}.test.dtbo
+ run_test dtbs_equal_unordered fdtfactor_board${i}.test.dtb \
+ fit_merged${i}.test.dtb
+ done
+ fdtget_raw fdtfactor.test.fit /images/fdt-base-2 data fit_base2.test.dtb
+ for i in 1 2; do
+ idx=$((i + 3))
+ fdtget_raw fdtfactor.test.fit /images/fdt-${idx} data \
+ fit_fdt${idx}.test.dtbo
+ run_wrap_test $FDTOVERLAY -i fit_base2.test.dtb \
+ -o fit_merged${idx}.test.dtb fit_fdt${idx}.test.dtbo
+ run_test dtbs_equal_unordered fdtfactor_other${i}.test.dtb \
+ fit_merged${idx}.test.dtb
+ done
+
+ # --single-base-dtb forces a single fdt-base-1 across all FDTs
+ run_wrap_test $PYTHON $FDTFACTOR --single-base-dtb \
+ -F fdtfactor_orig.test.fit --outfile fdtfactor_single.test.fit
+ run_fdtget_test "fdt-base-1.dtb" fdtfactor_single.test.fit \
+ /images/fdt-base-1 description
+ run_fdtget_test "fdt-base-1 fdt-1" fdtfactor_single.test.fit \
+ /configurations/conf-1 fdt
+ run_fdtget_test "fdt-base-1 fdt-4" fdtfactor_single.test.fit \
+ /configurations/conf-4 fdt
+ fdtget_raw fdtfactor_single.test.fit /images/fdt-base-1 data \
+ fit_single_base1.test.dtb
+ for i in 1 2 3; do
+ fdtget_raw fdtfactor_single.test.fit /images/fdt-${i} data \
+ fit_single_fdt${i}.test.dtbo
+ run_wrap_test $FDTOVERLAY -i fit_single_base1.test.dtb \
+ -o fit_single_merged${i}.test.dtb fit_single_fdt${i}.test.dtbo
+ run_test dtbs_equal_unordered fdtfactor_board${i}.test.dtb \
+ fit_single_merged${i}.test.dtb
+ done
+ for i in 1 2; do
+ idx=$((i + 3))
+ fdtget_raw fdtfactor_single.test.fit /images/fdt-${idx} data \
+ fit_single_fdt${idx}.test.dtbo
+ run_wrap_test $FDTOVERLAY -i fit_single_base1.test.dtb \
+ -o fit_single_merged${idx}.test.dtb fit_single_fdt${idx}.test.dtbo
+ run_test dtbs_equal_unordered fdtfactor_other${i}.test.dtb \
+ fit_single_merged${idx}.test.dtb
+ done
+
+ # Verify that conflicting image properties (e.g. arch) in a group fail
+ cp fdtfactor_orig.test.fit fdtfactor_bad_arch.test.fit
+ run_wrap_test $DTPUT fdtfactor_bad_arch.test.fit /images/fdt-4 \
+ arch -ts "arm"
+ run_wrap_error_test $PYTHON $FDTFACTOR --single-base-dtb \
+ -F fdtfactor_bad_arch.test.fit
+
+ # Verify that signed FIT images (with signature* subnodes) and
+ # already-factored FIT images (containing fdt-base-*) are rejected
+ cp fdtfactor_orig.test.fit fdtfactor_signed.test.fit
+ run_wrap_test $DTPUT fdtfactor_signed.test.fit -c \
+ /configurations/conf-1/signature-1
+ run_wrap_error_test $PYTHON $FDTFACTOR -F fdtfactor_signed.test.fit
+ run_wrap_error_test $PYTHON $FDTFACTOR -F fdtfactor.test.fit
+
+ if command -v lz4 >/dev/null 2>&1; then
+ run_wrap_test lz4 -12 -q -f -m \
+ fdtfactor_board1.test.dtb fdtfactor_board2.test.dtb \
+ fdtfactor_board3.test.dtb fdtfactor_other1.test.dtb \
+ fdtfactor_other2.test.dtb overlay_overlay_phandle.test.dtb
+ run_dtc_test -i . -I dts -O dtb -o fdtfactor_lz4.test.fit \
+ "$SRCDIR/fdtfactor_lz4.its"
+ run_wrap_test $PYTHON $FDTFACTOR -s \
+ -F fdtfactor_lz4.test.fit --outfile fdtfactor_lz4_out.test.fit
+ run_fdtget_test "fdt-base-1.dtb.lz4" fdtfactor_lz4_out.test.fit \
+ /images/fdt-base-1 description
+ run_fdtget_test "fdtfactor_board1.test.dtbo.lz4" \
+ fdtfactor_lz4_out.test.fit /images/fdt-1 description
+ run_fdtget_test "fdt-base-2.dtb.lz4" fdtfactor_lz4_out.test.fit \
+ /images/fdt-base-2 description
+ run_fdtget_test "fdtfactor_other1.test.dtbo.lz4" \
+ fdtfactor_lz4_out.test.fit /images/fdt-4 description
+ run_fdtget_test "fdt-base-1 fdt-1" fdtfactor_lz4_out.test.fit \
+ /configurations/conf-1 fdt
+ run_fdtget_test "fdt-base-2 fdt-4" fdtfactor_lz4_out.test.fit \
+ /configurations/conf-4 fdt
+ fdtget_raw fdtfactor_lz4_out.test.fit /images/fdt-base-1 data \
+ fit_lz4_base1.test.dtb.lz4
+ fdtget_raw fdtfactor_lz4_out.test.fit /images/fdt-1 data \
+ fit_lz4_fdt1.test.dtbo.lz4
+ run_wrap_test lz4 -d -q -f --rm fit_lz4_base1.test.dtb.lz4
+ run_wrap_test lz4 -d -q -f --rm fit_lz4_fdt1.test.dtbo.lz4
+ run_wrap_test $FDTOVERLAY -i fit_lz4_base1.test.dtb \
+ -o fit_lz4_merged1.test.dtb fit_lz4_fdt1.test.dtbo
+ run_test dtbs_equal_unordered fdtfactor_board1.test.dtb \
+ fit_lz4_merged1.test.dtb
+ fi
}
while getopts "vt:me" ARG ; do
@@ -1308,7 +1464,7 @@ if [ -z "$TESTSETS" ]; then
fi
# Make sure we don't have stale blobs lying around
-rm -f *.test.dtb *.test.dtbo *.test.dts *.test.dtb.gz *.test.dtbo.gz \
+rm -f *.test.dtb *.test.dtbo *.test.dts *.test.fit *.test.dtb.gz *.test.dtbo.gz \
*.test.dtb.xz *.test.dtbo.xz *.test.dtb.lzma *.test.dtbo.lzma \
*.test.dtb.lz4 *.test.dtbo.lz4
rm -rf *.test.dir
--
2.56.0.313.g2479322e68-goog
prev parent reply other threads:[~2026-10-28 21:56 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 21:46 [PATCH v2 00/10] fdtfactor: Introduce fdtfactor to auto-factor overlays Douglas Anderson
2026-10-28 21:46 ` [PATCH v2 01/10] pylibfdt: Fix FdtSw.as_fdt() when buffer is nearly full Douglas Anderson
2026-10-28 21:46 ` [PATCH v2 02/10] pylibfdt: Add subnode/property iterators and get_mem_rsv_list() Douglas Anderson
2026-10-29 8:38 ` David Gibson
2026-10-28 21:46 ` [PATCH v2 03/10] pylibfdt: Add NodeTree live tree representation Douglas Anderson
2026-10-28 21:46 ` [PATCH v2 04/10] libfdt: overlay: Factor out fixup and fragment path parsing helpers Douglas Anderson
2026-10-28 21:46 ` [PATCH v2 05/10] libfdt: overlay: Prepare fixup and fragment path helpers for reuse Douglas Anderson
2026-10-28 21:46 ` [PATCH v2 06/10] libfdt: overlay: Update base /__local_fixups__ on apply Douglas Anderson
2026-10-28 21:46 ` [PATCH v2 07/10] fdtfactor: Add tool to split DTBs into a base DTB and overlays Douglas Anderson
2026-10-28 21:46 ` [PATCH v2 08/10] fdtfactor: Add -S/--strip-local-fixups option Douglas Anderson
2026-10-28 21:46 ` [PATCH v2 09/10] fdtfactor: Support compressed DTB inputs and outputs Douglas Anderson
2026-10-28 21:46 ` Douglas Anderson [this message]
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=20261028215600.1713475-11-dianders@chromium.org \
--to=dianders@chromium.org \
--cc=Devicetree-compiler@vger.kernel.org \
--cc=Devicetree@vger.kernel.org \
--cc=andre.draszik@linaro.org \
--cc=briannorris@chromium.org \
--cc=conor+dt@kernel.org \
--cc=david@gibson.dropbear.id.au \
--cc=jwerner@chromium.org \
--cc=konradybcio@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=peter.griffin@linaro.org \
--cc=robh@kernel.org \
--cc=saravanak@kernel.org \
--cc=sjg@chromium.org \
--cc=tudor.ambarus@linaro.org \
--cc=wenst@chromium.org \
/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
all inboxes | Powered by JetHome®