Skip to content

DE25: aarch64 stage-1 initramfs + qemu-system-aarch64 test leg (stage 1 becomes a fragment stack) - #152

Merged
mcfbytes merged 4 commits into
masterfrom
feat/de25-initramfs
Sep 10, 2026
Merged

DE25: aarch64 stage-1 initramfs + qemu-system-aarch64 test leg (stage 1 becomes a fragment stack)#152
mcfbytes merged 4 commits into
masterfrom
feat/de25-initramfs

Conversation

@mcfbytes

@mcfbytes mcfbytes commented Sep 6, 2026

Copy link
Copy Markdown
Owner

What

ADR 0029 D11's owed pre-hardware work: the stage-1 initramfs built for aarch64, and a QEMU path that boots it. Three commits, reviewable in order.

1. Stage 1 becomes a fragment stack per board (5849aa3)

  • configs/mister_initramfs_defconfigconfigs/fragments/initramfs-common.fragment (everything but the arch) + initramfs-de10nano.fragment / initramfs-de25nano.fragment (arch/ABI + headers series only). stacks.mk gains INITRAMFS_DE10NANO_FRAGMENTS / INITRAMFS_DE25NANO_FRAGMENTS.
  • /init, initramfs-busybox.config, initramfs-post-build.sh move to board/mister/common/ (shared by path, like linux-mister.fragment). /init has no arch-specific line; the BusyBox config names no arch.
  • Makefile: stage-1 .config rules use the stacks (fragments stay file prerequisites — a stage-1 config is generated, not iterated on). New make de25-initramfs family into output-initramfs-de25/; initramfs-verify is parameterised so de25-initramfs-verify runs the same assertions under qemu-aarch64. de25 still embeds nothing (D11 interim); external.mk's guard comment has the one-commit switch.
  • check-config-fragments.sh check (f): every symbol initramfs-<board>.fragment sets must be set identically in <board>.fragment. Golden gains two lines; the existing four did not move.
  • CI: BR_INITRAMFS_HOST_KEY hashes the stack via config_stack_files (one cold initramfs host cache, once). lint.yml's per-board walk already covers board/mister/common/.

Identity proof (Buildroot 2026.08): DE10 stage-1 resolved config, old path vs new, differs only in BR2_DEFCONFIG, the -dirty suffix and the three moved paths — 5,240 lines otherwise identical. §11 of docs/buildroot-config.md records it.

2. scripts/test-initramfs.sh --board de25nano (690af50)

Boots the aarch64 cpio on qemu-system-aarch64 -M virt -cpu cortex-a76 through the DE10's eight cases, unchanged. The test kernel is built from the DE25's own linux.config + linux-mister.fragment at the DE25 pin (no arm64 multi_v7 exists; the product config builds in minutes and this way its exfat/vfat/loop/ext4 choices are proven too). Cross compiler = the stage-1 build's own musl toolchain, so make de25-initramfs is the only prerequisite. ci-tests.sh gains the leg (skips when no DE25 cpio was built). Kernel-tarball default now prefers dl/linux/, retiring the always-FAIL-on-a-fresh-tree papercut.

3. Docs (24c7925) — and the finding

Leg Result
--board de10nano 8/8 PASS (3 m 09 s)
--board de25nano 7/8 PASS (4 m 16 s incl. the 7.2.3 kernel build)

The symlink case Oopses the guest kernel (exfat_symlinkpage_symlinkpc: 0x0). Board patch 0031 (exFAT Samsung symlinks, ADR 0019) writes the link target with the VFS's page_symlink(), which calls a_ops->write_begin — and 7.x exFAT is iomap-based with no write_begin/write_end (6.18.49 has both). So 0031 crashes on symlink creation on every 7.x kernel. Not an aarch64 bug, not an /init bug. The DE10's RT 7.2.3 kernel carries the same file by symlink and nothing had ever executed it — the DE10 leg only boots 6.18. The shipped DE10 image is unaffected (8/8 above includes symlink on 6.18).

Recorded in ADR 0002 §8b, ADR 0029 D11, docs/rt-beta-kernel.md §6, the DE25 patch README, and docs/de25-nano-tasks.md (next-steps item 2 = a 7.x re-anchor of 0031; the aarch64 symlink case is its acceptance test). The fix is a separate PR.

Verified locally (Buildroot 2026.08, gcc 15.3.0)

  • make de25-initramfs: toolchain + cpio in 6 min from nothing; 466,944-byte cpio, static aarch64 BusyBox; verify OK.
  • make initramfs (DE10, rebuilt clean on 2026.08): 424,448-byte cpio; verify OK; QEMU 8/8.
  • check-config-fragments.sh OK (6 stacks, arch lockstep 6 + 3 symbols); check-kernel-defconfig-sync.sh OK; shellcheck clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FYjrnzEEu4HzJSPWNAaRvm

