From: Aristo Chen <aristo.chen@canonical.com>
To: u-boot@lists.u-boot-project.org
Cc: sjg@chromium.org, nora.schiffer@ew.tq-group.com,
Aristo Chen <aristo.chen@canonical.com>
Subject: [PATCH v2 0/8] bootm: size the noload buffer from the compressor header
Date: Tue, 18 Aug 2026 13:23:14 +0000 [thread overview]
Message-ID: <20260818132332.324173-1-aristo.chen@canonical.com> (raw)
In-Reply-To: <20260809042338.63397-1-aristo.chen@canonical.com>
This is v2 of "bootm: size the noload decompression buffer from the
compressor header". Tom pushed back on v1
(https://patchwork.ozlabs.org/project/uboot/patch/20260809042338.63397-2-aristo.chen@canonical.com/)
on two grounds:
1. No concrete problem report driving the change.
2. ~1297 platforms grew by ~170-400 bytes; the change is not
opt-in, so the size cost falls on everyone.
On the first point, Nora Schiffer replied with a concrete use case
(EFI-in-FIT plus padded loaders such as shim, systemd-boot, and
OpenWrt's lzma-loader can produce compression ratios that outrun the
8x heuristic), and mentioned this is on the road map for TQ-Systems
standard BSPs.
On the second point, v2 reworks the implementation to cut the size
cost, measures it across the format and architecture buckets, and
splits the work per format so each decompressor's support can be
taken or dropped on its own.
Background: for a compressed kernel_noload image, bootm_load_os()
sizes the decompression buffer as ALIGN(image_len * 8, SZ_1M). The
8x heuristic works for typical kernels, but any well-compressed
payload can exceed it, and no fixed multiplier is safe against
arbitrarily compressible input.
Each implementation patch adds a small static header-parse helper in
bootm.c (no new public API) and wires it into a size-hint switch;
helper and switch case are only compiled when the matching
decompressor is enabled, so boards that do not build a format pay no
code for it. gzip's ISIZE is a fixed trailer read, lzma's size a
fixed header read, lz4 mirrors ulz4fn()'s frame-header validation,
and zstd asks zstd_get_frame_header(), whose frame-parsing code
already ships with the zstd decompressor. The header-recorded value
is attacker-controlled, so it is capped at CONFIG_SYS_BOOTM_LEN, and
it is only an allocation hint: the decoder stays authoritative
during the actual decompression.
Text size deltas of the u-boot ELF (size(1), distro gcc 13.3 cross
toolchains); data/bss are unchanged everywhere. To make the columns
directly comparable, the v1 column is v1's implementation commit
cherry-picked onto this series' base, so both columns share one
baseline:
board arch decompressors v1 v2
qemu_arm arm gzip +160 +104
qemu-ppce500 powerpc gzip +176 +112
mt7623n_bpir2 arm gzip+lzma +184 +128
qemu_arm64 arm64 gzip+lzma+lz4 +384 +368
qemu-riscv64 riscv64 gzip+lzma+lz4 +332 +352
th1520_lpi4a riscv64 all four +412 +404
am62x_evm_a53 arm64 all four, LTO +0 * +8192 *
qemu-x86 x86 none +108 -2
* am62x_evm_a53's number is dominated by the Cortex-A53 erratum
843419 linker workaround (default-enabled in distro binutils for
aarch64): symbol-level code growth (nm -S) is +392 for v1 and
+452 for v2, but those bytes shift which ADRP instructions land
at the erratum's page offsets, and ld pads each inserted veneer
to a full 4 KiB page. v2 happens to trigger two such pages here;
v1 triggered the same two on its own original base and none on
this one. See the world-build note below.
To see how much each bucket weighs, I configured all 1550 defconfigs
and sorted them by which decompressors they enable next to bootm:
858 gzip only (722 of them arm, essentially the 32-bit boards)
530 gzip+lzma+lz4 (494 arm, mostly arm64, plus 36 riscv)
38 gzip+lzma
31 bootm with no decompressor at all
27 gzip+lzma+lz4+zstd
19 gzip+lz4
15 gzip+zstd
6 other combinations
26 do not link bootm at all
To measure at the same scale as the original objection, I also ran a
full world build (buildman, all 1550 defconfigs, distro plus
kernel.org toolchains, gcc 13.3/14.2) over one branch holding the
base, the v1 implementation, its revert, and this series. 1496
boards built on all four commits with the revert reproducing the
base sizes exactly (44 boards did not build on every commit, and 10
built nondeterministically; both sets were excluded). Of those 1496,
the same 1375 change under either version and the rest are
untouched, including every board without bootm or without a
decompressor:
v1 v2
mean delta over all boards +194 B +166 B
median delta (changed boards) +160 B +96 B
median, 856 gzip-only boards +112 B +80 B
median, 517 gzip+lzma+lz4 boards +392 B +376 B
boards cheaper with v2 - 1222
boards costlier with v2 - 74
The world build also puts the am62x footnote in proportion: 53
boards under v1 and 49 under v2 (28 in both sets), all arm64, show
size(1) jumps of one or two 4 KiB pages in either direction (min
-8192, max +8192). The mechanism is the Cortex-A53 erratum 843419
linker workaround: when a code change shifts which ADRP instructions
land at page offsets 0xff8/0xffc, ld materialises a 16-byte veneer
and pads it to a full 4 KiB page so the page offsets of all
downstream code stay unchanged. Any few-hundred-byte change re-rolls
which boards are affected, in both directions; symbol-level growth
on every such board I checked matches the byte ranges above.
Since Tom noted the higher growth in his run was on multi-algorithm
platforms: building each patch in sequence on a gzip+lzma+lz4 board
(qemu_arm64) and an all-four board (th1520_lpi4a) gives the
per-format cost directly, in bytes:
qemu_arm64 th1520_lpi4a
gzip +112 +86
zstd +0 +62
lz4 +176 +176
lzma +80 +80
(zstd is +0 on qemu_arm64 because that board does not enable it, so
the guard really does compile the helper out.) lz4 is the most
expensive parser because it mirrors ulz4fn()'s frame validation;
zstd is the cheapest because zstd_get_frame_header() already ships
with the decompressor. Since the series is split per format, if the
multi-algorithm cost still looks too high, dropping the lz4 patch
alone would cut the 517-board gzip+lzma+lz4 bucket from a median of
+376 to roughly +200; lz4 images then simply keep the 8x fallback.
In the v1 thread Simon suggested recording the uncompressed size as
a FIT property instead. As discussed there, the two compose: a FIT
property could be layered on top later, with bootm preferring the
property, then the stream header, then the 8x fallback. This series
provides the part that works for every existing image and for the
legacy uImage form of kernel_noload.
Series layout, one decompressor at a time:
1. gzip helper + wiring
2. gzip pytests (lying-header overflow, header-sized, boundary)
3. zstd helper
4. zstd pytest (guarded by requiredtool zstd)
5. lz4 helper
6. lz4 pytest (guarded by requiredtool lz4)
7. lzma helper
8. lzma pytests (real size patched into the header field, plus the
"unknown" size marker fallback; needs no external tool since
Python's lzma module is in the standard library)
Every patch builds in isolation on sandbox_defconfig and
qemu_arm_defconfig; the seven kernel_noload_decomp pytests and the
full test_fit class pass on sandbox.
Changes in v2:
- split the single implementation patch into per-format patches
(gzip, zstd, lz4, lzma), each acceptable or droppable on its own
- make the helpers static in bootm.c, compiled only when the
matching decompressor is enabled, instead of one always-built
public image_decomp_get_uncompressed_size() in boot/image.c; this
removes the cost from boards without the formats entirely
- regroup the pytests per format, next to the patch they exercise
- add runtime lzma coverage (header-recorded size and the "unknown"
marker fallback) in place of v1's parser-only C unit test, which
cannot reach a static helper
- measure the size cost on a common base across all 1550 boards and
document the per-bucket numbers and the arm64 erratum-843419 page
effect in this cover letter
Aristo Chen (8):
bootm: size the noload gzip decompression buffer from ISIZE
test: fit: cover the kernel_noload gzip header-size and lying-header
paths
bootm: size the noload zstd decompression buffer from
Frame_Content_Size
test: fit: cover the kernel_noload zstd header-size path
bootm: size the noload lz4 decompression buffer from Content_Size
test: fit: cover the kernel_noload lz4 header-size path
bootm: size the noload lzma decompression buffer from the header
test: fit: cover the kernel_noload lzma header-size and unknown-size
paths
boot/bootm.c | 144 ++++++++++++++++++++++-
test/py/tests/test_fit.py | 282 ++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 400 insertions(+), 26 deletions(-)
--
2.43.0
next prev parent reply other threads:[~2026-08-18 13:50 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-09 4:23 [PATCH 0/3] bootm: size the noload buffer from the compressor header Aristo Chen via U-Boot
2026-08-09 4:23 ` [PATCH 1/3] bootm: size the noload decompression " Aristo Chen via U-Boot
2026-08-09 15:27 ` Tom Rini
2026-08-10 2:32 ` Aristo Chen via U-Boot
2026-08-10 16:37 ` Tom Rini
2026-08-12 7:45 ` Nora Schiffer
2026-08-12 15:57 ` Tom Rini
2026-08-15 18:33 ` Simon Glass
2026-08-17 16:01 ` Aristo Chen via U-Boot
2026-08-17 19:24 ` Tom Rini
2026-08-09 4:23 ` [PATCH 2/3] test: fit: cover the kernel_noload header-size and lying-header paths Aristo Chen via U-Boot
2026-08-09 4:23 ` [PATCH 3/3] test: lib: cover image_decomp_get_uncompressed_size() for lzma streams Aristo Chen via U-Boot
2026-08-18 13:23 ` Aristo Chen [this message]
2026-08-18 13:23 ` [PATCH v2 1/8] bootm: size the noload gzip decompression buffer from ISIZE Aristo Chen
2026-08-18 13:23 ` [PATCH v2 2/8] test: fit: cover the kernel_noload gzip header-size and lying-header paths Aristo Chen
2026-08-18 13:23 ` [PATCH v2 3/8] bootm: size the noload zstd decompression buffer from Frame_Content_Size Aristo Chen
2026-08-18 13:23 ` [PATCH v2 4/8] test: fit: cover the kernel_noload zstd header-size path Aristo Chen
2026-08-18 13:23 ` [PATCH v2 5/8] bootm: size the noload lz4 decompression buffer from Content_Size Aristo Chen
2026-08-18 13:23 ` [PATCH v2 6/8] test: fit: cover the kernel_noload lz4 header-size path Aristo Chen
2026-08-18 13:23 ` [PATCH v2 7/8] bootm: size the noload lzma decompression buffer from the header Aristo Chen
2026-08-18 13:23 ` [PATCH v2 8/8] test: fit: cover the kernel_noload lzma header-size and unknown-size paths Aristo Chen
2026-08-18 22:10 ` [PATCH v2 0/8] bootm: size the noload buffer from the compressor header Tom Rini
2026-08-19 14:53 ` Aristo Chen
2026-08-21 18:55 ` Tom Rini
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=20260818132332.324173-1-aristo.chen@canonical.com \
--to=aristo.chen@canonical.com \
--cc=nora.schiffer@ew.tq-group.com \
--cc=sjg@chromium.org \
--cc=u-boot@lists.u-boot-project.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.