diff --git a/.github/actions/buildroot-build/action.yml b/.github/actions/buildroot-build/action.yml index 5b8690da..8a82c1e3 100644 --- a/.github/actions/buildroot-build/action.yml +++ b/.github/actions/buildroot-build/action.yml @@ -269,7 +269,13 @@ runs: # this is shell, and both halves read the same env var either way. # See docs/ci.md#cache-keys. tc_hash=$(sha256sum .br-toolchain-fingerprint | cut -d' ' -f1) - ir_hash=$(sha256sum configs/mister_initramfs_defconfig | cut -d' ' -f1) + # The stage-1 configuration is the initramfs-de10nano fragment stack + # (configs/fragments/stacks.mk), read through the same helper as the + # image/kernel stacks above so a fragment added to the stack moves this + # key without anyone editing a list here. + mapfile -t ir_files < <(config_stack_files INITRAMFS_DE10NANO) + [ "${#ir_files[@]}" -gt 0 ] || { echo "::error::configs/fragments/stacks.mk names no fragments for INITRAMFS_DE10NANO" >&2; exit 1; } + ir_hash=$(cat "${ir_files[@]}" | sha256sum | cut -d' ' -f1) echo "BR_INITRAMFS_HOST_KEY=br-initramfs-host-${ws_fp}-${ver}-${ir_hash}" >> "$GITHUB_ENV" # Variant-resolved host-toolchain key + paths (main's key STRING diff --git a/Makefile b/Makefile index 212e007d..8ed2124b 100644 --- a/Makefile +++ b/Makefile @@ -77,10 +77,34 @@ OUTPUT_DIR := $(ROOT_DIR)/output # # It is emphatically NOT BR2_TARGET_ROOTFS_INITRAMFS on the main config: that # option embeds the whole ~300 MB target rootfs into the kernel (A1). +# +# Since 2026-09-06 stage 1 is a fragment STACK like every other configuration +# here (configs/fragments/stacks.mk: `initramfs-common initramfs-de10nano`), +# not a standalone defconfig -- and it has an aarch64 sibling, +# $(DE25_INITRAMFS_OUTPUT_DIR) below, built from `initramfs-common +# initramfs-de25nano`. The /init, the BusyBox config and the post-build hook +# are shared by both boards under board/mister/common/. INITRAMFS_OUTPUT_DIR := $(ROOT_DIR)/output-initramfs INITRAMFS_CPIO := $(INITRAMFS_OUTPUT_DIR)/images/rootfs.cpio -INITRAMFS_DEFCONFIG := $(ROOT_DIR)/configs/mister_initramfs_defconfig -INITRAMFS_INIT := $(ROOT_DIR)/board/mister/de10nano/initramfs-overlay/init +INITRAMFS_INIT := $(ROOT_DIR)/board/mister/common/initramfs-overlay/init + +# --- DE25-Nano stage-1 initramfs (ADR 0029 D11; docs/de25-sdcard.md §2) ----- +# The SAME /init, BusyBox config and hook, built for aarch64 (static musl, the +# DE25's CPU tuning and headers series -- the initramfs-de25nano fragment). +# A sixth Buildroot output dir, for the usual reason: a +# different configuration needs a different O=, and this one's toolchain is a +# different architecture from output-initramfs/'s. +# +# NOT YET EMBEDDED. The shipped DE25 card keeps D11's interim plain-ext4 root +# until a board has booted it, and external.mk's embedding fixup still keys on +# BR2_arm. `make de25-initramfs` builds and verifies the cpio (the same +# applet/binary/parse assertions as the DE10's, run under qemu-aarch64) and +# `scripts/test-initramfs.sh --board de25nano` boots it through all eight +# cases on qemu-system-aarch64 -- so the day the card switches to the +# two-stage layout, the stage-1 half is already proven. See the DE25_OUTPUT_DIR +# header for why `de25` still does not depend on ANY initramfs target. +DE25_INITRAMFS_OUTPUT_DIR := $(ROOT_DIR)/output-initramfs-de25 +DE25_INITRAMFS_CPIO := $(DE25_INITRAMFS_OUTPUT_DIR)/images/rootfs.cpio # --- RT / Linux-7.2 "beta" kernel variant (docs/rt-beta-kernel.md) ------------ # A THIRD Buildroot output dir, same trick as the initramfs stage above: a @@ -191,13 +215,16 @@ CONFIG_CHECK_DIR := $(ROOT_DIR)/output-config-check # package fragment is in its stack (docs/buildroot-config.md §6, §10). # # ALSO UNLIKE `rt` and `all`: `de25` does NOT depend on `initramfs`. That cpio -# is an armv7 BusyBox built by configs/mister_initramfs_defconfig, and it exists -# because the DE10's real root is a loop-mounted ext4 image on a FAT partition -# that U-Boot will not load an initrd for (A3, docs/boot-chain.md). The DE25 -# boots a plain ext4 root partition, so there is nothing for a stage 1 to do — -# and embedding armv7 userspace in an aarch64 kernel would produce a kernel that -# panics in a novel and confusing way. external.mk's LINUX_KCONFIG_FIXUP_CMDS -# hook is guarded off for this build; see the guard's comment there. +# is an armv7 BusyBox (the initramfs-de10nano stack), and it exists because the +# DE10's real root is a loop-mounted ext4 image on a FAT partition that U-Boot +# will not load an initrd for (A3, docs/boot-chain.md). Embedding armv7 +# userspace in an aarch64 kernel would produce a kernel that panics in a novel +# and confusing way. Nor does `de25` depend on `de25-initramfs` -- YET: the +# aarch64 stage 1 exists and is QEMU-proven (see DE25_INITRAMFS_OUTPUT_DIR +# above), but the shipped card keeps ADR 0029 D11's interim plain-ext4 root +# until a board has booted it, so the DE25 kernel embeds nothing and +# external.mk's LINUX_KCONFIG_FIXUP_CMDS hook is guarded off for this build; +# see the guard's comment there for the switch. # # Scope reminder, because the target name invites the wrong assumption: this is # a BARE DEVELOPER OS. No MiSTer binaries, no DE10 packages. See ADR 0027 @@ -282,10 +309,14 @@ BR_MAKE = PATH="$(HOSTSHIM_DIR):$$PATH" \ $(MAKE) -C $(BR_DIR) O=$(OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR) # The same, aimed at the stage-1 output directory. Same Buildroot tree, same -# BR2_EXTERNAL, same download cache — only O= and the defconfig differ. +# BR2_EXTERNAL, same download cache — only O= and the fragment stack differ. BR_MAKE_INITRAMFS = PATH="$(HOSTSHIM_DIR):$$PATH" \ $(MAKE) -C $(BR_DIR) O=$(INITRAMFS_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR) +# The same, aimed at the DE25's stage-1 output directory. +BR_MAKE_DE25_INITRAMFS = PATH="$(HOSTSHIM_DIR):$$PATH" \ + $(MAKE) -C $(BR_DIR) O=$(DE25_INITRAMFS_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR) + # The same, aimed at the RT/beta output directory (docs/rt-beta-kernel.md). BR_MAKE_RT = PATH="$(HOSTSHIM_DIR):$$PATH" \ $(MAKE) -C $(BR_DIR) O=$(RT_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR) @@ -328,6 +359,8 @@ stack_files = $(addprefix $(FRAGMENT_DIR)/,$(addsuffix .fragment,$(1))) DE10NANO_STACK := $(call stack_files,$(DE10NANO_FRAGMENTS)) DE10NANO_KERNEL_STACK := $(call stack_files,$(DE10NANO_KERNEL_FRAGMENTS)) DE25NANO_STACK := $(call stack_files,$(DE25NANO_FRAGMENTS)) +INITRAMFS_DE10NANO_STACK := $(call stack_files,$(INITRAMFS_DE10NANO_FRAGMENTS)) +INITRAMFS_DE25NANO_STACK := $(call stack_files,$(INITRAMFS_DE25NANO_FRAGMENTS)) # $(call merge_fragments,,) -- step 1 of 2; the # caller follows it with the matching `$(BR_MAKE_*) olddefconfig`. The first @@ -354,6 +387,8 @@ endef .PHONY: all help buildroot-fetch buildroot-verify buildroot-unpack buildroot-showsig require-tools .PHONY: clean distclean .PHONY: initramfs initramfs-clean initramfs-menuconfig initramfs-busybox-menuconfig check-initramfs +.PHONY: initramfs-defconfig de25-initramfs de25-initramfs-clean de25-initramfs-defconfig +.PHONY: de25-initramfs-menuconfig de25-initramfs-busybox-menuconfig de25-initramfs-verify .PHONY: rt rt-clean rt-menuconfig rt-external-deps rt-legal-info .PHONY: de25 de25-clean de25-menuconfig de25-linux-menuconfig .PHONY: installer installer-clean installer-menuconfig installer-busybox-menuconfig @@ -377,14 +412,18 @@ Makefile: ; # O=$(OUTPUT_DIR). Same explicit-empty-rule fix as `Makefile: ;` above. $(FRAGMENT_DIR)/stacks.mk: ; -# [P1.10] Exactly the same landmine, one step further out. $(INITRAMFS_DEFCONFIG) is -# a prerequisite of $(INITRAMFS_OUTPUT_DIR)/.config below. It is an existing file with -# no rule of its own, so the catch-all `%: $(BR_STAMP) hostshim` pattern rule matched -# it and make dutifully "remade" it — by forwarding a target literally named -# `/…/configs/mister_initramfs_defconfig` into Buildroot **with O=$(OUTPUT_DIR)**, -# i.e. loading the stage-1 config into the MAIN build's output directory. Caught with -# `make -n initramfs`. An explicit empty rule beats a pattern rule. -$(INITRAMFS_DEFCONFIG): ; +# [P1.10] Exactly the same landmine, one step further out. The stage-1 stacks' +# fragment files are prerequisites of $(INITRAMFS_OUTPUT_DIR)/.config and +# $(DE25_INITRAMFS_OUTPUT_DIR)/.config below (unlike the image configs, a +# stage-1 .config IS regenerated when a fragment changes -- see +# $(OUTPUT_DIR)/.config's comment for why the two differ). Each is an existing +# file with no rule of its own, so the catch-all `%: $(BR_STAMP) hostshim` +# pattern rule matched the old standalone defconfig and make dutifully "remade" +# it — by forwarding a target literally named `/…/configs/mister_initramfs_defconfig` +# into Buildroot **with O=$(OUTPUT_DIR)**, i.e. loading the stage-1 config into +# the MAIN build's output directory. Caught with `make -n initramfs`. An +# explicit empty rule beats a pattern rule. +$(sort $(INITRAMFS_DE10NANO_STACK) $(INITRAMFS_DE25NANO_STACK)): ; # Exactly the same landmine, for the SD-card installer defconfig (see the # comment above it, and INSTALLER_OUTPUT_DIR's header comment). @@ -430,7 +469,7 @@ all: initramfs $(BR_STAMP) hostshim | $(OUTPUT_DIR)/.config # `hostshim` is order-only here too, so the Buildroot invocation below finds # the GNU `install` shim under `make -j`. The explicit rule also beats the `%:` # catch-all at the bottom of this file, same as `Makefile: ;` and -# $(INITRAMFS_DEFCONFIG) above. +# the stage-1 fragment rule above. $(OUTPUT_DIR)/.config: | $(BR_STAMP) hostshim $(call merge_fragments,$(OUTPUT_DIR),$(DE10NANO_STACK)) $(BR_MAKE) olddefconfig @@ -475,7 +514,7 @@ mister_de10nano_defconfig mister_kernel_defconfig mister_de25nano_defconfig: # is the only thing that knows what to delete and what to keep, so skipping it # would report success over a still-dirty tree — this bug, again, one layer out. clean: - @if [ ! -d $(BR_DIR) ] && { [ -d $(OUTPUT_DIR) ] || [ -d $(INITRAMFS_OUTPUT_DIR) ] || [ -d $(RT_OUTPUT_DIR) ] || [ -d $(INSTALLER_OUTPUT_DIR) ] || [ -d $(DE25_OUTPUT_DIR) ] || [ -d $(INSTALLER_KERNEL_OUTPUT_DIR) ] || [ -d $(SDCARD_STAGE_DIR) ] || [ -d $(SDCARD_BUILD_DIR) ]; }; then \ + @if [ ! -d $(BR_DIR) ] && { [ -d $(OUTPUT_DIR) ] || [ -d $(INITRAMFS_OUTPUT_DIR) ] || [ -d $(DE25_INITRAMFS_OUTPUT_DIR) ] || [ -d $(RT_OUTPUT_DIR) ] || [ -d $(INSTALLER_OUTPUT_DIR) ] || [ -d $(DE25_OUTPUT_DIR) ] || [ -d $(INSTALLER_KERNEL_OUTPUT_DIR) ] || [ -d $(SDCARD_STAGE_DIR) ] || [ -d $(SDCARD_BUILD_DIR) ]; }; then \ echo "FATAL: $(BR_DIR) is gone, so Buildroot's own 'clean' cannot run," >&2; \ echo " but an output directory still holds build products. Skipping" >&2; \ echo " would report success over a dirty tree." >&2; \ @@ -485,6 +524,7 @@ clean: fi @if [ -d $(OUTPUT_DIR) ]; then $(BR_MAKE) clean; fi @if [ -d $(INITRAMFS_OUTPUT_DIR) ]; then $(BR_MAKE_INITRAMFS) clean; fi + @if [ -d $(DE25_INITRAMFS_OUTPUT_DIR) ]; then $(BR_MAKE_DE25_INITRAMFS) clean; fi @if [ -d $(RT_OUTPUT_DIR) ]; then $(BR_MAKE_RT) clean; fi @if [ -d $(INSTALLER_OUTPUT_DIR) ]; then $(BR_MAKE_INSTALLER) clean; fi @if [ -d $(DE25_OUTPUT_DIR) ]; then $(BR_MAKE_DE25) clean; fi @@ -510,7 +550,7 @@ clean: # nothing-but-the-clone hammer; it takes work/ and dl/ with it. distclean: rm -rf $(OUTPUT_DIR) $(INITRAMFS_OUTPUT_DIR) $(RT_OUTPUT_DIR) \ - $(INSTALLER_OUTPUT_DIR) $(DE25_OUTPUT_DIR) \ + $(INSTALLER_OUTPUT_DIR) $(DE25_OUTPUT_DIR) $(DE25_INITRAMFS_OUTPUT_DIR) \ $(INSTALLER_KERNEL_OUTPUT_DIR) \ $(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR) $(CONFIG_CHECK_DIR) \ $(EXTRA_MODULES_OVERLAY) $(RT_OVERLAY_STAMP) @@ -518,7 +558,7 @@ distclean: # --- Stage 1: the initramfs cpio ---------------------------------------------- # Phony on purpose. Buildroot is the incremental build system here; re-entering it # is cheap when nothing changed, and it is the only thing that knows that editing -# board/mister/de10nano/initramfs-overlay/init or initramfs-busybox.config means +# board/mister/common/initramfs-overlay/init or initramfs-busybox.config means # the cpio must be regenerated. initramfs: $(INITRAMFS_OUTPUT_DIR)/.config hostshim $(BR_MAKE_INITRAMFS) all @@ -529,6 +569,28 @@ initramfs: $(INITRAMFS_OUTPUT_DIR)/.config hostshim @echo "==> stage-1 initramfs: $$(stat -c %s $(INITRAMFS_CPIO)) bytes ($(INITRAMFS_CPIO))" @echo "" +# The DE25's stage 1: the same recipe aimed at the aarch64 stack. NOT a +# prerequisite of `de25` (see DE25_INITRAMFS_OUTPUT_DIR's header: built and +# proven, not yet embedded). The verify step is the same target with its three +# artifact-locating variables re-pointed -- one set of assertions for both +# boards, so a new applet /init needs cannot be checked on one and not the other. +de25-initramfs: $(DE25_INITRAMFS_OUTPUT_DIR)/.config hostshim + $(BR_MAKE_DE25_INITRAMFS) all + @test -f $(DE25_INITRAMFS_CPIO) || { \ + echo "FATAL: DE25 stage 1 finished but produced no $(DE25_INITRAMFS_CPIO)" >&2; exit 1; } + @$(MAKE) --no-print-directory de25-initramfs-verify + @echo "" + @echo "==> DE25 stage-1 initramfs: $$(stat -c %s $(DE25_INITRAMFS_CPIO)) bytes ($(DE25_INITRAMFS_CPIO))" + @echo " Built and verified; NOT embedded in the DE25 kernel yet (ADR 0029 D11)." + @echo " Boot-test it: scripts/test-initramfs.sh --board de25nano" + @echo "" + +de25-initramfs-verify: + @$(MAKE) --no-print-directory initramfs-verify \ + INITRAMFS_VERIFY_CPIO=$(DE25_INITRAMFS_CPIO) \ + INITRAMFS_VERIFY_BUSYBOX=$(DE25_INITRAMFS_OUTPUT_DIR)/target/bin/busybox \ + INITRAMFS_VERIFY_QEMU=qemu-aarch64 + # Every command /init actually invokes, asserted against the cpio we just built. # # This exists because of a bug that shipped silently and was only caught by booting: @@ -552,30 +614,36 @@ INITRAMFS_REQUIRED_APPLETS := sh mount umount losetup switch_root cttyhack setsi INITRAMFS_REQUIRED_BINS := usr/sbin/fsck.exfat INITRAMFS_FORBIDDEN_BINS := usr/sbin/dump.exfat usr/sbin/exfat2img usr/sbin/exfatlabel \ usr/sbin/mkfs.exfat usr/sbin/tune.exfat + +# What initramfs-verify checks, and with which user-mode emulator it parses +# /init. Defaults are the DE10's; de25-initramfs-verify overrides all three. +INITRAMFS_VERIFY_CPIO ?= $(INITRAMFS_CPIO) +INITRAMFS_VERIFY_BUSYBOX ?= $(INITRAMFS_OUTPUT_DIR)/target/bin/busybox +INITRAMFS_VERIFY_QEMU ?= qemu-arm .PHONY: initramfs-verify initramfs-verify: @rc=0; \ - applets=$$(cpio -t --quiet < $(INITRAMFS_CPIO)); \ + applets=$$(cpio -t --quiet < $(INITRAMFS_VERIFY_CPIO)); \ for a in $(INITRAMFS_REQUIRED_APPLETS); do \ echo "$$applets" | grep -qE "^(bin|sbin|usr/bin|usr/sbin)/$$a$$" || { \ echo "FATAL: /init needs '$$a' but it is not in the cpio." >&2; \ echo " Check its CONFIG_ symbol really exists in this BusyBox version —" >&2; \ echo " kconfig silently discards unknown symbols. See the header of" >&2; \ - echo " board/mister/de10nano/initramfs-busybox.config." >&2; \ + echo " board/mister/common/initramfs-busybox.config." >&2; \ rc=1; }; \ done; \ for b in $(INITRAMFS_REQUIRED_BINS); do \ echo "$$applets" | grep -qx "$$b" || { \ echo "FATAL: /init needs '$$b' but it is not in the cpio." >&2; \ echo " Is BR2_PACKAGE_EXFATPROGS still set in" >&2; \ - echo " configs/mister_initramfs_defconfig, and did the package move its" >&2; \ + echo " configs/fragments/initramfs-common.fragment, and did the package move its" >&2; \ echo " install path? See ADR 0026." >&2; \ rc=1; }; \ done; \ for b in $(INITRAMFS_FORBIDDEN_BINS); do \ echo "$$applets" | grep -qx "$$b" && { \ echo "FATAL: '$$b' is in the cpio and must not be." >&2; \ - echo " board/mister/de10nano/initramfs-post-build.sh is meant to delete it" >&2; \ + echo " board/mister/common/initramfs-post-build.sh is meant to delete it" >&2; \ echo " (476 KB of zImage for tools stage 1 cannot invoke). Did the" >&2; \ echo " post-build hook run? See ADR 0026." >&2; \ rc=1; }; \ @@ -585,25 +653,53 @@ initramfs-verify: echo "$$applets" | grep -qx 'dev/console' || { \ echo "FATAL: /dev/console is not in the cpio — /init would have no stdio and the" >&2; \ echo " rescue shell would be unreachable. Is device creation set to STATIC?" >&2; rc=1; }; \ - if command -v qemu-arm >/dev/null 2>&1; then \ - qemu-arm $(INITRAMFS_OUTPUT_DIR)/target/bin/busybox ash -n $(INITRAMFS_INIT) || { \ + if command -v $(INITRAMFS_VERIFY_QEMU) >/dev/null 2>&1; then \ + $(INITRAMFS_VERIFY_QEMU) $(INITRAMFS_VERIFY_BUSYBOX) ash -n $(INITRAMFS_INIT) || { \ echo "FATAL: the BusyBox ash we just built cannot even PARSE /init." >&2; \ echo " Usually a shell FEATURE that allnoconfig left off (e.g." >&2; \ echo " CONFIG_FEATURE_SH_MATH for \$$((arith))). shellcheck cannot see this:" >&2; \ echo " it checks the language, this checks the interpreter we ship." >&2; rc=1; }; \ else \ - echo "WARN: qemu-arm not installed; skipping the ash -n parse check of /init." >&2; \ + echo "WARN: $(INITRAMFS_VERIFY_QEMU) not installed; skipping the ash -n parse check of /init." >&2; \ fi; \ - [ $$rc -eq 0 ] && echo "==> initramfs-verify OK: $(words $(INITRAMFS_REQUIRED_APPLETS)) applets + $(words $(INITRAMFS_REQUIRED_BINS)) binary + $(words $(INITRAMFS_FORBIDDEN_BINS)) trimmed + /init + /dev/console + ash parses /init"; \ + [ $$rc -eq 0 ] && echo "==> initramfs-verify OK ($$(basename $$(dirname $$(dirname $(INITRAMFS_VERIFY_CPIO))))): $(words $(INITRAMFS_REQUIRED_APPLETS)) applets + $(words $(INITRAMFS_REQUIRED_BINS)) binary + $(words $(INITRAMFS_FORBIDDEN_BINS)) trimmed + /init + /dev/console + ash parses /init"; \ exit $$rc -$(INITRAMFS_OUTPUT_DIR)/.config: $(INITRAMFS_DEFCONFIG) | $(BR_STAMP) - @mkdir -p $(INITRAMFS_OUTPUT_DIR) - $(BR_MAKE_INITRAMFS) mister_initramfs_defconfig +# Stage-1 configs are generated from their fragment stacks by the same +# merge_config.sh + olddefconfig idiom as every other configuration here (see +# the "Config fragments" block above). UNLIKE the image configs, the stack's +# fragment files ARE prerequisites: a stage-1 config is generated, never +# iterated on with menuconfig, so regenerating it whenever a fragment is newer +# is the right default (the same behaviour the old standalone defconfig had). +# The empty rule for the fragment files, above, keeps the `%:` catch-all off +# them. $(BR_STAMP) and hostshim are order-only for the reasons given at +# $(OUTPUT_DIR)/.config. +$(INITRAMFS_OUTPUT_DIR)/.config: $(INITRAMFS_DE10NANO_STACK) | $(BR_STAMP) hostshim + $(call merge_fragments,$(INITRAMFS_OUTPUT_DIR),$(INITRAMFS_DE10NANO_STACK)) + $(BR_MAKE_INITRAMFS) olddefconfig + +$(DE25_INITRAMFS_OUTPUT_DIR)/.config: $(INITRAMFS_DE25NANO_STACK) | $(BR_STAMP) hostshim + $(call merge_fragments,$(DE25_INITRAMFS_OUTPUT_DIR),$(INITRAMFS_DE25NANO_STACK)) + $(BR_MAKE_DE25_INITRAMFS) olddefconfig + +# Force-regenerate either stage-1 configuration, mirroring de10nano-defconfig / +# de25nano-defconfig (the file prerequisites above make this mostly redundant, +# but a stale .config left by an interrupted merge does not look newer than +# anything, and this is the deliberate way out). +initramfs-defconfig: | $(BR_STAMP) hostshim + @rm -f $(INITRAMFS_OUTPUT_DIR)/.config + @$(MAKE) --no-print-directory $(INITRAMFS_OUTPUT_DIR)/.config + +de25-initramfs-defconfig: | $(BR_STAMP) hostshim + @rm -f $(DE25_INITRAMFS_OUTPUT_DIR)/.config + @$(MAKE) --no-print-directory $(DE25_INITRAMFS_OUTPUT_DIR)/.config initramfs-clean: rm -rf $(INITRAMFS_OUTPUT_DIR) +de25-initramfs-clean: + rm -rf $(DE25_INITRAMFS_OUTPUT_DIR) + # --- RT / Linux-7.2 beta kernel (docs/rt-beta-kernel.md) ---------------------- # Generates the variant .config by layering configs/mister_rt.fragment on the # KERNEL-ONLY base stack (common + de10nano + kernel-only fragments, @@ -914,15 +1010,24 @@ installer-clean: rm -rf $(INSTALLER_OUTPUT_DIR) # Escape hatches for iterating on stage 1 without hand-editing the checked-in -# configs. Both write to output-initramfs/; remember to fold the result back into -# configs/mister_initramfs_defconfig (`savedefconfig`) or into -# board/mister/de10nano/initramfs-busybox.config by hand. +# configs. They write to output-initramfs/ (or output-initramfs-de25/); +# remember to fold the result back into the initramfs-common / initramfs- +# fragment under configs/fragments/ +# or into board/mister/common/initramfs-busybox.config by hand -- and note that +# the NEXT `make initramfs` regenerates the .config from the fragments if one +# of them is newer (a stage-1 config is generated, not iterated on). initramfs-menuconfig: $(INITRAMFS_OUTPUT_DIR)/.config hostshim $(BR_MAKE_INITRAMFS) menuconfig initramfs-busybox-menuconfig: $(INITRAMFS_OUTPUT_DIR)/.config hostshim $(BR_MAKE_INITRAMFS) busybox-menuconfig +de25-initramfs-menuconfig: $(DE25_INITRAMFS_OUTPUT_DIR)/.config hostshim + $(BR_MAKE_DE25_INITRAMFS) menuconfig + +de25-initramfs-busybox-menuconfig: $(DE25_INITRAMFS_OUTPUT_DIR)/.config hostshim + $(BR_MAKE_DE25_INITRAMFS) busybox-menuconfig + # --- The assertion that stops a silent brick ---------------------------------- # docs/boot-chain.md §8, I1 and I2. The failure this guards against is not loud: a # kernel built with CONFIG_INITRAMFS_SOURCE="" boots perfectly, runs the kernel's @@ -1010,7 +1115,7 @@ help: @echo " it back into configs/fragments/ by hand)" @echo " make olddefconfig - non-interactively resolve config to defaults" @echo " make list-defconfigs - list built-in and external defconfigs" - @echo " (only the initramfs/installer ones remain)" + @echo " (only the installer one remains)" @echo " make buildroot-verify - download (if needed) + SHA-256-verify the" @echo " pinned Buildroot tarball, without unpacking" @echo " make buildroot-showsig - print upstream's GPG-signed release manifest" @@ -1022,7 +1127,8 @@ help: @echo " make clean - delete everything the build produced," @echo " KEEPING all .config files" @echo " make distclean - rm -rf output/, output-initramfs/, output-rt/," - @echo " output-installer/, output-de25/, the sdcard" + @echo " output-installer/, output-de25/," + @echo " output-initramfs-de25/, the sdcard" @echo " staging dirs and" @echo " the extra-modules overlay, .config included;" @echo " dl/ is kept (it is a shared cache —" @@ -1034,6 +1140,10 @@ help: @echo " make initramfs-busybox-menuconfig - BusyBox menuconfig for the stage-1 BusyBox" @echo " make initramfs-clean - rm -rf output-initramfs/" @echo " make check-initramfs - assert the built kernel really embeds the cpio" + @echo " make de25-initramfs - the SAME stage 1 built for aarch64 into" + @echo " output-initramfs-de25/ and verified (built and" + @echo " QEMU-proven; not embedded in the DE25 kernel yet)" + @echo " make de25-initramfs-clean - rm -rf output-initramfs-de25/" @echo "" @echo "RT / Linux-7.2 beta kernel (docs/rt-beta-kernel.md):" @echo " make rt - kernel-only build of the PREEMPT_RT variant into" @@ -1057,7 +1167,8 @@ help: @echo " the SD-card image; asserts images/Image, a .dtb," @echo " bl31.bin, u-boot.itb and sdcard-de25.img exist)." @echo " BARE DEVELOPER OS: no MiSTer binaries. Does NOT" - @echo " run 'initramfs' -- that cpio is armv7." + @echo " run 'initramfs' (armv7) nor 'de25-initramfs'" + @echo " (built + QEMU-proven, not embedded yet: D11)." @echo " make de25nano-defconfig - (re)generate output-de25/.config from its" @echo " fragment stack (common + de25nano)" @echo " make de25-menuconfig - Buildroot menuconfig for the DE25 config" @@ -1166,7 +1277,7 @@ buildroot-unpack: $(BR_STAMP) # --- Forward everything else into Buildroot ------------------------------------ # make menuconfig, make linux-menuconfig, make savedefconfig, make -# mister_initramfs_defconfig (Buildroot's own %_defconfig rule finds it under +# mister_installer_defconfig (Buildroot's own %_defconfig rule finds it under # this tree's configs/, since BR2_EXTERNAL is set above), etc. %: $(BR_STAMP) hostshim $(BR_MAKE) $@ diff --git a/README.md b/README.md index b8e1c8e1..ec560eef 100644 --- a/README.md +++ b/README.md @@ -609,9 +609,11 @@ configs/fragments/ stacks.mk (which fragments form which de10nano-image.fragment (the shipped image: hooks, ext4, packages) kernel-only.fragment (kernel-only base, shared by variants) de25nano.fragment (DE25-Nano developer OS, aarch64) + initramfs-common.fragment (stage-1 cpio: everything but the arch) + initramfs-de10nano.fragment / initramfs-de25nano.fragment + (stage-1 arch/ABI + headers, per board) golden.sha256 (resolved-config hashes CI asserts) configs/ mister_rt.fragment (PREEMPT_RT / 7.x delta) - mister_initramfs_defconfig (stage-1 cpio) mister_installer_defconfig (SD-card installer cpio) -> docs/buildroot-config.md has the rationale for every line board/mister/de10nano/ @@ -763,6 +765,7 @@ Two things that will bite you otherwise: | `make rt` | Kernel-only `PREEMPT_RT` build → `zImage_dtb-rt` + module overlay | | `make sdcard` | Full `sdcard.img(.xz)` — run **after** `make all`. The card carries no variant kernel, so `make rt` is not required first; if you *do* build RT, run it before `make all` so its modules land in the image | | `make initramfs` | Stage-1 cpio only, and print its size | +| `make de25-initramfs` | The same stage 1 built for aarch64 (`output-initramfs-de25/`), verified; boot-test it with `scripts/test-initramfs.sh --board de25nano` | | `make menuconfig` / `linux-menuconfig` | Interactive Buildroot / kernel config | | `make savedefconfig` | Write the config back to the defconfig (**always** do this after editing) | | `make buildroot-verify` | Download + SHA-256-verify the pinned Buildroot tarball | @@ -802,7 +805,8 @@ broke and why, without grepping): - the image-contract checks — `check-zimage-dtb.sh`, `check-linux-img.sh`, `check-size-budget.sh` - the structural initramfs checks, plus a **full QEMU boot test of the initramfs `/init`** - (booted six times, across the failure paths) + (eight cases, across the failure paths — and the same eight again on + `qemu-system-aarch64` for the aarch64 build of the same `/init`, when it has been built) - an ABI smoke test running the **stock `MiSTer` binary** under `qemu-user` against the built rootfs: dynamic linking must resolve clean, and it must die at FPGA access and not one instruction earlier diff --git a/board/mister/de10nano/initramfs-busybox.config b/board/mister/common/initramfs-busybox.config similarity index 100% rename from board/mister/de10nano/initramfs-busybox.config rename to board/mister/common/initramfs-busybox.config diff --git a/board/mister/de10nano/initramfs-overlay/init b/board/mister/common/initramfs-overlay/init similarity index 100% rename from board/mister/de10nano/initramfs-overlay/init rename to board/mister/common/initramfs-overlay/init diff --git a/board/mister/de10nano/initramfs-post-build.sh b/board/mister/common/initramfs-post-build.sh similarity index 94% rename from board/mister/de10nano/initramfs-post-build.sh rename to board/mister/common/initramfs-post-build.sh index 368d02ac..1de33549 100755 --- a/board/mister/de10nano/initramfs-post-build.sh +++ b/board/mister/common/initramfs-post-build.sh @@ -4,7 +4,7 @@ # # Stage-1 (initramfs) post-build hook. Runs after the cpio's target tree is # assembled and before the cpio is generated (BR2_ROOTFS_POST_BUILD_SCRIPT in -# configs/mister_initramfs_defconfig). Reproducible: no timestamps, no +# configs/fragments/initramfs-common.fragment). Reproducible: no timestamps, no # randomness, no network -- it only deletes files (A9). # # WHY THIS EXISTS -- exfatprogs ships six binaries and /init calls one. @@ -55,7 +55,7 @@ done # it exists to catch. Keep the two in step if either ever moves. [ -x "${TARGET_DIR}/usr/sbin/fsck.exfat" ] || { echo "FATAL: initramfs-post-build.sh: no ${TARGET_DIR}/usr/sbin/fsck.exfat." >&2 - echo " BR2_PACKAGE_EXFATPROGS is set in configs/mister_initramfs_defconfig," >&2 + echo " BR2_PACKAGE_EXFATPROGS is set in configs/fragments/initramfs-common.fragment," >&2 echo " so the package moved its install path. The initramfs /init hardcodes" >&2 echo " FSCK_EXFAT=/usr/sbin/fsck.exfat; update both together. See ADR 0026." >&2 exit 1 diff --git a/board/mister/de10nano/installer-busybox.config b/board/mister/de10nano/installer-busybox.config index 3f8afed7..30897ee5 100644 --- a/board/mister/de10nano/installer-busybox.config +++ b/board/mister/de10nano/installer-busybox.config @@ -138,7 +138,7 @@ # ############################################################################ # # -# Hand-derived from board/mister/de10nano/initramfs-busybox.config +# Hand-derived from board/mister/common/initramfs-busybox.config # (see header above for the diff and rationale); NOT regenerated verbatim by # `make busybox-update-config` — do that, then re-apply this file's five # additions, after any BusyBox version bump. diff --git a/board/mister/de10nano/installer-overlay/init b/board/mister/de10nano/installer-overlay/init index a8983509..a568af78 100755 --- a/board/mister/de10nano/installer-overlay/init +++ b/board/mister/de10nano/installer-overlay/init @@ -73,7 +73,7 @@ # copy the base and SKIP the cores with a loud message rather than OOM-bricking. # update_all.sh on the installed card fetches cores anyway. # * DEFENSIVE LIKE THE STAGE-1 init. Same contract as -# board/mister/de10nano/initramfs-overlay/init: early proc/sys/dev mounts, parse +# board/mister/common/initramfs-overlay/init: early proc/sys/dev mounts, parse # the cmdline (never hardcode the device), and on ANY fatal error print a banner # and drop to a RESPAWNING rescue shell. PID 1 must NEVER exit (that is an instant # kernel panic). A half-reformatted card must give a diagnosable message, not a diff --git a/board/mister/de10nano/linux-patches-beta/0031-exfat-samsung-symlinks.patch b/board/mister/de10nano/linux-patches-beta/0031-exfat-samsung-symlinks.patch deleted file mode 120000 index 25908719..00000000 --- a/board/mister/de10nano/linux-patches-beta/0031-exfat-samsung-symlinks.patch +++ /dev/null @@ -1 +0,0 @@ -../linux-patches/0031-exfat-samsung-symlinks.patch \ No newline at end of file diff --git a/board/mister/de10nano/linux-patches-beta/0031-exfat-samsung-symlinks.patch b/board/mister/de10nano/linux-patches-beta/0031-exfat-samsung-symlinks.patch new file mode 100644 index 00000000..78fd2730 --- /dev/null +++ b/board/mister/de10nano/linux-patches-beta/0031-exfat-samsung-symlinks.patch @@ -0,0 +1,524 @@ +From df35bdb271325891b3a03c75097541d5b76fbeb6 Mon Sep 17 00:00:00 2001 +From: Michael C. Ferguson +Date: Mon, 14 Jul 2026 11:20:00 -0500 +Subject: [PATCH 1/1] exfat: Samsung-compatible symlinks on mainline exfat + +MiSTer's arcade organizer builds its entire _Organized tree out of +symlinks on the exFAT data partition, so ADR 0010's "symlinks appear +unused" finding is wrong in the way its own §Rationale warned it might +be (n=1 evidence). Mainline exfat has no symlink support at all; this +adds it, byte-compatible with the on-disk format of the out-of-tree +Samsung driver that every stock MiSTer kernel has shipped, so symlink +trees already on users' cards keep resolving: + + * a symlink is an ordinary file dentry set whose attributes carry + the DOS "system" bit (0x0004, EXFAT_ATTR_SYMLINK), and whose file + data is the target path, NOT NUL-terminated + (i_size == strlen(target)); + * 0x0040 (EXFAT_ATTR_SYMLINK_OLD), the marker used by even older + Samsung releases, is honoured on read and upgraded to 0x0004 on + the next attr writeback, never written on create. + +Unlike the Samsung driver (a parallel 20-file filesystem +implementation), this reuses the vanilla infrastructure: creation goes +through exfat_add_entry() + page_symlink() (which writes exactly len-1 +bytes -- the Samsung format falls straight out), and readback is +page_get_link(), whose nd_terminate_link() handles the on-disk string +having no NUL. The symlink inode needs inode_nohighmem() because +page_get_link() uses page_address() directly. + +Locking: exfat_get_block() takes sbi->s_lock, so exfat_symlink() must +drop s_lock before page_symlink(). If the data write fails, the +just-created dentry set is removed again (same incantation as +exfat_unlink()); only once that removal has durably reached the disk +is nlink cleared so eviction frees the allocated cluster -- on a +removal failure the entry is left alive instead, because a live +on-disk entry must never point at freed clusters. On dirsync mounts +(MiSTer mounts /media/fat with sync,dirsync) the target write is +flushed with write_inode_now() before the symlink is instantiated. + +Four sharp edges found in review, all handled here: + + * ei->type stays TYPE_FILE: TYPE_SYMLINK exists only as a creation- + time dispatch code for exfat_add_entry()/exfat_set_entry_type(). + An in-core type of its own would defeat __exfat_truncate()'s type + guard, leaking the target's cluster on every same-mount + create+delete (the arcade organizer's reorganize pattern), and + fsck.exfat does not even flag the orphaned bitmap bits. + * The SET_ATTRIBUTES ioctl pins EXFAT_ATTR_SYMLINK to its current + state on non-directories: the system bit now encodes S_IFMT, and + flipping it on a live inode would hand setattr an S_IFLNK/S_IFREG + type swap the VFS cannot express. + * readdir derives d_type with fs_umode_to_dtype(exfat_make_mode()), + the same classifier lstat uses, so getdents and stat can never + disagree about linkness (GNU find trusts d_type -- a DT_REG lie + here is exactly why ADR 0010's evidence scan missed the symlinks). + * The one attr predicate lives in EXFAT_ATTR_SYMLINK_ANY, consumed + by both exfat_make_mode() and exfat_fill_inode(). + +Semantics concession, inherited from stock: any non-directory entry +carrying attribute 0x0004 or 0x0040 is presented as a symlink, even if +it is a genuine Windows "system" file (e.g. IndexerVolumeGuid inside +System Volume Information). Stock MiSTer kernels have behaved this way +since 2021; matching that behaviour is the point of this patch. + +Scope concession: exFAT only. The Samsung driver also mounted +FAT12/16/32 and gave them the same symlinks; mainline vfat has no +symlink support and does not get any here. A FAT32-formatted MiSTer +card with an organized arcade tree still loses its symlinks (ADR 0019). + +Provenance +---------- +Origin: df35bdb271325891b3a03c75097541d5b76fbeb6 + "Add exFAT with symlinks support." + MiSTer-devel/Linux-Kernel_MiSTer, 2021-08-30, against v5.15. + https://github.com/MiSTer-devel/Linux-Kernel_MiSTer/commit/df35bdb271325891b3a03c75097541d5b76fbeb6 + Origin of the ON-DISK FORMAT only (exfat_super.c:676 + exfat_symlink(), exfat_core.c:2629 exfat_set_entry_type() + TYPE_SYMLINK, exfat_api.h:65 ATTR_SYMLINK/ATTR_SYMLINK_OLD). + The code below is a fresh implementation against mainline + fs/exfat, not a forward-port of the Samsung driver. +Author: Michael C. Ferguson (new code); format by Samsung, carried + into MiSTer by Sorgelig . +Upstream: No, and not upstreamable as-is: it overloads a real FAT + attribute bit that Windows assigns to ordinary files, so + upstream would (reasonably) reject the ambiguity. Carried + indefinitely; fs/exfat churn is low (6 files, all touching + stable functions). Disposition "carry" + (docs/patch-provenance.md §3.8 class G, option (c); + decision reversal recorded in ADR 0019, which amends + ADR 0010). +Forward-port: written against 6.18.38 directly (2026-07-14); adapted + 2026-07-25 to serve BOTH kernel series from this one file + -- see below. + +Signed-off-by: Michael C. Ferguson +[Two changes on 2026-07-25 which, together, make this ONE file the whole + story for both the 6.18 stable series and the 7.x beta series. There is + no longer a re-anchored copy: ../linux-patches-beta/0031-exfat-samsung- + symlinks.patch is a SYMLINK to this file, like the other shared entries. + Verified applying at -F0 to pristine 6.18.40 AND pristine 7.2-rc4, + 12/12 hunks across 6 files, on both. + + 1. 6.18.40 API adaptation. The stable series backported upstream's + benign-secondary-entry preservation rework, which gave + exfat_remove_entries() a 4th arg (bool free_benign). The symlink + error-path call passes true, matching upstream's own unlink/rmdir + idiom at the same ES_IDX_FILE site. 7.x already had this, so the + adaptation closed the last divergence in ADDED LINES. + + 2. fs/exfat/file.c re-anchor, closing the last divergence in CONTEXT. + exfat_symlink_inode_operations is now defined BEFORE + exfat_file_inode_operations instead of after it. Appending after was + unportable: 7.x both ADDED a .fileattr_get member and RETABBED the + member alignment (".setattr" + spaces became ".setattr" + a tab), so + the two series shared no trailing context at all -- not one line. + Prepending anchors instead on a blank line plus the line + "const struct inode_operations exfat_file_inode_operations = {", + byte-identical in both trees. Definition order is immaterial (both + are file-scope, and exfat_fs.h declares both), so this is a pure + re-anchor: post-patch source differs from the old form ONLY in the + order of those two definitions -- verified by diffing the patched + trees, on both kernels. + + That hunk deliberately carries ONE line of context per side instead of + the usual three, and MUST NOT be "helpfully" regenerated at -U3: three + lines reaches into the retabbed struct body on 7.x and into the + .splice_write/.setlease tail of exfat_file_operations on 6.18, both of + which differ between the series. The single trailing context line + occurs exactly once in file.c, so the match stays unambiguous; a future + upstream edit there fails CLOSED at Buildroot's `patch -F0` -- the same + failure mode as before, just reachable from a smaller perturbation. + + NOTE: on the stable side this patch now requires >= 6.18.40; it will + not compile against 6.18.39 or earlier. That is fine -- it tracks the + pinned BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE, not a range.] +[re-anchored for the 7.x beta series (2026-09-06): a real 7.x API delta, +not a context drift, so the shared 6.18 patch stays as it is and this copy +exists (the 2026-07-25 note in linux-patches-beta/series still holds for +context-only drift -- this is the other kind). 7.x exfat moved its data +path to iomap (fs/exfat/iomap.c) and exfat_aops has no write_begin / +write_end any more; page_symlink() in fs/namei.c calls aops->write_begin +unconditionally, so the 6.18 form of exfat_symlink() Oopses on the first +symlink created (pc: 0x0, lr: page_symlink+0x178, caught by +scripts/test-initramfs.sh --board de25nano's `symlink` case -- the first +thing to ever EXECUTE this patch on 7.x; it applies and compiles fine). +Delta vs the shared patch is confined to fs/exfat/namei.c: a new +exfat_symlink_write_target() allocates the clusters with +exfat_map_cluster() (7.x signature) under s_lock, fills the sectors +through buffer heads, flushes the range with sync_blockdev_range() so +page_get_link()'s iomap bio read sees it, and does the valid_size / +zeroed_size / i_size bookkeeping exfat_write_iomap_end() and the VFS +write path would have done; exfat_symlink() calls it where it called +page_symlink(). Everything else (on-disk format, attr bits, get_link, +readdir, ioctl pinning, error path) is byte-identical to the shared patch. +Diff regenerated at -U3 against pristine 7.2.3 -- the shared patch's +-U1 file.c hunk was only ever needed to match BOTH trees, and this copy +matches one. Verified: applies at -F0 to pristine 7.2.3, 12/12 hunks; +compiles for arm (the RT config) and arm64 (the DE25 config); the +aarch64 QEMU `symlink` case passes hot+cold with the cluster-leak +tripwire and fsck-clean.] +--- + +diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c +--- a/fs/exfat/dir.c ++++ b/fs/exfat/dir.c +@@ -256,8 +256,10 @@ + } + + mutex_unlock(&EXFAT_SB(sb)->s_lock); ++ /* one classifier for readdir and lstat: DT_LNK iff st_mode is S_IFLNK */ + if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, +- (de.attr & EXFAT_ATTR_SUBDIR) ? DT_DIR : DT_REG)) ++ fs_umode_to_dtype(exfat_make_mode(EXFAT_SB(sb), ++ de.attr, 0)))) + goto out; + ctx->pos = cpos; + goto get_new; +@@ -385,6 +387,10 @@ + } else if (type == TYPE_FILE) { + ep->type = EXFAT_FILE; + ep->dentry.file.attr = cpu_to_le16(EXFAT_ATTR_ARCHIVE); ++ } else if (type == TYPE_SYMLINK) { ++ ep->type = EXFAT_FILE; ++ ep->dentry.file.attr = cpu_to_le16(EXFAT_ATTR_ARCHIVE | ++ EXFAT_ATTR_SYMLINK); + } + } + +diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h +--- a/fs/exfat/exfat_fs.h ++++ b/fs/exfat/exfat_fs.h +@@ -62,6 +62,7 @@ + #define TYPE_VOLUME 0x0103 + #define TYPE_DIR 0x0104 + #define TYPE_FILE 0x011F ++#define TYPE_SYMLINK 0x015F + #define TYPE_CRITICAL_SEC 0x0200 + #define TYPE_STREAM 0x0201 + #define TYPE_EXTEND 0x0202 +@@ -349,6 +350,9 @@ + if (attr & EXFAT_ATTR_SUBDIR) + return (mode & ~sbi->options.fs_dmask) | S_IFDIR; + ++ if (attr & EXFAT_ATTR_SYMLINK_ANY) ++ return (mode & ~sbi->options.fs_fmask) | S_IFLNK; ++ + return (mode & ~sbi->options.fs_fmask) | S_IFREG; + } + +@@ -644,6 +648,7 @@ + + /* inode.c */ + extern const struct inode_operations exfat_file_inode_operations; ++extern const struct inode_operations exfat_symlink_inode_operations; + void exfat_sync_inode(struct inode *inode); + struct inode *exfat_build_inode(struct super_block *sb, + struct exfat_dir_entry *info, loff_t i_pos); +diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h +--- a/fs/exfat/exfat_raw.h ++++ b/fs/exfat/exfat_raw.h +@@ -72,6 +72,18 @@ + #define EXFAT_ATTR_SUBDIR 0x0010 + #define EXFAT_ATTR_ARCHIVE 0x0020 + ++/* ++ * Samsung's out-of-tree exfat driver (exfat-nofuse lineage, carried by ++ * MiSTer's kernel fork) overloads the DOS "system" attribute to mark a ++ * symlink whose target path is stored as the file's data, not ++ * NUL-terminated (i_size == strlen(target)). 0x0040 is a reserved ++ * attribute bit that even older Samsung releases used for the same ++ * purpose; it is honoured on read but never written. ++ */ ++#define EXFAT_ATTR_SYMLINK EXFAT_ATTR_SYSTEM ++#define EXFAT_ATTR_SYMLINK_OLD 0x0040 ++#define EXFAT_ATTR_SYMLINK_ANY (EXFAT_ATTR_SYMLINK | EXFAT_ATTR_SYMLINK_OLD) ++ + #define EXFAT_ATTR_RWMASK (EXFAT_ATTR_HIDDEN | EXFAT_ATTR_SYSTEM | \ + EXFAT_ATTR_VOLUME | EXFAT_ATTR_SUBDIR | \ + EXFAT_ATTR_ARCHIVE) +diff --git a/fs/exfat/file.c b/fs/exfat/file.c +--- a/fs/exfat/file.c ++++ b/fs/exfat/file.c +@@ -469,6 +469,16 @@ + EXFAT_ATTR_ARCHIVE); + attr |= (is_dir ? EXFAT_ATTR_SUBDIR : 0); + ++ /* ++ * The system bit doubles as the symlink marker (EXFAT_ATTR_SYMLINK), ++ * so flipping it here would change the inode's S_IFMT under the VFS ++ * (exfat_make_mode() below would hand setattr an S_IFLNK/S_IFREG ++ * mode swap on a live inode). Pin it to its current state. ++ */ ++ if (!is_dir) ++ attr = (attr & ~EXFAT_ATTR_SYMLINK) | ++ (oldattr & EXFAT_ATTR_SYMLINK); ++ + /* Equivalent to a chmod() */ + ia.ia_valid = ATTR_MODE | ATTR_CTIME; + ia.ia_ctime = current_time(inode); +@@ -972,6 +982,12 @@ + .setlease = generic_setlease, + }; + ++const struct inode_operations exfat_symlink_inode_operations = { ++ .get_link = page_get_link, ++ .setattr = exfat_setattr, ++ .getattr = exfat_getattr, ++}; ++ + const struct inode_operations exfat_file_inode_operations = { + .setattr = exfat_setattr, + .getattr = exfat_getattr, +diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c +--- a/fs/exfat/inode.c ++++ b/fs/exfat/inode.c +@@ -393,6 +393,24 @@ + inode->i_op = &exfat_dir_inode_operations; + inode->i_fop = &exfat_dir_operations; + set_nlink(inode, info->num_subdirs); ++ } else if (info->attr & EXFAT_ATTR_SYMLINK_ANY) { ++ /* ++ * Samsung-style symlink: target path stored as file data. ++ * EXFAT_ATTR_SYMLINK_OLD is not in EXFAT_ATTR_RWMASK, so a ++ * legacy-marked link would lose its marker on the next attr ++ * writeback; OR-ing in EXFAT_ATTR_SYMLINK keeps it a link if ++ * that ever happens. This does not itself dirty the inode — ++ * the on-disk marker only changes on a writeback triggered ++ * for some other reason. ++ */ ++ info->attr |= EXFAT_ATTR_SYMLINK; ++ inode->i_generation |= 1; ++ inode->i_mode = exfat_make_mode(sbi, info->attr, 0777); ++ inode->i_op = &exfat_symlink_inode_operations; ++ inode->i_mapping->a_ops = &exfat_aops; ++ inode->i_mapping->nrpages = 0; ++ /* page_get_link() dereferences page_address() directly */ ++ inode_nohighmem(inode); + } else { /* regular file */ + inode->i_generation |= 1; + inode->i_mode = exfat_make_mode(sbi, info->attr, 0777); +diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -514,8 +514,19 @@ + info->flags = ALLOC_NO_FAT_CHAIN; + info->type = type; + +- if (type == TYPE_FILE) { ++ if (type == TYPE_FILE || type == TYPE_SYMLINK) { + info->attr = EXFAT_ATTR_ARCHIVE; ++ if (type == TYPE_SYMLINK) { ++ info->attr |= EXFAT_ATTR_SYMLINK; ++ /* ++ * In-core, a symlink is a TYPE_FILE inode told apart ++ * by its attr bit, exactly as when re-read from disk ++ * (exfat_get_entry_type() has no symlink case). ++ * __exfat_truncate()'s type guard relies on this to ++ * free the target's cluster on eviction. ++ */ ++ info->type = TYPE_FILE; ++ } + info->start_clu = EXFAT_EOF_CLUSTER; + info->size = 0; + info->num_subdirs = 0; +@@ -581,6 +592,190 @@ + return err; + } + ++/* ++ * Write a new symlink's target path as the inode's file data. ++ * ++ * [7.x re-anchor] The 6.18 version of this patch hands the inode to the ++ * VFS's page_symlink(), which writes through a_ops->write_begin / ++ * write_end. 7.x exfat is iomap-based and has neither (fs/exfat/inode.c, ++ * exfat_aops), so page_symlink() jumps through a NULL pointer -- an Oops on ++ * the first symlink created. There is also no in-kernel way to drive ++ * iomap_file_buffered_write() without a struct file. So the target is ++ * written the way exfat writes its own metadata: allocate the clusters ++ * under s_lock, fill the sectors through buffer heads, and flush the range ++ * before anyone can read it back -- page_get_link() reads via iomap bios ++ * straight from the device, which never sees a dirty bdev buffer. Then do ++ * the bookkeeping exfat_write_iomap_end() would have done (valid_size, ++ * zeroed_size) plus the i_size update the VFS write path would have done. ++ * ++ * Runs on a fresh, unhashed-to-dcache inode with no page cache, so there ++ * is nothing to keep coherent and no reader to race. ++ */ ++static int exfat_symlink_write_target(struct inode *inode, const char *symname, ++ unsigned int len) ++{ ++ struct super_block *sb = inode->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct exfat_inode_info *ei = EXFAT_I(inode); ++ unsigned int clu_off = 0, need = exfat_bytes_to_cluster_round_up(sbi, len); ++ unsigned int written = 0; ++ int err; ++ ++ if (!len || len > PATH_MAX) ++ return -ENAMETOOLONG; ++ ++ while (written < len) { ++ unsigned int clu, count = need - clu_off; ++ sector_t blknr, last_blknr, i; ++ bool balloc = false; ++ ++ mutex_lock(&sbi->s_lock); ++ err = exfat_map_cluster(inode, clu_off, &clu, &count, 1, &balloc); ++ mutex_unlock(&sbi->s_lock); ++ if (err) ++ return err; ++ if (clu == EXFAT_EOF_CLUSTER || !count) { ++ exfat_fs_error(sb, "symlink: no cluster mapped at %u", clu_off); ++ return -EIO; ++ } ++ ++ /* one contiguous run of `count` clusters, sector by sector */ ++ blknr = exfat_cluster_to_sector(sbi, clu); ++ last_blknr = blknr + ((sector_t)count << sbi->sect_per_clus_bits); ++ if (last_blknr > sbi->num_sectors && sbi->num_sectors > 0) { ++ exfat_fs_error(sb, "symlink: out of range(sect:%llu len:%u)", ++ (unsigned long long)blknr, count); ++ return -EIO; ++ } ++ for (i = blknr; i < last_blknr && written < len; i++) { ++ unsigned int n = min_t(unsigned int, len - written, ++ sb->s_blocksize); ++ struct buffer_head *bh = sb_getblk(sb, i); ++ ++ if (!bh) ++ return -ENOMEM; ++ lock_buffer(bh); ++ memcpy(bh->b_data, symname + written, n); ++ if (n < sb->s_blocksize) ++ memset(bh->b_data + n, 0, sb->s_blocksize - n); ++ set_buffer_uptodate(bh); ++ unlock_buffer(bh); ++ mark_buffer_dirty(bh); ++ brelse(bh); ++ written += n; ++ } ++ /* the data must be on the device before the first get_link() */ ++ err = sync_blockdev_range(sb->s_bdev, ++ exfat_block_to_bytes(sb, blknr), ++ exfat_block_to_bytes(sb, i) - 1); ++ if (err) ++ return err; ++ clu_off += count; ++ } ++ ++ i_size_write(inode, len); ++ ei->valid_size = len; ++ ei->zeroed_size = len; ++ mark_inode_dirty(inode); ++ return 0; ++} ++ ++/* ++ * Samsung-style symlink (MiSTer carried patch): an ordinary file dentry ++ * set whose attributes carry EXFAT_ATTR_SYMLINK (the DOS "system" bit) ++ * and whose data is the target path, not NUL-terminated ++ * (i_size == strlen(target)). This is the on-disk format of the ++ * out-of-tree Samsung exfat driver that MiSTer's stock kernel shipped; ++ * cards written by either driver are interchangeable. ++ */ ++static int exfat_symlink(struct mnt_idmap *idmap, struct inode *dir, ++ struct dentry *dentry, const char *symname) ++{ ++ /* the entry-creation prologue mirrors exfat_create(); keep in sync */ ++ struct super_block *sb = dir->i_sb; ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ struct inode *inode; ++ struct exfat_dir_entry info; ++ struct exfat_entry_set_cache es; ++ loff_t i_pos; ++ loff_t size = i_size_read(dir); ++ int len = strlen(symname); ++ int err; ++ ++ if (unlikely(exfat_forced_shutdown(sb))) ++ return -EIO; ++ ++ mutex_lock(&sbi->s_lock); ++ exfat_set_volume_dirty(sb); ++ err = exfat_add_entry(dir, dentry->d_name.name, TYPE_SYMLINK, &info); ++ if (err) ++ goto unlock; ++ ++ inode_inc_iversion(dir); ++ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); ++ if (IS_DIRSYNC(dir) && size != i_size_read(dir)) ++ exfat_sync_inode(dir); ++ else ++ mark_inode_dirty(dir); ++ ++ i_pos = exfat_make_i_pos(&info); ++ inode = exfat_build_inode(sb, &info, i_pos); ++ err = PTR_ERR_OR_ZERO(inode); ++ if (err) ++ goto unlock; ++ ++ inode_inc_iversion(inode); ++ EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode); ++ exfat_truncate_inode_atime(inode); ++ ++ /* ++ * exfat_map_cluster() wants s_lock and the helper takes it itself, ++ * so the target is written with the lock dropped. Exactly len ++ * bytes: the path without its NUL, which is the Samsung format ++ * (i_size == strlen). [7.x re-anchor: was page_symlink(); see the ++ * helper's comment for why that cannot work on iomap exfat.] ++ */ ++ mutex_unlock(&sbi->s_lock); ++ err = exfat_symlink_write_target(inode, symname, len); ++ if (!err && IS_DIRSYNC(dir)) ++ err = write_inode_now(inode, 1); ++ if (err) ++ goto remove_entry; ++ ++ d_instantiate(dentry, inode); ++ return 0; ++ ++remove_entry: ++ mutex_lock(&sbi->s_lock); ++ exfat_set_volume_dirty(sb); ++ if (!exfat_get_dentry_set_by_ei(&es, sb, EXFAT_I(inode))) { ++ exfat_remove_entries(inode, &es, ES_IDX_FILE, true); ++ if (!exfat_put_dentry_set(&es, IS_DIRSYNC(dir))) { ++ EXFAT_I(inode)->dir.dir = DIR_DELETED; ++ /* ++ * Entry removal reached the disk, so eviction may ++ * free the clusters the target write allocated. On ++ * any removal failure keep nlink instead: a live ++ * on-disk entry must never point at freed clusters ++ * (same asymmetry exfat_unlink() accepts). ++ */ ++ clear_nlink(inode); ++ } ++ } ++ inode_inc_iversion(dir); ++ simple_inode_init_ts(dir); ++ exfat_truncate_inode_atime(dir); ++ mark_inode_dirty(dir); ++ exfat_unhash_inode(inode); ++ mutex_unlock(&sbi->s_lock); ++ iput(inode); ++ return err; ++ ++unlock: ++ mutex_unlock(&sbi->s_lock); ++ return err; ++} ++ + /* lookup a file */ + static int exfat_find(struct inode *dir, const struct qstr *qname, + struct exfat_dir_entry *info) +@@ -1327,6 +1522,7 @@ + .create = exfat_create, + .lookup = exfat_lookup, + .unlink = exfat_unlink, ++ .symlink = exfat_symlink, + .mkdir = exfat_mkdir, + .rmdir = exfat_rmdir, + .rename = exfat_rename, + diff --git a/board/mister/de10nano/linux-patches-beta/series b/board/mister/de10nano/linux-patches-beta/series index 93c9da3b..a6203f8a 100644 --- a/board/mister/de10nano/linux-patches-beta/series +++ b/board/mister/de10nano/linux-patches-beta/series @@ -88,7 +88,7 @@ # hole reserved. Applying order is unaffected — the shared dir is globbed and # sorted, so 0047 simply applies last there, and it is not in this series at all. # -# NOT-symlink exceptions: four of the shared-patch entries are REAL FILES +# NOT-symlink exceptions: five of the shared-patch entries are REAL FILES # re-anchored for 7.x (0043-0046 above are real files too, but they are # beta-local originals, not re-anchors of anything) — # the shared 6.18 patches are deliberately untouched (parity with stock). @@ -97,16 +97,24 @@ # each in lockstep with its ../linux-patches/ original (every copy carries # a bracketed re-anchor note). # -# 0031 WAS a fifth; it is a symlink again as of 2026-07-25. Worth reading -# before adding a sixth, because the same trick may apply: rather than -# re-anchoring the 7.x copy around the drifted context, the SHARED patch -# was moved to anchor somewhere both trees agree (it now defines -# exfat_symlink_inode_operations *before* exfat_file_inode_operations -# instead of after, because 7.x had both added a struct member and -# retabbed the alignment, leaving no common trailing context). Same -# resulting source, one file. A re-anchored copy is the fallback, not the -# first move — ask first whether the shared patch can be anchored on -# something stabler: +# 0031 has been both. It became a symlink on 2026-07-25 by the trick worth +# reading about before adding a sixth: rather than re-anchoring the 7.x +# copy around the drifted context, the SHARED patch was moved to anchor +# somewhere both trees agree (it defines exfat_symlink_inode_operations +# *before* exfat_file_inode_operations instead of after, because 7.x had +# both added a struct member and retabbed the alignment, leaving no common +# trailing context). Same resulting source, one file. A re-anchored copy is +# the fallback, not the first move — ask first whether the shared patch can +# be anchored on something stabler. It is a REAL FILE again as of +# 2026-09-06, for the OTHER reason: a real 7.x API delta that no anchoring +# can paper over. 7.x exfat is iomap-based and has no write_begin/write_end, +# so the shared patch's page_symlink() call is a NULL function call -- an +# Oops on the first `ln -s` -- on every 7.x kernel. It applied and compiled +# fine; the DE25's aarch64 QEMU initramfs leg (scripts/test-initramfs.sh +# --board de25nano, `symlink` case) was the first thing to EXECUTE it on 7.x +# and is its acceptance test. The copy differs from the shared patch only in +# fs/exfat/namei.c (a buffer-head target writer replaces page_symlink); +# the DE25 series links to THIS copy. The list: # 0001-fbdev-add-MiSTer_fb-driver — one-line delta: 7.x removed # ; fbcon_update_vcs() now comes from the fbdev-core # header ("core/fbcon.h"). diff --git a/board/mister/de10nano/rootfs-overlay/etc/ssh/sshd_config b/board/mister/de10nano/rootfs-overlay/etc/ssh/sshd_config index 2f7dda56..91252738 100644 --- a/board/mister/de10nano/rootfs-overlay/etc/ssh/sshd_config +++ b/board/mister/de10nano/rootfs-overlay/etc/ssh/sshd_config @@ -75,7 +75,7 @@ PermitRootLogin yes # # StrictModes STAYS ON (default yes) and this path satisfies it: the initramfs # mounts the FAT partition with fmask=0022,dmask=0022 and no uid/gid options -# (board/mister/de10nano/initramfs-overlay/init:27), so the file lands +# (board/mister/common/initramfs-overlay/init:27), so the file lands # root-owned 0755 and its parents 0755 -- owner-writable only, which is what # sshd requires. Those mount options are OURS and fixed, not user-tunable, so # this cannot be silently invalidated by a differently-mounted card. Verified diff --git a/board/mister/de25nano/linux-patches/0031-exfat-samsung-symlinks.patch b/board/mister/de25nano/linux-patches/0031-exfat-samsung-symlinks.patch index e9c87275..63be9f17 120000 --- a/board/mister/de25nano/linux-patches/0031-exfat-samsung-symlinks.patch +++ b/board/mister/de25nano/linux-patches/0031-exfat-samsung-symlinks.patch @@ -1 +1 @@ -../../de10nano/linux-patches/0031-exfat-samsung-symlinks.patch \ No newline at end of file +../../de10nano/linux-patches-beta/0031-exfat-samsung-symlinks.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/README.md b/board/mister/de25nano/linux-patches/README.md index 3a8e1655..512b2a52 100644 --- a/board/mister/de25nano/linux-patches/README.md +++ b/board/mister/de25nano/linux-patches/README.md @@ -58,7 +58,7 @@ Source of the verdicts: [`docs/de25-patch-portability.md`](../../../../docs/de25 | 22 | `0028-dwc2-fix-unaligned-in-split` | portable-as-is / shared | **included** | Real generic dwc2 bug; Agilex 5 also declares `snps,dwc2`. | | 23 | `0029-leds-gpio-brightness-hw-changed` | portable-as-is / shared | **included** | Generic `leds-gpio`/LED-class change; inert until a DTS wires an activity LED. | | 24 | `0030-i2c-designware-quiet-timeout` | portable-as-is / shared | **included (beta copy)** | Generic Synopsys DW I2C, which Agilex 5's HPS I2C also uses. Beta copy is re-anchored on 7.x's renamed `i2c_dw_init()`; the changed line is byte-identical. | -| 25 | `0031-exfat-samsung-symlinks` | portable-as-is / shared | **included** | Filesystem-format code; `inode_nohighmem()` is a no-op without HIGHMEM (normal on arm64). | +| 25 | `0031-exfat-samsung-symlinks` | portable-as-is / shared | **included (beta copy)** | Filesystem-format code; `inode_nohighmem()` is a no-op without HIGHMEM (normal on arm64). **Links to the 7.x re-anchored beta copy since 2026-09-06**, the day `scripts/test-initramfs.sh --board de25nano` (the first thing ever to run this patch on a 7.x kernel) found the shared 6.18 form Oopses on symlink CREATION: `page_symlink()` calls `a_ops->write_begin`, and 7.x exFAT is iomap-based with neither `write_begin` nor `write_end`. It applied at `-F0` and compiled, which is why the triage above missed it. The beta copy replaces `page_symlink()` with a buffer-head target writer (see its bracketed note); the aarch64 `symlink` case passes on it — hot+cold, `DT_LNK`, the cluster-leak tripwire, fsck-clean (ADR 0002 §8b). | | 26 | `0032-hid-nintendo-joycon-combo-led` | portable-as-is / shared | **included** | Virtual `led_classdev` as a userspace mailbox. | | 27 | `0033-hid-playstation-dualsense-player-id-led` | portable-as-is / shared | **included** | LED-class/HID only. | | 28 | `0034-hid-nintendo-nes-famicom-stock-ab-mapping` | portable-as-is / shared | **included** | Userspace ABI (stock A/B order), not cosmetic — audit §5.2. | diff --git a/configs/fragments/golden.sha256 b/configs/fragments/golden.sha256 index 4b77f165..62640d0a 100644 --- a/configs/fragments/golden.sha256 +++ b/configs/fragments/golden.sha256 @@ -7,6 +7,8 @@ 2026.08 de10nano fccaabea951a6d8b8f65f16bf85d199046eae9bacf6ea5102f5a1842317870b6 2026.08 de10nano-kernel b69db3e12782bfede48587acbb9549a0f2ef794acb6b8a41d5c95bc112a91104 2026.08 de25nano 68cc9ed6191e9539098c8307a6f470830c70a8f640ef37e05379c1b96e6b6e88 +2026.08 initramfs-de10nano 0dd82cead720b3ef2a9ce7d0359bced7c49494d5e9e5d278b515ddb071a743c6 +2026.08 initramfs-de25nano cd36760dde544017258c4ce964785361fc367f1b024d645ec90e58e66d6ff14a 2026.08 rt 103e68329df556920a06b736bcbafcad3913ce3ce49ef75f448df2797465ad95 2026.05.2 de10nano 251ae321011945e690a32f2fbb704b619681fd26d06acf578f7691e76286f992 2026.05.2 de10nano-kernel 5b372abe43c46c4eb5f3c88d51e48ff2eb6fa713b3c02f0f337c39d0f111dc69 diff --git a/configs/fragments/initramfs-common.fragment b/configs/fragments/initramfs-common.fragment new file mode 100644 index 00000000..cf7a0c6a --- /dev/null +++ b/configs/fragments/initramfs-common.fragment @@ -0,0 +1,41 @@ +# initramfs-common.fragment — STAGE 1 of the two-stage build, the part every +# board shares (TASKS.md P1.10 / A1, PLAN.md §5, docs/decisions/0002-initramfs.md). +# Builds ONE artifact per board: output-initramfs*/images/rootfs.cpio, a few +# hundred KB of static musl BusyBox plus the arch-neutral /init in +# board/mister/common/initramfs-overlay, which external.mk embeds into the +# board's kernel. The arch/ABI and headers-series lines are the ONLY +# per-board part and live in initramfs-.fragment; the stacks are +# `initramfs-common initramfs-` (configs/fragments/stacks.mk). +# Rationale for every line: docs/buildroot-config.md §8. +# +# WARNING: NEVER set BR2_TARGET_ROOTFS_INITRAMFS in a board's IMAGE stack to +# do this — that embeds the entire target rootfs into the kernel (trap A1). + +# --- Toolchain: musl, static-only (§8.2). The headers series is per-board. --- +BR2_TOOLCHAIN_BUILDROOT_MUSL=y +BR2_STATIC_LIBS=y + +# --- No init system: the kernel execs /init from the cpio (§8.3) --- +BR2_INIT_NONE=y + +# --- Device nodes: dynamic/devtmpfs (§8.4) --- +# WARNING: STATIC would leave /dev/console out of the cpio (no stdio for /init). +BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_DEVTMPFS=y + +# --- BusyBox: our own minimal config; /init via the overlay (§8.5) --- +# Both under board/mister/common: one /init, one BusyBox config, every board. +BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/common/initramfs-busybox.config" +BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/common/initramfs-overlay" + +# --- fsck.exfat for the on-demand repair path, ADR 0026 (§8.6) --- +BR2_PACKAGE_EXFATPROGS=y +BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/common/initramfs-post-build.sh" + +# --- Output: a cpio, uncompressed (§8.7) --- +BR2_TARGET_ROOTFS_CPIO=y +# BR2_TARGET_ROOTFS_TAR is not set + +# --- Reproducibility, A9 (§8.8) --- +# Restated here rather than taken from common.fragment: `common` carries +# BR2_LINUX_KERNEL=y, and a stage-1 stack must build no kernel. +BR2_REPRODUCIBLE=y diff --git a/configs/fragments/initramfs-de10nano.fragment b/configs/fragments/initramfs-de10nano.fragment new file mode 100644 index 00000000..b03096d3 --- /dev/null +++ b/configs/fragments/initramfs-de10nano.fragment @@ -0,0 +1,19 @@ +# initramfs-de10nano.fragment — the DE10-Nano's arch/ABI + headers-series +# lines for the stage-1 initramfs stack (`initramfs-common initramfs-de10nano`, +# configs/fragments/stacks.mk). Rationale: docs/buildroot-config.md §8.1, §8.2. +# +# WARNING: these values must match de10nano.fragment's (§3.1, §3.2) — the cpio +# runs on the same silicon the kernel it is embedded in was built for. +# scripts/check-config-fragments.sh (f) asserts the two files agree, symbol by +# symbol, so a CPU-tuning or headers-series change in one and not the other +# fails CI instead of shipping a mismatched stage 1. + +# --- Arch/ABI: same silicon as the main build (§8.1) --- +BR2_arm=y +BR2_cortex_a9=y +BR2_ARM_ENABLE_NEON=y +BR2_ARM_ENABLE_VFP=y +BR2_ARM_FPU_NEON=y + +# --- Kernel headers SERIES (§8.2 -> §3.2) --- +BR2_KERNEL_HEADERS_6_18=y diff --git a/configs/fragments/initramfs-de25nano.fragment b/configs/fragments/initramfs-de25nano.fragment new file mode 100644 index 00000000..843f0fa6 --- /dev/null +++ b/configs/fragments/initramfs-de25nano.fragment @@ -0,0 +1,19 @@ +# initramfs-de25nano.fragment — the DE25-Nano's arch/ABI + headers-series +# lines for the stage-1 initramfs stack (`initramfs-common initramfs-de25nano`, +# configs/fragments/stacks.mk). Rationale: docs/buildroot-config.md §8.1, §8.2. +# +# WARNING: these values must match de25nano.fragment's (§6.2) — same +# check-config-fragments (f) lockstep as the DE10's file. No NEON/VFP knobs +# exist on AArch64, which is why this file is three lines to the DE10's six. +# The cpio this stack builds is NOT yet embedded in the DE25 kernel: the +# shipped card keeps ADR 0029 D11's interim plain-ext4 shape until hardware, +# and external.mk's embedding fixup still keys on BR2_arm. What this stack +# gives is the aarch64 stage 1 built, verified and QEMU-proven ahead of the +# switch (docs/de25-sdcard.md §2). + +# --- Arch/ABI: same silicon as the DE25 image build (§8.1 -> §6.2) --- +BR2_aarch64=y +BR2_cortex_a76_a55=y + +# --- Kernel headers SERIES (§8.2 -> §6.2) --- +BR2_KERNEL_HEADERS_7_1=y diff --git a/configs/fragments/stacks.mk b/configs/fragments/stacks.mk index 1b5ea67d..20b9f7be 100644 --- a/configs/fragments/stacks.mk +++ b/configs/fragments/stacks.mk @@ -24,3 +24,15 @@ DE10NANO_FRAGMENTS := common de10nano image-common de10nano-image DE10NANO_KERNEL_FRAGMENTS := common de10nano kernel-only DE25NANO_FRAGMENTS := common de25nano image-common +# +# The stage-1 initramfs stacks. `initramfs-common` is the third sharing axis: +# it is in NO image stack and NO kernel-only stack — a stage-1 stack builds a +# static musl BusyBox cpio and no kernel (it deliberately does not include +# `common`, which carries BR2_LINUX_KERNEL=y). Per board, only the arch/ABI + +# headers-series lines differ, and scripts/check-config-fragments.sh (f) +# asserts each initramfs- fragment agrees symbol-for-symbol with that +# board's .fragment. Output dirs: output-initramfs/ (DE10, embedded in +# every DE10 kernel) and output-initramfs-de25/ (DE25, built and QEMU-proven, +# not yet embedded — ADR 0029 D11). +INITRAMFS_DE10NANO_FRAGMENTS := initramfs-common initramfs-de10nano +INITRAMFS_DE25NANO_FRAGMENTS := initramfs-common initramfs-de25nano diff --git a/configs/mister_initramfs_defconfig b/configs/mister_initramfs_defconfig deleted file mode 100644 index 8285f537..00000000 --- a/configs/mister_initramfs_defconfig +++ /dev/null @@ -1,42 +0,0 @@ -# mister_initramfs_defconfig — STAGE 1 of the two-stage build (TASKS.md P1.10 / -# A1, PLAN.md §5, docs/decisions/0002-initramfs.md): output-initramfs/images/ -# rootfs.cpio, a few hundred KB of static musl BusyBox plus our /init, which -# external.mk embeds into the DE10 kernel. A standalone Buildroot config, not a -# fragment stack. Rationale for every line: docs/buildroot-config.md §8. -# -# WARNING: NEVER set BR2_TARGET_ROOTFS_INITRAMFS in the MAIN config to do this — -# that embeds the entire ~300 MB target rootfs into the kernel (trap A1). - -# --- Arch/ABI: same silicon as the main build (§8.1) --- -BR2_arm=y -BR2_cortex_a9=y -BR2_ARM_ENABLE_NEON=y -BR2_ARM_ENABLE_VFP=y -BR2_ARM_FPU_NEON=y - -# --- Toolchain: musl, static-only (§8.2) --- -BR2_TOOLCHAIN_BUILDROOT_MUSL=y -BR2_KERNEL_HEADERS_6_18=y -BR2_STATIC_LIBS=y - -# --- No init system: the kernel execs /init from the cpio (§8.3) --- -BR2_INIT_NONE=y - -# --- Device nodes: dynamic/devtmpfs (§8.4) --- -# WARNING: STATIC would leave /dev/console out of the cpio (no stdio for /init). -BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_DEVTMPFS=y - -# --- BusyBox: our own minimal config; /init via the overlay (§8.5) --- -BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/initramfs-busybox.config" -BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/initramfs-overlay" - -# --- fsck.exfat for the on-demand repair path, ADR 0026 (§8.6) --- -BR2_PACKAGE_EXFATPROGS=y -BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/initramfs-post-build.sh" - -# --- Output: a cpio, uncompressed (§8.7) --- -BR2_TARGET_ROOTFS_CPIO=y -# BR2_TARGET_ROOTFS_TAR is not set - -# --- Reproducibility, A9 (§8.8) --- -BR2_REPRODUCIBLE=y diff --git a/configs/mister_installer_defconfig b/configs/mister_installer_defconfig index bfc15723..d7870bb6 100644 --- a/configs/mister_installer_defconfig +++ b/configs/mister_installer_defconfig @@ -2,7 +2,8 @@ # docs/decisions/0020-sdcard-exfat-reformat-installer.md): output-installer/ # images/rootfs.cpio, a static musl BusyBox + exfatprogs + util-linux(sfdisk) # rootfs that scripts/mk-sdcard.sh embeds into the installer's own zImage_dtb. -# A SIBLING of mister_initramfs_defconfig (same shape, line-for-line), plus +# A SIBLING of the stage-1 initramfs stack (configs/fragments/initramfs-common + +# initramfs-de10nano; same shape, line-for-line), plus # exactly what the reformat-and-handoff /init needs. Standalone Buildroot # config, not a fragment stack. Rationale: docs/buildroot-config.md §9. # diff --git a/docs/buildroot-config.md b/docs/buildroot-config.md index 3356626f..9bc24a67 100644 --- a/docs/buildroot-config.md +++ b/docs/buildroot-config.md @@ -23,7 +23,7 @@ Contents: 5. [`de10nano-image.fragment`](#5-de10nano-imagefragment) 6. [`de25nano.fragment`](#6-de25nanofragment) 7. [`mister_rt.fragment`](#7-mister_rtfragment) -8. [`mister_initramfs_defconfig`](#8-mister_initramfs_defconfig) +8. [The stage-1 initramfs stacks: `initramfs-common`, `initramfs-de10nano`, `initramfs-de25nano`](#8-the-stage-1-initramfs-stacks) 9. [`mister_installer_defconfig`](#9-mister_installer_defconfig) 10. [Placement decisions — what is common, what is board-only, and why](#10-placement-decisions) 11. [Checks, golden hashes, and the identity proof](#11-checks-golden-hashes-and-the-identity-proof) @@ -50,9 +50,11 @@ configs/ de10nano-image.fragment DE10-Nano shipped image: board hooks, ext4 contract, packages, system config kernel-only.fragment turns a board stack into the kernel-only base variants build on de25nano.fragment DE25-Nano developer OS (aarch64), layered on common + image-common + initramfs-common.fragment stage-1 initramfs cpio, everything but the arch (§8) + initramfs-de10nano.fragment stage-1 arch/ABI + headers series, DE10 (§8.1, §8.2) + initramfs-de25nano.fragment stage-1 arch/ABI + headers series, DE25 (§8.1, §8.2) golden.sha256 sha256 of each stack's normalised resolved .config (§11) mister_rt.fragment the RT / 7.2 kernel variant, layered on the kernel-only stack - mister_initramfs_defconfig stage-1 initramfs cpio (standalone Buildroot config, §8) mister_installer_defconfig SD-card installer cpio (standalone Buildroot config, §9) ``` @@ -64,6 +66,8 @@ configs/ | `de10nano-kernel` | `common de10nano kernel-only` | — (base only) | used by every kernel variant | | `de25nano` | `common de25nano image-common` | `output-de25/` | `make de25nano-defconfig` (and `make de25`) | | `rt` (variant) | `de10nano-kernel` + `configs/mister_rt.fragment` | `output-rt/` | `make rt` | +| `initramfs-de10nano` | `initramfs-common initramfs-de10nano` | `output-initramfs/` | `make initramfs` (regenerates when a fragment is newer; `make initramfs-defconfig` forces) | +| `initramfs-de25nano` | `initramfs-common initramfs-de25nano` | `output-initramfs-de25/` | `make de25-initramfs` (likewise; `make de25-initramfs-defconfig`) | **Generation** is the idiom `make rt` has used since ADR 0021: Buildroot's own `support/kconfig/merge_config.sh -m` concatenates the stack's fragments into @@ -97,17 +101,26 @@ produced — the only difference is `BR2_DEFCONFIG`, which is where - The kernel-only base shares `common` + `de10nano` with the image **by construction**. That replaces the old hand-mirrored copy; §4 and §11 say what the lockstep check still guards. -- The two sharing axes are at right angles: `common` is shared by every stack - INCLUDING the kernel-only one, `image-common` by every IMAGE stack and by NO - kernel-only stack. A package both images want goes in `image-common` (§12), - never in `common` — rule 4 of §10 says why (`common` is in the kernel-only - stack's fingerprint text, and the kernel-only rootfs.tar ships modules, not - blobs). +- The sharing axes are at right angles: `common` is shared by every image and + kernel stack INCLUDING the kernel-only one, `image-common` by every IMAGE + stack and by NO kernel-only stack. A package both images want goes in + `image-common` (§12), never in `common` — rule 4 of §10 says why (`common` + is in the kernel-only stack's fingerprint text, and the kernel-only + rootfs.tar ships modules, not blobs). The third axis, `initramfs-common`, + is in NO image or kernel stack at all: the stage-1 stacks build a cpio and + no kernel, so they deliberately do not include `common` (§8). +- The stage-1 configs are the one place a fragment IS a file prerequisite of + the generated `.config`: a stage-1 config is generated, never iterated on + with `menuconfig`, so `make initramfs` regenerates it whenever a fragment is + newer — the behaviour the old standalone defconfig had. - Adding a board = a new `.fragment` (+ optionally `-image`), a `_FRAGMENTS` line in `stacks.mk` (with `image-common` in it if the board ships an image), a `BR_MAKE_` / `.config` rule pair in the Makefile mirroring the DE25's, a golden line (§11), and rows in - `scripts/lib/board-expectations.sh`. Adding a kernel variant is unchanged + `scripts/lib/board-expectations.sh`; if the board boots the two-stage way, + also an `initramfs-.fragment` restating its arch/ABI + headers lines + (§8.1–§8.2; check (f) of §11 holds it to `.fragment`) and an + `INITRAMFS__FRAGMENTS` line. Adding a kernel variant is unchanged from ADR 0021: one `configs/mister_.fragment`, its Makefile targets, and nothing else (CI derives the matrix from the fragment glob; the fragment check picks it up automatically and expects it to override only what @@ -514,7 +527,7 @@ same `$(BR_DIR)`-relative path idiom as the post-image script (§3.6); `BR2_ROOTFS_OVERLAY` — P2.3, init & config parity overlay (`docs/init-parity.md`). Copied onto `TARGET_DIR` after every package installs, before the permission table and filesystem image are built (`system/system.mk`) — same -`BR2_EXTERNAL_MISTER_PATH`-relative form as `mister_initramfs_defconfig`'s own +`BR2_EXTERNAL_MISTER_PATH`-relative form as `initramfs-common.fragment`'s own `BR2_ROOTFS_OVERLAY`, just pointed at the full-rootfs overlay tree instead of the initramfs one. @@ -2337,14 +2350,20 @@ complements; mainline's socdk board file was the placeholder before D2.3 landed and is not the DE25-Nano (no mmc0, fpga-mgr or fpga-region). The Makefile asserts the .dtb by GLOB, not by name, for exactly this reason. -NO STAGE-1 INITRAMFS ON THIS BOARD, and that is a design point rather than a -gap. The DE10 embeds an armv7 BusyBox cpio into its zImage because U-Boot -passes `-` for bootz's initrd argument and the real root is a loop-mounted -ext4 image sitting on a FAT partition (`docs/boot-chain.md`). The DE25 boots a -plain ext4 root partition directly (decision 3: p1 FAT, p2 everything else), -so there is nothing for a stage-1 to do. `external.mk`'s initramfs-embedding -kernel fixup is guarded off for this build (it keys on `BR2_arm`) — see the -guard's comment there. +NO STAGE-1 INITRAMFS IN THIS KERNEL — YET. The DE10 embeds an armv7 BusyBox +cpio into its zImage because U-Boot passes `-` for bootz's initrd argument and +the real root is a loop-mounted ext4 image sitting on a FAT partition +(`docs/boot-chain.md`). ADR 0029 D11 (2026-09-03) makes that two-stage layout +the DE25's TARGET too, while the shipped card keeps the interim plain-ext4 +root (`root=/dev/mmcblk0p2`, decision 3: p1 FAT, p2 everything else) until a +board has booted it. So today `external.mk`'s initramfs-embedding kernel +fixup is guarded off for this build (it keys on `BR2_arm`) — but the aarch64 +stage 1 itself already exists: the `initramfs-de25nano` stack (§8) builds the +same `/init` for this board into `output-initramfs-de25/`, `make +de25-initramfs` verifies it, and `scripts/test-initramfs.sh --board de25nano` +boots it through all eight cases on `qemu-system-aarch64` — using THIS +board's `linux.config` + the shared fragment as the test kernel's base. The +guard's comment in `external.mk` spells out the one-commit switch. ### 6.6 Root filesystem — ext4 on p2, modest and plain @@ -2742,18 +2761,49 @@ fragment, for the `linux-update-defconfig` reason in §3.4). --- -## 8. `mister_initramfs_defconfig` +## 8. The stage-1 initramfs stacks STAGE 1 of the two-stage build (TASKS.md P1.10 / A1, PLAN.md §5, -`docs/decisions/0002-initramfs.md`). A standalone Buildroot config (not a -fragment stack — nothing in it is shared with the image stacks; see §10), -driven by the top-level Makefile's `initramfs` target into `output-initramfs/`. - -This config builds ONE artifact: `output-initramfs/images/rootfs.cpio`, a few -hundred KB of static BusyBox plus our `/init`. The main build (the de10nano -stack) then embeds that cpio into the kernel via `CONFIG_INITRAMFS_SOURCE` — -see `external.mk`, where the path is injected into the kernel .config, and -the top-level Makefile, which sequences stage 1 before stage 2. +`docs/decisions/0002-initramfs.md`). Since 2026-09-06 a fragment stack per +board, not a standalone defconfig — `initramfs-common` carries everything +that is not the architecture, and a three-to-six-line `initramfs-` +fragment restates the board's arch/ABI + headers-series lines: + +| Stack | Fragments | Output dir | Cpio | Embedded in | +|---|---|---|---|---| +| `initramfs-de10nano` | `initramfs-common initramfs-de10nano` | `output-initramfs/` | armv7 static musl BusyBox + `/init` | every DE10 kernel (image, kernel-only, rt) via `external.mk` | +| `initramfs-de25nano` | `initramfs-common initramfs-de25nano` | `output-initramfs-de25/` | aarch64 static musl BusyBox + the SAME `/init` | nothing yet — built and QEMU-proven ahead of ADR 0029 D11's card switch (§6.5) | + +The files the common fragment names are shared by path under +`board/mister/common/`: `initramfs-overlay/init`, `initramfs-busybox.config`, +`initramfs-post-build.sh`. `/init` has no architecture-specific line in it +(ADR 0029 D11's evidence), the BusyBox config has no arch symbol, and the hook +only deletes files — which is why one copy serves both boards and why the +DE25's cpio differs from the DE10's only by the architecture it is compiled +for. + +Why a stack now, when §10 recorded "left standalone" on 2026-09-02: that +call weighed six saved lines against a new stack shape for ONE board. A +second board changes the arithmetic — a DE25 stage 1 needs every line of the +old defconfig except the arch tuple, and two hand-mirrored copies of a config +whose `CONFIG_FEATURE_MOUNT_FLAGS`-class traps are documented at length is +precisely the drift the fragment split exists to end. The cost noted then +(the `BR_INITRAMFS_HOST_KEY` CI cache key moves) was paid once, on the PR +that made the change: the key now hashes the stack's files through the same +`config_stack_files` helper as the image/kernel keys. + +Each stack builds ONE artifact, `/images/rootfs.cpio`, a few +hundred KB of static BusyBox plus our `/init`. The DE10's main build (the +de10nano stack) then embeds that cpio into the kernel via +`CONFIG_INITRAMFS_SOURCE` — see `external.mk`, where the path is injected into +the kernel .config, and the top-level Makefile, which sequences stage 1 before +stage 2. The identity proof at the split (DE10 stage-1 resolved config before +vs after) is in §11. + +The stage-1 stacks deliberately do NOT include `common.fragment`: it carries +`BR2_LINUX_KERNEL=y`, and a stage-1 build must build no kernel. The one +`common` line stage 1 also wants, `BR2_REPRODUCIBLE`, is restated in +`initramfs-common` (§8.8). **NEVER set `BR2_TARGET_ROOTFS_INITRAMFS` in the MAIN config to do this.** That option embeds the entire ~300 MB target rootfs into the kernel image. It is @@ -2770,18 +2820,27 @@ is a conflict: nothing in the initramfs is an ABI surface. It runs BusyBox, calls mount(2)/losetup, and is deleted from RAM by switch_root before `/sbin/init` starts. It never meets Main_MiSTer. -### 8.1 Arch/ABI — same silicon as the main build (ADR 0001) +### 8.1 Arch/ABI — same silicon as the board's main build (`initramfs-.fragment`) -`BR2_arm`, `BR2_cortex_a9`, `BR2_ARM_ENABLE_NEON`, `BR2_ARM_ENABLE_VFP`, +DE10: `BR2_arm`, `BR2_cortex_a9`, `BR2_ARM_ENABLE_NEON`, `BR2_ARM_ENABLE_VFP`, `BR2_ARM_FPU_NEON` — not an ABI requirement here; it just has to run on a -Cortex-A9 (§3.1). +Cortex-A9 (§3.1). DE25: `BR2_aarch64`, `BR2_cortex_a76_a55` (§6.2; no +NEON/VFP knobs exist on AArch64). These lines are RESTATED from the board +fragment rather than shared with it, because a stage-1 stack cannot include +`.fragment` (kernel stanza, patch dir, image hooks). Check (f) of §11 +holds each restatement to its board fragment symbol-for-symbol, so a CPU +tuning changed 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. -### 8.2 Toolchain: musl, static-only — `BR2_TOOLCHAIN_BUILDROOT_MUSL`, `BR2_KERNEL_HEADERS_6_18`, `BR2_STATIC_LIBS` +### 8.2 Toolchain: musl, static-only — `BR2_TOOLCHAIN_BUILDROOT_MUSL`, `BR2_STATIC_LIBS` (common); `BR2_KERNEL_HEADERS_6_18` / `_7_1` (per board) musl is chosen *because* it permits `BR2_STATIC_LIBS` (glibc does not) and because a static musl BusyBox is roughly half the size of a static glibc one. The main build stays on glibc; see the note above on why that is not an -inconsistency. The headers series pin follows §3.2. +inconsistency. The headers series pin is per board and follows §3.2 (DE10) +and §6.2 (DE25) — the same series the board's kernel-header package uses, so +the same 2026.08-bump trap (§6.2: a retired series symbol silently collapses +the libc choice) applies here, and the golden hash is what would catch it. ### 8.3 No init system — `BR2_INIT_NONE` @@ -2799,11 +2858,18 @@ The Makefile's `initramfs-verify` asserts `/dev/console` is in the cpio. ### 8.5 BusyBox config and `/init` — `BR2_PACKAGE_BUSYBOX_CONFIG`, `BR2_ROOTFS_OVERLAY` -Our own minimal config, `board/mister/de10nano/initramfs-busybox.config` (see +Our own minimal config, `board/mister/common/initramfs-busybox.config` (see the header of that file for how it was generated and which symbols are load-bearing — `CONFIG_FEATURE_MOUNT_FLAGS` above all). `BR2_STATIC_LIBS` makes `busybox.mk` force `CONFIG_STATIC` on top of it. The overlay -`board/mister/de10nano/initramfs-overlay` is `/init` itself. +`board/mister/common/initramfs-overlay` is `/init` itself. Both live under +`board/mister/common/` (moved from `board/mister/de10nano/` on 2026-09-06, +when the DE25 stack started using them by path): the BusyBox config names no +architecture, and `/init` parses `/proc/cmdline` for everything it needs, so +the same two files build the DE10's armv7 cpio and the DE25's aarch64 one. +The Makefile's `initramfs-verify` (and `de25-initramfs-verify`, the same +recipe re-pointed) parses `/init` with the BusyBox ash it just built for that +board, under `qemu-arm` / `qemu-aarch64`. ### 8.6 fsck.exfat for the on-demand repair path (ADR 0026) — `BR2_PACKAGE_EXFATPROGS`, `BR2_ROOTFS_POST_BUILD_SCRIPT` @@ -2826,7 +2892,7 @@ for why the dirty flag alone is not a usable trigger. The only dependency is `BR2_USE_WCHAR`, which musl satisfies; the installer config (§9) already builds this package on the same musl+static toolchain. -`board/mister/de10nano/initramfs-post-build.sh` (`BR2_ROOTFS_POST_BUILD_SCRIPT`) +`board/mister/common/initramfs-post-build.sh` (`BR2_ROOTFS_POST_BUILD_SCRIPT`) then deletes the five binaries we do not use (dump.exfat, exfat2img, exfatlabel, mkfs.exfat, tune.exfat — 476 KB of zImage for tools stage 1 cannot invoke) — see there for why that is done in post-build. The Makefile's @@ -2848,7 +2914,9 @@ the no-default choice trap.) The cpio is embedded in the zImage, so if the cpio is not byte-reproducible then `zImage_dtb` is not either, and P4.3's double-build job fails for a -reason that has nothing to do with the kernel. +reason that has nothing to do with the kernel. Restated in `initramfs-common` +rather than inherited from `common.fragment` — see the top of §8 for why the +stage-1 stacks do not include `common`. --- @@ -2878,14 +2946,15 @@ to the real MiSTer. This config is that installer OS's rootfs. Its `/init` sfdisk/mkfs.exfat/copy-back/MAC-gen/dd-uboot.img/reboot dance described in ADR 0020 §2. -Relationship to `mister_initramfs_defconfig` (STAGE 1, §8): this is a SIBLING -of stage 1, not a variant of the main target config: same static musl -throwaway-cpio shape (`BR2_INIT_NONE`, `BR2_TARGET_ROOTFS_CPIO`, no shared -libc), because the installer runs from RAM exactly like stage 1's `/init` does -and is deleted the moment it reboots into the real system. It is deliberately -BASED ON `mister_initramfs_defconfig` line-for-line +Relationship to the stage-1 stacks (§8): this is a SIBLING of stage 1, not +a variant of the main target config: same static musl throwaway-cpio shape +(`BR2_INIT_NONE`, `BR2_TARGET_ROOTFS_CPIO`, no shared libc), because the +installer runs from RAM exactly like stage 1's `/init` does and is deleted the +moment it reboots into the real system. It was BASED ON the old +`mister_initramfs_defconfig` line-for-line (arch/toolchain/init/device-creation/output/reproducibility all copied -verbatim — §8 has the reasoning behind each) and adds exactly what the +verbatim — §8 has the reasoning behind each; it is DE10-only and stayed a +standalone defconfig when stage 1 became a stack) and adds exactly what the installer's job needs on top: - `BR2_PACKAGE_EXFATPROGS` -> mkfs.exfat (ADR 0020 §2 step 3; `-n MiSTer_Data`). @@ -3005,7 +3074,7 @@ The judgement calls, each recorded here: | DE25 getty/hostname/issue, `BR2_KERNEL_HEADERS_7_0`, `USE_CUSTOM_CONFIG`, `CUSTOM_CONFIG_FILE`, `CONFIG_FRAGMENT_FILES`, `# USE_ARCH_DEFAULT_CONFIG is not set`, `IMAGE`, `CUSTOM_DTS_PATH` | `de25nano` | Board-specific by nature (§6). The kernel-config pair names DE25 files; the *fragment* file it names is shared with the DE10 by path, but that sharing is a file, not a symbol (§6.5). | | The whole DE25 bootloader stanza (`BR2_TARGET_ARM_TRUSTED_FIRMWARE*`, `BR2_TARGET_UBOOT*`) | `de25nano` | The DE10 has no bootloader in its Buildroot config at all — its boot chain is the stock/Terasic one, assembled outside Buildroot (`docs/boot-chain.md`). Nothing to share, so no `common` question arises. | | `BR2_PACKAGE_HOST_UBOOT_TOOLS`, `_FIT_SUPPORT`, `BR2_PACKAGE_HOST_GENIMAGE`, `BR2_PACKAGE_HOST_MTOOLS`, `BR2_PACKAGE_HOST_DOSFSTOOLS` | `de25nano` | Host tooling for the FIT and the card image (§6.10, §6.11), and DE25-only in fact: the DE10 stack sets none of these five. Note the near-miss — `de10nano-image` sets `BR2_PACKAGE_DOSFSTOOLS` (+ `_FATLABEL`, `_FSCK_FAT`, `_MKFS_FAT`), the TARGET package that ships `mkfs.fat` on the board, which is a different symbol from `BR2_PACKAGE_HOST_DOSFSTOOLS`. Not a `common` candidate on either count, and rule 4 says so twice over: `common` is in the kernel-only stack, so a `BR2_PACKAGE_HOST_*` line added there would move the kernel-variant toolchain fingerprint and bust every variant's host-toolchain cache, exactly as recorded for `BR2_TARGET_GENERIC_ROOT_PASSWD` above. | -| `mister_initramfs_defconfig`, `mister_installer_defconfig` | left standalone | They share five arch lines and `BR2_KERNEL_HEADERS_6_18` with `de10nano.fragment` but differ on the toolchain (musl, static) and everything else; a "de10nano-arch" micro-fragment would save six lines at the price of a fourth stack shape and a toolchain-fingerprint change for the initramfs host cache (`BR_INITRAMFS_HOST_KEY` hashes that file). Not worth it; their comments moved here (§8, §9) for the same reason as the others. | +| ~~`mister_initramfs_defconfig`~~, `mister_installer_defconfig` | installer: left standalone. **Stage 1: a stack since 2026-09-06** (`initramfs-common` + `initramfs-`, §8) | The 2026-09-02 call was "left standalone": they share five arch lines and `BR2_KERNEL_HEADERS_6_18` with `de10nano.fragment` but differ on the toolchain (musl, static) and everything else; a "de10nano-arch" micro-fragment would save six lines at the price of a fourth stack shape and a toolchain-fingerprint change for the initramfs host cache (`BR_INITRAMFS_HOST_KEY` hashes that file). **Reversed for stage 1 when the DE25 needed one** (ADR 0029 D11): a second board wants every line but the arch tuple, so the split is `initramfs-common` (everything) + a per-board arch fragment (restated, held to `.fragment` by check (f)) rather than a shared arch micro-fragment — the arch lines are the part that DIFFERS per board, and what is shared is the rest. The cache-key cost was paid once. The installer is DE10-only (its `/init` runs `dd` of the DE10's `uboot.img`) and stays standalone. | | `BR2_PACKAGE_STRACE=y` twice in the old DE10 file | once, in the T5 section of `de10nano-image` | A duplicate within one fragment is a redefinition the check rejects and a kconfig "override: reassigning" warning; T5 had already made strace permanent (§5.32, §5.42). Resolved config unchanged. | Symbol counts (assignments + explicit not-set lines), re-measured 2026-09-03 @@ -3056,6 +3125,9 @@ compile; ~4 s warm; runs in `lint-config`): for each stack in `stacks.mk` plus `BR2_GDB_VERSION`, `BR2_DEFCONFIG`); - (d) the sha256 of the NORMALISED resolved `.config` equals `configs/fragments/golden.sha256` for the pinned `BUILDROOT_VERSION`; +- (f) stage-1 arch lockstep: every symbol an `initramfs-.fragment` + sets is set to the same value in `.fragment` (text level, stripped + fragments; §8.1); - (e) path consumers: every `configs/fragments/` named in the code/CI surface (`Makefile`, `scripts/`, `.github/`, `renovate.json` — not docs) exists, and `action.yml`'s two `hashFiles()` lists equal the `DE10NANO` / @@ -3124,6 +3196,18 @@ moved. The same identity was checked for `mister_initramfs_defconfig` and changed; the resolved configs are byte-identical, `BR2_DEFCONFIG` included, since the files kept their names). +THE STAGE-1 SPLIT (2026-09-06, Buildroot 2026.08): `mister_initramfs_defconfig` +became the `initramfs-de10nano` stack (§8). Its resolved `.config` from the old +`make mister_initramfs_defconfig` path, diffed against the stack's +merge_config.sh + olddefconfig output on the same Buildroot tree, differed in +exactly three kinds of line: `BR2_DEFCONFIG` (as above), the git-describe +`-dirty` suffix of the working tree, and the three `board/mister/de10nano/` +→ `board/mister/common/` paths of the moved files (§8.5, §8.6) — 5,240 lines +otherwise identical, toolchain (gcc 15.3.0, binutils 2.45.1, musl, headers +6.18) included. The two new golden lines were then recorded; the four +existing ones did not move. The `initramfs-de25nano` stack resolves to +`BR2_aarch64` + `BR2_cortex_a76_a55`, musl, headers 7.1, the same gcc/binutils. + SINCE THE SPLIT, the `de25nano` golden line has moved on purpose twice: once when the DE25 wave-2 work (§6.5's kernel-config switch and the §6.9–§6.11 bootloader, host-tool and card stanzas) was ported into `de25nano.fragment`, diff --git a/docs/de25-nano-tasks.md b/docs/de25-nano-tasks.md index a0f8c695..98d1295a 100644 --- a/docs/de25-nano-tasks.md +++ b/docs/de25-nano-tasks.md @@ -339,8 +339,9 @@ UART** is normal; capture the SPL's `DDR:` lines (the only real DRAM-size measur ### Still open after wave 2 - ~~p2 filesystem / DE10-style two-stage layout~~ **Decided 2026-09-03 (ADR 0029 D11):** the - two-stage layout is the target; the plain ext4 card stays until hardware. Owed before the - switch: an aarch64 stage-1 initramfs stack + its QEMU test path. + two-stage layout is the target; the plain ext4 card stays until hardware. ~~Owed before the + switch: an aarch64 stage-1 initramfs stack + its QEMU test path.~~ **Delivered 2026-09-06 + (wave 3 below).** Still owed before the switch: the 7.x re-anchor of patch 0031. - The DE25 kernel pin has no Renovate manager and shares `linux.hash` by symlink with the DE10 registry, so an rt bump replaces the 7.2.y hash line rather than adding one. **This happened on 2026-09-02** (rt 7.2.2 -> 7.2.3): the DE25 pin was moved to 7.2.3 in the same series of commits @@ -351,6 +352,27 @@ UART** is normal; capture the SPL's `DDR:` lines (the only real DRAM-size measur set via a shared `image-common` fragment (PR in flight); seccomp stays off as on the DE10. - Patch 0002 (MiSTer audio) still excluded; `openssh` will need `_SANDBOX` off when added. +## Wave 3 — 2026-09-06 (pre-hardware) — the aarch64 stage 1 and its QEMU leg + +Goal: ADR 0029 D11's owed pre-hardware work — the stage-1 initramfs built for aarch64 and a +QEMU path that boots it — done sequentially in the main tree (owner request: single file). + +| Track | Deliverable | Result | +|---|---|---| +| Stage 1 as a fragment stack | `configs/fragments/initramfs-common.fragment` + `initramfs-de10nano` / `initramfs-de25nano`; `configs/mister_initramfs_defconfig` deleted; `/init`, `initramfs-busybox.config`, `initramfs-post-build.sh` moved to `board/mister/common/`; `INITRAMFS_*_FRAGMENTS` in `stacks.mk`; golden lines for both; checker (f) arch lockstep | DE10 stage-1 resolved config identical old vs new on Buildroot 2026.08 bar `BR2_DEFCONFIG`, the `-dirty` suffix and the three moved paths (5,240 lines). Existing four golden lines unchanged. `BR_INITRAMFS_HOST_KEY` now hashes the stack via `config_stack_files` (one cold CI cache, once). | +| `make de25-initramfs` family | `de25-initramfs`, `-verify` (the DE10 `initramfs-verify` recipe with three variables re-pointed, parse under `qemu-aarch64`), `-clean`, `-defconfig`, `-menuconfig`, `-busybox-menuconfig`; `output-initramfs-de25/` in clean/distclean/help | aarch64 musl toolchain + cpio in 6 min from nothing; 466,944-byte cpio, static aarch64 BusyBox; verify OK (19 applets + fsck.exfat + 5 trimmed + /init + /dev/console + ash parses /init). NOT embedded in the DE25 kernel (D11 interim); `external.mk` comment carries the switch. | +| `scripts/test-initramfs.sh --board de25nano` | board switch (`--board` / `TEST_INITRAMFS_BOARD`); per-board cpio, QEMU binary/machine (`-M virt -cpu cortex-a76`), cross compiler (the stage-1 build's own musl toolchain — no `make de25` needed), kernel version pin, 0031 source, caches (`work/test-initramfs-de25*`); test kernel = `board/mister/de25nano/linux.config` + `linux-mister.fragment` + the unchanged harness fragment; kernel tarball default now prefers `dl/linux/` | Whole run incl. the 7.2.3 kernel build: 4 m 16 s; one case re-run 5.5 s. **7/8 PASS**; `symlink` FAIL = a real kernel Oops (below). DE10 leg byte-for-byte unchanged in behaviour. `ci-tests.sh` gained the DE25 leg (skips when no DE25 cpio was built). | + +**Finding (the point of the exercise).** Patch 0031's `exfat_symlink` → `page_symlink()` → +`a_ops->write_begin` = NULL → `Oops: pc 0x0`, kernel panic, on the first symlink created. 7.x +exFAT is iomap-based and has no `write_begin`/`write_end` (6.18.49 has both). Not an aarch64 bug; +not an `/init` bug; a 7.x carry bug in 0031 that the RT kernel shares and nothing had ever +run. Recorded in ADR 0002 §8b, `docs/rt-beta-kernel.md` §6, the DE25 patch README. + +| Track | Deliverable | Result | +|---|---|---| +| 0031 re-anchor for 7.x | `linux-patches-beta/0031-exfat-samsung-symlinks.patch` is a real file again (the fifth re-anchor; the shared 6.18 patch untouched); `de25nano/linux-patches/0031` symlink → the beta copy; `series` note updated | `exfat_symlink_write_target()`: clusters via `exfat_map_cluster()` (7.x signature) under `s_lock`, sectors via buffer heads, `sync_blockdev_range()`, then `valid_size`/`zeroed_size`/`i_size`. Applies at `-F0` to pristine 7.2.3 (12/12); arm compile clean with `output-rt`'s `.config`; **aarch64 leg 8/8** from a fresh source tree (4 m 04 s). | + ## What to do next — 2026-08-22 D0 and D1 are done; the opening move this section used to describe has been executed. The live @@ -362,12 +384,20 @@ Remaining, in unblock order: 1. **Hardware session** on a borrowed board (QSPI at factory): factory SPL boots our FIT → serial login → SD under the 25 MHz cap → `dd` the card clean → lift to 50 MHz → the §2.6 fabric test, SMMU-off first. -2. **`scripts/test-initramfs.sh` aarch64 path** (`qemu-system-aarch64 -M virt`) and the aarch64 - initramfs itself, which the two-stage layout will need. +2. ~~**`scripts/test-initramfs.sh` aarch64 path** (`qemu-system-aarch64 -M virt`) and the aarch64 + initramfs itself, which the two-stage layout will need.~~ **DONE 2026-09-06** (wave 3). + What it surfaced, and what the same PR fixed: **patch 0031 (exFAT Samsung symlinks) Oopsed + on symlink creation on every 7.x kernel** — `page_symlink()` calls + `a_ops->write_begin`, which 7.x exFAT (iomap) no longer has (ADR 0002 §8b). Affected the DE25 + AND the DE10's RT 7.2.3 kernel (same patch file by symlink). ~~Re-anchor 0031 for 7.x (write + the link target without `page_symlink`), in `linux-patches-beta/` as a beta-local copy and + pointed to from the DE25 series; the aarch64 leg's `symlink` case is the acceptance test — + it is the ONLY place 0031-on-7.x is executed rather than compiled.~~ **DONE 2026-09-06:** + beta-local copy, DE25 series relinked, aarch64 leg 8/8, arm compile clean on the RT config. + Remaining: the first `ln -s` on an RT-booted DE10 (32-bit 7.x is still compile-only). 3. **Owner decisions still open**: a Renovate manager for the DE25 kernel pin; upstream submission of 0101/0102; patch 0002 (audio). Hardware is expected after the owner's vacation - (ordered on return), so the aarch64 initramfs stack and its QEMU path are the pre-hardware - work that remains. Item 2 above should be done before the board arrives. + (ordered on return). 4. **Stand up D0.4** as a `/schedule` routine. Sequencing note learned the hard way on 2026-08-21: when a research phase feeds a claim set that a diff --git a/docs/de25-sdcard.md b/docs/de25-sdcard.md index 7856b145..8b8b1e94 100644 --- a/docs/de25-sdcard.md +++ b/docs/de25-sdcard.md @@ -117,6 +117,30 @@ Why this and not something else, stated as reasoning rather than as a settled de to be taken for real. Changing it means changing `post-image.sh`'s `ROOT_DEV`, the genimage config's p2 stanza and `check-sdcard-de25.sh`'s `EXPECT_ROOT_DEV` **in one commit**. +**Taken, 2026-09-03 (ADR 0029 D11):** the target IS the DE10's two-stage layout — p2 an exFAT +data partition holding `linux/linux.img`, loop-mounted as the root by the embedded stage-1 +initramfs, `/media/fat` = p2. The interim shape above stays until a board has booted it. + +**The stage-1 half is already built (2026-09-06).** `make de25-initramfs` builds the DE10's +`/init` for aarch64 (`configs/fragments/initramfs-de25nano.fragment`; the `/init`, BusyBox config +and post-build hook are shared under `board/mister/common/`) and verifies it; `scripts/ +test-initramfs.sh --board de25nano` boots it on `qemu-system-aarch64` through the DE10's eight +cases with a test kernel built from THIS board's `linux.config`. All eight pass — the `symlink` +case first exposed that patch 0031 crashed on any 7.x kernel, and the same PR re-anchored it +(ADR 0002 §8b). The switch itself is one commit touching, together: + +| File | Change | +|---|---| +| `external.mk` | the embedding fixup keys on `BR2_LINUX_KERNEL` and picks the cpio path by architecture (the guard's comment carries the exact text) | +| `Makefile` | `de25: de25-initramfs …`, and `check-initramfs` (or a DE25 twin) asserting the DE25 kernel's `CONFIG_INITRAMFS_SOURCE` | +| `board/mister/de25nano/post-image.sh` | `BOOTARGS` → `root=/dev/mmcblk0p2 loop=linux/linux.img ro rootwait console=… earlycon` (the DE10's exact shape, p2 instead of p1) | +| `board/mister/de25nano/genimage-sdcard.cfg` | p2 becomes an exFAT partition (type 0x07, label per the downloader contract) carrying `linux/linux.img` = `rootfs.ext4`; needs a host `mkfs.exfat` genimage can drive, or the DE10's installer route | +| `scripts/check-sdcard-de25.sh` | `EXPECT_ROOT_DEV` + a `loop=` assert; p2 type/label/contents | +| `docs/de25-boot-chain.md`, this file §1 | the layout table | + +Nothing in `/init` changes: it takes `root=` and `loop=` from the command line and has no +board-specific line, which is what the aarch64 leg proves. + --- ## 3. MBR, not GPT — and why that is a fail-closed choice diff --git a/docs/decisions/0002-initramfs.md b/docs/decisions/0002-initramfs.md index 3848fc4b..3cf8b966 100644 --- a/docs/decisions/0002-initramfs.md +++ b/docs/decisions/0002-initramfs.md @@ -11,8 +11,8 @@ PLAN.md **§5**; `docs/boot-chain.md` §8 (I1–I3, L1–L2) and §9. **Depends on:** **ADR 0010** (drop the out-of-tree exfat driver) — that decision is what makes the vfat fallback a *boot* requirement rather than a nicety. **Impact:** `configs/mister_initramfs_defconfig`, -`board/mister/de10nano/initramfs-overlay/init`, -`board/mister/de10nano/initramfs-busybox.config`, `external.mk`, top-level `Makefile`. +`board/mister/common/initramfs-overlay/init`, +`board/mister/common/initramfs-busybox.config`, `external.mk`, top-level `Makefile`. Creates a requirement on **P2.3** (§7) and on **P1.3** (§3). --- @@ -118,7 +118,7 @@ at the disk, not at the build. ## 4. What `/init` does -`board/mister/de10nano/initramfs-overlay/init` — 192 lines, `shellcheck`-clean under +`board/mister/common/initramfs-overlay/init` — 192 lines, `shellcheck`-clean under `bash`, `sh` and `dash`. 1. `mount` `/proc`, `/sys`, **`/dev` (devtmpfs)**. Not optional: `CONFIG_DEVTMPFS_MOUNT` @@ -338,6 +338,43 @@ ship*. So `make initramfs` now verifies the **artifact**: **After any BusyBox version bump, re-run `make initramfs` and believe its output, not this file.** +## 8b. Amendment 2026-09-06 — the same `/init` on aarch64, and what its first run found + +ADR 0029 D11 made the two-stage layout the DE25-Nano's target too. Stage 1 is now a +fragment stack per board (`configs/fragments/initramfs-common` + `initramfs-`, +`docs/buildroot-config.md` §8); `/init`, the BusyBox config and the post-build hook moved +to `board/mister/common/` and are built for both boards unchanged. `make de25-initramfs` +produces the aarch64 cpio (466,944 bytes, static musl BusyBox 1.38.0, gcc 15.3.0) and +runs the §8a artifact checks on it under `qemu-aarch64`; `scripts/test-initramfs.sh +--board de25nano` boots it on `qemu-system-aarch64 -M virt -cpu cortex-a76` through the +same eight cases as the DE10 leg — with the DE25's OWN product kernel config +(`board/mister/de25nano/linux.config` + the shared fragment) as the test kernel's base, +at the DE25's pinned 7.2.3, which the DE10 leg cannot do for its board. + +Result on the first run: **7 of 8 cases pass** (fat32, exfat, fsck-request, label, +nonascii, missing-image, rootwait — every `/init` path, unchanged). The `symlink` case +**fails with a kernel Oops** in `exfat_symlink` → `page_symlink` → `pc: 0x0`. That is not +an aarch64 bug and not an `/init` bug: board patch 0031 (ADR 0019) writes the link target +through the VFS's generic `page_symlink()`, which calls `a_ops->write_begin`, and 7.x +exFAT moved to iomap and no longer has `write_begin`/`write_end` in its address-space +ops (`fs/exfat/inode.c`, 7.2.3 vs 6.18.49). So 0031 as carried crashes on symlink +CREATION on every 7.x kernel. The DE10's shipped 6.18 kernel is unaffected; the DE10's +RT 7.2.3 kernel (`linux-patches-beta/0031` is a symlink to the same file) has the same +latent crash and had never been exercised — the DE10 QEMU leg only ever boots the 6.18 +pin. This is precisely the class of bug the harness exists to catch, and the first one a +second architecture caught for the first. + +**Fixed the same day.** `linux-patches-beta/0031` is a real re-anchored copy again (the +DE25 series links to it; the shared 6.18 patch is untouched): `exfat_symlink_write_target()` +allocates the clusters with 7.x's `exfat_map_cluster()` under `s_lock`, writes the sectors +through buffer heads, flushes them with `sync_blockdev_range()` (iomap `read_folio` reads the +device directly and never sees a dirty bdev buffer), and does the `valid_size` / +`zeroed_size` / `i_size` bookkeeping the write path would have done. Applies at `-F0` to +pristine 7.2.3; compiles for arm with the RT `.config` and for arm64 with the DE25's; the +aarch64 leg is **8/8** with the `symlink` case's full assertion set (hot+cold round-trip, +`DT_LNK`, the create+unlink cluster-leak tripwire via `statvfs`, host-side fsck-clean). What +remains unexecuted is 0031 on 32-bit 7.x — the RT kernel on a real board. + ## 9. Known gaps (deliberate, not oversights) * **`root=PARTUUID=…` is not supported.** BusyBox's `resolve_mount_spec()` diff --git a/docs/decisions/0020-sdcard-exfat-reformat-installer.md b/docs/decisions/0020-sdcard-exfat-reformat-installer.md index 285b528c..45f05fc1 100644 --- a/docs/decisions/0020-sdcard-exfat-reformat-installer.md +++ b/docs/decisions/0020-sdcard-exfat-reformat-installer.md @@ -87,7 +87,7 @@ reusing a proven reference implementation over writing new code in a boot-path c - `dd`s `uboot.img` onto the (unchanged-geometry) `0xA2` partition, `sync`s, reboots. Every step follows the defensive idiom already established by - `board/mister/de10nano/initramfs-overlay/init`: on any fatal error, print a clear + `board/mister/common/initramfs-overlay/init`: on any fatal error, print a clear banner and drop to a rescue shell rather than panicking silently — a half-reformatted card must be diagnosable, not a second brick added on top of the first-boot problem this feature exists to solve. PID 1 never exits. diff --git a/docs/decisions/0026-user-driven-exfat-fsck.md b/docs/decisions/0026-user-driven-exfat-fsck.md index 77ef31dc..23ae953a 100644 --- a/docs/decisions/0026-user-driven-exfat-fsck.md +++ b/docs/decisions/0026-user-driven-exfat-fsck.md @@ -2,8 +2,8 @@ **Status:** Accepted (2026-08-13) — decided by @mcfbytes **Impact:** `configs/mister_initramfs_defconfig` (adds `BR2_PACKAGE_EXFATPROGS`), -`board/mister/de10nano/initramfs-overlay/init`, `board/mister/de10nano/initramfs-busybox.config` -(adds `CONFIG_RM`), new files `board/mister/de10nano/initramfs-post-build.sh`, +`board/mister/common/initramfs-overlay/init`, `board/mister/common/initramfs-busybox.config` +(adds `CONFIG_RM`), new files `board/mister/common/initramfs-post-build.sh`, `board/mister/de10nano/rootfs-overlay/usr/sbin/mister-fsck-exfat`, `board/mister/de10nano/fat-payload/Scripts/check_storage.sh`; `Makefile` (`initramfs-verify`), `scripts/fetch-sdcard-payload.sh`, `install.sh`, `uninstall.sh`, diff --git a/docs/decisions/0029-de25-implementation-path.md b/docs/decisions/0029-de25-implementation-path.md index 503d4f85..2ef9d8e8 100644 --- a/docs/decisions/0029-de25-implementation-path.md +++ b/docs/decisions/0029-de25-implementation-path.md @@ -275,6 +275,13 @@ isolates the factory-SPL, DTS and SD-controller questions the first boot has to the switch; `genimage-sdcard.cfg`, `extlinux.conf`'s kernel arguments and the card checker change together. The DE25 inherits the DE10's `linux.img` update flow and downloader contract unchanged. This closes §8 Q7 of the implementation path. + **Delivered 2026-09-06:** the `initramfs-de25nano` stack (`make de25-initramfs`, the DE10's + `/init` built for aarch64, shared files under `board/mister/common/`) and + `scripts/test-initramfs.sh --board de25nano` (the eight DE10 cases on `qemu-system-aarch64`, + test kernel built from this board's own `linux.config`). 7/8 pass; the `symlink` case found + that patch 0031 Oopses on every 7.x kernel (ADR 0002 §8b) — a fix is owed before the switch, + and it applies to the DE10's RT kernel as well. The cpio is built and proven but NOT embedded + until the card switches (`external.mk` guard comment has the one-commit recipe). - **Re-open if.** The first hardware boot shows U-Boot cannot read the FAT boot files reliably, or Main_MiSTer's DE25 port needs a layout the loop root cannot provide. diff --git a/docs/main-shared-libs.md b/docs/main-shared-libs.md index 607e8d6f..02f874a4 100644 --- a/docs/main-shared-libs.md +++ b/docs/main-shared-libs.md @@ -70,7 +70,7 @@ Mapping from Main's vendored `lib/` dirs to their replacements "Main_MiSTer shared libraries" section asserts presence-in-rootfs (wildcarded versions, so Renovate bumps don't go stale-red — the PR #35 lesson, commit `1341c93`). -- **`mister_initramfs_defconfig` / installer defconfigs are deliberately +- **The stage-1 initramfs stacks / installer defconfig are deliberately unchanged** — static busybox, no ABI surface, nothing there links any of these. - Renovate manages both new pins (`lib-pin` label); hashes auto-refresh via diff --git a/docs/reproducibility.md b/docs/reproducibility.md index a079d9c2..5eaeb0ab 100644 --- a/docs/reproducibility.md +++ b/docs/reproducibility.md @@ -64,8 +64,8 @@ Four mechanisms combine, all landed in P2.5 (`configs/fragments/de10nano-image.f reference — same commit means same Buildroot tree, same kernel source, same patches, same defconfig. -4. **A checked-in config, not a locally-generated one.** `configs/fragments/de10nano-image.fragment` - and `configs/mister_initramfs_defconfig` are committed. There is no `menuconfig` +4. **A checked-in config, not a locally-generated one.** The fragment stacks under + `configs/fragments/` (image, kernel-only and stage-1 initramfs alike) are committed. There is no `menuconfig` step between "clone this commit" and "build this image" — the defconfig fully determines the build. diff --git a/docs/rt-beta-kernel.md b/docs/rt-beta-kernel.md index 05db0f77..c6389a93 100644 --- a/docs/rt-beta-kernel.md +++ b/docs/rt-beta-kernel.md @@ -74,7 +74,7 @@ with the per-variant fragment layered on at build time | `configs/fragments/` (`common` + `de10nano` + `kernel-only`) | The kernel-only base stack, shared by every kernel variant. Its toolchain/kernel fragments are the **same files** the image stack uses (since the 2026-09 fragment split — before it, a hand-mirrored copy in `configs/mister_kernel_defconfig`); `scripts/check-kernel-defconfig-sync.sh` asserts that structure holds (CI runs it before every kernel build and as a lint). With no fragment it builds the main 6.18 kernel. | | `configs/mister_rt.fragment` | Buildroot-config delta, layered on the `de10nano-kernel` fragment stack by `make rt` (kernel version → the 7.2 line, currently **`7.2.3`** — a plain point release, so Buildroot's `-rc` cgit-snapshot path in `linux/linux.mk` does not apply and it fetches the ordinary `linux-7.2.3.tar.xz` from the `v7.x` mirror directory; beta patch dir; kernel-config fragment). Merged onto the `de10nano-kernel` fragment stack (`common` + `de10nano` + `kernel-only`) via `merge_config.sh` by `make rt`. | | `board/mister/de10nano/linux-rt.fragment` | **Kernel**-config delta layered on the shared `linux.config`: `CONFIG_PREEMPT_RT=y`, the `CONFIG_UIO*` + cmdline set the doorbells need (§8), and the `CONFIG_PSTORE*` set the ramoops node needs (§9) — do not confuse the two fragment layers (RTL8814AU's in-kernel driver comes from `linux.config` itself, inherited — not duplicated here; the same is true of the watchdog, §9.3). | -| `board/mister/de10nano/linux-patches-beta/` | `series` file + **symlinks** to the shared `linux-patches/` — except `0001`, `0015`, `0030` and `0037`, which are real re-anchored copies (Buildroot patches at `-F0`; their 6.18 context or APIs drifted on 7.x — see the series header). The shared 6.18 patches stay byte-identical to stock. Applies **36 of the 37** shared patches in `linux-patches/` plus **four beta-local patches** (`0043-dts-uio-doorbells`, `0044-dts-uio-fpga-regions`, `0045-uio-writecombine` — see §8 — and `0046-dts-ramoops` — see §9; all real files in `linux-patches-beta/` only, kept out of the shared dir so the stock 6.18 build, which applies that entire directory via `BR2_LINUX_KERNEL_PATCH`, never sees them) — **40 entries**. The single shared patch it omits is `0047-btusb-mercusys-ma530-2c4e-0115`, and that omission is forced rather than chosen: `0047` backports mainline `ce21a5cf3d1f` (Mercusys MA530/MA550H, USB `2c4e:0115`), whose **first release is 7.2**, so this kernel already carries those two lines in `drivers/bluetooth/btusb.c`. Listing it would not be a harmless double-apply — at `-F0` against a pristine `v7.2` the hunk **fails** (`Hunk #1 FAILED at 786`), breaking `make rt` at patch time. The 6.18 image needs it because 6.18.y never received the commit (no `Cc: stable`), and the whole exclusion disappears the day the stock pin leaves 6.18.y. Per the series header, "already in the 7.x tree, verified against the pristine tarball" is the **only** admissible reason to omit a shared patch — "it does not apply" never is (that means re-anchor). ✅ **The five-patch gap closed 2026-08-17**: `0038`–`0042` had landed in the shared dir on 2026-07-24 and gone unlisted here for three weeks, which contradicted the standing rule in §7 item 3 (`0039` remaps NSO N64/Genesis buttons; `0040`/`0041`/`0042` are Main_MiSTer-coupled evdev-name and LED-classdev-name parity patches). Measured rather than assumed, they needed **no re-anchor at all** — plain symlinks, clean at `-F0` on 7.2 final, and `drivers/hid/` compiles for ARM with all five in (§6). (`0031` was a fifth copy until 2026-07-25, when the *shared* patch was re-anchored onto context both trees agree on and the beta entry became a symlink again; the series header explains why that is the preferred move.) `0015` is re-INCLUDED: the earlier "upstreamed in 7.2" finding was wrong (7.2 has no `FAML`/`FAMR` controller types — its left/right *nescon* support is a different thing). The separate `linux-patches-upstream/` series (carried for the exported `Linux-Kernel_MiSTer` tree only, never applied by Buildroot — `docs/patch-provenance.md` §12) is unrelated to this count and is not applied to the beta either. | +| `board/mister/de10nano/linux-patches-beta/` | `series` file + **symlinks** to the shared `linux-patches/` — except `0001`, `0015`, `0030`, `0031` and `0037`, which are real re-anchored copies (Buildroot patches at `-F0`; their 6.18 context or APIs drifted on 7.x — see the series header; `0031` since 2026-09-06 for an API delta that crashed, §6). The shared 6.18 patches stay byte-identical to stock. Applies **36 of the 37** shared patches in `linux-patches/` plus **four beta-local patches** (`0043-dts-uio-doorbells`, `0044-dts-uio-fpga-regions`, `0045-uio-writecombine` — see §8 — and `0046-dts-ramoops` — see §9; all real files in `linux-patches-beta/` only, kept out of the shared dir so the stock 6.18 build, which applies that entire directory via `BR2_LINUX_KERNEL_PATCH`, never sees them) — **40 entries**. The single shared patch it omits is `0047-btusb-mercusys-ma530-2c4e-0115`, and that omission is forced rather than chosen: `0047` backports mainline `ce21a5cf3d1f` (Mercusys MA530/MA550H, USB `2c4e:0115`), whose **first release is 7.2**, so this kernel already carries those two lines in `drivers/bluetooth/btusb.c`. Listing it would not be a harmless double-apply — at `-F0` against a pristine `v7.2` the hunk **fails** (`Hunk #1 FAILED at 786`), breaking `make rt` at patch time. The 6.18 image needs it because 6.18.y never received the commit (no `Cc: stable`), and the whole exclusion disappears the day the stock pin leaves 6.18.y. Per the series header, "already in the 7.x tree, verified against the pristine tarball" is the **only** admissible reason to omit a shared patch — "it does not apply" never is (that means re-anchor). ✅ **The five-patch gap closed 2026-08-17**: `0038`–`0042` had landed in the shared dir on 2026-07-24 and gone unlisted here for three weeks, which contradicted the standing rule in §7 item 3 (`0039` remaps NSO N64/Genesis buttons; `0040`/`0041`/`0042` are Main_MiSTer-coupled evdev-name and LED-classdev-name parity patches). Measured rather than assumed, they needed **no re-anchor at all** — plain symlinks, clean at `-F0` on 7.2 final, and `drivers/hid/` compiles for ARM with all five in (§6). (`0031` was a fifth copy until 2026-07-25, when the *shared* patch was re-anchored onto context both trees agree on and the beta entry became a symlink again; the series header explains why that is the preferred move.) `0015` is re-INCLUDED: the earlier "upstreamed in 7.2" finding was wrong (7.2 has no `FAML`/`FAMR` controller types — its left/right *nescon* support is a different thing). The separate `linux-patches-upstream/` series (carried for the exported `Linux-Kernel_MiSTer` tree only, never applied by Buildroot — `docs/patch-provenance.md` §12) is unrelated to this count and is not applied to the beta either. | | `Makefile` (`rt`, `rt-clean`, `rt-menuconfig`) | Builds into `output-rt/` (stage-1 initramfs first — its cpio is embedded into every kernel), reusing the shared dl/ccache; then stages the depmod'd module tree into `work/extra-modules-overlay/`, which the main defconfig's `BR2_ROOTFS_OVERLAY` folds into the ONE shipped `linux.img` at the next `make all`. The main `output/` is never touched by `make rt` itself. | The kernel config is the same `linux.config` + a fragment, and the patch set is @@ -230,6 +230,7 @@ card, and nothing on the card referenced it), and deliberately NOT inside | **Module-tree merge into the one linux.img** | ✅ **green** — the row's "first green run pending" was overtaken by CI run 29758320422 (2026-07-20, rc4: `build-kernel` + `build` both green, so the merge assert ran). Re-verified locally on rc5 (2026-07-28): after `make rt`, a `make all` produced `output/target/usr/lib/modules/` holding exactly `6.18.40` and `7.2.0-rc5` — two trees, no stale third — and `linux.img` passed every `check-linux-img.sh` assertion (512 MiB, pinned UUID/hash-seed, the 14-feature stock-derived set, ADR 0015 ssh-key checks) | | **RT kernel boots on the DE10-Nano** | ⚠️ **NOT on the currently pinned 7.2 — re-opened 2026-08-17 by the rc7 → 7.2 bump.** ✅ **CONFIRMED 2026-07-20 on 7.2-rc4**, which booted and ran MiSTer on real hardware. That retired the single biggest open risk on the variant, and it is how the `0037` DualSense regression was caught: booting far enough to use a controller is what exposed the shifted PS5 button map (§7 item 3). ✅ **RE-CONFIRMED 2026-08-14 on 7.2-rc7** — the Wave-1 hardware pass ran on a `7.2.0-rc7 SMP PREEMPT_RT` kernel carrying `0043`, and the doorbell nodes enumerated and delivered events (that pass is also where H-1 and H-2 were found). Boot is a **per-version claim** and every bump re-opens it, which is exactly the state this row is in now: 7.2 final is patch-verified (**40/40** at `-F0`), DTS-verified, and **built on the whole 40-entry series** (`make rt` green from clean, 2026-08-17 — see the build rows above), but it has **not been booted**. Everything that can be checked without hardware has been checked and passed; none of it is a boot. This ✅ covers rc4 and rc7 and nothing else. It also does **not** cover `0044`, `0045`, `0046` or the re-added `0038`–`0042`: none were in the series when the rc7 kernel was built, and all six have since been built but never booted — §2, §8, §9 | | **vsync/IRQ-40 latency under RT threaded IRQs** | ❌ **unproven** (the point of the exercise) — boot and general operation are confirmed, but the latency measurement that motivates RT has not been taken | +| **Patch 0031 (exFAT Samsung symlinks) on 7.x** | ✅ **FIXED 2026-09-06 — after being found BROKEN the same day**, not by this variant but by the DE25's aarch64 initramfs QEMU leg (`scripts/test-initramfs.sh --board de25nano`), the first thing ever to EXECUTE 0031 on a 7.x kernel: `exfat_symlink()` → `page_symlink()` → `a_ops->write_begin` is NULL on 7.x exFAT (iomap; no `write_begin`/`write_end`) → Oops, `pc: 0x0`, on the first symlink created. `linux-patches-beta/0031` WAS a symlink to the shared 6.18 file, so this kernel had the same crash on any `ln -s` on `/media/fat` (reading existing links was fine). The "applies at -F0" and "compiles" rows above were true and insufficient. Now a **beta-local re-anchored copy** (the fifth): `exfat_symlink_write_target()` allocates clusters via 7.x's `exfat_map_cluster()`, writes the sectors through buffer heads, `sync_blockdev_range()`s them (iomap reads go straight to the device), and does the `valid_size`/`zeroed_size`/`i_size` bookkeeping; the DE25 series links to this copy. Verified: applies at `-F0` to pristine 7.2.3 (12/12 hunks), **compiles for arm with this variant's own `output-rt` `.config`** (`fs/exfat/`, zero warnings), and the aarch64 leg's `symlink` case passes — hot+cold round-trip, `DT_LNK`, the create+unlink cluster-leak tripwire, fsck-clean (ADR 0002 §8b). Still not executed on 32-bit ARM: the DE10 leg boots 6.18 only, so the first `ln -s` on an RT-booted board is the remaining proof. | | `rtw88_8814au` firmware (`rtw88/rtw8814a_fw.bin`) present | ✅ ships via `BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88` | ## 7. What is left diff --git a/docs/ssh-ftp-parity.md b/docs/ssh-ftp-parity.md index f03911a2..7e4369c5 100644 --- a/docs/ssh-ftp-parity.md +++ b/docs/ssh-ftp-parity.md @@ -150,7 +150,7 @@ file*: machine-written state goes in `ssh.ext4`, user-supplied state goes on exF **`StrictModes` stays on (default `yes`), and the FAT path satisfies it.** `sshd` rejects an `authorized_keys` whose file or parent directories are group- or world-writable. The initramfs mounts the partition `fmask=0022,dmask=0022` with no `uid`/`gid` options -(`board/mister/de10nano/initramfs-overlay/init:27`), so the file lands root-owned `0755` +(`board/mister/common/initramfs-overlay/init:27`), so the file lands root-owned `0755` under `0755` parents — owner-writable only. Those mount options are **ours** and fixed, so this cannot be invalidated by a card mounted differently elsewhere. Disabling `StrictModes` was never necessary and `scripts/ci-tests.sh` now fails if someone does it. diff --git a/docs/util-linux-parity.md b/docs/util-linux-parity.md index c9845af5..9b5d7ead 100644 --- a/docs/util-linux-parity.md +++ b/docs/util-linux-parity.md @@ -82,7 +82,7 @@ that look like they might collide but do **not**: `setpriv` stays BusyBox. - **`setsid`** — this one *does* collide (util-linux ships it in the basic set), so BusyBox `SETSID` is disabled. (The initramfs `/init` also uses `setsid`, but that is - the **separate** stage-1 BusyBox — `board/mister/de10nano/initramfs-busybox.config` + the **separate** stage-1 BusyBox — `board/mister/common/initramfs-busybox.config` — and is untouched by this fragment.) ## The serial console: BusyBox `getty` → util-linux `agetty` diff --git a/external.mk b/external.mk index 4a97bbe8..3571946b 100644 --- a/external.mk +++ b/external.mk @@ -21,8 +21,9 @@ include $(sort $(wildcard $(BR2_EXTERNAL_MISTER_PATH)/package/*/*.mk)) # P1.10 — stage-2 half of the two-stage initramfs build (A1, PLAN.md §5, # docs/decisions/0002-initramfs.md). # -# Stage 1 (configs/mister_initramfs_defconfig, driven by the top-level Makefile's -# `initramfs` target) produces output-initramfs/images/rootfs.cpio. This block is +# Stage 1 (the `initramfs-common initramfs-de10nano` fragment stack, +# configs/fragments/stacks.mk, driven by the top-level Makefile's `initramfs` +# target) produces output-initramfs/images/rootfs.cpio. This block is # what makes the MAIN build's kernel swallow it: it injects CONFIG_INITRAMFS_SOURCE # into the kernel .config at kconfig-fixup time, which is the same mechanism # Buildroot itself uses for BR2_TARGET_ROOTFS_INITRAMFS (linux/linux.mk:412-419) — @@ -80,7 +81,27 @@ include $(sort $(wildcard $(BR2_EXTERNAL_MISTER_PATH)/package/*/*.mk)) # layer both DE10 stacks share) to keep them building — a toolchain-family # edit that changes the DE10's toolchain-fingerprint cache key, for zero # behavioural difference. -# Revisit if a third board ever needs a stage-1 cpio of its own architecture. +# +# THE DE25 SWITCH (ADR 0029 D11; docs/de25-sdcard.md §2). Since 2026-09-06 an +# aarch64 stage 1 of the same /init exists -- `make de25-initramfs`, the +# `initramfs-common initramfs-de25nano` stack, output-initramfs-de25/images/ +# rootfs.cpio, verified and booted through scripts/test-initramfs.sh --board +# de25nano -- but it is deliberately NOT embedded while the shipped DE25 card +# keeps D11's interim plain-ext4 root (the first hardware boot is meant to +# answer the SPL/DTS/SD-controller questions with as few moving parts as +# possible). When the card moves to the two-stage layout, the change HERE is +# to make the cpio path follow the architecture instead of gating on it: +# ifeq ($(BR2_LINUX_KERNEL),y) +# ifeq ($(BR2_aarch64),y) +# MISTER_INITRAMFS_CPIO ?= $(BR2_EXTERNAL_MISTER_PATH)/output-initramfs-de25/images/rootfs.cpio +# else +# MISTER_INITRAMFS_CPIO ?= $(BR2_EXTERNAL_MISTER_PATH)/output-initramfs/images/rootfs.cpio +# endif +# together with `de25: de25-initramfs ...` in the Makefile, the `loop=` +# bootargs in board/mister/de25nano/post-image.sh, the exFAT p2 in its +# genimage config and the checker -- one commit, as docs/de25-sdcard.md §2 +# lists. Until then the BR2_arm gate below stands, and the hazard it guards +# (an armv7 cpio in an aarch64 kernel) is unchanged. ifeq ($(BR2_LINUX_KERNEL)$(BR2_arm),yy) # Overridable so CI can build the two stages in separate workspaces. diff --git a/scripts/check-config-fragments.sh b/scripts/check-config-fragments.sh index 43d2b6ec..4cf51399 100755 --- a/scripts/check-config-fragments.sh +++ b/scripts/check-config-fragments.sh @@ -16,7 +16,8 @@ # edit with an unexpected knock-on — fails until the hash is deliberately # updated with a commit that explains why. # -# What it asserts, per stack (de10nano, de10nano-kernel, de25nano, and +# What it asserts, per stack (de10nano, de10nano-kernel, de25nano, +# initramfs-de10nano, initramfs-de25nano, and # de10nano-kernel + configs/mister_.fragment for every kernel # variant): # (a) NO REDEFINITION between fragments: a symbol defined by two fragments @@ -65,6 +66,12 @@ # paste, .github/workflows/renovate-hash-sync.yml case 8 commits them # on a Renovate bump PR, and the build is allowed to proceed so the # bump PR still proves it builds. +# (f) STAGE-1 ARCH LOCKSTEP: every symbol an initramfs- fragment +# sets is set to the same value in .fragment (text-level, over +# the stripped fragments). The stage-1 cpio is built by its own stack, +# which cannot include the board fragment (that carries the kernel +# stanza), so the arch/ABI + headers-series lines are restated -- and +# a restated line that drifts builds a stage 1 for the wrong target. # # Cost: needs the pinned Buildroot tree (fetched/unpacked by `make # buildroot-unpack` if absent — a 10 MB download, no compile beyond @@ -382,6 +389,40 @@ elif [ "${#only[@]}" -eq 0 ]; then fail "resolved-level lockstep: de10nano or de10nano-kernel stack missing from stacks.mk" fi +# (f) stage-1 arch lockstep. Each initramfs- fragment restates the +# board's arch/ABI + headers-series lines (a stage-1 stack cannot include +# .fragment itself: that carries the kernel stanza). The restated +# values must be the board's: a CPU-tuning or headers-series change made in +# .fragment and not here would build a stage 1 for a different target +# than the kernel it is embedded in. Text-level, over the stripped fragments, +# and symmetric in what it demands -- every symbol the initramfs- +# fragment sets must be set to the same value in .fragment. (Symbols +# the board sets and stage 1 does not are fine: stage 1 needs no kernel.) +if [ "${#only[@]}" -eq 0 ]; then + for var in $(config_stack_vars); do + case "$var" in INITRAMFS_*) ;; *) continue ;; esac + board_var="${var#INITRAMFS_}" + board_lc=$(config_stack_label "$board_var") + ir_frag="$CONFIG_FRAGMENT_DIR/initramfs-$board_lc.fragment" + board_frag="$CONFIG_FRAGMENT_DIR/$board_lc.fragment" + [ -f "$ir_frag" ] || { fail "stage-1 arch lockstep: $var names no initramfs-$board_lc.fragment"; continue; } + [ -f "$board_frag" ] || { fail "stage-1 arch lockstep: $var has no board fragment $board_lc.fragment to agree with"; continue; } + bad="" + while IFS= read -r line; do + [ -n "$line" ] || continue + sym=$(config_line_symbol "$line") + if ! config_strip_fragment "$board_frag" | grep -qxF -- "$line"; then + bad="$bad $sym" + fi + done < <(config_strip_fragment "$ir_frag") + if [ -n "$bad" ]; then + fail "stage-1 arch lockstep: initramfs-$board_lc.fragment sets$bad differently from (or absent in) $board_lc.fragment -- the stage-1 cpio must be built for the same arch/ABI and headers series as the kernel it is embedded in; change both files in one commit" + else + echo "==> stage-1 arch lockstep: initramfs-$board_lc.fragment agrees with $board_lc.fragment on every symbol it sets ($(config_strip_fragment "$ir_frag" | grep -c .))" + fi + done +fi + # --- Path consumers outside stacks.mk ----------------------------------------- # stacks.mk is the source of truth for WHICH fragments exist, but several # consumers must name fragment files literally and cannot read it: diff --git a/scripts/ci-tests.sh b/scripts/ci-tests.sh index 58bb41f5..2871a3ee 100755 --- a/scripts/ci-tests.sh +++ b/scripts/ci-tests.sh @@ -298,6 +298,38 @@ else fi fi +# The DE25-Nano's stage 1: the SAME /init built for aarch64 (`make +# de25-initramfs`, ADR 0029 D11), checked the same two ways -- the Makefile's +# structural cpio assertions and the eight QEMU cases, on qemu-system-aarch64. +# Gated on the cpio EXISTING rather than on a board flag: this suite runs +# against a DE10 image, and a tree that has never built the DE25 stage 1 has +# nothing to check here -- but one that has must not skip it silently. +DE25_INITRAMFS_CPIO="$ROOT/output-initramfs-de25/images/rootfs.cpio" +if [ ! -f "$DE25_INITRAMFS_CPIO" ]; then + skip "de25-initramfs-verify (aarch64 cpio applet/structure check)" "no $DE25_INITRAMFS_CPIO -- 'make de25-initramfs' not run in this tree" + skip "test-initramfs.sh --board de25nano (aarch64 QEMU boot test, 8 cases)" "same: no DE25 stage-1 cpio built" +else + printf -- '--- de25-initramfs-verify (Makefile: the DE10 initramfs-verify assertions, on the aarch64 cpio under qemu-aarch64) ---\n' + if ( cd "$ROOT" && make --no-print-directory de25-initramfs-verify ); then + pass "de25-initramfs-verify (aarch64 cpio applet/structure check)" + else + fail "de25-initramfs-verify (aarch64 cpio applet/structure check)" + fi + if [ "${CI_TESTS_SKIP_QEMU_SYSTEM:-0}" = "1" ]; then + skip "test-initramfs.sh --board de25nano (aarch64 QEMU boot test, 8 cases)" "CI_TESTS_SKIP_QEMU_SYSTEM=1" + elif ! have qemu-system-aarch64; then + skip "test-initramfs.sh --board de25nano (aarch64 QEMU boot test, 8 cases)" "qemu-system-aarch64 not found on PATH" + else + printf -- '--- test-initramfs.sh --board de25nano: the same 8 cases on qemu-system-aarch64 -M virt ---\n' + printf ' (builds/reuses an aarch64 QEMU test kernel from the DE25 product config and boots it 8 times)\n' + if "$ROOT/scripts/test-initramfs.sh" --board de25nano; then + pass "test-initramfs.sh --board de25nano (aarch64 QEMU boot test, 8 cases)" + else + fail "test-initramfs.sh --board de25nano (aarch64 QEMU boot test, 8 cases)" "one or more of the 8 cases failed -- see output above" + fi + fi +fi + # ============================================================================= section "ABI / stock-binary smoke (P2.2 + P2.8 core checks)" # ============================================================================= @@ -1539,7 +1571,7 @@ if tar_has "etc/ssh/sshd_config"; then # StrictModes must stay at its default (yes). The FAT path above satisfies it # only because the initramfs mounts with fmask=0022,dmask=0022 - # (board/mister/de10nano/initramfs-overlay/init); an explicit 'StrictModes no' + # (board/mister/common/initramfs-overlay/init); an explicit 'StrictModes no' # would mean someone worked around a permissions problem instead of fixing it. if grep -qE '^StrictModes[[:space:]]+no' "$sshd_conf"; then fail "sshd_config: StrictModes not disabled" \ diff --git a/scripts/list-kernel-variants.sh b/scripts/list-kernel-variants.sh index 3d2ddc5d..082be192 100755 --- a/scripts/list-kernel-variants.sh +++ b/scripts/list-kernel-variants.sh @@ -30,14 +30,15 @@ # # EXCLUDED ON PURPOSE: the board/common fragment stacks under # configs/fragments/ (the shared kernel-only BASE every variant builds against -# lives there, configs/fragments/stacks.mk) and configs/mister_initramfs_defconfig -# (the stage-1 initramfs config). The stacks live in a SUBDIRECTORY and the -# initramfs defconfig carries no `.fragment` suffix, so the glob below already +# lives there, configs/fragments/stacks.mk, and so do the two stage-1 +# initramfs stacks) and configs/mister_installer_defconfig (the SD-card +# installer config). The stacks live in a SUBDIRECTORY and the installer +# defconfig carries no `.fragment` suffix, so the glob below already # excludes both without any special-casing — the explicit denylist further # down exists only so that fact survives a future rename or move instead of # relying on an accident of path or extension. # -# ALSO RESERVED: the variant name "main". Unlike the two defconfigs above, +# ALSO RESERVED: the variant name "main". Unlike the files above, # a hypothetical configs/mister_main.fragment WOULD match the *.fragment glob # below -- but .github/actions/buildroot-build/action.yml's `case` matches # `main)` as its FULL-IMAGE build, not a kernel-only one, so that name reaching @@ -68,9 +69,9 @@ variants=() shopt -s nullglob for f in configs/mister_*.fragment; do case "$f" in - configs/fragments/* | configs/mister_initramfs_defconfig) + configs/fragments/* | configs/mister_installer_defconfig) # Unreachable given the *.fragment glob above -- the stacks are in a - # subdirectory and the initramfs defconfig lacks the extension -- but + # subdirectory and the installer defconfig lacks the extension -- but # kept explicit per the EXCLUDED ON PURPOSE note in the header, so # this is documented in code, not just prose. continue diff --git a/scripts/test-initramfs.sh b/scripts/test-initramfs.sh index 29441a18..7bcdf254 100755 --- a/scripts/test-initramfs.sh +++ b/scripts/test-initramfs.sh @@ -3,14 +3,13 @@ # scripts/test-initramfs.sh — CI-runnable QEMU boot test of the initramfs /init # (TASKS.md P1.12; constraint A7; docs/decisions/0002-initramfs.md §8). # -# QEMU has no Cyclone V SoC machine model, so this cannot boot the real -# DE10-Nano product kernel. What it CAN do, and what /init actually needs -# proven, is boot the REAL, unmodified stage-1 cpio -# (output-initramfs/images/rootfs.cpio) inside a generic `qemu-system-arm -M -# virt` kernel, attach a synthetic MBR disk shaped like a real MiSTer SD card -# (a FAT/exFAT data partition containing linux/linux.img), and assert -- from -# INSIDE the switched-root system -- every invariant /init is supposed to -# have established. Eight cases: +# QEMU has no Cyclone V (or Agilex 5) SoC machine model, so this cannot boot +# either board's product kernel. What it CAN do, and what /init actually +# needs proven, is boot the REAL, unmodified stage-1 cpio inside a generic +# `-M virt` kernel, attach a synthetic MBR disk shaped like a real MiSTer SD +# card (a FAT/exFAT data partition containing linux/linux.img), and assert -- +# from INSIDE the switched-root system -- every invariant /init is supposed +# to have established. Eight cases: # # fat32 FAT32 card: exfat probe fails, falls back to vfat (utf8=1) # exfat exFAT card: mounts on the first try @@ -34,20 +33,50 @@ # entry on it, so this synthetic test is the only place that regression can # ever be caught (see the case function below for the full argument). # -# Usage: scripts/test-initramfs.sh [case ...] -# With no arguments, runs all eight cases. Exit 0 iff every requested case -# passed; nonzero otherwise (wired for P4.1's CI job). +# Usage: scripts/test-initramfs.sh [--board de10nano|de25nano] [case ...] +# With no case arguments, runs all eight cases. Exit 0 iff every requested +# case passed; nonzero otherwise (wired for P4.1's CI job). +# +# TWO BOARDS, ONE /init (ADR 0029 D11, 2026-09-06). The stage-1 /init is +# arch-neutral and is built for both boards from the same fragment stack base +# (configs/fragments/initramfs-common.fragment); `--board` picks which built +# cpio to boot and which machine to boot it on: +# +# de10nano (default) output-initramfs/images/rootfs.cpio, armv7, on +# `qemu-system-arm -M virt` with a multi_v7_defconfig +# kernel at the DE10's pinned version. +# de25nano output-initramfs-de25/images/rootfs.cpio, aarch64, on +# `qemu-system-aarch64 -M virt -cpu cortex-a76` with a +# kernel built from the DE25's OWN product config +# (board/mister/de25nano/linux.config + the shared +# board/mister/common/linux-mister.fragment) at the +# DE25's pinned version, plus this harness's virtio/ +# PL011 fragment. There is no arm64 "multi_v7" to lean +# on, and the product config is minimal enough to build +# in minutes -- so the DE25 leg also proves the product +# config's own exfat/vfat/loop/ext4 choices, which the +# DE10 leg deliberately does not (its product kernel +# cannot run under QEMU at all). +# +# Every case, cmdline and assertion is identical between the two legs. The +# cross compiler for the DE25 leg is 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` needed. # # Prerequisites (all checked explicitly, with an actionable message, before # anything runs): -# - `make initramfs` already run (output-initramfs/images/rootfs.cpio and -# output-initramfs/host/bin/{mcopy,mmd} must exist) -# - the Buildroot host cross toolchain on PATH or at output/host/bin -# (arm-buildroot-linux-gnueabihf-gcc) -# - qemu-system-arm, mkfs.vfat, mkfs.exfat, sfdisk, mke2fs, cpio +# - `make initramfs` (or `make de25-initramfs`) already run, so the cpio +# exists +# - the matching Buildroot cross toolchain on PATH or under the output dir +# the board uses (arm-buildroot-linux-gnueabihf-gcc from output/host/bin; +# aarch64-buildroot-linux-musl-gcc from output-initramfs-de25/host/bin) +# - qemu-system-arm / qemu-system-aarch64, mkfs.vfat, mkfs.exfat, sfdisk, +# mke2fs, cpio, mtools (mcopy, mmd) # - a QEMU-bootable test kernel: reused from a cache -# (work/test-initramfs-kbuild/) if present, else built fresh from the -# pinned pristine source (work/linux-.tar.xz) and +# (work/test-initramfs[-de25]-kbuild/) if present, else built fresh from +# the pinned pristine source (dl/linux/linux-.tar.xz, the +# tarball the Buildroot kernel build already fetched; or +# work/linux-.tar.xz) and # scripts/test-initramfs/qemu-test-kernel.config -- see ensure_qemu_kernel(). set -uo pipefail # deliberately not -e: run every requested case, then report @@ -56,42 +85,116 @@ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$HERE/.." && pwd)" SUPPORT="$HERE/test-initramfs" -CPIO="$ROOT/output-initramfs/images/rootfs.cpio" -INIT_SRC="$ROOT/board/mister/de10nano/initramfs-overlay/init" -MTOOLS_BIN="$ROOT/output-initramfs/host/bin" +# --- Board selection ------------------------------------------------------- +# `--board` is consumed here, before any board-dependent path is derived; the +# remaining arguments are case names for main(). TEST_INITRAMFS_BOARD in the +# environment is the same switch for callers that cannot pass arguments +# (ci-tests.sh's per-board legs use the flag). +BOARD="${TEST_INITRAMFS_BOARD:-de10nano}" +_args=() +while [ "$#" -gt 0 ]; do + case "$1" in + --board) shift; BOARD="${1:-}" ;; + --board=*) BOARD="${1#--board=}" ;; + *) _args+=("$1") ;; + esac + shift +done +set -- ${_args[@]+"${_args[@]}"} + +case "$BOARD" in +de10nano) + CPIO="$ROOT/output-initramfs/images/rootfs.cpio" + CPIO_MAKE_TARGET="initramfs" + KARCH=arm + CROSS_COMPILE="${CROSS_COMPILE:-arm-buildroot-linux-gnueabihf-}" + TOOLCHAIN_BIN="$ROOT/output/host/bin" + QEMU_SYSTEM=qemu-system-arm + QEMU_MACHINE=(-M virt) + KERNEL_IMAGE_TARGET=zImage + KERNEL_IMAGE_REL=arch/arm/boot/zImage + # The generic ARM kernel: multi_v7_defconfig + this harness's fragment. + KERNEL_BASE_DEFCONFIG=multi_v7_defconfig + KERNEL_BASE_FILES=() + PIN_FRAGMENT="$ROOT/configs/fragments/de10nano.fragment" + EXFAT_SYMLINK_PATCH="$ROOT/board/mister/de10nano/linux-patches/0031-exfat-samsung-symlinks.patch" + CACHE_TAG="" + ;; +de25nano) + CPIO="$ROOT/output-initramfs-de25/images/rootfs.cpio" + CPIO_MAKE_TARGET="de25-initramfs" + KARCH=arm64 + CROSS_COMPILE="${CROSS_COMPILE:-aarch64-buildroot-linux-musl-}" + TOOLCHAIN_BIN="$ROOT/output-initramfs-de25/host/bin" + QEMU_SYSTEM=qemu-system-aarch64 + # `-M virt` has no default CPU on aarch64; cortex-a76 is the DE25's big + # core and what the stage-1 toolchain tunes for (BR2_cortex_a76_a55 -- + # an armv8.2 target whose LSE atomics a cortex-a53 model would SIGILL on). + QEMU_MACHINE=(-M virt -cpu cortex-a76) + KERNEL_IMAGE_TARGET=Image + KERNEL_IMAGE_REL=arch/arm64/boot/Image + # The DE25's own product kernel config as the base (see the header). + KERNEL_BASE_DEFCONFIG="" + KERNEL_BASE_FILES=("$ROOT/board/mister/de25nano/linux.config" + "$ROOT/board/mister/common/linux-mister.fragment") + PIN_FRAGMENT="$ROOT/configs/fragments/de25nano.fragment" + # Resolved through the DE25's own patch dir, which links to the 7.x + # re-anchored copy in linux-patches-beta/ (since 2026-09-06 -- this + # very case found the shared 6.18 form Oopsing on 7.x, ADR 0002 §8b). + EXFAT_SYMLINK_PATCH="$ROOT/board/mister/de25nano/linux-patches/0031-exfat-samsung-symlinks.patch" + CACHE_TAG="-de25" + ;; +*) + printf 'test-initramfs.sh: FATAL: unknown --board %s (known: de10nano, de25nano)\n' "'$BOARD'" >&2 + exit 2 + ;; +esac + +INIT_SRC="$ROOT/board/mister/common/initramfs-overlay/init" MARKER_C="$SUPPORT/marker-init.c" TEST_SYMLINK_C="$SUPPORT/test-symlink.c" KERNEL_FRAGMENT="$SUPPORT/qemu-test-kernel.config" -EXFAT_SYMLINK_PATCH="$ROOT/board/mister/de10nano/linux-patches/0031-exfat-samsung-symlinks.patch" -CROSS_COMPILE="${CROSS_COMPILE:-arm-buildroot-linux-gnueabihf-}" -export PATH="$ROOT/output/host/bin:$MTOOLS_BIN:$PATH" +export PATH="$TOOLCHAIN_BIN:$PATH" export MTOOLS_SKIP_CHECK=1 # Cache locations. Overridable so CI can point these at a persistent cache # across runs (a full kernel build is the expensive part of this script by a -# wide margin) or a scratch dir for a fully clean run. -WORK="${TEST_INITRAMFS_WORK:-$ROOT/work/test-initramfs}" -KBUILD="${TEST_INITRAMFS_KBUILD:-$ROOT/work/test-initramfs-kbuild}" -KERNEL_SRC="${TEST_INITRAMFS_KERNEL_SRC:-$ROOT/work/test-initramfs-kernel-src}" -# Derived from the product defconfig, NOT hardcoded: board patch 0031 (applied +# wide margin) or a scratch dir for a fully clean run. The DE25 leg's caches +# carry a -de25 tag so the two boards' kernel trees never share a directory. +WORK="${TEST_INITRAMFS_WORK:-$ROOT/work/test-initramfs$CACHE_TAG}" +KBUILD="${TEST_INITRAMFS_KBUILD:-$ROOT/work/test-initramfs$CACHE_TAG-kbuild}" +KERNEL_SRC="${TEST_INITRAMFS_KERNEL_SRC:-$ROOT/work/test-initramfs$CACHE_TAG-kernel-src}" +# Derived from the board's fragment, NOT hardcoded: board patch 0031 (applied # below) tracks the pinned kernel's APIs and will not compile against an older # one -- 6.18.40 gave exfat_remove_entries() a 4th arg, so a stale pin here # fails the QEMU kernel build with a confusing "too few arguments". Reading the # pin keeps this test kernel on the same version the image ships, which is what # this script's header already claims it does. -KERNEL_VERSION="${TEST_INITRAMFS_KERNEL_VERSION:-$(sed -n 's/^BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="\(.*\)"$/\1/p' "$ROOT/configs/fragments/de10nano.fragment")}" +KERNEL_VERSION="${TEST_INITRAMFS_KERNEL_VERSION:-$(sed -n 's/^BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="\(.*\)"$/\1/p' "$PIN_FRAGMENT")}" # Inline, not die() -- that is defined further down, and this block runs before # it. Under `set -uo pipefail` (no -e) an undefined-function call would print # "command not found" and CARRY ON, which is exactly the silent failure this # guard exists to prevent. [ -n "$KERNEL_VERSION" ] || { printf 'test-initramfs.sh: FATAL: %s\n' \ - "could not read BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE from configs/fragments/de10nano.fragment" >&2 + "could not read BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE from ${PIN_FRAGMENT#"$ROOT"/}" >&2 exit 2 } -KERNEL_TARBALL="${TEST_INITRAMFS_KERNEL_TARBALL:-$ROOT/work/linux-$KERNEL_VERSION.tar.xz}" -QEMU_ZIMAGE="$KBUILD/arch/arm/boot/zImage" +# The pristine source tarball. Buildroot's own kernel build fetches it into +# dl/linux/ (BR2_DL_DIR), so that is where it is on any tree that has built +# the board's image; work/ is the older convention and stays as the fallback +# for a tarball dropped there by hand. +if [ -z "${TEST_INITRAMFS_KERNEL_TARBALL:-}" ]; then + if [ -f "$ROOT/dl/linux/linux-$KERNEL_VERSION.tar.xz" ]; then + KERNEL_TARBALL="$ROOT/dl/linux/linux-$KERNEL_VERSION.tar.xz" + else + KERNEL_TARBALL="$ROOT/work/linux-$KERNEL_VERSION.tar.xz" + fi +else + KERNEL_TARBALL="$TEST_INITRAMFS_KERNEL_TARBALL" +fi +QEMU_KERNEL="$KBUILD/$KERNEL_IMAGE_REL" BUILD="$WORK/run" MARKER_INIT="$WORK/marker-init" @@ -102,6 +205,9 @@ TEST_SYMLINK_BIN="$WORK/test-symlink" # shell within a couple of seconds of real 6.18 kernel + qemu virt boot time; # 30s is generous headroom, not a tuned minimum. `-k 10` guarantees qemu is # actually gone even if it ignores SIGTERM (observed in sandboxed CI runners). +# The aarch64 leg boots a 7.2 product-config kernel under TCG on a single +# emulated cortex-a76; measured 2026-09-06 it reaches switch_root in well under +# 10 s on a loaded 32-core host, so the same budget holds for both boards. BOOT_TIMEOUT=30 BOOT_TIMEOUT_KILL=10 @@ -150,7 +256,7 @@ need() { # ---------------------------------------------------------------- prereqs check_prereqs() { - need qemu-system-arm "install qemu-system-arm" + need "$QEMU_SYSTEM" "install $QEMU_SYSTEM" need mkfs.vfat "install dosfstools" need mkfs.exfat "install exfatprogs (or exfat-utils)" need fsck.exfat "install exfatprogs (or exfat-utils)" @@ -158,38 +264,52 @@ check_prereqs() { need mke2fs "install e2fsprogs" need cpio "install cpio" need patch "install patch" - need "${CROSS_COMPILE}gcc" "expected the Buildroot host toolchain on PATH (output/host/bin)" - need mcopy "run 'make initramfs' first (builds host mtools under output-initramfs/host/bin)" - need mmd "run 'make initramfs' first (builds host mtools under output-initramfs/host/bin)" + need "${CROSS_COMPILE}gcc" "expected the Buildroot cross toolchain on PATH (${TOOLCHAIN_BIN#"$ROOT"/}); run 'make $CPIO_MAKE_TARGET' first, or set CROSS_COMPILE" + need mcopy "install mtools" + need mmd "install mtools" - [ -f "$CPIO" ] || die "no $CPIO -- run 'make initramfs' first." + [ -f "$CPIO" ] || die "no $CPIO -- run 'make $CPIO_MAKE_TARGET' first." [ -f "$INIT_SRC" ] || die "missing $INIT_SRC" [ -f "$MARKER_C" ] || die "missing $MARKER_C" [ -f "$TEST_SYMLINK_C" ] || die "missing $TEST_SYMLINK_C" [ -f "$KERNEL_FRAGMENT" ] || die "missing $KERNEL_FRAGMENT" [ -f "$EXFAT_SYMLINK_PATCH" ] || die "missing $EXFAT_SYMLINK_PATCH" + local f + for f in ${KERNEL_BASE_FILES[@]+"${KERNEL_BASE_FILES[@]}"}; do + [ -f "$f" ] || die "missing $f" + done } # ---------------------------------------------------------- the QEMU test kernel -# NOT the DE10-Nano product kernel (board/mister/de10nano/linux.config) -- see -# scripts/test-initramfs/qemu-test-kernel.config's header. Built out-of-tree -# (O=) against a pristine source tree at the pinned kernel version so incremental rebuilds (e.g. -# after /init changes -- see the re-point below) are cheap. +# For the DE10 leg: NOT the product kernel (board/mister/de10nano/linux.config) +# -- see scripts/test-initramfs/qemu-test-kernel.config's header. For the DE25 +# leg: the product config IS the base (see the file header for why). Either +# way it is built out-of-tree (O=) against a pristine source tree at the +# board's pinned kernel version so incremental rebuilds (e.g. after /init +# changes -- see the re-point below) are cheap. ensure_qemu_kernel() { if [ ! -f "$KBUILD/Makefile" ]; then log "no cached QEMU test kernel at $KBUILD -- building from scratch" [ -f "$KERNEL_TARBALL" ] || die \ "$KERNEL_TARBALL missing; cannot bootstrap the QEMU test kernel." \ - "Fetch the pinned kernel source tarball (same one P1.3's kernel" \ - "build already uses) to that path, or point TEST_INITRAMFS_KERNEL_TARBALL" \ - "at it." + "Fetch the pinned kernel source tarball (same one the board's Buildroot" \ + "kernel build already uses -- dl/linux/) to that path, or point" \ + "TEST_INITRAMFS_KERNEL_TARBALL at it." mkdir -p "$KERNEL_SRC" log "extracting $KERNEL_TARBALL" tar -C "$KERNEL_SRC" --strip-components=1 -xf "$KERNEL_TARBALL" mkdir -p "$KBUILD" - log "configuring: multi_v7_defconfig + $KERNEL_FRAGMENT" - make -C "$KERNEL_SRC" O="$KBUILD" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" \ - multi_v7_defconfig + if [ -n "$KERNEL_BASE_DEFCONFIG" ]; then + log "configuring: $KERNEL_BASE_DEFCONFIG + $KERNEL_FRAGMENT" + make -C "$KERNEL_SRC" O="$KBUILD" ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" \ + "$KERNEL_BASE_DEFCONFIG" + else + # A minimal product config as the base: seed .config with it and + # let merge_config.sh layer the rest on. The seed is the first + # base file; the others are merged like the harness fragment. + log "configuring: ${KERNEL_BASE_FILES[*]#"$ROOT"/} + $KERNEL_FRAGMENT" + cp "${KERNEL_BASE_FILES[0]}" "$KBUILD/.config" + fi # merge_config.sh finishes with a BARE `make ... alldefconfig` in the # CURRENT directory -- it has no -C. Run from this repo's root (the # normal way to invoke this script) that `make` hits the wrapper @@ -202,9 +322,11 @@ ensure_qemu_kernel() { # nothing like this. So: cd into the kernel tree, give its make the # ARCH it needs, and refuse to continue if the merge fails. The # fragment-survival check after olddefconfig below is the backstop. - (cd "$KERNEL_SRC" && ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" \ + (cd "$KERNEL_SRC" && ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" \ scripts/kconfig/merge_config.sh -O "$KBUILD" \ - "$KBUILD/.config" "$KERNEL_FRAGMENT" >&2) \ + "$KBUILD/.config" \ + ${KERNEL_BASE_FILES[@]+"${KERNEL_BASE_FILES[@]:1}"} \ + "$KERNEL_FRAGMENT" >&2) \ || die "merge_config.sh failed for $KERNEL_FRAGMENT" elif [ ! -d "$KERNEL_SRC" ]; then die "$KBUILD exists but its source tree $KERNEL_SRC does not." \ @@ -228,7 +350,7 @@ ensure_qemu_kernel() { # straight to nothing-to-do), silently testing a STALE /init if skipped. "$KERNEL_SRC/scripts/config" --file "$KBUILD/.config" \ --set-str CONFIG_INITRAMFS_SOURCE "$CPIO" - make -C "$KERNEL_SRC" O="$KBUILD" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" \ + make -C "$KERNEL_SRC" O="$KBUILD" ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" \ olddefconfig >&2 # Every `CONFIG_X=y` the fragment asks for must be in the resolved config, @@ -248,16 +370,16 @@ ensure_qemu_kernel() { "merge_config.sh cwd fix, remove it (rm -rf $KBUILD) and re-run to" \ "bootstrap a correct one." - log "building QEMU test kernel zImage (embedding $(basename "$CPIO"))" - make -C "$KERNEL_SRC" O="$KBUILD" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" \ - -j"$(nproc)" zImage >&2 + log "building QEMU test kernel $KERNEL_IMAGE_TARGET (embedding $(basename "$CPIO"))" + make -C "$KERNEL_SRC" O="$KBUILD" ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" \ + -j"$(nproc)" "$KERNEL_IMAGE_TARGET" >&2 - [ -f "$QEMU_ZIMAGE" ] || die "kernel build finished but produced no $QEMU_ZIMAGE" + [ -f "$QEMU_KERNEL" ] || die "kernel build finished but produced no $QEMU_KERNEL" } # ---------------------------------------------------------------- marker-init # Compiled statically (no shared libs available in the tiny ext4 image it -# ships in) for ARM, since it runs under the guest, not the host. +# ships in) for the guest's architecture, since it runs there, not on the host. build_marker_inits() { log "compiling marker-init (+ nonascii variant) with ${CROSS_COMPILE}gcc" "${CROSS_COMPILE}gcc" -O2 -static -Wall -Wextra -o "$MARKER_INIT" "$MARKER_C" \ @@ -303,8 +425,8 @@ boot_qemu() { local diskimg=$1 cmdline=$2 logfile=$3 shift 3 timeout -k "$BOOT_TIMEOUT_KILL" "$BOOT_TIMEOUT" \ - qemu-system-arm -M virt -m 512 -nographic -no-reboot \ - -kernel "$QEMU_ZIMAGE" \ + "$QEMU_SYSTEM" "${QEMU_MACHINE[@]}" -m 512 -nographic -no-reboot \ + -kernel "$QEMU_KERNEL" \ -drive file="$diskimg",format=raw,if=none,id=sd0 \ -device virtio-blk-device,drive=sd0 \ "$@" \ @@ -372,7 +494,7 @@ boot_qemu() { # Callers' commands may reference $DATADEV (the MBR disk's data partition) # and $IMGDEV (the raw imgfile's whole-disk device) -- both set by the time # any caller command runs. There is no grep, awk or sed in this BusyBox -# config (allnoconfig-derived, board/mister/de10nano/initramfs-busybox.config +# config (allnoconfig-derived, board/mister/common/initramfs-busybox.config # -- confirmed missing directly, not assumed), so the detection below is # plain `test`/`case` only. populate_in_guest() { @@ -393,8 +515,8 @@ populate_in_guest() { # `exec {fd}>` (the writer) rendezvous with it. Getting this order backwards # deadlocks the whole script on the very first `exec` -- found exactly this # way: it hung with wchan=wait_for_partner and no qemu process ever existed. - qemu-system-arm -M virt -m 512 -nographic -no-reboot \ - -kernel "$QEMU_ZIMAGE" \ + "$QEMU_SYSTEM" "${QEMU_MACHINE[@]}" -m 512 -nographic -no-reboot \ + -kernel "$QEMU_KERNEL" \ -drive file="$diskimg",format=raw,if=none,id=sd0 \ -device virtio-blk-device,drive=sd0 \ -drive file="$imgfile",format=raw,if=none,id=img0 \ @@ -899,6 +1021,7 @@ main() { ensure_qemu_kernel build_marker_inits + log "board: $BOARD ($KARCH, $QEMU_SYSTEM ${QEMU_MACHINE[*]}, kernel $KERNEL_VERSION, cpio ${CPIO#"$ROOT"/})" log "running ${#requested[@]} case(s): ${requested[*]}" echo "" local c @@ -907,7 +1030,7 @@ main() { done echo "" - echo "==== scripts/test-initramfs.sh summary ($RAN case(s)) ====" + echo "==== scripts/test-initramfs.sh summary ($BOARD, $RAN case(s)) ====" printf '%s\n' "${SUMMARY[@]}" if [ "$FAILED" -ne 0 ]; then echo "==== RESULT: FAIL ====" diff --git a/scripts/test-initramfs/marker-init.c b/scripts/test-initramfs/marker-init.c index 8c3b423e..01baf109 100644 --- a/scripts/test-initramfs/marker-init.c +++ b/scripts/test-initramfs/marker-init.c @@ -7,7 +7,7 @@ * * This is the /sbin/init baked into the tiny ext4 image that the REAL * initramfs /init loop-mounts and switch_root's into (see - * board/mister/de10nano/initramfs-overlay/init). It runs as PID 1 in the + * board/mister/common/initramfs-overlay/init). It runs as PID 1 in the * switched root and asserts, from *inside* the booted system, every property * /init is supposed to have established, then powers the machine off so QEMU * exits with a deterministic status. diff --git a/scripts/test-initramfs/qemu-test-kernel.config b/scripts/test-initramfs/qemu-test-kernel.config index 46856c7f..7ad3009e 100644 --- a/scripts/test-initramfs/qemu-test-kernel.config +++ b/scripts/test-initramfs/qemu-test-kernel.config @@ -2,13 +2,22 @@ # # TASKS.md P1.12 / A7: "QEMU has no Cyclone V SoC machine model. CI runs ... # the initramfs logic on a generic QEMU ARM machine." This fragment, merged -# onto multi_v7_defconfig (scripts/test-initramfs.sh does this via -# scripts/kconfig/merge_config.sh), builds a kernel `qemu-system-arm -M virt` -# can actually boot. It is NOT the DE10-Nano product kernel -# (board/mister/de10nano/linux.config) and never will be -- different SoC, -# different UART, no PL310, virtio instead of the real MMC/USB stack. Its only -# job is to run the REAL, unmodified initramfs cpio -# (output-initramfs/images/rootfs.cpio) far enough to prove /init's own logic. +# onto a base config (scripts/test-initramfs.sh does this via +# scripts/kconfig/merge_config.sh), builds a kernel `-M virt` can actually +# boot. Every symbol in it is arch-neutral, so the same file serves both legs: +# --board de10nano base = multi_v7_defconfig; qemu-system-arm. NOT the +# DE10-Nano product kernel (board/mister/de10nano/ +# linux.config) and never will be -- different SoC, +# different UART, no PL310, virtio instead of the real +# MMC/USB stack. +# --board de25nano base = the DE25's OWN product config (board/mister/ +# de25nano/linux.config + board/mister/common/ +# linux-mister.fragment); qemu-system-aarch64. arm64 has +# no multi_v7 equivalent, and the product config is +# minimal enough to build in minutes. +# Its only job is to run the REAL, unmodified initramfs cpio +# (output-initramfs[-de25]/images/rootfs.cpio) far enough to prove /init's +# own logic. # # Promoted from work/p1.10-qemu/kernel-fragment.config (P1.10's throwaway # harness) verbatim, plus NLS_CODEPAGE_437 kept for the mojibake case this