mcfbytes and others added 4 commits September 6, 2026 02:05
…4 (DE25) stage 1

ADR 0029 D11 made the DE10's two-stage layout the DE25-Nano's target, which
means a stage-1 initramfs built for aarch64. The stage-1 config was the one
standalone defconfig left (docs/buildroot-config.md §10 recorded "left
standalone" on 2026-09-02, weighing six saved lines against a new stack shape
for one board); a second board wants every line of it except the arch tuple,
so it is now a stack like everything else:

  configs/fragments/initramfs-common.fragment     everything but the arch
  configs/fragments/initramfs-de10nano.fragment   BR2_arm + cortex_a9 + NEON/VFP + headers 6.18
  configs/fragments/initramfs-de25nano.fragment   BR2_aarch64 + cortex_a76_a55 + headers 7.1
  stacks.mk: INITRAMFS_DE10NANO_FRAGMENTS / INITRAMFS_DE25NANO_FRAGMENTS

configs/mister_initramfs_defconfig is deleted. The three arch-neutral files
it named -- initramfs-overlay/init, initramfs-busybox.config,
initramfs-post-build.sh -- move from board/mister/de10nano/ to
board/mister/common/ (the linux-mister.fragment precedent): /init has no
architecture-specific line, the BusyBox config names no arch, the hook only
deletes files. Every live reference is updated; PLAN.md/TASKS.md and the
readiness ledger keep their historical citations.

Identity proof (Buildroot 2026.08, this tree): the DE10 stage-1 resolved
.config from the old `make mister_initramfs_defconfig` path vs the new
merge_config.sh + olddefconfig path differs in exactly BR2_DEFCONFIG (the
savedefconfig output path, as at the image split), the git-describe -dirty
suffix, and the three moved paths -- 5,240 lines otherwise identical
(gcc 15.3.0, binutils 2.45.1, musl, headers 6.18). golden.sha256 gains
lines for the two new stacks; the existing four did not move.

Makefile: the stage-1 .config rules use the fragment stacks (fragments ARE
file prerequisites here, as the defconfig was -- a stage-1 config is
generated, not iterated on; an explicit empty rule keeps the `%:` catch-all
off them). New `de25-initramfs` family (build/verify/clean/defconfig/
menuconfig/busybox-menuconfig) into output-initramfs-de25/. initramfs-verify
is parameterised by three variables so de25-initramfs-verify runs the SAME
applet/binary/parse assertions on the aarch64 cpio under qemu-aarch64 --
one assertion set for both boards. `de25` still depends on no initramfs
target: the cpio is built and verified, NOT embedded, while the shipped
card keeps D11's interim plain-ext4 root; external.mk's BR2_arm guard
stands and its comment now carries the one-commit switch.

scripts/check-config-fragments.sh: new check (f), stage-1 arch lockstep --
every symbol initramfs-<board>.fragment sets must be set identically in
<board>.fragment, so a CPU-tuning or headers-series change in one file and
not the other fails CI instead of building a stage 1 for a different target
than the kernel it rides in. The initramfs stacks are picked up by (a)-(d)
automatically (stacks.mk enumeration).

CI: BR_INITRAMFS_HOST_KEY hashes the initramfs-de10nano stack's files
through config_stack_files instead of the deleted defconfig (one cold
initramfs host cache, once). lint.yml's per-board script walk already
covers board/mister/common/.

Built and verified in this tree on 2026.08: DE10 cpio 424,448 bytes,
DE25 cpio 466,944 bytes (static aarch64 BusyBox 1.38.0; toolchain + cpio in
6 min from nothing), both initramfs-verify OK; check-config-fragments OK
(6 stacks), check-kernel-defconfig-sync OK.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYjrnzEEu4HzJSPWNAaRvm
…stem-aarch64

The harness gains a board switch (`--board de10nano|de25nano`, or
TEST_INITRAMFS_BOARD); the DE10 leg is unchanged in behaviour. The DE25 leg:

  * boots output-initramfs-de25/images/rootfs.cpio on
    `qemu-system-aarch64 -M virt -cpu cortex-a76` (virt has no default CPU
    on aarch64; a76 is what BR2_cortex_a76_a55 tunes for -- an armv8.2
    target whose LSE atomics a cortex-a53 model would SIGILL on);
  * builds its test kernel from the DE25's OWN product config
    (board/mister/de25nano/linux.config + board/mister/common/
    linux-mister.fragment) at the DE25's pinned 7.2.3, plus the unchanged
    harness fragment (every symbol in it is arch-neutral) -- arm64 has no
    multi_v7_defconfig to lean on, the product config is minimal enough to
    build in minutes, and this way the leg also proves the product
    config's own exfat/vfat/loop/ext4 choices, which the DE10 leg cannot
    do for its board;
  * cross-compiles marker-init/test-symlink with the stage-1 build's own
    musl toolchain (output-initramfs-de25/host/bin), so `make
    de25-initramfs` is the only build prerequisite -- no `make de25`;
  * keeps its own caches (work/test-initramfs-de25*).

