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: "Chen-Yu Tsai" <wenst@chromium.org>,
linux-kernel@vger.kernel.org, Devicetree@vger.kernel.org,
"Brian Norris" <briannorris@chromium.org>,
"Tudor Ambarus" <tudor.ambarus@linaro.org>,
"André Draszik" <andre.draszik@linaro.org>,
"Julius Werner" <jwerner@chromium.org>,
"Saravana Kannan" <saravanak@kernel.org>,
"Peter Griffin" <peter.griffin@linaro.org>,
"Douglas Anderson" <dianders@chromium.org>
Subject: [PATCH 4/4] fdtfactor: Add compiled FIT image factoring support
Date: Thu, 15 Oct 2026 11:21:37 -0700 [thread overview]
Message-ID: <20261015182221.2931742-5-dianders@chromium.org> (raw)
In-Reply-To: <20261015182221.2931742-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`, `-F`/`--fit-out`, 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`, `gzip`).
- 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, and `--single-base-dtb`
FIT image test cases to the `fdtoverlay` test suite.
Assisted-by: Gemini:gemini-pro-prerelease
Signed-off-by: Douglas Anderson <dianders@chromium.org>
---
README.md | 15 +
fdtfactor | 726 +++++++++++++++++++++++++++++++++++--
tests/Makefile.tests | 2 +-
tests/fdtfactor_other1.dts | 69 ++++
tests/fdtfactor_other2.dts | 69 ++++
tests/fdtfactor_test.its | 109 ++++++
tests/run_tests.sh | 69 +++-
7 files changed, 1033 insertions(+), 26 deletions(-)
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 6ace31bfbfdb..30fc28fa7545 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,12 @@ Basic usage:
$ fdtfactor -b base.dtb -o out_dir board1.dtb board2.dtb board3.dtb
```
+Compiled FIT image usage (targeting depthcharge / coreboot / U-Boot):
+
+```
+$ fdtfactor -S -f image.fit -F image.factored.fit
+```
+
Options:
* `-b`, `--base-dtb`: Output path for the common base `.dtb` (compressed if
@@ -118,6 +124,11 @@ Options:
without a directory component).
* `-o`, `--outdir`: Output directory for generated `.dtbo` files (and for
`--base-dtb` when given without a directory component).
+* `-f`, `--fit`: Input compiled FIT image (`.fit` / `.itb`) to factor in
+ place (or write to `-F`), creating one `fdt-base-N` per compatible
+ board/SoC family.
+* `-F`, `--fit-out`: Output path for the factored FIT image (default:
+ overwrite `--fit`).
* `-S`, `--strip-local-fixups`: Omit `/__local_fixups__` from the generated
`base.dtb` and final reconstructed device trees. When `-L` is passed to
`dtc` solely so `fdtfactor` can locate phandle cells at build time, the
@@ -127,6 +138,10 @@ Options:
`.dtbo` overlays. Omit `-S` if downstream tooling or verification
expects applying a board's `.dtbo` to `base.dtb` to reproduce the
original `.dtb`'s `/__local_fixups__` subtree verbatim.
+* `--single-base-dtb`: In FIT mode (`-f`/`--fit`), force all `flat_dt`
+ images to be factored into a single `fdt-base-1` base `.dtb` regardless of
+ node-path overlap, failing only if their FDT headers (`boot_cpuid_phys`,
+ `/memreserve/`), architecture, or compression differ.
* `--verify`: Verify that applying each generated `.dtbo` to `base.dtb`
with `libfdt` recreates the original `.dtb`.
* `-v`, `--verbose`: Print uncompressed and compressed size savings.
diff --git a/fdtfactor b/fdtfactor
index 9f06801f973c..8a0c996581e5 100755
--- a/fdtfactor
+++ b/fdtfactor
@@ -2,21 +2,24 @@
# SPDX-License-Identifier: GPL-2.0-or-later
"""Split compiled device tree blobs (.dtb) into a base .dtb and .dtbo overlays.
-Given a set of compiled device tree blobs, fdtfactor extracts all common nodes
-and properties into a single base .dtb file and writes the per-board
-differences into .dtbo overlay files. Applying an overlay to the base .dtb
-using fdtoverlay or libfdt's fdt_overlay_apply() recreates the corresponding
-original device tree.
+Given a set of compiled device tree blobs (or a compiled FIT image),
+fdtfactor extracts all common nodes and properties into a base .dtb and writes
+the per-board differences into .dtbo overlays. Applying an overlay to the base
+.dtb using fdtoverlay, libfdt's fdt_overlay_apply(), or depthcharge's FIT
+loader recreates the corresponding original device tree.
"""
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
@@ -27,8 +30,8 @@ LZ4_LEGACY_MAGIC = b'\x02\x21\x4c\x18'
GZIP_MAGIC = b'\x1f\x8b'
XZ_MAGIC = b'\xfd7zXZ\x00'
# Matches raw LZMA_ALONE headers with default lc=3, lp=0, pb=2 (0x5d) and
-# power-of-two dictionary sizes >= 64 KiB; .lzma extension handles any
-# non-default LZMA headers.
+# power-of-two dictionary sizes >= 64 KiB; .lzma extension or FIT compression
+# property handles any non-default LZMA headers.
LZMA_ALONE_MAGIC = b'\x5d\x00\x00'
COMP_TO_EXT = {
@@ -209,6 +212,51 @@ class NodeTree:
self.props = {}
self.children = {}
+ @classmethod
+ def from_fdt(cls, fdt, nodeoffset=0):
+ """Recursively copy a `libfdt.FdtRo` subtree into a `NodeTree`.
+
+ Args:
+ fdt: A `libfdt.FdtRo` instance.
+ nodeoffset: Node offset in `fdt` to copy (defaults to root `0`).
+
+ Returns:
+ A `NodeTree` containing all properties and subnodes of
+ `nodeoffset`.
+ """
+ node = cls()
+ poff = fdt.first_property_offset(nodeoffset, quiet=libfdt.QUIET_NOTFOUND)
+ while poff >= 0:
+ prop = fdt.get_property_by_offset(poff)
+ node.props[prop.name] = bytes(prop)
+ poff = fdt.next_property_offset(poff, quiet=libfdt.QUIET_NOTFOUND)
+
+ soff = fdt.first_subnode(nodeoffset, quiet=libfdt.QUIET_NOTFOUND)
+ while soff >= 0:
+ name = fdt.get_name(soff)
+ node.children[name] = cls.from_fdt(fdt, soff)
+ soff = fdt.next_subnode(soff, quiet=libfdt.QUIET_NOTFOUND)
+ return node
+
+ def copy(self):
+ """Return a deep copy of this `NodeTree`."""
+ return copy.deepcopy(self)
+
+ def update_fit_hashes(self):
+ """Recompute `value` properties on any FIT `hash*` subnodes of `self`."""
+ data_bytes = self.props.get('data')
+ if data_bytes is None:
+ return
+ for sub_name, sub_node in self.children.items():
+ if not sub_name.startswith('hash'):
+ continue
+ algo = _prop_as_str(sub_node.props.get('algo'))
+ if not algo:
+ raise ValueError(
+ f'FIT hash subnode {sub_name!r} lacks an algo property'
+ )
+ sub_node.props['value'] = _compute_fit_hash(algo, data_bytes)
+
def get_node(self, path):
"""Return the descendant NodeTree at `path`, creating missing nodes.
@@ -337,7 +385,7 @@ class ParsedDtb:
"""Parsed representation of an input .dtb blob."""
def __init__(self, filename, raw_bytes, compressed_size=None,
- compression='none'):
+ compression='none', fdt=None):
"""Parse an uncompressed FDT blob into nodes, symbols, and fixups.
Args:
@@ -347,6 +395,7 @@ class ParsedDtb:
decompression (defaults to `len(raw_bytes)`).
compression: Compression algorithm of the input file (`'none'`,
`'lz4'`, `'lzma'`, `'xz'`, or `'gzip'`).
+ fdt: Optional pre-validated `libfdt.FdtRo` instance for `raw_bytes`.
"""
self.filename = filename
self.raw_bytes = raw_bytes
@@ -354,11 +403,12 @@ class ParsedDtb:
compressed_size if compressed_size is not None else len(raw_bytes)
)
self.compression = compression
- fdt = _open_fdt_ro(raw_bytes, filename)
- if _is_dtbo_overlay(fdt):
- raise ValueError(
- f'{filename}: Input is already a DTB overlay (.dtbo)'
- )
+ if fdt is None:
+ fdt = _open_fdt_ro(raw_bytes, filename)
+ if _is_dtbo_overlay(fdt):
+ raise ValueError(
+ f'{filename}: Input is already a DTB overlay (.dtbo)'
+ )
# Extract FDT header metadata (boot_cpuid_phys and /memreserve/ table)
# stored outside the DT node/property tree; see _validate_dtb_headers()
@@ -1211,6 +1261,588 @@ def verify_recreated_dtb(orig_dtb, base_dtb_blob, dtbo_blob,
)
+# ---------------------------------------------------------------------------
+# Compiled FIT image factoring (targeting depthcharge / coreboot / U-Boot)
+# ---------------------------------------------------------------------------
+
+def _prop_as_str(raw_bytes):
+ """Decode a NUL-terminated FDT string property.
+
+ Args:
+ raw_bytes: Raw property bytes (or None).
+
+ Returns:
+ Decoded string with trailing NULs stripped, or `''` if empty/None.
+ """
+ if not raw_bytes:
+ return ''
+ return raw_bytes.rstrip(b'\x00').decode('utf-8', errors='replace')
+
+
+def _prop_as_str_list(raw_bytes):
+ """Decode a NUL-separated FDT stringlist property.
+
+ Args:
+ raw_bytes: Raw property bytes (or None).
+
+ Returns:
+ List of decoded non-empty strings.
+ """
+ if not raw_bytes:
+ return []
+ parts = raw_bytes.rstrip(b'\x00').split(b'\x00')
+ return [p.decode('utf-8', errors='replace') for p in parts if p]
+
+
+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. `'sha1'`, `'sha256'`, `'crc32'`).
+ data_bytes: Image `data` property bytes to hash.
+
+ Returns:
+ Binary digest as `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 _is_dtb_filename_desc(desc):
+ """Return True if `desc` looks like a `.dtb` filename."""
+ if not desc or ' ' in desc:
+ return False
+ _, ext = os.path.splitext(desc)
+ if ext in EXT_TO_COMP:
+ desc = desc[:-len(ext)]
+ return desc.endswith('.dtb') and len(os.path.basename(desc)) > 4
+
+
+def _derive_base_dtb_description(entries, base_dtb_node_name, comp):
+ """Derive a `description` string for a factored base FDT image node.
+
+ Extracts the common prefix across `.dtb` filename descriptions in `entries`
+ and trims any partially matched trailing token before appending
+ `'-base.dtb[.comp]'` (falling back to `'<base_dtb_node_name>.dtb[.comp]'`
+ if no common prefix remains):
+
+ * Diverging at a letter (truncate back to the last `-`, `_`, or `.`):
+ - `['sc7180-trogdor-lazor', 'sc7180-trogdor-pompom']`
+ -> `'sc7180-trogdor-base.dtb'`
+ - `['sc7180-lazor', 'sc7180-limozeen']` -> `'sc7180-base.dtb'`
+ - `['alpha', 'apricot']` -> `'fdt-base-1.dtb'` (no prior separator)
+ * Diverging at a digit inside a token (strip trailing digits from prefix):
+ - `['fdtfactor_board1', 'fdtfactor_board2']` -> `'fdtfactor_board-base.dtb'`
+ - `['sc7180-trogdor', 'sc7280-herobrine']` -> `'sc-base.dtb'`
+ * Diverging at or right after a separator (keep full tokens before
+ separator):
+ - `['sc7180-1', 'sc7180-2']` -> `'sc7180-base.dtb'`
+ - `['sc7180-trogdor-r1', 'sc7180-trogdor-r1-lte']`
+ -> `'sc7180-trogdor-r1-base.dtb'`
+
+ Args:
+ entries: List of FIT `flat_dt` entry dicts in the factor group.
+ base_dtb_node_name: Node name for the base FDT (e.g. `'fdt-base-1'`).
+ comp: Compression algorithm name (`'none'`, `'lz4'`, etc.).
+
+ Returns:
+ Description string (e.g. `'sc7180-trogdor-base.dtb.lz4'`).
+ """
+ stems = []
+ for e in entries:
+ desc = _prop_as_str(e['node'].props.get('description'))
+ if _is_dtb_filename_desc(desc):
+ _, stem, _ = split_dtb_filename(desc)
+ if stem:
+ stems.append(stem)
+ if len(stems) >= 2:
+ prefix = os.path.commonprefix(stems)
+ if any(
+ len(s) > len(prefix) and s[len(prefix)].isalpha()
+ for s in stems
+ ):
+ sep_idx = max(prefix.rfind(c) for c in ('-', '_', '.'))
+ prefix = prefix[:sep_idx] if sep_idx >= 0 else ''
+ elif (
+ any(
+ len(s) > len(prefix) and s[len(prefix)].isdigit()
+ for s in stems
+ )
+ and not prefix.endswith(('-', '_', '.'))
+ ):
+ prefix = prefix.rstrip('0123456789')
+ prefix = prefix.rstrip('-_.')
+ if prefix:
+ return f'{prefix}-base.dtb{COMP_TO_EXT[comp]}'
+ return f'{base_dtb_node_name}.dtb{COMP_TO_EXT[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%) so that a single FIT image
+ containing multiple board/SoC families (e.g. `sc7180-trogdor` and
+ `sc7280-herobrine`) produces one base DTB per family.
+
+ Args:
+ fdt_entries: List of FIT `flat_dt` entry dicts.
+
+ Returns:
+ List of entry lists, one per compatible factor group.
+ """
+ groups = []
+ for entry in fdt_entries:
+ dtb = entry['dtb']
+ header_key = (
+ entry['compression'],
+ entry['arch'],
+ entry['node'].props.get('load'),
+ entry['node'].props.get('entry'),
+ dtb.boot_cpuid_phys,
+ tuple(dtb.mem_rsv),
+ )
+ entry_nodes = set(dtb.nodes.keys())
+
+ placed = False
+ for grp in groups:
+ if grp['key'] != header_key:
+ continue
+ ref_nodes = grp['ref_nodes']
+ union_len = len(entry_nodes | ref_nodes)
+ overlap = (
+ len(entry_nodes & ref_nodes) / union_len if union_len else 1.0
+ )
+ if overlap >= 0.5:
+ grp['entries'].append(entry)
+ placed = True
+ break
+
+ if not placed:
+ groups.append({
+ 'key': header_key,
+ 'ref_nodes': entry_nodes,
+ 'entries': [entry],
+ })
+
+ return [grp['entries'] for grp in groups]
+
+
+def _extract_fit_fdt_entries(fit_in_path, images_node):
+ """Extract and parse non-overlay `flat_dt` entries from `/images`.
+
+ Any `/images` nodes that are already `.dtbo` overlays are skipped and left
+ intact in `/images`.
+
+ Args:
+ fit_in_path: Input FIT file path for error messages.
+ images_node: The `NodeTree` for `/images`.
+
+ Returns:
+ List of parsed `flat_dt` entry dicts.
+ """
+ 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)'
+ )
+
+ fdt_entries = []
+ for img_name, img_node in images_node.children.items():
+ # Check all /images/* nodes (including kernel/ramdisk) because
+ # re-serializing fit_root would drop external data appended past
+ # the FDT totalsize.
+ 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'
+ )
+ if _prop_as_str(img_node.props.get('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 = _prop_as_str(img_node.props.get('compression')) or 'none'
+ dtb_bytes, comp = decompress_blob(
+ raw_data, filename=node_label, comp=comp_prop
+ )
+ fdt_ro = _open_fdt_ro(dtb_bytes, node_label)
+ if _is_dtbo_overlay(fdt_ro):
+ continue
+
+ arch_str = _prop_as_str(img_node.props.get('arch'))
+ parsed_dtb = ParsedDtb(
+ node_label,
+ dtb_bytes,
+ compressed_size=len(raw_data),
+ compression=comp,
+ fdt=fdt_ro,
+ )
+ fdt_entries.append({
+ 'name': img_name,
+ 'node': img_node,
+ 'dtb': parsed_dtb,
+ 'compression': comp,
+ 'arch': arch_str,
+ })
+
+ 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 non-overlay FIT `flat_dt` entry dicts.
+ single_base_dtb: Whether `--single-base-dtb` was requested.
+
+ Returns:
+ List of entry lists to factor (each with `len >= 2`).
+ """
+ if single_base_dtb:
+ ref_entry = fdt_entries[0]
+ for entry in fdt_entries[1:]:
+ if entry['compression'] != ref_entry['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["compression"]!r} vs {entry["name"]} '
+ f'{entry["compression"]!r}'
+ )
+ if entry['arch'] != ref_entry['arch']:
+ raise ValueError(
+ f'--single-base-dtb requires all FIT flat_dt nodes to use '
+ f'the same arch: {ref_entry["name"]} has '
+ f'{ref_entry["arch"]!r} vs {entry["name"]} '
+ f'{entry["arch"]!r}'
+ )
+ for prop in ('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"]}'
+ )
+ _validate_dtb_headers([e['dtb'] for e in fdt_entries])
+ 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, verify):
+ """Factor one compatible group of FIT `flat_dt` entries.
+
+ Updates each `entry['node']` in place with its compressed `.dtbo` overlay
+ and returns the newly created `base_dtb_node` along with group size
+ statistics.
+
+ Args:
+ entries: List of FIT `flat_dt` entry dicts in the group.
+ base_dtb_node_name: Name for the group's base DTB node (e.g.
+ `'fdt-base-1'`).
+ strip_local_fixups: Whether to strip `/__local_fixups__`.
+ verify: Whether to verify each reconstructed DTB.
+
+ Returns:
+ Tuple of `(base_dtb_node, group_stat)`.
+ """
+ group_dtbs = [e['dtb'] for e in entries]
+ comp = entries[0]['compression']
+
+ base_dtb_blob, dtbo_blobs = create_base_dtb_and_overlays(
+ group_dtbs,
+ strip_local_fixups=strip_local_fixups,
+ )
+
+ if verify:
+ for d, dtbo_blob in zip(group_dtbs, dtbo_blobs):
+ verify_recreated_dtb(
+ d, base_dtb_blob, dtbo_blob, strip_local_fixups
+ )
+
+ base_dtb_comp_bytes = compress_blob(base_dtb_blob, comp)
+ base_dtb_node = entries[0]['node'].copy()
+ for prop in ('phandle', 'linux,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)
+ sub_node.props.pop('linux,phandle', None)
+ if 'description' in base_dtb_node.props:
+ base_dtb_desc = _derive_base_dtb_description(
+ entries, base_dtb_node_name, comp
+ )
+ base_dtb_node.props['description'] = (
+ base_dtb_desc.encode('utf-8') + b'\x00'
+ )
+ base_dtb_node.props['data'] = base_dtb_comp_bytes
+ if 'compression' in base_dtb_node.props or comp != 'none':
+ base_dtb_node.props['compression'] = comp.encode('utf-8') + b'\x00'
+ base_dtb_node.update_fit_hashes()
+
+ dtbo_comp_sizes = []
+ for entry, dtbo_blob in zip(entries, dtbo_blobs):
+ img_node = entry['node']
+ dtbo_comp_bytes = compress_blob(dtbo_blob, comp)
+ dtbo_comp_sizes.append(len(dtbo_comp_bytes))
+
+ if 'description' in img_node.props:
+ old_desc = _prop_as_str(img_node.props['description'])
+ if _is_dtb_filename_desc(old_desc):
+ desc_dir, desc_stem, _ = split_dtb_filename(old_desc)
+ new_desc_name = f'{desc_stem}.dtbo{COMP_TO_EXT[comp]}'
+ new_desc = (
+ os.path.join(desc_dir, new_desc_name)
+ if desc_dir
+ else new_desc_name
+ )
+ img_node.props['description'] = (
+ new_desc.encode('utf-8') + b'\x00'
+ )
+
+ for prop in ('load', 'entry'):
+ img_node.props.pop(prop, None)
+ for sub_name in list(img_node.children):
+ if sub_name.startswith('signature'):
+ print(
+ f'Warning: Stripping invalidated signature subnode '
+ f'/images/{entry["name"]}/{sub_name}',
+ file=sys.stderr,
+ )
+ del img_node.children[sub_name]
+
+ img_node.props['data'] = dtbo_comp_bytes
+ img_node.update_fit_hashes()
+
+ group_stat = {
+ 'base_dtb_name': base_dtb_node_name,
+ 'comp': comp,
+ 'dtbs': group_dtbs,
+ 'base_dtb_blob': base_dtb_blob,
+ 'dtbo_blobs': dtbo_blobs,
+ 'base_dtb_comp_size': len(base_dtb_comp_bytes),
+ 'dtbo_comp_sizes': dtbo_comp_sizes,
+ }
+ return base_dtb_node, group_stat
+
+
+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: The `NodeTree` for `/images`.
+ configs_node: The `NodeTree` for `/configurations`.
+ insert_before_first: Dict mapping the first `flat_dt` node name of each
+ group to `(base_dtb_node_name, base_dtb_node)`.
+ fdt_to_base_dtb: Dict mapping each factored `flat_dt` node name to
+ `(base_dtb_node_name, orig_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():
+ fdt_list = _prop_as_str_list(conf_node.props.get('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]
+ new_fdt_list = [base_dtb_name] + fdt_list
+ conf_node.props['fdt'] = (
+ '\x00'.join(new_fdt_list).encode('utf-8') + b'\x00'
+ )
+ # 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.nodes.get('/', {}).get('compatible')
+ if root_compat is not None:
+ conf_node.props['compatible'] = root_compat
+ for sub_name in list(conf_node.children):
+ if sub_name.startswith(('signature', 'hash')):
+ print(
+ f'Warning: Stripping invalidated {sub_name} subnode '
+ f'/configurations/{conf_name}/{sub_name}',
+ file=sys.stderr,
+ )
+ del conf_node.children[sub_name]
+
+
+def _print_fit_stats(group_stats, orig_fit_size, new_fit_size):
+ """Print per-group and total FIT image size savings.
+
+ Args:
+ group_stats: List of group statistics dicts from `_factor_fit_group()`.
+ orig_fit_size: Original FIT image size in bytes.
+ new_fit_size: Factored FIT image size in bytes.
+ """
+ for st in group_stats:
+ g_dtbs = st['dtbs']
+ orig_uncomp = sum(len(d.raw_bytes) for d in g_dtbs)
+ base_dtb_uncomp = len(st['base_dtb_blob'])
+ dtbo_uncomp = sum(len(b) for b in st['dtbo_blobs'])
+ new_uncomp = base_dtb_uncomp + dtbo_uncomp
+ saved_uncomp = orig_uncomp - new_uncomp
+ pct_uncomp = (
+ (saved_uncomp / orig_uncomp * 100.0) if orig_uncomp else 0.0
+ )
+ print(
+ f'[{st["base_dtb_name"]}] Uncompressed DTBs: {len(g_dtbs)} '
+ f'files ({orig_uncomp:,} B) -> base {base_dtb_uncomp:,} B + '
+ f'{len(st["dtbo_blobs"])} overlays {dtbo_uncomp:,} B = '
+ f'{new_uncomp:,} B (saved {saved_uncomp:,} B / '
+ f'{pct_uncomp:.1f}%)'
+ )
+ if st['comp'] != 'none':
+ orig_comp = sum(d.compressed_size for d in g_dtbs)
+ base_dtb_csize = st['base_dtb_comp_size']
+ dtbo_csize = sum(st['dtbo_comp_sizes'])
+ new_comp = base_dtb_csize + dtbo_csize
+ saved_comp = orig_comp - new_comp
+ pct_comp = (
+ (saved_comp / orig_comp * 100.0) if orig_comp else 0.0
+ )
+ print(
+ f'[{st["base_dtb_name"]}] Compressed ({st["comp"]}): '
+ f'{len(g_dtbs)} files ({orig_comp:,} B) -> base '
+ f'{base_dtb_csize:,} B + {len(st["dtbo_blobs"])} overlays '
+ f'{dtbo_csize:,} B = {new_comp:,} B '
+ f'(saved {saved_comp:,} B / {pct_comp:.1f}%)'
+ )
+ fit_saved = orig_fit_size - new_fit_size
+ fit_pct = (fit_saved / orig_fit_size * 100.0) if orig_fit_size else 0.0
+ print(
+ f'FIT image total: {orig_fit_size:,} bytes -> {new_fit_size:,} bytes '
+ f'(saved {fit_saved:,} B / {fit_pct:.1f}%)'
+ )
+
+
+def factor_fit_image(fit_in_path, fit_out_path, strip_local_fixups=False,
+ single_base_dtb=False, verify=False, verbose=False):
+ """Factor all `flat_dt` images in a compiled FIT image into base + overlays.
+
+ Args:
+ fit_in_path: Input `.fit` / `.itb` file path.
+ fit_out_path: Output `.fit` / `.itb` file path.
+ strip_local_fixups: Whether to strip `/__local_fixups__` from factored
+ DTBs.
+ single_base_dtb: If True, force all `flat_dt` nodes in the FIT image to
+ be factored into a single `fdt-base-1` base DTB, failing only if
+ their FDT headers, architecture, or compression differ.
+ verify: Whether to verify each board's reconstructed DTB using
+ `fdt_overlay_apply()`.
+ verbose: Whether to print size savings statistics.
+
+ Raises:
+ ValueError: If the FIT image lacks `/images` or `/configurations`,
+ a `flat_dt` node lacks a `data` property, or `single_base_dtb` is
+ requested and the `flat_dt` nodes have incompatible headers,
+ architecture, or compression.
+ """
+ with open(fit_in_path, 'rb') as f:
+ orig_fit_bytes = f.read()
+
+ orig_fit_fdt = _open_fdt_ro(orig_fit_bytes, fit_in_path)
+ fit_root = NodeTree.from_fdt(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)
+ 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, group_stat = _factor_fit_group(
+ entries, base_dtb_node_name, strip_local_fixups, verify
+ )
+ 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(group_stat)
+
+ _update_fit_images_and_configs(
+ images_node, configs_node, insert_before_first, fdt_to_base_dtb
+ )
+
+ fit_mem_rsv = [
+ tuple(orig_fit_fdt.get_mem_rsv(i))
+ for i in range(orig_fit_fdt.num_mem_rsv())
+ ]
+ new_fit_bytes = fit_root.to_fdt_blob(
+ mem_rsv=fit_mem_rsv,
+ boot_cpuid_phys=orig_fit_fdt.boot_cpuid_phys(),
+ size_hint=max(len(orig_fit_bytes) + 65536, 65536),
+ )
+
+ 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 verbose:
+ _print_fit_stats(group_stats, len(orig_fit_bytes), len(new_fit_bytes))
+
+
def get_dtbo_output_path(dtb_path, outdir, comp='none'):
"""Determine the output `.dtbo[.comp]` file path for an input `.dtb` path.
@@ -1238,13 +1870,13 @@ def parse_args():
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) or a compiled FIT image into common '
+ 'base .dtb(s) and per-board overlay blobs (.dtbo).'
)
)
parser.add_argument(
'-b', '--base-dtb',
- required=True,
+ default=None,
metavar='BASE_DTB',
help=(
'Output common base .dtb (or .dtb.lz4/.xz/.lzma/.gz) file path '
@@ -1260,6 +1892,23 @@ def parse_args():
'filenames (default: alongside each input .dtb)'
),
)
+ parser.add_argument(
+ '-f', '--fit',
+ default=None,
+ metavar='FIT_IMAGE',
+ help=(
+ 'Input compiled FIT image (.fit / .itb) to factor (updated '
+ 'in-place unless -F/--fit-out is specified)'
+ ),
+ )
+ parser.add_argument(
+ '-F', '--fit-out',
+ 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',
@@ -1268,6 +1917,15 @@ def parse_args():
'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(
'--verify',
action='store_true',
@@ -1283,16 +1941,29 @@ def parse_args():
)
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()
- if len(args.dtbs) < 2:
- parser.error('at least two input .dtb files are required')
+ if args.fit_out and not args.fit:
+ parser.error('-F/--fit-out 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:
+ parser.error(
+ '-f/--fit cannot be combined with -b/--base-dtb, '
+ '-o/--outdir, or positional DTB arguments'
+ )
+ else:
+ if not args.base_dtb:
+ parser.error('-b/--base-dtb is required when -f/--fit is not used')
+ if len(args.dtbs) < 2:
+ parser.error('at least two input .dtb files are required')
return args
@@ -1417,7 +2088,18 @@ def main():
"""
args = parse_args()
try:
- factor_dtb_files(args)
+ if args.fit:
+ fit_out = args.fit_out if args.fit_out else args.fit
+ factor_fit_image(
+ args.fit,
+ fit_out,
+ strip_local_fixups=args.strip_local_fixups,
+ single_base_dtb=args.single_base_dtb,
+ verify=args.verify,
+ verbose=args.verbose,
+ )
+ 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 d0bdc4afefb2..0ca2d70457f9 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.its *.test.fit *.test.dt.yaml *.dtsv1 \
*.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_other1.dts b/tests/fdtfactor_other1.dts
new file mode 100644
index 000000000000..26fca3816d5a
--- /dev/null
+++ b/tests/fdtfactor_other1.dts
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/dts-v1/;
+
+/ {
+ model = "Other Board 1";
+ compatible = "vendor,other1", "vendor,other";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ intc: interrupt-controller@8000 {
+ compatible = "vendor,other-intc";
+ reg = <0x8000 0x100>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ clk: clock-controller@9000 {
+ compatible = "vendor,other-clk";
+ reg = <0x9000 0x100>;
+ #clock-cells = <1>;
+ };
+
+ soc@10000000 {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+ interrupt-parent = <&intc>;
+
+ uart0: serial@a000 {
+ compatible = "vendor,other-uart";
+ reg = <0xa000 0x100>;
+ interrupts = <1 0>;
+ clocks = <&clk 1>;
+ status = "okay";
+ };
+
+ i2c0: i2c@b000 {
+ compatible = "vendor,other-i2c";
+ reg = <0xb000 0x100>;
+ interrupts = <2 0>;
+ clocks = <&clk 2>;
+ status = "okay";
+
+ pmic: pmic@50 {
+ compatible = "vendor,other-pmic";
+ reg = <0x50>;
+
+ reg_buck1: buck1 {
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+ };
+ };
+
+ spi0: spi@c000 {
+ compatible = "vendor,other-spi";
+ reg = <0xc000 0x100>;
+ clocks = <&clk 3>;
+ vdd-supply = <®_buck1>;
+ cs-supply = <&other1_reg>;
+ };
+ };
+
+ other1_reg: regulator-other1 {
+ compatible = "regulator-fixed";
+ regulator-name = "other1-cs";
+ };
+};
diff --git a/tests/fdtfactor_other2.dts b/tests/fdtfactor_other2.dts
new file mode 100644
index 000000000000..50cbc73759d2
--- /dev/null
+++ b/tests/fdtfactor_other2.dts
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/dts-v1/;
+
+/ {
+ model = "Other Board 2";
+ compatible = "vendor,other2", "vendor,other";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ other2_early_reg: regulator-other-early {
+ compatible = "regulator-fixed";
+ regulator-name = "other2-early";
+ };
+
+ intc: interrupt-controller@8000 {
+ compatible = "vendor,other-intc";
+ reg = <0x8000 0x100>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ clk: clock-controller@9000 {
+ compatible = "vendor,other-clk";
+ reg = <0x9000 0x100>;
+ #clock-cells = <1>;
+ };
+
+ soc@10000000 {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+ interrupt-parent = <&intc>;
+
+ uart0: serial@a000 {
+ compatible = "vendor,other-uart";
+ reg = <0xa000 0x100>;
+ interrupts = <1 0>;
+ clocks = <&clk 1>;
+ status = "disabled";
+ };
+
+ i2c0: i2c@b000 {
+ compatible = "vendor,other-i2c";
+ reg = <0xb000 0x100>;
+ interrupts = <2 0>;
+ clocks = <&clk 2>;
+ status = "okay";
+
+ pmic: pmic@50 {
+ compatible = "vendor,other-pmic";
+ reg = <0x50>;
+
+ reg_buck1: buck1 {
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <3300000>;
+ };
+ };
+ };
+
+ mmc0: mmc@d000 {
+ compatible = "vendor,other-mmc";
+ reg = <0xd000 0x100>;
+ clocks = <&clk 4>;
+ vmmc-supply = <®_buck1>;
+ vqmmc-supply = <&other2_early_reg>;
+ };
+ };
+};
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 92edec8b724c..70a2f5ef6d97 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -1177,6 +1177,69 @@ fdtoverlay_tests() {
run_wrap_test check_dtb_recreated \
fdtfactor_board1.test.dtb "fdtfactor_board1.${ext}_recreated.test.dtb"
done
+
+ # Compiled FIT image mode (2 families with <50% node-path overlap: 3 boards + 2 boards)
+ run_dtc_test -@ -L -I dts -O dtb -o fdtfactor_other1.test.dtb "$SRCDIR/fdtfactor_other1.dts"
+ run_dtc_test -@ -L -I dts -O dtb -o fdtfactor_other2.test.dtb "$SRCDIR/fdtfactor_other2.dts"
+
+ run_dtc_test -i . -I dts -O dtb -o fdtfactor_in.test.fit "$SRCDIR/fdtfactor_test.its"
+ run_wrap_test $PYTHON "$FDTFACTOR" -v --verify \
+ -f fdtfactor_in.test.fit -F fdtfactor.test.fit
+ run_fdtget_test "fdtfactor_board-base.dtb" fdtfactor.test.fit /images/fdt-base-1 description -ts
+ run_fdtget_test "fdtfactor_other-base.dtb" fdtfactor.test.fit /images/fdt-base-2 description -ts
+ run_fdtget_test "fdtfactor_board1.test.dtbo" fdtfactor.test.fit /images/fdt-1 description -ts
+ run_fdtget_test "fdtfactor_board2.test.dtbo" fdtfactor.test.fit /images/fdt-2 description -ts
+ run_fdtget_test "fdtfactor_board3.test.dtbo" fdtfactor.test.fit /images/fdt-3 description -ts
+ run_fdtget_test "fdtfactor_other1.test.dtbo" fdtfactor.test.fit /images/fdt-4 description -ts
+ run_fdtget_test "fdtfactor_other2.test.dtbo" fdtfactor.test.fit /images/fdt-5 description -ts
+ run_wrap_test test -n "$($DTGET fdtfactor.test.fit /images/fdt-base-1/hash-1 value -tx)"
+ run_wrap_test test -n "$($DTGET fdtfactor.test.fit /images/fdt-1/hash-1 value -tx)"
+ run_fdtget_test "fdt-base-1 fdt-1" fdtfactor.test.fit /configurations/conf-1 fdt -ts
+ run_fdtget_test "fdt-base-1 fdt-2" fdtfactor.test.fit /configurations/conf-2 fdt -ts
+ run_fdtget_test "fdt-base-1 fdt-3 fdt-ovl-1" fdtfactor.test.fit /configurations/conf-3 fdt -ts
+ run_fdtget_test "fdt-base-2 fdt-4" fdtfactor.test.fit /configurations/conf-4 fdt -ts
+ run_fdtget_test "fdt-base-2 fdt-5" fdtfactor.test.fit /configurations/conf-5 fdt -ts
+ run_fdtget_test "overlay_overlay_phandle.test.dtb" fdtfactor.test.fit /images/fdt-ovl-1 description -ts
+ run_fdtget_test "board-meta-1" fdtfactor.test.fit /configurations/conf-1 description -ts
+ run_fdtget_test "vendor,board1 vendor,common" fdtfactor.test.fit /configurations/conf-1 compatible -ts
+ run_fdtget_test "vendor,board2 vendor,common" fdtfactor.test.fit /configurations/conf-2 compatible -ts
+ run_fdtget_test "vendor,other2 vendor,other" fdtfactor.test.fit /configurations/conf-5 compatible -ts
+
+ # --single-base-dtb combines all boards (even with <50% overlap) into 1 base DTB
+ run_wrap_test $PYTHON "$FDTFACTOR" --verify -S --single-base-dtb \
+ -f fdtfactor_in.test.fit -F fdtfactor_single.test.fit
+ run_fdtget_test "kernel-1\nfdt-base-1\nfdt-1\nfdt-2\nfdt-3\nfdt-4\nfdt-5\nfdt-ovl-1" \
+ fdtfactor_single.test.fit -l /images
+ run_fdtget_test "fdtfactor-base.dtb" fdtfactor_single.test.fit /images/fdt-base-1 description -ts
+ run_fdtget_test "fdt-base-1 fdt-5" fdtfactor_single.test.fit /configurations/conf-5 fdt -ts
+
+ # --single-base-dtb fails when flat_dt nodes have mismatched arch/headers
+ cp fdtfactor_in.test.fit fdtfactor_mismatch.test.fit
+ run_wrap_test $DTPUT -ts fdtfactor_mismatch.test.fit /images/fdt-5 arch arm
+ run_wrap_error_test $PYTHON "$FDTFACTOR" --verify --single-base-dtb \
+ -f fdtfactor_mismatch.test.fit -F fdtfactor_single_fail.test.fit
+
+ # Compressed (.dtb.lz4) FIT image mode (fdtfactor_board{1,2,3}.test.dtb.lz4
+ # were already generated by the compressed test loop above)
+ if command -v lz4 >/dev/null 2>&1; then
+ run_wrap_test lz4 -12 -q -f -m \
+ fdtfactor_other1.test.dtb fdtfactor_other2.test.dtb overlay_overlay_phandle.test.dtb
+ sed -e 's/\.test\.dtb/.test.dtb.lz4/g' \
+ -e 's/compression = "none"/compression = "lz4"/g' \
+ -e 's/description = "fdtfactor_other1/description = "soc99-alpha/' \
+ -e 's/description = "fdtfactor_other2/description = "soc99-beta/' \
+ "$SRCDIR/fdtfactor_test.its" > fdtfactor_lz4.test.its
+ run_dtc_test -I dts -O dtb -o fdtfactor_lz4.test.fit fdtfactor_lz4.test.its
+ run_wrap_test $PYTHON "$FDTFACTOR" --verify \
+ -f fdtfactor_lz4.test.fit
+ run_fdtget_test "lz4" fdtfactor_lz4.test.fit /images/fdt-base-1 compression -ts
+ run_fdtget_test "lz4" fdtfactor_lz4.test.fit /images/fdt-base-2 compression -ts
+ run_fdtget_test "fdtfactor_board-base.dtb.lz4" fdtfactor_lz4.test.fit /images/fdt-base-1 description -ts
+ run_fdtget_test "soc99-base.dtb.lz4" fdtfactor_lz4.test.fit /images/fdt-base-2 description -ts
+ run_fdtget_test "fdtfactor_board1.test.dtbo.lz4" fdtfactor_lz4.test.fit /images/fdt-1 description -ts
+ run_fdtget_test "fdt-base-1 fdt-3 fdt-ovl-1" fdtfactor_lz4.test.fit /configurations/conf-3 fdt -ts
+ run_fdtget_test "fdt-base-2 fdt-5" fdtfactor_lz4.test.fit /configurations/conf-5 fdt -ts
+ fi
fi
}
@@ -1228,9 +1291,9 @@ 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 \
- *.test.dtb.xz *.test.dtbo.xz *.test.dtb.lzma *.test.dtbo.lzma \
- *.test.dtb.lz4 *.test.dtbo.lz4
+rm -f *.test.dtb *.test.dtbo *.test.dts *.test.its *.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
for set in $TESTSETS; do
case $set in
--
2.56.0.176.g3575e65682-goog
next prev parent reply other threads:[~2026-10-15 18:23 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-15 18:21 [PATCH 0/4] fdtfactor: Introduce fdtfactor to auto-factor overlays Douglas Anderson
2026-10-15 18:21 ` [PATCH 1/4] tests: Fix Python test environment setup in meson.build Douglas Anderson
2026-10-23 4:51 ` David Gibson
2026-10-15 18:21 ` [PATCH 2/4] fdtfactor: Add tool to split DTBs into a base DTB and overlays Douglas Anderson
2026-10-15 18:21 ` [PATCH 3/4] fdtfactor: Support compressed DTB inputs and outputs Douglas Anderson
2026-10-15 18:21 ` Douglas Anderson [this message]
2026-10-23 4:49 ` [PATCH 0/4] fdtfactor: Introduce fdtfactor to auto-factor overlays David Gibson
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=20261015182221.2931742-5-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=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=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®