The probe piped the candidate binary into grep to look for -B support.
mkimage with no arguments prints usage and exits non-zero, and this script
runs under `set -euo pipefail`, so the pipeline reported failure even when
grep matched. Every candidate was rejected and the script failed closed with
"no mkimage on PATH supports -B" while a capable mkimage 2025.01 sat first
on PATH, making it impossible to build a boot.img at all.
Neutralise the probed command's exit status before the pipe. Sentinel
comments now bracket the probe so the new regression test extracts and
exercises the shipping code rather than a copy of it.
Closes#17
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2D2KtdgwbhbF6Mo64eUrn
054790d claimed the vendor aligns FIT sub-images to 0x800 and made that a build
gate. Both were wrong, and the reasoning was thin: two payloads from one build,
generalised into an invariant.
What U-Boot actually requires. IMAGE_ALIGN_SIZE is 512 (include/image.h:955-958)
and the read is blk_off = (FIT_ALIGN(fdt_totalsize) + offset) / blksz
(arch/arm/mach-rockchip/fit.c:331), a truncating divide by the 512-byte eMMC
block. So the metadata size must be a multiple of 512 -- otherwise FIT_ALIGN
rounds it up and EVERY payload is read late, including ones whose own position
is perfectly aligned -- and each data-position must be a multiple of 512.
Nothing in the FIT or RESC path references 2048.
Why the 2048 gate was actively harmful: of the eight boot.img files on this
machine, four are 512-aligned but sit at data-position % 2048 = 1536, including
vendor RELEASE_TEST builds that boot. The gate would have rejected images the
vendor shipped. 0x800 is the vendor's -p value -- the file position of the FIRST
payload -- not an alignment; 054790d moved it into the -B slot.
And it broke the build inside the SDK. mk-bootimg.sh resolves mkimage with a
bare `command -v`, project/build.sh:64 prepends the SDK tool directory to PATH,
and the SDK vendors mkimage 2017.09, which has no -B and exits 255 with
"invalid option -- 'B'" under set -euo pipefail. The 6.18 image built only
because it was made from a normal shell that found host mkimage 2025.01. The
flag is now feature-detected; the vendor packer 512-aligns natively, so omitting
it there is correct rather than a fallback.
9387cff's -B 0x200 was right, and better derived than 054790d credited: the
proven image's resource sits at 8748544, a multiple of 512 but not of 1024 or
2048, which pins the alignment at exactly 512 rather than bounding it.
The gate now asserts what U-Boot enforces -- metadata % 512 and every
data-position % 512 -- and the embedded-data guard drops from 65536 to >= 4096
to match FIT_FDT_MAX_SIZE (SZ_4K, fit.c:24). At 65536 a 5000-byte header passed
the build and returned "No fit blob" on a panel.
The already-flashed 6.18 boot.img is unaffected: 2048 is a multiple of 512, so
it satisfies the real requirement, which is why it booted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2D2KtdgwbhbF6Mo64eUrn
mkimage -p only places the FIRST payload. The rest are packed contiguously, and
U-Boot reads each by dividing its data-position by the 512-byte block size with a
truncating divide -- so a payload that is not block-aligned is read from the
wrong offset. Measured: without -B this script emitted the kernel at 0x9A94,
data-position % 512 = 148, which is exactly the failing shape.
That failure is on record. flare-edge 9387cff (2026-09-01) hit it on a boot_b
FIT: "unaligned sub-images fail the RESC loader's sha256 and the logo silently
falls back to 'No resource file'". It landed on -B 0x200, inferred from noticing
the proven image was "512-aligned" without checking for a larger factor.
The proven images are aligned to 0x800, not merely to 512. Measured on the pair
that boots this board today: boot.img fdt/kernel/resource at 0x800 / 0x12800 /
0x3A7800, recovery.img at 0x800 / 0x12800 / 0x3F8800 -- every one a multiple of
2048. So 0x800 satisfies everything 0x200 does, since every 0x800 multiple is a
0x200 multiple, and additionally reproduces the vendor layout exactly. The cost
is at most 2 KiB of padding. Where a remembered rule and a booting image
disagree, match the image.
The alignment is now also asserted at build time rather than trusted. It runs on
every build because the failure it catches is silent -- a wrong offset does not
fail the build, it fails on a panel, and only sometimes visibly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2D2KtdgwbhbF6Mo64eUrn
Two gaps between "this SDK builds a kernel" and "this SDK can replace the
vendor SDK for the device".
BOOT IMAGE. build-kernel.sh emitted a zImage and a dtb and stopped, so
producing something the board's U-Boot would actually boot still meant going
through the vendor tree. mk-bootimg.sh packages the pair the way M2 bring-up
established (kernel/docs/m2-boot-on-c8a3.md): an EXTERNAL-DATA FIT
(mkimage -E -p 0x800), a mandatory `resource` multi sub-image carrying
rk-kernel.dtb plus any logos, and the sysmem sentinel load addresses. Each of
those was learned from a specific failure -- an embedded-data FIT is "No fit
blob", a missing resource image is "Failed to load DTB, ret=-19", real load
addresses collide -- so the script also ASSERTS the metadata stayed small,
because an embedded-data FIT looks perfectly fine until a panel will not come
back.
NETWORKING. Diffing this defconfig's expansion against the kernel actually
shipping on the panel found three whole subsystems missing, none of which fail
at build time and none of which are visible until the unit is in the field:
- WIREGUARD + NET_UDP_TUNNEL: flared's mesh to FLARE. Without it wg0 never
comes up.
- VLAN_8021Q: the MikroTik app configures tagged ports the panel terminates.
- NETFILTER and legacy iptables: every rule in S35iptables, and NAT for
router mode.
The netfilter half carried a trap worth naming. 6.18 split the legacy tables
out behind NETFILTER_XTABLES_LEGACY and IP_NF_IPTABLES_LEGACY, symbols that do
not exist in 5.10 -- so copying the vendor kernel's symbol list verbatim gives
a kernel where IP_NF_FILTER and IP_NF_NAT silently stay off and `iptables` has
no filter or nat table at all. Our userspace drives legacy iptables, not nft.
The result now shows ZERO regressions against the shipping 5.10 kernel across
mesh, gadget, firewall, VLAN, storage, net core, display/input, RGA/NPU,
wifi/BT and watchdog.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2D2KtdgwbhbF6Mo64eUrn