Every case, cmdline and assertion is identical between the legs. The
kernel-tarball default now prefers dl/linux/linux-<pin>.tar.xz (where the
Buildroot kernel build actually puts it) over the work/ convention nothing
creates, which retires the always-FAIL-on-a-fresh-tree papercut. The mtools
prerequisite message no longer claims `make initramfs` builds host mtools
(it never did; the system package is what is used).

ci-tests.sh gains the DE25 leg (de25-initramfs-verify + the eight cases),
gated on the DE25 cpio existing so a DE10-only tree skips it visibly.

Results, this tree, 2026-09-06:
  --board de10nano  8/8 PASS (3 m 09 s, cached kernel)
  --board de25nano  7/8 PASS (4 m 16 s including the 7.2.3 kernel build;
                    one case re-run 5.5 s). `symlink` FAILS with a kernel
                    Oops in exfat_symlink -> page_symlink -> pc 0x0: board
                    patch 0031 relies on a_ops->write_begin, which 7.x
                    iomap exFAT no longer has. Not an aarch64 bug, not an
                    /init bug -- a 7.x carry bug in 0031 that the DE10 RT
                    kernel shares and nothing had ever executed. Recorded
                    in the next commit; the fix is a follow-up.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYjrnzEEu4HzJSPWNAaRvm
…finding

ADR 0002 §8b, ADR 0029 D11 (delivered), docs/de25-nano-tasks.md (wave 3
record; next-steps item 2 becomes the 0031 re-anchor), docs/de25-sdcard.md
§2 (the one-commit switch table), docs/rt-beta-kernel.md §6 (the RT kernel
carries the same broken 0031 by symlink), the DE25 patch README row 25.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYjrnzEEu4HzJSPWNAaRvm
…_symlink() Oopses on iomap exfat

Found by the previous commit's aarch64 QEMU leg, the first thing ever to
EXECUTE 0031 on a 7.x kernel: exfat_symlink() -> page_symlink() ->
aops->write_begin, and 7.x exfat (iomap, fs/exfat/iomap.c) has no
write_begin/write_end in exfat_aops -- 6.18.49 has both. So the first
`ln -s` on any 7.x kernel is a NULL function call: Oops, pc 0x0, lr
page_symlink+0x178. The patch applied at -F0 and compiled, which is why
every "verified" row about it was true and insufficient. Blast radius: the
DE25 (7.2.3) and the DE10's RT 7.2.3 kernel -- linux-patches-beta/0031
was a symlink to the shared 6.18 file. The shipped DE10 6.18 kernel is
unaffected (its QEMU leg passes `symlink` 8/8).

linux-patches-beta/0031-exfat-samsung-symlinks.patch is a REAL re-anchored
copy again (the fifth; the series header explains why this one is the API
kind of drift, not the context kind that the 2026-07-25 trick could absorb),
and board/mister/de25nano/linux-patches/0031 now links to it. The shared
6.18 patch is untouched. The delta is confined to fs/exfat/namei.c: a new
exfat_symlink_write_target() allocates the clusters with 7.x's
exfat_map_cluster() under s_lock, fills the sectors through buffer heads,
flushes the range with sync_blockdev_range() -- page_get_link() reads via
iomap bios straight from the device and never sees a dirty bdev buffer --
and does the valid_size / zeroed_size / i_size bookkeeping
exfat_write_iomap_end() and the VFS write path would have done.
iomap_file_buffered_write() was not an option: it dereferences
iocb->ki_filp, and a ->symlink op has no struct file.

Verified: applies at -F0 to pristine 7.2.3 (12/12 hunks, 6 files);
compiles for arm with output-rt's own 7.2.3 .config (fs/exfat/, zero
warnings) and for arm64 with the DE25 config; scripts/test-initramfs.sh
--board de25nano from a fresh source tree: 8/8 PASS, the `symlink` case
with its full assertion set (hot+cold round-trip, DT_LNK, the create+unlink
cluster-leak tripwire via statvfs, host-side fsck-clean). Still unexecuted:
0031 on 32-bit 7.x -- the RT kernel on a real DE10 -- which the docs now
say in as many words.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYjrnzEEu4HzJSPWNAaRvm
@mcfbytes
mcfbytes merged commit ef885ec into master Sep 10, 2026
7 checks passed
@mcfbytes
mcfbytes deleted the feat/de25-initramfs branch September 10, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant