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 3/4] fdtfactor: Support compressed DTB inputs and outputs
Date: Thu, 15 Oct 2026 11:21:36 -0700 [thread overview]
Message-ID: <20261015182221.2931742-4-dianders@chromium.org> (raw)
In-Reply-To: <20261015182221.2931742-1-dianders@chromium.org>
Firmware and FIT image build pipelines frequently store device tree
blobs compressed with LZ4, LZMA/XZ, or gzip (e.g., `.dtb.lz4`).
Extend `fdtfactor` to detect compressed input blobs by magic bytes or
file extension, transparently decompress them before factoring, and
compress the resulting `.dtbo` and `base.dtb` outputs when appropriate:
- Support `.lz4` (via the `lz4` CLI), `.lzma`/`.xz` (via Python's
`lzma` module), and `.gz` (via Python's `gzip` module).
- Preserve the input compression format on generated `.dtbo` files and
compress `base.dtb` when its output path carries a compression suffix
(e.g., `-b base.dtb.lz4`).
- Report compressed size savings alongside uncompressed savings when
`-v`/`--verbose` is specified.
On the 47 Qualcomm SC7180 Trogdor Chromebook `.dtb.lz4` files,
`fdtfactor -S` reduces total LZ4-compressed size from 1.71 MB to 213 KB
(87.5% savings).
Assisted-by: Gemini:gemini-pro-prerelease
Signed-off-by: Douglas Anderson <dianders@chromium.org>
---
README.md | 16 +--
fdtfactor | 267 ++++++++++++++++++++++++++++++++++++++-----
tests/Makefile.tests | 5 +-
tests/run_tests.sh | 34 +++++-
4 files changed, 284 insertions(+), 38 deletions(-)
diff --git a/README.md b/README.md
index e2ff2374b01d..6ace31bfbfdb 100644
--- a/README.md
+++ b/README.md
@@ -94,10 +94,10 @@ values.
## fdtfactor
`fdtfactor` is a Python tool built on `pylibfdt` that takes a set of
-compiled device tree blobs (`.dtb`) and splits them into a single common
-`base.dtb` and per-board `.dtbo` overlays so that applying a board's
-`.dtbo` to `base.dtb` recreates the original board device tree while
-reducing total storage size.
+compiled device tree blobs (`.dtb`, or compressed `.dtb.lz4`, `.dtb.xz`,
+`.dtb.lzma`, `.dtb.gz`) and splits them into a single common `base.dtb` and
+per-board `.dtbo` overlays so that applying a board's `.dtbo` to `base.dtb`
+recreates the original board device tree while reducing total storage size.
Input `.dtb` files must be compiled with `dtc -@ -L` so that both
`/__symbols__` (used to resolve overlay `/__fixups__` against `base.dtb`)
@@ -112,8 +112,10 @@ $ fdtfactor -b base.dtb -o out_dir board1.dtb board2.dtb board3.dtb
Options:
-* `-b`, `--base-dtb`: Output path for the common base `.dtb` (placed in
- `--outdir` if specified without a directory component).
+* `-b`, `--base-dtb`: Output path for the common base `.dtb` (compressed if
+ given a `.lz4`, `.xz`, `.lzma`, or `.gz` suffix, whereas each `.dtbo`
+ preserves its input `.dtb`'s compression; placed in `--outdir` if specified
+ without a directory component).
* `-o`, `--outdir`: Output directory for generated `.dtbo` files (and for
`--base-dtb` when given without a directory component).
* `-S`, `--strip-local-fixups`: Omit `/__local_fixups__` from the generated
@@ -127,7 +129,7 @@ Options:
original `.dtb`'s `/__local_fixups__` subtree verbatim.
* `--verify`: Verify that applying each generated `.dtbo` to `base.dtb`
with `libfdt` recreates the original `.dtb`.
-* `-v`, `--verbose`: Print size savings summary.
+* `-v`, `--verbose`: Print uncompressed and compressed size savings.
### TODO
diff --git a/fdtfactor b/fdtfactor
index 377f499bea50..9f06801f973c 100755
--- a/fdtfactor
+++ b/fdtfactor
@@ -11,31 +11,195 @@ original device tree.
import argparse
from collections import Counter
+import gzip
+import lzma
import os
import struct
+import subprocess
import sys
import libfdt
+FDT_MAGIC = b'\xd0\x0d\xfe\xed'
+LZ4_MAGIC = b'\x04\x22\x4d\x18'
+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.
+LZMA_ALONE_MAGIC = b'\x5d\x00\x00'
+
+COMP_TO_EXT = {
+ 'none': '',
+ 'lz4': '.lz4',
+ 'lzma': '.lzma',
+ 'xz': '.xz',
+ 'gzip': '.gz',
+}
+
+EXT_TO_COMP = {
+ '.lz4': 'lz4',
+ '.lzma': 'lzma',
+ '.xz': 'xz',
+ '.gz': 'gzip',
+}
+
+
+def detect_compression(raw_bytes, filename=''):
+ """Detect compression algorithm from magic bytes or filename extension.
+
+ Args:
+ raw_bytes: Input file or blob contents as `bytes`.
+ filename: Optional file path or name for extension fallback.
+
+ Returns:
+ Compression algorithm name (`'none'`, `'lz4'`, `'lzma'`, `'xz'`, or
+ `'gzip'`).
+ """
+ if raw_bytes.startswith(FDT_MAGIC):
+ return 'none'
+ if (raw_bytes.startswith(LZ4_MAGIC) or
+ raw_bytes.startswith(LZ4_LEGACY_MAGIC)):
+ return 'lz4'
+ if raw_bytes.startswith(GZIP_MAGIC):
+ return 'gzip'
+ if raw_bytes.startswith(XZ_MAGIC):
+ return 'xz'
+ if raw_bytes.startswith(LZMA_ALONE_MAGIC):
+ return 'lzma'
+ _, ext = os.path.splitext(filename)
+ if ext in EXT_TO_COMP:
+ return EXT_TO_COMP[ext]
+ return 'none'
+
+
+def decompress_blob(raw_bytes, filename='', comp=None):
+ """Decompress a DTB blob if compressed.
+
+ Args:
+ raw_bytes: Input file or blob contents as `bytes`.
+ filename: Optional file path or name for error reporting and extension
+ detection.
+ comp: Optional explicit compression algorithm name; auto-detected if
+ None or empty.
+
+ Returns:
+ Tuple of `(uncompressed_bytes, compression_type)`.
+
+ Raises:
+ RuntimeError: If `lz4` is missing or fails to decompress `raw_bytes`.
+ ValueError: If the compression type is unsupported.
+ """
+ if not comp:
+ comp = detect_compression(raw_bytes, filename)
+ if comp == 'none':
+ return raw_bytes, 'none'
+ if comp == 'lz4':
+ try:
+ res = subprocess.run(
+ ['lz4', '-d', '-q', '-c'],
+ input=raw_bytes,
+ capture_output=True,
+ check=False,
+ )
+ except FileNotFoundError as exc:
+ raise RuntimeError(
+ 'lz4 command not found; install lz4 to process .lz4 files'
+ ) from exc
+ if res.returncode != 0:
+ raise RuntimeError(
+ f'Failed to decompress {filename} with lz4: '
+ f'{res.stderr.decode("utf-8", errors="replace").strip()}'
+ )
+ return res.stdout, 'lz4'
+ if comp == 'gzip':
+ try:
+ return gzip.decompress(raw_bytes), 'gzip'
+ except Exception as exc:
+ raise RuntimeError(
+ f'Failed to decompress {filename} with gzip: {exc}'
+ ) from exc
+ if comp in ('lzma', 'xz'):
+ try:
+ return lzma.decompress(raw_bytes), comp
+ except Exception as exc:
+ raise RuntimeError(
+ f'Failed to decompress {filename} with {comp}: {exc}'
+ ) from exc
+ prefix = f'{filename}: ' if filename else ''
+ raise ValueError(f'{prefix}Unsupported compression type: {comp}')
+
+
+def compress_blob(raw_bytes, comp):
+ """Compress an FDT blob using the specified compression algorithm.
+
+ Args:
+ raw_bytes: Uncompressed FDT blob as `bytes`.
+ comp: Compression algorithm name (`'none'`, `'lz4'`, `'lzma'`, `'xz'`,
+ or `'gzip'`).
+
+ Returns:
+ Compressed (or uncompressed if `comp` is `'none'`) blob as `bytes`.
+
+ Raises:
+ RuntimeError: If `lz4` is missing or fails to compress `raw_bytes`.
+ ValueError: If `comp` is not a supported compression type.
+ """
+ if comp in ('none', '', None):
+ return raw_bytes
+ if comp == 'lz4':
+ try:
+ res = subprocess.run(
+ ['lz4', '-12', '-z', '-q', '-c'],
+ input=raw_bytes,
+ capture_output=True,
+ check=False,
+ )
+ except FileNotFoundError as exc:
+ raise RuntimeError(
+ 'lz4 command not found; install lz4 to process .lz4 files'
+ ) from exc
+ if res.returncode != 0:
+ raise RuntimeError(
+ f'Failed to compress blob with lz4: '
+ f'{res.stderr.decode("utf-8", errors="replace").strip()}'
+ )
+ return res.stdout
+ if comp == 'gzip':
+ return gzip.compress(raw_bytes, mtime=0)
+ if comp == 'lzma':
+ return lzma.compress(raw_bytes, format=lzma.FORMAT_ALONE)
+ if comp == 'xz':
+ return lzma.compress(raw_bytes, format=lzma.FORMAT_XZ)
+ raise ValueError(f'Unsupported compression type: {comp}')
+
+
def split_dtb_filename(path):
- """Split a DTB path into its directory and filename stem.
+ """Split a DTB path into its directory, stem, and compression type.
- Example:
- '/dir/board.dtb' -> ('/dir', 'board')
+ Examples:
+ '/dir/board.dtb' -> ('/dir', 'board', 'none')
+ '/dir/board.dtb.lz4' -> ('/dir', 'board', 'lz4')
Args:
- path: File path to a .dtb or .dtbo file.
+ path: File path to a `.dtb` or `.dtbo` (optionally compressed) file.
Returns:
- Tuple of (directory, stem) where `stem` has any trailing `.dtb` or
- `.dtbo` suffix removed.
+ Tuple of `(directory, stem, compression_type)` where `stem` has any
+ trailing compression and `.dtb`/`.dtbo` suffixes removed.
"""
directory, filename = os.path.split(path)
+ comp = 'none'
+ prefix, sep, ext = filename.rpartition('.')
+ if prefix and sep and f'.{ext}' in EXT_TO_COMP:
+ comp = EXT_TO_COMP[f'.{ext}']
+ filename = prefix
stem, sep, ext = filename.rpartition('.')
if not stem or not sep or ext not in ('dtb', 'dtbo'):
stem = filename
- return directory, stem
+ return directory, stem, comp
class NodeTree:
@@ -172,15 +336,24 @@ def _is_dtbo_overlay(fdt_ro):
class ParsedDtb:
"""Parsed representation of an input .dtb blob."""
- def __init__(self, filename, raw_bytes):
+ def __init__(self, filename, raw_bytes, compressed_size=None,
+ compression='none'):
"""Parse an uncompressed FDT blob into nodes, symbols, and fixups.
Args:
filename: Display name or file path for error reporting.
raw_bytes: Uncompressed FDT binary contents as `bytes`.
+ compressed_size: Original on-disk size in bytes before
+ decompression (defaults to `len(raw_bytes)`).
+ compression: Compression algorithm of the input file (`'none'`,
+ `'lz4'`, `'lzma'`, `'xz'`, or `'gzip'`).
"""
self.filename = filename
self.raw_bytes = raw_bytes
+ self.compressed_size = (
+ 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(
@@ -211,17 +384,19 @@ class ParsedDtb:
@classmethod
def from_file(cls, path):
- """Read and parse a .dtb file from disk.
+ """Read and parse a .dtb (or compressed .dtb.lz4/.lzma/.xz/.gz) file.
Args:
- path: Path to the input .dtb file.
+ path: Path to the input .dtb (or compressed .dtb) file.
Returns:
A new `ParsedDtb` instance.
"""
with open(path, 'rb') as f:
- raw_bytes = f.read()
- return cls(path, raw_bytes)
+ file_bytes = f.read()
+ raw_bytes, comp = decompress_blob(file_bytes, path)
+ return cls(path, raw_bytes, compressed_size=len(file_bytes),
+ compression=comp)
def _parse_tree(self, fdt):
"""Walk the FDT and extract nodes, properties, symbols, and fixups.
@@ -1036,18 +1211,20 @@ def verify_recreated_dtb(orig_dtb, base_dtb_blob, dtbo_blob,
)
-def get_dtbo_output_path(dtb_path, outdir):
- """Determine the output `.dtbo` file path for an input `.dtb` path.
+def get_dtbo_output_path(dtb_path, outdir, comp='none'):
+ """Determine the output `.dtbo[.comp]` file path for an input `.dtb` path.
Args:
- dtb_path: Path to the input `.dtb` file.
+ dtb_path: Path to the input `.dtb` (or compressed `.dtb`) file.
outdir: Output directory if `--outdir` was specified, else None.
+ comp: Compression algorithm name (`'none'`, `'lz4'`, `'lzma'`, `'xz'`,
+ or `'gzip'`).
Returns:
- Output `.dtbo` file path string.
+ Output `.dtbo[.comp]` file path string.
"""
- dtb_dir, stem = split_dtb_filename(dtb_path)
- dtbo_name = f'{stem}.dtbo'
+ dtb_dir, stem, _ = split_dtb_filename(dtb_path)
+ dtbo_name = f'{stem}.dtbo{COMP_TO_EXT[comp]}'
target_dir = outdir if outdir is not None else dtb_dir
return os.path.join(target_dir, dtbo_name)
@@ -1060,8 +1237,9 @@ def parse_args():
"""
parser = argparse.ArgumentParser(
description=(
- 'Split a set of compiled device tree blobs (.dtb) into a single '
- 'common base .dtb and per-board overlay blobs (.dtbo).'
+ '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).'
)
)
parser.add_argument(
@@ -1069,8 +1247,8 @@ def parse_args():
required=True,
metavar='BASE_DTB',
help=(
- 'Output common base .dtb file path (placed in --outdir if '
- 'specified without a directory component)'
+ 'Output common base .dtb (or .dtb.lz4/.xz/.lzma/.gz) file path '
+ '(placed in --outdir if specified without a directory component)'
),
)
parser.add_argument(
@@ -1101,13 +1279,16 @@ def parse_args():
parser.add_argument(
'-v', '--verbose',
action='store_true',
- help='Print size statistics summary',
+ help='Print size statistics and compression summary',
)
parser.add_argument(
'dtbs',
nargs='+',
metavar='DTB',
- help='Input .dtb files (at least two required)',
+ help=(
+ 'Input .dtb (or compressed .dtb.lz4/.xz/.lzma/.gz) files (at '
+ 'least two required)'
+ ),
)
args = parser.parse_args()
if len(args.dtbs) < 2:
@@ -1130,6 +1311,7 @@ def factor_dtb_files(args):
if args.outdir is not None and not os.path.dirname(args.base_dtb)
else args.base_dtb
)
+ _, _, base_dtb_comp = split_dtb_filename(base_dtb_path)
input_abs_paths = {os.path.abspath(p) for p in input_dtb_paths}
base_abs_path = os.path.abspath(base_dtb_path)
@@ -1141,8 +1323,8 @@ def factor_dtb_files(args):
dtbo_paths = []
seen_paths = {base_abs_path}
- for dtb_path in input_dtb_paths:
- out_path = get_dtbo_output_path(dtb_path, args.outdir)
+ for dtb_path, d in zip(input_dtb_paths, dtbs):
+ out_path = get_dtbo_output_path(dtb_path, args.outdir, comp=d.compression)
abs_out = os.path.abspath(out_path)
if abs_out in input_abs_paths:
raise ValueError(
@@ -1167,20 +1349,24 @@ def factor_dtb_files(args):
d, base_dtb_blob, dtbo_blob, args.strip_local_fixups
)
+ base_dtb_file_bytes = compress_blob(base_dtb_blob, base_dtb_comp)
base_dtb_dir = os.path.dirname(base_dtb_path)
if base_dtb_dir:
os.makedirs(base_dtb_dir, exist_ok=True)
with open(base_dtb_path, 'wb') as f:
- f.write(base_dtb_blob)
+ f.write(base_dtb_file_bytes)
if args.outdir:
os.makedirs(args.outdir, exist_ok=True)
- for out_path, dtbo_blob in zip(dtbo_paths, dtbo_blobs):
+ dtbo_file_sizes = []
+ for out_path, dtbo_blob, d in zip(dtbo_paths, dtbo_blobs, dtbs):
out_dir = os.path.dirname(out_path)
if out_dir:
os.makedirs(out_dir, exist_ok=True)
+ out_bytes = compress_blob(dtbo_blob, d.compression)
+ dtbo_file_sizes.append(len(out_bytes))
with open(out_path, 'wb') as f:
- f.write(dtbo_blob)
+ f.write(out_bytes)
if args.verbose:
orig_uncomp = sum(len(d.raw_bytes) for d in dtbs)
@@ -1198,6 +1384,29 @@ def factor_dtb_files(args):
f'{dtbo_uncomp:,} B = {new_uncomp:,} bytes '
f'(saved {saved_uncomp:,} B / {pct_uncomp:.1f}%)'
)
+ all_comps = {base_dtb_comp, *(d.compression for d in dtbs)}
+ comp_names = sorted(c for c in all_comps if c != 'none')
+ if comp_names:
+ comp_label = '/'.join(sorted(all_comps))
+ hdr = (
+ f'Compressed ({comp_label}):'
+ if len(all_comps) == 1
+ else f'On-disk ({comp_label}): '
+ )
+ orig_comp = sum(d.compressed_size for d in dtbs)
+ base_dtb_csize = len(base_dtb_file_bytes)
+ dtbo_csize = sum(dtbo_file_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'{hdr} {len(dtbs)} files '
+ f'({orig_comp:,} bytes) -> base {base_dtb_csize:,} B + '
+ f'{len(dtbo_blobs)} overlays {dtbo_csize:,} B '
+ f'(avg {dtbo_csize // len(dtbo_blobs):,} B) = '
+ f'{new_comp:,} bytes '
+ f'(saved {saved_comp:,} B / {pct_comp:.1f}%)'
+ )
def main():
diff --git a/tests/Makefile.tests b/tests/Makefile.tests
index 9f8a2896d597..d0bdc4afefb2 100644
--- a/tests/Makefile.tests
+++ b/tests/Makefile.tests
@@ -53,7 +53,10 @@ TESTS_DEPFILES = $(TESTS:%=%.d) \
$(addprefix $(TESTS_PREFIX),testutils.d)
TESTS_CLEANFILES_L = $(STD_CLEANFILES) \
- *.dtb *.test.dtbo *.test.dts *.test.dt.yaml *.dtsv1 tmp.* *.bak \
+ *.dtb *.test.dtbo *.test.dts *.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 \
treegen
TESTS_CLEANFILES = $(TESTS) $(TESTS_CLEANFILES_L:%=$(TESTS_PREFIX)%)
TESTS_CLEANDIRS_L = fs
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index 92fa417d3457..92edec8b724c 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -1147,6 +1147,36 @@ fdtoverlay_tests() {
run_wrap_test check_dtb_recreated fdtfactor_board1.recreated.test.dtb fdtfactor_board1.stripped.test.dtb
run_wrap_test check_dtb_recreated fdtfactor_board2.recreated.test.dtb fdtfactor_board2.stripped.test.dtb
run_wrap_test check_dtb_recreated fdtfactor_board3.recreated.test.dtb fdtfactor_board3.stripped.test.dtb
+
+ # Compressed (.dtb.gz / .dtb.xz / .dtb.lzma / .dtb.lz4) modes (3 boards)
+ for comp_spec in "gz:gzip -9 -k -f:gzip -d -f" \
+ "xz:xz -k -f:xz -d -f" \
+ "lzma:xz --format=lzma -k -f:xz -d -f" \
+ "lz4:lz4 -12 -q -f -m:lz4 -d -q -f --rm"; do
+ ext="${comp_spec%%:*}"
+ rest="${comp_spec#*:}"
+ comp_cmd="${rest%%:*}"
+ decomp_cmd="${rest#*:}"
+ if ! command -v "${comp_cmd%% *}" >/dev/null 2>&1; then
+ continue
+ fi
+ run_wrap_test $comp_cmd \
+ fdtfactor_board1.test.dtb fdtfactor_board2.test.dtb fdtfactor_board3.test.dtb
+ run_wrap_test $PYTHON "$FDTFACTOR" -v --verify \
+ -b "fdtfactor_base.test.dtb.$ext" \
+ "fdtfactor_board1.test.dtb.$ext" \
+ "fdtfactor_board2.test.dtb.$ext" \
+ "fdtfactor_board3.test.dtb.$ext"
+ cp "fdtfactor_base.test.dtb.$ext" "fdtfactor_base.${ext}_dec.test.dtb.$ext"
+ cp "fdtfactor_board1.test.dtbo.$ext" "fdtfactor_board1.${ext}_dec.test.dtbo.$ext"
+ run_wrap_test $decomp_cmd "fdtfactor_base.${ext}_dec.test.dtb.$ext"
+ run_wrap_test $decomp_cmd "fdtfactor_board1.${ext}_dec.test.dtbo.$ext"
+ run_wrap_test $FDTOVERLAY -i "fdtfactor_base.${ext}_dec.test.dtb" \
+ -o "fdtfactor_board1.${ext}_recreated.test.dtb" \
+ "fdtfactor_board1.${ext}_dec.test.dtbo"
+ run_wrap_test check_dtb_recreated \
+ fdtfactor_board1.test.dtb "fdtfactor_board1.${ext}_recreated.test.dtb"
+ done
fi
}
@@ -1198,7 +1228,9 @@ if [ -z "$TESTSETS" ]; then
fi
# Make sure we don't have stale blobs lying around
-rm -f *.test.dtb *.test.dtbo *.test.dts
+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
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 ` Douglas Anderson [this message]
2026-10-15 18:21 ` [PATCH 4/4] fdtfactor: Add compiled FIT image factoring support Douglas Anderson
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-4-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®