diff --git a/.github/actions/buildroot-build/action.yml b/.github/actions/buildroot-build/action.yml index 071aaa6..45a8fc7 100644 --- a/.github/actions/buildroot-build/action.yml +++ b/.github/actions/buildroot-build/action.yml @@ -29,9 +29,10 @@ name: Build the MiSTer image (Buildroot) description: >- Prepares a GitHub-hosted runner for a Buildroot build (disk reclaim, apt deps, five input caches), then builds the requested variant. main: configures - from the tracked defconfig and runs `make all` — linux.img + zImage_dtb in - output/images/. Any other name is a kernel-only variant (`make ` over - configs/mister_kernel_defconfig + configs/mister_.fragment) — the + from the DE10-Nano fragment stack (configs/fragments/, stacks.mk) and runs + `make all` — linux.img + zImage_dtb in output/images/. Any other name is a + kernel-only variant (`make ` over the de10nano-kernel fragment stack + + configs/mister_.fragment) — the variant kernel at output-/images/zImage_dtb plus a depmod'd module tree in output-/target/. @@ -162,10 +163,12 @@ runs: echo "::error::unknown variant '$VARIANT' -- .github/actions/buildroot-build accepts 'main' or any kernel variant with a fragment at configs/mister_.fragment (looked for configs/mister_${VARIANT}.fragment)" >&2 exit 1 fi - # Kernel variants build a COPY of main's toolchain/kernel stanzas - # (configs/mister_kernel_defconfig) — assert it hasn't drifted - # BEFORE any cache work, or a drift builds wrong under a cache key - # that then pins the wrong toolchain in. See docs/ci.md#variants. + # Kernel variants build on the de10nano-kernel fragment stack, + # which shares its toolchain/kernel fragments with main's stack by + # construction (configs/fragments/stacks.mk) — assert that + # structure still holds BEFORE any cache work, or a drift builds + # wrong under a cache key that then pins the wrong toolchain in. + # See docs/ci.md#variants. scripts/check-kernel-defconfig-sync.sh ;; esac @@ -192,16 +195,29 @@ runs: # first so editing one can't evict the cross-toolchain either. See # docs/ci.md#toolchain-fingerprint before changing this filter. # - # Fingerprints the variant's own base defconfig + fragment, not - # main's. See docs/ci.md#toolchain-fingerprint. + # Fingerprints the variant's own base STACK + fragment, not main's: + # main = the de10nano stack, a kernel variant = the de10nano-kernel + # stack (configs/fragments/stacks.mk, read through the same helper + # the check scripts use). The fragments are concatenated before the + # strip/filter/sort, so the residue is the same sorted set of lines + # the old single-file defconfig produced — the fingerprint, and + # therefore the cache key, did not move at the fragment split. + # See docs/ci.md#toolchain-fingerprint. TC_GEN=1 + ROOT="$PWD" + # shellcheck source=scripts/lib/config-stacks.sh + source scripts/lib/config-stacks.sh if [ "$VARIANT" = "main" ]; then - fp_defconfig=configs/mister_de10nano_defconfig + mapfile -t fp_files < <(config_stack_files DE10NANO) else - fp_defconfig=configs/mister_kernel_defconfig + mapfile -t fp_files < <(config_stack_files DE10NANO_KERNEL) + fi + if [ "${#fp_files[@]}" -eq 0 ]; then + echo "::error::configs/fragments/stacks.mk names no fragments for this variant's stack" >&2 + exit 1 fi sed -e 's/^[[:space:]]*#.*$//' -e 's/[[:space:]]\+#.*$//' -e 's/[[:space:]]*$//' \ - "$fp_defconfig" \ + "${fp_files[@]}" \ | grep -vE '^($|BR2_PACKAGE_|BR2_LINUX_KERNEL)' \ | sort > .br-toolchain-fingerprint @@ -315,16 +331,18 @@ runs: # 2. dl/ — every package source tarball. Lives at repo root (survives # `make clean`) — NEVER move it under output/. Keyed on the full - # defconfig; restore-keys falls back to the version alone so an - # imperfect match still hydrates what didn't change. See - # docs/ci.md#dl-cache. + # DE10-Nano fragment stack (the three files configs/fragments/stacks.mk + # lists for DE10NANO_FRAGMENTS — hashFiles() cannot read stacks.mk, so + # they are spelled out here; keep the two in step); restore-keys falls + # back to the version alone so an imperfect match still hydrates what + # didn't change. See docs/ci.md#dl-cache. - name: Restore Buildroot dl/ download cache id: dl-cache if: env.VARIANT == 'main' uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: dl/ - key: br-dl-${{ env.BUILDROOT_VERSION }}-${{ hashFiles('configs/mister_de10nano_defconfig') }} + key: br-dl-${{ env.BUILDROOT_VERSION }}-${{ hashFiles('configs/fragments/common.fragment', 'configs/fragments/de10nano.fragment', 'configs/fragments/de10nano-image.fragment') }} restore-keys: | br-dl-${{ env.BUILDROOT_VERSION }}- @@ -339,7 +357,7 @@ runs: uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: dl/ - key: br-dl-${{ env.VARIANT }}-${{ env.BUILDROOT_VERSION }}-${{ hashFiles('configs/mister_kernel_defconfig', format('configs/mister_{0}.fragment', env.VARIANT)) }} + key: br-dl-${{ env.VARIANT }}-${{ env.BUILDROOT_VERSION }}-${{ hashFiles('configs/fragments/common.fragment', 'configs/fragments/de10nano.fragment', 'configs/fragments/kernel-only.fragment', format('configs/mister_{0}.fragment', env.VARIANT)) }} restore-keys: | br-dl-${{ env.VARIANT }}-${{ env.BUILDROOT_VERSION }}- br-dl-${{ env.BUILDROOT_VERSION }}- @@ -435,14 +453,14 @@ runs: run: make hostshim # Regenerate output/.config UNCONDITIONALLY every run — a stale cached - # one can silently override a defconfig change (run 29293209070 died + # one can silently override a fragment change (run 29293209070 died # here after a 52min stage 1). MAIN-ONLY: a kernel variant's .config is # never cached, so it can't go stale in the first place. See # docs/ci.md#configure-buildroot. - - name: Configure Buildroot (generate output/.config from the defconfig) + - name: Configure Buildroot (generate output/.config from the fragment stack) if: env.VARIANT == 'main' shell: bash - run: make mister_de10nano_defconfig + run: make de10nano-defconfig # `make all` builds both stages under one ccache (BR2_CCACHE propagates # through MAKEFLAGS to the musl initramfs too — distinct ccache keys, so diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36560dd..7ea2d92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -153,13 +153,50 @@ jobs: echo "::notice::Documentation-only change -- skipping the image build." fi + # Configuration-level lint, in its own job so a bad fragment or patch + # header fails in about a minute BEFORE any kernel leg or the 3h image + # build starts (both `needs` this). Config-only by design: the fragment + # check unpacks the pinned Buildroot tarball (10 MB, hash-verified by the + # wrapper Makefile) and runs kconfig -- no toolchain, no package, no + # compile. See docs/ci.md#lint-config. + lint-config: + name: Lint configuration (patch headers, fragment stacks) + needs: gate + if: needs.gate.outputs.build_needed == 'true' + runs-on: ubuntu-26.04 + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + # Guards what the build can't: `patch -p1` ignores mail headers, so a + # malformed `From:` builds green and only breaks `git am` at export + # time. See docs/ci.md#patch-lint-placement. + - name: Lint kernel patch headers (git am-ability, both series) + run: scripts/lint-kernel-patches.sh + + # Text-level: the image and kernel-only fragment stacks share their + # toolchain/kernel fragments by construction -- assert that structure + # holds (no Buildroot tree needed; the kernel legs re-run this before + # any cache restore). See docs/ci.md#kernel-defconfig-lockstep. + - name: Lint kernel-defconfig lockstep + run: scripts/check-kernel-defconfig-sync.sh + + # Resolved-level: regenerate every fragment stack through Buildroot's + # own merge_config.sh + olddefconfig and assert no redefinition + # between fragments, no silently dropped symbol, resolved lockstep, + # and the golden hash of the resolved DE10 configuration. See + # docs/ci.md#lint-config. + - name: Check fragment stacks (regenerate + golden hashes) + run: scripts/check-config-fragments.sh + # KERNEL-ONLY leg per variant (ADR 0021); runs before `build`, which # consumes its module tree -- a failed leg auto-skips `build` (see # `status`). Add a variant = one new configs/mister_.fragment; # everything here derives from the name via `gate`. See docs/ci.md#variants. build-kernel: name: Build kernel (${{ matrix.kernel }}) - needs: gate + needs: [gate, lint-config] if: needs.gate.outputs.build_needed == 'true' runs-on: ubuntu-26.04 strategy: @@ -188,7 +225,7 @@ jobs: # needs on the matrix job aggregates ALL its legs; a failed (or cancelled) # leg therefore auto-skips this job — `status` below translates that skip # into "the kernel leg is the root cause", not a second failure. - needs: [gate, build-kernel] + needs: [gate, lint-config, build-kernel] if: needs.gate.outputs.build_needed == 'true' runs-on: ubuntu-26.04 @@ -201,19 +238,10 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - # BEFORE the build (fails in ~1s, not after 300 min). Guards what the - # build can't: `patch -p1` ignores mail headers, so a malformed - # `From:` builds green and only breaks `git am` at export time. - # See docs/ci.md#patch-lint-placement. - - name: Lint kernel patch headers (git am-ability, both series) - run: scripts/lint-kernel-patches.sh - - # Kernel-only defconfig is a manually mirrored COPY of the main - # defconfig's toolchain/kernel stanzas -- this asserts it hasn't - # drifted (a main-only edit is easy to forget to mirror). - # See docs/ci.md#kernel-defconfig-lockstep. - - name: Lint kernel-defconfig lockstep - run: scripts/check-kernel-defconfig-sync.sh + # The patch-header and configuration lints that used to sit here run + # in the `lint-config` job above, which this job `needs` -- same + # fail-before-the-3h-build property, minus the wait for the kernel + # legs. See docs/ci.md#lint-config. # Populates the overlay the build below consumes -- see # .github/actions/merge-kernel-modules and docs/ci.md#kernel-module-overlay. @@ -351,19 +379,20 @@ jobs: # needs..result is used instead of success()/failure()). status: name: Build status - needs: [gate, build-kernel, build] + needs: [gate, lint-config, build-kernel, build] if: always() runs-on: ubuntu-26.04 steps: - name: Report env: GATE_RESULT: ${{ needs.gate.result }} + LINT_RESULT: ${{ needs.lint-config.result }} KERNEL_RESULT: ${{ needs.build-kernel.result }} BUILD_RESULT: ${{ needs.build.result }} BUILD_NEEDED: ${{ needs.gate.outputs.build_needed }} run: | set -euo pipefail - echo "gate=$GATE_RESULT build-kernel=$KERNEL_RESULT build=$BUILD_RESULT build_needed=$BUILD_NEEDED" + echo "gate=$GATE_RESULT lint-config=$LINT_RESULT build-kernel=$KERNEL_RESULT build=$BUILD_RESULT build_needed=$BUILD_NEEDED" if [ "$GATE_RESULT" != "success" ]; then echo "::error::The gate job did not succeed ($GATE_RESULT), so whether a build was needed is unknown. Failing rather than assuming it wasn't." @@ -371,12 +400,32 @@ jobs: fi rc=0 + case "$LINT_RESULT" in + success) + echo "lint-config: patch headers, lockstep and fragment stacks are clean." + ;; + skipped) + if [ "$BUILD_NEEDED" = "true" ]; then + echo "::error::The gate asked for a build and lint-config was skipped anyway. That is a workflow bug, not a docs change." + rc=1 + else + echo "lint-config: documentation-only change, nothing to lint." + fi + ;; + *) + echo "::error::lint-config $LINT_RESULT. (Expect build-kernel and build to show as skipped below -- that is this failure cascading through needs, not two more bugs.)" + rc=1 + ;; + esac + case "$KERNEL_RESULT" in success) echo "build-kernel: every kernel leg built and uploaded its module tree." ;; skipped) - if [ "$BUILD_NEEDED" = "true" ]; then + if [ "$BUILD_NEEDED" = "true" ] && [ "$LINT_RESULT" != "success" ]; then + echo "build-kernel: skipped because lint-config did not succeed -- the needs-cascade, already failed above." + elif [ "$BUILD_NEEDED" = "true" ]; then echo "::error::The gate asked for a build and build-kernel was skipped anyway. That is a workflow bug, not a docs change." rc=1 else @@ -396,8 +445,8 @@ jobs: skipped) if [ "$BUILD_NEEDED" != "true" ]; then echo "build: documentation-only change, no build needed." - elif [ "$KERNEL_RESULT" != "success" ]; then - echo "build: skipped because build-kernel did not succeed -- the needs-cascade, already failed above." + elif [ "$LINT_RESULT" != "success" ] || [ "$KERNEL_RESULT" != "success" ]; then + echo "build: skipped because lint-config or build-kernel did not succeed -- the needs-cascade, already failed above." else echo "::error::The gate asked for a build and build was skipped anyway. That is a workflow bug, not a docs change." rc=1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ee5e779..92a5ed0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -186,6 +186,7 @@ jobs: ) sh_relpaths=( post-build.sh + post-image.sh initramfs-post-build.sh initramfs-overlay/init ) @@ -249,6 +250,7 @@ jobs: fat-payload/Scripts/check_storage.sh fat-payload/Scripts/pair_logitech.sh post-build.sh + post-image.sh initramfs-post-build.sh initramfs-overlay/init rootfs-overlay/usr/sbin/mister-fsck-exfat diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4560b5c..eb1f942 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -654,15 +654,15 @@ jobs: restore-keys: br-dl-azcopy- # `echo >> .config` + olddefconfig is the standard Buildroot idiom for - # "the committed defconfig, plus this one symbol". It is used rather than - # a committed fragment because there is exactly one symbol and no make - # target to hang a fragment off (unlike configs/mister_rt.fragment, which - # `make rt` exists to apply). BR2_PACKAGE_HOST_GO and + # "the committed configuration, plus this one symbol". It is used rather + # than a committed fragment because there is exactly one symbol and no + # make target to hang a fragment off (unlike configs/mister_rt.fragment, + # which `make rt` exists to apply). BR2_PACKAGE_HOST_GO and # BR2_PACKAGE_CA_CERTIFICATES come along via the package's own `select`s. - - name: Configure (defconfig + azcopy) + - name: Configure (DE10-Nano fragment stack + azcopy) run: | set -eu - make mister_de10nano_defconfig + make de10nano-defconfig echo 'BR2_PACKAGE_AZCOPY=y' >> output/.config make olddefconfig grep -qx 'BR2_PACKAGE_AZCOPY=y' output/.config diff --git a/.github/workflows/renovate-hash-sync.yml b/.github/workflows/renovate-hash-sync.yml index 53c381e..ab73007 100644 --- a/.github/workflows/renovate-hash-sync.yml +++ b/.github/workflows/renovate-hash-sync.yml @@ -108,10 +108,10 @@ on: # here, the workflow never runs on a Buildroot bump at all -- no skipped # job, no red X, no outcome row. - "Makefile" - - "configs/mister_de10nano_defconfig" - # mister_kernel_defconfig copies this stanza's kernel version (one Renovate - # PR bumps both). - - "configs/mister_kernel_defconfig" + # The DE10 kernel pin (BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE) lives in + # the board fragment since the 2026-09 fragment split; the kernel-only + # stack shares that same file, so there is no second copy to list. + - "configs/fragments/de10nano.fragment" # The RT/beta kernel pin. ADDED 2026-08-17: this file used to be # deliberately ABSENT here, because while that pin tracked mainline -rc # its hash could not be auto-refreshed at all (no signed manifest exists @@ -501,6 +501,20 @@ jobs: - name: Rebuild and refresh the azcopy vendored-tarball hash run: .hash-sync-tools/scripts/hash-sync-azcopy.sh "$GITHUB_WORKSPACE" + # --- 8. Golden config hashes (configs/fragments/golden.sha256) --------- + # NEW 2026-09-02, with the fragment split. A Buildroot bump changes + # Kconfig defaults and so moves every stack's golden resolved-config + # hash; lint-config only WARNS on a version with no golden line, so the + # bump PR still builds, and this case records the new lines in the same + # PR. Runs the TARGET branch's scripts/check-config-fragments.sh + # --update-golden through the target branch's Makefile (the golden + # format is that tree's), after case 6 has written the tarball hash the + # unpack verifies against. Never rewrites lines that already exist for + # the pinned version -- drift is lint-config's to fail, not this case's + # to bless. See the script's header and docs/renovate.md. + - name: Record golden config hashes for a bumped Buildroot + run: .hash-sync-tools/scripts/hash-sync-golden.sh "$GITHUB_WORKSPACE" + # Gate the push: ANY pin recorded "failed" (from any of the steps # above) suppresses the commit/push below. MUST run and be evaluated # BEFORE the push -- the summary/gate step at the end of the job @@ -525,13 +539,13 @@ jobs: env.RT_PATCH_HASH_CHANGED == '1' || env.LZMA_SDK_HASH_CHANGED == '1' || env.SEVENZIP_HASH_CHANGED == '1' || env.PAYLOAD_HASH_CHANGED == '1' || env.BUILDROOT_HASH_CHANGED == '1' || - env.AZCOPY_HASH_CHANGED == '1') && + env.AZCOPY_HASH_CHANGED == '1' || env.GOLDEN_CHANGED == '1') && env.HASH_SYNC_FAILED == '0' run: | set -euo pipefail git config user.name "renovate-hash-sync[bot]" git config user.email "renovate-hash-sync@users.noreply.github.com" - git add Makefile package/*/*.hash board/mister/de10nano/patches/linux/linux.hash scripts/fetch-sdcard-payload.sh + git add Makefile package/*/*.hash board/mister/de10nano/patches/linux/linux.hash scripts/fetch-sdcard-payload.sh configs/fragments/golden.sha256 git commit -F - <<'COMMITMSG' renovate-hash-sync: refresh companion hash(es) for this PR @@ -562,6 +576,12 @@ jobs: the only way this value can be derived, since no URL serves that file (see package/azcopy/azcopy.hash's header). LICENSE and NOTICE.txt were re-hashed from the same tarball + - golden config hashes (configs/fragments/golden.sha256): on a + Buildroot bump only, the target branch's own + scripts/check-config-fragments.sh --update-golden was run + against the freshly unpacked (hash-verified) Buildroot tree and + the new version's lines recorded -- never a rewrite of lines + that already existed (docs/buildroot-config.md section 11) COMMITMSG # Shell variable, NOT a workflow-expression interpolation -- # TARGET_BRANCH is attacker-controlled text on a PR (command-injection @@ -606,7 +626,10 @@ jobs: # in $HASH_SYNC_PACKAGES (that string is case 1's curl-and-hash # roster, which must never touch a golang-package) and so has to be # named here separately, exactly like the four pins before it. - all_pins="$HASH_SYNC_PACKAGES kernel kernel-rt lzma-sdk 7zip buildroot azcopy PINNED_UPDATE_ALL PINNED_WIFI_SH PINNED_CORES" + # + # `golden` joined on 2026-09-02 with case 8 (the resolved-config + # golden hashes a Buildroot bump moves; scripts/hash-sync-golden.sh). + all_pins="$HASH_SYNC_PACKAGES kernel kernel-rt lzma-sdk 7zip buildroot azcopy golden PINNED_UPDATE_ALL PINNED_WIFI_SH PINNED_CORES" complete_file=$(mktemp) cp "$outcomes_file" "$complete_file" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 93e0efb..cdf763e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -98,7 +98,7 @@ All external sources (kernel, Buildroot, packages, firmware) must be: KERNEL_VERSION = 6.18.y # Good: pinned tag (version illustrative -- the live kernel pin is -# BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE in configs/mister_de10nano_defconfig, +# BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE in configs/fragments/de10nano.fragment, # which is the one place it is written down) KERNEL_VERSION = 6.18.38 diff --git a/MISTER-KERNEL-PATCH-RECON.md b/MISTER-KERNEL-PATCH-RECON.md index eca6b47..c532383 100644 --- a/MISTER-KERNEL-PATCH-RECON.md +++ b/MISTER-KERNEL-PATCH-RECON.md @@ -51,7 +51,7 @@ doc's failure mode was confident, uncited claims; do not reproduce it. | Ref | What | Where | |---|---|---| | **Fork** | `MiSTer-devel/Linux-Kernel_MiSTer` — the commits to reconcile | Local full clone: `/mnt/source/Linux-Kernel_MiSTer`. Pinned HEAD (`MiSTer-v5.15`): `f0fb626acadd07f0718934826b143b6e4c9ce81c`. Vanilla base: **v5.15.1** (see §1.1) | -| **Vanilla** | Target kernel **6.18.38** | Local linux-stable clone: `/mnt/source/linux`. Pinned `v6.18.38` = `2aa1767b5e96f79560675d55bc0da08ea36fff29`; version also pinned at `configs/mister_de10nano_defconfig:77`. **Must be unshallowed first** (see §1.2) | +| **Vanilla** | Target kernel **6.18.38** | Local linux-stable clone: `/mnt/source/linux`. Pinned `v6.18.38` = `2aa1767b5e96f79560675d55bc0da08ea36fff29`; version also pinned in the Buildroot config (then `configs/mister_de10nano_defconfig`; since the 2026-09 fragment split `configs/fragments/de10nano.fragment`, `BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`). **Must be unshallowed first** (see §1.2) | | **This repo** | Carried patches + prior-art doc | `board/mister/de10nano/linux-patches/*.patch` (25 files, `0001`–`0031`, with gaps — see §8); `docs/patch-provenance.md`; `docs/stock-inventory/stock-linux.config` | | **Userspace** | `MiSTer-devel/Main_MiSTer` — for userspace-coupling cross-ref | https://github.com/MiSTer-devel/Main_MiSTer | @@ -367,7 +367,8 @@ Audit output amends the record in place and appends an `audit_findings.md`. reassuring to the community. - **Config reconciliation is its own axis.** For `kconfig` changes the question is not "is it upstream" but "does this `CONFIG_*` still exist / was it renamed / is it set in our defconfig" — - verify against 6.18 Kconfig + `configs/mister_de10nano_defconfig`, not the source tree. + verify against 6.18 Kconfig + the Buildroot config (`configs/fragments/de10nano.fragment` + since the 2026-09 fragment split; `configs/mister_de10nano_defconfig` before it), not the source tree. - **Provenance & license.** For vendored / third-party-PR code (xone, wifi vendor drivers, Fanatec, etc.), capture author + license — gates whether we can carry *or* upstream it. diff --git a/Makefile b/Makefile index 345915a..1dd5ee7 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,14 @@ # 3. Unpacks it to work/buildroot/ (idempotent: if a Buildroot tree of the # pinned version is already present there, the download/verify/unpack # step is skipped entirely and no network access is made). -# 4. Forwards every other target (menuconfig, mister_de10nano_defconfig, -# olddefconfig, savedefconfig, ...) into that Buildroot tree with -# BR2_EXTERNAL set to this repo, O= pointed at an out-of-tree output -# directory, and BR2_DL_DIR pointed at the persistent download cache. +# 4. Generates each Buildroot configuration by layering the fragments under +# configs/fragments/ (configs/fragments/stacks.mk says which) with +# Buildroot's own merge_config.sh + olddefconfig -- there is no monolithic +# defconfig any more (docs/buildroot-config.md §1) -- and forwards every +# other target (menuconfig, olddefconfig, savedefconfig, ...) into that +# Buildroot tree with BR2_EXTERNAL set to this repo, O= pointed at an +# out-of-tree output directory, and BR2_DL_DIR pointed at the persistent +# download cache. # # work/, dl/, and output/ are all gitignored — see .gitignore. # @@ -84,10 +88,11 @@ INITRAMFS_INIT := $(ROOT_DIR)/board/mister/de10nano/initramfs-overlay/init # own O=. Deliberately not naming the -rc here: it is only a label at this point # and configs/mister_rt.fragment is the single place that pins it. # Since ADR 0021's 2026-07-18 amendment this is a KERNEL-ONLY build, not a -# second full image: configs/mister_kernel_defconfig (the main defconfig's -# toolchain + kernel stanzas, rootfs-tar only, no packages) with -# configs/mister_rt.fragment layered on at `make rt` time via Buildroot's own -# merge_config.sh. It produces zImage_dtb (ships as zImage_dtb-rt) plus a +# second full image: the de10nano-kernel fragment stack (common + de10nano + +# kernel-only, configs/fragments/stacks.mk -- the SAME toolchain + kernel +# fragments the shipped image is built from, rootfs-tar only, no packages) +# with configs/mister_rt.fragment layered on at `make rt` time via Buildroot's +# own merge_config.sh. It produces zImage_dtb (ships as zImage_dtb-rt) plus a # depmod'd module tree, which `rt` copies into $(EXTRA_MODULES_OVERLAY) below # so the ONE shipped linux.img carries both kernels' modules. # A future kernel variant `foo` needs only configs/mister_foo.fragment, its own @@ -159,13 +164,17 @@ INSTALLER_DEFCONFIG := $(ROOT_DIR)/configs/mister_installer_defconfig INSTALLER_KERNEL_OUTPUT_DIR := $(ROOT_DIR)/output-installer-kernel SDCARD_STAGE_DIR := $(ROOT_DIR)/output-sdcard-stage SDCARD_BUILD_DIR := $(ROOT_DIR)/output-sdcard-build +# scripts/check-config-fragments.sh's scratch O= dirs (one per fragment +# stack, config-only, never built). Not a Buildroot-cleanable tree either: +# plain rm -rf'd below, like the three above. +CONFIG_CHECK_DIR := $(ROOT_DIR)/output-config-check # --- DE25-Nano developer OS (D2.1, docs/de25-nano-tasks.md) ------------------- # A FIFTH Buildroot output dir, and by far the biggest departure of the five: # every directory above builds for the DE10-Nano's armv7 Cyclone V. This one # builds for a DIFFERENT BOARD — the Terasic DE25-Nano, an Intel/Altera # Agilex 5 whose HPS is aarch64 (2x Cortex-A76 + 2x Cortex-A55). Different -# architecture, different toolchain, different kernel line (mainline 7.2.2), +# architecture, different toolchain, different kernel line (mainline 7.2.3), # different rootfs. It shares with the main build exactly two things: the # pinned Buildroot tree and the dl/ download cache. # @@ -176,12 +185,10 @@ SDCARD_BUILD_DIR := $(ROOT_DIR)/output-sdcard-build # never share a host tree even if you wanted them to (ADR 0021 §3 makes the # same point about output-rt/). # -# UNLIKE `rt`, there is NO fragment to merge: configs/mister_de25nano_defconfig -# is a standalone, self-contained defconfig, so $(DE25_OUTPUT_DIR)/.config is a -# plain one-line `$(BR_MAKE_DE25) mister_de25nano_defconfig` rather than -# defconfig + merge_config.sh + olddefconfig. The rt fragment exists because -# that variant is a *delta* on the DE10's own kernel stanza; the DE25 shares no -# stanza with anything. +# Its configuration is the de25nano fragment stack (common + de25nano, +# configs/fragments/stacks.mk): it shares exactly the arch-neutral `common` +# layer with the DE10 stacks and nothing else -- no DE10 toolchain, kernel or +# 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 @@ -291,11 +298,46 @@ BR_MAKE_INSTALLER = PATH="$(HOSTSHIM_DIR):$$PATH" \ # The same, aimed at the DE25-Nano output directory (docs/de25-nano-tasks.md # D2.1). Byte-for-byte the same shape as the four above — same Buildroot tree, -# same BR2_EXTERNAL, same dl/ cache; only O= and the defconfig differ. The -# aarch64-ness lives entirely in configs/mister_de25nano_defconfig, not here. +# same BR2_EXTERNAL, same dl/ cache; only O= and the fragment stack differ. The +# aarch64-ness lives entirely in configs/fragments/de25nano.fragment, not here. BR_MAKE_DE25 = PATH="$(HOSTSHIM_DIR):$$PATH" \ $(MAKE) -C $(BR_DIR) O=$(DE25_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR) +# --- Config fragments (docs/buildroot-config.md §1) --------------------------- +# There is no monolithic defconfig. Every Buildroot configuration in this tree +# is a STACK of fragments under configs/fragments/, listed in merge order by +# configs/fragments/stacks.mk (the single source of truth -- the check scripts +# parse that same file). Generation is exactly the idiom `rt` has always used: +# Buildroot's own support/kconfig/merge_config.sh -m concatenates the fragments +# into /.config (warning on any symbol a later fragment redefines -- there +# must be none within a stack; scripts/check-config-fragments.sh enforces that +# in CI), then `olddefconfig` resolves every unlisted symbol to its Kconfig +# default. That resolves to the byte-identical .config the old +# `make mister__defconfig` produced (proved at the split: only +# BR2_DEFCONFIG, the savedefconfig OUTPUT path, differs). +# +# Consequence worth knowing: `make savedefconfig` now writes output/defconfig +# (Buildroot's default when BR2_DEFCONFIG names no file) instead of clobbering a +# tracked file. Fold a menuconfig experiment back by hand into the right +# fragment -- savedefconfig output is unordered and comment-free, which is why +# the old monolith kept losing its comments. +FRAGMENT_DIR := $(ROOT_DIR)/configs/fragments +include $(FRAGMENT_DIR)/stacks.mk +# $(call stack_files,) -> absolute fragment paths, in merge order. +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)) + +# $(call merge_fragments,,) -- step 1 of 2; the +# caller follows it with the matching `$(BR_MAKE_*) olddefconfig`. The first +# fragment is merge_config.sh's base file, the rest are merged onto it. +define merge_fragments + @mkdir -p $(1) + cd $(BR_DIR) && KCONFIG_CONFIG=$(1)/.config \ + ./support/kconfig/merge_config.sh -m -O $(1) $(2) +endef + # NOTE: there is no BR_MAKE_INSTALLER_KERNEL. mk-sdcard.sh's step 2 relink no longer # builds a fourth Buildroot tree in output-installer-kernel/ — it relinks the kernel # IN output/ (reusing the completed main build) and restores it. output-installer- @@ -329,6 +371,12 @@ BR_MAKE_DE25 = PATH="$(HOSTSHIM_DIR):$$PATH" \ # so this simple line is what breaks that cycle. Makefile: ; +# The included configs/fragments/stacks.mk is a makefile too, so GNU Make +# would try to remake it through the `%:` catch-all as well -- forwarding a +# target named `/.../configs/fragments/stacks.mk` into Buildroot with +# 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 @@ -370,19 +418,42 @@ all: initramfs $(BR_STAMP) hostshim | $(OUTPUT_DIR)/.config # # The empty prerequisite list is the load-bearing part: with no prerequisites, an # existing .config is always up to date and this recipe never fires again. Giving -# it the shape stage 1 uses at :253 ($(OUTPUT_DIR)/.config: ) -# would instead re-load the checked-in defconfig over output/.config every time -# the defconfig looked newer — silently discarding `make menuconfig` edits that -# had not been folded back with `savedefconfig`. Stage 1 can afford that; its -# config is generated, not iterated on. Stage 2's is the one people edit. +# it the shape stage 1 uses ($(INITRAMFS_OUTPUT_DIR)/.config: ) +# would instead re-generate output/.config from the fragments every time one +# of them looked newer — silently discarding `make menuconfig` edits that had +# not been folded back into a fragment. Stage 1 can afford that; its config is +# generated, not iterated on. Stage 2's is the one people edit. Regenerate +# deliberately with `make de10nano-defconfig` (what CI does, every run). # # $(BR_STAMP) is order-only because a parallel `make -j all` gives no ordering # between all's own prerequisites, so this cannot rely on all's copy of it. -# The explicit rule also beats the `%:` catch-all at the bottom of this file, -# same as `Makefile: ;` and $(INITRAMFS_DEFCONFIG) above. -$(OUTPUT_DIR)/.config: | $(BR_STAMP) - @mkdir -p $(OUTPUT_DIR) - $(BR_MAKE) mister_de10nano_defconfig +# `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. +$(OUTPUT_DIR)/.config: | $(BR_STAMP) hostshim + $(call merge_fragments,$(OUTPUT_DIR),$(DE10NANO_STACK)) + $(BR_MAKE) olddefconfig + +# Force-regenerate the DE10-Nano configuration from its fragment stack -- +# the replacement for the old `make mister_de10nano_defconfig`. CI runs this +# unconditionally so a stale output/.config can never mask a fragment change. +.PHONY: de10nano-defconfig +de10nano-defconfig: | $(BR_STAMP) hostshim + @rm -f $(OUTPUT_DIR)/.config + @$(MAKE) --no-print-directory $(OUTPUT_DIR)/.config + +# The three monolithic defconfigs were split into configs/fragments/ (see the +# fragment block above). Anything still asking for them by name gets told +# where to go instead of a confusing Buildroot "no rule" failure. +.PHONY: mister_de10nano_defconfig mister_kernel_defconfig mister_de25nano_defconfig +mister_de10nano_defconfig mister_kernel_defconfig mister_de25nano_defconfig: + @echo "FATAL: configs/$@ no longer exists -- the monolithic defconfigs were split into" >&2 + @echo " configs/fragments/ (docs/buildroot-config.md §1). Use instead:" >&2 + @echo " make de10nano-defconfig # the DE10-Nano image (output/.config)" >&2 + @echo " make de25nano-defconfig # the DE25-Nano developer OS (output-de25/.config)" >&2 + @echo " make rt # the kernel-only base + configs/mister_rt.fragment" >&2 + @exit 1 # --- Cleaning ----------------------------------------------------------------- # Buildroot's vocabulary, kept on purpose: `clean` deletes what the build @@ -417,7 +488,7 @@ clean: @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 - @rm -rf $(INSTALLER_KERNEL_OUTPUT_DIR) $(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR) + @rm -rf $(INSTALLER_KERNEL_OUTPUT_DIR) $(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR) $(CONFIG_CHECK_DIR) @# The extra-modules overlay is a build product (staged module trees), so @# clean takes it wholesale, stamps included — the next `make rt` restages @# its tree, and `all` recreates the (empty) dir before Buildroot needs it. @@ -441,7 +512,7 @@ distclean: rm -rf $(OUTPUT_DIR) $(INITRAMFS_OUTPUT_DIR) $(RT_OUTPUT_DIR) \ $(INSTALLER_OUTPUT_DIR) $(DE25_OUTPUT_DIR) \ $(INSTALLER_KERNEL_OUTPUT_DIR) \ - $(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR) \ + $(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR) $(CONFIG_CHECK_DIR) \ $(EXTRA_MODULES_OVERLAY) $(RT_OVERLAY_STAMP) # --- Stage 1: the initramfs cpio ---------------------------------------------- @@ -535,7 +606,8 @@ initramfs-clean: # --- 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 configs/mister_kernel_defconfig with Buildroot's own +# KERNEL-ONLY base stack (common + de10nano + kernel-only fragments, +# $(DE10NANO_KERNEL_STACK)) with Buildroot's own # merge_config.sh, then builds it into its own output-rt/ (shared # toolchain sources/dl/ccache; the main output/ is untouched). Produces # output-rt/images/zImage_dtb — the RT kernel, shipped as zImage_dtb-rt and @@ -547,11 +619,11 @@ initramfs-clean: # prerequisite is caught by the catch-all target-forwarding rule and would # re-run against O=$(OUTPUT_DIR)). Re-generate after editing the fragment with # `make rt-clean && make rt` (same manual step the main config's design implies). -$(RT_OUTPUT_DIR)/.config: | $(BR_STAMP) - @mkdir -p $(RT_OUTPUT_DIR) - $(BR_MAKE_RT) mister_kernel_defconfig - cd $(BR_DIR) && KCONFIG_CONFIG=$(RT_OUTPUT_DIR)/.config \ - ./support/kconfig/merge_config.sh -m -O $(RT_OUTPUT_DIR) $(RT_OUTPUT_DIR)/.config $(RT_FRAGMENT) +# The rt fragment is the ONE place a later fragment legitimately redefines +# earlier symbols (kernel version + patch dir); scripts/check-config-fragments.sh +# allowlists exactly those. +$(RT_OUTPUT_DIR)/.config: | $(BR_STAMP) hostshim + $(call merge_fragments,$(RT_OUTPUT_DIR),$(DE10NANO_KERNEL_STACK) $(RT_FRAGMENT)) $(BR_MAKE_RT) olddefconfig # `initramfs` is a hard prerequisite for the same reason it is on `all`: @@ -612,7 +684,7 @@ rt: initramfs $(RT_OUTPUT_DIR)/.config hostshim echo "FATAL: expected exactly one module tree under" >&2; \ echo " $(RT_OUTPUT_DIR)/target/usr/lib/modules/ but found $$#." >&2; \ echo " Zero means depmod/target-finalize never ran (is BR2_TARGET_ROOTFS_TAR" >&2; \ - echo " still set in configs/mister_kernel_defconfig?); more than one is the" >&2; \ + echo " still set in configs/fragments/kernel-only.fragment?); more than one is the" >&2; \ echo " stale-sibling hazard described above. Run 'make rt-clean && make rt'." >&2; exit 1; \ fi; \ kver=$$(basename "$$1"); \ @@ -678,10 +750,10 @@ rt-clean: rm -rf $(RT_OUTPUT_DIR) # --- DE25-Nano developer OS (D2.1, docs/de25-nano-tasks.md) ------------------- -# Loads configs/mister_de25nano_defconfig into output-de25/. Order-only -# $(BR_STAMP) and NO file prerequisite on the defconfig — same shape, same two +# Generates output-de25/.config from the de25nano fragment stack. Order-only +# $(BR_STAMP) and NO file prerequisite on the fragments — same shape, same two # reasons, as $(OUTPUT_DIR)/.config and $(RT_OUTPUT_DIR)/.config above: a -# defconfig listed as a normal prerequisite gets caught by the `%:` catch-all +# fragment listed as a normal prerequisite gets caught by the `%:` catch-all # target-forwarding rule at the bottom of this file and would be "remade" with # O=$(OUTPUT_DIR) (i.e. loaded into the DE10's output dir — here that would # mean loading an AARCH64 config over the armv7 build, which is about as bad as @@ -689,17 +761,24 @@ rt-clean: # always up to date, so `make de25-menuconfig` edits are not silently # discarded by the next `make de25`. # -# Re-generate after editing the defconfig with `make de25-clean && make de25`, -# the same manual step the main and rt configs imply. +# Re-generate after editing a fragment with `make de25nano-defconfig` (or +# `make de25-clean && make de25`), the same deliberate step the main and rt +# configs imply. # -# No merge_config.sh step: unlike `rt`, this defconfig is standalone. # `hostshim` is an order-only prerequisite HERE, not only on `de25`: under # `make -j de25` the sibling prerequisites of `de25` run concurrently, so the # config recipe (which invokes Buildroot, whose dependency check needs the # shim's `install` on PATH) could otherwise start before the shim exists. $(DE25_OUTPUT_DIR)/.config: | $(BR_STAMP) hostshim - @mkdir -p $(DE25_OUTPUT_DIR) - $(BR_MAKE_DE25) mister_de25nano_defconfig + $(call merge_fragments,$(DE25_OUTPUT_DIR),$(DE25NANO_STACK)) + $(BR_MAKE_DE25) olddefconfig + +# Force-regenerate the DE25 configuration -- the replacement for the old +# `make mister_de25nano_defconfig`; mirrors de10nano-defconfig above. +.PHONY: de25nano-defconfig +de25nano-defconfig: | $(BR_STAMP) hostshim + @rm -f $(DE25_OUTPUT_DIR)/.config + @$(MAKE) --no-print-directory $(DE25_OUTPUT_DIR)/.config # Deliberately NOT `de25: initramfs ...` — see DE25_OUTPUT_DIR's header for why # the stage-1 cpio has no business in an aarch64 kernel. @@ -735,21 +814,61 @@ de25: $(DE25_OUTPUT_DIR)/.config hostshim echo " BR2_LINUX_KERNEL_CUSTOM_DTS_PATH did not build." >&2; exit 1; \ fi; \ echo ""; \ - echo "==> DE25 kernel: $(DE25_OUTPUT_DIR)/images/Image ($$(stat -c %s $(DE25_OUTPUT_DIR)/images/Image) bytes)"; \ - for d in "$$@"; do echo "==> DE25 dtb: $$d ($$(stat -c %s $$d) bytes)"; done; \ + echo "==> DE25 kernel: $(DE25_OUTPUT_DIR)/images/Image ($$(stat -L -c %s $(DE25_OUTPUT_DIR)/images/Image) bytes)"; \ + for d in "$$@"; do echo "==> DE25 dtb: $$d ($$(stat -L -c %s $$d) bytes)"; done; \ test -f $(DE25_OUTPUT_DIR)/images/rootfs.ext4 || { \ echo "FATAL: de25 build finished but produced no $(DE25_OUTPUT_DIR)/images/rootfs.ext4" >&2; \ echo " (BR2_TARGET_ROOTFS_EXT2 + _EXT2_4 select it -- a config that emits no" >&2; \ echo " rootfs is not a green build, whatever the kernel did.)" >&2; exit 1; }; \ - echo "==> DE25 rootfs: $(DE25_OUTPUT_DIR)/images/rootfs.ext4 ($$(stat -c %s $(DE25_OUTPUT_DIR)/images/rootfs.ext4) bytes)"; \ - echo " Bare developer OS -- no MiSTer binaries, no bootloader yet (D2.2)."; \ - echo "" + echo "==> DE25 rootfs: $(DE25_OUTPUT_DIR)/images/rootfs.ext4 ($$(stat -L -c %s $(DE25_OUTPUT_DIR)/images/rootfs.ext4) bytes)"; \ + if [ "$${DE25_ALLOW_NO_UBOOT:-0}" = 1 ] && [ ! -f $(DE25_OUTPUT_DIR)/images/u-boot.itb ]; then \ + echo "==> DE25 bl31/FIT: SKIPPED (DE25_ALLOW_NO_UBOOT=1 and no u-boot.itb was built)"; \ + else \ + test -f $(DE25_OUTPUT_DIR)/images/bl31.bin || { \ + echo "FATAL: de25 build finished but produced no $(DE25_OUTPUT_DIR)/images/bl31.bin" >&2; \ + echo " (BR2_TARGET_ARM_TRUSTED_FIRMWARE_BL31 + _IMAGES=\"bl31.bin\" select it.)" >&2; \ + echo " BL31 is what goes INSIDE u-boot.itb as the 'atf' image, so a missing" >&2; \ + echo " bl31.bin means the FIT below is either absent or built around a" >&2; \ + echo " binman-faked zero blob -- which boots nothing and says nothing." >&2; exit 1; }; \ + echo "==> DE25 bl31: $(DE25_OUTPUT_DIR)/images/bl31.bin ($$(stat -L -c %s $(DE25_OUTPUT_DIR)/images/bl31.bin) bytes)"; \ + test -f $(DE25_OUTPUT_DIR)/images/u-boot.itb || { \ + echo "FATAL: de25 build finished but produced no $(DE25_OUTPUT_DIR)/images/u-boot.itb" >&2; \ + echo " This is THE artifact of the bootloader half of the build: the factory" >&2; \ + echo " SPL in QSPI looks for a file of exactly that name on FAT partition 1" >&2; \ + echo " (SPL_FS_LOAD_PAYLOAD_NAME under SPL_LOAD_FIT, boot partition 1)." >&2; \ + echo " The usual cause is CONFIG_BINMAN having gone off: it is selected only" >&2; \ + echo " as 'select BINMAN if SPL_ATF' and it has no prompt, so anything that" >&2; \ + echo " turns CONFIG_SPL off takes the FIT with it, silently and with a green" >&2; \ + echo " U-Boot build. See board/mister/de25nano/uboot.fragment, SPL block." >&2; exit 1; }; \ + echo "==> DE25 FIT: $(DE25_OUTPUT_DIR)/images/u-boot.itb ($$(stat -L -c %s $(DE25_OUTPUT_DIR)/images/u-boot.itb) bytes)"; \ + echo " Verify its shape against the factory SPL contract with:"; \ + echo " $(DE25_OUTPUT_DIR)/host/bin/dumpimage -l $(DE25_OUTPUT_DIR)/images/u-boot.itb"; \ + echo " Bare developer OS -- no MiSTer binaries."; \ + echo ""; \ + fi + @if [ -f $(DE25_OUTPUT_DIR)/images/sdcard-de25.img ]; then \ + echo "==> DE25 card: $(DE25_OUTPUT_DIR)/images/sdcard-de25.img ($$(stat -L -c %s $(DE25_OUTPUT_DIR)/images/sdcard-de25.img) bytes)"; \ + echo " dd it to a card; docs/de25-sdcard.md."; \ + echo ""; \ + elif [ "$${DE25_ALLOW_NO_UBOOT:-0}" = 1 ] && [ ! -f $(DE25_OUTPUT_DIR)/images/u-boot.itb ]; then \ + echo "==> DE25 card: SKIPPED (DE25_ALLOW_NO_UBOOT=1 and no u-boot.itb) -- nothing"; \ + echo " this build produced can boot a board."; \ + echo ""; \ + else \ + echo "FATAL: de25 build finished but produced no $(DE25_OUTPUT_DIR)/images/sdcard-de25.img" >&2; \ + echo " (BR2_ROOTFS_POST_IMAGE_SCRIPT runs board/mister/de25nano/post-image.sh," >&2; \ + echo " which assembles the card and hands it to scripts/check-sdcard-de25.sh." >&2; \ + echo " A build that emits no card is not a green build.)" >&2; exit 1; \ + fi -# Escape hatches for iterating without hand-editing the checked-in defconfig. +# Escape hatches for iterating without hand-editing the checked-in fragments. # Both write to output-de25/; fold the result back into -# configs/mister_de25nano_defconfig (`savedefconfig`, then hand-restore the -# header comments -- see that file's own note) or into -# board/mister/de25nano/linux.fragment by hand. +# configs/fragments/de25nano.fragment by hand (a `savedefconfig` of +# output-de25/ is unordered, comment-free and NOT tracked -- +# docs/buildroot-config.md §1), into board/mister/de25nano/linux.config, or -- +# carefully, it is SHARED with the DE10 and must stay a no-op there +# (scripts/check-kernel-fragment-noop.sh) -- into +# board/mister/common/linux-mister.fragment. # # de25-linux-menuconfig exists as its own target for the same reason # rt-menuconfig does: the `%:` catch-all would forward a bare @@ -828,7 +947,7 @@ check-initramfs: exit $$rc # --- zImage_dtb (P1.11 / A3) ---------------------------------------------------- -# The REAL hook is BR2_ROOTFS_POST_IMAGE_SCRIPT in configs/mister_de10nano_defconfig +# The REAL hook is BR2_ROOTFS_POST_IMAGE_SCRIPT in configs/fragments/de10nano.fragment # (board/mister/de10nano/post-image.sh), which Buildroot runs automatically at the # end of every `$(BR_MAKE) all` and which already fails the build on a contract # violation -- so `make all` needs no extra step here, unlike check-initramfs above @@ -883,12 +1002,15 @@ sdcard: hostshim help: @echo "MiSTer BR2_EXTERNAL wrapper (TASKS.md P1.1)" @echo "" - @echo " make mister_de10nano_defconfig - load configs/mister_de10nano_defconfig" + @echo " make de10nano-defconfig - (re)generate output/.config from the DE10-Nano" + @echo " fragment stack (configs/fragments/, stacks.mk)" @echo " make menuconfig - interactive Buildroot config" @echo " make linux-menuconfig - interactive kernel config" - @echo " make savedefconfig - save current config back to a defconfig" + @echo " make savedefconfig - save current config to output/defconfig (fold" + @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 " 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" @@ -930,11 +1052,14 @@ help: @echo "" @echo "DE25-Nano developer OS (aarch64 / Agilex 5 -- docs/de25-nano-tasks.md D2.1):" @echo " make de25 - build the DE25-Nano image into output-de25/" - @echo " (aarch64 toolchain + mainline 7.2.2 kernel +" - @echo " minimal BusyBox ext4 rootfs; asserts images/Image" - @echo " and a .dtb exist). BARE DEVELOPER OS: no MiSTer" - @echo " binaries, no bootloader yet. Does NOT run" - @echo " 'initramfs' -- that cpio is armv7." + @echo " (aarch64 toolchain + mainline 7.2.3 kernel +" + @echo " minimal BusyBox ext4 rootfs + TF-A/U-Boot FIT +" + @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 " 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" @echo " make de25-linux-menuconfig - kernel menuconfig for the DE25 kernel" @echo " make de25-clean - rm -rf output-de25/" @@ -1041,7 +1166,7 @@ buildroot-unpack: $(BR_STAMP) # --- Forward everything else into Buildroot ------------------------------------ # make menuconfig, make linux-menuconfig, make savedefconfig, make -# mister_de10nano_defconfig (Buildroot's own %_defconfig rule finds it under +# mister_initramfs_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/PLAN.md b/PLAN.md index 159bb16..522deab 100644 --- a/PLAN.md +++ b/PLAN.md @@ -530,7 +530,7 @@ mister-linux/ ├── external.mk ├── Config.in ├── configs/ -│ ├── mister_de10nano_defconfig +│ ├── fragments/ (common, de10nano, de10nano-image, kernel-only, de25nano + stacks.mk) │ └── mister_initramfs_defconfig # stage-1 tiny static-BusyBox cpio (§5) ├── board/mister/de10nano/ │ ├── linux.config # full kernel config (make savedefconfig) diff --git a/README.md b/README.md index 33d1365..89c29e6 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ both sides — stock from the extracted stock `linux.img`, ours read off the bui **On version drift, since this table cites documents that can lag it.** The ground truth for "ours" is always the build pins — `BUILDROOT_VERSION` in the `Makefile` and -`configs/mister_de10nano_defconfig` — plus whatever Buildroot's own `.mk` files resolve +the DE10-Nano fragment stack under `configs/fragments/` — plus whatever Buildroot's own `.mk` files resolve to at that pin. The documents below are **dated analyses**, not a live mirror of those pins: a Renovate bump or a Buildroot line bump moves a package without rewriting the prose that reasoned about it. Where a document is behind the pin it now says so at the @@ -302,7 +302,7 @@ make, use the download-and-read form above, or the by-hand route. Stock forked Linux 5.15.1 in November 2021 and **never took a single subsequent 5.15.y stable release**. 5.15 itself reaches end-of-life in October 2026. This project tracks **6.18 LTS** — the exact patch level is -`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE` in `configs/mister_de10nano_defconfig`, +`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE` in `configs/fragments/de10nano.fragment`, and it is deliberately not repeated in prose here because stable `.y` releases land weekly. Pinned by version *and* SHA-256 against kernel.org, with Renovate opening a PR on every `.y` bump. @@ -585,11 +585,17 @@ shipped **byte-identical to stock's**, fetched by hash. ``` Makefile wrapper: fetches + hash-verifies Buildroot, forwards targets Config.in / external.mk BR2_EXTERNAL definition for the 16 in-tree packages -configs/ mister_de10nano_defconfig (the shipped image) - mister_kernel_defconfig (kernel-only base, shared by variants) - mister_rt.fragment (PREEMPT_RT / 7.x delta) +configs/fragments/ stacks.mk (which fragments form which config) + common.fragment (policy shared by every board + variant) + de10nano.fragment (DE10 arch/ABI, headers, kernel stanza) + 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) + 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/ linux.config minimal kernel defconfig (an absent CONFIG_X is NOT "off") linux-patches/ 37 carried patches: 36 MiSTer + 1 mainline backport (0047) @@ -662,7 +668,14 @@ driver-defined and there is nothing generic to wrap, and the internal `vfs_ioctl in-kernel on a 64-bit ARM SoC is the rewrite this project already had to do for 6.18: export a purpose-built attach helper from the loop driver itself and call it with `filp_open()`ed files instead of descriptors (`linux-patches-upstream/0100-…`, carried -for the upstream fork, not shipped in this image). The initramfs design, by contrast, +for the upstream fork, not shipped in this image). That rewrite contains no syscall and +no architecture-specific line, and it does compile for aarch64: applied to a pristine +6.18.48 tree with an arm64 toolchain, `init/do_mounts.o` and `drivers/block/loop.o` +build clean, referencing only `filp_open()`, `init_mount()` and the two exported loop +helpers. So the *rewritten* patch is portable; it is the *stock* patch that is not, and +the rewrite still has to be re-anchored on every kernel line it is carried to (it +already needs a context refresh for 7.x, where the `load_ramdisk=` block it sits next +to was removed). The initramfs design, by contrast, contains no architecture-specific line at all: `mount`, `losetup`, and `switch_root` behave identically on any CPU the kernel runs on. Should this image ever need to boot something that is not a Cyclone V, the boot path comes along unchanged, and @@ -712,7 +725,7 @@ inventoried against real mr-fusion output in ```sh make # prints help — deliberately NOT a build -make mister_de10nano_defconfig # load the config +make de10nano-defconfig # generate output/.config from the fragment stack make all # build (first run bootstraps a cross-toolchain — hours, not minutes) ``` diff --git a/TASKS.md b/TASKS.md index 4f68962..f8a139a 100644 --- a/TASKS.md +++ b/TASKS.md @@ -289,7 +289,8 @@ boots to a serial console on real hardware (P1.13). - [x] **P1.1 — BR2_EXTERNAL skeleton** — [SONNET] — Size S — Depends: P0.9 `external.desc`, `external.mk`, `Config.in`, `configs/mister_de10nano_defconfig` - (minimal, builds nothing yet), plus a top-level `Makefile`/script that downloads the + (minimal, builds nothing yet; since the 2026-09 fragment split that file is the + `configs/fragments/` de10nano stack — `docs/buildroot-config.md`), plus a top-level `Makefile`/script that downloads the pinned Buildroot 2026.05.x tarball, verifies its SHA-256, unpacks to `work/buildroot/`, and invokes it with `BR2_EXTERNAL` set. Buildroot is never vendored (G4/§6). **Reference:** `/mnt/source/sb-enema/Makefile` — a working 2026.02.3 pinned-tarball @@ -706,7 +707,8 @@ Exit criterion: hardware matrix (§11) green (P3.13). **Result: module-autoload half already done pre-task (kmod+depmod xz support, modules.dep/modules.alias populated — see the P3.3 (core) commit). This pass covers only - `/lib/firmware` population.** `configs/mister_de10nano_defconfig` gained + `/lib/firmware` population.** `configs/mister_de10nano_defconfig` (now + `configs/fragments/de10nano-image.fragment`, §5.28 of `docs/buildroot-config.md`) gained `BR2_PACKAGE_LINUX_FIRMWARE` + 9 sub-options (`MEDIATEK_MT7601U/MT7610E/MT7650/MT76X2E`, `RALINK_RT2XX`, `RTL_81XX/RTL_87XX/RTL_87XX_BT/RTL_88XX_BT`), `BR2_PACKAGE_WIRELESS_REGDB` (a separate package from linux-firmware for `regulatory.db`/`.p7s`), and diff --git a/board/mister/common/linux-mister.fragment b/board/mister/common/linux-mister.fragment new file mode 100644 index 0000000..c8a8fb9 --- /dev/null +++ b/board/mister/common/linux-mister.fragment @@ -0,0 +1,723 @@ +# +# board/mister/common/linux-mister.fragment — the ARCH-NEUTRAL MiSTer kernel +# personality, shared by every board this repo builds. +# +# WHAT THIS FILE IS +# ----------------- +# A MiSTer is defined, at the kernel level, far more by *what a user plugs into +# it* than by which FPGA SoC it happens to run on: USB gamepads and arcade +# encoders, Bluetooth controllers, Wi-Fi dongles, USB audio, USB storage, the +# exFAT/NFS/CIFS filesystems its games live on. None of that is arch-specific. +# This file is that set, extracted from the DE10-Nano's kernel config, which is +# the canonical MiSTer hardware list (it is a port of the stock MiSTer kernel +# config, see docs/kernel-config-deltas.md). +# +# HOW THE TWO BOARDS CONSUME IT +# ----------------------------- +# DE25-Nano (today) BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE = the board's own +# minimal arm64 base, board/mister/de25nano/linux.config +# BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES = THIS FILE +# -> Buildroot merges base + fragment with +# support/kconfig/merge_config.sh, then `olddefconfig`. +# +# DE10-Nano (today) still ships one whole file, +# board/mister/de10nano/linux.config. 404 of the 409 +# lines below appear in it verbatim; the other 5 are +# SENTINELS (the USB, USB_HID, HID, INPUT and WLAN menu +# gates) that the DE10 gets from a Kconfig `default` or, +# in the USB case, from a `select` — they are named here +# because they are single points of failure, and each was +# checked to already hold that value in the DE10's +# RESOLVED config. The DE10 does NOT reference this +# fragment yet — adopting it is a separate change, and +# the invariant below is what makes that change safe. +# +# ***THE INVARIANT: THIS FILE MUST BE A NO-OP ON THE DE10.*** +# Every single line here is byte-identical to the corresponding line in the +# DE10's RESOLVED kernel config (output/build/linux-/.config), not merely +# to its minimal defconfig. Merging this fragment onto the DE10's resolved +# config must therefore produce zero `merge_config.sh` "redefined" warnings and +# a bit-identical config after `olddefconfig`. +# +# Check it mechanically: scripts/check-kernel-fragment-noop.sh +# +# That check is the whole point. It is what lets the DE10 adopt this fragment +# later with a provable zero-delta, and it is what stops a DE25-motivated edit +# from silently changing the DE10's driver set. +# +# FIVE RULES FOR EDITING THIS FILE +# -------------------------------- +# 1. A symbol belongs here only if BOTH boards want the SAME VALUE and the +# symbol is not arch- or SoC-specific. Anything that names an on-chip IP +# block (8250_DW, DESIGNWARE_I2C, DWAPB GPIO, DW_WATCHDOG, SDHCI_CADENCE, +# PL330, the FPGA manager, the SoC's clk/reset drivers) lives in the +# board's own linux.config, NOT here — even when both boards happen to +# have the same IP. +# 2. A symbol that does not exist in BOTH kernel trees must not be named here. +# Kernel versions differ between boards (DE10 is on 6.18.y, DE25 on 7.2.y). +# `olddefconfig` DISCARDS an unknown symbol silently — never an error — so a +# version-skewed line is a silent no-op, not a build failure. Two symbols +# were excluded for exactly this reason; see docs/de25-kernel-config.md §9. +# 3. An ABSENT symbol is NOT an off symbol. Several `# CONFIG_X is not set` +# lines below are load-bearing because the symbol is `default y` (or +# `default m` under CONFIG_MMC=y) and would otherwise switch itself on. +# They are marked where that is the case. +# 4. Never add a symbol here without checking its value in the DE10's resolved +# .config first. Rule 1 is a judgement; the invariant is arithmetic. +# 5. NEVER write a bare `CONFIG_` token in a COMMENT here when the file +# also sets . merge_config.sh resolves a symbol's "new value" with +# `grep -w CONFIG_ `, which happily matches prose, so a +# comment mentioning a symbol makes merge_config print a FALSE "Value of +# CONFIG_ is redefined by fragment" warning on every build — and that +# warning is exactly the signal the no-op check reads. Name symbols in +# comments without the prefix ("the USB symbol", "SECCOMP"). +# +# SIX SYMBOLS BELOW REQUIRE CARRIED PATCHES, on BOTH boards: +# HID_GUNCON2 (0010), HID_GUNCON3 (0011), HID_FTEC (0012), +# HID_VADER4 (0013), HID_GAMECUBE_ADAPTER + _FF (0014). +# Both board patch directories carry 0010-0014 (the DE25's are symlinks into +# the DE10's). If a board ever drops one of those patches, its symbol vanishes +# from that board's tree and this fragment silently loses a driver there. +# +# Provenance: extracted 2026-09-02 from board/mister/de10nano/linux.config and +# verified line-by-line against output/build/linux-6.18.48/.config (all 475 +# lines of the DE10 defconfig were confirmed to resolve to exactly their stated +# value first, so any subset of it is a no-op by construction). +# Rationale, per subsystem, and the per-kernel-bump re-check list: +# docs/de25-kernel-config.md. +# + +# ============================================================================= +# 1. Core kernel personality +# ============================================================================= +# SYSVIPC: required by several MiSTer userland pieces and by Buildroot's +# default init. TASK*/TASKSTATS/PROFILING/RELAY: stock-parity accounting. +# IKCONFIG_PROC: /proc/config.gz — the only way to answer "what is actually in +# the kernel on this board" from the board itself, which this project relies on +# repeatedly (see docs/kernel-config-deltas.md). +CONFIG_SYSVIPC=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# cgroups + namespaces as the DE10 has them — enough for the container/Docker +# use the fork enabled MACVLAN/TUN for, no more. Notably NOT the full +# MEMCG/BLK_CGROUP set: the DE10 does not have it and neither board needs it. +CONFIG_CGROUPS=y +CONFIG_CPUSETS=y +CONFIG_NAMESPACES=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_SYSFS_SYSCALL=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +# 1000 Hz is a MiSTer latency posture, not a default: the generic Kconfig.hz +# choice defaults to HZ_250, so this is a real change on any base. +CONFIG_HZ_1000=y +# SUSPEND is `default y` wherever ARCH_SUSPEND_POSSIBLE — an explicit off is +# required, not optional (rule 3). Nothing on either board suspends; the DE25 +# has no PSCI idle-states in its DTS and the DE10 has no PM hardware at all. +# CONFIG_SUSPEND is not set +CONFIG_PM=y +# SECCOMP OFF IS LOAD-BEARING, AND IT REACHES INTO THE BUILDROOT DEFCONFIG. +# It is `default y` on arm64 and matches stock on the DE10, so this is a real +# change on any arm64 base. The coupling: Buildroot's BR2_PACKAGE_OPENSSH_SANDBOX +# is `default y`, and since openssh 10.4 a failed prctl(PR_SET_SECCOMP) is +# fatal() rather than debug() — so an image with SECCOMP off and the sandbox on +# gets an sshd that BINDS AND LISTENS while killing every connection preauth, +# password and key alike. The DE10 fixes that in its own defconfig with +# `# BR2_PACKAGE_OPENSSH_SANDBOX is not set` (see its comment there, commit +# 9824cd6). ANY BOARD THAT ADOPTS THIS FRAGMENT AND SHIPS OPENSSH MUST CARRY +# THAT DEFCONFIG LINE TOO. (Note it is a configure-time flag: changing it needs +# `make openssh-dirclean` or the stale stamp ships the same broken sshd.) +# CONFIG_SECCOMP is not set + +# ============================================================================= +# 2. Modules and the .ko.xz layout +# ============================================================================= +# MODULE_COMPRESS_XZ is an ABI contract, not a size tweak: the on-disk module +# layout is `.ko.xz` and docs/abi-contract.md + the Downloader's expectations +# are written against it. docs/kernel-config-deltas.md §3.1 records this as one +# of the three symbols `olddefconfig` silently dropped once already. +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_COMPRESS=y +CONFIG_MODULE_COMPRESS_XZ=y + +# ============================================================================= +# 3. Block layer, partitions, binfmt +# ============================================================================= +# ATARI_PARTITION: MiSTer mounts Atari/Amiga-era disk images. PARTITION_ADVANCED +# is its gate. +CONFIG_PARTITION_ADVANCED=y +CONFIG_ATARI_PARTITION=y +# CONFIG_IOSCHED_BFQ is not set +CONFIG_BINFMT_MISC=y +# Loop devices: how MiSTer mounts .img/.vhd game media. RAM disks at the DE10's +# geometry (2 x 8 MiB). +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=8192 + +# ============================================================================= +# 4. /dev and hotplug +# ============================================================================= +# devtmpfs + its automount are BOOT-PATH essentials and they live here, not in +# the per-board base: they are arch-neutral and both boards need exactly this. +# A board base file alone therefore does NOT boot — this fragment is mandatory, +# not optional. UEVENT_HELPER is the mdev hotplug path (docs/init-parity.md). +CONFIG_UEVENT_HELPER=y +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y + +# ============================================================================= +# 5. Networking core +# ============================================================================= +# IPv6 is OFF, matching the DE10 and stock. IP_PNP* keep kernel-level IP +# configuration (and therefore an NFS root) possible. +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_NET_KEY=y +CONFIG_NET_KEY_MIGRATE=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_RARP=y +# CONFIG_IPV6 is not set +CONFIG_NETWORK_PHY_TIMESTAMPING=y + +# --- netfilter: exactly the DE10's set (iptables-legacy + conntrack helpers) -- +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_PROCFS=y +# CONFIG_NF_CT_PROTO_SCTP is not set +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_SIP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NETFILTER_XTABLES_LEGACY=y +CONFIG_NETFILTER_XT_MARK=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_IP_NF_IPTABLES_LEGACY=y +CONFIG_NF_LOG_ARP=y +CONFIG_NF_LOG_IPV4=y +CONFIG_IP_NF_IPTABLES=y +# IP_NF_FILTER + IP_NF_TARGET_REJECT: docs/kernel-config-deltas.md §3.3 — these +# two were silently dropped once by olddefconfig and took the whole legacy +# iptables filter table with them. +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_VLAN_8021Q=y +CONFIG_VLAN_8021Q_GVRP=y + +# ============================================================================= +# 6. Bluetooth — the controller path +# ============================================================================= +# BT_HIDP is what turns a paired DS4/DualSense/Pro Controller into an input +# device. RFCOMM_TTY is used by the pairing tooling (docs/bluetooth-parity.md). +# btusb is =m and autoloads on the dongle's modalias; the two built-in vendor +# options inside it (MTK) and the two other transports match the DE10 exactly. +CONFIG_BT=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_HCIBTUSB_MTK=y +CONFIG_BT_HCIBCM203X=y +CONFIG_BT_ATH3K=m + +# ============================================================================= +# 7. Wireless stack +# ============================================================================= +# mac80211/cfg80211 are =m on the DE10 and stay =m here: a Wi-Fi dongle is +# optional hardware, and the stack costs ~1 MiB resident when loaded. +# CFG80211_WEXT is needed by the older wireless tooling MiSTer scripts use. +CONFIG_CFG80211=m +# CONFIG_CFG80211_DEFAULT_PS is not set +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_LEDS=y +# Sentinel: WLAN is the menu gate every driver below hangs off. `default y`, +# but naming it makes an upstream demotion a visible merge warning. +CONFIG_WLAN=y + +# ============================================================================= +# 8. Network devices +# ============================================================================= +CONFIG_NETDEVICES=y +# MACVLAN + TUN: container/Docker networking, and the only way to give an +# emulated NIC (e.g. the Amiga A2065) its own MAC over wireless. =y not =m — +# the module directory does not match `uname -r`, so autoload is unreliable. +CONFIG_MACVLAN=y +CONFIG_TUN=y +# --- vendor gates: every wired-NIC vendor menu the DE10 turns OFF ------------- +# These are `bool ... default y` menu gates; leaving them absent turns dozens of +# drivers back on (rule 3). The board's own linux.config keeps whichever vendor +# menu its SoC MAC lives under (STMICRO/stmmac on both boards today) and adds +# any further arch-only vendor gates its base exposes. +# NOT listed, although the DE10 turns them off too: NET_VENDOR_CIRRUS and +# NET_VENDOR_FARADAY are `depends on ARM` and do not exist on arm64, so naming +# them here would be a line that survives on one board and is silently dropped +# on the other — exactly what rule 2 forbids. +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# --- PHYs. Board-agnostic on purpose: which part is fitted is a DTS question, +# and phylib binds by MDIO ID, not by DT. Both boards carry both drivers. +CONFIG_MARVELL_PHY=y +CONFIG_MICREL_PHY=y +# --- PPP: dial-up/serial-link cores and the MiSTer "modem" use cases. +CONFIG_PPP=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=y +CONFIG_PPP_ASYNC=y +# USB Ethernet adapters are NOT supported on the DE10 and are not added here — +# `default y` menu gate, so the explicit off is load-bearing (rule 3). +# CONFIG_USB_NET_DRIVERS is not set + +# ============================================================================= +# 9. Wi-Fi dongles — the exact DE10 set, chip for chip +# ============================================================================= +# Every driver here is mainline (ADR 0016 "mainline-first"); all are =m and +# autoload on the dongle's modalias. Firmware comes from the board defconfig's +# BR2_PACKAGE_LINUX_FIRMWARE_* selection plus package/linux-firmware-extra — +# a driver enabled here with no firmware selected in the board fragment binds and +# then fails at request_firmware(). See docs/wifi-parity.md. +# +# Every *_SDIO bus driver is explicitly OFF: SDIO bus support is `default y` +# whenever CONFIG_MMC=y (both boards, for the SD card), so olddefconfig would +# otherwise build SDIO Wi-Fi drivers for a slot neither board has (rule 3). +# Every *_PCIE/*E sibling is unreachable — neither board sets CONFIG_PCI. +# +# --- Atheros: ath9k_htc (AR9271/AR7010), carl9170 (AR9170), ath6kl (AR600x) --- +CONFIG_WLAN_VENDOR_ATH=y +CONFIG_ATH9K_HTC=m +CONFIG_CARL9170=m +CONFIG_ATH6KL=m +CONFIG_ATH6KL_USB=m +# CONFIG_WLAN_VENDOR_ADMTEK is not set +# CONFIG_WLAN_VENDOR_ATMEL is not set +# --- Broadcom/Cypress FullMAC USB (BCM43143/43236B/43242A/43569/4373) -------- +CONFIG_WLAN_VENDOR_BROADCOM=y +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_USB=y +# CONFIG_BRCMFMAC_SDIO is not set +# CONFIG_BRCMSMAC is not set +# CONFIG_WLAN_VENDOR_INTEL is not set +# CONFIG_WLAN_VENDOR_INTERSIL is not set +# --- Marvell: Libertas + mwifiex USB ---------------------------------------- +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBERTAS_THINFIRM=m +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_MWIFIEX=m +CONFIG_MWIFIEX_USB=m +# --- MediaTek mt76 USB: 7601U, 76x0U, 76x2U, 7663U, 7921U(=MT7961), 7925U ---- +CONFIG_MT7601U=m +CONFIG_MT76x0U=m +CONFIG_MT76x2U=m +CONFIG_MT7663U=m +CONFIG_MT7921U=m +CONFIG_MT7925U=m +# CONFIG_WLAN_VENDOR_MICROCHIP is not set +# --- Ralink rt2x00 USB ------------------------------------------------------ +CONFIG_RT2X00=m +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_RT2800USB_UNKNOWN=y +# --- Realtek: legacy rtlwifi/rtl8xxxu, then the whole mainline rtw88/rtw89 set +CONFIG_RTL8187=m +CONFIG_RTL8192CU=m +CONFIG_RTL8192DU=m +# CONFIG_RTLWIFI_DEBUG is not set +CONFIG_RTL8XXXU=m +CONFIG_RTL8XXXU_UNTESTED=y +CONFIG_RTW88=m +CONFIG_RTW88_8822BU=m +CONFIG_RTW88_8821CU=m +CONFIG_RTW88_8822CU=m +CONFIG_RTW88_8814AU=m +CONFIG_RTW88_8723DU=m +CONFIG_RTW88_8821AU=m +CONFIG_RTW88_8812AU=m +CONFIG_RTW89=m +CONFIG_RTW89_8851BU=m +CONFIG_RTW89_8852BU=m +# --- Redpine RS9113/RS9116 USB ---------------------------------------------- +CONFIG_WLAN_VENDOR_RSI=y +CONFIG_RSI_91X=m +CONFIG_RSI_USB=m +# CONFIG_RSI_SDIO is not set +# --- vendor menus left off -------------------------------------------------- +# CONFIG_WLAN_VENDOR_ST is not set +# CONFIG_WLAN_VENDOR_TI is not set +# CONFIG_WLAN_VENDOR_ZYDAS is not set +# CONFIG_WLAN_VENDOR_QUANTENNA is not set +# EEPROM_93CX6 is a `select`ed dependency of rt2x00 and rtl8187; named here +# because it is otherwise invisible and its disappearance would be silent. +CONFIG_EEPROM_93CX6=y + +# ============================================================================= +# 10. Input — the core MiSTer contract +# ============================================================================= +# joydev/evdev/uinput and mousedev are the three device classes MiSTer_Main and +# every controller-mapping tool open. uinput is what the pairing/remap helpers +# write through. PS/2 keyboard and mouse are OFF: neither board has a PS/2 +# controller, and the DE10's Keyrah adapters arrive over USB HID. +CONFIG_INPUT=y +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_JOYDEV=y +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_MOUSE_PS2 is not set +CONFIG_MOUSE_APPLETOUCH=y +CONFIG_MOUSE_BCM5974=y +CONFIG_MOUSE_SYNAPTICS_USB=y +# Classic serial/USB joysticks — the pre-HID arcade and flight-stick era. +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_IFORCE=y +CONFIG_JOYSTICK_IFORCE_USB=y +CONFIG_JOYSTICK_IFORCE_232=y +CONFIG_JOYSTICK_WARRIOR=y +CONFIG_JOYSTICK_MAGELLAN=y +CONFIG_JOYSTICK_SPACEORB=y +CONFIG_JOYSTICK_SPACEBALL=y +CONFIG_JOYSTICK_STINGER=y +CONFIG_JOYSTICK_TWIDJOY=y +CONFIG_JOYSTICK_ZHENHUA=y +# xpad is =m (it is the one input driver users swap for a fork). +CONFIG_JOYSTICK_XPAD=m +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +# SERIO stays (iforce-232 needs it) but the legacy serial-port attach does not. +# CONFIG_SERIO_SERPORT is not set +# CONFIG_LEGACY_PTYS is not set + +# ============================================================================= +# 11. HID — every hid-* driver the DE10 enables +# ============================================================================= +# Sentinels first: HID is the bus, USB_HID is the transport every wired pad +# arrives on. Both are `default y` but both are the single point of failure for +# the entire controller story, so they are named. +CONFIG_HID=y +CONFIG_USB_HID=y +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=y +CONFIG_HID_ACRUX=y +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_BELKIN=y +CONFIG_HID_CHERRY=y +CONFIG_HID_CHICONY=y +CONFIG_HID_CORSAIR=y +CONFIG_HID_COUGAR=y +CONFIG_HID_DRAGONRISE=y +CONFIG_HID_ELECOM=y +CONFIG_HID_EZKEY=y +CONFIG_HID_GEMBIRD=y +CONFIG_HID_HOLTEK=y +CONFIG_HID_GOOGLE_STADIA_FF=y +CONFIG_HID_KEYTOUCH=y +CONFIG_HID_KYE=y +CONFIG_HID_ICADE=y +CONFIG_HID_KENSINGTON=y +CONFIG_HID_LED=y +CONFIG_HID_LENOVO=y +CONFIG_HID_LOGITECH=y +CONFIG_HID_LOGITECH_DJ=y +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MAYFLASH=y +CONFIG_HID_REDRAGON=y +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MONTEREY=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_NINTENDO=y +CONFIG_NINTENDO_FF=y +CONFIG_HID_ORTEK=y +CONFIG_HID_PANTHERLORD=y +CONFIG_PANTHERLORD_FF=y +CONFIG_HID_PLANTRONICS=y +CONFIG_HID_PLAYSTATION=y +CONFIG_PLAYSTATION_FF=y +CONFIG_HID_PRIMAX=y +CONFIG_HID_RETRODE=y +CONFIG_HID_ROCCAT=y +CONFIG_HID_SAITEK=y +CONFIG_HID_SONY=y +CONFIG_SONY_FF=y +CONFIG_HID_SPEEDLINK=y +CONFIG_HID_STEAM=y +CONFIG_HID_SUNPLUS=y +CONFIG_HID_RMI=y +CONFIG_HID_GREENASIA=y +CONFIG_HID_SMARTJOYPLUS=y +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_THRUSTMASTER=y +CONFIG_HID_WIIMOTE=y +CONFIG_HID_XINMO=y +CONFIG_HID_ZEROPLUS=y +CONFIG_HID_PID=y +CONFIG_HID_BETOP_FF=y +CONFIG_HID_BIGBEN_FF=y +CONFIG_HID_MEGAWORLD_FF=y +CONFIG_HID_STEELSERIES=y +# --- PATCH-GATED. These six symbols do not exist in a stock tree; they come +# from linux-patches/0010-0014, which both boards carry (the DE25's entries are +# symlinks into the DE10's series). Drop a patch on either board and the +# corresponding line here becomes a silent no-op on that board. +CONFIG_HID_GUNCON2=y +CONFIG_HID_GUNCON3=y +CONFIG_HID_FTEC=y +CONFIG_HID_GAMECUBE_ADAPTER=y +CONFIG_HID_GAMECUBE_ADAPTER_FF=y +CONFIG_HID_VADER4=m +CONFIG_USB_HIDDEV=y + +# ============================================================================= +# 12. USB host — class drivers and the dongle-facing bus plumbing +# ============================================================================= +# Sentinel, and an important one: the USB symbol has NO `default` in Kconfig at +# all. On the DE10 it is currently switched on only as a side effect of +# `select USB` inside MOUSE_APPLETOUCH / MOUSE_BCM5974 / MOUSE_SYNAPTICS_USB. +# That is far too fragile to leave implicit for the bus every MiSTer peripheral +# arrives on, so it is stated. +# (The HOST CONTROLLER driver is per-board and lives in the board's own file: +# dwc2 on both boards today, but that is an SoC fact, not a shared one.) +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_DYNAMIC_MINORS=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +# usbip: used by the debug rig to forward a controller from a host PC to the +# board without physically moving it (docs/debug-tooling.md). +CONFIG_USBIP_CORE=y +CONFIG_USBIP_VHCI_HCD=y +CONFIG_USBIP_HOST=y +# USB serial adapters — MIDI/serial-link cores and console cables. +CONFIG_USB_SERIAL=y +CONFIG_USB_SERIAL_CONSOLE=y +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=y +CONFIG_USB_SERIAL_CH341=y +CONFIG_USB_SERIAL_CP210X=y +CONFIG_USB_SERIAL_FTDI_SIO=y +CONFIG_USB_SERIAL_PL2303=y +# PHY plumbing. NOP_USB_XCEIV is required by BOTH boards' USB DT: the DE10's +# dwc2 and the DE25's usb0 both reference a `usb-nop-xceiv` node. +CONFIG_USB_ULPI_BUS=y +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_ULPI=y + +# ============================================================================= +# 13. SCSI / USB mass storage +# ============================================================================= +# There is no real SCSI HBA on either board; this is the transport layer USB +# storage rides on. SCSI_LOWLEVEL off keeps every actual HBA driver out. +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_BLK_DEV_SR=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_SCSI_LOWLEVEL is not set + +# ============================================================================= +# 14. Sound +# ============================================================================= +# USB audio is the arch-neutral half of MiSTer's audio story: USB DACs and +# headsets. The board's OWN audio path (the DE10's MiSTer audio SPI codec, from +# linux-patches/0002) is NOT here — it is patch-gated and board-specific. +# SND_DUMMY gives userland a card to open when no real one is present. +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_OSSEMUL=y +CONFIG_SND_HRTIMER=y +# CONFIG_SND_SUPPORT_OLD_API is not set +CONFIG_SND_SEQUENCER=y +CONFIG_SND_SEQUENCER_OSS=y +CONFIG_SND_DUMMY=y +CONFIG_SND_USB_AUDIO=y + +# ============================================================================= +# 15. I2C, GPIO, LEDs, RTC, regulators, watchdog core, hwrng +# ============================================================================= +# Cores and userland ABIs only. Every controller driver (DESIGNWARE_PLATFORM, +# GPIO_DWAPB, DW_WATCHDOG) is on-chip IP and lives in the board's own file. +CONFIG_HW_RANDOM=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +# HELPER_AUTO off keeps the algorithm bit-bang helpers from being pulled in +# wholesale; I2C_GPIO is enabled explicitly instead. +# CONFIG_I2C_HELPER_AUTO is not set +CONFIG_I2C_SMBUS=y +CONFIG_I2C_GPIO=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_SYSFS=y +CONFIG_WATCHDOG=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +# LEDs. LEDS_BRIGHTNESS_HW_CHANGED is what linux-patches/0029 teaches leds-gpio +# to report; LEDS_USER is /dev/uleds, used by the controller LED tooling; the +# multicolour class is what hid-playstation and hid-nintendo register their +# player/lightbar LEDs through (patches 0032/0033/0041/0042). +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=y +CONFIG_LEDS_BRIGHTNESS_HW_CHANGED=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_USER=y +CONFIG_LEDS_TRIGGERS=y +# The three I2C RTC parts MiSTer add-on boards fit (docs/rtc-parity.md). All +# three are plug-in modules on an I2C header, i.e. user hardware, not SoC. +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_DS1307=y +CONFIG_RTC_DRV_PCF8563=y +CONFIG_RTC_DRV_M41T80=y + +# ============================================================================= +# 16. Filesystems +# ============================================================================= +# ext4 (rootfs), vfat (the FAT boot partition), exfat (the MiSTer data +# partition convention, ADR 0010 / 0019) — all BOOT PATH, all =y, and all here +# rather than in the board base because they are arch-neutral and identical. +# FAT_DEFAULT_UTF8 matches stock. NTFS3 is =m: real but rarely used. +# AFFS is the Amiga filesystem; ISO9660/UDF are for CD images; FUSE+CUSE are +# what the userland mounters (exfat tooling, network mounts) need. +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_UTF8=y +CONFIG_EXFAT_FS=y +CONFIG_NTFS3_FS=m +# CONFIG_DNOTIFY is not set +CONFIG_FUSE_FS=y +CONFIG_CUSE=y +CONFIG_FSCACHE=y +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=y +CONFIG_TMPFS=y +CONFIG_CONFIGFS_FS=y +CONFIG_AFFS_FS=y +# Network filesystems: NFS client (ADR 0022) and CIFS/SMB (docs/samba-parity.md, +# docs/netfs-parity.md). NOTE: NFS_V4_1 is deliberately absent — see rule 2 and +# docs/de25-kernel-config.md §9; it was removed as a separate symbol after 6.18 +# and NFSv4.1 is unconditional in 7.x. +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_2=y +# CONFIG_NFS_V4_2_READ_PLUS is not set +CONFIG_CIFS=y +# CONFIG_CIFS_STATS2 is not set +# Codepages. UTF8 is NOT optional with exfat (its default iocharset is utf8 and +# a missing codepage fails the mount at runtime, not at build time). The rest +# are the stock set, for FAT/CIFS/ISO9660 media authored in other locales. +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_855=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_936=y +CONFIG_NLS_CODEPAGE_950=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_ISO8859_15=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_KOI8_U=y +CONFIG_NLS_MAC_CYRILLIC=y +CONFIG_NLS_UTF8=y + +# ============================================================================= +# 17. Keys and crypto +# ============================================================================= +# The generic-C algorithms CIFS/NFS/PPP-MPPE need. No arch accelerators here: +# those are per-arch symbols (CRYPTO_AES_ARM vs CRYPTO_AES_ARM64_*) and belong +# in the board's own file if wanted at all. +CONFIG_ENCRYPTED_KEYS=y +CONFIG_INIT_STACK_NONE=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_CTS=y +CONFIG_CRYPTO_XTS=y +CONFIG_CRYPTO_SEQIV=y +CONFIG_CRYPTO_ECHAINIV=y +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_CRC32C=y + +# ============================================================================= +# 18. Diagnostics +# ============================================================================= +# The P3.13 crash/hang triage set. On a board with a serial console and no +# display, a kernel that limps after an oops is strictly worse than one that +# panics loudly: PANIC_ON_OOPS + SOFTLOCKUP_DETECTOR + WQ_WATCHDOG + +# DETECT_HUNG_TASK between them cover a spinning CPU, a stalled workqueue and a +# wedged D-state I/O. MAGIC_SYSRQ is the last resort over the same cable. +# FUNCTION_TRACER is the ftrace substrate docs/debug-tooling.md assumes. +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_FS=y +CONFIG_DETECT_HUNG_TASK=y +# CONFIG_RCU_TRACE is not set +CONFIG_FUNCTION_TRACER=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_SOFTLOCKUP_DETECTOR=y +CONFIG_WQ_WATCHDOG=y diff --git a/board/mister/de10nano/initramfs-post-build.sh b/board/mister/de10nano/initramfs-post-build.sh index fdf0c3e..368d02a 100755 --- a/board/mister/de10nano/initramfs-post-build.sh +++ b/board/mister/de10nano/initramfs-post-build.sh @@ -25,7 +25,7 @@ # and switch_roots. mkfs.exfat in particular has no business being one `sh` # typo away from the boot path of a device whose data partition is the thing it # would reformat. (The full set does ship in the ROOTFS, where a user with a -# shell can reach it -- BR2_PACKAGE_EXFATPROGS in mister_de10nano_defconfig.) +# shell can reach it -- BR2_PACKAGE_EXFATPROGS in configs/fragments/de10nano-image.fragment.) # # KEEP THIS LIST IN STEP with what the initramfs /init actually invokes. The # Makefile's `initramfs-verify` target asserts both directions against the built diff --git a/board/mister/de10nano/linux.config b/board/mister/de10nano/linux.config index efb18be..e61270d 100644 --- a/board/mister/de10nano/linux.config +++ b/board/mister/de10nano/linux.config @@ -56,7 +56,7 @@ CONFIG_ATARI_PARTITION=y CONFIG_BINFMT_MISC=y # >>> DEBUG TOOLING — TEMPORARY, REMOVE AS ONE BLOCK <<< (docs/debug-tooling.md) # Enabled on request, alongside the gdb/strace/perf/rt-tests block in -# configs/mister_de10nano_defconfig. This is a DELIBERATE DIVERGENCE FROM STOCK: +# configs/fragments/de10nano-image.fragment. This is a DELIBERATE DIVERGENCE FROM STOCK: # stock's own config has `# CONFIG_COREDUMP is not set` # (docs/stock-inventory/stock-linux.config:721), and so did this file until now. # @@ -82,7 +82,7 @@ CONFIG_BINFMT_MISC=y # round-trip this file, restore this block by hand afterwards — the same # hand-restore the defconfig header already demands for its own comments. # -# NOTE this file is shared with configs/mister_kernel_defconfig, so the RT/beta +# NOTE this file is shared with the kernel-only fragment stack (configs/fragments/stacks.mk), so the RT/beta # kernel gets coredumps too. That is wanted, not a side effect: the RT variant is # the one under active on-hardware investigation. # diff --git a/board/mister/de10nano/post-image.sh b/board/mister/de10nano/post-image.sh index 5d671d6..15d6fb6 100755 --- a/board/mister/de10nano/post-image.sh +++ b/board/mister/de10nano/post-image.sh @@ -85,7 +85,7 @@ echo "$prog: wrote $out ($(wc -c <"$out") bytes)" ################################################################################ # P2.5 (A9) — linux.img: the flashable, loop-mounted rootfs image. # -# KERNEL-ONLY CONFIGS SKIP THIS HALF. configs/mister_kernel_defconfig (the +# KERNEL-ONLY CONFIGS SKIP THIS HALF. the kernel-only fragment stack (configs/fragments/kernel-only.fragment) (the # kernel-variant base, ADR 0021 as amended 2026-07-18) reuses this script for # the zImage_dtb assembly above but builds only a rootfs TAR — there is no # rootfs.ext2 and nothing to ship as linux.img. Gate on what the DRIVING @@ -101,7 +101,7 @@ if [ -n "${BR2_CONFIG:-}" ] && [ -f "${BR2_CONFIG:-}" ] \ fi ################################################################################ -# BR2_TARGET_ROOTFS_EXT2 (ext4 variant, see configs/mister_de10nano_defconfig +# BR2_TARGET_ROOTFS_EXT2 (ext4 variant, see configs/fragments/de10nano-image.fragment # for why that mechanism and not genimage) writes the actual filesystem to # BINARIES_DIR/rootfs.ext2 -- that name is fixed by fs/ext2/ext2.mk # regardless of the ext2/3/4 GEN choice; Buildroot additionally symlinks diff --git a/board/mister/de10nano/rootfs-overlay/etc/udev/rules.d/70-persistent-net.rules b/board/mister/de10nano/rootfs-overlay/etc/udev/rules.d/70-persistent-net.rules index e890f81..f0fcdf8 100644 --- a/board/mister/de10nano/rootfs-overlay/etc/udev/rules.d/70-persistent-net.rules +++ b/board/mister/de10nano/rootfs-overlay/etc/udev/rules.d/70-persistent-net.rules @@ -199,7 +199,7 @@ # output/target/sbin -> usr/sbin, so stock's /sbin/ifup and /sbin/ifdown # spellings resolve to those same two entries (BR2_ROOTFS_MERGED_USR=y). # output/target/usr/sbin/iw, a 264720-byte ARM ELF, from -# BR2_PACKAGE_IW=y at configs/mister_de10nano_defconfig:698 -- the pre-up +# BR2_PACKAGE_IW=y at configs/fragments/de10nano-image.fragment -- the pre-up # loop's `iw dev` would be a permanent 20s no-op without it. SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", KERNEL=="wlan*", RUN+="/etc/wifi-hotplug.sh up %k" diff --git a/board/mister/de10nano/rootfs-overlay/etc/wifi-hotplug.sh b/board/mister/de10nano/rootfs-overlay/etc/wifi-hotplug.sh index 84e5fa9..fb4fde0 100755 --- a/board/mister/de10nano/rootfs-overlay/etc/wifi-hotplug.sh +++ b/board/mister/de10nano/rootfs-overlay/etc/wifi-hotplug.sh @@ -27,7 +27,7 @@ # setsid + redirected std fds is what makes the detach actually stick: # - setsid (util-linux's, not BusyBox's -- board/mister/de10nano/ # busybox.fragment carries "# CONFIG_SETSID is not set" specifically -# because util-linux provides it; configs/mister_de10nano_defconfig's +# because util-linux provides it; configs/fragments/de10nano-image.fragment's # BR2_PACKAGE_UTIL_LINUX_BINARIES comment lists setsid among the basic # set it installs, and it is confirmed present at output/target/usr/bin/ # setsid, an ELF binary, not a BusyBox applet) starts ifup/ifdown in a diff --git a/board/mister/de25nano/genimage-sdcard.cfg b/board/mister/de25nano/genimage-sdcard.cfg new file mode 100644 index 0000000..986060f --- /dev/null +++ b/board/mister/de25nano/genimage-sdcard.cfg @@ -0,0 +1,189 @@ +# genimage config for the DE25-Nano SD card -- produces images/sdcard-de25.img +# (D2.4; docs/de25-nano-tasks.md, ADR 0029 D3/D4). +# +# The rationale, the layout table and the write-the-card procedure live in +# docs/de25-sdcard.md. This file states the layout and cites; it deliberately +# does NOT re-derive the boot chain the way the DE10's sibling file has to. +# +# Read board/mister/de10nano/genimage-sdcard.cfg only as a CONTRAST. That card +# is built around the Cyclone V BootROM's raw 0xA2 partition scan; this one is +# built around a completely different mechanism, and almost none of its lore +# transfers (docs/de25-readiness-ledger.md coupling (b)). +# +# --------------------------------------------------------------------------- +# The three facts this layout is made of +# --------------------------------------------------------------------------- +# +# 1. The factory U-Boot SPL -- which lives in QSPI and which NOTHING WE SHIP +# EVER WRITES (ADR 0029 D4) -- loads `u-boot.itb` BY NAME from a FAT +# filesystem on partition 1: CONFIG_SPL_FS_FAT=y plus +# SYS_MMCSD_FS_BOOT_PARTITION defaulting to 1, with the FIT landing at +# CONFIG_SPL_LOAD_FIT_ADDRESS=0x82000000 +# (docs/de25-boot-chain.md §2 step 4, §8.3). So: partition 1 is FAT, is +# partition-table ENTRY 1, and holds a file called exactly `u-boot.itb`. +# Those three are the entire interface we own to the board's boot firmware. +# +# 2. THERE IS NO 0xA2 PARTITION HERE, and adding one would be cargo cult. +# The 0xA2 type byte is the *Cyclone V BootROM's* raw-partition contract +# (de10nano/genimage-sdcard.cfg header, docs/boot-chain.md §2.1). On Agilex +# 5 the SDM boots the SPL out of QSPI and the SPL then reads a *filesystem* +# -- "No 0xA2 analogue [V]", docs/de25-boot-chain.md §2. There is no raw +# region on this card at all. scripts/check-sdcard-de25.sh fails the image +# if a 0xA2 partition, or a third partition, ever appears. +# +# 3. NOTHING ON THIS CARD MAY REFERENCE QSPI. Not the FAT payload, not +# extlinux.conf, not a U-Boot script. The QSPI holds the SDM firmware, the +# phase-1 bitstream and the FSBL; a write to it is brick-class with +# JTAG-and-a-PC recovery and no RSU safety net +# (docs/de25-boot-chain.md §6, §7 rows 1/5/10/11/12). The checker greps +# extlinux.conf for `sf probe`, `ubi` and `mtd` and fails on any hit. +# +# --------------------------------------------------------------------------- +# Inputs, all read from genimage's --inputpath (= Buildroot's BINARIES_DIR) +# --------------------------------------------------------------------------- +# u-boot.itb FIT: ATF BL31 + U-Boot proper + U-Boot DTB. +# Built by D2.2's BR2_TARGET_UBOOT; the ONLY +# file the factory SPL looks for. +# Image aarch64 kernel, uncompressed +# (BR2_LINUX_KERNEL_IMAGE=y). +# socfpga_agilex5_de25nano.dtb our board DTB +# (BR2_LINUX_KERNEL_CUSTOM_DTS_PATH). +# extlinux/ staged by board/mister/de25nano/post-image.sh, +# which generates extlinux.conf from a +# template so the kernel/DTB names and the +# bootargs have exactly one author. +# rootfs.ext4 BR2_TARGET_ROOTFS_EXT2 + _EXT2_4, written to +# p2 verbatim. +# +# post-image.sh is what actually runs genimage (via Buildroot's own +# support/scripts/genimage.sh); it fails loudly if any of the above is missing. + +# Partition 1's filesystem, built separately so its size is ours to choose and +# so `-F 32` can be forced -- see the size and extraargs notes below. +image boot-de25.vfat { + vfat { + # The FAT volume label. It is what the card identifies itself as to + # a human with a card reader, and scripts/check-sdcard-de25.sh pins + # it: an unlabelled or differently-labelled p1 is how you find out + # you wrote the DE10's card, or someone else's, into this slot. + label = "DE25BOOT" + + # `-F 32` is NOT optional and NOT cosmetic. mkfs.vfat picks the FAT + # width from the volume size, and at 256 MiB it picks FAT16 -- which + # would leave the partition-type byte below (0x0c = "FAT32 LBA") + # lying about the filesystem inside it. The DE10's mk-sdcard.sh + # forces the same flag for the same reason. + extraargs = "-F 32" + + # The last entry, `extlinux`, is a DIRECTORY, copied recursively + # (genimage hands each entry to mcopy; Buildroot's own + # board/uevm5432 and board/beagleboard/* genimage.cfgs list + # `extlinux` exactly this way). It contains one file, + # extlinux/extlinux.conf, generated by post-image.sh. + # + # Note for editors: genimage's parser does not accept comments + # INSIDE a `files = { ... }` list -- it reports them as an + # unexpected token. Annotate above the list, as here. + files = { + "u-boot.itb", + "Image", + "socfpga_agilex5_de25nano.dtb", + "extlinux" + } + } + + # 256 MiB. Sized for room, not for fit: the payload today is ~42 MiB of + # kernel plus a DTB, a FIT and a text file. The slack is there because + # this partition is where a `core.rbf`, a `uboot.env` and any future + # boot-time fabric bitstream have to go -- all of them FAT-resident by + # the same §2/§3 argument that puts u-boot.itb here -- and because + # growing p1 later means re-writing every card, while wasting 200 MiB of + # a microSD costs nothing. + # + # Also a FAT32 floor: below ~32 MiB (at 512 B/cluster) mkfs.vfat cannot + # make a conforming FAT32 at all, so "256 MiB" and "-F 32" back each + # other up. + size = 256M +} + +image sdcard-de25.img { + hdimage { + # 1 MiB partition alignment: the universal removable-media + # convention, what sfdisk lays down by default, and what keeps the + # FAT32 BPB's hidden-sectors field agreeing with the real partition + # start. Same reasoning as the DE10 file's `align = 1M`. + align = 1M + + # ------------------------------------------------------------------ + # MBR, NOT GPT -- and this is a fail-closed choice, not a preference. + # ------------------------------------------------------------------ + # The only reader that matters is Terasic's FACTORY U-Boot SPL, and + # the question is which partition drivers it was compiled with + # (SPL_DOS_PARTITION vs SPL_EFI_PARTITION). What the dossier records: + # + # * docs/de25-boot-chain.md §8.3 enumerates the SPL's compiled-in + # contract from the mainline agilex5 defconfig -- SPL_LOAD_FIT, + # SPL_LOAD_FIT_ADDRESS=0x82000000, SPL_FS_FAT, + # SYS_MMCSD_FS_BOOT_PARTITION=1, SPL_FIT_SIGNATURE, ENV_IS_IN_FAT + # -- and NO partition-table symbol appears anywhere in it, in + # either direction. GPT is therefore unproven, not disproven, and + # the factory SPL's own binary has never been read for it (the + # published SPL was only carved for its DTB, §8.2). + # + # * The one positive datum anyone has is MBR: a physical + # DE25-Nano has been observed booting from an MBR-partitioned card + # -- "Single active MBR partition, exFAT, spanning the card", + # docs/de25-reference-implementation.md:572 -- under an SPL built + # from the same U-Boot 2025.01 vendor tree the factory SPL comes + # from (:212, both identify as 2025.01, vendor=terasic). + # + # Two partitions need nothing GPT offers. So: take the option that + # has evidence behind it. If an SPL readback (boot-chain §5) ever + # proves SPL_EFI_PARTITION is compiled in, this becomes a one-line + # change -- and it is a change that must be re-tested on hardware, + # never waved through at a desk. + partition-table-type = "mbr" + + # `disk-signature` is deliberately NOT set. Left alone, genimage + # writes 0x00000000 at MBR offset 440 -- deterministic, which is what + # BR2_REPRODUCIBLE=y in configs/fragments/common.fragment wants. + # Setting it to `random` would make every build's image differ in + # four bytes for no functional gain (nothing on this board keys off + # the MBR disk signature). + } + + # --- p1 / /dev/mmcblk0p1 ------------------------------------------------- + # MUST be table entry 1: SYS_MMCSD_FS_BOOT_PARTITION=1 addresses the MBR + # ENTRY, not an on-disk order. genimage assigns entry numbers strictly in + # the order the `partition` sections are declared here, so `boot` first. + # + # 0x0c = FAT32 with LBA addressing. `bootable` is set because U-Boot's + # distro/bootstd scan prefers the active partition and it costs nothing; + # it is NOT load-bearing for the SPL, which keys on the partition number. + partition boot { + partition-type = 0xc + bootable = "true" + image = "boot-de25.vfat" + } + + # --- p2 / /dev/mmcblk0p2 ------------------------------------------------- + # The ext4 rootfs, written verbatim; the kernel mounts it directly via + # `root=/dev/mmcblk0p2` in extlinux.conf. No loop-mounted linux.img, no + # initramfs, no installer -- the DE25 developer OS has none of the DE10's + # machinery (docs/buildroot-config.md §6.5, "NO STAGE-1 INITRAMFS"). + # + # INTERIM DECISION, recorded as interim: ADR 0029 D3 fixes the partition + # COUNT and p1's FAT type only; p2's filesystem is explicitly still an + # owner decision (docs/de25-implementation-path.md §6.3, §8 Q7). For this + # developer-OS card p2 is the plain ext4 rootfs Buildroot already builds. + # Revisit when the card grows a user-visible data volume -- see + # docs/de25-sdcard.md "The interim p2 decision". + # + # 0x83 = Linux. No `size =`: genimage takes it from rootfs.ext4's own size + # (BR2_TARGET_ROOTFS_EXT2_SIZE), so the card grows when the rootfs does + # and there is no second number to keep in sync. + partition rootfs { + partition-type = 0x83 + image = "rootfs.ext4" + } +} diff --git a/board/mister/de25nano/linux-patches/README.md b/board/mister/de25nano/linux-patches/README.md index 28b00e5..3a8e165 100644 --- a/board/mister/de25nano/linux-patches/README.md +++ b/board/mister/de25nano/linux-patches/README.md @@ -1,6 +1,6 @@ # DE25-Nano kernel patch series — what is here and why -**Base:** mainline Linux **7.2.2**, aarch64 (Agilex 5). See +**Base:** mainline Linux **7.2.3**, aarch64 (Agilex 5). See [`docs/de25-implementation-path.md`](../../../../docs/de25-implementation-path.md) §5 for the version pin. @@ -19,9 +19,9 @@ without renumbering. `linux-patches/` (the shipped 6.18 series) and `linux-patches-beta/` (the 7.x series), so they link to the canonical file in `linux-patches/`. Three — `0015`, `0030`, `0037` — have a **7.x-re-anchored** copy in `linux-patches-beta/`, and those link to the beta copy, because this -board is on 7.2.2. (`0001` is the fourth divergent pair; it is DE10-only and excluded either way.) +board is on 7.2.x. (`0001` is the fourth divergent pair; it is DE10-only and excluded either way.) -That choice is not cosmetic — the shipped 6.18-anchored copies **hard-fail** on 7.2.2 at +That choice is not cosmetic — the shipped 6.18-anchored copies **hard-fail** on 7.2.x at Buildroot's `patch -F0`: `0015` 3/5 hunks FAILED, `0030` 1/1 FAILED, `0037` 4/6 FAILED. If you ever "simplify" these three to point at `linux-patches/`, the build breaks immediately. @@ -130,14 +130,22 @@ Both DE25-local patches were generated with `git format-patch` against a pristin Buildroot's own applier at its `patch -F0` (zero-fuzz) setting: ``` -work/buildroot/support/scripts/apply-patches.sh \ +work/buildroot/support/scripts/apply-patches.sh \ board/mister/de25nano/linux-patches ``` 34/34 applied, zero hunks taking fuzz, zero rejects, exit 0. `scripts/lint-kernel-patches.sh` accepts this directory as an argument and passes. -`dtbs_check` on `socfpga_agilex5_de25nano.dtb` at 7.2.2 + `0101` + `0102` leaves **5** warnings, +RE-VERIFIED AT 7.2.3 (2026-09-02), when Renovate's rt bump moved the shared `linux.hash` and the +DE25 pin followed (`docs/buildroot-config.md` §6.4). `make de25` after a `linux-dirclean` on a +freshly downloaded, hash-verified `linux-7.2.3.tar.xz`: **34/34 applied, 0 hunks with fuzz, 0 +rejects**, 79 hunks relocated by line OFFSET only — which `patch -F0` permits (`-F` caps *fuzz*, +i.e. context mismatch, not displacement). Note what this does and does not prove: the series still +applies and the kernel still builds; nothing here has been run on hardware at 7.2.3. + +`dtbs_check` on `socfpga_agilex5_de25nano.dtb` at 7.2.2 + `0101` + `0102` (the measurement was +taken at 7.2.2 and has not been re-run at 7.2.3) leaves **5** warnings, all of them the expected `fpga-mgr` two-string ones from `docs/de25-dts-rationale.md` §2.2 rows 1–5; both `mmc@10808000` warnings are gone. `make dt_binding_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml` is clean diff --git a/board/mister/de25nano/linux.config b/board/mister/de25nano/linux.config new file mode 100644 index 0000000..0edb417 --- /dev/null +++ b/board/mister/de25nano/linux.config @@ -0,0 +1,295 @@ +# +# board/mister/de25nano/linux.config — the DE25-Nano's kernel BASE config. +# +# THIS FILE IS A MINIMAL DEFCONFIG, NOT A FULL .config. Read that sentence +# twice: **an absent CONFIG_X is NOT an off CONFIG_X.** Everything not named +# here takes its Kconfig default, and plenty of Kconfig defaults are `y` +# (SUSPEND, EFI, USB_HID, NET_VENDOR_*, the SDIO Wi-Fi bus drivers...). The +# only way to know what this board actually builds is to read the RESOLVED +# config at output-de25*/build/linux-/.config. Same house rule as the +# DE10's linux.config; same trap that has bitten this repo before +# (memory: linux-config-is-minimal-defconfig). +# +# WHAT IS IN HERE, AND WHAT IS DELIBERATELY NOT +# --------------------------------------------- +# This file is ONLY the arm64 + Agilex 5 half of the kernel: the architecture, +# the SoC's on-chip IP blocks, the boot path, the FPGA-configuration stack, and +# the "not a distro kernel" exclusions. +# +# Everything a *MiSTer user* plugs in — USB gamepads and every hid-* driver, +# Bluetooth, Wi-Fi dongles, USB audio, USB storage, exFAT/NFS/CIFS, LEDs, the +# I2C RTCs, netfilter, the input core — lives in the ARCH-NEUTRAL shared +# fragment, board/mister/common/linux-mister.fragment, which Buildroot merges +# ON TOP of this file. That fragment is byte-for-byte a subset of the DE10's +# resolved config, which is what makes "the two boards have the same driver +# support" a checkable claim rather than an aspiration +# (scripts/check-kernel-fragment-noop.sh). +# +# **THIS FILE ALONE DOES NOT BOOT.** devtmpfs, ext4/vfat/exfat, MODULES, the +# input core and the console-adjacent userland plumbing all come from the +# fragment. The two files are a pair; configs/fragments/de25nano.fragment +# names both: +# +# BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y +# BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=".../board/mister/de25nano/linux.config" +# BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES=".../board/mister/common/linux-mister.fragment" +# +# HISTORY. Until 2026-09-02 this board built arm64's in-tree `defconfig` plus a +# 38-symbol delta (board/mister/de25nano/linux.fragment, now deleted). That +# produced a generic distro kernel: 1,481 modules, 90 MB of them, ~50 unrelated +# SoC families, PCI, ACPI, DRM, KVM. Everything that file established is +# preserved below — the load-bearing comments moved to +# docs/de25-kernel-config.md, which is where the per-subsystem rationale, the +# deliberate omissions and the per-kernel-bump re-check list now live. +# +# TWO TRAPS THAT APPLY TO EVERY LINE. +# 1. merge_config.sh only WARNS when a symbol is dropped, and `olddefconfig` +# silently discards any symbol whose dependencies are unmet or that this +# kernel version does not have. A typo here is a SILENT no-op, never an +# error. Every line below was verified to survive `olddefconfig` on a real +# 7.2.x tree -- 460 asked-for symbols, 0 dropped, at both 7.2.2 and 7.2.3 +# (docs/de25-kernel-config.md §8). +# 2. Some lines restate a value the base default already has. They are +# SENTINELS, not redundancy: they make an upstream change visible as a +# merge_config warning instead of as a board that stops booting. +# +# Source of truth for the file:line cites below: linux-7.2.2 as patched by +# board/mister/de25nano/linux-patches/, read 2026-09-02. +# + +# ============================================================================= +# 1. Kernel identity +# ============================================================================= +# LOCALVERSION_AUTO off: the version string must not depend on whether a git +# tree happened to be present at build time (docs/reproducibility.md). +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_DEFAULT_HOSTNAME="de25" + +# ============================================================================= +# 2. Platform and CPU topology +# ============================================================================= +# The Agilex 5 HPS. Every group below hangs off this symbol's `depends on`. +CONFIG_ARCH_INTEL_SOCFPGA=y +# Agilex 5 A5EB013 = 2x Cortex-A76 + 2x Cortex-A55. arm64 defaults NR_CPUS to +# 512; sizing it to the real core count shrinks every per-CPU array. +CONFIG_NR_CPUS=4 +# Not required to boot, but it is how a core can be taken offline for latency +# work, and it is what the DE10 has. +CONFIG_HOTPLUG_CPU=y + +# ============================================================================= +# 3. What this board is NOT — the "not a distro kernel" exclusions +# ============================================================================= +# These are the lines that turn 1,481 modules into ~90. Note that the *other* +# ~50 arm64 SoC families (ARCH_ROCKCHIP, ARCH_QCOM, ARCH_MEDIATEK, ...) need no +# lines at all: they are `default n`, and it was arm64's in-tree `defconfig`, +# not Kconfig, that switched them on. Writing a minimal defconfig removes them +# by construction. The five below are different — each is `default y` or is +# reachable by default, so each needs saying. +# +# EFI: the DE25 boots via the factory SPL -> u-boot.itb FIT contract +# (docs/de25-boot-chain.md); there is no UEFI anywhere in that chain. +# Turning it off ALSO forecloses ACPI: on arm64 `ARCH_SUPPORTS_ACPI` is +# `select`ed only by EFI (arch/arm64/Kconfig:2473), so with EFI=n the whole +# ACPI menu is structurally unreachable and needs no line of its own. +# CONFIG_EFI is not set +# No PCI host bridge is wired on this board and none is described in the DTS. +# This is load-bearing for the Wi-Fi set in the shared fragment: it is what +# makes every RTW88_*E / BRCMFMAC_PCIE / ath10k-PCIe sibling unreachable. +# CONFIG_PCI is not set +# Nothing on this board runs guests, and VIRTUALIZATION is `default y` on arm64. +# CONFIG_VIRTUALIZATION is not set +# 32-bit EL0 userspace. Our userland is aarch64-only (Buildroot builds one ABI). +# CONFIG_COMPAT is not set +# No display path exists on the DE25 in wave 1. The DE10's framebuffer is +# CONFIG_FB_MISTER, which comes from linux-patches/0001 — a patch this board +# deliberately does NOT carry (board/mister/de25nano/linux-patches/README.md), +# because it targets the Cyclone V fabric-memory aperture. Until an Agilex 5 +# framebuffer path exists there is nothing for fbdev or DRM to drive, and DRM +# alone is ~40 MB of modules. +# CONFIG_DRM is not set +# CONFIG_FB is not set +# V4L/DVB: nothing on a MiSTer is a capture device. +# CONFIG_MEDIA_SUPPORT is not set + +# ============================================================================= +# 4. SoC clocks — the reason this board is pinned to 7.2 and not 6.18 +# ============================================================================= +# docs/de25-implementation-path.md §0 Finding 1 / §5.1: clk-agilex5.c does not +# exist before v6.19, so on a 6.18 base every `clocks = <&clkmgr ...>` consumer +# (mmc0 and all three gmacs included) defers forever and the board cannot boot +# from SD at all. +# +# THERE IS NO `CONFIG_CLK_AGILEX5`. The real symbols are these two +# (drivers/clk/socfpga/Kconfig:2,16); the Agilex 5 driver is built by the +# SOCFPGA64 one — `obj-$(CONFIG_CLK_INTEL_SOCFPGA64) += ... clk-agilex5.o` +# (drivers/clk/socfpga/Makefile:5-7). Both are `default ARCH_INTEL_SOCFPGA` and +# invisible under exactly the condition we build in, so kconfig would set them +# anyway: sentinels on the single most load-bearing driver on the board. +CONFIG_CLK_INTEL_SOCFPGA=y +CONFIG_CLK_INTEL_SOCFPGA64=y + +# ============================================================================= +# 5. Reset controller — a silent SD-boot dependency +# ============================================================================= +# The board DTS gives mmc0 `resets = <&rst SDMMC_RESET>`, and the rstmgr node is +# `altr,stratix10-rst-mgr` — matched by drivers/reset/reset-simple.c:137, NOT by +# reset-socfpga.c (that one is `default ARM && ARCH_INTEL_SOCFPGA`, i.e. the +# DE10's 32-bit path). RESET_SIMPLE is `default (ARCH_INTEL_SOCFPGA && ARM64)` +# so it resolves on its own — but with no reset provider mmc0 probe-defers +# forever with nothing in dmesg naming the reason, so it is stated. +CONFIG_RESET_SIMPLE=y + +# ============================================================================= +# 6. System manager and on-chip SRAM +# ============================================================================= +# sysmgr (`altr,sys-mgr-s10`, socfpga_agilex5.dtsi:425) is a syscon that +# dwmac-socfpga reads through `altr,sysmgr-syscon` to set the PHY interface mode +# — without it gmac0 does not come up. +CONFIG_MFD_ALTERA_SYSMGR=y +# ocram@0 (`mmio-sram`, socfpga_agilex5.dtsi:319). +CONFIG_SRAM=y + +# ============================================================================= +# 7. Serial console — HPS UART1 +# ============================================================================= +# The DE25-Nano's header UART is uart1 (uart0 is the SoCDK's); both are +# `snps,dw-apb-uart`, driven by 8250_DW, and the board DTS selects uart1 via +# serial0/stdout-path. A serial login IS this board's acceptance criterion, and +# a console that is a module is a console that does not exist at panic time. +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=2 +CONFIG_SERIAL_8250_RUNTIME_UARTS=2 +CONFIG_SERIAL_8250_DW=y +CONFIG_SERIAL_OF_PLATFORM=y + +# ============================================================================= +# 8. SD card — the boot path +# ============================================================================= +# mmc0 on Agilex 5 is a Cadence SD4HC. The board DTS declares +# `"intel,agilex5-sd4hc","cdns,sd4hc"`; with linux-patches/0101 the first entry +# wins and installs the 40-bit DMA mask, and without it the bare `cdns,sd4hc` +# entry still binds (docs/de25-implementation-path.md §2.1, §8 Q2). +# MMC_SDHCI_CADENCE `depends on MMC_SDHCI_PLTFM` and `depends on OF`. +# All four =y: there is no initramfs on this board, so a driver that is a module +# cannot be loaded before the root filesystem it is needed to reach exists. +CONFIG_MMC=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_CADENCE=y + +# ============================================================================= +# 9. IOMMU +# ============================================================================= +# socfpga_agilex5.dtsi ships an `arm,smmu-v3` node and the board DTS leaves it +# DISABLED for wave 1: with the SMMU on, mainline's stratix10-svc cannot program +# the fabric, because it hands the SDM raw physical addresses over a translated +# domain (docs/de25-dts-rationale.md §4). Every `iommus` in the tree is +# therefore inert at runtime. +# The DRIVER stays =y anyway, so that the SMMU-on leg of the §2.6 programming +# test is a one-line DTS change with no kernel rebuild. +CONFIG_ARM_SMMU_V3=y + +# ============================================================================= +# 10. Ethernet MAC — SoC glue only +# ============================================================================= +# Agilex 5's three gmacs are stmmac + the socfpga glue layer +# (`altr,socfpga-stmmac-agilex5`, socfpga_agilex5.dtsi:557). DWMAC_SOCFPGA is +# `default ARCH_INTEL_SOCFPGA` but tristate, so it would follow STMMAC_ETH to =m +# without these lines. =y, not =m: "eth0 up at a serial login" is this board's +# acceptance criterion and there is no initramfs to load a module from. +# The PHY DRIVERS are NOT here — they are user-visible hardware and live in the +# shared fragment (MARVELL_PHY + MICREL_PHY, exactly the DE10's pair). +CONFIG_STMMAC_ETH=y +CONFIG_STMMAC_PLATFORM=y +CONFIG_DWMAC_SOCFPGA=y + +# ============================================================================= +# 11. FPGA manager, bridge and region — the core-loading plumbing (DP-9) +# ============================================================================= +# docs/de25-fpga-reconfig.md §4.1 is the authority for this whole group; every +# row there is [V]. Dependency chain, from this tree: +# FPGA_MGR_STRATIX10_SOC depends on ARCH_INTEL_SOCFPGA && +# INTEL_STRATIX10_SERVICE drivers/fpga/Kconfig:61 +# FPGA_REGION depends on FPGA_BRIDGE :145 +# OF_FPGA_REGION depends on OF && FPGA_REGION :153 +# INTEL_STRATIX10_SERVICE depends on ARCH_INTEL_SOCFPGA && ARM64 && +# HAVE_ARM_SMCCC drivers/firmware/Kconfig:145 +# All =y: this is the board's whole reason for existing, there is no initramfs, +# and a half-modular fpga stack is exactly the sort of thing that probes at the +# wrong time and reports nothing. FPGA_BRIDGE is needed even though no bridge +# driver is used — the Cyclone V bridge shape has no Agilex analogue and must +# not be transliterated (docs/de25-fpga-reconfig.md §4.2) — purely because +# FPGA_REGION depends on it. +CONFIG_FPGA=y +CONFIG_FPGA_BRIDGE=y +CONFIG_FPGA_REGION=y +CONFIG_OF_FPGA_REGION=y +CONFIG_FPGA_MGR_STRATIX10_SOC=y +CONFIG_INTEL_STRATIX10_SERVICE=y +CONFIG_FW_LOADER=y + +# THE TRAP (docs/de25-fpga-reconfig.md §4.1, tagged [V] there). +# OF_FPGA_REGION is `depends on OF && FPGA_REGION` with NO `select OF_OVERLAY`. +# With OF_OVERLAY=n, of_overlay_notifier_register() is a static-inline stub that +# returns 0 — so the region driver registers successfully at boot, prints +# nothing wrong, and its notifier can NEVER fire. A kernel missing this line is +# silently non-functional for core loading: no error, no warning, no +# reconfiguration. This is the single most important line in the file. +CONFIG_OF_OVERLAY=y + +# Deliberately OFF, and this is a posture choice, not an oversight. +# INTEL_STRATIX10_RSU (drivers/firmware/Kconfig:157) is the Remote System Update +# driver: it exists to drive SDM commands that rewrite the QSPI boot firmware. +# docs/de25-boot-chain.md's posture-1 contract is that the factory QSPI image is +# never written by anything we ship — the QSPI seam is permanent on this board +# and an interrupted write is a brick with no recovery path. Not shipping the +# driver at all is strictly stronger than relying on there being no +# `intel,stratix10-rsu` DT node. +# CONFIG_INTEL_STRATIX10_RSU is not set + +# ============================================================================= +# 12. Low-speed on-chip IP +# ============================================================================= +# All three are Synopsys DesignWare blocks named in socfpga_agilex5.dtsi and +# enabled by the board DTS. Only the CONTROLLER drivers are here; the I2C core, +# chardev and userland ABI are arch-neutral and live in the shared fragment. +# +# GPIO: `snps,dw-apb-gpio` — gpio1/portb drives the board's hps_led0. +CONFIG_GPIO_DWAPB=y +# I2C: `snps,designware-i2c`. I2C_DESIGNWARE_PLATFORM is the bus glue that +# actually probes a DT node and is tristate with `default I2C_DESIGNWARE_CORE`, +# so it needs saying. linux-patches/0030 quiets its timeout message. +CONFIG_I2C_DESIGNWARE_CORE=y +CONFIG_I2C_DESIGNWARE_PLATFORM=y +# Watchdog: `snps,dw-wdt`. The board DTS enables all five, deliberately — a +# watchdog started by the SPL whose node is DISABLED in Linux is never petted +# and resets the board (docs/de25-dts-rationale.md). The WATCHDOG core itself is +# in the shared fragment. +CONFIG_DW_WATCHDOG=y +# SPI: `snps,dw-apb-ssi` (spi0/spi1, socfpga_agilex5.dtsi:392,409). Both nodes +# are `status = "disabled"` in the base DTS today, so nothing probes; the driver +# is built anyway because the controllers physically exist and enabling one is +# then a DTS-only change. SPI_MEM off — there is no SPI-NOR user in Linux here +# (the QSPI flash is the factory boot device and we never touch it). +CONFIG_SPI=y +# CONFIG_SPI_MEM is not set +CONFIG_SPI_DESIGNWARE=y +CONFIG_SPI_DW_MMIO=y +CONFIG_SPI_SPIDEV=y + +# ============================================================================= +# 13. USB host controller +# ============================================================================= +# Verified against the DTSI, not assumed: Agilex 5's usb0@10b00000 is +# `compatible = "snps,dwc2"` with a `usb-nop-xceiv` phy +# (socfpga_agilex5.dtsi:161-163,483-492) — so DWC2, NOT dwc3, and NOT xhci. +# The mode is a kconfig CHOICE whose default follows USB_GADGET; pinned to host +# explicitly because this board's USB port exists to carry a hub and +# controllers. linux-patches/0028 fixes an unaligned-split bug in this driver. +# Everything above the controller — the USB core itself, the class drivers, HID, +# storage, audio, serial, the PHY shim NOP_USB_XCEIV — is in the shared +# fragment. +CONFIG_USB_DWC2=y +CONFIG_USB_DWC2_HOST=y diff --git a/board/mister/de25nano/linux.fragment b/board/mister/de25nano/linux.fragment deleted file mode 100644 index 1c0d235..0000000 --- a/board/mister/de25nano/linux.fragment +++ /dev/null @@ -1,240 +0,0 @@ -# -# board/mister/de25nano/linux.fragment — the DE25-Nano's KERNEL-config delta, -# layered on arm64's in-tree `defconfig` (D2.1). -# -# READ THIS FIRST — WHAT LAYER THIS IS. -# There are two "fragment" layers in this repo and they are easy to confuse -# (a reviewer already has, see the `rt` recipe's error text in the Makefile): -# -# * configs/mister_de25nano_defconfig — BUILDROOT config (BR2_*). -# * THIS FILE — KERNEL config (CONFIG_*), named by -# that defconfig's BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES and merged onto -# the base by Buildroot's own support/kconfig/merge_config.sh, followed by -# `olddefconfig`. -# -# BASE. Unlike the DE10 (which ships a whole minimal defconfig at -# board/mister/de10nano/linux.config), the DE25 starts from the kernel's OWN -# arm64 `defconfig` and only states the delta. That is deliberate for a -# bring-up board: arm64 defconfig is the configuration mainline actually CI- -# tests, so every symbol we do not name tracks upstream for free, and the file -# below stays short enough to review line by line. Once the board boots and the -# shape settles, this may be converted to a full pinned linux.config the way -# the DE10 has one — that is a later decision, not this task's. -# -# TWO TRAPS THAT APPLY TO EVERY LINE BELOW. -# -# 1. merge_config.sh only WARNS when a symbol it was asked for is dropped, and -# `olddefconfig` silently discards any symbol whose dependencies are unmet -# or that this kernel version does not have (memory: the initramfs -# CONFIG_ASH_BUILTIN_TEST incident, and linux-config-is-minimal-defconfig). -# So a typo here is a SILENT no-op, never an error. Every symbol below was -# read out of a real 7.2 tree before being written — see the per-group -# source cites — and any future addition must be too. -# -# 2. An ABSENT symbol is NOT an off symbol. Several lines below restate a -# value arm64 defconfig already has; they are sentinels, not redundancy. -# They exist so that an upstream defconfig change that drops or demotes one -# of them shows up as a merge_config warning instead of as a board that -# stops booting. Each group's comment says which of its lines are -# sentinels ("already y in arm64 defconfig") and which are real changes. -# -# SOURCE OF TRUTH for the Kconfig/dtsi line numbers cited below: the Linux -# 7.2.1 tree unpacked at output-rt/build/linux-7.2.1/ (read-only), 2026-09-02. -# Cited as `:`. 7.2.1 rather than our pinned 7.2.2 only because -# that is the 7.2.y tree this machine already had extracted; same line, and the -# check below was run against 7.2.2 itself. -# -# MEASURED, not merely written — 2026-09-02, against a pristine linux-7.2.2 -# extracted from dl/. `make ARCH=arm64 defconfig`, then this file through -# scripts/kconfig/merge_config.sh -m, then `make ARCH=arm64 olddefconfig`: -# ALL 38 symbols below survive with EXACTLY the value asked for, zero silently -# dropped. merge_config reported 11 "redefined" notices, each one an intended -# promotion (=m -> =y, or not-set -> =y); the other 27 lines are the sentinels -# and matched the base already. Re-run that three-command check on any kernel -# bump — it is cheap and it is the only thing that catches trap 1. -# -# Everything on the boot path is =y, not =m, on purpose: the DE25 boots a plain -# ext4 root off the SD card with NO initramfs (see the defconfig header), so a -# driver that is a module cannot be loaded before the root filesystem it is -# needed to reach exists. -# - -# --- Platform ---------------------------------------------------------------- -# The Agilex 5 HPS. Already y in arm64 defconfig (:74) — sentinel: every other -# group below hangs off this symbol's `depends on`. -CONFIG_ARCH_INTEL_SOCFPGA=y - -# --- SoC clocks — the reason this board is pinned to 7.2 and not 6.18 -------- -# docs/de25-implementation-path.md §0 Finding 1 / §5.1: clk-agilex5.c does not -# exist before v6.19, so on a 6.18 base every `clocks = <&clkmgr ...>` consumer -# (mmc0 and all three gmacs included) defers forever and the board cannot boot -# from SD at all. -# -# THERE IS NO `CONFIG_CLK_AGILEX5`. The task brief guessed that name; the real -# symbols are the two below (drivers/clk/socfpga/Kconfig:2,16) and the Agilex 5 -# driver is built by the SOCFPGA64 one — `obj-$(CONFIG_CLK_INTEL_SOCFPGA64) += -# ... clk-agilex.o clk-agilex5.o` (drivers/clk/socfpga/Makefile:5-7). -# -# Both are `default ARCH_INTEL_SOCFPGA` / `default ARM64 && ARCH_INTEL_SOCFPGA` -# and are made invisible by exactly the condition we build under -# (`bool "..." if COMPILE_TEST && !ARCH_INTEL_SOCFPGA`), so kconfig sets them -# for us and these two lines cannot change the outcome. They are here purely as -# named sentinels for the single most load-bearing driver on the board. -CONFIG_CLK_INTEL_SOCFPGA=y -CONFIG_CLK_INTEL_SOCFPGA64=y - -# --- Serial console — HPS UART1 (the DE25-Nano's header UART; uart0 is the -# SoCDK's) — both are `snps,dw-apb-uart` in socfpga_agilex5.dtsi, driven by -# 8250_DW. The DTS selects uart1 via serial0/stdout-path. -# All four already y in arm64 defconfig (:557,:558,:563,:568); sentinels, -# because a serial login IS the acceptance criterion for this board and a -# console that is a module is a console that does not exist at panic time. -CONFIG_SERIAL_8250=y -CONFIG_SERIAL_8250_CONSOLE=y -CONFIG_SERIAL_8250_DW=y -CONFIG_SERIAL_OF_PLATFORM=y - -# --- SD card — the boot path ------------------------------------------------- -# mmc0 on Agilex 5 is a Cadence SD4HC; mainline binds it through the bare -# `cdns,sd4hc` entry in sdhci-cadence's match table -# (docs/de25-implementation-path.md §2.1). MMC_SDHCI_CADENCE `depends on -# MMC_SDHCI_PLTFM` and `depends on OF` (drivers/mmc/host/Kconfig:291-295). -# All four already y in arm64 defconfig (:1268,:1271,:1273,:1278) — sentinels -# on the one path whose failure mode is "no root filesystem". -# -# NOTE the still-open hardware question this config cannot settle: mainline -# takes sdhci's 64-bit DMA-mask branch on a controller Terasic's vendor tree -# caps at 40 bits (§0 Finding 1's sibling, §1.1). The fix is decision 8's -# carried sdhci-cadence patch plus a matching DTS compatible — neither is a -# kernel-config knob, and neither is in scope for D2.1. -CONFIG_MMC=y -CONFIG_MMC_SDHCI=y -CONFIG_MMC_SDHCI_PLTFM=y -CONFIG_MMC_SDHCI_CADENCE=y - -# --- IOMMU ------------------------------------------------------------------- -# socfpga_agilex5.dtsi ships an `arm,smmu-v3` node, status = "disabled" at -# v7.2, and the board DTS LEAVES IT DISABLED for wave 1 (mainline's svc hands -# the SDM physical addresses, so a translated SMMU stream cannot program the -# fabric — docs/de25-dts-rationale.md §4). Every `iommus` in the tree is -# therefore inert at runtime. The driver stays =y (already y in arm64 -# defconfig :1608) so the SMMU-on leg of the §2.6 programming test is a -# one-line DTS change with no kernel rebuild; it is not load-bearing for the -# shipped configuration. -CONFIG_ARM_SMMU_V3=y - -# --- Ethernet ---------------------------------------------------------------- -# Agilex 5's three gmacs are stmmac + the socfpga glue layer. arm64 defconfig -# has STMMAC_ETH=m (:446) and NO DWMAC_SOCFPGA at all, so both lines below are -# real changes, not sentinels. DWMAC_SOCFPGA is `default ARCH_INTEL_SOCFPGA` -# but tristate, so it would follow STMMAC_ETH to =m without this. -# drivers/net/ethernet/stmicro/stmmac/Kconfig:2 (STMMAC_ETH), -# :29-32 (STMMAC_PLATFORM, `default y`), :203-209 (DWMAC_SOCFPGA). -# =y rather than =m: "eth0 up at a serial login" is this board's acceptance -# criterion and there is no initramfs and no module-loading userland worth -# depending on yet. -CONFIG_STMMAC_ETH=y -CONFIG_STMMAC_PLATFORM=y -CONFIG_DWMAC_SOCFPGA=y -# The PHY. Which one the DE25-Nano actually wires is NOT settled here — it is a -# DTS question and the board DTS is being authored in parallel. Micrel/Microchip -# is the DE10-Nano's part and is already y in arm64 defconfig (:466); Realtek -# (:470) and Marvell 10G (:464) are too, and phylib's Generic PHY covers a -# basic link either way. Left as a sentinel on the most likely part rather than -# guessed wider; revisit when the DTS lands. [ASSUMPTION — see the report] -CONFIG_MICREL_PHY=y - -# --- FPGA manager + region — the core-loading plumbing (DP-9) ---------------- -# docs/de25-fpga-reconfig.md §4.1 is the authority for this whole group; every -# row there is [V]. Dependency chain, from drivers/fpga/Kconfig in THIS tree: -# FPGA_MGR_STRATIX10_SOC depends on (ARCH_INTEL_SOCFPGA && -# INTEL_STRATIX10_SERVICE) :61-63 -# FPGA_REGION depends on FPGA_BRIDGE :145-147 -# OF_FPGA_REGION depends on OF && FPGA_REGION :153-155 -# INTEL_STRATIX10_SERVICE depends on ARCH_INTEL_SOCFPGA && ARM64 -# && HAVE_ARM_SMCCC drivers/firmware/Kconfig:145-147 -# -# arm64 defconfig has FPGA=y (:1837) but FPGA_MGR_STRATIX10_SOC, FPGA_BRIDGE, -# FPGA_REGION and OF_FPGA_REGION all =m (:1839,:1840,:1843,:1844). Promoted to -# =y here: this is the board's whole reason for existing, the DE25 ships no -# initramfs, and a half-modular fpga stack is exactly the sort of thing that -# probes at the wrong time and reports nothing. -CONFIG_FPGA=y -CONFIG_FPGA_BRIDGE=y -CONFIG_FPGA_REGION=y -CONFIG_OF_FPGA_REGION=y -CONFIG_FPGA_MGR_STRATIX10_SOC=y -CONFIG_INTEL_STRATIX10_SERVICE=y -CONFIG_FW_LOADER=y - -# THE TRAP (docs/de25-fpga-reconfig.md §4.1, tagged [V] there). -# OF_FPGA_REGION is `depends on OF && FPGA_REGION` with NO `select OF_OVERLAY`. -# With OF_OVERLAY=n, of_overlay_notifier_register() is a static-inline stub -# that returns 0 — so the region driver registers successfully at boot, prints -# nothing wrong, and its notifier can NEVER fire. A kernel missing this line is -# silently non-functional for core loading: no error, no warning, no -# reconfiguration. Already y in arm64 defconfig (:305); this is the single most -# important sentinel in the file. -CONFIG_OF_OVERLAY=y - -# Deliberately OFF, and this is a posture choice, not an oversight. -# INTEL_STRATIX10_RSU (drivers/firmware/Kconfig:157-159, =m in arm64 defconfig -# at :268) is the Remote System Update driver: it exists to drive SDM commands -# that rewrite the QSPI boot firmware. docs/de25-boot-chain.md's whole posture-1 -# contract is that the factory QSPI image is never written by anything we ship — -# the QSPI seam is permanent on this board (Q1 [V]) and an interrupted write is -# a brick with no recovery path. Not shipping the driver at all is strictly -# stronger than relying on there being no `intel,stratix10-rsu` DT node. -# [ASSUMPTION — the brief said "only if harmless"; this file judges it not -# harmless and fails closed. Owner to confirm.] -# CONFIG_INTEL_STRATIX10_RSU is not set - -# --- Low-speed peripherals on the HPS ---------------------------------------- -# GPIO: socfpga_agilex5.dtsi's gpio controllers are `snps,dw-apb-gpio`. -# Already y in arm64 defconfig (:712) — sentinel. -# I2C: `snps,designware-i2c`. arm64 defconfig has I2C=y (:610) and -# I2C_DESIGNWARE_CORE=y (:617) but NOT the PLATFORM bus glue, which is -# what actually probes a DT node (drivers/i2c/busses/Kconfig:580-583, -# `default I2C_DESIGNWARE_CORE` — tristate, so it needs saying). -CONFIG_GPIO_DWAPB=y -CONFIG_I2C_DESIGNWARE_CORE=y -CONFIG_I2C_DESIGNWARE_PLATFORM=y - -# --- USB --------------------------------------------------------------------- -# Verified against the DTSI rather than assumed, per the brief: Agilex 5's -# usb0@10b00000 is `compatible = "snps,dwc2"` with a `usb-nop-xceiv` phy -# (arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi:161-163,483-492) — so DWC2, -# NOT dwc3. USB=y and USB_DWC2=y are already in arm64 defconfig (:1189,:1215). -# The mode is a kconfig CHOICE whose default follows USB_GADGET -# (drivers/usb/dwc2/Kconfig:25-27); pinned to host explicitly because this -# board's USB port exists to carry a hub and controllers, and because -# "whatever the choice defaults to today" is not something a peripheral bus -# should depend on (same reasoning as external.mk's INITRAMFS_COMPRESSION note). -CONFIG_USB=y -CONFIG_USB_DWC2=y -CONFIG_USB_DWC2_HOST=y - -# --- Filesystems ------------------------------------------------------------- -# ext4 is the rootfs (BR2_TARGET_ROOTFS_EXT2_4) — already y (:1899). -# vfat is the FAT boot partition p1 (decision 3, two partitions) — already y -# (:1911). -# exfat is the MiSTer data-partition convention this project standardises on -# (ADR 0010 dropped the out-of-tree driver in favour of the in-kernel one); it -# is absent from arm64 defconfig entirely, so this is a real addition. -# fs/exfat/Kconfig:3-7 — `select BUFFER_HEAD`, `select NLS`, `select FS_IOMAP`. -# NLS_UTF8 is NOT optional with it: exfat's default iocharset is utf8, and -# without the codepage the mount fails at runtime rather than at build time. -# 437 and ISO8859-1 are already y (:1925,:1926); UTF8 is not. -CONFIG_EXT4_FS=y -CONFIG_VFAT_FS=y -CONFIG_EXFAT_FS=y -CONFIG_NLS_CODEPAGE_437=y -CONFIG_NLS_UTF8=y - -# --- Userland plumbing ------------------------------------------------------- -# devtmpfs + its automount: Buildroot's BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_* -# choices and BusyBox's own /dev handling both assume it. Already y in arm64 -# defconfig (:257,:258) — sentinels, because "no /dev/console" is another -# failure that presents as a dead serial line rather than as an error. -CONFIG_DEVTMPFS=y -CONFIG_DEVTMPFS_MOUNT=y diff --git a/board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash b/board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash new file mode 100644 index 0000000..708f8d4 --- /dev/null +++ b/board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash @@ -0,0 +1,60 @@ +# ARM Trusted Firmware tarball hash for the pinned +# BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_VERSION_VALUE. +# +# WHY THIS FILE EXISTS +# Buildroot ships NO hash file for arm-trusted-firmware at all (there is no +# boot/arm-trusted-firmware/arm-trusted-firmware.hash in 2026.05.2), because +# the package downloads by git and Buildroot excuses git-generated tarballs +# from hashing: +# +# ifeq ($(BR2_TARGET_ARM_TRUSTED_FIRMWARE):$(..._CUSTOM_VERSION)...,y:y) +# BR_NO_CHECK_HASH_FOR += $(ARM_TRUSTED_FIRMWARE_SOURCE) +# +# That excuse does NOT apply here. BR2_DOWNLOAD_FORCE_CHECK_HASHES=y (set in +# the defconfig) empties BR_NO_CHECK_HASH_FOR entirely +# (package/pkg-download.mk:119), so with no hash file the download fails closed +# with "No hash found". Buildroot finds this file via pkg-patch-hash-dirs +# (package/pkg-utils.mk:163), which searches $(BR2_GLOBAL_PATCH_DIR)//. +# +# NOTE THE FILENAME, it is not the obvious one. Buildroot's git backend names +# its generated tarball --git.tar.gz, where N is +# BR_FMT_VERSION_git in package/pkg-download.mk -- the ARCHIVE FORMAT version, +# bumped whenever upstream changes what goes into the tarball or how it is +# packed. If a Buildroot bump changes that number, this filename AND this hash +# both change, and the build will fail closed until they are re-derived. That +# is the intended behaviour; do not "fix" it by loosening the check. +# +# WHERE THIS HASH COMES FROM -- read before changing it, and note honestly +# what it is and is not. +# +# It is a TRUST-ON-FIRST-USE value, unlike the U-Boot hash next door. +# trustedfirmware.org publishes no release tarballs and no signed checksum +# manifest, so there is no signed artifact to transcribe from. What exists is +# an annotated, PGP-SIGNED git tag, and the chain that value rests on is: +# +# 1. Buildroot cloned https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git +# over TLS and checked out tag v2.15.0 +# (dl/arm-trusted-firmware/git, verified 2026-09-02). +# 2. That tag is annotated object 9ad327a8d124ce82002614c23e33992d4de6f7cf, +# tagged "Trusted Firmware-A release v2.15.0" by Olivier Deprez +# , and it points at commit +# da738d5eae93af342fdc4995dd3c05acb4c9d757. +# 3. The SAME commit id was obtained from an INDEPENDENT clone made in a +# separate working directory on the same day, so the value below is not +# anchored on a single fetch. +# 4. The tag carries a PGP signature by RSA key +# 5D6F896043ADEFDF7B76BFAA89C08CFDB8673E0C. That signature could NOT be +# verified: the key is published on neither keys.openpgp.org nor +# keyserver.ubuntu.com (both returned 404 on 2026-09-02). Say so plainly +# rather than implying a verification that did not happen. +# 5. sha256 of the tarball Buildroot then generated from that checkout. +# +# TO UPGRADE THIS TO A VERIFIED CHAIN: obtain the TF-A release key through a +# channel you trust (the trustedfirmware.org project pages, or an Arm-published +# keyring), import it, run `git tag -v v2.15.0` in dl/arm-trusted-firmware/git, +# and record the result here in place of item 4. +# +# ON A VERSION BUMP: re-derive both lines. The cheap, honest way is to set this +# hash to all zeros, run `make de25`, and transcribe the "got" value the +# check-hash error prints -- after re-doing steps 1-4 for the new tag. +sha256 b807d8bf190b7c46b4260729b4bbe2f7df83cd1086edcb181d66595ae6598d23 arm-trusted-firmware-v2.15.0-git4.tar.gz diff --git a/board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch b/board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch new file mode 100644 index 0000000..a726d16 --- /dev/null +++ b/board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch @@ -0,0 +1,90 @@ +From: MiSTer Buildroot (DE25-Nano board support) +Subject: [PATCH] configs: socfpga_soc64: only define mtdids/mtdparts env when MTD exists + +CFG_EXTRA_ENV_SETTINGS in socfpga_soc64_common.h references +CONFIG_MTDIDS_DEFAULT and CONFIG_MTDPARTS_DEFAULT unconditionally, in all +three of its variants. Those two symbols are + + config MTDIDS_DEFAULT + string "Default MTD IDs" + depends on MTD || SPI_FLASH + +(cmd/Kconfig), so they simply do not exist once a board turns the MTD and +SPI-NOR stack off, and the build fails with + + include/configs/socfpga_soc64_common.h:133:19: error: expected '}' + before 'CONFIG_MTDIDS_DEFAULT' + +That is not a hypothetical configuration. The Terasic DE25-Nano (Agilex 5) +boots from SD under a factory SPL resident in QSPI, and its QSPI additionally +holds the SDM firmware and the phase-1 HPS bitstream. There is no +power-loss-safe update path for that flash on this board, so a U-Boot that +can write it is a brick risk with a JTAG-only recovery. The board's config +therefore compiles out CADENCE_QSPI, the SPI-NOR stack, MTD, UBI and every +command that could reach them -- and then cannot build. + +Wrap the two lines in a macro that expands to nothing when neither symbol is +defined. No functional change for any board that has MTD or SPI_FLASH. + +Upstream status: not submitted yet. Local carry for +board/mister/de25nano; see docs/de25-uboot.md. + +Signed-off-by: MiSTer Buildroot +--- +--- a/include/configs/socfpga_soc64_common.h ++++ b/include/configs/socfpga_soc64_common.h +@@ -59,6 +59,24 @@ + /* + * Environment variable + */ ++/* ++ * MTD-dependent environment variables. ++ * ++ * CONFIG_MTDIDS_DEFAULT and CONFIG_MTDPARTS_DEFAULT only exist when ++ * MTD or SPI_FLASH is enabled (they are "depends on MTD || SPI_FLASH" ++ * in cmd/Kconfig). Referencing them unconditionally breaks the build of ++ * any SoC64 board that has no flash of its own to manage -- for example a ++ * board that boots from SD only and deliberately compiles out the SPI-NOR ++ * stack so that nothing in U-Boot can write its boot flash. ++ */ ++#if defined(CONFIG_MTDIDS_DEFAULT) && defined(CONFIG_MTDPARTS_DEFAULT) ++#define SOCFPGA_MTDPARTS_ENV_SETTINGS \ ++ "mtdids=" CONFIG_MTDIDS_DEFAULT "\0" \ ++ "mtdparts=" CONFIG_MTDPARTS_DEFAULT "\0" ++#else ++#define SOCFPGA_MTDPARTS_ENV_SETTINGS ++#endif ++ + #if IS_ENABLED(CONFIG_DISTRO_DEFAULTS) + #if IS_ENABLED(CONFIG_CMD_MMC) + #define BOOT_TARGET_DEVICES_MMC(func) func(MMC, mmc, 0) +@@ -130,8 +148,7 @@ + "loadaddr=" __stringify(CONFIG_SYS_LOAD_ADDR) "\0" \ + "bootfile=" CONFIG_BOOTFILE "\0" \ + "mmcroot=/dev/mmcblk0p2\0" \ +- "mtdids=" CONFIG_MTDIDS_DEFAULT "\0" \ +- "mtdparts=" CONFIG_MTDPARTS_DEFAULT "\0" \ ++ SOCFPGA_MTDPARTS_ENV_SETTINGS \ + "linux_qspi_enable=if sf probe; then " \ + "echo Enabling QSPI at Linux DTB...;" \ + "fdt addr ${fdt_addr}; fdt resize;" \ +@@ -162,8 +179,7 @@ + "loadaddr=" __stringify(CONFIG_SYS_LOAD_ADDR) "\0" \ + "bootfile=" CONFIG_BOOTFILE "\0" \ + "mmcroot=/dev/mmcblk0p2\0" \ +- "mtdids=" CONFIG_MTDIDS_DEFAULT "\0" \ +- "mtdparts=" CONFIG_MTDPARTS_DEFAULT "\0" \ ++ SOCFPGA_MTDPARTS_ENV_SETTINGS \ + "linux_qspi_enable=if sf probe; then " \ + "echo Enabling QSPI at Linux DTB...;" \ + "fdt addr ${fdt_addr}; fdt resize;" \ +@@ -216,8 +232,7 @@ + "bootm ${loadaddr}\0" \ + "mmcfitload=mmc rescan;" \ + "load mmc 0:1 ${loadaddr} ${bootfile}\0" \ +- "mtdids=" CONFIG_MTDIDS_DEFAULT "\0" \ +- "mtdparts=" CONFIG_MTDPARTS_DEFAULT "\0" \ ++ SOCFPGA_MTDPARTS_ENV_SETTINGS \ + "linux_qspi_enable=if sf probe; then " \ + "echo Enabling QSPI at Linux DTB...;" \ + "fdt addr ${fdt_addr}; fdt resize;" \ diff --git a/board/mister/de25nano/patches/uboot/uboot.hash b/board/mister/de25nano/patches/uboot/uboot.hash new file mode 100644 index 0000000..68c7dc9 --- /dev/null +++ b/board/mister/de25nano/patches/uboot/uboot.hash @@ -0,0 +1,44 @@ +# U-Boot tarball hash for the pinned BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE. +# +# WHY THIS FILE EXISTS +# Buildroot ships boot/uboot/uboot.hash, but it only ever carries the ONE +# version that release bundles — 2026.04 in Buildroot 2026.05.2. We pin 2026.07 +# (docs/de25-uboot.md), so that file has no line for our tarball. +# +# That is not a "no hash file" WARNING, it is a HARD FAILURE, and the +# difference matters. support/download/check-hash counts hash FILES and hash +# LINES separately: with at least one hash file present but no line matching +# the downloaded basename it exits 3 ("No hash found for ..."), and +# BR2_DOWNLOAD_FORCE_CHECK_HASHES=y (set in the defconfig) empties +# BR_NO_CHECK_HASH_FOR so nothing can excuse it. So the build fails closed +# until this file exists — which is the intended behaviour, and the reason a +# U-Boot version bump MUST update this file in the same commit. +# +# Buildroot finds it because pkg-patch-hash-dirs (package/pkg-utils.mk:163) +# searches $(BR2_GLOBAL_PATCH_DIR)// as well as the package directory, and +# the defconfig points BR2_GLOBAL_PATCH_DIR at board/mister/de25nano/patches. +# Same mechanism as the kernel's linux.hash next door; that file's header has +# the long version. +# +# WHERE THIS HASH COMES FROM — read before changing it. +# Fetched 2026-09-02 from the U-Boot release server: +# +# https://ftp.denx.de/pub/u-boot/u-boot-2026.07.tar.bz2 +# https://ftp.denx.de/pub/u-boot/u-boot-2026.07.tar.bz2.sig +# +# and the detached signature was VERIFIED, not merely downloaded: +# +# gpg: Signature made Mon Jul 6 19:02:11 2026 CDT +# gpg: using EDDSA key F3CEA8743D60E0192F9B4C7A2BE2A0F50ABFE40A +# gpg: issuer "trini@konsulko.com" +# gpg: Good signature from "Thomas Rini " +# +# Thomas Rini is the U-Boot maintainer and the signer of every U-Boot release +# tarball; the key was retrieved from keys.openpgp.org by full fingerprint. +# The sha256 below is of the tarball that signature covers. This is therefore a +# SIGNED provenance chain, not a trust-on-first-use value — do not downgrade it +# to "locally computed" on the next bump without re-doing the verification. +# +# The gpl-2.0.txt line is for `legal-info` only, and Buildroot's own +# boot/uboot/uboot.hash already carries it; it is not repeated here. +sha256 78e8bfc382fe388f9b55aa1daf8c563522a037779b5d4c349d1415e381f1243e u-boot-2026.07.tar.bz2 diff --git a/board/mister/de25nano/post-image.sh b/board/mister/de25nano/post-image.sh new file mode 100755 index 0000000..47cc523 --- /dev/null +++ b/board/mister/de25nano/post-image.sh @@ -0,0 +1,260 @@ +#!/bin/sh +# +# post-image.sh — assemble the DE25-Nano SD-card image (D2.4). +# +# Buildroot calls this after every image build (BR2_ROOTFS_POST_IMAGE_SCRIPT, +# system/Config.in: "executed from the main Buildroot source directory as the +# current directory", first argument = BINARIES_DIR). It does three things and +# no more: +# +# 1. Generates $BINARIES_DIR/extlinux/extlinux.conf from the template below. +# Every name and every kernel argument on this card has exactly ONE +# author, and it is this script: the genimage config lists file names, the +# checker asserts them, and neither invents them. +# 2. Runs genimage via Buildroot's own support/scripts/genimage.sh against +# board/mister/de25nano/genimage-sdcard.cfg, producing +# $BINARIES_DIR/sdcard-de25.img. +# 3. Hands the result to scripts/check-sdcard-de25.sh, which is the one and +# only place the layout assertions live. A checker failure is a nonzero +# exit, which Buildroot treats as a failed build (its Makefile runs +# post-image scripts as ordinary recipe lines, with no `|| true`). +# +# It does NOT re-derive the boot chain. That is docs/de25-boot-chain.md §2/§8.3 +# and the genimage config's header; the short version is that the factory SPL +# in QSPI — which nothing we ship ever writes (ADR 0029 D4) — reads a file +# called `u-boot.itb` from a FAT filesystem on partition 1, and that is our +# whole interface to the board's boot firmware. +# +# THE MISSING u-boot.itb CASE. Until D2.2 lands BR2_TARGET_UBOOT there is no +# FIT in BINARIES_DIR, and a card without one is a card that cannot boot. The +# default is therefore to FAIL. Set DE25_ALLOW_NO_UBOOT=1 to downgrade that to +# "skip the card, build succeeds" — for kernel/rootfs iteration and for the +# dry-run harness only. It is an opt-in with a loud message, never a default, +# because a silently u-boot.itb-less image is exactly the artifact somebody +# writes to a card and then debugs at a dead serial console for an hour. +# +# Usage: post-image.sh BINARIES_DIR [buildroot-config-name...] +# (Buildroot always passes BINARIES_DIR first; any BR2_ROOTFS_POST_SCRIPT_ARGS +# follow it and are ignored here.) +# +# Environment (all supplied by Buildroot's EXTRA_ENV, package/Makefile.in:362): +# BUILD_DIR genimage's scratch dir lives at $BUILD_DIR/genimage.tmp. If +# unset, derived from BINARIES_DIR — see the assertion below for +# why an EMPTY value must never reach genimage.sh. +# BR2_CONFIG read by genimage.sh only, for its optional bmaptool step. + +set -eu + +prog="post-image.sh(de25nano)" +board_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +# board/mister/de25nano -> repo root is three levels up. +repo_root=$(CDPATH='' cd -- "$board_dir/../../.." && pwd) + +genimage_cfg="$board_dir/genimage-sdcard.cfg" +checker="$repo_root/scripts/check-sdcard-de25.sh" + +# --------------------------------------------------------------------------- +# The names. Changing one here means changing it in genimage-sdcard.cfg too — +# which is why check_cfg_mentions() below refuses to run if they drift apart. +# --------------------------------------------------------------------------- +FIT_NAME=u-boot.itb # SPL_FS_LOAD_PAYLOAD_NAME, boot-chain §8.3 +KERNEL_NAME=Image # BR2_LINUX_KERNEL_IMAGE=y (uncompressed) +DTB_NAME=socfpga_agilex5_de25nano.dtb # BR2_LINUX_KERNEL_CUSTOM_DTS_PATH basename +ROOTFS_NAME=rootfs.ext4 # BR2_TARGET_ROOTFS_EXT2 + _EXT2_4 +SDCARD_NAME="sdcard-de25.img" # the `image` section in the cfg + +# Kernel command line, and the two halves of it that can silently produce a +# board that looks dead: +# +# root=/dev/mmcblk0p2 — the INTERIM p2 decision. ADR 0029 D3 fixes the +# partition count and p1's FAT type ONLY; p2's filesystem is still an open +# owner decision (implementation-path §6.3, §8 Q7). For this developer-OS +# card p2 is the ext4 rootfs written verbatim and mounted directly: no +# loop-mounted linux.img, no initramfs. See docs/de25-sdcard.md. +# +# console=ttyS0,115200 — the DE25-Nano's header UART is HPS **uart1** +# (serial@10c02100), aliased serial0 with stdout-path "serial0:115200n8" in +# board/mister/de25nano/socfpga_agilex5_de25nano.dts. It is the only +# enabled 8250 port, so it is ttyS0 under any 8250 numbering rule. uart0 is +# the SoC Development Kit's console and is a different board. +# `earlycon` (no argument) picks the port up from stdout-path. +ROOT_DEV=/dev/mmcblk0p2 +CONSOLE_ARG=ttyS0,115200 +BOOTARGS="root=$ROOT_DEV rw rootwait console=$CONSOLE_ARG earlycon" + +die() { + echo "$prog: FATAL: $*" >&2 + exit 1 +} + +note() { printf '%s: %s\n' "$prog" "$*"; } + +[ $# -ge 1 ] || die "usage: $prog BINARIES_DIR" +binaries_dir=$1 +[ -d "$binaries_dir" ] || die "BINARIES_DIR '$binaries_dir' is not a directory" +binaries_dir=$(CDPATH='' cd -- "$binaries_dir" && pwd) + +[ -f "$genimage_cfg" ] || die "genimage config not found: $genimage_cfg" +[ -x "$checker" ] || die "checker not found or not executable: $checker" + +# --- the cfg and this script must agree on every file name ------------------- +# Cheap, and it catches the one class of drift that would otherwise surface as +# a genimage "file not found" three steps later, or worse as a card missing a +# file nobody notices until it does not boot. +check_cfg_mentions() { + grep -q -- "$1" "$genimage_cfg" || + die "genimage-sdcard.cfg does not mention '$1' — this script and that config have drifted apart; fix both in the same commit" +} +for _n in "$FIT_NAME" "$KERNEL_NAME" "$DTB_NAME" "$ROOTFS_NAME" "$SDCARD_NAME"; do + check_cfg_mentions "$_n" +done + +# --- locate Buildroot's genimage wrapper ------------------------------------- +# THE BUILDROOT WAY, and it is worth saying why rather than calling genimage +# directly: support/scripts/genimage.sh already handles the two things that are +# easy to get wrong by hand — it passes an EMPTY --rootpath (genimage copies +# the whole rootpath into its tmpdir, so handing it TARGET_DIR would waste +# minutes and gigabytes for an image type that never reads it), and it wipes +# GENIMAGE_TMP first, which genimage insists be empty. +# +# Normal case: cwd is the Buildroot source directory, so ./support/... resolves. +# The other two entries exist so this script is runnable stand-alone (the +# dry-run harness, and anyone re-assembling a card from an existing +# output-de25/ without a full rebuild). +genimage_sh=${DE25_GENIMAGE_SH:-} +if [ -z "$genimage_sh" ]; then + for _c in "$PWD/support/scripts/genimage.sh" \ + "$repo_root/work/buildroot/support/scripts/genimage.sh"; do + if [ -x "$_c" ]; then + genimage_sh=$_c + break + fi + done +fi +[ -n "$genimage_sh" ] && [ -x "$genimage_sh" ] || + die "Buildroot's support/scripts/genimage.sh not found (cwd=$PWD); set \$DE25_GENIMAGE_SH" + +# --- BUILD_DIR must be non-empty before genimage.sh sees it ------------------ +# genimage.sh does `GENIMAGE_TMP="${BUILD_DIR}/genimage.tmp"` and then +# `rm -rf "${GENIMAGE_TMP}"` with no guard of its own. An empty BUILD_DIR turns +# that into an rm -rf on an absolute path at the filesystem root. Buildroot +# always exports it (package/Makefile.in:362); this derives Buildroot's own +# layout ($(BASE_DIR)/build beside $(BASE_DIR)/images) when it is absent, and +# refuses to continue if the result is still empty. +if [ -z "${BUILD_DIR:-}" ]; then + BUILD_DIR=$(CDPATH='' cd -- "$binaries_dir/.." && pwd)/build + note "BUILD_DIR was unset; using $BUILD_DIR" +fi +[ -n "$BUILD_DIR" ] || die "BUILD_DIR is empty — refusing to hand that to genimage.sh" +mkdir -p "$BUILD_DIR" +export BUILD_DIR +export BINARIES_DIR="$binaries_dir" + +# ============================================================================= +# 1. Inputs +# ============================================================================= +# Every missing input is reported, not just the first: someone whose build is +# missing both the kernel and the rootfs should learn that in one run. +missing="" +for _f in "$KERNEL_NAME" "$DTB_NAME" "$ROOTFS_NAME"; do + [ -f "$binaries_dir/$_f" ] || missing="$missing $_f" +done +if [ -n "$missing" ]; then + echo "$prog: FATAL: missing from $binaries_dir:$missing" >&2 + echo "$prog: $KERNEL_NAME <- BR2_LINUX_KERNEL_IMAGE=y" >&2 + echo "$prog: $DTB_NAME <- BR2_LINUX_KERNEL_CUSTOM_DTS_PATH" >&2 + echo "$prog: $ROOTFS_NAME <- BR2_TARGET_ROOTFS_EXT2 + BR2_TARGET_ROOTFS_EXT2_4" >&2 + exit 1 +fi + +if [ ! -f "$binaries_dir/$FIT_NAME" ]; then + if [ "${DE25_ALLOW_NO_UBOOT:-0}" = 1 ]; then + note "WARNING: no $FIT_NAME in $binaries_dir, and DE25_ALLOW_NO_UBOOT=1 —" + note "WARNING: SKIPPING $SDCARD_NAME. The kernel and rootfs are built; there" + note "WARNING: is NO SD-card image, and nothing produced by this build can boot" + note "WARNING: a DE25-Nano. Unset DE25_ALLOW_NO_UBOOT to make this a hard error." + # A card from an EARLIER run must not survive this one: the de25 recipe + # reports whatever sdcard-de25.img it finds, and a stale image would make + # the skip above a lie. Remove the card and its p1 filesystem image. + rm -f "$binaries_dir/$SDCARD_NAME" "$binaries_dir/boot-de25.vfat" + exit 0 + fi + die "no $FIT_NAME in $binaries_dir. + The factory SPL loads that FIT by name from p1 and nothing else will do + (docs/de25-boot-chain.md §2 step 4, §8.3), so a card without it cannot + boot. It comes from BR2_TARGET_UBOOT + BR2_TARGET_ARM_TRUSTED_FIRMWARE + in configs/fragments/de25nano.fragment (docs/buildroot-config.md §6.9), + so a missing FIT means that stanza did not build. + To build the kernel and rootfs anyway and skip the card, re-run with + DE25_ALLOW_NO_UBOOT=1." +fi + +note "inputs in $binaries_dir:" +for _f in "$FIT_NAME" "$KERNEL_NAME" "$DTB_NAME" "$ROOTFS_NAME"; do + printf ' %-32s %s bytes\n' "$_f" "$(wc -c <"$binaries_dir/$_f" | tr -d ' ')" +done + +# ============================================================================= +# 2. Stage the FAT payload's one generated file: extlinux/extlinux.conf +# ============================================================================= +# genimage's vfat handler copies the `extlinux` DIRECTORY out of --inputpath +# (= BINARIES_DIR) recursively, so this is where it has to land. Written to a +# temp file and renamed, so an interrupted run never leaves a half-written +# boot configuration for the next one to package. +extlinux_dir="$binaries_dir/extlinux" +extlinux_conf="$extlinux_dir/extlinux.conf" +mkdir -p "$extlinux_dir" +tmp_conf="$extlinux_conf.tmp.$$" +trap 'rm -f "$tmp_conf"' EXIT + +cat > "$tmp_conf" <-u-boot.dtsi next to + * .dts (scripts/Makefile.lib, u_boot_dtsi_options), which is why this + * file has no entry anywhere in the config: it is found by NAME. Rename the + * .dts and this file must be renamed in the same commit or the board silently + * loses everything below -- including the FIT description, which is the entire + * output of this build. + * + * Modelled on mainline v2026.07's arch/arm/dts/socfpga_agilex5_socdk-u-boot.dtsi. + */ + +/* + * LOAD-BEARING, AND NOT OPTIONAL: this pulls in socfpga_agilex5-u-boot.dtsi, + * which in turn #includes socfpga_soc64_fit-u-boot.dtsi -- the binman + * description that IS u-boot.itb. Without this line the build produces a + * working U-Boot binary and no FIT at all. + */ +#include "socfpga_agilex5-u-boot.dtsi" + +/ { + chosen { + /* + * serial0 is aliased to uart1 in the .dts. 115200 8N1 is what the + * board's USB-UART bridge and every reference tree use. + * + * NOTE what is NOT here: mainline's socdk carries + * u-boot,spl-boot-order = &mmc,&flash0,&nand,"/memory"; + * That property is read by board_boot_order() in SPL. OUR SPL + * NEVER RUNS -- the factory SPL in QSPI does, from its own DTB, + * whose boot order is already known and fixed: + * "/soc/mmc0@10808000", "/soc/spi@108d2000/flash@0", + * "/soc/nand@10b80000", "/memory" + * (docs/de25-boot-chain.md section 2). Restating it here would + * describe a decision we do not get to make, and referencing + * &flash0 would not even compile: this board file deliberately + * declares no QSPI flash node. + */ + stdout-path = "serial0:115200n8"; + }; +}; + +&uart1 { + /* + * socfpga_agilex5-u-boot.dtsi marks &uart0 bootph-all because socdk's + * console is uart0. Ours is uart1, and without this marking the node is + * not bound before relocation, so every printf between board_init_f and + * relocation is lost -- which is precisely the window where a bring-up + * failure would be diagnosed. + */ + bootph-all; +}; + +&mmc { + /* + * CONSERVATIVE ON THE BOARD FACTS, SoCDK-VALIDATED ON THE SoC FACTS. + * Those are two different things and this block deliberately treats them + * differently. + * + * BOARD facts we do NOT take from socdk: mainline's + * socfpga_agilex5_socdk-u-boot.dtsi declares sd-uhs-sdr50/sdr104 with + * vqmmc-supply = <&sd_io_1v8_reg>, whose GPIO is <&portb 3>. That pin is + * a SoC Development Kit wiring fact; the DE25-Nano's 1.8V/3.3V switch, if + * it has one, is not necessarily there, and driving the wrong GPIO to + * change SD bus voltage is a way to break a card rather than a way to go + * faster. The reference DE25 U-Boot tree declares no vqmmc regulator + * either. So: no UHS modes, no voltage switching, no regulator phandles. + * + * SoC facts we DO take from socdk: the cdns,* PHY/controller delay values + * below, copied verbatim from + * u-boot v2026.07 arch/arm/dts/socfpga_agilex5_socdk-u-boot.dtsi:122-134 + * (the sd-ds and sd-hs stanzas only). These are Agilex 5 SD-controller + * timings, not board wiring -- and they are the only Agilex 5 values + * anyone has run on silicon. + * + * WHY COPY THEM AT ALL, when the driver has defaults? Because the + * driver's defaults are not a validated configuration, they are a + * fallback. drivers/mmc/sdhci-cadence6.c's property tables give every + * entry a default, and for sd-hs those defaults are + * dqs 0x00380004, gate-lpbk 0x01A00040, dq 0x00000001, + * and NO ctrl-hrs07 / ctrl-hrs16 entries at all + * -- values no validated Agilex 5 board ships. socdk uses 0x780001, + * 0x81a40040, 0x10000001, hrs16 = 0x101, hrs07 = 0xA0001. Shipping the + * driver defaults on first-contact hardware would mean debugging a card + * read against timings nobody has ever proven on this SoC. + * + * SPEED: default-speed (DS) only, 25 MHz. cap-sd-highspeed is + * deliberately NOT set and max-frequency is 25 MHz, so U-Boot proper + * never leaves DS mode. This is the slowest and most forgiving SD timing + * there is, and the controller's entire job here is to read one Image and + * one .dtb, once, per boot -- at 25 MHz 4-bit that is about 12.5 MB/s, + * so a 20 MB kernel costs under two seconds. The sd-hs values are still + * declared above so that lifting the cap is a one-line change rather than + * a research task. See docs/de25-uboot.md section 5 for the lift + * procedure and for what a timing problem looks like on the console. + */ + status = "okay"; + no-mmc; + no-sdio; + disable-wp; + bus-width = <4>; + max-frequency = <25000000>; + + /* SD card default speed (DS) and UHS-I SDR12 mode timing configuration */ + cdns,phy-dqs-timing-delay-sd-ds = <0x00780000>; + cdns,phy-gate-lpbk-ctrl-delay-sd-ds = <0x81a40040>; + cdns,phy-dll-slave-ctrl-sd-ds = <0x00a000fe>; + cdns,phy-dq-timing-delay-sd-ds = <0x28000001>; + + /* SD card high speed and UHS-I SDR25 mode timing configuration. + * Declared but NOT REACHED while cap-sd-highspeed is absent -- kept so + * that enabling high speed later is one line, not a research task. */ + cdns,phy-dqs-timing-delay-sd-hs = <0x780001>; + cdns,phy-gate-lpbk-ctrl-delay-sd-hs = <0x81a40040>; + cdns,phy-dq-timing-delay-sd-hs = <0x10000001>; + cdns,ctrl-hrs16-slave-ctrl-sd-hs = <0x101>; + cdns,ctrl-hrs07-timing-delay-sd-hs = <0xA0001>; + + bootph-all; +}; + +/* + * Name the FIT's fdt-0 after this board. + * + * socfpga_soc64_fit-u-boot.dtsi hardcodes description = "socfpga_socdk" for + * every SoC64 board. It is a description string with no functional effect -- + * board_fit_config_name_match() compares the CONFIGURATION node's description, + * not the image's -- but "socfpga_socdk" in a DE25 artifact is exactly the kind + * of label that gets believed later. docs/de25-implementation-path.md section + * 6.1 asks for it to be renamed per board. + */ +&images { + fdt-0 { + description = "socfpga_agilex5_de25nano"; + }; +}; + +/* + * Drop the kernel.itb image from the binman description. + * + * TWO independent reasons, and either alone would be sufficient: + * + * 1. WE DO NOT SHIP kernel.itb. This board boots via extlinux: U-Boot reads + * extlinux/extlinux.conf off the FAT partition and loads Image + dtb as + * plain files. A second, unused, unsigned FIT carrying a copy of the kernel + * is a redundant artifact whose staleness nobody would notice. + * + * 2. WITHOUT THIS, THE BUILD FAILS. binman is invoked with --allow-missing + * --fake-ext-blobs but WITHOUT --ignore-missing (U-Boot's Makefile only + * adds the latter when BINMAN_ALLOW_MISSING is set), and it returns 103 -- + * "Some images are invalid" -- if any image is missing an external blob. + * The kernel image's blob-ext entries are "Image" and "linux.dtb". Buildroot + * passes BINMAN_INDIRS=$(BINARIES_DIR), so binman does find images/Image -- + * but nothing in this project ever produces a file called linux.dtb (ours + * is socfpga_agilex5_de25nano.dtb), so kernel.itb can never be complete. + * + * The alternative fixes were setting BINMAN_ALLOW_MISSING=1 -- which would + * ALSO silence a genuinely missing bl31.bin, the one blob whose absence must + * never be silent -- or patching U-Boot. Deleting the node we do not want is + * smaller than both and needs no patch. The reference DE25 tree does the same + * thing in the same place. + */ +&binman { + /delete-node/ kernel; +}; diff --git a/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts b/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts new file mode 100644 index 0000000..f9faedb --- /dev/null +++ b/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * U-Boot board device tree for the Terasic DE25-Nano (Intel/Altera Agilex 5). + * + * THIS IS THE U-BOOT DEVICE TREE, NOT THE KERNEL ONE. + * The kernel's board file is ../socfpga_agilex5_de25nano.dts and the two share + * a basename on purpose -- each tree names its board file the same way -- but + * they are different files describing the same board to two different sets of + * drivers. Do not "deduplicate" them: U-Boot's socfpga_agilex5.dtsi and the + * kernel's are separate upstream files with different node sets, different + * compatibles (U-Boot's mmc0 is "altr,agilex5-sd6hc"/"cdns,sd6hc"; the kernel's + * has no mmc0 at all and ours declares the SD4HC form), and different + * -u-boot.dtsi machinery. See docs/de25-uboot.md and docs/de25-dts-rationale.md. + * + * WHY THIS FILE EXISTS AT ALL. Mainline U-Boot v2026.07 has no DE25-Nano board: + * board/terasic/ has de0-nano-soc, de1-soc, de10-nano, de10-standard and sockit + * and no de25 entry, and there is no configs/*de25* anywhere in the tree + * (docs/de25-implementation-path.md section 6). The nearest in-tree Agilex 5 + * board is socfpga_agilex5_socdk, and it is NOT usable as-is: it aliases + * serial0 to &uart0, while the DE25-Nano's header UART is uart1. Booting the + * socdk device tree on this board produces a console on a pin nobody has wired + * -- i.e. a board that looks dead. That single line is why we carry a board + * file instead of reusing socdk's. + * + * Authored against mainline U-Boot v2026.07's arch/arm/dts/socfpga_agilex5.dtsi + * (which this #includes out of the U-Boot tree, so this is a small delta, not a + * from-scratch device tree). Cross-checked node by node against the reference + * DE25 U-Boot tree at /mnt/source/de25-uboot-socfpga + * (arch/arm/dts/socfpga_agilex5_de25_nano.dts) -- read for board WIRING only; + * none of its SPL, RSU or exFAT-SPL work is carried. + */ + +#include "socfpga_agilex5.dtsi" + +/ { + model = "SoCFPGA Agilex5 Terasic DE25-Nano"; + + aliases { + /* + * THE ONE LINE THIS FILE EXISTS FOR. uart1 (serial@10c02100) is + * the DE25-Nano's USB-UART header; uart0 is the SoC Development + * Kit's console. Every DE25 reference tree agrees + * (docs/de25-dts-rationale.md U1 [V]); the -u-boot.dtsi beside + * this file points stdout-path at serial0, so this alias is what + * decides whether the board has a console at all. + */ + serial0 = &uart1; + + /* + * Pin the SD controller at mmc 0, explicitly. + * + * UCLASS_MMC carries DM_UC_FLAG_SEQ_ALIAS, so this alias fixes + * dev_seq(). With a single MMC controller the answer would be 0 + * anyway, but "0" is load-bearing in three places that are not + * in this file and cannot check each other: + * - CONFIG_ENV_FAT_DEVICE_AND_PART="0:1" (where uboot.env lives) + * - the factory SPL's own FS-boot device + * - distro_bootcmd's mmc0 boot target, and every "load mmc 0:1" + * in the extlinux/boot path on the SD card + * Writing it down means a future second MMC device cannot silently + * renumber the one the card layout is written against. + */ + mmc0 = &mmc; + }; + + /* + * DRAM, declared -- not handed off. + * + * Mainline's socdk board file leaves this <0 0 0 0> and says "we expect + * the bootloader to fill in the reg", which on Agilex 5 means U-Boot + * proper reads gd->ram_size out of the SPL's bloblist handoff + * (arch/arm/mach-socfpga/misc.c dram_init(), guarded on CONFIG_HANDOFF + * && ARCH_SOCFPGA_AGILEX5). We do NOT run our own SPL -- the factory + * SPL in QSPI does, and it is a Terasic build of a different U-Boot + * whose CONFIG_BLOBLIST_ADDR we cannot read out of any published + * artifact. So the fragment turns CONFIG_HANDOFF off and dram_init() + * falls back to fdtdec_setup_mem_size_base(), which reads the "/memory" + * node BY PATH -- hence the node name with no unit address, exactly as + * mainline's socdk and the reference DE25 tree both write it. + * + * 1 GiB at 0x8000_0000 is what the DE25-Nano User Manual and every DE25 + * bootloader source state, and it matches the value our kernel DTS + * declares (docs/de25-dts-rationale.md, "Memory"). It is a VENDOR + * DECLARATION, not a measurement: the authority is the IO96B readout the + * factory SPL prints as its "DDR:" lines on first boot. Under-declaring + * degrades gracefully (we simply do not use the top half); + * over-declaring puts U-Boot's relocation target in nonexistent DRAM. + */ + memory { + device_type = "memory"; + reg = <0 0x80000000 0 0x40000000>; + }; + + soc { + clocks { + /* + * The SoC dtsi declares osc1 as a fixed-clock with no + * rate; every Agilex 5 board file sets it, and the whole + * clock tree (and therefore the UART divisor) hangs off + * it. 25 MHz is what socdk and the reference DE25 tree + * both use. + */ + osc1 { + clock-frequency = <25000000>; + }; + }; + }; +}; + +/* Console. See the alias above. */ +&uart1 { + status = "okay"; +}; + +/* + * microSD. The controller is enabled here; its capabilities and the + * bootph-all marking live in the -u-boot.dtsi beside this file, because that + * is where mainline's socdk keeps them. + */ +&mmc { + status = "okay"; +}; + +/* + * DELIBERATE OMISSIONS -- each one is a decision, not an oversight: + * + * - NO &qspi and NO flash node. The QSPI holds the factory SDM firmware, + * the phase-1 bitstream and the factory SPL, and nothing we ship may + * write it (docs/de25-boot-chain.md section 7, rows 1/5/10/12). Leaving + * the node at the dtsi's "disabled" default means that even if a QSPI + * driver were somehow compiled in, nothing would probe. The fragment + * removes the driver too; this is the second lock on the same door. + * + * - NO &nand. Same reasoning, plus the board has no NAND. + * + * - NO &gmac0 / no PHY node. U-Boot does not need ethernet to load a kernel + * off the card, and the DE25's PHY address and phy-mode in U-Boot terms + * are unverified. Add it in the hardware session if netboot is wanted. + * + * - NO leds, watchdogs, timers, i2c, i3c, usb, spi0/spi1. None is on the + * path from reset to "load Image from FAT and boot it". Every node here + * is a node someone has to keep correct for a board nobody has booted yet. + */ diff --git a/board/mister/de25nano/uboot.fragment b/board/mister/de25nano/uboot.fragment new file mode 100644 index 0000000..43c568a --- /dev/null +++ b/board/mister/de25nano/uboot.fragment @@ -0,0 +1,299 @@ +# uboot.fragment -- DE25-Nano deltas on mainline's socfpga_agilex5_defconfig. +# +# Applied by Buildroot on top of BR2_TARGET_UBOOT_BOARD_DEFCONFIG +# ("socfpga_agilex5") via BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES: +# merge_config.sh, then olddefconfig. Every line below is a DELTA -- if a value +# here matches what the board defconfig already resolves to, it is restated +# only where the restatement is the point (see the environment block). +# +# Design: docs/de25-uboot.md (per-line rationale + the QSPI-write audit table), +# docs/de25-implementation-path.md sections 6.1-6.3 and section 8 Q6, +# docs/de25-boot-chain.md sections 2, 3, 5 and the section 7 brick-risk +# register. +# +# THE ONE RULE THIS FILE ENFORCES: nothing we ship may write the QSPI. The QSPI +# holds the SDM firmware, the phase-1 HPS bitstream (with ALL the DDR and +# pinmux handoff data) and the factory SPL. It has no power-loss-safe update +# path on this board, and a corrupted one is a JTAG-and-a-PC recovery. That is +# brick-class. See the audit block below, and de25-boot-chain.md section 7. +# +# EDITING RULE, learned the hard way: never start a COMMENT line with +# "# CONFIG_" unless it really is an "is not set" directive. +# support/kconfig/merge_config.sh greps this file for every symbol it is +# changing and echoes the matching lines back at you, so a prose line that +# begins that way turns the merge report into nonsense -- and a line that +# happens to end in "is not set" would be parsed as a real directive +# (SED_CONFIG_EXP2 is: caret, hash, space, the symbol name, then the +# literal words "is not set" anchored to end of line). Indent +# such prose, or lead with a word. + +################################################################################ +# Board device tree +################################################################################ +# +# Names board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts, which +# BR2_TARGET_UBOOT_CUSTOM_DTS_PATH copies into arch/arm/dts/ before the build. +# scripts/Makefile.dts adds $(CONFIG_DEFAULT_DEVICE_TREE).dtb to dtb-y, so a +# dts that appears in no arch Makefile list still gets built -- which is what +# makes carrying a board file without patching U-Boot possible at all. +# +# Mainline v2026.07 has no DE25-Nano board and socdk aliases serial0 to uart0, +# which is the wrong UART on this board. That is the whole reason for the +# custom dts; the .dts header explains it at length. +CONFIG_DEFAULT_DEVICE_TREE="socfpga_agilex5_de25nano" + +################################################################################ +# Environment -- the block that makes a QSPI write structurally impossible +################################################################################ +# +# docs/de25-implementation-path.md section 6.2, and de25-boot-chain.md section +# 7 rows 5 and 12. +# +# The stock socfpga_agilex5_defconfig compiles in BOTH env locations: FAT +# (device 0, partition 1) and UBI (volume "env" in the QSPI "root" MTD +# partition). FAT is tried first and env_save() writes back to whichever +# location env_load() succeeded from, so with a valid uboot.env on FAT, +# `saveenv` never reaches UBI. +# +# THE HAZARD IS NOT saveenv. It is on the LOAD path, with no user action at +# all: env_ubi_load() (env/ubi.c:107) calls ubi_part() UNCONDITIONALLY at +# env/ubi.c:128 whenever the FAT load fails. That runs ubi_dev_scan() -> +# ubi_init() -> ubi_attach_mtd_dev() -> ubi_attach(); against a blank MTD +# partition the attach SUCCEEDS ("empty MTD device detected"), and +# ubi_read_volume_table() then calls create_empty_lvol() -> create_vtbl(), +# which WRITES a fresh UBI layout volume into the QSPI. +# +# Missing or corrupt uboot.env on FAT + a blank QSPI "root" MTD +# ==> QSPI is written on the very first env_load(), with no saveenv anywhere. +# +# Grepping env/ubi.c for a write finds nothing -- the write is three call +# levels down, inside the UBI attach path. That is why the guard has to be +# "the driver is not compiled in", not "do not run saveenv". +# +# ENV_IS_IN_UBI is a plain defconfig choice: nothing in ARCH_SOCFPGA_AGILEX5 or +# ARCH_SOCFPGA_SOC64 selects it, so turning it off here is sufficient. +CONFIG_ENV_IS_IN_FAT=y +CONFIG_ENV_FAT_DEVICE_AND_PART="0:1" +# CONFIG_ENV_IS_IN_UBI is not set +# +# The other env backends, stated explicitly rather than left to a default. +# None is set by the board defconfig today; naming them means a future +# Buildroot or U-Boot bump that flips a default fails the CI grep instead of +# quietly re-opening the hazard. ENV_IS_IN_SPI_FLASH is the sharpest of the +# three: it would put the environment directly in the QSPI. +# CONFIG_ENV_IS_IN_SPI_FLASH is not set +# CONFIG_ENV_IS_IN_NAND is not set +# CONFIG_ENV_IS_IN_MMC is not set + +################################################################################ +# QSPI: no driver, no commands, no filesystem -- three locks on one door +################################################################################ +# +# Turning ENV_IS_IN_UBI off closes the automatic path. These lines close the +# manual ones: a root shell at the U-Boot prompt, a boot script, or a ported +# DE10 habit. de25-boot-chain.md section 7 rows 10 and 11 exist because the +# env guard alone does not cover them. +# +# LOCK 1 -- the controller driver. cadence_qspi is the ONLY way U-Boot can +# reach this flash; the SD/MMC controller is a physically separate block, so +# with this off no amount of sector arithmetic on mmcblk can touch boot flash. +# CONFIG_CADENCE_QSPI is not set +# +# LOCK 2 -- the SPI-NOR stack above it. Without these, `sf` has nothing to +# bind to even if a command survived. +# CONFIG_DM_SPI_FLASH is not set +# CONFIG_SPI_FLASH is not set +# CONFIG_SPI_FLASH_MTD is not set +# CONFIG_SPI_FLASH_STMICRO is not set +# CONFIG_SPI_FLASH_SPANSION is not set +# +# LOCK 3 -- the commands. Each of these can erase or write flash directly. +# CMD_SF is "default y if DM_SPI_FLASH", so it is on in the stock config and +# has to be named here even though lock 2 already removes its subject. +# CONFIG_CMD_SF is not set +# CONFIG_CMD_SF_TEST is not set +# CONFIG_CMD_MTD is not set +# CONFIG_CMD_MTDPARTS is not set +# CONFIG_CMD_UBI is not set +# CONFIG_CMD_UBIFS is not set +# CONFIG_CMD_NAND is not set +# +# ... and the layers those commands sit on, so that nothing can re-select them. +# CONFIG_MTD is not set +# CONFIG_DM_MTD is not set +# CONFIG_MTD_UBI is not set +# CONFIG_MTD_RAW_NAND is not set +# +# The same, SPL-side. Our SPL is compiled (see the SPL block below) but never +# shipped, so this is belt-and-braces rather than a live hazard -- it also +# keeps the SPL link from referencing a QSPI stack we just removed. +# CONFIG_SPL_SPI_LOAD is not set +# CONFIG_SPL_SPI_FLASH_MTD is not set +# CONFIG_SPL_DM_SPI_FLASH is not set +# CONFIG_SPL_MTD is not set +# +# NOT DISABLED, and deliberately so: +# * CONFIG_SPI / CONFIG_DESIGNWARE_SPI -- the DesignWare SPI master (spi0/spi1 +# general-purpose pins). A different controller from the Cadence QSPI block; +# it cannot reach boot flash. +# * CONFIG_QSPI_BOOT -- despite the name this is a boot/Kconfig media choice +# consumed only by NXP Layerscape and i.MX code (grep: every user is under +# arch/arm/cpu/armv8/fsl-layerscape, arch/arm/cpu/armv7/ls102xa or +# arch/arm/mach-imx). It is inert on socfpga and changing it would be +# cosmetic churn in a file whose every line should mean something. + +################################################################################ +# DRAM -- do not depend on the factory SPL's bloblist +################################################################################ +# +# A coupling that no existing document names, found while building this. +# +# arch/arm/mach-socfpga/misc.c dram_init() has two branches. Under the +# combination CONFIG_HANDOFF + ARCH_SOCFPGA_AGILEX5 it does +# bloblist_find(BLOBLISTT_U_BOOT_SPL_HANDOFF, ...) +# and returns -ENOENT -- a fatal early initcall failure -- if the blob is not +# there. That blob is written by OUR SPL, at OUR CONFIG_BLOBLIST_ADDR. But our +# SPL never runs: the factory SPL in QSPI does (posture 1), it is a Terasic +# build of U-Boot 2025.01, and its BLOBLIST_ADDR is a compiled-in constant that +# appears in no artifact we can read. Mainline's socdk uses 0x7e000; the +# reference DE25 tree uses 0x72000. If they disagree, U-Boot proper dies before +# it has done anything, and the failure is indistinguishable from a bad card. +# +# With HANDOFF off, dram_init() takes the other branch -- +# fdtdec_setup_mem_size_base(), which reads the "/memory" node from our own +# u-boot.dtb -- and the whole question disappears. The cost is that the DRAM +# size is a declared constant (1 GiB at 0x8000_0000, in the board .dts) instead +# of the IO96B readout. That is the right trade for posture 1: the QSPI side is +# not ours and not verifiable, so U-Boot's first initcall must not depend on it. +# +# IF THE HARDWARE SESSION SHOWS THE WRONG DRAM SIZE, the fix is the constant in +# the .dts, not this line. (The struct spl_handoff ABI itself is byte-identical +# between the reference 2025.01-lineage tree and v2026.07 -- diff of +# include/handoff.h -- so re-enabling HANDOFF with CONFIG_BLOBLIST_ADDR=0x72000 +# is a viable fallback if the measured size ever has to come from the SPL.) +# CONFIG_HANDOFF is not set +# +# ... and with HANDOFF off, the bloblist itself has no remaining user in this +# build, so it goes too. That is not tidiness -- it removes a latent memory +# overlap. +# +# The stock defconfig sets BLOBLIST + BLOBLIST_FIXED with ADDR 0x7e000 and +# SIZE 0x1000, i.e. the region 0x7E000..0x7EFFF in on-chip RAM. TF-A v2.15.0's +# Agilex 5 platform puts its secondary-CPU handshake words at the top of that +# same page: +# PLAT_HANDOFF_OFFSET = 0x0007F000 (agilex5/socfpga_plat_def.h:30) +# BL_DATA_LIMIT = PLAT_HANDOFF_OFFSET +# PLAT_CPUID_RELEASE = BL_DATA_LIMIT - 16 = 0x7EFF0 +# PLAT_SEC_ENTRY = BL_DATA_LIMIT - 8 = 0x7EFF8 (common/platform_def.h:125-128) +# and bl31_plat_setup.c:59 writes PLAT_SEC_ENTRY. The declared bloblist region +# covers both words. +# +# TODAY that is benign -- U-Boot only ever writes the ~32-byte bloblist header +# at 0x7E000 and never grows into the last 16 bytes of the page. But "benign +# because nothing currently fills the buffer" is a property of today's blob +# set, not a guarantee, and the failure it guards against is a secondary CPU +# jumping to a clobbered entry point. Since nothing in this build reads or +# writes a bloblist once HANDOFF is off, deleting the region is strictly +# better than reasoning about how full it gets. +# +# CHECKED, not assumed: with this off the config still resolves to SPL=y, +# SPL_ATF=y and BINMAN=y, and a full U-Boot build completes and still emits +# u-boot.itb. BLOBLIST_FIXED, BLOBLIST_ADDR, BLOBLIST_SIZE, SPL_BLOBLIST, +# HANDOFF and SPL_HANDOFF all disappear from the resolved config with it -- +# which is why the HANDOFF line above is now belt-and-braces rather than the +# operative guard. Keep both: they document two separate decisions, and if a +# future bump makes something select BLOBLIST again, the HANDOFF line is still +# the one that keeps dram_init() off the factory SPL's bloblist. +# CONFIG_BLOBLIST is not set + +################################################################################ +# SPL -- section 8 Q6, answered by building it +################################################################################ +# +# de25-implementation-path.md section 6.1 reasoned from the Kconfig graph that +# our fragment could carry "# CONFIG_SPL is not set" and genuinely eliminate +# SPL compilation, since none of the binman FIT images references anything +# under spl/. IT CANNOT, and the reason is one line of Kconfig: +# +# config ARCH_SOCFPGA_AGILEX5 +# select BINMAN if SPL_ATF (arch/arm/mach-socfpga/Kconfig) +# +# SPL_ATF lives inside `menu "SPL configuration options" depends on SPL` +# (common/spl/Kconfig:19-20), so with SPL off SPL_ATF is off, BINMAN is not +# selected -- and CONFIG_BINMAN is a bool with NO PROMPT (dts/Kconfig:15), so +# it cannot be turned back on from a defconfig or a fragment; kconfig drops the +# line. No binman means no u-boot.itb, which is the only artifact this build +# exists to produce. +# +# That was CHECKED, not just reasoned: this same fragment plus one extra +# "SPL is not set" line, resolved through merge_config.sh + olddefconfig, +# produces a .config in which SPL_ATF and BINMAN are absent entirely. +# +# So SPL STAYS COMPILED, and NOTHING OF IT IS SHIPPED. That is enforced +# positively rather than by omission: BR2_TARGET_UBOOT_SPL is not set in +# configs/fragments/de25nano.fragment, so Buildroot copies no spl/* file into +# images/, and the Makefile's `de25` assertions name u-boot.itb and bl31.bin +# and nothing else. The factory SPL in QSPI remains untouched, which is the +# posture-1 contract. +# +# Q6 is therefore CLOSED, in the negative. Do not re-open it by adding +# "# CONFIG_SPL is not set" here: it does not fail loudly, it produces a build +# with no FIT in it. + +################################################################################ +# Filesystems and boot +################################################################################ +# +# FAT: partition 1 is FAT by the factory SPL's own contract (CONFIG_SPL_FS_FAT +# + SYS_MMCSD_FS_BOOT_PARTITION=1), and it is where u-boot.itb, the kernel, the +# dtb and extlinux/extlinux.conf all live -- and where uboot.env WOULD live if +# anything ever wrote one; the card ships none. FS_FAT and CMD_FAT are +# already pulled in by BOOT_DEFAULTS_CMDS; restated because the environment and +# the boot path both depend on them and a silent regression here is a board +# that does not boot. +CONFIG_FS_FAT=y +CONFIG_CMD_FAT=y +# +# exFAT: mainline gained a real fs/exfat in commit b86a651b64 (2025-03-17), +# after the reference board's 2025.01 base -- which is exactly why that project +# had to hand-roll libexfat and an exFAT-aware SPL. For us it is one line. +# Partition 2's filesystem is still an open owner decision +# (de25-implementation-path.md section 8 Q7); enabling this now means the +# answer "exFAT" does not require a U-Boot change later. It reads and writes +# the SD card only and cannot touch QSPI. +CONFIG_FS_EXFAT=y +# +# Boot flow: extlinux, from the FAT partition. +# +# DISTRO_DEFAULTS is already y in the board defconfig and resolves BOOTCOMMAND +# to "run distro_bootcmd"; BOOTMETH_EXTLINUX is y by default. distro_bootcmd +# scans mmc 0 and, per prefix "/" then "/boot/", looks for +# extlinux/extlinux.conf. Restated here because the boot path is the deliverable +# and because upstream marks DISTRO_DEFAULTS deprecated -- the day it is +# removed, this line is where the migration to BOOTSTD_DEFAULTS starts, and the +# grep that finds it should find a comment too. +# +# The card side of this contract (D2.4's other half, another owner): +# p1 (FAT) : /u-boot.itb, /Image, /socfpga_agilex5_de25nano.dtb, +# /extlinux/extlinux.conf +# and NO uboot.env -- the card deliberately ships none. With +# ENV_IS_IN_UBI gone there is exactly one env driver compiled in, +# so a FAT miss loads the built-in default environment and writes +# nothing; a frozen uboot.env on the card would instead override +# every future release's default env. docs/de25-uboot.md section +# 10 has the trace. +# extlinux.conf: +# default de25 +# label de25 +# kernel /Image +# fdt /socfpga_agilex5_de25nano.dtb +# append console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait +CONFIG_DISTRO_DEFAULTS=y +# +# Fallback bootargs. With extlinux these are REPLACED by the config's `append` +# line, so this string is only used if something boots the kernel by hand from +# the U-Boot prompt. The stock socfpga_agilex5_defconfig ships a ramdisk-and- +# nosmp string aimed at Simics emulation; leaving that in place would make a +# manual `booti` do something surprising on real hardware. +CONFIG_USE_BOOTARGS=y +CONFIG_BOOTARGS="console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait" diff --git a/configs/fragments/common.fragment b/configs/fragments/common.fragment new file mode 100644 index 0000000..6e72b5f --- /dev/null +++ b/configs/fragments/common.fragment @@ -0,0 +1,27 @@ +# common.fragment — Buildroot policy shared by EVERY board and every kernel +# variant in this tree, including the kernel-only stack. Layered first; a +# later fragment must never redefine anything here +# (scripts/check-config-fragments.sh fails the build if one does). +# Rationale for every line, and why some "obviously shared" symbols are NOT +# here: docs/buildroot-config.md §2 and §10. + +# --- Toolchain: C++ (docs/buildroot-config.md §2.1) --- +BR2_TOOLCHAIN_BUILDROOT_CXX=y + +# --- Download integrity (docs/buildroot-config.md §2.2) --- +BR2_DOWNLOAD_FORCE_CHECK_HASHES=y + +# --- Kernel: pinned custom version, DTS support (docs/buildroot-config.md §2.3) --- +BR2_LINUX_KERNEL=y +BR2_LINUX_KERNEL_CUSTOM_VERSION=y +BR2_LINUX_KERNEL_DTS_SUPPORT=y + +# --- Reproducibility (docs/buildroot-config.md §2.4) --- +# WARNING: not cosmetic for the kernel-only stack either — linux.mk gates the +# KBUILD_BUILD_* stamps on it; without it a release re-run mints a different +# zImage_dtb-. Does NOT pin mke2fs's UUID/seed (§5.2 does, per image). +BR2_REPRODUCIBLE=y + +# --- Merged /usr (docs/buildroot-config.md §2.5) --- +# WARNING: load-bearing — Buildroot validates BR2_ROOTFS_OVERLAY's shape against it. +BR2_ROOTFS_MERGED_USR=y diff --git a/configs/fragments/de10nano-image.fragment b/configs/fragments/de10nano-image.fragment new file mode 100644 index 0000000..13ccda4 --- /dev/null +++ b/configs/fragments/de10nano-image.fragment @@ -0,0 +1,437 @@ +# de10nano-image.fragment — everything that makes the DE10-Nano stack the +# SHIPPED MiSTer image rather than a kernel-only build: board hooks, the +# ext4 linux.img contract, the full package set, and system configuration. +# Layered on common + de10nano (configs/fragments/stacks.mk). +# +# NOTHING in this file may touch the toolchain or the kernel stanza — those +# live in de10nano.fragment so the kernel-only stack shares them by +# construction (scripts/check-kernel-defconfig-sync.sh fails if a toolchain/ +# kernel family symbol appears here). Package selection is by design invisible +# to CI's toolchain-cache fingerprint (docs/ci.md#toolchain-fingerprint). +# +# The package list is docs/package-manifest.md §6 applied verbatim, plus the +# additions each section's doc entry records. Rationale for every line, with +# citations: docs/buildroot-config.md §5. Section numbers below refer to it. + +# --- Board hooks: post-build script, rootfs overlays (§5.1) --- +# WARNING: the SECOND overlay entry (work/extra-modules-overlay) is where +# kernel variants stage their module trees; `make all` mkdir -p's it because +# Buildroot hard-fails on a missing overlay path. Adding/changing this line +# busts the CI toolchain cache once (it is not BR2_PACKAGE_/BR2_LINUX_KERNEL). +BR2_ROOTFS_POST_BUILD_SCRIPT="../../board/mister/de10nano/post-build.sh" +BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/rootfs-overlay $(BR2_EXTERNAL_MISTER_PATH)/work/extra-modules-overlay" + +# --- Image generation: ext4 linux.img, reproducible (§5.2) --- +# WARNING: MKFS_OPTIONS pins the feature set, UUID, hash seed and block size +# explicitly — mke2fs defaults drift across e2fsprogs versions and the UUID/ +# seed are random otherwise. scripts/check-linux-img.sh asserts this contract. +# INODE_SIZE is an int symbol: no trailing comment allowed on that line. +BR2_TARGET_ROOTFS_EXT2=y +BR2_TARGET_ROOTFS_EXT2_4=y +BR2_TARGET_ROOTFS_EXT2_LABEL="rootfs" +BR2_TARGET_ROOTFS_EXT2_SIZE="512M" +BR2_TARGET_ROOTFS_EXT2_INODE_SIZE=256 +BR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS="-U 71916572-439f-448e-b8d8-12b0a032fa56 -E hash_seed=9afc615c-c310-4e03-ada9-613522e83ae6 -b 4096 -O has_journal,ext_attr,resize_inode,dir_index,filetype,extent,64bit,flex_bg,sparse_super,large_file,huge_file,dir_nlink,extra_isize,metadata_csum,^metadata_csum_seed,^orphan_file" + +################################################################################ +# P2.1 — Full package set (§5.3 for the preamble: manifest provenance, the +# imlib2 loader exception, BR2_ENABLE_LOCALE deliberately absent) +################################################################################ + +# --- compression (§5.4) --- +# WARNING: ZLIB_NG (zlib-ng in ZLIB_COMPAT mode) is the concrete zlib provider; +# MINIZIP is minizip-ng, MINIZIP_ZLIB is the classic zlib-contrib one — both. +BR2_PACKAGE_ZLIB=y +BR2_PACKAGE_ZLIB_NG=y +BR2_PACKAGE_BZIP2=y +BR2_PACKAGE_XZ=y +BR2_PACKAGE_LZO=y +BR2_PACKAGE_ZSTD=y +BR2_PACKAGE_MINIZIP=y +BR2_PACKAGE_MINIZIP_ZLIB=y + +# --- Main_MiSTer shared libs (§5.5, docs/main-shared-libs.md) --- +BR2_PACKAGE_LZMA_SDK=y +BR2_PACKAGE_LIBCHDR=y + +# --- graphics / fonts (§5.6) --- +# WARNING: the IMLIB2_* loaders are dlopen'd plugins — without them menu.png/ +# menu.jpg backgrounds silently fail to load with no DT_NEEDED signal. +BR2_PACKAGE_FREETYPE=y +BR2_PACKAGE_LIBPNG=y +BR2_PACKAGE_JPEG=y +BR2_PACKAGE_JPEG_TURBO=y +BR2_PACKAGE_TIFF=y +BR2_PACKAGE_GIFLIB=y +BR2_PACKAGE_IMLIB2=y +BR2_PACKAGE_IMLIB2_JPEG=y +BR2_PACKAGE_IMLIB2_PNG=y +BR2_PACKAGE_IMLIB2_GIF=y +BR2_PACKAGE_IMLIB2_TIFF=y +BR2_PACKAGE_IMLIB2_ID3=y +BR2_PACKAGE_LIBXKBCOMMON=y +BR2_PACKAGE_SDL2=y + +# --- audio (§5.7) --- +BR2_PACKAGE_ALSA_LIB=y +BR2_PACKAGE_LIBAO=y +BR2_PACKAGE_LIBVORBIS=y +BR2_PACKAGE_LIBOGG=y +BR2_PACKAGE_MPG123=y +BR2_PACKAGE_LIBID3TAG=y +BR2_PACKAGE_LIBMODPLUG=y +BR2_PACKAGE_FLUIDSYNTH=y +BR2_PACKAGE_FLUIDSYNTH_ALSA_LIB=y + +# --- MIDI / MT-32 (P3.8) + ALSA CLI tools (P3.15) (§5.8, docs/midi-mt32-parity.md) --- +BR2_PACKAGE_MUNT=y +BR2_PACKAGE_MIDILINK=y +BR2_PACKAGE_ALSA_UTILS=y +BR2_PACKAGE_ALSA_UTILS_ACONNECT=y +BR2_PACKAGE_ALSA_UTILS_AMIDI=y +BR2_PACKAGE_ALSA_UTILS_APLAYMIDI=y +BR2_PACKAGE_ALSA_UTILS_ARECORDMIDI=y +BR2_PACKAGE_ALSA_UTILS_ASEQDUMP=y +BR2_PACKAGE_ALSA_UTILS_ASEQNET=y +BR2_PACKAGE_ALSA_UTILS_ALSACTL=y +BR2_PACKAGE_ALSA_UTILS_ALSALOOP=y +BR2_PACKAGE_ALSA_UTILS_ALSAMIXER=y +BR2_PACKAGE_ALSA_UTILS_ALSATPLG=y +BR2_PACKAGE_ALSA_UTILS_ALSAUCM=y +BR2_PACKAGE_ALSA_UTILS_AMIXER=y +BR2_PACKAGE_ALSA_UTILS_APLAY=y +BR2_PACKAGE_ALSA_UTILS_BAT=y +BR2_PACKAGE_ALSA_UTILS_IECSET=y +BR2_PACKAGE_ALSA_UTILS_SPEAKER_TEST=y + +# --- crypto / TLS (§5.9) --- +# nettle/gmp/libtasn1/libgpg-error/libffi arrive transitively — do not set. +BR2_PACKAGE_OPENSSL=y +BR2_PACKAGE_LIBOPENSSL=y +BR2_PACKAGE_GNUTLS=y +BR2_PACKAGE_LIBGCRYPT=y +BR2_PACKAGE_LIBSSH2=y +BR2_PACKAGE_CA_CERTIFICATES=y + +# --- networking / D-Bus / GLib (§5.10) --- +# GNU wget (issue #130): the BusyBox applet is disabled in busybox.fragment. +BR2_PACKAGE_LIBCURL=y +BR2_PACKAGE_LIBCURL_CURL=y +BR2_PACKAGE_LIBCURL_OPENSSL=y +BR2_PACKAGE_WGET=y +BR2_PACKAGE_DBUS=y +BR2_PACKAGE_DBUS_CPP=y +BR2_PACKAGE_DBUS_GLIB=y +BR2_PACKAGE_LIBEVENT=y +BR2_PACKAGE_LIBNL=y +BR2_PACKAGE_IPTABLES=y +BR2_PACKAGE_LIBGLIB2=y +BR2_PACKAGE_GOBJECT_INTROSPECTION=y + +# --- util-linux / e2fsprogs / disk & fs tools (§5.11, docs/util-linux-parity.md) --- +# WARNING: each util-linux lib sub-option defaults to n — list them or the +# SONAME is not built. `raw` is deliberately absent (unbuildable on >=5.14). +BR2_PACKAGE_UTIL_LINUX=y +BR2_PACKAGE_UTIL_LINUX_LIBBLKID=y +BR2_PACKAGE_UTIL_LINUX_LIBFDISK=y +BR2_PACKAGE_UTIL_LINUX_LIBMOUNT=y +BR2_PACKAGE_UTIL_LINUX_LIBSMARTCOLS=y +BR2_PACKAGE_UTIL_LINUX_LIBUUID=y +BR2_PACKAGE_UTIL_LINUX_BINARIES=y +BR2_PACKAGE_UTIL_LINUX_MOUNT=y +BR2_PACKAGE_UTIL_LINUX_MOUNTPOINT=y +BR2_PACKAGE_UTIL_LINUX_AGETTY=y +BR2_PACKAGE_UTIL_LINUX_HWCLOCK=y +BR2_PACKAGE_UTIL_LINUX_FSCK=y +BR2_PACKAGE_UTIL_LINUX_PARTX=y +BR2_PACKAGE_UTIL_LINUX_SCHEDUTILS=y +BR2_PACKAGE_UTIL_LINUX_IRQTOP=y +BR2_PACKAGE_UTIL_LINUX_KILL=y +BR2_PACKAGE_UTIL_LINUX_MORE=y +BR2_PACKAGE_UTIL_LINUX_NEWGRP=y +BR2_PACKAGE_UTIL_LINUX_NOLOGIN=y +BR2_PACKAGE_UTIL_LINUX_RENAME=y +BR2_PACKAGE_UTIL_LINUX_SETTERM=y +BR2_PACKAGE_UTIL_LINUX_SWITCH_ROOT=y +BR2_PACKAGE_E2FSPROGS=y +BR2_PACKAGE_PARTED=y +BR2_PACKAGE_NTFS_3G=y +BR2_PACKAGE_NTFS_3G_NTFSPROGS=y +BR2_PACKAGE_KMOD=y +BR2_PACKAGE_INOTIFY_TOOLS=y +BR2_PACKAGE_JQ=y +BR2_PACKAGE_EXPAT=y +BR2_PACKAGE_POPT=y +BR2_PACKAGE_READLINE=y +BR2_PACKAGE_NCURSES=y +# WARNING: NCURSES_WCHAR is an ABI-contract fix (wide libncursesw.so.6, what +# stock ships); narrow -> wide is a clean-rebuild change. +BR2_PACKAGE_NCURSES_WCHAR=y +BR2_PACKAGE_SLANG=y +BR2_PACKAGE_NEWT=y +BR2_PACKAGE_GPM=y +BR2_PACKAGE_LIBARCHIVE=y +BR2_PACKAGE_LIBFUSE=y + +# --- USB / input (§5.12, docs/usb-automount-parity.md) --- +BR2_PACKAGE_LIBUSB=y +BR2_PACKAGE_LIBUSB_COMPAT=y +BR2_PACKAGE_LIBEVDEV=y +BR2_PACKAGE_LIBINPUT=y +BR2_PACKAGE_MTDEV=y +BR2_PACKAGE_USBMOUNT=y + +# --- Bluetooth (§5.13) --- +# WARNING: DEPRECATED depends on CLIENT || TOOLS — silently dropped without them. +BR2_PACKAGE_BLUEZ5_UTILS=y +BR2_PACKAGE_BLUEZ5_UTILS_CLIENT=y +BR2_PACKAGE_BLUEZ5_UTILS_TOOLS=y +BR2_PACKAGE_BLUEZ5_UTILS_DEPRECATED=y +BR2_PACKAGE_BLUEZ5_UTILS_PLUGINS_SIXAXIS=y + +# --- PAM / capabilities (§5.14) --- +BR2_PACKAGE_LINUX_PAM=y +BR2_PACKAGE_LIBCAP=y +BR2_PACKAGE_LIBCAP_NG=y + +# --- misc small libraries / tools (§5.15) --- +# WARNING: BUSYBOX_SHOW_OTHERS gates I2C_TOOLS and LSOF; the EUDEV device- +# creation choice gates EUDEV, LIBINPUT and BLUEZ5 SIXAXIS — kconfig drops all +# of them silently without these lines. +BR2_PACKAGE_DTC=y +BR2_PACKAGE_DTC_PROGRAMS=y +BR2_PACKAGE_SUDO=y +BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y +BR2_PACKAGE_I2C_TOOLS=y +BR2_PACKAGE_JIMTCL=y +BR2_PACKAGE_LIBLOCKFILE=y +BR2_PACKAGE_LIBXML2=y +BR2_PACKAGE_FILE=y +BR2_PACKAGE_MEMTOOL=y +BR2_PACKAGE_PCRE2=y +BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y +BR2_PACKAGE_EUDEV=y + +# --- lftp (§5.16) --- +BR2_PACKAGE_LFTP=y + +# --- Python (A6 / P3.9) + btctl runtime (§5.17, docs/python-compat.md) --- +# WARNING: SSL + ZLIB are hard blockers for Downloader_MiSTer. +BR2_PACKAGE_PYTHON3=y +BR2_PACKAGE_PYTHON3_SSL=y +BR2_PACKAGE_PYTHON3_ZLIB=y +BR2_PACKAGE_PYTHON3_BZIP2=y +BR2_PACKAGE_PYTHON3_XZ=y +BR2_PACKAGE_PYTHON3_PYEXPAT=y +BR2_PACKAGE_PYTHON3_READLINE=y +BR2_PACKAGE_PYTHON3_CURSES=y +BR2_PACKAGE_DBUS_PYTHON=y +BR2_PACKAGE_PYTHON_GOBJECT=y + +# --- Samba: standalone file server only (§5.18) --- +BR2_PACKAGE_SAMBA4=y + +# --- daemons / user-facing binaries (§5.19, docs/ssh-ftp-parity.md, docs/wifi-parity.md) --- +# WARNING: OPENSSH_SANDBOX must stay off — CONFIG_SECCOMP=n in our kernel and +# openssh >= 10.4 makes a failed seccomp setup FATAL for every connection. +# Configure-time flag: `make openssh-dirclean` before rebuilding after a change. +BR2_PACKAGE_OPENSSH=y +# BR2_PACKAGE_OPENSSH_SANDBOX is not set +BR2_PACKAGE_PROFTPD=y +BR2_PACKAGE_WPA_SUPPLICANT=y +BR2_PACKAGE_WPA_SUPPLICANT_NL80211=y +BR2_PACKAGE_WPA_SUPPLICANT_WEXT=y +BR2_PACKAGE_WPA_SUPPLICANT_DEBUG_SYSLOG=y +BR2_PACKAGE_WPA_SUPPLICANT_WPA3=y +BR2_PACKAGE_WPA_SUPPLICANT_CLI=y +BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y +BR2_PACKAGE_BASH=y +BR2_PACKAGE_DIALOG=y +BR2_PACKAGE_WIRELESS_TOOLS=y +BR2_PACKAGE_WIRELESS_TOOLS_IWCONFIG=y +BR2_PACKAGE_IW=y +BR2_PACKAGE_IPROUTE2=y + +# --- editors, ifupdown, BusyBox config fragment, dhcpcd, ntp, cifs (§5.20) --- +BR2_PACKAGE_JOE=y +BR2_PACKAGE_NANO=y +BR2_PACKAGE_IFUPDOWN=y +BR2_PACKAGE_BUSYBOX_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/busybox.fragment" +BR2_PACKAGE_DHCPCD=y +BR2_PACKAGE_NTP=y +BR2_PACKAGE_CIFS_UTILS=y + +# --- Midnight Commander (T3, §5.21) --- +BR2_PACKAGE_MC=y + +# --- NFS client userland (ADR 0022, §5.22) --- +# WARNING: RPC_NFSD is `default y` upstream and its LINUX_CONFIG_FIXUPS would +# flip CONFIG_NFSD on in our kernel — the not-set line is load-bearing. +BR2_PACKAGE_NFS_UTILS=y +BR2_PACKAGE_NFS_UTILS_NFSV4=y +# BR2_PACKAGE_NFS_UTILS_RPC_NFSD is not set +# BR2_PACKAGE_LVM2_STANDARD_INSTALL is not set + +# --- rsync, BusyBox (§5.23) --- +BR2_PACKAGE_RSYNC=y +BR2_PACKAGE_BUSYBOX=y + +# --- Realtek USB WiFi: mainline-first out-of-tree driver policy (§5.24, docs/wifi-parity.md) --- +# WARNING: exactly ONE OOT fork is on (rtl8852cu-morrownr, no mainline USB +# driver exists). Every other chip is driven in-kernel; enabling a fork too +# would bind-fight on the same USB IDs. +# BR2_PACKAGE_RTL8812AU is not set +# BR2_PACKAGE_RTL8821AU_MORROWNR is not set +# BR2_PACKAGE_RTL8188EU_AIRCRACK_NG is not set +# BR2_PACKAGE_RTL8188FU is not set +# BR2_PACKAGE_RTL8821CU_MORROWNR is not set +# BR2_PACKAGE_RTL88X2BU is not set +BR2_PACKAGE_RTL8852CU_MORROWNR=y + +# --- xone: Xbox One/Series accessory driver + dongle firmware (P3.2, ADR 0003, §5.25) --- +BR2_PACKAGE_XONE=y +BR2_PACKAGE_XOW_FIRMWARE=y + +# --- dualsensectl (§5.26, docs/dualsense-tooling.md) --- +# NOTE: pulls BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_COPY on via hidapi — deliberate +# (stock ships gconv); scripts/ci-tests.sh asserts the modules are present. +BR2_PACKAGE_DUALSENSECTL=y + +# --- ltunify: Logitech Unifying pairing (§5.27, docs/logitech-pairing.md) --- +BR2_PACKAGE_LTUNIFY=y + +# --- /lib/firmware population (P3.3, §5.28, docs/firmware-parity.md) --- +# WARNING: changing linux-firmware SUB-options on an incremental build installs +# nothing (Buildroot stamping) — `make linux-firmware-dirclean` first. +BR2_PACKAGE_LINUX_FIRMWARE=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7601U=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7610E=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7650=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT76X2E=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7921=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7925=y +BR2_PACKAGE_LINUX_FIRMWARE_RALINK_RT2XX=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_81XX=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_88XX_BT=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW89=y +BR2_PACKAGE_LINUX_FIRMWARE_ATHEROS_9271=y +BR2_PACKAGE_LINUX_FIRMWARE_ATHEROS_7010=y +BR2_PACKAGE_LINUX_FIRMWARE_ATHEROS_9170=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7921_BT=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7922_BT=y +BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7925_BT=y +BR2_PACKAGE_LINUX_FIRMWARE_QUALCOMM_6174A_BT=y +BR2_PACKAGE_LINUX_FIRMWARE_ATHEROS_6004=y +BR2_PACKAGE_LINUX_FIRMWARE_REDPINE_RS9113=y +BR2_PACKAGE_LINUX_FIRMWARE_REDPINE_RS9116=y +BR2_PACKAGE_LINUX_FIRMWARE_AR3011=y +BR2_PACKAGE_LINUX_FIRMWARE_AR3012_USB=y +BR2_PACKAGE_LINUX_FIRMWARE_BRCM_BCM43XX=y +BR2_PACKAGE_LINUX_FIRMWARE_BRCM_BCM43XXX=y +BR2_PACKAGE_WIRELESS_REGDB=y +BR2_PACKAGE_LINUX_FIRMWARE_EXTRA=y +BR2_PACKAGE_BCM20702_FIRMWARE=y + +# --- explicitly NOT carried forward: manifest §5 Drop list (§5.29) — a note, no config --- + +# --- kmod: target tools (§5.30; the HOST xz half is in de10nano.fragment) --- +BR2_PACKAGE_KMOD_TOOLS=y + +################################################################################ +# T5 — utility binaries stock ships (2026-07-27). Preamble, rejected list +# (perl, vim, screen, gdb, ltrace, unrar): §5.31. +################################################################################ + +# --- T5: process / file / syscall inspection (§5.32) --- +BR2_PACKAGE_HTOP=y +BR2_PACKAGE_STRACE=y +BR2_PACKAGE_LSOF=y + +# --- T5: USB / input / joystick & force-feedback (§5.33) --- +BR2_PACKAGE_USBUTILS=y +BR2_PACKAGE_EVTEST=y +BR2_PACKAGE_LINUXCONSOLETOOLS=y +BR2_PACKAGE_LINUXCONSOLETOOLS_JOYSTICK=y +BR2_PACKAGE_LINUXCONSOLETOOLS_FORCEFEEDBACK=y + +# --- T5: filesystem tools, FAT/exFAT (§5.34) --- +# WARNING: dosfstools' three programs each default to n — all three needed. +BR2_PACKAGE_DOSFSTOOLS=y +BR2_PACKAGE_DOSFSTOOLS_FATLABEL=y +BR2_PACKAGE_DOSFSTOOLS_FSCK_FAT=y +BR2_PACKAGE_DOSFSTOOLS_MKFS_FAT=y +BR2_PACKAGE_EXFATPROGS=y + +# --- T5: serial / terminal (§5.35) --- +BR2_PACKAGE_PICOCOM=y +BR2_PACKAGE_LRZSZ=y +BR2_PACKAGE_TMUX=y + +# --- T5: network diagnostics (§5.36) --- +BR2_PACKAGE_ETHTOOL=y +BR2_PACKAGE_SOCAT=y +BR2_PACKAGE_TCPDUMP=y +BR2_PACKAGE_IPERF3=y + +# --- T5: archival — package/7zip (OURS), not upstream p7zip (ADR 0023, §5.37) --- +BR2_PACKAGE_7ZIP=y +BR2_PACKAGE_ZIP=y +BR2_PACKAGE_LZOP=y + +# --- Off-device backup: azcopy — packaged, NOT enabled, size only (§5.38, docs/azcopy.md) --- +# BR2_PACKAGE_AZCOPY is not set + +# --- T5: hardware buses (§5.39) --- +BR2_PACKAGE_SPI_TOOLS=y + +# --- T5: Bluetooth CLI (§5.40) --- +BR2_PACKAGE_BLUEZ_TOOLS=y + +# --- T5: console / keyboard (§5.41) --- +BR2_PACKAGE_KBD=y + +################################################################################ +# >>> DEBUG TOOLING — TEMPORARY, REMOVE AS ONE BLOCK <<< (§5.42, docs/debug-tooling.md) +# TO REVERT: delete this block (banner to banner) and the matching +# CONFIG_COREDUMP block in board/mister/de10nano/linux.config, then +# make de10nano-defconfig && make all +# WARNING: do not add numactl/mpfr lines here — they arrive by select and must +# disappear with this block. GDB_SERVER must stay explicit next to GDB_DEBUGGER. +# strace is NOT in this block any more: T5 (§5.32) made it permanent, and a +# second BR2_PACKAGE_STRACE=y line here would be a redefinition +# check-config-fragments.sh rejects. +################################################################################ +BR2_PACKAGE_GDB=y +BR2_PACKAGE_GDB_SERVER=y +BR2_PACKAGE_GDB_DEBUGGER=y +BR2_PACKAGE_LINUX_TOOLS_PERF=y +BR2_PACKAGE_LINUX_TOOLS_PERF_NEEDS_HOST_PYTHON3=y +BR2_PACKAGE_RT_TESTS=y +################################################################################ +# >>> END DEBUG TOOLING <<< +################################################################################ + +################################################################################ +# System configuration (§5.43) — the System configuration menu, NOT the +# toolchain menu (so no from-scratch-rebuild hazard). Merged /usr lives in +# common.fragment. +################################################################################ + +# --- root password: empty = passwordless root, matching stock (§5.43, ADR 0015) --- +BR2_TARGET_GENERIC_ROOT_PASSWD="" + +# --- locale data (§5.44) --- +# WARNING: BR2_ENABLE_LOCALE (toolchain menu, already y) only compiles locale +# SUPPORT; this line GENERATES the data — empty shipped no /usr/lib/locale. +BR2_GENERATE_LOCALE="en_US.UTF-8" + +# --- timezone / tzdata (§5.45) --- +# WARNING: BR2_TARGET_LOCALTIME installs /etc/localtime as a symlink into +# zoneinfo — the rootfs-overlay overwrites it to point at the FAT data +# partition, which is the only thing that survives a reflash (ADR 0025). +BR2_TARGET_TZ_INFO=y +BR2_TARGET_TZ_ZONELIST="default" +BR2_TARGET_LOCALTIME="Etc/UTC" diff --git a/configs/fragments/de10nano.fragment b/configs/fragments/de10nano.fragment new file mode 100644 index 0000000..8cde3d1 --- /dev/null +++ b/configs/fragments/de10nano.fragment @@ -0,0 +1,43 @@ +# de10nano.fragment — the DE10-Nano BOARD layer: arch/ABI, toolchain headers +# series, and the kernel stanza. Shared by the full image stack +# (+ de10nano-image) and the kernel-only stack (+ kernel-only) — see +# configs/fragments/stacks.mk. Everything a kernel variant must agree with +# the shipped image on lives HERE and nowhere else. +# Rationale for every line: docs/buildroot-config.md §3. + +# --- Arch / ABI: armv7-a Cortex-A9, NEON + VFPv3, EABIhf (docs/buildroot-config.md §3.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 (docs/buildroot-config.md §3.2) --- +# WARNING: do NOT "fix" this to BR2_KERNEL_HEADERS_AS_KERNEL — that silently +# drops glibc's --enable-kernel floor to 2.6. Re-diff include/uapi on ANY +# kernel or Buildroot bump (a Buildroot bump moves the series' point release). +BR2_KERNEL_HEADERS_6_18=y + +# --- Global patch dir = the kernel-tarball hash registry (docs/buildroot-config.md §3.3) --- +# WARNING: load-bearing even with no packages — it is where Buildroot finds +# patches/linux/linux.hash, the ONLY thing that hash-verifies the pinned +# kernel tarball; kernel-only variants need it too. +BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/patches" + +# --- Kernel stanza (docs/buildroot-config.md §3.4) --- +# Renovate bumps the version line (renovate.json, kernel-longterm-6.18); +# the tarball hash follows via .github/workflows/renovate-hash-sync.yml. +BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.49" +BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux-patches" +BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y +BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux.config" +BR2_LINUX_KERNEL_LZ4=y +BR2_LINUX_KERNEL_ZIMAGE=y +BR2_LINUX_KERNEL_INTREE_DTS_NAME="intel/socfpga/socfpga_cyclone5_de10nano" + +# --- Host kmod with xz: build-time depmod must read .ko.xz (docs/buildroot-config.md §3.5) --- +BR2_PACKAGE_HOST_KMOD_XZ=y + +# --- Post-image: zImage_dtb assembly + contract check (docs/buildroot-config.md §3.6) --- +# Path is relative to work/buildroot/ (post-image scripts run from there). +BR2_ROOTFS_POST_IMAGE_SCRIPT="../../board/mister/de10nano/post-image.sh" diff --git a/configs/fragments/de25nano.fragment b/configs/fragments/de25nano.fragment new file mode 100644 index 0000000..98199fa --- /dev/null +++ b/configs/fragments/de25nano.fragment @@ -0,0 +1,129 @@ +# de25nano.fragment — D2.1: a BARE DEVELOPER OS for the Terasic DE25-Nano +# (Intel/Altera Agilex 5, HPS = 2x Cortex-A76 + 2x Cortex-A55, aarch64). +# NO MiSTer packages, NO DE10 packages — that is the accepted release scope +# (ADR 0027 Decision 6, ADR 0029, docs/de25-nano-tasks.md D2.7), not a gap. +# Layered on common only (configs/fragments/stacks.mk, DE25NANO_FRAGMENTS); +# no BR2_ symbol here is shared with the DE10 stacks except through that common +# layer. Two FILES are shared by path — the kernel-tarball hash registry (§6.3) +# and the MiSTer kernel-config fragment (§6.5) — and each says so at its line. +# Rationale for every line: docs/buildroot-config.md §6. + +# --- Architecture & toolchain (docs/buildroot-config.md §6.2) --- +# WARNING: BR2_cortex_a76_a55 is the big.LITTLE tuning tuple — do NOT +# "simplify" it to BR2_cortex_a76. No NEON/VFP knobs exist on AArch64. +BR2_aarch64=y +BR2_cortex_a76_a55=y +# WARNING: headers SERIES pin, same trap as the DE10's (§3.2). 7_0 is the +# newest series Buildroot 2026.05.2 offers below our 7.2 kernel; re-check on +# every Buildroot bump and move to a 7.2 series the day one exists. +BR2_KERNEL_HEADERS_7_0=y + +# --- Download integrity (docs/buildroot-config.md §6.3) --- +# WARNING: patches/linux/linux.hash here is a SYMLINK to the DE10's hash +# registry — bumping the kernel below means editing that de10nano file. +BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/patches" + +# --- Kernel: mainline 7.2.3 (docs/buildroot-config.md §6.4) --- +# WARNING: do NOT point PATCH at the DE10's linux-patches/ — 4 of the 40 differ +# in content between the DE10's main and beta series alone, and none has been +# compile-tested on aarch64/7.2; this directory takes only what D0.3 triaged. +BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="7.2.3" +BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/linux-patches" + +# --- Kernel config = pinned minimal base + shared MiSTer fragment; Image; board DTS (docs/buildroot-config.md §6.5) --- +# WARNING: USE_CUSTOM_CONFIG, not USE_DEFCONFIG (that one would build +# `defconfig_defconfig`). No stage-1 initramfs on this board by design. +# WARNING: the wave-1 arm64-`defconfig` base is stated OFF, not just dropped, so +# the change of shape is visible here rather than inferable from an absence. +# WARNING: the fragment file is SHARED WITH THE DE10 BY PATH and must stay a +# no-op against the DE10's resolved config — scripts/check-kernel-fragment-noop.sh. +# BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG is not set +BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y +BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/linux.config" +BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/common/linux-mister.fragment" +# WARNING: the shared fragment sets CONFIG_MODULE_COMPRESS_XZ, so build-time +# depmod needs host kmod built with xz or it silently ships an EMPTY +# modules.dep (0 lines; found on the first wave-2 card). Same trap as the +# DE10 -- docs/buildroot-config.md §3.5. +BR2_PACKAGE_HOST_KMOD_XZ=y +BR2_LINUX_KERNEL_IMAGE=y +BR2_LINUX_KERNEL_CUSTOM_DTS_PATH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/socfpga_agilex5_de25nano.dts" + +# --- Root filesystem: plain ext4, 256 MiB (docs/buildroot-config.md §6.6) --- +BR2_TARGET_ROOTFS_EXT2=y +BR2_TARGET_ROOTFS_EXT2_4=y +BR2_TARGET_ROOTFS_EXT2_LABEL="rootfs" +BR2_TARGET_ROOTFS_EXT2_SIZE="256M" + +# --- System configuration (docs/buildroot-config.md §6.7) --- +# WARNING: the console is HPS uart1 = ttyS0 at 115200 8N1; wrong values here +# make the board look dead. +BR2_TARGET_GENERIC_GETTY_PORT="ttyS0" +BR2_TARGET_GENERIC_GETTY_BAUDRATE_115200=y +BR2_TARGET_GENERIC_HOSTNAME="de25" +BR2_TARGET_GENERIC_ISSUE="Welcome to MiSTer DE25-Nano (developer OS)" +# Empty = passwordless root; on this board the only way in (no SSH keys yet). +BR2_TARGET_GENERIC_ROOT_PASSWD="" + +# --- Packages: none, deliberately (docs/buildroot-config.md §6.8) --- +# WARNING: the day BR2_PACKAGE_OPENSSH=y is added here it must be added +# TOGETHER WITH `# BR2_PACKAGE_OPENSSH_SANDBOX is not set` — this board's +# linux.config has no CONFIG_SECCOMP, and openssh's sandbox is `default y`, +# which yields an sshd that listens and kills every connection preauth +# (docs/buildroot-config.md §6.8, docs/de25-kernel-config.md). + +# --- Bootloader: ATF BL31 + mainline U-Boot -> u-boot.itb (docs/buildroot-config.md §6.9) --- +# WARNING: TF-A v2.15.0 and U-Boot 2026.07 are CUSTOM versions because +# Buildroot 2026.05.2 offers neither an Agilex 5 TF-A platform nor a new enough +# U-Boot. BUILD_SYSTEM_KCONFIG is NOT optional on a custom version — it +# defaults to KCONFIG only under BR2_TARGET_UBOOT_LATEST_VERSION. +BR2_TARGET_ARM_TRUSTED_FIRMWARE=y +BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_VERSION=y +BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_VERSION_VALUE="v2.15.0" +BR2_TARGET_ARM_TRUSTED_FIRMWARE_PLATFORM="agilex5" +BR2_TARGET_ARM_TRUSTED_FIRMWARE_BL31=y +BR2_TARGET_ARM_TRUSTED_FIRMWARE_IMAGES="bl31.bin" +BR2_TARGET_UBOOT=y +BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y +BR2_TARGET_UBOOT_CUSTOM_VERSION=y +BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2026.07" +BR2_TARGET_UBOOT_USE_DEFCONFIG=y +BR2_TARGET_UBOOT_BOARD_DEFCONFIG="socfpga_agilex5" +# WARNING: the whole U-Boot delta — including the traps that keep a QSPI write +# structurally impossible (CONFIG_ENV_IS_IN_UBI off) and the factory SPL's +# bloblist out of our dram_init (CONFIG_HANDOFF / CONFIG_BLOBLIST off) — lives +# in uboot.fragment, line by line. Read it before touching this stanza. +BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/uboot.fragment" +# WARNING: both files travel together — U-Boot auto-includes the -u-boot.dtsi +# BY NAME, and the U-Boot board file shares a basename with the KERNEL board +# file, which is why they live in uboot-dts/ and not beside it. +BR2_TARGET_UBOOT_CUSTOM_DTS_PATH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts $(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano-u-boot.dtsi" +BR2_TARGET_UBOOT_NEEDS_ATF_BL31=y +BR2_TARGET_UBOOT_NEEDS_ATF_BL31_BIN=y +BR2_TARGET_UBOOT_USE_BINMAN=y +BR2_TARGET_UBOOT_NEEDS_OPENSSL=y +# WARNING: NO SPL IS SHIPPED (no BR2_TARGET_UBOOT_SPL). The FSBL is the factory +# U-Boot SPL in QSPI and is NEVER touched — posture 1, brick-class. The SPL is +# nevertheless COMPILED, because `select BINMAN if SPL_ATF` is the only thing +# that turns BINMAN on and BINMAN is what produces u-boot.itb. +BR2_TARGET_UBOOT_FORMAT_ITB=y +# FORMAT_BIN is `default y`; off so images/ holds only what goes on the card. +# BR2_TARGET_UBOOT_FORMAT_BIN is not set + +# --- Host tools: dumpimage/mkimage, WITH FIT support (docs/buildroot-config.md §6.10) --- +# WARNING: FIT_SUPPORT is NOT `default y`, and without it `dumpimage -l +# u-boot.itb` prints nothing and exits 0 — a verification step that always +# passes. Found the hard way on the first build. +BR2_PACKAGE_HOST_UBOOT_TOOLS=y +BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y + +# --- SD-card image: p1 FAT32 + p2 ext4, assembled by post-image.sh (docs/buildroot-config.md §6.11) --- +# WARNING: host-genimage pulls in NEITHER mtools NOR dosfstools, and genimage's +# vfat handler shells out to `mcopy`/`mkdosfs` by name — without these two the +# card build fails inside genimage, not at configure time. +BR2_PACKAGE_HOST_GENIMAGE=y +BR2_PACKAGE_HOST_MTOOLS=y +BR2_PACKAGE_HOST_DOSFSTOOLS=y +# WARNING: fails the build when images/u-boot.itb is missing, BY DESIGN; +# DE25_ALLOW_NO_UBOOT=1 in the environment downgrades it to a loud skip. +BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/post-image.sh" diff --git a/configs/fragments/golden.sha256 b/configs/fragments/golden.sha256 new file mode 100644 index 0000000..500a85a --- /dev/null +++ b/configs/fragments/golden.sha256 @@ -0,0 +1,10 @@ +# configs/fragments/golden.sha256 — sha256 of each stack's NORMALISED resolved +# .config for the pinned Buildroot version (scripts/check-config-fragments.sh +# (d); docs/buildroot-config.md §11). Regenerate ONLY with +# scripts/check-config-fragments.sh --update-golden +# and say in the commit message what changed and why. Columns: +# +2026.05.2 de10nano 3cb8e3d9b32d701c4e3eefa1b9993da9b76a73a29a3b29f57924c10b807138a1 +2026.05.2 de10nano-kernel 5b372abe43c46c4eb5f3c88d51e48ff2eb6fa713b3c02f0f337c39d0f111dc69 +2026.05.2 de25nano 2adb6b4029bee4467053403fdcf290042ccd2b554cc126de9a2f7a155b433b28 +2026.05.2 rt e34a522a56eb088ac84b1e6db7350a22f12293d0d84086dbf1f405f68a58b7e5 diff --git a/configs/fragments/kernel-only.fragment b/configs/fragments/kernel-only.fragment new file mode 100644 index 0000000..22ebcd0 --- /dev/null +++ b/configs/fragments/kernel-only.fragment @@ -0,0 +1,17 @@ +# kernel-only.fragment — turns a board stack into the KERNEL-ONLY base a +# kernel variant (`make rt`, CI's build-kernel legs) builds against: no init, +# no shell, no BusyBox, rootfs-tar only. Layered on common + de10nano +# (configs/fragments/stacks.mk, DE10NANO_KERNEL_FRAGMENTS); a variant's own +# configs/mister_.fragment is merged on top of that by the Makefile. +# This is NOT a second image. Rationale: docs/buildroot-config.md §4. + +# --- No init, no shell, no BusyBox (docs/buildroot-config.md §4.1) --- +# WARNING: all three lines are needed; BusyBox is `default y` on its own. +BR2_INIT_NONE=y +BR2_SYSTEM_BIN_SH_NONE=y +# BR2_PACKAGE_BUSYBOX is not set + +# --- Rootfs: tar, and ONLY tar (docs/buildroot-config.md §4.2) --- +# WARNING: drop this and depmod never runs — the module tree ships without +# modules.dep/modules.alias. The tar is also CI's module-transport artifact. +BR2_TARGET_ROOTFS_TAR=y diff --git a/configs/fragments/stacks.mk b/configs/fragments/stacks.mk new file mode 100644 index 0000000..4c66b31 --- /dev/null +++ b/configs/fragments/stacks.mk @@ -0,0 +1,17 @@ +# configs/fragments/stacks.mk — the ONE place that says which fragments make +# up which Buildroot configuration. Included by the top-level Makefile and +# parsed (as plain `NAME := words` lines) by scripts/check-config-fragments.sh +# and scripts/check-kernel-defconfig-sync.sh. Keep it to that shape: one +# `_FRAGMENTS := ...` line per stack, names without the +# `.fragment` suffix, in merge order (later fragments layer on earlier ones). +# +# See docs/buildroot-config.md §1 for the mechanism and §10 for why each +# symbol lives where it does. +# +# The de10nano and de10nano-kernel stacks share `common` and `de10nano` BY +# CONSTRUCTION — that is what keeps the kernel-only base (used by `make rt` +# and every CI kernel leg) in lockstep with the shipped image without a +# mirrored copy. scripts/check-kernel-defconfig-sync.sh asserts this. +DE10NANO_FRAGMENTS := common de10nano de10nano-image +DE10NANO_KERNEL_FRAGMENTS := common de10nano kernel-only +DE25NANO_FRAGMENTS := common de25nano diff --git a/configs/mister_de10nano_defconfig b/configs/mister_de10nano_defconfig deleted file mode 100644 index 073acc5..0000000 --- a/configs/mister_de10nano_defconfig +++ /dev/null @@ -1,1960 +0,0 @@ -# mister_de10nano_defconfig — P1.2: toolchain & arch/ABI + minimal BusyBox rootfs -# -# Supersedes the P1.1 placeholder (which deliberately set nothing). This is the -# canonical output of `make savedefconfig` — it only lists lines that diverge -# from Buildroot's kconfig defaults, so it is intentionally short (and, per the -# P1.1 placeholder's own warning, this header will be silently dropped the next -# time `make savedefconfig` runs — hand-restore it, same as this commit did). -# See docs/decisions/0001-toolchain.md for the full toolchain evaluation -# (internal vs Bootlin external) and PLAN.md §3 / docs/abi-contract.md §1 for -# where the arch/ABI requirements below come from. -# -# What this pins, and why: -# - arm / cortex-a9 / NEON / VFPv3 / EABIhf: reproduces the stock `MiSTer` -# binary's own readelf -A tags (abi-contract.md §1.1, T1-T4). -# BR2_ARM_FPU_NEON is the Buildroot FPU choice that reproduces -# "Tag_FP_arch: VFPv3" + "Tag_Advanced_SIMD_arch: NEONv1" together (gcc -# -mfpu=neon — NEON mandates the 32-register VFPv3 variant; cortex-a9 only -# select BR2_ARM_CPU_MAYBE_HAS_{NEON,VFPV3}, so ENABLE_NEON/ENABLE_VFP are -# both required or Buildroot silently falls back to a narrower FPU). -# EABIhf itself is NOT a line below because it is already Buildroot's -# default the moment a CPU with an FPU is selected (arch/Config.in.arm: -# `default BR2_ARM_EABIHF if BR2_ARM_CPU_HAS_FPU`) — savedefconfig -# correctly drops it as non-divergent; verified present via readelf in -# the P1.2 acceptance run. -# - glibc via the Buildroot-internal toolchain (not Bootlin, not -# musl/uClibc): see the ADR. Also not a line below — glibc is already the -# default C library for the internal toolchain (toolchain-buildroot's own -# `default BR2_TOOLCHAIN_BUILDROOT_GLIBC`) — musl is a non-goal (PLAN §3) -# and would not even start the stock binary (abi-contract.md §1.3). -# - BR2_KERNEL_HEADERS_6_18: pins the headers SERIES explicitly. This overrides -# Buildroot's own default of BR2_KERNEL_HEADERS_AS_KERNEL -# (package/linux-headers/Config.in.host:5), and the override is load-bearing. -# DO NOT "fix" it to AS_KERNEL to keep the headers in lockstep with the kernel -- -# verified by A/B-ing this defconfig through `make defconfig`: -# -# BR2_KERNEL_HEADERS_6_18=y -> BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.18" -# BR2_KERNEL_HEADERS_AS_KERNEL -> BR2_TOOLCHAIN_HEADERS_AT_LEAST="2.6" -# -# glibc is configured --enable-kernel=$(BR2_TOOLCHAIN_HEADERS_AT_LEAST) -# (package/glibc/glibc.mk:131). Under AS_KERNEL our kernel version arrives as the -# free-form string BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.49", which Kconfig -# cannot compare numerically to select BR2_TOOLCHAIN_HEADERS_AT_LEAST_6_18 -- so it -# silently falls back to the floor, 2.6. That would build glibc with ~15 years of -# dead compatibility code and runtime syscall-fallback paths for kernels this board -# will never run, and drop every 6.18-era fast path. The series pin is the only way -# Buildroot learns the headers version here. -# -# (An earlier version of this comment justified the pin with "this defconfig does not -# build a kernel itself" -- that was simply false, BR2_LINUX_KERNEL=y is set below. -# Right setting, wrong reason, which is how it nearly got reverted.) -# -# ACCEPTED CONSEQUENCE: the series resolves to whatever 6.18.x Buildroot pins for it -# (package/linux-headers/Config.in.host, the `default "6.18.34" if -# BR2_KERNEL_HEADERS_6_18` line -- currently 6.18.34, moved from 6.18.33 by the -# Buildroot 2026.02 -> 2026.05 bump) while the kernel is 6.18.40, so the headers lag -# the kernel slightly. That is correct and harmless: headers older than the running -# kernel is the supported direction, because the kernel's uapi is forward-compatible -# by guarantee. Re-checked 2026-07-25 for the 6.18.40 bump: the uapi delta between -# 6.18.34 and 6.18.40 is FOUR added lines across THREE files, all additive -- -# * include/uapi/linux/bpf.h two explicit `__u32 :32;` pads (bpf_prog_info, -# bpf_map_info) -# * include/uapi/linux/tee.h one explicit `__u32 :32;` pad -# * include/uapi/linux/if_link.h one new enum member, IFLA_BOND_LACP_STRICT, -# appended before __IFLA_BOND_MAX -# arch/arm/include/uapi/ is byte-identical. The `__u32 :32;` lines make padding the -# compiler was ALREADY inserting explicit (both structs are -# __attribute__((aligned(8)))), so they are not an ABI change at all; the bonding -# netlink attribute is additive and this board does not use bonding, tee or bpf -# uapi. Checked with -# `git diff v6.18.34 v6.18.40 -- include/uapi/ arch/arm/include/uapi/` -# (equivalently: diff the two extracted tarballs over those two paths). -# -# RE-CHECK THAT DIFF ON ANY BUMP, patchlevel included -- of the KERNEL or of -# BUILDROOT, since the 2026.05 bump moved the headers end of the range on its own. -# It is tempting to assume only a series bump can move uapi; every line above -# disproves it, since 6.18.34 -> 6.18.40 is itself a patchlevel range. Stable rules -# discourage uapi changes but do not forbid them, so the diff is the authority, not -# the version numbers. (This block was last found stale by Copilot review on PR #67: -# it still said 6.18.33/6.18.38 after the kernel had moved to 6.18.40 and Buildroot -# had moved the headers pin to 6.18.34 -- neither of which Renovate can rewrite, -# because both live in prose.) -# -# Also note linux-headers only applies BR2_LINUX_KERNEL_PATCH / BR2_GLOBAL_PATCH_DIR -# under AS_KERNEL (package/linux-headers/linux-headers.mk:82), so the series pin -# means our carried patches do not reach the headers tree. Verified irrelevant: no -# patch in board/mister/de10nano/linux-patches/ MODIFIES a header under include/uapi -# or arch/arm/include/uapi (checked against the patches' `+++ b/` target paths: zero -# hits). Some do mention uapi headers in prose -- 0001 cites include/uapi/linux/fb.h -# to explain an ioctl number -- but none change one; the series is drivers, one DTS, -# and fs/exfat. -# - BR2_TOOLCHAIN_BUILDROOT_CXX: libstdc++.so.6 is a *toolchain*-provided -# library, not a package (docs/abi-contract.md §2.2 rows L5/L6 say so -# explicitly), and T8 requires it to export GLIBCXX_3.4.21 + CXXABI_1.3.9. -# Main_MiSTer is C++, so this is non-optional for the project — and since -# it is a toolchain knob rather than a package it belongs here in P1.2 -# rather than in P2.1's package set. GCC 14.3 satisfies T8 with enormous -# margin (T8 needs only GCC >= 5.1); verified by readelf in the P1.2 -# acceptance run. -# - No rootfs *packages* beyond Buildroot's own defaults: this is -# deliberately BusyBox + the toolchain's own runtime libraries (libc, the -# post-2.34 libpthread/librt compat stubs, libstdc++ — installed by the -# toolchain, not by package selection). The ten DT_NEEDED *packages* -# (zlib, bzip2, libpng, freetype, imlib2, bluez — L7-L12) are P2.1's job, -# not this task's. -BR2_arm=y -BR2_cortex_a9=y -BR2_ARM_ENABLE_NEON=y -BR2_ARM_ENABLE_VFP=y -BR2_ARM_FPU_NEON=y -BR2_KERNEL_HEADERS_6_18=y -BR2_TOOLCHAIN_BUILDROOT_CXX=y - -# P1.11 (A3): assemble zImage_dtb (plain `cat zImage dtb`) and assert its -# U-Boot contract (scripts/check-zimage-dtb.sh) after every image build. Post- -# image scripts run with $(BR_DIR) (work/buildroot/) as CWD (system/Config.in: -# "executed from the main Buildroot source directory"), so this path is -# relative to THAT directory, not BR2_EXTERNAL -- verified against the working -# reference wrapper at /mnt/source/sb-enema (`../../sb_enema/board/sb-enema/ -# post-image.sh` from a BR_DIR one level deeper than ours). A build-time -# checker failure here fails `make all` (Buildroot's Makefile runs post-image -# scripts as plain recipe lines; a nonzero exit stops make). -BR2_ROOTFS_POST_BUILD_SCRIPT="../../board/mister/de10nano/post-build.sh" -BR2_ROOTFS_POST_IMAGE_SCRIPT="../../board/mister/de10nano/post-image.sh" -BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/patches" - -# 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_ROOTFS_OVERLAY, just pointed at the full-rootfs overlay tree instead of -# the initramfs one. -# -# The SECOND entry (ADR 0021 as amended 2026-07-18) is the gitignored -# work/extra-modules-overlay/, where kernel-variant builds stage their -# depmod'd usr/lib/modules// trees (`make rt` locally; CI's build-kernel -# artifacts) so the ONE shipped linux.img carries every variant's modules. -# Empty -> byte-identical image; the Makefile's `all` mkdir -p's it because -# Buildroot fails on a missing overlay path. -# NOTE: this line is part of the toolchain-fingerprint deny-list residue in -# .github/actions/buildroot-build (it is neither BR2_PACKAGE_ nor -# BR2_LINUX_KERNEL), so ADDING it busts the br-host cache exactly once — one -# deliberate ~3h cold main build on the PR that introduced it. -BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/rootfs-overlay $(BR2_EXTERNAL_MISTER_PATH)/work/extra-modules-overlay" -BR2_LINUX_KERNEL=y -BR2_LINUX_KERNEL_CUSTOM_VERSION=y -BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.49" -BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux-patches" -BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y -BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux.config" -BR2_LINUX_KERNEL_LZ4=y -BR2_LINUX_KERNEL_ZIMAGE=y -BR2_LINUX_KERNEL_DTS_SUPPORT=y -BR2_LINUX_KERNEL_INTREE_DTS_NAME="intel/socfpga/socfpga_cyclone5_de10nano" -BR2_DOWNLOAD_FORCE_CHECK_HASHES=y - -################################################################################ -# P2.5 — Image generation, reproducible (A9). -# -# Mechanism: BR2_TARGET_ROOTFS_EXT2 (ext4 variant), NOT genimage. The stock -# artifact (linux/linux.img) is a single loop-mounted ext4 filesystem with NO -# partition table -- our /init losetup's + mounts it directly (docs/boot- -# chain.md; confirmed on hardware in P1.13). genimage exists to assemble -# MULTI-PARTITION disk images (MBR/GPT, bootloader + several filesystems); -# here there is exactly one filesystem and nothing else to lay out, so it -# would add a whole config layer to produce byte-for-byte what -# BR2_TARGET_ROOTFS_EXT2 already produces directly -- Buildroot's own manual -# recommends going straight to the fs/ target when no partition table is -# needed, which is this case. post-image.sh (extended, not forked) hard-links -# the resulting output/images/rootfs.ext2 to output/images/linux.img (see -# that script for why rootfs.ext2, not the rootfs.ext4 convenience symlink -# Buildroot also creates, is the canonical name here). -# -# Size: 512 MiB (task text), not stock's 375 MiB. P2.1's full rootfs, once -# actually built into this ext4 image (with journal/inode-table/GDT -# overhead), uses ~198 MiB of the 512 -> measured 61.4% free -# (dumpe2fs -h: 80433 free / 131072 total blocks), comfortably above P2.7's -# 15% floor, with headroom for A8 (the updater mounts the NEW image rw and -# copies 5 user-files into /etc before flashing) and for future package -# growth without another resize. -BR2_TARGET_ROOTFS_EXT2=y -BR2_TARGET_ROOTFS_EXT2_4=y -BR2_TARGET_ROOTFS_EXT2_LABEL="rootfs" # stock parity -- some tooling - # keys on the volume label. - # Kconfig's own default is - # already "rootfs"; explicit - # here so a future Buildroot - # default change can't - # silently change it under us. -BR2_TARGET_ROOTFS_EXT2_SIZE="512M" -# Buildroot's own default (256). DELIBERATE divergence from stock's 128: a -# 128-byte inode hits the Y2K38 timestamp problem (mke2fs(8) says so -# explicitly). Costs us nothing -- inode_size is invisible to the mount/ABI -# contract. (An int-type Kconfig symbol can't carry a trailing same-line -# comment the way the bool/string ones above do -- conf --defconfig treats -# anything after the digits as part of the value and rejects it; verified -# via the "invalid for BR2_TARGET_ROOTFS_EXT2_INODE_SIZE" warning this -# produced before being moved here.) -BR2_TARGET_ROOTFS_EXT2_INODE_SIZE=256 -# -# BR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS -- pinned explicitly, not left to -# mke2fs defaults. Three independent reasons, each checked against the real -# stock artifact (work/extracted/files/linux/linux.img) and this Buildroot's -# actual e2fsprogs 1.47.3, not assumed: -# -# 1. FEATURE SET. `dumpe2fs -h` on the real stock linux.img gives exactly: -# has_journal ext_attr resize_inode dir_index filetype extent 64bit -# flex_bg sparse_super large_file huge_file dir_nlink extra_isize -# metadata_csum -# This build's e2fsprogs 1.47.3 default `ext4` fs_type -# (output/host/etc/mke2fs.conf) gives that SAME 14 features PLUS -# metadata_csum_seed and orphan_file -- two features e2fsprogs added to -# its own defaults some time after stock's image was built. That is -# the "drift across e2fsprogs versions" this task warns about, caught -# in the act: left to defaults, THIS build already diverges from -# stock's feature set, and a future e2fsprogs bump could add more. So -# every feature below is forced on or off explicitly (^), not -# inherited from mke2fs.conf. -# This also overrides fs/ext2/Config.in's own default of "-O ^64bit" -# (chosen upstream for pre-2017.02 U-Boot bootloaders that can't read a -# 64bit ext4). Irrelevant here: U-Boot never reads linux.img at all -# (only uboot.img/zImage_dtb off the FAT partition -- docs/boot- -# chain.md); linux.img is loop-mounted by the KERNEL, which has -# supported the 64bit feature since 3.18. Stock itself ships 64bit ON, -# confirming the kernel side is fine with it. -# 2. UUID (-U) and directory-hash-seed (-E hash_seed=) are BOTH -# `uuid_generate()` -- i.e. /dev/urandom-backed and RANDOM -- when not -# given explicitly (this e2fsprogs's misc/mke2fs.c:3325 and :3348), and -# the hash seed is written into the superblock at creation time -# regardless of whether any directory actually becomes htree-indexed. -# Leaving either implicit makes the image's own superblock bytes -# non-reproducible even with BR2_REPRODUCIBLE=y and an identical -# TARGET_DIR -- BR2_REPRODUCIBLE only pins SOURCE_DATE_EPOCH -# (timestamps, see below); it does not touch mke2fs's own UUID/seed -# generation. The two fixed values below are two separate -# /proc/sys/kernel/random/uuid draws, pinned once and never -# regenerated -- deliberately DIFFERENT from stock's own UUID -# (50ef310c-47b9-4c1c-a2fe-d0202d02b6b4) so a user who still has a -# stock SD-card backup lying around never has two filesystems with the -# identical UUID visible to the same host at once. -# 3. -b 4096: already what mke2fs.conf's [defaults] section would pick for -# an image this size, made explicit for the same "don't trust the -# defaults to hold across an e2fsprogs bump" reason as (1). -# -# SOURCE_DATE_EPOCH (below, via BR2_REPRODUCIBLE) covers the remaining -# source of mke2fs non-determinism: this e2fsprogs (lib/ext2fs/ -# initialize.c) reads SOURCE_DATE_EPOCH for the filesystem's own -# created/last-write superblock timestamps, and fs/common.mk's -# ROOTFS_REPRODUCIBLE hook touches every file under TARGET_DIR to that same -# timestamp before ANY rootfs image (tar or ext2) is generated -- so file -# mtimes inside the image are pinned too, and so is file ORDERING to the -# extent it's driven by TARGET_DIR's own (stable, un-mutated-between-builds) -# directory order -- see the P2.5 acceptance run for the actual two-build -# byte-identical proof. -BR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS="-U 71916572-439f-448e-b8d8-12b0a032fa56 -E hash_seed=9afc615c-c310-4e03-ada9-613522e83ae6 -b 4096 -O has_journal,ext_attr,resize_inode,dir_index,filetype,extent,64bit,flex_bg,sparse_super,large_file,huge_file,dir_nlink,extra_isize,metadata_csum,^metadata_csum_seed,^orphan_file" - -# Byte-identical builds from the same commit (P2.5's "done when"). Exports -# SOURCE_DATE_EPOCH (pinned to work/buildroot's OWN last commit date -- -# top-level buildroot Makefile:538-540 -- constant as long as that pinned -# tree doesn't change) and, via fs/common.mk's ROOTFS_REPRODUCIBLE hook, -# touches every TARGET_DIR file to it before any rootfs image is built. -# Does NOT, by itself, pin mke2fs's UUID/hash-seed -- see the -# MKFS_OPTIONS comment above for why those are pinned separately. -BR2_REPRODUCIBLE=y - -################################################################################ -# P2.1 — Full package set. -# -# Source of truth: docs/package-manifest.md §6 "Ready-to-paste BR2_PACKAGE_* -# list" (P0.7's deliverable). This block is that list, applied verbatim, -# EXCEPT for the imlib2 loader sub-options (marked below) which P0.7 did not -# include -- it mapped SONAMEs, not dlopen()'d plugins, and abi-contract.md -# §2.2 explicitly warns imlib2's loaders are invisible to a DT_NEEDED/SONAME -# scan (they're dlopen'd from usr/lib/imlib2/loaders/*.so at runtime) and -# must be turned on by hand or menu.png/menu.jpg backgrounds silently fail -# to load. Verified against docs/stock-inventory/shared-libraries.md's -# on-device loader list (argb/bmp/bz2/ff/gif/ico/id3/jpeg/lbm/png/pnm/tga/ -# xpm/zlib) -- everything except gif/id3/jpeg/png/tiff builds into imlib2 -# unconditionally with no Buildroot Config.in gate, so enabling those five -# reproduces stock's loader set exactly (package/imlib2/Config.in, checked -# against the then-pinned Buildroot 2026.02.3 and NOT re-checked since the -# 2026.05 bump -- a dated finding, not a standing guarantee). -# -# Every symbol below was cross-checked against the actual pinned Buildroot -# tree's Config.in files (not from memory) before being pasted here -- see -# the P2.1 task's verification pass. BR2_PACKAGE_UTIL_LINUX_BINARIES IS now -# enabled (see the util-linux binaries block further down): stock actually -# ships the real util-linux mount/umount/blkid/fdisk/dmesg/agetty/... (2.36.2 -# ELF binaries, not BusyBox -- verified against work/imgroot), so shipping the -# util-linux programs is the parity-correct choice. The earlier "covered by -# BusyBox" note was wrong about what stock shipped. See docs/util-linux-parity.md. -# Samba's AD DC / ADS / -# smbtorture sub-options are deliberately left unset (standalone file -# server only, manifest §5 Drop list) -- BusyBox and every other package -# below is otherwise the full manifest, ungated. -# -# BR2_ENABLE_LOCALE is deliberately NOT listed here even though the -# manifest recommends it: it is already =y in the current built output/ -# (glibc's own default for this toolchain, confirmed in output/.config) -# and it lives in the *toolchain* Kconfig menu -- P1.2's hazard ("Buildroot -# silently ignores toolchain menu changes on an incremental build") means -# touching that menu at all is a make-clean-and-rebuild-from-scratch -# decision. Since the effective value doesn't change (y -> y), adding it -# buys nothing and only invites that risk for zero benefit. -# -# BR2_ENABLE_LOCALE only compiles locale *support* into glibc; it does not -# generate any locale *data*. That is a separate knob -- see the locale -# section further down -- and leaving it empty is what shipped an image with -# no /usr/lib/locale/locale-archive at all. -################################################################################ - -# --- compression --- -BR2_PACKAGE_ZLIB=y # meta-prompt -# zlib provider: zlib-ng in ZLIB_COMPAT mode, not classic zlib. package/zlib's -# Config.in is a virtual package with a choice between BR2_PACKAGE_LIBZLIB and -# BR2_PACKAGE_ZLIB_NG; ZLIB_NG_ARCH_SUPPORTS is `default y if BR2_arm` (and -# BR2_aarch64, so this survives a 64-bit port). zlib-ng.mk builds -DZLIB_COMPAT=1, -# so it installs libz.so.1 and every consumer follows transparently. -# -# BLAST RADIUS, measured on the rig before making the switch. Inside Main_MiSTer -# the dynamic libz is used by exactly two things: support/uef/uef_reader.cpp -# (gzip-wrapped UEF tape images -- BBC Micro / Acorn Electron only), and -# libpng16/Imlib2 for the OSD background images and boot logo (video.cpp:3822+). -# It is NOT used for CHD: stock compiles libchdr in statically and that copy -# includes , whose compat macros rewrite inflate -> mz_inflate, so every -# CD core decodes through miniz inside the binary. Verified from the published -# release binary, not just the Makefile. -# -# Verified on-device with the library preloaded: PNG decode through imlib2 -> -# libpng -> zlib succeeds identically, gzip round-trips, and curl reports -# "zlib/1.3.1.zlib-ng" while still working. No package we enable selects -# BR2_PACKAGE_ZLIB_FORCE_LIBZLIB (only assimp/clamav/quazip do, none of them ours). -# -# WHY: measured CHD decode win for the greenfield firmware, which unlike stock -# links the SHARED libchdr and so does reach system zlib -- audio hunks p90 -# -10 to -11%, max -7 to -15%. See harness/rig/chd-decode-optimization.md in -# Main_MiSTer for the numbers. -BR2_PACKAGE_ZLIB_NG=y # NOT "BR2_PACKAGE_ZLIB" alone -- concrete provider -BR2_PACKAGE_BZIP2=y -BR2_PACKAGE_XZ=y -BR2_PACKAGE_LZO=y -BR2_PACKAGE_ZSTD=y # libzstd.so.1 + the zstd CLI (upstream has no - # sub-option to omit the CLI). Needed by libchdr - # (CHD v5 zstd hunks) and flips minizip-ng's - # MZ_ZSTD=ON (see MINIZIP below) -# BR2_PACKAGE_MINIZIP IS minizip-ng 4.0.3 (zlib-ng/minizip-ng) -- the concrete -# provider matters here exactly like the ZLIB provider above: this is NOT the classic -# zlib-contrib zip.h/unzip.h library (that one is BR2_PACKAGE_MINIZIP_ZLIB, -# enabled just below). Buildroot forces -DMZ_COMPAT=OFF (work/buildroot/ -# package/minizip/minizip.mk), so there is no zip.h/unzip.h compat layer at -# all -- the mz_zip.h native API is here for an eventual Main_MiSTer port to -# it. Installs libminizip-ng.so.4 + minizip-ng.pc. Feature set under -# THIS defconfig (minizip.mk keys each MZ_* feature off other BR2_PACKAGE_* -# symbols): bzip2 + openssl (pkcrypt/wzaes) + lzma-via-xz + zlib + zstd (the -# ZSTD=y above); NO iconv -- BR2_ENABLE_LOCALE=y, so minizip's "select -# BR2_PACKAGE_LIBICONV if !BR2_ENABLE_LOCALE" stays off. -BR2_PACKAGE_MINIZIP=y -# BR2_PACKAGE_MINIZIP_ZLIB is the CLASSIC zlib-contrib minizip (zlib 1.3.1's -# contrib/minizip, autotools) -- a SEPARATE package from minizip-ng above. -# SONAME libminizip.so.1, the zip.h/unzip.h API (zipOpen/unzOpen). Enabled for -# backward compatibility: the current Main_MiSTer shared-lib cleanup links -# libminizip.so.1 (a NEEDED entry in the MiSTer binary), so the target must -# ship it or MiSTer fails at exec with "cannot open shared object file". It -# coexists with minizip-ng -- distinct SONAME (.so.1 vs -ng.so.4) and -# non-overlapping symbols (zipOpen/unzOpen vs mz_*), so both load conflict-free. -BR2_PACKAGE_MINIZIP_ZLIB=y - -# --- Main_MiSTer shared libs --- -# The BR2_EXTERNAL half of the Main_MiSTer shared-lib refactor (no task ID -- -# referenced by name): Main stops vendoring lib/{lzma,zstd,miniz,libchdr} and -# links Buildroot-provided shared libraries; the upstream half (zstd, -# minizip-ng) is enabled in the compression block above. Both packages -# authored under package/; see docs/main-shared-libs.md. -BR2_PACKAGE_LZMA_SDK=y # 7-Zip LZMA SDK 26.02 as liblzma-sdk.so.; - # the full-version SONAME is the deliberate - # loud-ABI-event policy: the Main binary lives on - # /media/fat and SURVIVES rootfs reflashes, so an - # SDK bump must refuse-to-load, not corrupt - # (package/lzma-sdk/lzma-sdk.mk) -BR2_PACKAGE_LIBCHDR=y # libchdr.so.0; commit-pinned past v0.3.0 for the - # Findzstd pkg-config fallback (the tag cannot - # configure against Buildroot's zstd); system - # zlib/zstd/lzma-sdk via our 3 patches; exports - # chd_* ONLY (version script), so no symbol - # collisions with minizip-ng et al. - -# --- graphics / fonts --- -BR2_PACKAGE_FREETYPE=y -BR2_PACKAGE_LIBPNG=y -BR2_PACKAGE_JPEG=y # meta-prompt -BR2_PACKAGE_JPEG_TURBO=y # default on ARM/NEON; builds -DWITH_JPEG8=ON - # -> libjpeg.so.8, matching stock exactly -BR2_PACKAGE_TIFF=y -BR2_PACKAGE_GIFLIB=y -BR2_PACKAGE_IMLIB2=y # critical ABI-contract SONAME (libImlib2.so.1) -BR2_PACKAGE_IMLIB2_JPEG=y # loader plugins -- dlopen'd, NOT in the manifest's -BR2_PACKAGE_IMLIB2_PNG=y # paste list, added here per abi-contract.md's -BR2_PACKAGE_IMLIB2_GIF=y # explicit warning (see comment block above). -BR2_PACKAGE_IMLIB2_TIFF=y # Without these, menu.png/background images -BR2_PACKAGE_IMLIB2_ID3=y # silently fail to load with no DT_NEEDED signal. -BR2_PACKAGE_LIBXKBCOMMON=y -BR2_PACKAGE_SDL2=y - -# --- audio --- -BR2_PACKAGE_ALSA_LIB=y # provides libasound + libatopology together -BR2_PACKAGE_LIBAO=y -BR2_PACKAGE_LIBVORBIS=y # provides vorbis + vorbisenc + vorbisfile together -BR2_PACKAGE_LIBOGG=y -BR2_PACKAGE_MPG123=y # provides libmpg123 + libout123 together -BR2_PACKAGE_LIBID3TAG=y -BR2_PACKAGE_LIBMODPLUG=y -BR2_PACKAGE_FLUIDSYNTH=y -BR2_PACKAGE_FLUIDSYNTH_ALSA_LIB=y # ALSA-seq MIDI backend -- needed for stock's - # ALSA MIDI device list to match (P3.8) - -# --- MIDI / MT-32 (P3.8) --- -# munt (mt32d) + MidiLink reproduce stock's MIDI/MT-32 stack: MidiLink -# (usr/sbin/midilink, usr/sbin/mlinkutil) is the ALSA-seq client that shells -# out to mt32d (munt) or fluidsynth on demand. Neither has an upstream -# Buildroot package -- both authored under package/. See docs/midi-mt32-parity.md. -BR2_PACKAGE_MUNT=y -BR2_PACKAGE_MIDILINK=y -# alsa-utils MIDI tools -- stock ships amidi/aplaymidi/arecordmidi/aseqdump/ -# aseqnet/aconnect (docs/stock-inventory/binaries-needed-full.txt), the tooling -# that exercises the ALSA-seq MIDI graph. NOTE: stock's GENERAL (non-MIDI) ALSA -# tools (alsactl/alsamixer/aplay/arecord/amixer/...) are a separate parity gap, -# tracked outside P3.8 -- see docs/midi-mt32-parity.md. -BR2_PACKAGE_ALSA_UTILS=y -BR2_PACKAGE_ALSA_UTILS_ACONNECT=y -BR2_PACKAGE_ALSA_UTILS_AMIDI=y -BR2_PACKAGE_ALSA_UTILS_APLAYMIDI=y -BR2_PACKAGE_ALSA_UTILS_ARECORDMIDI=y -BR2_PACKAGE_ALSA_UTILS_ASEQDUMP=y -BR2_PACKAGE_ALSA_UTILS_ASEQNET=y -# General (non-MIDI) ALSA CLI tools (P3.15) -- stock ships all of these -# (docs/stock-inventory/binaries-needed-full.txt); the P3.8 MIDI pass -# deliberately left them for this separate general-ALSA-parity pass. -# alsactl (mixer save/restore), alsamixer/amixer (volume), aplay/arecord -# (APLAY provides both), alsabat (BAT), alsaloop, alsatplg, alsaucm, -# iecset (S/PDIF status bits), speaker-test (channel test tones) -- every one -# of these is present in docs/stock-inventory/binaries-needed-full.txt. -# NOT enabled: alsaconf -- stock never shipped it (it has an option here but no -# stock binary to match). One stock ALSA binary has no parity path at all: -# usr/bin/aserver IS in stock, but alsa-utils 1.2.15 exposes no -# BR2_PACKAGE_ALSA_UTILS_* target for it (dropped upstream), so it cannot be -# selected from this defconfig -- see docs/midi-mt32-parity.md section 5. -BR2_PACKAGE_ALSA_UTILS_ALSACTL=y -BR2_PACKAGE_ALSA_UTILS_ALSALOOP=y -BR2_PACKAGE_ALSA_UTILS_ALSAMIXER=y -BR2_PACKAGE_ALSA_UTILS_ALSATPLG=y -BR2_PACKAGE_ALSA_UTILS_ALSAUCM=y -BR2_PACKAGE_ALSA_UTILS_AMIXER=y -BR2_PACKAGE_ALSA_UTILS_APLAY=y -BR2_PACKAGE_ALSA_UTILS_BAT=y -BR2_PACKAGE_ALSA_UTILS_IECSET=y -BR2_PACKAGE_ALSA_UTILS_SPEAKER_TEST=y - -# --- crypto / TLS --- -BR2_PACKAGE_OPENSSL=y # meta-prompt -BR2_PACKAGE_LIBOPENSSL=y # NOT "BR2_PACKAGE_OPENSSL" alone -- concrete - # provider. 1.1->3.6.2, SONAME .so.1.1->.so.3, - # harmless (everything rebuilt together, see risk table) -BR2_PACKAGE_GNUTLS=y -BR2_PACKAGE_LIBGCRYPT=y -BR2_PACKAGE_LIBSSH2=y -# nettle, gmp, libtasn1, libgpg-error, libffi are all pulled in transitively as -# dependencies of gnutls/gcrypt/samba4/python3 -- do not set separately. -# CA trust store (found missing on hardware): without it, curl's default CA path -# /etc/ssl/certs/ca-certificates.crt is absent and every HTTPS verify fails -# ("error adding trust anchors"), which also breaks Downloader_MiSTer's HTTPS -# fetches. Installs the Mozilla bundle as ca-certificates.crt + OpenSSL hashed -# symlinks (curl + python default context). The cacert.pem/cert.pem aliases the -# Downloader (DEFAULT_CACERT_FILE=/etc/ssl/certs/cacert.pem) and stock expect are -# added as overlay symlinks -> ca-certificates.crt. Functional parity with stock's -# cacert.pem CA story. -BR2_PACKAGE_CA_CERTIFICATES=y - -# --- networking / D-Bus / GLib --- -BR2_PACKAGE_LIBCURL=y -BR2_PACKAGE_LIBCURL_CURL=y # installs the `curl` CLI binary -- off by - # default, stock ships it, community scripts use it -BR2_PACKAGE_LIBCURL_OPENSSL=y # TLS backend parity: stock's curl links - # libcrypto/libssl, not GnuTLS -# GNU wget -- stock parity restored (issue #130, 2026-09-01). Stock ships a real -# GNU wget ELF at usr/bin/wget linked against libgnutls.so.30, libnettle.so.8, -# libpcre.so.1, libuuid.so.1 and libz.so.1 (docs/stock-inventory/ -# binaries-needed-full.txt:351), plus GNU wget's own /etc/wgetrc -- 4945 bytes, -# see etc-configs.md:1097 -- a file BusyBox's applet never reads. Stock's BusyBox -# 1.33.1 ALSO had the wget applet compiled in (busybox-applets.md:278), but the -# GNU ELF owned the path, so the applet was unreachable as `wget` -- the same -# "two providers, one path" shape as ifup/util-linux/lsof below. -# This image previously shipped ONLY the BusyBox applet, with -# CONFIG_FEATURE_WGET_HTTPS and CONFIG_FEATURE_WGET_OPENSSL both off, so -# SSL_SUPPORTED was 0 and every https:// URL died at networking/wget.c:578 with -# "wget: not an http or ftp url:" -- while curl (above) worked, which is exactly -# how issue #130 was reported. -# The recorded reason for leaving wget out was the PCRE1 removal note further -# down this file ("the only stock consumers were wget/zsh, neither of which we -# build"). That premise is stale: this Buildroot's wget.mk:16 passes -# --disable-pcre UNCONDITIONALLY, so GNU wget does not want PCRE1 at all, and -# wget.mk:67 gives it --enable-pcre2 against the BR2_PACKAGE_PCRE2 we already -# ship. Nothing here resurrects PCRE1. -# TLS backend is GnuTLS, matching stock, and for free: wget.mk:26 prefers -# BR2_PACKAGE_GNUTLS over OpenSSL when both are present, and we set both. -# Built and readelf'd, not predicted (2026-09-01): the resolved DT_NEEDED is -# libgnutls.so.30, libnettle.so.8, libuuid.so.1, libz.so.1, libc.so.6 and -# ld-linux-armhf.so.3 -- identical to stock's -- plus libpcre2-8.so.0 where -# stock had libpcre.so.1, plus libunistring.so.5, which stock's older wget did -# not link. The last one is free: BR2_PACKAGE_LIBUNISTRING was already set and -# the .so was already in the image before this change. libpsl, libidn2 and -# c-ares stay out (wget.mk:18/40/60 take their --without/--disable branches), -# which is also what stock did -- none of the three is in stock's list either. -# The installed /etc/wgetrc is 4945 bytes, byte-for-byte stock's size, and the -# ARM binary's --version banner reports "+https ... +ssl/gnutls" under qemu-arm. -# Prerequisites were already satisfied, nothing else had to change: -# BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y (below), BR2_USE_WCHAR=y and BR2_USE_MMU=y. -# The colliding BusyBox applet is turned off in board/mister/de10nano/ -# busybox.fragment so this binary wins deterministically (same idiom as -# ifup/ifdown and the util-linux block). -BR2_PACKAGE_WGET=y # real GNU wget 1.25.0 w/ GnuTLS -- https - # works, /etc/wgetrc is read (stock parity) -BR2_PACKAGE_DBUS=y -BR2_PACKAGE_DBUS_CPP=y # dbusxx-introspect; low-value but zero-cost parity -BR2_PACKAGE_DBUS_GLIB=y -BR2_PACKAGE_LIBEVENT=y -BR2_PACKAGE_LIBNL=y -BR2_PACKAGE_IPTABLES=y -BR2_PACKAGE_LIBGLIB2=y -BR2_PACKAGE_GOBJECT_INTROSPECTION=y - -# --- util-linux / e2fsprogs / disk & fs tools --- -BR2_PACKAGE_UTIL_LINUX=y -BR2_PACKAGE_UTIL_LINUX_LIBBLKID=y # each lib sub-option defaults to "n" -- -BR2_PACKAGE_UTIL_LINUX_LIBFDISK=y # must be listed explicitly or the -BR2_PACKAGE_UTIL_LINUX_LIBMOUNT=y # corresponding SONAME won't be built -BR2_PACKAGE_UTIL_LINUX_LIBSMARTCOLS=y -BR2_PACKAGE_UTIL_LINUX_LIBUUID=y -# util-linux BINARIES + programs -- stock parity (usbmount work). Stock ships real -# util-linux 2.36.2 ELF binaries for mount/umount/blkid/fdisk/dmesg/agetty/hwclock/ -# ... (NOT BusyBox -- verified against work/imgroot), so we ship them too (2.41.4 -# here). The util-linux `mount` matters functionally: it dispatches `mount -t ntfs` -# to the /sbin/mount.ntfs -> ntfs-3g helper, which BusyBox mount cannot do (no -# CONFIG_FEATURE_MOUNT_HELPERS) -- that is how NTFS USB drives auto-mount under -# usbmount, exactly like stock. The overlapping BusyBox applets are turned off in -# board/mister/de10nano/busybox.fragment so these win deterministically (same idiom -# as ifupdown). The libs above are already selected; BINARIES re-selects them -# harmlessly. Full stock<->ours program map: docs/util-linux-parity.md. -BR2_PACKAGE_UTIL_LINUX_BINARIES=y # basic set: blkid, blockdev, dmesg, - # fdisk/sfdisk, findfs, findmnt, flock, - # fstrim, getopt, hexdump, lsblk, lscpu, - # mkswap, setarch(+linux32/64), setsid, - # swapon/swapoff, ... (stock's set) -BR2_PACKAGE_UTIL_LINUX_MOUNT=y # mount + umount -- the functional core - # (helper dispatch to mount.ntfs) -BR2_PACKAGE_UTIL_LINUX_MOUNTPOINT=y # mountpoint -BR2_PACKAGE_UTIL_LINUX_AGETTY=y # serial-console getty; inittab uses it, - # replacing BusyBox getty (stock parity) -BR2_PACKAGE_UTIL_LINUX_HWCLOCK=y # hwclock (manual/debug; no S05rtc, like stock) -BR2_PACKAGE_UTIL_LINUX_FSCK=y # fsck -BR2_PACKAGE_UTIL_LINUX_PARTX=y # addpart/delpart/partx/resizepart -BR2_PACKAGE_UTIL_LINUX_SCHEDUTILS=y # chrt/ionice/taskset -BR2_PACKAGE_UTIL_LINUX_IRQTOP=y # irqtop/lsirq -BR2_PACKAGE_UTIL_LINUX_KILL=y # kill -BR2_PACKAGE_UTIL_LINUX_MORE=y # more -BR2_PACKAGE_UTIL_LINUX_NEWGRP=y # newgrp -BR2_PACKAGE_UTIL_LINUX_NOLOGIN=y # nologin -# NB: util-linux `raw` (stock had /sbin/raw) is intentionally NOT enabled -- its -# Config.in `depends on !BR2_TOOLCHAIN_HEADERS_AT_LEAST_5_14` and our 6.18 headers -# are >= 5.14, so the raw(8) char-device interface (removed from the kernel in 5.14) -# is unbuildable. Obsolete; no BusyBox `raw` applet either, so nothing is lost. -BR2_PACKAGE_UTIL_LINUX_RENAME=y # rename -BR2_PACKAGE_UTIL_LINUX_SETTERM=y # setterm -BR2_PACKAGE_UTIL_LINUX_SWITCH_ROOT=y # switch_root -BR2_PACKAGE_E2FSPROGS=y -BR2_PACKAGE_PARTED=y -BR2_PACKAGE_NTFS_3G=y # stock has no NTFS driver at all (kernel side); - # this is userland-only parity for exFAT/NTFS - # USB drives via FUSE, matches stock's ntfs-3g -# T5 (2026-07-27): mkfs.ntfs/ntfsfix DID NOT LAND despite the line above -- -# a real oversight, not a deliberate omission, found while auditing stock's -# util binaries. BR2_PACKAGE_NTFS_3G=y alone only builds the ntfs-3g FUSE -# driver + mount.ntfs-3g; the rest of ntfsprogs (mkntfs/mkfs.ntfs, ntfsfix, -# ntfsclone, ntfsresize, ntfslabel, ...) is gated by this separate sub-option, -# which defaults to "n" with no dependency of its own (package/ntfs-3g/ -# Config.in:27-30 -- "config BR2_PACKAGE_NTFS_3G_NTFSPROGS / bool 'ntfsprogs' / -# help / Install NTFS utilities.", no "default", no "depends on"). Confirmed -# via the .mk too: without this symbol ntfs-3g.mk passes --disable-ntfsprogs -# to configure (ntfs-3g.mk:32-34). -# PATHS ARE SPLIT, and not the way "ntfsprogs" suggests -- ntfsprogs/ -# Makefile.am:17 puts `ntfsfix` (with ntfsinfo/ntfscluster/ntfsls/ntfscat/ -# ntfscmp) in bin_PROGRAMS -> /usr/bin, while :18's sbin_PROGRAMS holds -# mkntfs/ntfslabel/ntfsundelete/ntfsresize/ntfsclone/ntfscp -> /usr/sbin, and -# the install-exec-hook at :166-169 adds the mkfs.ntfs -> mkntfs symlink beside -# mkntfs in sbin. ntfs-3g.mk passes no --exec-prefix override (unlike -# dosfstools.mk:13's "--exec-prefix=/"), so bindir really is /usr/bin. Stock -# lands exactly the same way -- work/imgroot has usr/bin/ntfsfix and -# usr/sbin/{mkntfs,mkfs.ntfs}, and NO usr/sbin/ntfsfix. scripts/ci-tests.sh's -# T5 block asserts both paths in that split. Worth spelling out because the -# first draft of that gate asserted usr/sbin/ntfsfix -- which would have failed -# deterministically on the first real build; it was caught in review, before -# any build ran, not at runtime. -BR2_PACKAGE_NTFS_3G_NTFSPROGS=y # mkfs.ntfs (-> mkntfs), ntfsfix, + - # the rest of ntfsprogs -BR2_PACKAGE_KMOD=y -BR2_PACKAGE_INOTIFY_TOOLS=y -BR2_PACKAGE_JQ=y -BR2_PACKAGE_EXPAT=y -BR2_PACKAGE_POPT=y -BR2_PACKAGE_READLINE=y -BR2_PACKAGE_NCURSES=y -# WIDE-CHAR ncurses. Not cosmetic -- it is an ABI-contract fix. Plain -# BR2_PACKAGE_NCURSES builds the NARROW libncurses.so.6; the wide libncursesw.so.6 -# only comes from --enable-widec (this symbol). docs/package-manifest.md:193 lists -# the required SONAME as libncursesw.so.6, stock ships exactly that, and 35 stock -# binaries (bash, dialog, clear, dmesg, alsamixer, ...) DT_NEEDED it -- so a -# libncursesw-linked ARM binary dropped on the device would fail to start against -# our narrow lib. This was shipped narrow by omission: the manifest mapped the -# libncursesw SONAME to BR2_PACKAGE_NCURSES without noting that the "w" requires -# this second symbol. -# -# It also restores wide-char curses in Python (the build symlinks -# libncurses.so -> libncursesw.so, so _curses/_curses_panel/readline all relink -# against the wide lib): our narrow _curses lacks the wide-char key-read API -- -# the window.get_wch() method and its module-level companion _curses.unget_wch, -# both compiled in only against ncursesw. A TUI that reads a keystroke via -# window.get_wch() -- e.g. to catch the UP arrow -- hits AttributeError on narrow -# ncurses and commonly falls back to line mode, where the arrow just echoes as -# ^[[A instead of being captured. Enabling widec is what stock has and what makes -# window.get_wch() work. -BR2_PACKAGE_NCURSES_WCHAR=y -BR2_PACKAGE_SLANG=y -BR2_PACKAGE_NEWT=y -BR2_PACKAGE_GPM=y -BR2_PACKAGE_LIBARCHIVE=y # keep the library (samba4 can use it); do NOT - # package archivemount itself -- see Drop list -BR2_PACKAGE_LIBFUSE=y # ditto -- real dependents may want it even - # though archivemount itself is dropped - -# --- USB / input --- -BR2_PACKAGE_LIBUSB=y -BR2_PACKAGE_LIBUSB_COMPAT=y # legacy libusb-0.1 API shim -- still NEEDed - # by name (libusb-0.1.so.4) in stock's binary set -BR2_PACKAGE_LIBEVDEV=y -BR2_PACKAGE_LIBINPUT=y -BR2_PACKAGE_MTDEV=y -BR2_PACKAGE_USBMOUNT=y # USB mass-storage automount, stock parity. - # Stock ships the Debian `usbmount` package - # (udev RUN+= rule -> /usr/share/usbmount/usbmount) - # that mounts sd*/ub* block devices under - # /media/usb0..7 on hotplug and unmounts on - # removal. Buildroot's usbmount is the same tool - # (0.0.22, patched to read udev's ID_FS_* env - # instead of shelling out to blkid -- functionally - # identical to stock's 0.0.24 script). It needs - # udev (BR2_PACKAGE_HAS_UDEV -- eudev above) and - # `select`s BR2_PACKAGE_LOCKFILE_PROGS (-> the - # liblockfile already enabled below) for the - # lockfile-create serialisation in its add path. - # run-parts/logger/expr are BusyBox applets, all - # present. The stock-tuned usbmount.conf (adds - # exfat/ntfs/fuseblk + NTFS/fuseblk mount opts, - # which upstream 0.0.22's default omits) ships in - # the rootfs-overlay and overrides the package's - # default -- see docs/usb-automount-parity.md. - -# --- Bluetooth --- -BR2_PACKAGE_BLUEZ5_UTILS=y -BR2_PACKAGE_BLUEZ5_UTILS_CLIENT=y # NOT in the manifest -- discovered during P2.1 - # verification: DEPRECATED below "depends on - # BLUEZ5_UTILS_CLIENT || BLUEZ5_UTILS_TOOLS", and - # stock ships usr/bin/bluetoothctl (needs CLIENT) - # + usr/bin/gatttool (also needs CLIENT), per - # docs/stock-inventory/binaries-needed-full.txt -BR2_PACKAGE_BLUEZ5_UTILS_TOOLS=y # NOT in the manifest -- the other half of - # DEPRECATED's prerequisite; stock also ships - # hciattach/l2ping which live under TOOLS -BR2_PACKAGE_BLUEZ5_UTILS_DEPRECATED=y # hciconfig/hcitool/sdptool/rfcomm/l2ping/ - # hcidump -- all present in stock, gated by - # this option upstream now. Depends on CLIENT - # or TOOLS above (package/bluez5_utils/Config.in) - # -- silently unsatisfiable without them; confirmed - # missing from output/.config before this fix. -BR2_PACKAGE_BLUEZ5_UTILS_PLUGINS_SIXAXIS=y # PS3 controller BT pairing (selects - # _PLUGINS_HID transitively -- don't set that too) - -# --- PAM / capabilities --- -BR2_PACKAGE_LINUX_PAM=y -BR2_PACKAGE_LIBCAP=y -BR2_PACKAGE_LIBCAP_NG=y - -# --- misc small libraries / tools --- -BR2_PACKAGE_DTC=y # libfdt -# T5 (2026-07-27): the line above ships ONLY the library -- package/dtc/ -# Config.in says so explicitly ("Note that only the library is installed. If -# you want the programs, say 'y' here, and to 'dtc programs', below"). The -# `dtc` CLI itself (plus convert-dtsv0/fdtdump/fdtget/fdtput/dtdiff) needs -# this separate sub-option, which was never set -- so this image has never -# actually shipped the `dtc` binary despite DTC=y being on since P2.1. -# dtdiff additionally needs bash, already on (BR2_PACKAGE_BASH=y, wifi.sh). -BR2_PACKAGE_DTC_PROGRAMS=y # dtc, fdtget/fdtput/fdtdump, - # convert-dtsv0, dtdiff -BR2_PACKAGE_SUDO=y -BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y # NOT in the manifest -- discovered during P2.1 - # verification: BR2_PACKAGE_I2C_TOOLS "depends - # on BR2_PACKAGE_BUSYBOX_SHOW_OTHERS" - # (package/i2c-tools/Config.in); without this, - # I2C_TOOLS=y below is silently unsatisfiable and - # Kconfig drops it with no error (confirmed: it - # doesn't land in output/.config without this line) -BR2_PACKAGE_I2C_TOOLS=y # for the i2c-gpio RTC add-on, P3.11 -BR2_PACKAGE_JIMTCL=y # NOT just an obscure shell -- usb_modeswitch's - # dispatcher (3G/LTE modem support) needs it -BR2_PACKAGE_LIBLOCKFILE=y -BR2_PACKAGE_LIBXML2=y -BR2_PACKAGE_FILE=y # libmagic -# memtool (T3, addon.tar §3c): stock's usr/bin/memtool looked like an -# unsourceable ARM blob in the first reconciliation pass ("ARM ELF" with no -# provenance) -- it is not. strings on the stock binary yields pengutronix -# memtool's exact usage text ("memtool is divided into subcommands", the -# "Usage: md [-bwlqsx] REGION" / "Usage: mw [-bwlqd] OFFSET DATA..." lines), -# and addon.tar's usr/bin/md + usr/bin/mw are symlinks -> memtool (argv[0] -# dispatch), matching pengutronix's md/mw subcommand model. Buildroot packages -# that exact tool (package/memtool, 2018.03.0 -- upstream's last release), so -# this is a plain package enable, not a (D)-infeasible item. Only usr/bin/fpga -# remains without public source (docs/stock-reconciliation.md §3c). -# The package installs only /usr/bin/memtool (bin_PROGRAMS in its Makefile.am -# -- no symlinks); stock's md/mw argv[0] sugar is reproduced by two overlay -# symlinks instead (memtool.c:475 dispatches on basename(argv[0]), verified in -# the pinned 2018.03.0 tarball, so `md`/`mw` and `memtool md`/`memtool mw` are -# the same operations). -BR2_PACKAGE_MEMTOOL=y # stock usr/bin/memtool (pengutronix) -# PCRE1 (libpcre.so.1) was REMOVED upstream in Buildroot 2026.05 (EOL, unmaintained; -# it is now a Config.in.legacy stub that hard-stops the build). Nothing in this image -# needs it: the stock MiSTer binary does not link it (verified -- no -lpcre, no -# DT_NEEDED), Python uses its built-in sre engine (not PCRE), and 2026.05's slang -# dropped its pcre module (--with-pcre=no). The only stock consumers were wget/zsh, -# neither of which we build. See docs/package-manifest.md for the recorded parity deviation. -# We DO ship PCRE2 as the replacement, so a libpcre2-8.so.0 provider is present for -# anything that wants modern PCRE. It is already select'd transitively by libglib2 -# and libselinux; listed explicitly here so it can never be silently dropped. -BR2_PACKAGE_PCRE2=y # libpcre2-8.so.0 -- PCRE1 replacement -# eudev needs its "/dev management" choice (system/Config.in) switched away from -# the BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_DEVTMPFS default -- NOT in the manifest, -# discovered during P2.1 verification: BR2_PACKAGE_EUDEV=y alone is silently -# unsatisfiable (depends on BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV, which the -# manifest's paste list never sets) and Kconfig drops it with no error. This one -# line cascades to fix THREE other silently-dropped symbols below it in this same -# file: BR2_PACKAGE_LIBINPUT (depends on BR2_PACKAGE_HAS_UDEV, only select'd by -# eudev) and BLUEZ5_UTILS_PLUGINS_SIXAXIS (same). Confirmed: none of the four -# landed in output/.config until this line was added. -BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y # selects BR2_PACKAGE_EUDEV automatically -BR2_PACKAGE_EUDEV=y # NOT mdev -- PLAN §3 explicit requirement - # (kept explicit for readability even though - # the choice above already selects it) - -# --- lftp --- -BR2_PACKAGE_LFTP=y # provides all 4 bundled liblftp-*.so together - -# --- Python (A6) --- -BR2_PACKAGE_PYTHON3=y # 3.14.6 -- no legacy-version toggle exists in - # Buildroot 2026.05; see P3.9 risk entry -# Python extension modules (P3.9) -- match stock's 3.9 lib-dynload set exactly. -# SSL + ZLIB are HARD BLOCKERS: without them Downloader_MiSTer crashes on -# `import ssl` and cannot read its own db.json.zip (proven under qemu-user, -# docs/python-compat.md). openssl/zlib target libs are already built. BZIP2/XZ/ -# PYEXPAT/READLINE/CURSES round out stock parity. (SQLITE/DECIMAL deliberately -# omitted -- stock's Python 3.9 shipped neither.) -BR2_PACKAGE_PYTHON3_SSL=y -BR2_PACKAGE_PYTHON3_ZLIB=y -BR2_PACKAGE_PYTHON3_BZIP2=y -BR2_PACKAGE_PYTHON3_XZ=y # _lzma -BR2_PACKAGE_PYTHON3_PYEXPAT=y -BR2_PACKAGE_PYTHON3_READLINE=y -BR2_PACKAGE_PYTHON3_CURSES=y -# btctl runtime (T3, addon.tar §3c). Stock's OSD Bluetooth pairing flow is a -# Python script: Main_MiSTer popen()s the absolute path /usr/sbin/btpair -# (work/Main_MiSTer/menu.cpp:7102) and later runs "btctl disconnect " -# (input.cpp:5581), and btpair is a 12-line wrapper whose whole job is running -# `btctl pair`. btctl (vendored into the overlay, see §3c) opens with -# `import dbus`, `import dbus.service`, `dbus.mainloop.glib` and -# `from gi.repository import GLib` -- i.e. classic dbus-python plus PyGObject. -# Stock ships exactly those bindings for its python3.9 (verified in -# work/imgroot/usr/lib/python3.9/site-packages/: dbus/, _dbus_bindings.so, -# _dbus_glib_bindings.so, gi/, PyGObject-3.36.1.egg-info). Without both, -# btctl dies on its first import and OSD pairing silently does nothing. -# Cost check before enabling: python-gobject's one heavyweight dependency -# (gobject-introspection, which drags in host-qemu) is ALREADY paid -- -# BR2_PACKAGE_GOBJECT_INTROSPECTION=y in the networking/D-Bus/GLib section -# above -- and dbus-python needs only dbus + libglib2, both long since on. -# python-dbus-fast/-next (the asyncio reimplementations Buildroot also carries) -# were considered and rejected: btctl uses the dbus-python API surface -# (dbus.service.Object agents, mainloop glue), and rewriting a proven stock -# script to a different binding is exactly the kind of unverifiable-off-target -# churn this project avoids. -BR2_PACKAGE_DBUS_PYTHON=y # dbus.mainloop.glib -- btctl's bus + agent -BR2_PACKAGE_PYTHON_GOBJECT=y # gi.repository.GLib -- btctl's main loop - -# --- Samba (single package covers ~125 of the 251 SONAMEs, see manifest §1) --- -BR2_PACKAGE_SAMBA4=y -# Deliberately NOT set (standalone file server only, not a domain controller/member): -# BR2_PACKAGE_SAMBA4_AD_DC -# BR2_PACKAGE_SAMBA4_ADS -# BR2_PACKAGE_SAMBA4_SMBTORTURE - -# --- daemons / user-facing binaries (manifest §2) --- -BR2_PACKAGE_OPENSSH=y -# Deliberately NOT set (Buildroot defaults it to y, i.e. --with-sandbox): -# BR2_PACKAGE_OPENSSH_SANDBOX -# Our kernel carries "# CONFIG_SECCOMP is not set" (linux.config:48), matching -# stock (docs/stock-inventory/stock-linux.config:592), so prctl(PR_SET_SECCOMP) -# returns EINVAL. Through openssh 10.3 that was only a debug() and sshd ran the -# pre-auth child unsandboxed -- which is what this image has silently done for -# its entire life; the seccomp sandbox was never once active here. openssh 10.4 -# (upstream 7ab700f, "Make failure to set SECCOMP or NO_NEW_PRIVS fatal") turned -# it into fatal(). The LISTENER still binds and listens normally, but the -# pre-auth privsep child of sshd-session then dies status 255 on every single -# connection, before any auth -- so the box looks like it is serving SSH while -# refusing every client, password and key alike. That regression reached us via -# Buildroot 2026.05.1 -> 2026.05.2, which bumped openssh 10.3p1 -> 10.5p1 and so -# crossed the 10.4 boundary. --without-sandbox selects SANDBOX_NULL -- verified on -# the rebuild: config.h gets "#define SANDBOX_NULL 1" and none of sshd, -# sshd-session or sshd-auth carries a seccomp string any more. That restores the -# posture the image actually had all along (the sandbox never engaged). Setting -# CONFIG_SECCOMP=y instead is the beyond-parity fix (post-build.sh:22 already -# lists it as such), but it would arm that armhf/glibc syscall allowlist for the -# first time ever and needs a real build-and-SSH test, not a hotfix. -# NB: configure-time flag -- changing it requires `make openssh-dirclean` before -# the rebuild, or the stale --with-sandbox stamp ships the same broken sshd. -# BR2_PACKAGE_OPENSSH_SANDBOX is not set -BR2_PACKAGE_PROFTPD=y -BR2_PACKAGE_WPA_SUPPLICANT=y -BR2_PACKAGE_WPA_SUPPLICANT_NL80211=y # default y already, listed for clarity -BR2_PACKAGE_WPA_SUPPLICANT_WEXT=y # stock's interfaces file passes - # "-D nl80211,wext" -- both drivers needed -BR2_PACKAGE_WPA_SUPPLICANT_DEBUG_SYSLOG=y # compiles in the "-s" (log-to-syslog) flag. - # Stock's /etc/network/interfaces invokes - # "wpa_supplicant -s ..."; without CONFIG_DEBUG_SYSLOG - # the -s is unknown -> wpa_supplicant rejects the args - # and dumps its usage text to the console at every boot - # (once per wlanN stanza). Enabling it makes -s valid so - # the pre-up starts cleanly and logs to syslog, not the - # console -- exact stock parity. (P3.4 hardware fix.) -BR2_PACKAGE_WPA_SUPPLICANT_WPA3=y # SAE/OWE/DPP -- WPA3-Personal support. BEYOND - # stock (stock's wpa_supplicant 2.9 had no WPA3, so - # WPA3 networks were unjoinable). Our 2.11 + the - # morrownr 88x2bu driver do SAE over nl80211. Requested - # for hardware testing on a real WPA3 network. -# T5: wpa_cli (interactive/scriptable control of a running wpa_supplicant -- -# status, scan, reassociate, list/select saved networks) + wpa_passphrase (turns -# an ASCII passphrase into the PSK hex blob wpa_supplicant.conf wants, so a -# script never has to embed the plaintext passphrase). Highest value-per-byte -# item in the T5 pass and squarely WiFi work: both are sub-options of the -# wpa_supplicant package already built above, not a new package. CLI selects -# WPA_SUPPLICANT_CTRL_IFACE (the Unix-socket control API) automatically -- -# don't also list that symbol by hand, it would just be redundant with the -# select (package/wpa_supplicant/Config.in:137-141). Neither has any other -# dependency (verified against the pinned Config.in). -BR2_PACKAGE_WPA_SUPPLICANT_CLI=y -BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y -# WiFi userland parity (P3.4) -- the community wifi.sh (Scripts_MiSTer) and the -# stock WiFi stack need these; all four are in stock's rootfs. See docs/wifi-parity.md. -# (CONFIG_CFG80211_WEXT=y is already resolved in our kernel, so iwlist/iwgetid's -# legacy WEXT ioctls work against the cfg80211-only Realtek drivers.) -BR2_PACKAGE_BASH=y # wifi.sh shebang -BR2_PACKAGE_DIALOG=y # wifi.sh interactive menus -BR2_PACKAGE_WIRELESS_TOOLS=y # iwmulticall: iwconfig + iwlist/iwgetid symlinks -BR2_PACKAGE_WIRELESS_TOOLS_IWCONFIG=y -BR2_PACKAGE_IW=y # stock ships usr/sbin/iw (nl80211 CLI) -- parity -BR2_PACKAGE_IPROUTE2=y # stock's /usr/sbin/ip -- wifi.sh's link up/down fallback - -# --- On-device text editors (stock parity) ------------------------------------ -# Stock ships usr/bin/joe, usr/bin/nano AND usr/bin/vim (P0.3 inventory, -# docs/stock-inventory/binaries-needed-full.txt:149/218/348). We shipped none of -# them -- only BusyBox's built-in `vi`. docs/package-manifest.md:665 had flagged -# this as a deliberate P2.7 size-budget call ("keep the light ones... if the -# community expects them"), left unresolved. Resolving it now: people SSH into a -# MiSTer to edit wpa_supplicant.conf / MiSTer.ini, and BusyBox vi is a hostile -# way to do that for most users. -# -# joe: ~0.65 MiB (docs/stock-inventory/disk-usage.md). Needs MMU only. -# nano: small; needs wchar + ncurses (both already on). -# vim is NOT enabled here -- it is the heavy one, and its libgpm dependency is -# already satisfied (BR2_PACKAGE_GPM=y above) if we ever want full parity. -BR2_PACKAGE_JOE=y # stock usr/bin/joe -BR2_PACKAGE_NANO=y # stock usr/bin/nano -# ifupdown package (stock parity): stock ships the real ifupdown, not busybox's -# ifup applet. busybox ifup mangles the interfaces file's pre-up "$IFACE", so -# wpa_supplicant is called with bad args and dumps its usage text twice at every -# boot. The busybox.fragment disables busybox's IFUP/IFDOWN so ifupdown's -# /sbin/ifup wins (== usr/sbin/ifup under usr-merge, stock's exact path). P3.4. -BR2_PACKAGE_IFUPDOWN=y -BR2_PACKAGE_BUSYBOX_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/busybox.fragment" -BR2_PACKAGE_DHCPCD=y -BR2_PACKAGE_NTP=y # classic ntpd, matches stock -- NOT chrony/openntpd -BR2_PACKAGE_CIFS_UTILS=y - -# --- T3: Midnight Commander (stock parity, addon.tar §3c UX closure) --------- -# Stock ships mc 4.8.25 (strings on work/imgroot/usr/bin/mc) as THE on-device -# file manager, and it is load-bearing for MiSTer's media-player UX, not just a -# convenience: addon.tar overlays etc/mc/mc.ext with Open= handlers for -# aplay/mpg123/vgmplay/timidity/m3u_play/vhd_mount, ships a MiSTer skin, and -# usr/bin/timidity plays $MC_EXT_SELECTED -- an mc-set variable, so that script -# is mc-integration by construction. Without mc, four of the §3c helpers lose -# the UI they were written for. -# Version gap that matters: Buildroot's is 4.8.33, and upstream REPLACED the -# mc.ext format with mc.ext.ini in 4.8.29 -- read from the pinned tarball's own -# changelog, not from memory: mc-4.8.33 NEWS:191 is the "Version 4.8.29" -# header, NEWS:203 "Port mc.ext to INI format and rename to mc.ext.ini (#4141, -# #3742, #3191)", NEWS:205 "There is no fallback to previous mc.ext format". -# (NOT 4.8.28, whose section starts at NEWS:249 and still carries plain mc.ext -# bugfixes at NEWS:281-282.) Stock's etc/mc/mc.ext therefore CANNOT be carried -# verbatim -- 4.8.33 would ignore it outright. The MiSTer handlers are -# ported into the overlay's etc/mc/mc.ext.ini instead; see that file's header -# and docs/stock-reconciliation.md §3c for the per-handler mapping. -# Screen backend: ncurses (BR2_PACKAGE_NCURSES=y above; slang deliberately not -# enabled). mc's `select BR2_PACKAGE_NCURSES_WCHAR if BR2_PACKAGE_NCURSES` is a -# no-op here -- NCURSES_WCHAR=y is already set above, so the narrow->wide -# SONAME/clean-rebuild trap documented there is not re-triggered by this line. -# -# RUNTIME DEPENDENCY, worth knowing before someone debugs it the hard way: mc -# needs ALL THREE of its XDG dirs to be creatable, not just the one we vendor. -# mc_config_init_config_paths() (4.8.33 lib/mcconfig/paths.c:183-188) builds -# ~/.config/mc, ~/.cache/mc AND ~/.local/share/mc via mc_config_mkdir() -# (:104-112, g_mkdir_with_parents 0700 -> mc_propagate_error on failure), and -# src/main.c:315-320 treats that error as fatal (`mc_event_deinit(NULL); goto -# startup_exit_falure;`). The overlay ships only root/.config/mc/{ini,panels.ini} -# -- the other two must be created at runtime, on a root filesystem the kernel -# mounts READ-ONLY (cmdline `... loop=linux/linux.img ro rootwait`, -# docs/boot-chain.md:323; the inittab remount is deliberately commented out at -# etc/inittab:53). What makes mc work at all is /etc/profile:31's -# `mount -o remount,rw /`, which runs on the first login shell. So mc invoked -# before ANY login shell has run on a fresh boot (e.g. straight from a -# Main_MiSTer Scripts entry) dies with "Cannot create /root/.cache/mc directory". -# This is PARITY, not a regression: stock's addon.tar ships those same two mc -# files and no ~/.cache/mc or ~/.local/share/mc either (tar tvf on the pinned -# archive: under ./root/ only .config/mc/{ini,panels.ini} and .ssh/environment), -# and stock's /etc/profile carries the identical remount at :23 -- so stock -# behaves exactly the same way. -# Deliberately NOT "fixed" by shipping empty root/.cache/mc + root/.local/share/mc -# in the overlay: that would diverge from stock, and the overlay's rsync chmod -# (--chmod=u=rwX,go=rX, system/system.mk:64-68) would create them 0755 where mc -# wants 0700. Recorded here instead; revisit only if a Scripts-launched mc is -# ever actually wanted. -BR2_PACKAGE_MC=y # stock usr/bin/mc -- file manager + - # the §3c media-helper launcher UI - -# --- NFS client userland (ADR 0022) -- reverses P3.10's "kernel-only NFS" call --- -# The kernel has carried the whole NFS client all along (NFS_FS/V2/V3/V4/V4_1/V4_2, -# SUNRPC, LOCKD_V4, NFS_USE_KERNEL_DNS -- docs/netfs-parity.md), but with no -# mount.nfs helper `mount -t nfs` could not work AT ALL: util-linux's mount execs -# /sbin/mount. for a network fs, and there is no BusyBox fallback -- its mount -# applet is disabled outright in our build (`# CONFIG_MOUNT is not set`, and -# `# CONFIG_FEATURE_MOUNT_NFS is not set` with it; the latter is v2/v3-only anyway). -# Supplying /sbin/mount.nfs (+ the mount.nfs4 symlink) is the entire point here. -BR2_PACKAGE_NFS_UTILS=y -BR2_PACKAGE_NFS_UTILS_NFSV4=y # NFSv4/4.1/4.2 -> nfsidmap + rpc.idmapd. - # Buildroot hard-couples --enable-nfsv4 to - # --enable-blkmapd, which is why lvm2 gets - # pulled in (trimmed just below). -# CLIENT ONLY -- this line is load-bearing, not decoration. Upstream defaults -# `BR2_PACKAGE_NFS_UTILS_RPC_NFSD` to y, and leaving it alone would install -# rpc.nfsd/rpc.mountd/exportfs + an S60nfs init script, select rpcbind, and -- the -# real trap -- fire NFS_UTILS_LINUX_CONFIG_FIXUPS, whose KCONFIG_ENABLE_OPT would -# flip on the in-kernel NFS *server* underneath our deliberate `# CONFIG_NFSD is -# not set`. We are a client; the server stays off on both sides. -# BR2_PACKAGE_NFS_UTILS_RPC_NFSD is not set -# lvm2 is not a feature we want -- it arrives solely as the blkmapd dependency above -# (blkmapd links libdevicemapper for the pNFS block layout, which no home NAS uses). -# Upstream defaults to installing the full LVM suite; keep only dmsetup + -# libdevicemapper so we do not ship an unused volume manager on a games console. -# BR2_PACKAGE_LVM2_STANDARD_INSTALL is not set - -BR2_PACKAGE_RSYNC=y -BR2_PACKAGE_BUSYBOX=y # 1.38.0 in this Buildroot (busybox.mk:7; - # output/build/busybox-1.38.0), always on. - # Parity with STOCK's 274-applet set -- stock - # runs 1.33.1, count from its own - # `busybox --list` under qemu-arm, see - # docs/stock-inventory/busybox-applets.md -- - # is a P2.3 config concern, not a - # package-selection one; the applet set this - # image actually ships is decided by - # board/mister/de10nano/busybox.fragment on - # top of package/busybox/busybox.config - -# --- P3.1/v9: Realtek USB WiFi -- MAINLINE-FIRST out-of-tree driver policy --- -# EXACTLY ONE out-of-tree Realtek WiFi fork is selected: rtl8852cu-morrownr, for -# the RTL8852CU/RTL8832CU (Wi-Fi 6E) -- see its own block at the end of this -# section. It is the only Realtek USB chip 6.18.40 cannot drive at all, so it is -# the only chip that satisfies ADR 0016's exception rule. Until v10.2 this -# section said "NO out-of-tree Realtek WiFi fork is selected any more"; that was -# true when written and is no longer. -# -# Every Realtek USB chip MiSTer's 5.15 stock drove with a vendor fork is still -# handled by an IN-KERNEL driver (board/mister/de10nano/linux.config) -- -# enabling both would bind-fight on the same USB IDs, so each of THOSE forks' -# packages remains DISABLED here: -# 8188eu, 8188fu, 8710bu -> rtl8xxxu (CONFIG_RTL8XXXU=m, already on) -# 8811cu, 8821cu -> rtw88_8821cu (CONFIG_RTW88_8821CU=m) -# 8822bu -> rtw88_8822bu (CONFIG_RTW88_8822BU=m, HW-verified WPA3) -# 8814au -> rtw88_8814au (CONFIG_RTW88_8814AU=m, merged in 6.16) -# 8812au -> rtw88_8812au (CONFIG_RTW88_8812AU=m, merged in 6.13) -# 8811au, 8821au -> rtw88_8821au (CONFIG_RTW88_8821AU=m, merged in 6.13) -# The last two lines are new: RTL8812AU and RTL8811AU/RTL8821AU were ADR 0016's -# only standing exceptions ("no mainline USB driver"), and that is simply no -# longer true -- the shared rtw88_88xxa core landed in 6.13, after that ADR was -# written. Mainline goes through mac80211 (WPA3/SAE/PMF work properly, the -# concrete defect that drove the 8822bu switch) and stays maintained, whereas -# the morrownr forks need hand-written compat patches every kernel bump. -# Coverage was diffed, not assumed, and NOTHING is lost. The two forks' USB-ID -# tables list 57 IDs, mainline rtw88_8812au + rtw88_8821au list 50, and the 50 -# are a strict subset of the 57. Every one of the 7 remaining IDs is claimed by -# a DIFFERENT in-kernel driver this image already builds -- the forks' tables -# simply over-claimed IDs belonging to other chips, and mainline attributes them -# correctly: 5 to rtw88_8814au (056e:400b, 056e:400d, 0b05:1817, 2001:331a, -# 7392:a834 -- e.g. 0b05:1817 is the ASUS USB-AC68, a 4x4 RTL8814AU), 1 to -# rtl8xxxu (07b8:8179, an RTL8188EUS), and 1 to rtw88_8822bu/8822cu (13b1:0043, -# the Linksys WUSB6300 v2 -- RTL8822BU; only the v1, 13b1:003f, is a true -# 8812AU, and mainline's 8812au table does carry it). Verified by grepping the -# pinned kernel tree for each ID. See docs/wifi-parity.md §6 for the worked diff. -# The disabled packages stay sourced (Config.in) as a selectable fallback. -# -# rtl8188eu-aircrack-ng / rtl8821au-morrownr / rtl8821cu-morrownr / rtl8814au- -# morrownr / rtl8852cu-morrownr carry a fork suffix, NOT plain -# rtl8188eu/rtl8821au/rtl8821cu -- so as -# not to collide with Buildroot's own same-named upstream packages (different -# forks; re-confirmed present on the pinned tree 2026-07-25) on the Kconfig -# symbol and Make namespace. (rtl8814au-morrownr and rtl8852cu-morrownr have no -# upstream twin to collide with -- re-checked 2026-07-27 -- and take the suffix -# only for a uniform morrownr naming scheme.) Buildroot's own -# rtl8188eu/rtl8821au/rtl8821cu/rtl8812au-aircrack-ng packages are left OFF -- -# our pins stay in control, not Buildroot's release cadence (A9 reproducibility). -# RTL8812AU: now the in-kernel mac80211 rtw88_8812au driver (CONFIG_RTW88_8812AU -# in board/mister/de10nano/linux.config, merged upstream in 6.13 via the shared -# rtw88_88xxa core), NOT the OOT rtl8812au package. To revert: re-add -# BR2_PACKAGE_RTL8812AU=y here and drop CONFIG_RTW88_8812AU from linux.config. -# BR2_PACKAGE_RTL8812AU is not set # -> mainline rtw88_8812au (8812au) -# RTL8814AU: now the in-kernel mac80211 rtw88_8814au driver (CONFIG_RTW88_8814AU -# in board/mister/de10nano/linux.config, merged upstream in 6.16), NOT the OOT -# rtl8814au-morrownr package. The package/ definition is kept but unselected (the -# OOT fork gets no API updates past kernel 6.14; running both would conflict on -# the same USB IDs). To revert: re-add BR2_PACKAGE_RTL8814AU_MORROWNR=y here and -# drop CONFIG_RTW88_8814AU from linux.config. -# RTL8811AU/RTL8821AU: now the in-kernel mac80211 rtw88_8821au driver -# (CONFIG_RTW88_8821AU, same 6.13 rtw88_88xxa core as 8812au above), NOT the OOT -# rtl8821au-morrownr package. To revert: re-add -# BR2_PACKAGE_RTL8821AU_MORROWNR=y here and drop CONFIG_RTW88_8821AU. -# BR2_PACKAGE_RTL8821AU_MORROWNR is not set # -> mainline rtw88_8821au (8811au/8821au) -# BR2_PACKAGE_RTL8188EU_AIRCRACK_NG is not set # -> mainline rtl8xxxu (8188eu) -# BR2_PACKAGE_RTL8188FU is not set # -> mainline rtl8xxxu (8188fu) -# BR2_PACKAGE_RTL8821CU_MORROWNR is not set # -> mainline rtw88_8821cu (8811cu/8821cu) -# RTL8822BU (0bda:b812): SWITCHED to the MAINLINE rtw88 driver (kernel -# CONFIG_RTW88_8822BU=m, see board/.../linux.config) instead of the out-of-tree -# 88x2bu. Mainline goes through mac80211, so WPA3/SAE/PMF work correctly (the -# out-of-tree 88x2bu advertised SAE+CMAC but failed WPA3-only association, -# status_code=1 -- verified on hardware). rtw88 USB support for this chip landed -# in mainline ~6.2, AFTER stock's 5.15 froze -- which is why the out-of-tree -# driver was needed then and isn't now. The out-of-tree package is left OFF to -# avoid a bind conflict on the same USB ID; re-enable it (and drop RTW88_8822BU) -# to fall back. See docs/wifi-parity.md. -# BR2_PACKAGE_RTL88X2BU is not set -# -# RTL8852CU / RTL8832CU (Wi-Fi 6E, 2x2, 2.4/5/6 GHz USB) -- the ONE out-of-tree -# WiFi fork this image ships (v10.2). This reverses the "zero out-of-tree WiFi -# drivers" state v10 reached, deliberately and under ADR 0016's own unchanged -# rule: keep a fork only where mainline has no USB driver for the chip. -# Mainline 6.18.40 has none. rtw89 carries the 8852C chip HAL (rtw8852c.c, -# rtw8852c_rfk.c, rtw8852c_table.c) but its ONLY bus file for that HAL is the -# PCIe one -- rtw8852ce.c is present, rtw8852cu.c does not exist -- and the only -# Kconfig symbol offered is RTW89_8852CE, "depends on PCI" -# (drivers/net/wireless/realtek/rtw89/Kconfig:113-122). This board has no PCIe -# (CONFIG_PCI unset), so even that is unreachable. Directory listing checked on -# the pinned tree, not assumed; note the same directory DOES ship rtw8851bu.c -# and rtw8852bu.c, so this is an 8852C-specific gap, not "rtw89 has no USB". -# Net effect before this line: an RTL8852CU dongle got NO driver whatsoever. -# It was the last open USB WiFi gap from the v10.1 audit (docs/wifi-parity.md §7). -# -# Bind conflict: NONE. The fork's tree is multi-chip but upstream enables only -# CONFIG_RTL8852C, and its USB ID table is #ifdef-partitioned per chip, so the -# built module claims just nine IDs (0bda:c85a/c832/c85d, 0db0:991d, 2c4e:0127, -# 3574:6251, 35b2:0502, 35bc:0101, 35bc:0102). All nine were grepped against -# drivers/net/wireless/ and drivers/bluetooth/ in 6.18.40: zero matches. Near -# misses worth knowing: rtw89_8852bu holds 35bc:0100/0108 and btusb holds -# 2c4e:0128. The fork's compiled-OUT 8852B/8851B blocks WOULD collide (with -# rtw89_8852bu, rtw89_8851bu and mt7921u), which is why the chip switches must -# stay as upstream ships them -- see package/rtl8852cu-morrownr/*.mk. -# -# No firmware toggle needed: this vendor tree links its firmware in as a C array -# (LOAD_FW_HEADER_FROM_DRIVER) instead of calling request_firmware(), unlike -# mainline rtw89 which needs BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW89 (already on -# for 8851BU/8852BU). Expect a LARGE .ko in return -- ~15 MB of the source tree -# is firmware arrays; the built size has not been measured. -# -# Caveats, stated plainly: upstream's README declares 5.15-6.14 as Realtek- -# tested and 6.15-7.1 as community-supported, so 6.18.40 is in the weaker band; -# and the package needs a KSRC= override to build under Buildroot at all (the -# driver's EXTRA_CFLAGS->ccflags-y translation is gated on a kernel-version -# probe that looks at the BUILD HOST's /lib/modules). Both are documented with -# file:line evidence in package/rtl8852cu-morrownr/rtl8852cu-morrownr.mk. -# To revert: drop this line. Nothing in linux.config needs changing with it -- -# there is no in-kernel driver to turn back on. -BR2_PACKAGE_RTL8852CU_MORROWNR=y # RTL8852CU/8832CU Wi-Fi 6E USB -- - # no mainline USB driver exists - # (rtw89 is PCIe-only for 8852C) - -# --- P3.2: xone (Xbox One/Series accessory driver, PLAN.md §4.1 class D/E) --- -# Commit-pinned, hash-verified, sourced from dlundqvist/xone -- the actively -# maintained fork; medusalix/xone (the original, and what stock's fork -# vendored) is explicitly in "maintenance mode" per its own README. See -# package/xone/xone.mk for the full fork-choice comparison. -# -# xow-firmware fetches and extracts the Xbox Wireless Dongle firmware from -# Microsoft's own driver package at BUILD TIME (never committed to git, G6) -# and installs it under both stock's literal filename (xow_dongle.bin, for -# parity -- docs/stock-inventory/firmware.md) and the name this driver fork -# actually requests (xone_dongle_02fe.bin, a symlink to the same bytes). -# ACCEPTED maintainer decision, 2026-07-13 -- docs/decisions/0003-xone-firmware.md. -BR2_PACKAGE_XONE=y -BR2_PACKAGE_XOW_FIRMWARE=y - -# --- dualsensectl: DualSense operator CLI (userspace, not a driver) --- -# Reaches the DualSense features hid-playstation exposes no interface for at -# all -- adaptive trigger effects, speaker/headphone routing, output volume, -# rumble/trigger attenuation, microphone mode and volume, player/mic LED -# dimming, BT power-off, firmware info. STRICTLY ADDITIVE to the DualSense -# kernel patches (0033 player_id LED, 0037 mic-mute -> BTN_Z, 0042 stock -# lightbar LED names): those back Main_MiSTer's sysfs-LED and input-event -# ABIs, which no userspace hidraw client can serve. docs/dualsense-tooling.md -# has the analysis; package/dualsensectl/dualsensectl.mk has the pin. -# -# Nothing invokes it automatically -- no init script, no udev rule. It and -# hid-playstation both write DS_OUTPUT reports to the same pad and the fields -# they share (the lightbar above all) are last-writer-wins, so this stays an -# operator tool run from a shell. -# -# It selects BR2_PACKAGE_HIDAPI and BR2_PACKAGE_DBUS. Neither appears as its -# own line below, and neither needs to: dbus is already explicitly set above, -# and savedefconfig omits any symbol a select already forces. Verified by -# regenerating: `make savedefconfig` before and after this change differ by -# exactly one line, the BR2_PACKAGE_DUALSENSECTL=y above. -# -# THE SELECTS ARE NOT JUST hidapi+libgudev -- READ THIS BEFORE TRIMMING. -# hidapi's Config.in carries `select BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_COPY if -# BR2_TOOLCHAIN_USES_GLIBC` (for its runtime UTF conversion of USB string -# descriptors). This image is glibc and that symbol was OFF, so enabling -# dualsensectl flips it on, and with BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_LIST empty -# that copies ALL of glibc's gconv charset modules to the target: 253 .so -# files, ~6.4 MiB apparent, more after ext4 4 KiB block rounding. -# -# That is left ON DELIBERATELY rather than pinned to a minimal list, because it -# closes a gap this repo already documented as load-bearing: glibc built these -# modules all along (they are in the sysroot) but nothing ever installed them, -# so /usr/lib/gconv did not exist on the target at all. Stock ships them -# (docs/package-manifest.md §1 "glibc iconv/gconv charset modules" -- libCNS, -# libGB, libJIS, libKSC et al are in stock's own SONAME inventory), and that -# same doc lists gconv in its "Not recommended to drop (tempting by size, but -# load-bearing)" set, reason: "needed for any non-ASCII filename over SMB". -# So this is a stock-parity fix that arrived as a side effect -- accepted on -# its own merits, not smuggled in. Guarding it: scripts/ci-tests.sh asserts the -# modules are present, so they cannot silently vanish if dualsensectl is ever -# turned off again. Pinning GCONV_LIBS_LIST to a guessed subset would risk -# silently breaking exactly the SMB filename case the manifest calls out. -# ~6.4 MiB is ~3% of the last measured 222 MiB of free image space. -BR2_PACKAGE_DUALSENSECTL=y - -# --- ltunify: Logitech Unifying receiver pairing (userspace, not a driver) --- -# Closes the one Logitech gap the kernel cannot: the PAIRING HANDSHAKE. Every -# other part is already covered and needs nothing added -- -# CONFIG_HID_LOGITECH_DJ (board/mister/de10nano/linux.config) gives each paired -# device its own input node with its own logical VID/PID, which is what -# Main_MiSTer identifies pads and keyboards by, and it `select`s -# CONFIG_HID_LOGITECH_HIDPP (drivers/hid/Kconfig:697), which is why HIDPP is =y -# in the resolved .config while being absent from our minimal defconfig. Stock -# has exactly the same four symbols. A device that came pre-paired in its box -# therefore works with nothing installed at all. -# -# Pairing is the exception: no sysfs knob, no ioctl, no kernel interface of any -# kind. ltunify writes the HID++ 1.0 registers itself over /dev/hidraw*. -# -# ~40 KiB installed and it links against nothing but libc -- this is the -# cheapest package in the image, not a size question. -# -# LIMITS ARE REAL AND ARE HANDLED IN THE WRAPPER, NOT HERE. ltunify supports -# Unifying (c52b/c532) and Nano (c52f/c534) only; it does NOT support Bolt -# (c548) or Lightspeed (c539/c53a/c53f/c543). It also assumes the first hidraw -# node it finds is the receiver you meant -- wrong when two are plugged in, and -# wrong again for a single NANO receiver, which owns two nodes because -# logi_dj_probe's "no HID++ collection -> -ENODEV" guard is recvr_type_dj-only. -# /usr/sbin/mister-pair-logitech (rootfs overlay) groups nodes by physical USB -# device, classifies by product ID independently of driver (Bolt is bound by -# hid-multitouch here, so a driver-first filter would not even see it), refuses -# the unsupported families by name, and pins the choice with ltunify's -d flag. -# Scripts/pair_logitech.sh is its launcher on the data partition, the same shim -# shape ADR 0026 established for check_storage.sh. -# -# NOT SOLAAR, which is the maintained tool and does cover Bolt: its only -# console_scripts entry point routes through solaar.gtk, which imports -# solaar.ui, which requires Gtk 3.0 -- on an image with zero X11/GTK packages. -# docs/logitech-pairing.md §2 has the comparison and the recheck conditions. -# -# No new selects worth noting: `select BR2_PACKAGE_LIBEXECINFO if -# !BR2_TOOLCHAIN_USES_GLIBC` is inert here (this image is glibc), so this is a -# one-line change with no transitive tail -- unlike dualsensectl above. -BR2_PACKAGE_LTUNIFY=y - -# --- P3.3: /lib/firmware population (PLAN.md §3/§4.1, module loading & -# firmware infra -- the module-autoload/depmod/kmod/xz-compress half is -# already done, see BR2_PACKAGE_HOST_KMOD_XZ/BR2_PACKAGE_KMOD_TOOLS below). -# Source of truth: docs/firmware-parity.md (the inventory -> sub-option -# mapping + the built-vs-stock diff). Target: docs/stock-inventory/ -# firmware.md's 66-file inventory (xow_dongle.bin, the 67th stock file, is -# P3.2's xow-firmware above, not repeated here). -# -# linux-firmware itself (BR2_PACKAGE_LINUX_FIRMWARE) is a meta-option with no -# files of its own -- every actual file comes from a sub-option below, each -# picked because it is the SMALLEST upstream grouping that contains an -# inventory file (Buildroot's own file lists are coarse per sub-option, so -# some non-inventory sibling files ride along -- a documented superset, not a -# problem; see the parity doc). regulatory.db/.p7s come from a SEPARATE -# package (wireless-regdb, not linux-firmware -- upstream split them out -# after the kernel gained direct .db-loading support in 4.15). -BR2_PACKAGE_LINUX_FIRMWARE=y -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7601U=y # mt7601u.bin (top-level, via - # WHENCE-driven symlink -- see - # parity doc for the build- - # verified proof) -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7610E=y # mediatek/mt7610e.bin -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7650=y # mt7650.bin -- filed under - # Buildroot's "Bluetooth - # firmware" menu (MT7650 is a - # WiFi+BT combo chip) but is - # the ONLY toggle that - # installs this WiFi file; - # stock's own inventory - # attributes it to - # rt2800usb, not to Bluetooth -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT76X2E=y # mediatek/mt7662.bin + - # mediatek/mt7662_rom_patch.bin - # (top-level via symlink) -- - # also what the in-tree - # mt76x2u USB driver requests - # (mt76x2/usb_mcu.c), not a - # separate mt7662u.bin (see - # parity doc: stock's own - # mediatek/mt7662u.bin / - # mt7662u_rom_patch.bin are - # the OLD out-of-tree name, - # superseded, not reproduced) -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7921=y # WIFI_RAM_CODE_MT7961*.bin -- - # MT7921U (mt7921u.ko, WiFi6 USB; - # the USB part reports as MT7961) -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7925=y # WIFI_RAM_CODE_MT7925*.bin -- - # MT7925U (mt7925u.ko, WiFi6E USB) -BR2_PACKAGE_LINUX_FIRMWARE_RALINK_RT2XX=y # rt2870.bin (rt2800usb, - # FIRMWARE_RT2870) + siblings -BR2_PACKAGE_LINUX_FIRMWARE_RTL_81XX=y # rtlwifi 8188e/8192c/8192d/ - # 8192s/8192eu family -BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX=y # rtlwifi 8712u/8723a/8723b - # family -BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT=y # rtl_bt 8723a/8723b/8723bs/ - # 8761a/8761bu family -BR2_PACKAGE_LINUX_FIRMWARE_RTL_88XX_BT=y # rtl_bt/rtl88*.bin glob -- - # covers 8812ae/8821a/8821c/ - # 8822b/8822cu in one option -BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88=y # rtw88/rtw8822b_fw.bin etc. -- - # firmware for the MAINLINE rtw88 - # driver we now use for RTL8822BU - # (replacing out-of-tree 88x2bu); - # also covers 8821cu/8822cu rtw88 -BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW89=y # rtw89/*.bin -- mainline rtw89 - # (RTL8851BU/RTL8852BU WiFi6/6E USB) -BR2_PACKAGE_LINUX_FIRMWARE_ATHEROS_9271=y # ar9271.fw + htc_9271* -- ath9k_htc - # (AR9271 802.11n USB) -BR2_PACKAGE_LINUX_FIRMWARE_ATHEROS_7010=y # ar7010*.fw + htc_7010* -- ath9k_htc - # (AR7010-based 802.11n USB) -BR2_PACKAGE_LINUX_FIRMWARE_ATHEROS_9170=y # carl9170-1.fw -- carl9170 - # (AR9170 802.11n USB) -# --- v10.2: Bluetooth firmware for combo/BT dongles whose driver we already -# build (docs/bluetooth-parity.md). Same class of gap as ath3k below: the -# driver binds, then dies at request_firmware(). -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7921_BT=y # mediatek/BT_RAM_CODE_MT7961_ - # 1_2_hdr.bin -- the BT half of - # the MT7921AU combo dongle - # whose WiFi half we already - # ship (_MEDIATEK_MT7921 below). - # Requested by btmtk.c; without - # it WiFi works and BT does not -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7922_BT=y # mediatek/BT_RAM_CODE_MT7922_ - # 1_1_hdr.bin -- btusb carries - # MT7922 USB IDs, so this is a - # reachable USB path, not just - # the M.2 part -BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7925_BT=y # mediatek/mt7925/BT_RAM_CODE_ - # MT7925_1_1_hdr.bin -- BT half - # of the MT7925U combo -BR2_PACKAGE_LINUX_FIRMWARE_QUALCOMM_6174A_BT=y # qca/rampatch_usb_00000302.bin - # + qca/nvm_usb_00000302.bin -- - # QCA ROME 6174A over USB. btusb - # requests exactly the "_usb_" - # names (btusb.c: - # "qca/rampatch_usb_%08x.bin"), - # and its QCA path is - # self-contained -- it needs no - # CONFIG_BT_QCA, so this - # firmware is the only missing - # piece. 132 KiB. -# NOT enabled: _QUALCOMM_9377_BT. Its two files are qca/rampatch_00230302.bin / -# nvm_00230302.bin -- the NON-usb names, which only the UART path (hci_qca, -# CONFIG_BT_HCIUART, not built) ever requests. No consumer here. -# NOT enabled: _LINUX_FIRMWARE_IBT (Intel Bluetooth, intel/ibt-*). 30 MiB, and -# Intel BT controllers ship essentially only on M.2 WiFi+BT combo cards, which -# this board cannot host -- there is no realistic external Intel BT USB dongle. -# CONFIG_BT_INTEL is nonetheless built because CONFIG_BT_HCIBTUSB `select`s it -# unconditionally (it cannot be turned off while btusb is on), so this is a -# DELIBERATE driver-without-firmware, unlike the ath3k/mt7663 cases which were -# accidental. Flip this on if an Intel BT dongle ever needs to work. -BR2_PACKAGE_LINUX_FIRMWARE_ATHEROS_6004=y # ath6k/AR6004/hw1.2 + hw1.3 - # (132 KiB) -- ath6kl_usb - # (CONFIG_ATH6KL_USB=m, new in - # v10.1; AR6003/AR6004 802.11n - # USB) -BR2_PACKAGE_LINUX_FIRMWARE_REDPINE_RS9113=y # rsi/rs9113_*.rps -- rsi_usb -BR2_PACKAGE_LINUX_FIRMWARE_REDPINE_RS9116=y # rsi/rs9116_wlan.rps, requested - # by rsi_91x_hal.c:35 (both - # toggles needed; CONFIG_RSI_USB=m) -BR2_PACKAGE_LINUX_FIRMWARE_AR3011=y # ath3k-1.fw -- the ath3k driver - # (CONFIG_BT_ATH3K=m, already on) - # for AR3011 USB Bluetooth. The - # driver was built but its - # firmware was NEVER installed, - # so every AR3011 dongle failed - # at request_firmware(); this - # closes that gap. -BR2_PACKAGE_LINUX_FIRMWARE_AR3012_USB=y # ar3k/*.dfu -- AR3012 USB - # Bluetooth patch/config RAM - # images, loaded by the same - # ath3k driver (and by btusb for - # the newer AR3012 IDs) -BR2_PACKAGE_LINUX_FIRMWARE_BRCM_BCM43XX=y # brcm/brcmfmac4373.bin + the - # 43xx SDIO/PCIe siblings -- - # brcmfmac (CONFIG_BRCMFMAC=m, - # new). Implicitly `select`s - # BR2_PACKAGE_LINUX_FIRMWARE_CYPRESS_CYW43XX - # (cypress/cyfmac*, the same - # silicon post-acquisition) -BR2_PACKAGE_LINUX_FIRMWARE_BRCM_BCM43XXX=y # brcm/brcmfmac43143.bin, - # 43236b.bin, 43242a.bin, - # 43569.bin -- the four BCM43xx - # USB parts brcmfmac drives; - # `select`s _CYPRESS_CYW43XXX -BR2_PACKAGE_WIRELESS_REGDB=y # regulatory.db + - # regulatory.db.p7s (separate - # from linux-firmware, see - # above) - -# Ten files that NO linux-firmware sub-option covers, even though upstream -# linux-firmware carries them and this project's pinned kernel has an in-tree -# consumer for each (verified by grep against the actual built kernel source, -# not assumed; last checked on 6.18.40 -- re-grep on a kernel bump rather than -# trusting this line -- see package/linux-firmware-extra/linux-firmware-extra.mk -# and docs/firmware-parity.md for the per-file citation). Same upstream tarball -# and hash-pin as linux-firmware itself, just a different subset kept. -# Four are stock-parity files; the other five are NOT (stock ships none of -# them) -- mediatek/mt7663* x4 back CONFIG_MT7663U=m and rtlwifi/rtl8192dufw.bin -# backs CONFIG_RTL8192DU=m, both enabled beyond stock, and each would otherwise -# probe and then fail at request_firmware(). See docs/wifi-parity.md §6.2, §7. -BR2_PACKAGE_LINUX_FIRMWARE_EXTRA=y - -# Broadcom BCM20702 BT dongle firmware (P3.14) -- brcm/BCM20702A1-0b05-17cb.hcd, -# the one brcm .hcd stock ships (35000 bytes) but mainline linux-firmware lacks. -# Hash-pinned build-time fetch, never a committed blob -- same maintainer- -# approved vendor-firmware posture as xow (ADR 0003). See package/bcm20702-firmware/. -BR2_PACKAGE_BCM20702_FIRMWARE=y - -# --- explicitly NOT carried forward (manifest §5 Drop list) --- -# archivemount (broken in stock; its deps libarchive/libfuse ARE kept above for others) -# adplay / adplug / binio -- no Buildroot package, no known MiSTer use -# libhid / libhid-detach-device -- no Buildroot package, superseded API -# jack1/jack2 (libjack) -- dangling in stock, unused by MiSTer -# rtorrent / libtorrent -- unused BitTorrent client, SONAME already drifted -# -# (Nothing is set by this block -- it is a note, not config. Settings resume below.) - -# --- kmod --------------------------------------------------------------------- -# Two different things, despite the shared prefix: -BR2_PACKAGE_HOST_KMOD_XZ=y # HOST kmod: "support xz-compressed modules", - # so the host depmod that runs at build time - # can read the .ko.xz we ship -BR2_PACKAGE_KMOD_TOOLS=y # TARGET kmod: installs depmod/insmod/lsmod/ - # modinfo/modprobe/rmmod on the device - -################################################################################ -# T5 -- utility binaries stock ships that this image left out (2026-07-27) -# -# A filename diff of stock's /bin,/sbin,/usr/bin,/usr/sbin (work/imgroot) -# against our rootfs.tar found 315 differences. Most are noise -- a full Perl -# install, python3.9-versioned scripts, GNU long-form duplicates of BusyBox -# applets already covered by the util-linux/coreutils blocks elsewhere in -# this file. The packages below are the real gaps: each is confirmed present -# in this pinned work/buildroot/package/ tree, and its Config.in was read -# (not assumed) for hidden `select`s, sub-options that default to "n", and -# toolchain prerequisites BEFORE being added here -- three of those reads -# found a genuine collision with a BusyBox applet that is already ON in this -# image (lsof, lsusb, mkdosfs -- plus chvt/deallocvt/openvt/setkeycodes via -# kbd below); see board/mister/de10nano/busybox.fragment for the disables -# that resolve them, same non-deterministic-last-install-wins idiom as the -# existing ifup/ifdown and util-linux blocks in that file. -# -# The BusyBox-applet half of this same task (stat, timeout, tac, shuf, comm, -# split, expand, groups, nc) lives entirely in busybox.fragment, not here -- -# nothing to enable in THIS file for those nine. -# -# wpa_cli/wpa_passphrase (WPA_SUPPLICANT_CLI/_PASSPHRASE) are also part of -# this task but live up with the rest of the wpa_supplicant block, not here -- -# see that block, a few hundred lines up, for the two lines and their comment. -# BR2_PACKAGE_NTFS_3G_NTFSPROGS and BR2_PACKAGE_DTC_PROGRAMS are likewise -# placed next to their already-existing parent lines (NTFS_3G / DTC) rather -# than duplicated down here. -# -# Explicitly REJECTED (maintainer's call, not a gap missed) -- recorded here -# so the decision is durable and doesn't get "rediscovered" as an oversight -# later; full reasoning in docs/package-manifest.md's Drop list (§5): -# perl -- anyone who needs it can build their own image from this repo; -# no MiSTer-specific consumer was found (stock's own init/service -# scripts are all shell, not Perl -- package-manifest.md §5). -# vim -- BusyBox vi AND nano (BR2_PACKAGE_NANO=y, above) already cover -# on-device editing; vim is the heaviest of stock's three editors. -# screen -- tmux (below) is this image's terminal multiplexer. Not both. -# gdb -- (the on-device, TARGET debugger, as opposed to gdbserver) -- -# host gdb + gdbserver cross-debugging is judged the better shape -# for this project, and a target debugger is 4-8 MB. -# NOTE, so this doesn't read as inconsistent with the very next -# section: BR2_PACKAGE_GDB/_GDB_DEBUGGER=y ARE set below, but by -# the DEBUG TOOLING block, for the separate, still-open -# RT-latency investigation (docs/debug-tooling.md) -- that is a -# dated, revert-as-one-unit decision, not a reversal of this one. -# ltrace -- narrow, frequently broken on ARM (a known, longstanding -# upstream limitation, not specific to this toolchain); strace -# (below) supersedes it for what this image needs. -# unrar -- non-free (RARLAB) licence. (The "rule G6" this line used to -# cite is about not committing binaries to git, not about -# licensing -- PLAN.md §2; corrected in passing 2026-07-27.) Not -# needed anyway: MiSTer release archives are .7z, not .rar, and -# BR2_PACKAGE_7ZIP below closes that gap. 7-Zip additionally -# brings RAR/RAR5 **extraction** along for free, under LGPL-2.1+ -# with the unRAR restriction (a no-reverse-engineering-of-the-RAR- -# compressor clause -- see package/7zip/7zip.mk's license -# comment), which is a different and far weaker thing than -# vendoring RARLAB's own non-free unrar. Compressing to .rar is -# still not possible, and nothing here needs it. -################################################################################ - -# --- T5: process / file / syscall inspection --- -BR2_PACKAGE_HTOP=y # interactive process viewer. Needs - # BR2_USE_MMU (fork()) + dynamic libs - # (dlopen()) -- both already true on - # this glibc/ARM target. Selects - # BR2_PACKAGE_NCURSES, already =y above, - # so this is a zero-marginal-dependency add - # (package/htop/Config.in). -BR2_PACKAGE_STRACE=y # syscall tracer. Already enabled - # TEMPORARILY by the DEBUG TOOLING block - # below (docs/debug-tooling.md) for the - # field-hang/RT-latency work -- this line - # promotes it to a PERMANENT part of the - # package set, so strace keeps shipping - # once that block is eventually deleted. - # Setting it twice is harmless (same value, - # last one wins); it is set again here - # rather than removed from the debug block, - # per that block's own "must stay - # contiguous, banner to banner" rule - # (configs comment above the banner). -BR2_PACKAGE_LSOF=y # lsof(8). Needs BR2_USE_MMU (fork()) and - # BR2_PACKAGE_BUSYBOX_SHOW_OTHERS (already - # =y above, for i2c-tools) -- no new - # dependency. COLLIDES with BusyBox's own - # `lsof` applet (CONFIG_LSOF=y in the base - # config) -- disabled in busybox.fragment, - # see that file for the full citation. - -# --- T5: USB / input / joystick & force-feedback --- -BR2_PACKAGE_USBUTILS=y # lsusb, usb-devices, lsusb.py (removed by - # the package's own install rule when no - # target python3 -- irrelevant here, we DO - # ship python3, but the C lsusb is what - # matters). Needs BR2_TOOLCHAIN_HAS_THREADS, - # gcc >= 4.9 and BR2_PACKAGE_HAS_UDEV (hwdb) - # -- all already true (eudev is on, P2.1). - # Selects LIBUSB (already =y). We did NOT - # already get lsusb from anywhere else -- - # verified no BR2_PACKAGE_USBUTILS and no - # busybox-provided lsusb.py equivalent were - # previously on, so this is a clean add, not - # a fix for a regression. COLLIDES with - # BusyBox's own `lsusb` applet -- disabled in - # busybox.fragment. -BR2_PACKAGE_EVTEST=y # evtest -- dumps /dev/input/eventN activity. - # No dependencies beyond libc. -BR2_PACKAGE_LINUXCONSOLETOOLS=y # On a games console, arguably the single -BR2_PACKAGE_LINUXCONSOLETOOLS_JOYSTICK=y # most valuable package in this whole T5 -BR2_PACKAGE_LINUXCONSOLETOOLS_FORCEFEEDBACK=y # pass: joystick calibration (jstest, jscal, - # jscal-store/-restore, evdev-joystick) and - # force-feedback testing (fftest, ffcfstress, - # ffmvforce, ffset). FORCEFEEDBACK needs - # dynamic libs (already true) and selects - # BR2_PACKAGE_SDL2, already =y above (P2.1 - # graphics block) -- zero marginal cost. - # Side effect, not asked for but harmless: - # the package's top-level `select - # LINUXCONSOLETOOLS_INPUTATTACH if - # !JOYSTICK && !FORCEFEEDBACK` does NOT - # fire (both are on), but INPUTATTACH's OWN - # "default y" (package/linuxconsoletools/ - # Config.in) still applies since nothing - # sets it off -- so `inputattach` (legacy - # serial-joystick/GPS attach helper) lands - # too, unconditionally by upstream default. - # Small, harmless, not worth suppressing. - -# --- T5: filesystem tools (FAT/exFAT) --- -# Both need BR2_USE_WCHAR, already true (glibc). dosfstools' three programs -# each default to "n" with NO indication of that in the parent's prompt text -- -# confirmed by reading dosfstools.mk directly: each of FATLABEL/FSCK_FAT/ -# MKFS_FAT is wrapped in its own `ifeq (...,y)` install guard, so leaving any -# one unset means that binary (and its compat symlinks) simply does not -# install, silently. All three are wanted here (fatlabel, fsck.vfat via -# FSCK_FAT, mkfs.vfat via MKFS_FAT are the task's explicit list), so all three -# are set. MKFS_FAT's compat symlinks include `mkdosfs` -- collides with -# BusyBox's own applet of that name, disabled in busybox.fragment. -BR2_PACKAGE_DOSFSTOOLS=y -BR2_PACKAGE_DOSFSTOOLS_FATLABEL=y # fatlabel (+ dosfslabel compat symlink) -BR2_PACKAGE_DOSFSTOOLS_FSCK_FAT=y # fsck.fat, + fsck.vfat/fsck.msdos/dosfsck - # compat symlinks -BR2_PACKAGE_DOSFSTOOLS_MKFS_FAT=y # mkfs.fat, + mkdosfs/mkfs.msdos/mkfs.vfat - # compat symlinks (mkdosfs collision above) -BR2_PACKAGE_EXFATPROGS=y # mkfs.exfat, fsck.exfat, dump.exfat -- no - # sub-options, no collision (BusyBox has no - # exFAT support of any kind to collide with). - -# --- T5: serial / terminal --- -BR2_PACKAGE_PICOCOM=y # minimal serial terminal. No dependencies. - # Does NOT collide with BusyBox's `microcom` - # applet -- different binary name. -BR2_PACKAGE_LRZSZ=y # rz/sz (X/Y/Zmodem) -- stock ships them - # (docs/stock-reconciliation.md §3c). Needs - # !BR2_STATIC_LIBS (dynamic, already true -- - # lrzsz redefines error()/error_at_line() and - # clashes with a static libc's own). Installs - # rz/sz plus SIX bonus compat symlinks -- - # lrz/rb/rx -> rz and lsz/sb/sx -> sz, counted - # off lrzsz.mk:21-26 one `ln -sf` at a time - # (rx/sx are easy to miss) -- that stock's - # addon.tar does not have: harmless extras, - # not a divergence that matters. -BR2_PACKAGE_TMUX=y # terminal multiplexer -- chosen over screen - # (see the rejected list above; not both). - # Needs BR2_USE_MMU (fork()), BR2_USE_WCHAR - # (mbtowc()) and BR2_ENABLE_LOCALE (runtime - # UTF-8 locale) -- all three already true - # (BR2_GENERATE_LOCALE="en_US.UTF-8", System - # configuration section, below). Selects - # BR2_PACKAGE_LIBEVENT (already =y, P2.1 - # networking block) and BR2_PACKAGE_NCURSES - # (already =y) -- no new dependency weight. - -# --- T5: network diagnostics --- -BR2_PACKAGE_ETHTOOL=y # examine/tune the ethernet NIC. No - # dependencies. ETHTOOL_PRETTY_PRINT is a - # sub-option that DOES default to "y" (unlike - # dosfstools' three above), so it is not - # listed separately -- confirmed in - # package/ethtool/Config.in. -BR2_PACKAGE_SOCAT=y # multipurpose socket relay/debug tool. Needs - # BR2_USE_MMU (fork()), already true. -BR2_PACKAGE_TCPDUMP=y # selects BR2_PACKAGE_LIBPCAP automatically - # (package/tcpdump/Config.in) -- not listed - # separately, it is a plain `select`, not a - # default-off sub-option. TCPDUMP_SMB ("smb - # dump support", the package's own Config.in - # calls it "possibly-buggy") deliberately - # left off -- not asked for, adds risk for - # nothing this image needs. -BR2_PACKAGE_IPERF3=y # active bandwidth measurement. Needs - # BR2_TOOLCHAIN_HAS_ATOMIC + _THREADS, both - # already true on this glibc/ARM toolchain. - -# --- T5: archival --- -# 7z support is the highest-value item here: MiSTer release archives are .7z, -# so without it on-device extraction of a downloaded release is impossible. -# -# THIS IS package/7zip (OURS), NOT upstream's package/p7zip -- and the swap is -# not a preference, it closes a real hole. The Downloader hardcodes -# /media/fat/linux/7za and, when that file is absent, DOWNLOADS p7zip 16.02 -# (2016-05-21, ARM, dynamically linked) from -# SD-Installer-Win64_MiSTer/raw/master/7za.gz to fill it. Our build now ships -# a statically-linked 7-Zip 26.02 into that exact path via both payload -# routes, so the fetch never fires. Full mechanism + evidence: ADR 0023, -# docs/downloader-contract.md §4. p7zip itself is dead upstream since 16.02 -# and carried on only by a community fork at 17.06 (2022), so "the latest -# p7zip" would still be four years stale against 7-Zip's own Linux support. -# -# Same four toolchain deps p7zip had, all still satisfied and all still real -# (they are not copied over blindly): BR2_TOOLCHAIN_HAS_SYNC_4 for -# C/Threads.c:783's __sync_add_and_fetch on a 4-byte LONG, -# BR2_INSTALL_LIBSTDCPP because the link driver is g++ (C++ was already pulled -# in for Main_MiSTer/T1-T4, BR2_TOOLCHAIN_BUILDROOT_CXX=y, P1.2), -# BR2_TOOLCHAIN_HAS_THREADS for the -lpthread the makefile hardcodes, and -# BR2_USE_WCHAR because 7-Zip's UString is wchar_t-based throughout. -# -# There is no 7za/7zr Kconfig `choice` to pin here the way p7zip needed: 7-Zip -# builds ONE full application (`7zz`) and the old 7za/7zr split is a matter of -# which upstream Bundles/ target you compile, not a runtime mode. `7za` is -# installed as an alias to it -- see package/7zip/7zip.mk. That also makes the -# old "7za's extra zip/cab/arj/... support is not needed" note moot: it comes -# along at zero cost, and zip/lzop below plus the busybox xz/bzip2/gzip -# applets stay for the compress-side and applet-parity reasons in their own -# comments. -BR2_PACKAGE_7ZIP=y -BR2_PACKAGE_ZIP=y # zip/PKZIP-compatible archiver. No - # dependencies. BusyBox has `unzip` but no - # `zip` applet of its own -- no collision. -BR2_PACKAGE_LZOP=y # lzop compressor. Selects BR2_PACKAGE_LZO, - # already =y above (P2.1 compression block). - # Installs ONLY the `lzop` binary (verified - # by reading the upstream 1.04 Makefile.am: - # `bin_PROGRAMS = src/lzop`, no unlzop/lzopcat - # symlinks) -- does not collide with - # BusyBox's own `unlzop`/`lzopcat` applets - # (decompress-only, different names, still - # on). BusyBox's OWN `lzop` (compressing) - # applet is separately already off in the - # base config, so there is nothing to - # disable even if it did share the name. - -# --- Off-device backup: Azure Storage CLI (package present, NOT enabled) ------ -# azcopy (package/azcopy, BR2_EXTERNAL -- upstream Buildroot has none) pushes -# the exFAT data partition's saves/screenshots/config to Azure Storage straight -# from the board. It WORKS: it was built, installed and exercised end-to-end on -# real hardware (132 MiB uploaded, md5-verified round trip, incremental `sync`, -# Azure Files, plan files on exFAT). docs/azcopy.md section 4 has the transcript. -# -# IT IS OFF BECAUSE OF SIZE, and only because of size. 41,007,016 bytes = -# 39.1 MiB installed -- the second-largest package in the image after samba4's -# ~49 MiB, and about a fifth of the free space linux.img has left -# (scripts/check-size-budget.sh on the 2026-08-17 build: 195 MiB / 38.1% free). -# The budget would still pass at ~30.5% free. "Would still pass" is not the -# same as "is worth spending", and for a tool most users will never run it is -# not: azcopy ships as a standalone downloadable artifact instead, where the -# people who want it pay the 8.3 MiB (xz) download and nobody else pays -# anything. See docs/azcopy.md section 1. -# -# WHY IT IS SO BIG, since that is the obvious next question: almost none of it -# is AzCopy. Measured by linking each dependency tree on its own for ARMv7 -- -# an empty Go binary is 1.2 MB, +Azure SDK is 5.5 MB, and +Google Cloud Storage -# is 27.6 MB. GCS drags in gRPC, protobuf and the Envoy go-control-plane xDS -# protos, none of which a MiSTer backup will ever execute. AzCopy's own code is -# ~1.3 MB of symbols. docs/azcopy.md section 1 also prices the surgery to cut -# it out (~20 files, a permanently-carried patch across credential handling). -# -# TURNING IT ON is one line -- uncomment the line below and the package builds, -# installs /usr/bin/azcopy and its /etc/profile.d/azcopy.sh defaults, and -# selects BR2_PACKAGE_HOST_GO (build-time only, nothing from it ships) plus -# BR2_PACKAGE_CA_CERTIFICATES (already =y in its own right in the crypto/TLS -# block above, and deliberately kept explicit there so it survives azcopy being -# switched off again). Enabling it also pulls host-go's five-stage from-source -# bootstrap into the build -- measured at ~4.2 min, plus ~12 s to compile -# azcopy itself, so WALL CLOCK is not the concern. DISK is: host-go's module -# cache measured 1.7 GiB, and docs/ci.md's disk-and-cache budget was written -# without it. Check that before turning it on in CI. -# -# ARMv7 IS NOT AN UPSTREAM-SUPPORTED TARGET for AzCopy: Microsoft publishes -# linux/amd64 and linux/arm64 binaries only, and two defects had to be patched -# to build and run at all (package/azcopy/0001-*, 0002-*). -# BR2_PACKAGE_AZCOPY is not set - -# --- T5: hardware buses --- -# dtc (BR2_PACKAGE_DTC_PROGRAMS) and i2c-tools (BR2_PACKAGE_I2C_TOOLS) are also -# part of this task's Group 3 list, but both are handled where their existing -# line already lives, not here: DTC_PROGRAMS is set right next to -# BR2_PACKAGE_DTC=y a few hundred lines up (it was library-only until now -- -# see that line's own comment), and BR2_PACKAGE_I2C_TOOLS=y was already fully -# on (P3.11, RTC add-on) with no sub-options gating any of its tools -- nothing -# to add there. -BR2_PACKAGE_SPI_TOOLS=y # spi-config, spi-pipe -- Linux spidev - # command-line helpers. No dependencies at - # all beyond autoreconf (host-side only). - -# --- T5: Bluetooth CLI --- -BR2_PACKAGE_BLUEZ_TOOLS=y # bt-adapter, bt-agent, bt-device, - # bt-network, bt-obex. Depends on - # BR2_PACKAGE_BLUEZ5_UTILS (already =y, - # P2.1 Bluetooth block), BR2_USE_MMU and - # BR2_USE_WCHAR (both true) and - # BR2_TOOLCHAIN_HAS_THREADS (true). Selects - # DBUS, DBUS_GLIB, LIBGLIB2 -- ALL THREE - # already =y above (P2.1 networking block) -- - # and READLINE, already =y (P2.1 util-linux - # block) -- so this is a genuinely - # zero-marginal-dependency add, every one of - # its `select`s was already paid for. NOTE - # for whoever reads this next to T3: stock's - # usr/sbin/btctl/btpair scripts (vendored, - # docs/stock-reconciliation.md §3c) are - # dbus-python + PyGObject talking to - # bluetoothd directly, NOT wrappers around - # these bt-* CLI tools -- the two are - # independent Bluetooth control paths that - # happen to ship together, not a dependency - # of one on the other. - -# --- T5: console / keyboard --- -# kbd is the SAME upstream package stock's own loadkeys/setfont/showkey/ -# dumpkeys came from (kbd-2.9.0 here; verified stock's strings match this -# family in board/mister/de10nano/rootfs-overlay/etc/inittab's own note 3). -# T3 already vendored etc/kbd.map and restored the guarded inittab lines -# ([ -x /usr/bin/loadkeys ] && ...) in anticipation of this line landing -- -# see docs/stock-reconciliation.md §3c ("etc/kbd.map", "consolefonts" rows) -# and etc/inittab's note 3. Needs BR2_USE_MMU (fork()) and gcc >= 4.9 -# (_Generic) -- both already true. --disable-vlock/--disable-tests -# (package/kbd/kbd.mk) are upstream Buildroot's own choices, not touched here. -# COLLIDES with four BusyBox console-tools applets that are already on -- -# chvt, deallocvt, openvt, setkeycodes -- all four disabled in -# busybox.fragment; see that file for the full citation (kbd's own -# src/Makefile.am PROGS list + configure.ac's KEYCODES_PROGS default). -# setfont's font: kbd 2.9.0 ships its own data/consolefonts/default8x16.psfu -# and installs it to /usr/share/consolefonts (data/Makefile.am:45-49) -- the -# exact directory stock's own setfont hardcodes -- so bare `setfont` (our -# guarded inittab line) resolves without any extra data file needing to be -# vendored, matching or not matching stock's exact filename -# (default8x16.psfu.gz) does not matter either way: setfont's own lookup -# (kbd-2.9.0 src/libkfont/setfont.c:417, findfont() -> kbdfile_find()) -# tries the bare name AND every configured decompressor's suffix -# (kbdfile.c:241-267, maybe_pipe_open()) before giving up, so it finds -# default8x16.psfu OR default8x16.psfu.gz equally well, transparently -# decompressing via a pipe if needed. Whether the installed file actually -# ends up named .psfu or .psfu.gz depends on whether THIS build host has -# gzip at kbd's configure time (data/Makefile.am's install-consolefonts -# runs configure.ac's enable_compress=auto -> "gzip -n" check first) -- -# not verified against a real build for this task; check -# output/target/usr/share/consolefonts/ on the next one if the exact -# filename ever matters. Either way, functionally identical, not a gap. -# SIZE, honestly: that one font is not the only thing that lands. kbd's -# `install-data-hook` (data/Makefile.am:73) unconditionally runs FOUR install -# rules -- install-keymaps, install-consolefonts, install-consoletrans, -# install-unimaps -- there is no Buildroot/configure knob to install only the -# one font. Read directly from the pinned 2.9.0 tarball: data/consolefonts -# (209 files, 1.5 MiB), data/keymaps (281 files minus a handful ignored by -# IGNORE_KEYMAPS, 3.1 MiB), data/consoletrans (500 KiB), data/unimaps -# (368 KiB) -- ~5.5 MiB of uncompressed source. configure's default -# (`enable_compress=auto`) gzips fonts+keymaps at BUILD time if a host gzip -# exists (it does, on any real build host), so the INSTALLED size is smaller -# than 5.5 MiB, but by how much was not measured here (no build was run for -# this task) -- do not assume a specific number without checking -# output/target/usr/share/{consolefonts,keymaps,consoletrans,unimaps}/du -sh -# on the next real build. Not a blocker: `./scripts/check-size-budget.sh -# output/images/linux.img` on the last built image reports 512 MiB total, -# 290 MiB USED, 222 MiB / 43.4% FREE against a 15% threshold -- so even the -# full 5.5 MiB uncompressed worst case is ~2.5% of the headroom. Two caveats -# on that number, stated rather than glossed: it is measured on an image built -# BEFORE T3/T5's ~21 new packages, so real free space after this lands is -# lower; and the 290 MiB figure is the USED half, not the free half (an easy -# swap to make -- docs/package-manifest.md's Drop-list §5 zoneinfo row is the -# companion discussion of what "not a concern" looks like at this scale, and -# of why block usage beats the byte column for many-small-files trees like -# these). Re-run that script after the next real build rather than trusting -# either number here. This is still real data this image did not -# carry before, useful beyond the one keymap/font stock actually used, and -# docs/package-manifest.md §5's consolefonts/keymaps row is updated to say so. -BR2_PACKAGE_KBD=y - -################################################################################ -# >>> DEBUG TOOLING — TEMPORARY, REMOVE AS ONE BLOCK <<< -# -# Everything between this banner and the matching "END DEBUG TOOLING" banner is -# on-device debugging/profiling tooling, enabled on request for the field -# hard-hang investigation and for RT latency measurement. It is NOT stock -# parity, it is NOT part of the P2.1 package manifest, and it is expected to be -# removed once those investigations close. Full rationale, per-package sizes, -# on-device usage and the exact revert recipe: docs/debug-tooling.md. -# -# TO REVERT: delete this whole block (banner to banner) and re-run -# make mister_de10nano_defconfig && make all -# Nothing outside this block and the matching CONFIG_COREDUMP block in -# board/mister/de10nano/linux.config depends on any of it. -# -# The block is deliberately contiguous and BR2_PACKAGE_-only so that (a) it can -# be deleted with one editor motion, and (b) it cannot evict the CI -# cross-toolchain cache: .github/actions/buildroot-build's toolchain -# fingerprint filters out every ^BR2_PACKAGE_ line, so adding these costs no -# cold 3h rebuild (docs/ci.md#toolchain-fingerprint). -# -# ⚠ `make savedefconfig` DISSOLVES this block. It rewrites this file from -# kconfig's own state, which knows nothing about banners: the comments go, and -# the seven symbols below scatter into the generated ordering — after which -# "delete the block" is no longer a one-motion operation and the isolation this -# whole arrangement buys is gone. That is the same hazard this file's own header -# already warns about for its top comment ("hand-restore it, same as this commit -# did"); it is called out again here because the cost is higher for a block whose -# entire purpose is being cleanly removable. If you must round-trip this file, -# re-cut this block by hand afterwards, or revert the debug tooling FIRST and -# re-apply it after. -################################################################################ - -# gdb — gdbserver AND the full on-device debugger. Both are asked for -# explicitly: gdbserver for the normal cross-debug flow (host cross-gdb over -# TCP), the full debugger so a core file can be opened on the device itself -# with no host toolchain present, which is the realistic flow for a field -# hard-hang report from a beta tester. -# -# NOTE on BR2_PACKAGE_GDB_SERVER: package/gdb/Config.in `select`s it whenever -# GDB_DEBUGGER is off, so it would be implied by GDB alone TODAY — but that -# select disappears the moment GDB_DEBUGGER=y (which is the case here), so it -# must be listed explicitly or enabling the full debugger would silently DROP -# gdbserver. Setting it is not redundant. -# -# GDB_DEBUGGER pulls in BR2_PACKAGE_{GMP,MPFR,READLINE,ZLIB} by select. Three of -# the four are already on and stay on after this block is deleted, but for TWO -# different reasons, worth keeping straight: -# - readline and zlib are set EXPLICITLY in this file (:504, :295-296); -# - GMP is NOT set here at all — it arrives transitively via gnutls/gcrypt -# (see the crypto/TLS note at :424, which says do not set it separately). -# Only MPFR is genuinely new, and it too arrives transitively rather than being -# listed here — do NOT add a line for it, or it would outlive this block's -# deletion, which is the one thing this arrangement exists to prevent. -# BR2_USE_WCHAR=y is a GDB_DEBUGGER dependency and is already satisfied -# (glibc + BR2_ENABLE_LOCALE). -# GDB_TUI / GDB_PYTHON are deliberately NOT enabled: neither was requested and -# both only add on-device UI weight. -BR2_PACKAGE_GDB=y -BR2_PACKAGE_GDB_SERVER=y -BR2_PACKAGE_GDB_DEBUGGER=y - -# strace — syscall tracing. No dependencies at all on this toolchain. -# `strace -k` (stack traces on each syscall) additionally wants -# BR2_PACKAGE_LIBUNWIND, which is deliberately left off; see docs/debug-tooling.md -# "what is deliberately NOT enabled". -BR2_PACKAGE_STRACE=y - -# perf, built from OUR kernel's own tools/perf (BR2_PACKAGE_LINUX_TOOLS_PERF -# selects the BR2_PACKAGE_LINUX_TOOLS meta-symbol, which is part of the linux -# package, not a standalone one — so perf is rebuilt whenever the kernel is). -# -# The kernel side needs nothing from us: package/linux-tools/linux-tool-perf.mk.in -# force-enables CONFIG_PERF_EVENTS via KCONFIG_ENABLE_OPT at kconfig-fixup time, -# and in this tree that fixup is already a no-op — CONFIG_PERF_EVENTS=y, -# CONFIG_ARM_PMU=y and CONFIG_HW_PERF_EVENTS=y all resolve on by kconfig default -# and are live in the built .config today (verified against -# output/build/linux-/.config; last confirmed on 6.18.39). -# Hardware counters are wired too: socfpga.dtsi -# has the arm,cortex-a9-pmu node with both per-CPU PMU interrupts, so `perf stat` -# gets real cycle/instruction counts rather than software events only. -# -# _NEEDS_HOST_PYTHON3 is NOT optional on this kernel and NOT cosmetic: since -# ~6.0 perf generates pmu-events.c at build time by running -# tools/perf/pmu-events/jevents.py, and the pinned kernel still does (that file -# is present in the unpacked tree; last confirmed on 6.18.39). Without this -# symbol Buildroot never adds host-python3 to -# PERF_DEPENDENCIES and the perf build fails on whatever python3 the *host* -# happens to have, or none. -BR2_PACKAGE_LINUX_TOOLS_PERF=y -BR2_PACKAGE_LINUX_TOOLS_PERF_NEEDS_HOST_PYTHON3=y - -# rt-tests — cyclictest et al., the standard PREEMPT_RT latency harness. This is -# what actually measures the RT kernel's wakeup latency on hardware, which -# docs/rt-beta-kernel.md still lists as an open TODO (the RT kernel boots; its -# latency has never been measured). Selects BR2_PACKAGE_NUMACTL transitively — -# again, do not list numactl here, it must disappear with this block. -# hwlatdetect is a Python script and installs because BR2_PACKAGE_PYTHON3=y. -BR2_PACKAGE_RT_TESTS=y - -################################################################################ -# >>> END DEBUG TOOLING <<< -################################################################################ - -################################################################################ -# System configuration -# -# Everything below lives in Buildroot's *System configuration* menu. They were -# previously scattered -- BR2_ROOTFS_MERGED_USR sat on line 1, ABOVE this file's -# own header comment, and BR2_TARGET_GENERIC_ROOT_PASSWD was stranded directly -# under the "explicitly NOT carried forward" note above, where it read as part of -# the drop list. Grouped here instead; no values were changed by that move. -# -# NOTE this menu is NOT the toolchain menu, so the P1.2 "incremental builds -# silently ignore toolchain menu changes" hazard (see the block near the top of -# the package list) does not apply to anything in this section. -################################################################################ - -# Merged /usr (/bin, /sbin, /lib are symlinks into /usr). Stock parity, and -# load-bearing: Buildroot's own `support/scripts/check-merged -t overlay -u` -# validates BR2_ROOTFS_OVERLAY's shape against this at target-finalize, so -# flipping it off would start rejecting our rootfs-overlay tree. -BR2_ROOTFS_MERGED_USR=y - -# Empty = passwordless root, matching stock. Per-device SSH host keys are -# generated on first boot instead (ADR 0015). -BR2_TARGET_GENERIC_ROOT_PASSWD="" - -# --- locale data (stock parity; fixes update_all.sh) -------------------------- -# Distinct from BR2_ENABLE_LOCALE (=y, toolchain menu, see the block above): -# that one compiles locale *support* into glibc, this one actually *generates* -# the locale data. With it empty -- the Buildroot default -- the Makefile never -# registers its GENERATE_GLIBC_LOCALES target-finalize hook and never builds -# host-localedef, so the image shipped with no /usr/lib/locale at all. Our own -# rootfs-overlay /etc/profile exports LC_ALL=en_US.UTF-8, so *every* login shell -# printed "setlocale: LC_ALL: cannot change locale (en_US.UTF-8)", and anything -# calling setlocale(LC_CTYPE, "") hard-failed -- notably update_all.sh, which -# died on locale.Error: unsupported locale setting before doing any work. -# -# Stock's /usr/lib/locale is a single 2.9 MB locale-archive (docs/stock- -# inventory/disk-usage.md), which is exactly the artifact support/misc/gen-glibc- -# locales.mk produces. en_US.UTF-8 is what /etc/profile asks for and is already -# in BR2_ENABLE_LOCALE_WHITELIST ("C en_US"), so locale-purge keeps it. -# -# This lives in the *System configuration* menu (work/buildroot/system/Config.in -# :575), NOT the toolchain menu -- it only adds a host package and a finalize -# hook, so it does not trigger the from-scratch-rebuild hazard described above. -BR2_GENERATE_LOCALE="en_US.UTF-8" - -# --- timezone / tzdata (stock parity) ----------------------------------------- -# We shipped NO tzdata at all: no /usr/share/zoneinfo, no /etc/localtime. So the -# timezone did not survive a reboot and TZ=America/New_York could not resolve. -# package-manifest.md §5's Drop list floated trimming zoneinfo and hedged -- -# "international users likely rely on the full zoneinfo set for TZ=" -- and that -# caveat is exactly what bit. Not dropped on purpose; just never enabled. -# -# Stock's /usr/share/zoneinfo contains BOTH posix/ and right/ subtrees (verified -# against the extracted stock rootfs), which is precisely what Buildroot's tzdata -# installs with the "default" zonelist -- so stock *was* plain Buildroot tzdata, -# and this reproduces it 1:1. No zone present in stock is missing from ours. -# BR2_TARGET_LOCALTIME="Etc/UTC" also reproduces stock's /etc/timezone -# byte-for-byte ("Etc/UTC"). -# -# Costs ~4.9 MB of ext4 *blocks* (1,191 mostly-tiny zone files, each rounding up -# to a 4 KiB block) -- NOT the 1.57 MiB that disk-usage.md's byte column implies. -# Either way: not a size concern. `./scripts/check-size-budget.sh -# output/images/linux.img` on the last built image reports 512 MiB total, -# 290 MiB USED, 222 MiB / 43.4% FREE against the 15% threshold. (290 is the -# used half, not the free half -- an earlier revision of this comment had the -# two swapped. The measurement also predates T3/T5's ~21 new packages, so real -# free space after those land is lower; re-run the script, do not trust a -# number cached in a comment.) -# -# NOTE: BR2_TARGET_LOCALTIME makes tzdata install /etc/localtime as a symlink to -# ../usr/share/zoneinfo/Etc/UTC. That is NOT what stock does and NOT what makes -# the setting persist. Stock points /etc/localtime at a file on the FAT data -# partition, which is the only thing that survives reflashing the rootfs -- our -# rootfs-overlay ships that symlink and overwrites tzdata's, because Buildroot -# rsyncs BR2_ROOTFS_OVERLAY *after* package install (Makefile:816). See -# board/mister/de10nano/rootfs-overlay/etc/localtime. -# -# That symlink is dangling on a FRESH card -- /media/fat/linux/timezone does not -# exist yet, so glibc silently falls back to UTC and stays there. The overlay's -# usr/lib/dhcpcd/dhcpcd-hooks/90-timezone fills it in once, the first time the -# box gets an address, from a geo-IP lookup (ADR 0025). It needs the full -# zonelist below to have a zone to copy, and the `posix/` subtree specifically -- -# the same path the community timezone.sh copies from. -BR2_TARGET_TZ_INFO=y -BR2_TARGET_TZ_ZONELIST="default" -BR2_TARGET_LOCALTIME="Etc/UTC" diff --git a/configs/mister_de25nano_defconfig b/configs/mister_de25nano_defconfig deleted file mode 100644 index a0b779a..0000000 --- a/configs/mister_de25nano_defconfig +++ /dev/null @@ -1,338 +0,0 @@ -# mister_de25nano_defconfig — D2.1: a bare developer OS for the Terasic -# DE25-Nano (Intel/Altera Agilex 5, HPS = 2x Cortex-A76 + 2x Cortex-A55, -# aarch64). -# -# Plan: docs/de25-nano-tasks.md, task D2.1 (Phase D2). -# Decisions: docs/de25-implementation-path.md §1 (the nine owner decisions); -# ADR 0027 (multi-board readiness); ADR 0027 Decision 6 as -# formalised by D2.7 (the release scope is a BARE DEVELOPER OS). -# -# WHAT THIS IS, AND — more importantly — WHAT IT IS NOT. -# This builds an aarch64 toolchain, a mainline 7.2.2 kernel and a minimal -# BusyBox ext4 rootfs that boots to a serial login with ethernet up. That is -# the whole scope. There are NO MiSTer packages here, NO DE10 packages, and -# nothing beyond what a developer needs to get a shell on the board. That is -# not an oversight or a staging state — it is the accepted release scope for -# this board until the upstream MiSTer framework grows an aarch64 story -# (de25-nano-tasks.md D2.7 / Phase D3). -# -# THE DE10 IS NOT AFFECTED BY THIS FILE. Nothing here is shared with -# configs/mister_de10nano_defconfig, configs/mister_kernel_defconfig or -# configs/mister_rt.fragment; the DE25 gets its OWN Buildroot output directory -# (output-de25/, `make de25`) exactly the way the RT variant and the two -# initramfs stages get theirs. The one file the two boards genuinely share is -# the kernel-tarball hash registry — see BR2_GLOBAL_PATCH_DIR below. -# -# WHY IT IS NOT THE OUTPUT OF `savedefconfig`. -# The DE10 defconfig's header explains that it IS canonical savedefconfig -# output and that its comments get dropped on every regeneration. This file is -# hand-written and hand-maintained for the same reason the DE10's comments keep -# getting hand-restored: on a board with no hardware validation yet, the -# reasoning is the deliverable. It has been round-tripped through -# `savedefconfig` to prove every symbol below really exists — but the file -# itself is not the machine's output. -# -# ROUND-TRIP RESULT, 2026-09-02 (Buildroot 2026.05.2). savedefconfig ADDED -# nothing (so no symbol below is implied-but-unstated) and DROPPED exactly -# three lines as non-divergent from a kconfig default: -# BR2_LINUX_KERNEL_IMAGE, BR2_TARGET_ROOTFS_EXT2_LABEL, -# BR2_TARGET_GENERIC_ROOT_PASSWD. -# All three are kept anyway, for the reason the DE10 defconfig gives for its own -# EXT2_LABEL line: a Buildroot default is not a promise. BR2_LINUX_KERNEL_IMAGE -# is the sharpest case — the "Kernel binary format" choice carries `default -# BR2_LINUX_KERNEL_ZIMAGE if BR2_arm || BR2_armeb` and NO default for aarch64 -# (linux/Config.in:242-244), so on this architecture it resolves to whichever -# entry upstream happens to list first. That is not something a boot artifact -# should depend on. -# -################################################################################ -# Architecture & toolchain -################################################################################ -# -# - aarch64, cortex-a76.cortex-a55 big.LITTLE: the Agilex 5 HPS is a -# 2xA76 + 2xA55 cluster. BR2_cortex_a76_a55 is Buildroot's own name for -# that tuning target and resolves to `-mcpu=cortex-a76.cortex-a55` -# (work/buildroot/arch/Config.in.arm:474, :945). It selects -# BR2_ARM_CPU_ARMV8A + FP_ARMV8 and needs GCC >= 9; this Buildroot's -# internal toolchain is GCC 14.x, so that floor is met with room to spare. -# NOTE it is a *tuning* choice, not an ISA restriction: the generated code -# runs on either cluster, which is the entire point of the big.LITTLE -# tuple. Do NOT "simplify" it to BR2_cortex_a76 — that would tune for the -# big core only and schedule badly on the A55s. -# -# There is no NEON/VFP stanza here, unlike the DE10's. On AArch64 Advanced -# SIMD and FP are mandatory parts of the base ISA, so Buildroot has no -# BR2_ARM_ENABLE_NEON / BR2_ARM_FPU_* knobs on this architecture at all. -# Their absence below is correct, not a dropped line. -# -# - Internal Buildroot toolchain, glibc, with C++. glibc is already the -# default C library for the internal toolchain, so it is not a line below -# (savedefconfig drops non-divergent symbols); musl is a project-wide -# non-goal. C++ IS a line, for the same reason as on the DE10: libstdc++ -# is a toolchain-provided library rather than a package, and every future -# consumer of this board (starting with any Main_MiSTer port) is C++. It -# costs one toolchain rebuild to add later and nothing to have now. -# -# - BR2_KERNEL_HEADERS_7_0: pins the headers SERIES explicitly, for exactly -# the reason the DE10 defconfig's header spells out at length — do not -# "fix" it to BR2_KERNEL_HEADERS_AS_KERNEL to keep headers in lockstep with -# the kernel. Under AS_KERNEL the kernel version arrives as the free-form -# string BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE, which kconfig cannot -# compare numerically, so BR2_TOOLCHAIN_HEADERS_AT_LEAST silently falls -# back to its 2.6 floor and glibc gets configured --enable-kernel=2.6: -# fifteen years of dead compatibility code and syscall-fallback paths for -# kernels this board will never run. -# -# WHY 7_0 AND NOT 7_2. Buildroot 2026.05.2 offers NO 7.2 headers series. -# package/linux-headers/Config.in.host tops out at BR2_KERNEL_HEADERS_7_0 -# (:55-58, resolving to 7.0.14 at :477); the series list is -# 5.10/5.15/6.1/6.6/6.12/6.18/7.0. 7_0 is therefore the newest series -# Buildroot has that is <= our 7.2.2 kernel, and headers OLDER than the -# running kernel is the supported direction — the kernel's uapi is -# forward-compatible by guarantee. The DE10's header documents the -# diff-the-uapi discipline that comes with this; the same discipline -# applies here on any kernel or Buildroot bump, and the range to diff is -# 7.0.14 -> 7.2.2. -# -# RE-CHECK ON EVERY BUILDROOT BUMP: a Buildroot bump moves the point -# release inside a series on its own (memory: buildroot-bump-moves-kernel- -# headers), and the day Buildroot adds a 7.2 series this pin should move to -# it in a deliberate commit. -BR2_aarch64=y -BR2_cortex_a76_a55=y -BR2_KERNEL_HEADERS_7_0=y -BR2_TOOLCHAIN_BUILDROOT_CXX=y - -################################################################################ -# Download integrity -################################################################################ -# -# BR2_GLOBAL_PATCH_DIR is load-bearing here for exactly ONE reason, and it is -# not patches: it is where Buildroot finds `/linux/linux.hash`, the only -# thing that hash-verifies a pinned custom kernel download. Buildroot's own -# lookup resolves the kernel's hash file to `linux/linux.hash`, a path that -# does not exist in the release (its real hashes live in -# linux/from-6.17/linux.hash, which that lookup never consults), so without -# this the 7.2.2 tarball would download with a "no hash file" WARNING and never -# be verified — and BR2_DOWNLOAD_FORCE_CHECK_HASHES cannot save you, because it -# only forces the checking of hashes that exist. The full mechanism is written -# up in the hash file's own header. -# -# THE HASH FILE IS SHARED WITH THE DE10, BY SYMLINK, ON PURPOSE. -# board/mister/de25nano/patches/linux/linux.hash is a relative symlink to -# board/mister/de10nano/patches/linux/linux.hash. That file is already the -# repo's kernel-tarball hash registry: it carries BOTH pins (the DE10's 6.18.y -# line and the 7.2.y line the RT variant tracks), its header records the -# provenance rule for each, and scripts/hash-sync-kernel.sh is its single -# automated writer. The DE25 pins 7.2.2 — the same tarball the RT variant -# already pins — so a second copy of that sha256 could only ever drift out of -# sync with the one the sync script maintains. A symlink cannot drift. -# -# CONSEQUENCE, say it out loud: bumping the DE25 kernel version below means -# editing board/mister/de10nano/patches/linux/linux.hash, a de10nano path, in -# the same commit. That cross-board reach is deliberate and is the reason this -# paragraph is this long. (The alternative — pointing BR2_GLOBAL_PATCH_DIR -# straight at the de10nano patches directory — was rejected: it would also -# hand this board the DE10's bluez5_utils patch set, and it would make the -# board directory non-self-contained, which is the specific coupling -# docs/de25-readiness-ledger.md exists to stop spreading.) -# -# The de25nano/patches/linux/ directory contains the hash symlink and nothing -# else — no global patches are applied to the kernel from here. Carried kernel -# patches live in BR2_LINUX_KERNEL_PATCH below instead. -BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/patches" -BR2_DOWNLOAD_FORCE_CHECK_HASHES=y - -################################################################################ -# Kernel — mainline 7.2.2 -################################################################################ -# -# WHY 7.2 AND NOT THE DE10'S 6.18 (docs/de25-implementation-path.md §5, and -# decision 7, which supersedes decision 6's openness): -# -# drivers/clk/socfpga/clk-agilex5.c does not exist before v6.19. Mainline -# 6.18 ships the `intel,agilex5-clkmgr` binding, the clock-ID header and the -# DT node — and no driver at all. On a 6.18 base every consumer of &clkmgr, -# mmc0 and all three gmacs included, defers forever, so the board cannot boot -# from SD. Basing on 6.18 would mean carrying a whole SoC clock driver when a -# mainline route exists one release later, which decision 5 (mainline-first, -# strongly) forbids without a justification that no mainline route existed. -# 7.2 is chosen over 6.19 because 6.19 is EOL and because this repo already -# builds and patches the 7.2.y line for the RT variant — so the DE25 is a new -# instance of an existing pattern rather than a third kernel line. -# -# The cost, stated honestly: 7.2 is not LTS, so this board inherits the RT -# beta's bump treadmill (docs/rt-beta-kernel.md). Re-open the choice if -# kernel.org designates a 7.x release longterm. -# -# The tarball is already hash-pinned in the shared linux.hash (the RT variant -# tracks the same 7.2.2), so this costs no new download and no new TOFU value. -BR2_LINUX_KERNEL=y -BR2_LINUX_KERNEL_CUSTOM_VERSION=y -BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="7.2.2" - -# Carried patches for this board. This line is wired up independently of the -# series itself, so adding or removing a patch is a one-file change and never a -# defconfig change — Buildroot applies whatever `*.patch` the directory holds, -# in `series`/sort order, and is perfectly happy with an empty one. -# -# What is expected to land here, per docs/de25-implementation-path.md: -# * decision 8's sdhci-cadence 40-bit DMA-mask patch — an UPSTREAMABLE carry, -# to be submitted, not a permanent fork, and COUPLED to the board DTS -# (the driver's bare `cdns,sd4hc` match entry has no .data, so the quirk -# needs a new match entry and therefore a new compatible string in mmc0); -# * the subset of the DE10's 40-patch series that D0.3 triaged as -# shared/portable and that a compile test against aarch64/7.2 confirms. -# Do NOT assume the DE10's linux-patches/ series applies here: 4 of the 40 -# differ in content between the main and beta series alone, and the beta -# series' 6.18-vs-7.2 re-anchoring lessons apply again for aarch64. -BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/linux-patches" - -# KERNEL CONFIG = the kernel's own arm64 defconfig + one fragment. -# -# Deliberately UNLIKE the DE10, which ships a full pinned linux.config. arm64 -# `defconfig` is the configuration mainline actually CI-tests; starting there -# means every symbol we do not name tracks upstream for free, and the delta -# stays short enough to review line by line during bring-up. Converting to a -# pinned full config the way the DE10 has one is a later decision, taken when -# the board's shape settles. -# -# NOTE THE SYMBOL NAME — this is NOT BR2_LINUX_KERNEL_USE_DEFCONFIG. -# That option means "an in-tree defconfig NAMED " and appends `_defconfig` -# to BR2_LINUX_KERNEL_DEFCONFIG (linux/linux.mk:360-361), so asking it for -# arm64's plain `defconfig` would build `defconfig_defconfig` — a file that -# does not exist. BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG is the option that -# means literally `make ARCH=arm64 defconfig` (linux.mk:362-372, and its own -# help text names ARM64 as the case it exists for). Getting this wrong fails -# late, in the kernel build, not at configure time. -BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG=y -BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/linux.fragment" - -# Uncompressed `Image`, not Image.gz. This is the Agilex/U-Boot FIT convention: -# the FIT image U-Boot loads carries its own compression metadata, so a -# pre-gzipped kernel would be either double-compressed or mislabelled. Whether -# we later gzip anything is a phase-2 decision that belongs with the U-Boot and -# genimage work (D2.2/D2.4), not here. -BR2_LINUX_KERNEL_IMAGE=y - -# DEVICE TREE — the DE25-Nano board file, authored on mainline's -# socfpga_agilex5.dtsi (it `#include`s the dtsi out of the kernel tree, so a -# custom path does not mean a from-scratch device tree). Node set and every -# per-line justification: docs/de25-dts-rationale.md; the node set itself is -# docs/de25-implementation-path.md §3.1. Depends on the two carried patches in -# board/mister/de25nano/linux-patches (0101 sdhci-cadence 40-bit mask + binding, -# 0102 intel,agilex5-svc match). Ships with the SMMU disabled for wave 1 — see -# the rationale §4 for why the SMMU-on shape cannot program the fabric on -# mainline. CUSTOM_DTS_PATH and INTREE_DTS_NAME are alternatives, not -# 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). -BR2_LINUX_KERNEL_DTS_SUPPORT=y -BR2_LINUX_KERNEL_CUSTOM_DTS_PATH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/socfpga_agilex5_de25nano.dts" - -# 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 — see the guard's comment there. - -################################################################################ -# Root filesystem -################################################################################ -# -# ext4 on p2, modest and plain: this is a developer OS, not the DE10's shipped -# linux.img. 256 MiB comfortably holds BusyBox + the toolchain runtime with -# room for a developer to scp things in. There is deliberately none of the -# DE10's ceremony here — no pinned UUID/hash-seed, no forced feature list, no -# hard-link to linux.img — because none of it has a contract to satisfy yet: -# no Downloader channel, no stock artifact to be byte-compatible with, and no -# reproducibility lane (D2.8 is where a release lane, and with it the question -# of pinning mke2fs's randomness, gets designed). -# -# Buildroot also emits a rootfs tarball by default (BR2_TARGET_ROOTFS_TAR is -# `default y`); it is left on, since it is nearly free and is the convenient -# form for `tar -x` onto a card partition before genimage exists (D2.4). -BR2_TARGET_ROOTFS_EXT2=y -BR2_TARGET_ROOTFS_EXT2_4=y -BR2_TARGET_ROOTFS_EXT2_LABEL="rootfs" -BR2_TARGET_ROOTFS_EXT2_SIZE="256M" - -# Reproducibility groundwork, not a byte-identical-image guarantee yet. It pins -# SOURCE_DATE_EPOCH and the kernel's KBUILD_BUILD_* stamps, so Image and the -# module tree rebuild identically from the same commit; rootfs.ext4 does NOT, -# because mke2fs's UUID and hash seed are still random here (the DE10 pins -# them via _MKFS_OPTIONS; this defconfig does not yet — see the ext2 note -# above). Cheap, and the release lane (D2.8, "attested artifacts") will want -# it; adopting it now means the first release is not the commit that -# discovers what BR2_REPRODUCIBLE changes. -BR2_REPRODUCIBLE=y - -################################################################################ -# System configuration -################################################################################ - -# Merged /usr (/bin, /sbin, /lib are symlinks into /usr). Same as the DE10, and -# for a forward-looking reason: kernel modules install under usr/lib/modules/ -# either way, and any future variant-module overlay for this board has to agree -# with the main image about that path or depmod's indexes point nowhere. -BR2_ROOTFS_MERGED_USR=y - -# Serial console. The DE25-Nano's header UART is the HPS **uart1** -# (`serial@10c02100`, `snps,dw-apb-uart`, 16550-compatible) — not uart0, -# which is the SoC Development Kit's console. The board DTS enables only -# uart1 and aliases it `serial0` with `stdout-path = "serial0:115200n8"` -# (board/mister/de25nano/socfpga_agilex5_de25nano.dts, "Console UART"; every -# reference tree agrees, docs/de25-dts-rationale.md U1 [V]). It is the only -# enabled 8250 port, so it is ttyS0 whichever numbering rule 8250 applies. -# 115200 8N1 is what every reference asks for and what the board's USB-UART -# bridge uses, so anything else means a garbled login prompt rather than a -# missing one. -# -# This is the ONE line whose failure mode is "the board looks dead". The kernel -# side of it comes from the fragment (8250 + 8250_DW + 8250_CONSOLE, all =y) -# and the console= argument comes from U-Boot's bootargs, which is phase 2 — -# so a first boot may need `console=ttyS0,115200` passed by hand. -BR2_TARGET_GENERIC_GETTY_PORT="ttyS0" -BR2_TARGET_GENERIC_GETTY_BAUDRATE_115200=y - -BR2_TARGET_GENERIC_HOSTNAME="de25" -BR2_TARGET_GENERIC_ISSUE="Welcome to MiSTer DE25-Nano (developer OS)" - -# Empty = passwordless root, the same posture as the DE10 image. On this board -# it is additionally the only way in: there is no network provisioning, no -# authorized_keys, and no per-device SSH host-key machinery yet (ADR 0015 is a -# DE10 rootfs-overlay feature and is not carried here). -BR2_TARGET_GENERIC_ROOT_PASSWD="" - -################################################################################ -# Packages — none, deliberately -################################################################################ -# -# BusyBox is Buildroot's own default (BR2_PACKAGE_BUSYBOX is `default y` in -# package/busybox/Config.in) and so does not appear as a line here; it is the -# entire userland. Nothing else is enabled: no MiSTer packages, no DE10 -# packages, no out-of-tree WiFi or controller drivers, no debug tooling. -# -# When something is eventually needed here, add it in a commit that says which -# task authorised it — the empty package list is the D2.1/D2.7 scope decision -# made visible, and a package added "just to have it" quietly repeals that -# decision. - -################################################################################ -# NOT HERE YET — phase 2 (D2.2 / D2.4) -################################################################################ -# -# BOOTLOADER. There is no BR2_TARGET_ARM_TRUSTED_FIRMWARE and no -# BR2_TARGET_UBOOT in this file, so `make de25` produces a kernel + rootfs and -# nothing that can boot them. Both are planned for D2.2, and the shape is -# already settled by docs/de25-boot-chain.md and de25-implementation-path.md §6: -# we build `u-boot.itb` ONLY, from mainline, and the factory SPL in QSPI is -# NEVER touched (posture 1 — the SDM on this board cannot boot from the microSD -# at all, so the QSPI seam is permanent and any write to it is a brick risk with -# no recovery path). ATF comes in as the BL31 that goes inside that FIT. -# -# SD-CARD IMAGE. genimage-sdcard-de25.cfg + a fail-closed check script are D2.4. -# Two partitions, fixed by the factory SPL's CONFIG_SPL_FS_FAT + boot partition -# 1: p1 FAT (the FIT and the DTB), p2 the ext4 root built above. There is -# explicitly NO shared SD card with the DE10 (decision 4). diff --git a/configs/mister_initramfs_defconfig b/configs/mister_initramfs_defconfig index fb90b2f..8285f53 100644 --- a/configs/mister_initramfs_defconfig +++ b/configs/mister_initramfs_defconfig @@ -1,98 +1,42 @@ -# mister_initramfs_defconfig — STAGE 1 of the two-stage build (TASKS.md P1.10 / A1, -# PLAN.md §5, docs/decisions/0002-initramfs.md). +# 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. # -# This config builds ONE artifact: output-initramfs/images/rootfs.cpio, a few hundred -# KB of static BusyBox plus our /init. The main build (mister_de10nano_defconfig) 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. -# -# ############################################################################ -# # 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 the trap A1 exists to name. Two configs, one cpio. # -# ############################################################################ -# -# Why this is a whole second Buildroot config and not a flag on the first one: the two -# rootfses have opposite requirements. The target rootfs must be glibc/shared, because -# the stock MiSTer binary is linked against it (ADR 0001, abi-contract §1.3). This one -# must be static, because it lives inside the zImage and every byte is a byte of kernel. -# BR2_STATIC_LIBS is not even offered with glibc ("static only needs a toolchain w/ -# uclibc or musl" — Buildroot Config.in:684), so the C library choice differs too. None -# of that 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. +# 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 (ADR 0001). Not an ABI requirement here -# — it just has to run on a Cortex-A9. +# --- 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. -# 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. +# --- 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 directly; there is no -# /sbin/init, no inittab and no S-scripts in stage 1. +# --- No init system: the kernel execs /init from the cpio (§8.3) --- BR2_INIT_NONE=y -# --- Device nodes: dynamic/devtmpfs. This is not cosmetic. Buildroot's fs/cpio/cpio.mk -# only mknod's /dev/console (c 5 1) in the NON-static branch, and the kernel needs -# that node to exist *before* /init runs or /init has no stdin/stdout/stderr and the -# rescue shell is unreachable. Choosing STATIC device creation here would also make -# cpio.mk symlink /init -> sbin/init instead of leaving ours alone. +# --- 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 (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. +# --- 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" - -# --- /init itself. BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/initramfs-overlay" -# --- fsck.exfat, for the on-demand repair path (ADR 0026). -# -# /media/fat is exFAT, has no journal, and is never cleanly unmounted -- not even -# by the OSD's own "Reboot", which is a direct write to the HPS reset controller -# (Main:fpga_io.cpp:588-605) that never reaches init's ::shutdown entries. So the -# volume-dirty flag (fs/exfat/super.c:512) is effectively always set and nothing -# ever repairs the lost clusters an unlucky power cut leaves behind. -# -# The initramfs is the ONLY place a repair can run. The root filesystem is -# linux/linux.img, a file *on* the partition being checked, so once the system is -# up the block device can never be released: fsck.exfat's repair mode opens -# O_RDWR|O_EXCL and a mount holds an exclusive bdev claim (fs/super.c:1617) -# whether it is ro or rw. Check-then-boot, or not at all. -# -# It runs ONLY when Scripts/check_storage.sh has left a marker, never on a plain -# boot -- see the fsck_if_requested() section of the initramfs /init for why the -# dirty flag alone is not a usable trigger. -# -# The only dependency is BR2_USE_WCHAR, which musl satisfies; the installer -# config (configs/mister_installer_defconfig) already builds this package on the -# same musl+static toolchain. initramfs-post-build.sh then deletes the five -# binaries we do not use -- see there for why that is done in post-build. +# --- 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. -# Uncompressed is deliberate (docs/boot-chain.md I3). The cpio ends up inside the -# zImage, which is itself LZ4-compressed (CONFIG_KERNEL_LZ4, stock parity), so -# compressing it here would just be compressing it twice — and it would additionally -# require a matching CONFIG_RD_* decompressor in the kernel. `copy` is the default in -# the kernel's usr/Makefile for a plain .cpio, so this needs nothing on the kernel side. +# --- Output: a cpio, uncompressed (§8.7) --- BR2_TARGET_ROOTFS_CPIO=y # BR2_TARGET_ROOTFS_TAR is not set -# --- Reproducibility (A9). 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. +# --- Reproducibility, A9 (§8.8) --- BR2_REPRODUCIBLE=y diff --git a/configs/mister_installer_defconfig b/configs/mister_installer_defconfig index 2964d7e..bfc1572 100644 --- a/configs/mister_installer_defconfig +++ b/configs/mister_installer_defconfig @@ -1,130 +1,47 @@ # mister_installer_defconfig — the THROWAWAY INSTALLER OS (TASKS.md P5.3, -# docs/decisions/0020-sdcard-exfat-reformat-installer.md, PLAN.md §8/§9). +# 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 +# exactly what the reformat-and-handoff /init needs. Standalone Buildroot +# config, not a fragment stack. Rationale: docs/buildroot-config.md §9. # -# This config builds ONE artifact: output-installer/images/rootfs.cpio, a static -# BusyBox + exfatprogs + util-linux(sfdisk) rootfs that scripts/mk-sdcard.sh embeds -# into a SECOND, dedicated kernel build to produce the installer's zImage_dtb. That -# image ships as `linux/zImage_dtb` on the shipped sdcard.img's FAT32 partition — it -# is NEVER output/images/zImage_dtb (the real MiSTer kernel) and NEVER runs on a -# card that has already been installed (ADR 0020 §2.1: the reformat replaces this -# kernel with the real one, which is the primary re-run guard). -# -# WHY THIS EXISTS AT ALL (read ADR 0020 §1 first): mr-fusion's "auto-resize" is not -# a filesystem grow-in-place — MiSTer's data partition is exFAT, and Linux has no -# resize-in-place tool for exFAT. So a fresh card ships small (fast write, small -# download) and an on-device first-boot installer OS repartitions + reformats it to -# fill whatever medium the user actually has, then hands off to the real MiSTer. -# This config is that installer OS's rootfs. Its /init (a separate task, -# board/mister/de10nano/installer-overlay/init) does the sfdisk/mkfs.exfat/copy-back/ -# MAC-gen/dd-uboot.img/reboot dance described in ADR 0020 §2. -# -# --- Relationship to configs/mister_initramfs_defconfig (STAGE 1) --- -# 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 (arch/toolchain/init/device-creation/ -# output/reproducibility all copied verbatim — see that file's header for the -# reasoning behind each) and adds exactly what the installer's job needs on top: -# -# - BR2_PACKAGE_EXFATPROGS -> mkfs.exfat (ADR 0020 §2 step 3; -n MiSTer_Data) -# - BR2_PACKAGE_UTIL_LINUX + BINARIES -> sfdisk (repartition) and blkid (belt-and- -# suspenders re-run guard, ADR 0020 §2.1). Buildroot's util-linux "basic set" is -# not further sub-selectable — enabling it for sfdisk also pulls in blkid, -# blockdev, dmesg, findfs, hexdump, mkfs, wipefs etc. as a bundle. That overlaps -# some BusyBox applets (dmesg, findfs) already in the stage-1 BusyBox set; the -# overlap is harmless (both are real, separate binaries; the installer's /init -# names whichever it wants) and is NOT a reason to drop either side. -# - A few extra BusyBox applets stage 1 does not need: cp (for the payload -> -# tmpfs -> exFAT copies), dd (uboot.img -> the 0xA2 partition), reboot (the -# final handoff), blockdev and hexdump (MAC-address generation from -# /dev/urandom). See board/mister/de10nano/installer-busybox.config's header -# for exactly which CONFIG_ symbols that required and why. -# -# What it deliberately does NOT add: e2fsprogs. The installer only ever `cp`'s -# linux/linux.img as an opaque byte blob (never fscks or resizes its ext4 contents; -# that partition's size is fixed at build time, only the exFAT data partition -# around it grows) and never builds one, so there is nothing for e2fsprogs to do -# here. Re-add it if a later /init revision needs to inspect/repair linux.img. -# -# ############################################################################ -# # NOT BR2_TARGET_ROOTFS_INITRAMFS. NOT the main defconfig with a flag. # -# # Same trap A1 names for stage 1 (see mister_initramfs_defconfig) applies # -# # here verbatim: a fourth Buildroot output dir (output-installer/), a # -# # fourth small cpio, never the ~300 MB target rootfs. # -# ############################################################################ +# WARNING: NOT BR2_TARGET_ROOTFS_INITRAMFS, NOT the main config with a flag — +# a fourth small cpio in its own O=, never the ~300 MB target rootfs. -# --- Arch/ABI: same silicon as every other build on this board (ADR 0001). Not an -# ABI requirement here either — the installer runs on the same Cortex-A9 the -# production system does, briefly, once, in RAM. +# --- Arch/ABI: same silicon as every other build on this board (§8.1, §9) --- 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. Same reasoning as stage 1: static musl permits -# BR2_STATIC_LIBS (glibc does not offer it — Buildroot Config.in:684), and -# everything in this cpio is deleted from RAM within seconds of the reformat -# finishing, so there is no ABI surface to keep glibc-compatible for. -# BR2_TOOLCHAIN_USES_MUSL auto-selects BR2_USE_WCHAR, which is what -# BR2_PACKAGE_EXFATPROGS's "depends on BR2_USE_WCHAR" needs — nothing extra to -# set for that. +# --- Toolchain: musl, static-only (§8.2, §9) --- 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 directly, exactly as in -# stage 1 — no /sbin/init, no inittab, no S-scripts. Unlike stage 1, this /init -# never switch_roots into anything: it does its reformat dance and calls -# `reboot` directly (ADR 0020 §2, last step). PID 1 must still never exit -# without warning — see board/mister/de10nano/installer-overlay/init (a -# separate task) for the rescue-shell contract this config's BusyBox set exists -# to support (CTTYHACK/SETSID/ASH_TEST/FEATURE_SH_MATH are all carried over from -# stage 1 for exactly that reason). +# --- No init system: /init reformats and calls reboot directly (§8.3, §9) --- BR2_INIT_NONE=y -# --- Device nodes: dynamic/devtmpfs, same load-bearing reasoning as stage 1 (see -# that file's header) — /dev/console must exist before /init runs, or there is -# no stdin/stdout/stderr and a rescue shell is unreachable. +# --- 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 -# --- Target packages: the installer-only additions on top of BusyBox. ----------- - -# mkfs.exfat -n MiSTer_Data (ADR 0020 §2 step 3). Depends on BR2_USE_WCHAR, which -# the musl toolchain choice above already selects. +# --- Target packages: the installer-only additions on top of BusyBox (§9) --- +# mkfs.exfat (ADR 0020 §2 step 3); sfdisk + blkid via util-linux's one +# non-sub-selectable "basic set". No e2fsprogs, deliberately. BR2_PACKAGE_EXFATPROGS=y - -# sfdisk (repartition to the real medium size) + blkid (re-run guard: skip the -# reformat if the data partition is already exFAT labelled MiSTer_Data and already -# holds linux/linux.img). Buildroot's util-linux only offers this as one "basic -# set" bool — see this file's header comment for what else rides along with it. BR2_PACKAGE_UTIL_LINUX=y BR2_PACKAGE_UTIL_LINUX_BINARIES=y -# --- BusyBox: stage 1's minimal config PLUS the installer-specific applets -# (cp -a, dd, reboot, blockdev, hexdump). See that file's header for how it was -# generated and which symbols are load-bearing, and this file's header for -# which ones were added on top and why. +# --- BusyBox: stage 1's config PLUS the installer applets; /init overlay (§9) --- BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/installer-busybox.config" - -# --- /init itself. A SEPARATE overlay from stage 1's — this is a different -# program with a different job (reformat-and-handoff, not mount-and- -# switch_root). Not yet written as of this config (a sibling task); this line -# is the fixed interface the rest of the sdcard-image plumbing (mk-sdcard.sh, -# the installer kernel relink) targets. BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/installer-overlay" -# --- Output: a cpio, uncompressed. Same reasoning as stage 1: this cpio is -# embedded inside the installer's own zImage (itself LZ4-compressed, -# CONFIG_KERNEL_LZ4, stock parity), so compressing it here would compress it -# twice and need a matching CONFIG_RD_* decompressor for no benefit. `copy` is -# the kernel usr/Makefile's default for a plain .cpio — nothing extra needed on -# the kernel-config side of the relink either. +# --- Output: a cpio, uncompressed (§8.7, §9) --- BR2_TARGET_ROOTFS_CPIO=y # BR2_TARGET_ROOTFS_TAR is not set -# --- Reproducibility. The cpio ends up embedded in the installer zImage_dtb that -# ships inside sdcard.img(.xz) release assets, so the same double-build -# rationale P4.3 applies to the main build applies here too. +# --- Reproducibility (§8.8, §9) --- BR2_REPRODUCIBLE=y diff --git a/configs/mister_kernel_defconfig b/configs/mister_kernel_defconfig deleted file mode 100644 index feed862..0000000 --- a/configs/mister_kernel_defconfig +++ /dev/null @@ -1,123 +0,0 @@ -# mister_kernel_defconfig — the shared KERNEL-ONLY base for kernel variants -# (docs/rt-beta-kernel.md, ADR 0021 as amended 2026-07-18). -# -# This is NOT a second image. It exists so a kernel variant (`make rt`, and any -# future sibling) can build a zImage_dtb + a depmod'd module tree in its own -# O= WITHOUT rebuilding the ~300 MB userland: same toolchain, same kernel -# stanza, no target packages. The per-variant delta lives in -# configs/mister_.fragment, merged on top by the top-level Makefile -# (same merge_config.sh mechanism the old full-image variant used). With NO -# fragment merged, this file builds the main 6.18 kernel — which is exactly -# what makes the lockstep below testable. -# -# LOCKSTEP: every BR2_ symbol this file shares with mister_de10nano_defconfig -# MUST carry the identical value, or the variant kernel is built by a different -# toolchain / from different sources than the image it plugs into. -# scripts/check-kernel-defconfig-sync.sh asserts exactly that (CI runs it for -# every kernel variant before any cache is restored, and build.yml runs it as -# a lint next to the patch-header lint). Edit the main defconfig's toolchain -# or kernel stanza -> mirror it here in the same commit, or CI fails in -# seconds. - -# --- Toolchain & arch/ABI — copied from mister_de10nano_defconfig ------------ -# (see that file's header for the full derivation; abi-contract.md §1). The -# kernel itself does not need C++ (BR2_TOOLCHAIN_BUILDROOT_CXX), but the stanza -# is mirrored WHOLE on purpose: partial mirroring is how the two toolchains -# drift. The sync script compares values where a symbol exists in both files -# AND asserts the toolchain-family name sets (BR2_arm*/BR2_ARM_*/BR2_cortex*/ -# BR2_KERNEL_HEADERS*/BR2_TOOLCHAIN_BUILDROOT_*) match — choice symbols carry -# their value in the NAME, so dropping or one-sidedly bumping one of these -# lines fails CI rather than silently narrowing the lockstep. -BR2_arm=y -BR2_cortex_a9=y -BR2_ARM_ENABLE_NEON=y -BR2_ARM_ENABLE_VFP=y -BR2_ARM_FPU_NEON=y -BR2_KERNEL_HEADERS_6_18=y -BR2_TOOLCHAIN_BUILDROOT_CXX=y - -# --- System shape ------------------------------------------------------------ -# Merged /usr, same as the main image: kernel modules physically install under -# usr/lib/modules// (lib -> usr/lib), which is the exact path the main -# rootfs uses — the module tree copied out of this build's target/ must line up -# byte-for-path with where work/extra-modules-overlay/ drops it into the main -# image, or depmod's indexes point nowhere. -BR2_ROOTFS_MERGED_USR=y - -# No init system, no shell, no BusyBox: this target rootfs is never booted (it -# exists only to receive the module install + depmod). All THREE lines are -# needed: init/sh choices default to BusyBox, and BR2_PACKAGE_BUSYBOX is -# `default y` in its own right (package/busybox/Config.in), so without the -# explicit not-set it quietly builds anyway — verified by loading this -# defconfig through kconfig with and without the line. -BR2_INIT_NONE=y -BR2_SYSTEM_BIN_SH_NONE=y -# BR2_PACKAGE_BUSYBOX is not set - -# --- Download integrity — same posture as the main defconfig ----------------- -# BR2_GLOBAL_PATCH_DIR is load-bearing even with no packages: it is where -# Buildroot finds board/mister/de10nano/patches/linux/linux.hash, the ONLY -# thing that hash-verifies a pinned custom kernel download (see that file's -# header for why Buildroot's own lookup misses its shipped hashes). -BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/patches" -BR2_DOWNLOAD_FORCE_CHECK_HASHES=y - -# --- Reproducibility — same posture as the main defconfig -------------------- -# Not cosmetic even though this rootfs never ships: linux.mk gates -# KBUILD_BUILD_VERSION/USER/HOST/TIMESTAMP on BR2_REPRODUCIBLE -# (work/buildroot/linux/linux.mk:169-175), so without it the variant kernel -# bakes the build machine's real hostname/user/wallclock into its UTS banner — -# and a release.yml re-run on the same tag (a flow that workflow explicitly -# supports) would mint a byte-different zImage_dtb- and SHA256SUMS -# line, unlike every other shipped binary. As a symbol defined in both files it -# also falls under check-kernel-defconfig-sync.sh's value comparison for free. -BR2_REPRODUCIBLE=y - -# --- Kernel stanza — mirrored from mister_de10nano_defconfig ----------------- -# Base-without-fragment = the main 6.18 kernel (version, patches, config, -# compression, DTS — all identical). A variant fragment overrides only what it -# must (e.g. mister_rt.fragment: version -> the 7.2 line, patch dir -> the beta -# subset, one kernel-config fragment). The stage-1 initramfs cpio is embedded -# by external.mk's LINUX_KCONFIG_FIXUP_CMDS hook exactly as in the main build -# (it keys on BR2_LINUX_KERNEL=y, not on which defconfig) — which is why the -# Makefile's kernel-variant targets depend on `initramfs`: without the cpio the -# kernel kconfig-fixup fails hard, and a variant zImage without it would panic -# on the FAT root at boot (docs/boot-chain.md I1/I2). -BR2_LINUX_KERNEL=y -BR2_LINUX_KERNEL_CUSTOM_VERSION=y -BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.49" -BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux-patches" -BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y -BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux.config" -BR2_LINUX_KERNEL_LZ4=y -BR2_LINUX_KERNEL_ZIMAGE=y -BR2_LINUX_KERNEL_DTS_SUPPORT=y -BR2_LINUX_KERNEL_INTREE_DTS_NAME="intel/socfpga/socfpga_cyclone5_de10nano" - -# --- Host kmod with xz ------------------------------------------------------- -# linux.config sets CONFIG_MODULE_COMPRESS_XZ=y, so the .ko land as .ko.xz and -# the build-time depmod (LINUX_RUN_DEPMOD, a target-finalize hook) must be able -# to read them — same line, same reason, as the main defconfig. -BR2_PACKAGE_HOST_KMOD_XZ=y - -# --- Rootfs: tar, and ONLY tar ----------------------------------------------- -# Deliberate, and load-bearing twice over: -# 1. depmod runs from LINUX_TARGET_FINALIZE_HOOKS (linux.mk), and -# target-finalize only runs when at least one rootfs image is built — -# tar is the cheapest one there is. Drop this and the module tree ships -# WITHOUT modules.dep/modules.alias, i.e. no autoload on device. -# 2. the tar doubles as the module-transport artifact: CI's build-kernel legs -# tar target/usr/lib/modules into the kernel artifact that the main build -# job unpacks into work/extra-modules-overlay/. -# No ext2/no linux.img here — this rootfs is never flashed or shipped, which is -# also why there is NO BR2_ROOTFS_POST_BUILD_SCRIPT (post-build.sh only bakes -# /MiSTer.version into a rootfs that ships; this one doesn't). -BR2_TARGET_ROOTFS_TAR=y - -# --- Post-image: zImage_dtb assembly ----------------------------------------- -# Same script, same BR_DIR-relative path idiom as the main defconfig (post- -# image scripts run with work/buildroot/ as CWD). It cats zImage+DTB into -# images/zImage_dtb and runs scripts/check-zimage-dtb.sh; its linux.img half -# self-gates on BR2_TARGET_ROOTFS_EXT2 (absent here), so a kernel-only build -# gets the kernel artifact and its contract check with nothing else. -BR2_ROOTFS_POST_IMAGE_SCRIPT="../../board/mister/de10nano/post-image.sh" diff --git a/configs/mister_rt.fragment b/configs/mister_rt.fragment index 371fcdb..adfe355 100644 --- a/configs/mister_rt.fragment +++ b/configs/mister_rt.fragment @@ -1,118 +1,28 @@ -# mister_rt.fragment — the RT / Linux-7.2 "beta" kernel variant. -# -# ⚠ 7.2-rc4 booted and ran MiSTer on real hardware (2026-07-20), and 7.2-rc7 -# again on 2026-08-14; the currently pinned 7.2 FINAL has NOT been booted. -# Every version bump re-opens that question — the variant is beta precisely -# because the pin moves faster than hardware testing. That is now a slower -# clock than it was: as of 2026-08-17 this pin tracks the 7.2.y line, not -# mainline, so the next bump is a stable point release rather than the next -# -rc. See docs/rt-beta-kernel.md for status, design, and the open TODOs. -# -# This is the BUILDROOT-config layer of the variant, layered on the KERNEL-ONLY -# base configs/mister_kernel_defconfig at build time by `make rt` (Buildroot's -# support/kconfig/merge_config.sh). Everything not listed here is inherited -# from that base unchanged — same armv7-a / Cortex-A9 toolchain, same -# 6.18-pinned kernel headers (so the userland ABI is identical), rootfs-tar -# only. Three things change: the kernel version, its patch set, and its -# kernel-config delta. (The base has NO packages, so the old full-image -# variant's "disable the 7.x-incompatible OOT WiFi drivers" lines are gone — -# there is nothing to disable; the OOT-WiFi story for RT is in -# docs/rt-beta-kernel.md §4.) -# -# Do not confuse the two fragment layers: THIS file is Buildroot config (BR2_*); -# board/mister/de10nano/linux-rt.fragment (named below) is KERNEL config -# (CONFIG_*) and is where CONFIG_PREEMPT_RT actually lives. -# -# Adding a future kernel variant `foo` = a sibling configs/mister_foo.fragment -# like this one + foo/foo-clean/... Makefile targets + one CI matrix entry. +# mister_rt.fragment — the RT / Linux-7.2 "beta" kernel variant: the +# BUILDROOT-config layer, merged on the de10nano-kernel fragment stack +# (common + de10nano + kernel-only, configs/fragments/stacks.mk) by `make rt`. +# Everything not listed here is inherited from that stack unchanged. +# Rationale, status and the boot/verification history: docs/buildroot-config.md §7 +# and docs/rt-beta-kernel.md. +# +# WARNING: the pinned 7.2.y release has NOT been booted on hardware; every +# version bump re-opens that question. `make rt-clean` is MANDATORY before +# `make rt` on a bump. The kernel-config (CONFIG_*) layer, where +# CONFIG_PREEMPT_RT actually lives, is board/mister/de10nano/linux-rt.fragment. -# --- The 7.2 kernel ----------------------------------------------------------- -# Just override the version value; the base defconfig already sets -# BR2_LINUX_KERNEL_CUSTOM_VERSION=y. -# -# 2026-08-17: this pin CROSSED THE -rc BOUNDARY. 7.2 released on 2026-08-16 and -# the value below is now a plain mainline release, not a snapshot. Three things -# changed with it, and all three are why the crossing was a deliberate commit -# rather than one more automated -rc bump: -# -# 1. The ARTIFACT. linux/linux.mk branches on the literal substring "-rc" -# (linux/linux.mk:35). While it matched, Buildroot fetched a cgit-generated -# snapshot — linux-.tar.GZ from https://git.kernel.org/torvalds/t. -# Without it, the ordinary release tarball linux-7.2.tar.XZ comes from -# $(BR2_KERNEL_MIRROR)/linux/kernel/v7.x — the series directory is computed -# as v$(firstword $(subst ., ,$(LINUX_VERSION))).x, so a two-component "7.2" -# resolves to v7.x correctly. Verified against the real mirror, not assumed. -# 2. The PROVENANCE. kernel.org publishes no signed manifest for an -rc, so -# the old hash was TOFU. linux-7.2.tar.xz IS covered by the PGP-signed -# sha256sums.asc; linux.hash's own header records the transcription and the -# signature check. Strictly stronger — do not reintroduce a TOFU value here -# without moving back to an -rc, which this pin no longer does. -# 3. The KERNEL RELEASE STRING, hence the module directory: 7.2.0-rc7 -> 7.2.0. -# 7.2's own Makefile reads VERSION=7 PATCHLEVEL=2 SUBLEVEL=0 EXTRAVERSION= -# (checked in the pristine tarball). Note the asymmetry, which is exactly -# the thing that looks like a mistake in a diff and is not: the TARBALL is -# two-component (linux-7.2.tar.xz — kernel.org publishes no linux-7.2.0), -# while the kernel it builds calls itself three-component (7.2.0). Both -# spellings below are correct and neither is a typo for the other. -# -# On a version bump, `make rt-clean` is MANDATORY before `make rt` — the old -# kernel tree survives in output-rt/build/ otherwise and `rt` refuses to guess -# which of two trees to validate (see the Makefile's rt recipe). -# -# COUPLED to board/mister/de10nano/patches/linux/linux.hash: the base config's -# BR2_DOWNLOAD_FORCE_CHECK_HASHES empties Buildroot's BR_NO_CHECK_HASH_FOR -# exemption, so the tarball MUST have a sha256 line there or the build fails -# closed at download. Bump the version here -> update that line in the same -# commit (its header says where the value must come from). +# --- The 7.2 kernel: the ONE allowlisted override of the base stack's version (§7.1) --- +# WARNING: coupled to board/mister/de10nano/patches/linux/linux.hash — bump +# both in one commit (Renovate + renovate-hash-sync.yml do this for a .y bump). +# This pin is a signed-manifest release, strictly stronger than the TOFU hash +# the old -rc pins had: do NOT reintroduce a TOFU value here without moving +# back to an -rc (the sync script refuses -rc values; only a hand pin can). BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="7.2.3" -# --- Beta patch set ----------------------------------------------------------- -# linux-patches-beta/ is a series-file-driven SUBSET of linux-patches/: symlinks -# to the shared patch files, EXCEPT 0001, 0015, 0030 and 0037, which are real -# re-anchored copies — Buildroot applies patches with `patch -F0` (fuzz zero), -# and those four patches' 6.18 context or APIs drifted upstream (see the -# series file's header; note 0015 was once wrongly believed upstreamed in -# 7.2 — it is not: 7.2 has no FAML/FAMR controller types, and 0037 was once -# wrongly written off as cosmetic — it is not, it shifts the DualSense button -# indices). 0031 was a fifth -# until 2026-07-25, when the SHARED patch was re-anchored onto context both -# trees agree on and the copy became a symlink again — see the series header -# for why that is the preferred move over a re-anchored copy. The shared 6.18 -# patches are otherwise deliberately untouched, keeping them byte-identical -# to stock. -# The series drops exactly ONE shared patch, and only because 7.2 already has -# it: 0047-btusb-mercusys-ma530-2c4e-0115, a backport of mainline ce21a5cf3d1f -# (Mercusys MA530/MA550H, USB 2c4e:0115) whose first release IS 7.2. The 6.18 -# image needs it because 6.18.y never received the commit; this kernel does not, -# and listing it would not be harmlessly redundant -- at -F0 against pristine -# v7.2 the hunk FAILS ("Hunk #1 FAILED at 786"), which would break the build. -# It goes away on its own the day the stock pin leaves 6.18.y. Nothing else is -# dropped: all 40 entries (the other 36 shared + the four beta-local patches -# 0043/0044/0045 -- the UIO set -- and 0046, the ramoops -# crash-record reservation) apply to 7.2 FINAL at -F0 -- verified 2026-08-17 -# through Buildroot's own apply-patches.sh against a freshly extracted pristine -# linux-7.2.tar.xz whose sha256 matched the signed manifest: 40/40 applied, -# exit 0, ZERO hunks taking fuzz (80 hunks land at an offset, which -F0 -# permits). No re-anchor was needed anywhere: the four re-anchored copies -# (0001, 0015, 0030, 0037) carry over unchanged and, with all four beta-local -# patches, land at zero offset. -# 0038-0042 were the last gap -- listed nowhere in the beta series from -# 2026-07-24 until 2026-08-17, on the ASSUMPTION they would need re-anchoring. -# Measured, they needed none: plain symlinks, clean at -F0, and drivers/hid/ -# cross-compiles for ARM with all five in (hid-nintendo.o and hid-playstation.o -# both build, zero warnings). The three runs that day nest: 34/34 (70 offsets) -# on the rc7 -> 7.2 bump, 35/35 (70) once 0046 landed, 40/40 (80) with -# 0038-0042 symlinked in. See docs/rt-beta-kernel.md §2 and §6. -# And BUILT, not just applied: `make rt` is green on all 40 from a clean tree -# (2026-08-17) -- exit 0, release 7.2.0, CONFIG_PREEMPT_RT=y, zImage_dtb -# 9303550 bytes, 90 modules. Still NOT BOOTED; that is per-version and open. -# (This line previously read "drops only 0030 + 0037 ... all 29 listed", which -# went stale when those two were re-anchored and re-included; 0037 in -# particular is NOT cosmetic -- see the series header. It then read "drops -# NOTHING, full stop" from 2026-08-17 until 2026-08-24, when 0047 landed in the -# shared dir. The 40/40 measurement below is unaffected: 0047 was never in the -# series, so the run that produced it is still a run of the whole series.) +# --- Beta patch set: the series-file-driven subset of linux-patches/ (§7.2) --- +# WARNING: the series deliberately DROPS 0047 (already in 7.2; at -F0 its hunk +# FAILS against pristine v7.2 — "Hunk #1 FAILED at 786") and carries four +# re-anchored copies (0001/0015/0030/0037); see the series header. BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux-patches-beta" -# --- RT + 7.x kernel-config delta, layered on the shared linux.config --------- +# --- RT + 7.x kernel-config delta, layered on the shared linux.config (§7.3) --- BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux-rt.fragment" diff --git a/docs/abi-contract.md b/docs/abi-contract.md index c53a537..88af495 100644 --- a/docs/abi-contract.md +++ b/docs/abi-contract.md @@ -268,7 +268,7 @@ is the point of the project. **Not a hazard, but write it down — `libz.so.1`'s *provider* is not classic zlib.** Buildroot models zlib as a **virtual package** with a Kconfig `choice` (`package/zlib/Config.in`): `BR2_PACKAGE_LIBZLIB` is upstream's default, `BR2_PACKAGE_ZLIB_NG` -is the other arm. This image selects **zlib-ng 2.3.3** (`configs/mister_de10nano_defconfig`, +is the other arm. This image selects **zlib-ng 2.3.3** (`configs/fragments/de10nano-image.fragment`, compression block, where the blast-radius measurement is recorded). That choice is invisible to every row above: diff --git a/docs/azcopy.md b/docs/azcopy.md index 5bf57c4..9f322b7 100644 --- a/docs/azcopy.md +++ b/docs/azcopy.md @@ -20,7 +20,7 @@ this tree authors one. It is also **the first and only Go package in the tree**, has consequences of its own (§5). > **The default image does not contain azcopy.** The package builds and is fully -> wired, but `configs/mister_de10nano_defconfig` leaves `BR2_PACKAGE_AZCOPY` unset, +> wired, but `configs/fragments/de10nano-image.fragment` leaves `BR2_PACKAGE_AZCOPY` unset, > for the size reason in §1. To get azcopy onto a MiSTer today you either flip that > line and rebuild, or drop the released binary onto `/media/fat` yourself — which is > exactly how every test in §4 was run. @@ -29,7 +29,7 @@ has consequences of its own (§5). ## 1. Size and cost — read this first -**The package is present but NOT enabled** in `configs/mister_de10nano_defconfig`, and +**The package is present but NOT enabled** in `configs/fragments/de10nano-image.fragment`, and size is the only reason. At 39.1 MiB installed it would be the second-largest package in the image after samba4, spending about a fifth of the free space `linux.img` has left, for a tool most users will never run. It works — §4 is a transcript of it working @@ -235,7 +235,7 @@ all of it is fetched on a cold build, and all of it is inside the hash. ### Turning it on, and turning it back off -Uncomment `BR2_PACKAGE_AZCOPY` in `configs/mister_de10nano_defconfig`. Turning it off +Uncomment `BR2_PACKAGE_AZCOPY` in `configs/fragments/de10nano-image.fragment`. Turning it off again is deleting that line: nothing in the image depends on azcopy — no init script starts it, no other package links it, no parity test asserts it. `BR2_PACKAGE_CA_CERTIFICATES` and diff --git a/docs/buildroot-config.md b/docs/buildroot-config.md new file mode 100644 index 0000000..0dcec47 --- /dev/null +++ b/docs/buildroot-config.md @@ -0,0 +1,3080 @@ +# Buildroot configuration — fragments, stacks, and the reasoning behind every line + +This document is the rationale for everything under `configs/`. The fragments +themselves carry only one-line section pointers (`§N.M` below) and a short +`WARNING:` next to each trap symbol; **every fact, measurement and citation +that used to live as an inline comment in the monolithic defconfigs is here**, +organised by fragment and section. When a fragment line changes, the matching +section here changes in the same commit. + +History: until 2026-09 the DE10-Nano image was `configs/mister_de10nano_defconfig` +(~2,000 lines, three-quarters comments), the kernel-only base was a hand-mirrored +copy in `configs/mister_kernel_defconfig`, and the DE25-Nano developer OS was a +standalone `configs/mister_de25nano_defconfig`. All three were split into the +fragment stacks described in §1; the resolved `.config` of every stack was proved +byte-identical to what the old files produced before they were deleted (§11). + +Contents: + +1. [Layout and mechanism](#1-layout-and-mechanism) +2. [`common.fragment`](#2-commonfragment) +3. [`de10nano.fragment`](#3-de10nanofragment) +4. [`kernel-only.fragment`](#4-kernel-onlyfragment) +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) +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) + +--- + +## 1. Layout and mechanism + +``` +configs/ + fragments/ + stacks.mk the ONE place that says which fragments form which config + common.fragment policy shared by every board and every kernel variant + de10nano.fragment DE10-Nano board layer: arch/ABI, headers series, kernel stanza + 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 only + 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) +``` + +`stacks.mk`, in merge order: + +| Stack | Fragments | Output dir | Make entry point | +|---|---|---|---| +| `de10nano` | `common de10nano de10nano-image` | `output/` | `make de10nano-defconfig` (and `make all`) | +| `de10nano-kernel` | `common de10nano kernel-only` | — (base only) | used by every kernel variant | +| `de25nano` | `common de25nano` | `output-de25/` | `make de25nano-defconfig` (and `make de25`) | +| `rt` (variant) | `de10nano-kernel` + `configs/mister_rt.fragment` | `output-rt/` | `make rt` | + +**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 +`/.config` (the first fragment is its base file, each later one is merged +on; it warns on any symbol a later fragment redefines), then `make olddefconfig` +resolves every symbol the fragments do not mention to its Kconfig default. That +resolves to the **byte-identical** `.config` that `make mister__defconfig` +produced — the only difference is `BR2_DEFCONFIG`, which is where +`savedefconfig` writes (§11). + +**Consequences worth knowing** + +- `make savedefconfig` now writes `output/defconfig` (Buildroot's default when + `BR2_DEFCONFIG` names no file) instead of clobbering a tracked file. Fold a + `menuconfig` experiment back into the right fragment by hand. This is an + improvement: the old monolith's header warned that every `savedefconfig` + round-trip silently dropped its comments and scattered its deliberately + contiguous blocks (the debug-tooling block above all), and it kept happening. +- The old target names (`make mister_de10nano_defconfig`, + `mister_kernel_defconfig`, `mister_de25nano_defconfig`) print a pointer to the + new ones and fail; CI's configure step is `make de10nano-defconfig`. +- `output/.config` is generated once and then never touched by `make all` (no + file prerequisites on the rule, so a `menuconfig` edit is not silently + discarded — the Makefile's own comment explains). Regenerate deliberately + with `make de10nano-defconfig` / `make de25nano-defconfig` / `make rt-clean`. +- A symbol belongs in **exactly one** fragment of a stack. A symbol set in + `common` and overridden per board should have been board-only in the first + place; `scripts/check-config-fragments.sh` fails on any redefinition (the + rt fragment's kernel version + patch-dir overrides are the one allowlisted + exception, §7). +- 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. +- Adding a board = a new `.fragment` (+ optionally `-image`), a + `_FRAGMENTS` line in `stacks.mk`, 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 + 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 + `ALLOWED_OVERRIDES` lists). +- `merge_config.sh` matches symbols with `grep -w`, so a fragment comment that + quotes a symbol name defined by an EARLIER fragment is harmless (only real + `BR2_X=` / `# BR2_X is not set` lines in the later fragment count as + definitions), but keep comments in the fragments to one-line pointers anyway: + the old files' verbatim-quoted symbols are exactly what produced double + matches for Renovate's regex managers (`renovate.json`'s header) and for + `scripts/hash-sync-kernel.sh` (bug #42). +- `# BR2_X is not set` lines are configuration, not commentary: `conf` reads + them as an explicit `=n`. Several are load-bearing (§4.1, §5.19, §5.22, + §5.24). Do not "clean them up". + +--- + +## 2. `common.fragment` + +Buildroot policy that every stack — both boards **and** the kernel-only base — +sets identically. Seven symbols. §10 explains why several symbols that look +shared (`BR2_TARGET_GENERIC_ROOT_PASSWD`, the ext4 rootfs choice) are +deliberately *not* here. + +### 2.1 Toolchain: C++ — `BR2_TOOLCHAIN_BUILDROOT_CXX=y` + +`libstdc++.so.6` is a *toolchain*-provided library, not a package +(`docs/abi-contract.md` §2.2 rows L5/L6 say so explicitly), and T8 requires it +to export `GLIBCXX_3.4.21` + `CXXABI_1.3.9`. Main_MiSTer is C++, so this is +non-optional for the project — and since it is a toolchain knob rather than a +package it belongs with the toolchain (P1.2) rather than in P2.1's package set. +GCC 14.3 satisfies T8 with enormous margin (T8 needs only GCC >= 5.1); verified +by `readelf` in the P1.2 acceptance run. + +The kernel itself does not need C++, but the kernel-only stack keeps it on +purpose: partial mirroring is how two toolchains drift, and the whole toolchain +stanza is what the lockstep check compares (§4). + +On the DE25 the same reasoning applies: every future consumer of that board +(starting with any Main_MiSTer port) is C++. It costs one toolchain rebuild to +add later and nothing to have now. + +glibc is not a line anywhere: it is already the default C library for the +internal toolchain (`toolchain-buildroot`'s own `default +BR2_TOOLCHAIN_BUILDROOT_GLIBC`) — see `docs/decisions/0001-toolchain.md` for the +full evaluation (internal vs Bootlin external). musl is a non-goal (PLAN §3) and +would not even start the stock binary (`abi-contract.md` §1.3). + +### 2.2 Download integrity — `BR2_DOWNLOAD_FORCE_CHECK_HASHES=y` + +Empties Buildroot's `BR_NO_CHECK_HASH_FOR` exemption, so every download — +the pinned custom kernel tarball above all — MUST have a hash line or the +build fails closed at download time. It only forces the checking of hashes +that *exist*: where Buildroot finds the kernel's hash file is a board-fragment +matter (`BR2_GLOBAL_PATCH_DIR`, §3.3 and §6.3), and a kernel-version bump must +update `board/mister/de10nano/patches/linux/linux.hash` in the same commit +(that file's header says where the value must come from; the RT fragment's +§7.1 records the coupling in detail). + +### 2.3 Kernel: pinned custom version, DTS support + +`BR2_LINUX_KERNEL=y`, `BR2_LINUX_KERNEL_CUSTOM_VERSION=y`, +`BR2_LINUX_KERNEL_DTS_SUPPORT=y`. Every stack builds a kernel from an +explicitly pinned version (the value itself is per board, §3.4 / §6.4, and is +what Renovate bumps) and ships a device tree. The kernel-only stack exists +precisely to build this kernel with no userland (§4). + +### 2.4 Reproducibility — `BR2_REPRODUCIBLE=y` + +Byte-identical builds from the same commit (P2.5's "done when"). Exports +`SOURCE_DATE_EPOCH` (pinned to `work/buildroot`'s OWN last commit date — +top-level buildroot `Makefile:538-540` — constant as long as that pinned tree +doesn't change) and, via `fs/common.mk`'s `ROOTFS_REPRODUCIBLE` hook, touches +every `TARGET_DIR` file to it before any rootfs image is built. Does NOT, by +itself, pin mke2fs's UUID/hash-seed — see §5.2 for why those are pinned +separately on the DE10. + +For the kernel-only stack it is not cosmetic even though that rootfs never +ships: `linux.mk` gates `KBUILD_BUILD_VERSION/USER/HOST/TIMESTAMP` on +`BR2_REPRODUCIBLE` (`work/buildroot/linux/linux.mk:169-175`), so without it the +variant kernel bakes the build machine's real hostname/user/wallclock into its +UTS banner — and a `release.yml` re-run on the same tag (a flow that workflow +explicitly supports) would mint a byte-different `zImage_dtb-` and +`SHA256SUMS` line, unlike every other shipped binary. + +For the DE25 it is reproducibility groundwork, not a byte-identical-image +guarantee yet: it pins `SOURCE_DATE_EPOCH` and the kernel's `KBUILD_BUILD_*` +stamps, so `Image` and the module tree rebuild identically from the same +commit; `rootfs.ext4` does NOT, because mke2fs's UUID and hash seed are still +random there (the DE10 pins them via `_MKFS_OPTIONS`; the DE25 does not yet — +§6.6). Cheap, and the release lane (D2.8, "attested artifacts") will want it; +adopting it now means the first release is not the commit that discovers what +`BR2_REPRODUCIBLE` changes. + +See also `docs/reproducibility.md` and `docs/decisions/0018-db-json-version-is-release-date-driven.md`. + +### 2.5 Merged /usr — `BR2_ROOTFS_MERGED_USR=y` + +`/bin`, `/sbin`, `/lib` are symlinks into `/usr`. Stock parity, and +load-bearing: Buildroot's own `support/scripts/check-merged -t overlay -u` +validates `BR2_ROOTFS_OVERLAY`'s shape against this at target-finalize, so +flipping it off would start rejecting the DE10 rootfs-overlay tree. + +For the kernel-only stack: kernel modules physically install under +`usr/lib/modules//` (`lib -> usr/lib`), which is the exact path the main +rootfs uses — the module tree copied out of that build's `target/` must line up +byte-for-path with where `work/extra-modules-overlay/` drops it into the main +image, or depmod's indexes point nowhere. + +For the DE25, the same forward-looking reason: any future variant-module +overlay for that board has to agree with its main image about that path. + +--- + +## 3. `de10nano.fragment` + +The DE10-Nano **board layer**: everything a kernel variant must agree with the +shipped image on — arch/ABI, the headers series, the kernel stanza, the patch +and hash registry, the build-time depmod knob and the post-image script. Shared +by the `de10nano` and `de10nano-kernel` stacks; `scripts/check-kernel-defconfig-sync.sh` +fails if any symbol of these families appears in a fragment only one stack uses. + +### 3.1 Arch / ABI — `BR2_arm`, `BR2_cortex_a9`, `BR2_ARM_ENABLE_NEON`, `BR2_ARM_ENABLE_VFP`, `BR2_ARM_FPU_NEON` + +arm / cortex-a9 / NEON / VFPv3 / EABIhf reproduces the stock `MiSTer` binary's +own `readelf -A` tags (`abi-contract.md` §1.1, T1-T4). `BR2_ARM_FPU_NEON` is the +Buildroot FPU choice that reproduces "Tag_FP_arch: VFPv3" + +"Tag_Advanced_SIMD_arch: NEONv1" together (gcc `-mfpu=neon` — NEON mandates the +32-register VFPv3 variant; cortex-a9 only `select`s +`BR2_ARM_CPU_MAYBE_HAS_{NEON,VFPV3}`, so `ENABLE_NEON`/`ENABLE_VFP` are both +required or Buildroot silently falls back to a narrower FPU). + +EABIhf itself is NOT a line because it is already Buildroot's default the +moment a CPU with an FPU is selected (`arch/Config.in.arm`: `default +BR2_ARM_EABIHF if BR2_ARM_CPU_HAS_FPU`) — savedefconfig correctly drops it as +non-divergent; verified present via readelf in the P1.2 acceptance run. + +This is the P1.2 toolchain & arch/ABI work; PLAN.md §3 / `docs/abi-contract.md` +§1 are where the requirements come from, `docs/decisions/0001-toolchain.md` is +the toolchain evaluation. (The P1.2 defconfig deliberately shipped no rootfs +*packages* beyond Buildroot's own defaults — BusyBox + the toolchain's own +runtime libraries: libc, the post-2.34 libpthread/librt compat stubs, +libstdc++, installed by the toolchain, not by package selection. The ten +DT_NEEDED *packages* — zlib, bzip2, libpng, freetype, imlib2, bluez, L7-L12 — +were P2.1's job, §5.) + +The stage-1 initramfs (§8) and the installer (§9) pin the same five lines — +same silicon; not an ABI requirement there, it just has to run on a Cortex-A9. + +### 3.2 Kernel headers SERIES — `BR2_KERNEL_HEADERS_6_18=y` + +Pins the headers SERIES explicitly. This overrides Buildroot's own default of +`BR2_KERNEL_HEADERS_AS_KERNEL` (`package/linux-headers/Config.in.host:5`), and +the override is load-bearing. **DO NOT "fix" it to AS_KERNEL to keep the headers +in lockstep with the kernel** — verified by A/B-ing the defconfig through `make +defconfig`: + +``` +BR2_KERNEL_HEADERS_6_18=y -> BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.18" +BR2_KERNEL_HEADERS_AS_KERNEL -> BR2_TOOLCHAIN_HEADERS_AT_LEAST="2.6" +``` + +glibc is configured `--enable-kernel=$(BR2_TOOLCHAIN_HEADERS_AT_LEAST)` +(`package/glibc/glibc.mk:131`). Under AS_KERNEL our kernel version arrives as +the free-form string `BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, which Kconfig +cannot compare numerically to select `BR2_TOOLCHAIN_HEADERS_AT_LEAST_6_18` — so +it silently falls back to the floor, 2.6. That would build glibc with ~15 years +of dead compatibility code and runtime syscall-fallback paths for kernels this +board will never run, and drop every 6.18-era fast path. The series pin is the +only way Buildroot learns the headers version here. + +(An earlier version of this rationale justified the pin with "this defconfig +does not build a kernel itself" — that was simply false, `BR2_LINUX_KERNEL=y` +is set. Right setting, wrong reason, which is how it nearly got reverted.) + +ACCEPTED CONSEQUENCE: the series resolves to whatever 6.18.x Buildroot pins for +it (`package/linux-headers/Config.in.host`, the `default "6.18.34" if +BR2_KERNEL_HEADERS_6_18` line — 6.18.34 at the time of writing, moved from +6.18.33 by the Buildroot 2026.02 -> 2026.05 bump) while the kernel is newer +(6.18.40 when this was last re-checked), so the headers lag the kernel +slightly. That is correct and harmless: headers older than the running kernel +is the supported direction, because the kernel's uapi is forward-compatible by +guarantee. Re-checked 2026-07-25 for the 6.18.40 bump: the uapi delta between +6.18.34 and 6.18.40 is FOUR added lines across THREE files, all additive — + +- `include/uapi/linux/bpf.h` — two explicit `__u32 :32;` pads (`bpf_prog_info`, `bpf_map_info`) +- `include/uapi/linux/tee.h` — one explicit `__u32 :32;` pad +- `include/uapi/linux/if_link.h` — one new enum member, `IFLA_BOND_LACP_STRICT`, appended before `__IFLA_BOND_MAX` + +`arch/arm/include/uapi/` is byte-identical. The `__u32 :32;` lines make padding +the compiler was ALREADY inserting explicit (both structs are +`__attribute__((aligned(8)))`), so they are not an ABI change at all; the +bonding netlink attribute is additive and this board does not use bonding, tee +or bpf uapi. Checked with `git diff v6.18.34 v6.18.40 -- include/uapi/ +arch/arm/include/uapi/` (equivalently: diff the two extracted tarballs over +those two paths). + +**RE-CHECK THAT DIFF ON ANY BUMP, patchlevel included — of the KERNEL or of +BUILDROOT**, since the 2026.05 bump moved the headers end of the range on its +own. It is tempting to assume only a series bump can move uapi; every line +above disproves it, since 6.18.34 -> 6.18.40 is itself a patchlevel range. +Stable rules discourage uapi changes but do not forbid them, so the diff is +the authority, not the version numbers. (This block was last found stale by +Copilot review on PR #67: it still said 6.18.33/6.18.38 after the kernel had +moved to 6.18.40 and Buildroot had moved the headers pin to 6.18.34 — neither +of which Renovate can rewrite, because both live in prose. The kernel pin has +moved again since — read it off the fragment, not off this paragraph.) + +Also note linux-headers only applies `BR2_LINUX_KERNEL_PATCH` / +`BR2_GLOBAL_PATCH_DIR` under AS_KERNEL (`package/linux-headers/linux-headers.mk:82`), +so the series pin means our carried patches do not reach the headers tree. +Verified irrelevant: no patch in `board/mister/de10nano/linux-patches/` +MODIFIES a header under `include/uapi` or `arch/arm/include/uapi` (checked +against the patches' `+++ b/` target paths: zero hits). Some do mention uapi +headers in prose — 0001 cites `include/uapi/linux/fb.h` to explain an ioctl +number — but none change one; the series is drivers, one DTS, and fs/exfat. + +The same point is made from the other side in §6.2: a Buildroot bump moves +the point release inside a headers series on its own (2026.02 -> 2026.05 moved +this one from 6.18.33 to 6.18.34 with no kernel bump at all), which is why the +re-check discipline above names Buildroot bumps as well as kernel bumps. + +### 3.3 Global patch dir = the kernel-tarball hash registry — `BR2_GLOBAL_PATCH_DIR` + +`$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/patches`. Load-bearing even +in the kernel-only stack, which has no packages to patch: it is where Buildroot +finds `board/mister/de10nano/patches/linux/linux.hash`, the ONLY thing that +hash-verifies a pinned custom kernel download (see that file's header for why +Buildroot's own lookup misses its shipped hashes — it resolves the kernel's +hash file to `linux/linux.hash`, a path that does not exist in the release; the +real hashes live in `linux/from-6.17/linux.hash`, which that lookup never +consults). That file is the repo's kernel-tarball hash registry: it carries +both the DE10's 6.18.y line and the 7.2.y line the RT variant and the DE25 +track, and `scripts/hash-sync-kernel.sh` is its single automated writer. The +DE25's patch dir reaches it by symlink (§6.3). + +It also carries the DE10's `bluez5_utils` patch set, which is the reason the +DE25 does NOT point at this directory (§6.3). + +### 3.4 Kernel stanza + +``` +BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.48" (Renovate-managed) +BR2_LINUX_KERNEL_PATCH=".../board/mister/de10nano/linux-patches" +BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y +BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=".../board/mister/de10nano/linux.config" +BR2_LINUX_KERNEL_LZ4=y +BR2_LINUX_KERNEL_ZIMAGE=y +BR2_LINUX_KERNEL_INTREE_DTS_NAME="intel/socfpga/socfpga_cyclone5_de10nano" +``` + +- The version is the 6.18 LTS line; the exact patch level is deliberately not + repeated in prose anywhere because stable `.y` releases land weekly. + `renovate.json`'s `kernel-longterm-6.18` manager rewrites this ONE line (the + kernel-only stack shares this fragment, so the old "bump both defconfigs" + hazard is gone), and `.github/workflows/renovate-hash-sync.yml` refreshes the + companion hash in `linux.hash` from kernel.org's signed `sha256sums.asc`. + `scripts/ci-tests.sh`, `scripts/hash-sync-kernel.sh`, + `scripts/export-kernel-tree.sh`, `scripts/lint-kernel-patches.sh`, + `scripts/test-initramfs.sh` and `scripts/test-sdcard-install.sh` all read the + value off this fragment (anchored to `^`, last match — bug #42). +- `linux-patches/` is the carried MiSTer series (README "Repository layout"; + `docs/kernel-recon/`). The beta variant substitutes its own subset dir (§7.2). +- `linux.config` is a MINIMAL defconfig: an absent `CONFIG_X` is NOT "off" — + read the resolved `output/build/linux-*/.config`. It is shared with the + kernel-only stack and so with every kernel variant (their `CONFIG_*` deltas + are `BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES` fragments, §7.3). The stage-1 + initramfs cpio is NOT named here — `external.mk`'s `LINUX_KCONFIG_FIXUP_CMDS` + hook injects `CONFIG_INITRAMFS_SOURCE` at kconfig-fixup time (it keys on + `BR2_LINUX_KERNEL=y` + `BR2_arm=y`, not on which stack), because + `package/pkg-kconfig.mk:19-20` makes `make linux-update-defconfig` / + `linux-savedefconfig` HARD-FAIL as soon as any fragment file is configured, + and those are the commands that regenerate `linux.config`. That is also why + the Makefile's kernel-variant targets depend on `initramfs`: without the + cpio the kernel kconfig-fixup fails hard, and a variant zImage without it + would panic on the FAT root at boot (`docs/boot-chain.md` I1/I2). +- LZ4 compression + zImage + the in-tree Cyclone V DE10-Nano DTS: stock parity + (`docs/boot-chain.md`, A3). U-Boot loads `zImage_dtb`, which §3.6 assembles. + +### 3.5 Host kmod with xz — `BR2_PACKAGE_HOST_KMOD_XZ=y` + +`linux.config` sets `CONFIG_MODULE_COMPRESS_XZ=y`, so the `.ko` land as +`.ko.xz` and the build-time depmod (`LINUX_RUN_DEPMOD`, a target-finalize hook) +must be able to read them. HOST kmod, "support xz-compressed modules" — one of +two different things despite the shared prefix; the TARGET half +(`BR2_PACKAGE_KMOD_TOOLS`, depmod/insmod/lsmod/modinfo/modprobe/rmmod on the +device) is an image matter, §5.30. Both stacks need this one, so it lives here. + +### 3.6 Post-image: zImage_dtb assembly — `BR2_ROOTFS_POST_IMAGE_SCRIPT` + +P1.11 (A3): `../../board/mister/de10nano/post-image.sh` assembles `zImage_dtb` +(plain `cat zImage dtb`) and asserts its U-Boot contract +(`scripts/check-zimage-dtb.sh`) after every image build. Post-image scripts run +with `$(BR_DIR)` (`work/buildroot/`) as CWD (`system/Config.in`: "executed from +the main Buildroot source directory"), so this path is relative to THAT +directory, not `BR2_EXTERNAL` — verified against the working reference wrapper +at `/mnt/source/sb-enema` (`../../sb_enema/board/sb-enema/post-image.sh` from a +BR_DIR one level deeper than ours). A build-time checker failure here fails +`make all` (Buildroot's Makefile runs post-image scripts as plain recipe lines; +a nonzero exit stops make). + +The same script serves the kernel-only stack: its `linux.img` half self-gates +on `BR2_TARGET_ROOTFS_EXT2` (absent there), so a kernel-only build gets the +kernel artifact and its contract check with nothing else. `post-image.sh` also +hard-links `rootfs.ext2` to `linux.img` for the image (§5.2). + +--- + +## 4. `kernel-only.fragment` + +Turns a board stack into the shared KERNEL-ONLY base that kernel variants +(`make rt`, and any future sibling; CI's `build-kernel` legs) build against +(`docs/rt-beta-kernel.md`, ADR 0021 as amended 2026-07-18). + +**This is NOT a second image.** It exists so a variant can build a `zImage_dtb` ++ a depmod'd module tree in its own `O=` WITHOUT rebuilding the ~300 MB +userland: same toolchain, same kernel stanza, no target packages. The +per-variant delta lives in `configs/mister_.fragment`, merged on top by +the top-level Makefile. With NO variant fragment merged, this stack builds the +main 6.18 kernel — which is exactly what makes the lockstep testable. + +**LOCKSTEP.** Every toolchain/kernel symbol this stack shares with the image +MUST carry the identical value, or the variant kernel is built by a different +toolchain / from different sources than the image it plugs into. Before the +fragment split this was a hand-mirrored copy held in line by +`scripts/check-kernel-defconfig-sync.sh` comparing two files; a copy can drift, +and the failure mode of drift is the quiet kind (wrong `-mcpu`, wrong headers). +Now the two stacks share `common` + `de10nano` by construction, and the check +guards the construction instead: (0) every toolchain/kernel-family symbol must +live in a fragment BOTH stacks use, (1) any symbol both stacks define must +agree, (2) the sentinels (arch, CPU, headers series, C++) must be present, (3) +choice-family name sets must match — because a kconfig CHOICE carries its +value in its NAME, so a headers or CPU bump on one side drops the old name and +adds a new one and no shared symbol ever disagrees. `scripts/check-config-fragments.sh` +(c) then proves the RESOLVED configs still agree once package `select`s are in +play (§11). CI runs the text-level check for every kernel variant before any +cache is restored and both checks in `build.yml`'s `lint-config` job. + +Known follow-up, and it has now HAPPENED ONCE: the DE25 kernel pin (§6.4) +has no Renovate manager and shares `linux.hash` by symlink (§6.3), so an rt +bump handled by `hash-sync-kernel.sh --pin=rt` replaces the 7.2.y hash line +rather than adding one — and the DE25, still pinned to the old point release, +loses the only hash that verifies its tarball. 2026-09-02: Renovate's rt bump +7.2.2 -> 7.2.3 did exactly that; the DE25 pin was moved to 7.2.3 in the same +series of commits and the two are back in step (the RT variant and the DE25 +have always pinned the same tarball, which is the whole reason the hash file +is shared). Had they not been moved together the DE25 build would have failed +CLOSED at download — the fail-safe direction, but a failure nonetheless. +Either a DE25 manager with the same depName as rt (one PR moves both) or a +hash-sync rule that keeps every line a fragment still pins is the standing +fix; neither is taken here. + +### 4.1 No init, no shell, no BusyBox — `BR2_INIT_NONE`, `BR2_SYSTEM_BIN_SH_NONE`, `# BR2_PACKAGE_BUSYBOX is not set` + +This target rootfs is never booted (it exists only to receive the module +install + depmod). All THREE lines are needed: init/sh choices default to +BusyBox, and `BR2_PACKAGE_BUSYBOX` is `default y` in its own right +(`package/busybox/Config.in`), so without the explicit not-set it quietly +builds anyway — verified by loading the config through kconfig with and without +the line. The not-set line is configuration, not a comment (§1). + +### 4.2 Rootfs: tar, and ONLY tar — `BR2_TARGET_ROOTFS_TAR=y` + +Deliberate, and load-bearing twice over: + +1. depmod runs from `LINUX_TARGET_FINALIZE_HOOKS` (`linux.mk`), and + target-finalize only runs when at least one rootfs image is built — tar is + the cheapest one there is. Drop this and the module tree ships WITHOUT + `modules.dep`/`modules.alias`, i.e. no autoload on device. +2. the tar doubles as the module-transport artifact: CI's `build-kernel` legs + tar `target/usr/lib/modules` into the kernel artifact that the main build + job unpacks into `work/extra-modules-overlay/`. + +No ext2/no `linux.img` here — this rootfs is never flashed or shipped, which +is also why there is NO `BR2_ROOTFS_POST_BUILD_SCRIPT` (`post-build.sh` only +bakes `/MiSTer.version` into a rootfs that ships; this one doesn't) and why the +ext4 symbols are an image-fragment matter rather than a `common` one (§10). +The Makefile's `rt` recipe checks for exactly one module tree under +`output-rt/target/usr/lib/modules/` and points here if it finds zero. + +--- + +## 5. `de10nano-image.fragment` + +Everything that makes the DE10-Nano stack the SHIPPED MiSTer image rather than +a kernel-only build: board hooks, the ext4 `linux.img` contract, the full +package set, and system configuration. Nothing in it may touch the toolchain +or the kernel stanza (those are §3); package selection is by design invisible +to CI's toolchain-cache fingerprint (`docs/ci.md#toolchain-fingerprint`). + +### 5.1 Board hooks: post-build script, rootfs overlays + +`BR2_ROOTFS_POST_BUILD_SCRIPT="../../board/mister/de10nano/post-build.sh"` — +same `$(BR_DIR)`-relative path idiom as the post-image script (§3.6); +`post-build.sh` stamps `/MiSTer.version` and applies parity fixups. + +`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_ROOTFS_OVERLAY`, just pointed at the full-rootfs overlay tree instead of +the initramfs one. + +The SECOND entry (ADR 0021 as amended 2026-07-18) is the gitignored +`work/extra-modules-overlay/`, where kernel-variant builds stage their depmod'd +`usr/lib/modules//` trees (`make rt` locally; CI's build-kernel +artifacts) so the ONE shipped `linux.img` carries every variant's modules. +Empty -> byte-identical image; the Makefile's `all` `mkdir -p`'s it because +Buildroot fails on a missing overlay path. NOTE: this line is part of the +toolchain-fingerprint deny-list residue in `.github/actions/buildroot-build` (it +is neither `BR2_PACKAGE_` nor `BR2_LINUX_KERNEL`), so ADDING it busted the +br-host cache exactly once — one deliberate ~3h cold main build on the PR +that introduced it. Changing it again would do the same. + +### 5.2 Image generation: ext4 `linux.img`, reproducible (P2.5, A9) + +Mechanism: `BR2_TARGET_ROOTFS_EXT2` (ext4 variant, `BR2_TARGET_ROOTFS_EXT2_4`), +NOT genimage. The stock artifact (`linux/linux.img`) is a single loop-mounted +ext4 filesystem with NO partition table — our `/init` losetup's + mounts it +directly (`docs/boot-chain.md`; confirmed on hardware in P1.13). genimage +exists to assemble MULTI-PARTITION disk images (MBR/GPT, bootloader + several +filesystems); here there is exactly one filesystem and nothing else to lay +out, so it would add a whole config layer to produce byte-for-byte what +`BR2_TARGET_ROOTFS_EXT2` already produces directly — Buildroot's own manual +recommends going straight to the fs/ target when no partition table is +needed, which is this case. `post-image.sh` (extended, not forked) hard-links +the resulting `output/images/rootfs.ext2` to `output/images/linux.img` (see +that script for why `rootfs.ext2`, not the `rootfs.ext4` convenience symlink +Buildroot also creates, is the canonical name here). + +`BR2_TARGET_ROOTFS_EXT2_LABEL="rootfs"` — stock parity; some tooling keys on +the volume label. Kconfig's own default is already "rootfs"; explicit so a +future Buildroot default change can't silently change it under us. + +`BR2_TARGET_ROOTFS_EXT2_SIZE="512M"` — 512 MiB (task text), not stock's +375 MiB. P2.1's full rootfs, once actually built into this ext4 image (with +journal/inode-table/GDT overhead), used ~198 MiB of the 512 -> measured 61.4% +free (`dumpe2fs -h`: 80433 free / 131072 total blocks), comfortably above +P2.7's 15% floor, with headroom for A8 (the updater mounts the NEW image rw and +copies 5 user-files into `/etc` before flashing) and for future package growth +without another resize. (Later measurements: §5.41, §5.45.) + +`BR2_TARGET_ROOTFS_EXT2_INODE_SIZE=256` — Buildroot's own default (256). +DELIBERATE divergence from stock's 128: a 128-byte inode hits the Y2K38 +timestamp problem (`mke2fs(8)` says so explicitly). Costs us nothing — +inode_size is invisible to the mount/ABI contract. (An int-type Kconfig symbol +can't carry a trailing same-line comment the way the bool/string ones can — +`conf --defconfig` treats anything after the digits as part of the value and +rejects it; verified via the "invalid for BR2_TARGET_ROOTFS_EXT2_INODE_SIZE" +warning this produced before the note was moved off the line.) + +`BR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS` — pinned explicitly, not left to mke2fs +defaults. Three independent reasons, each checked against the real stock +artifact (`work/extracted/files/linux/linux.img`) and this Buildroot's actual +e2fsprogs 1.47.3, not assumed: + +1. FEATURE SET. `dumpe2fs -h` on the real stock `linux.img` gives exactly: + `has_journal ext_attr resize_inode dir_index filetype extent 64bit flex_bg + sparse_super large_file huge_file dir_nlink extra_isize metadata_csum`. + This build's e2fsprogs 1.47.3 default `ext4` fs_type + (`output/host/etc/mke2fs.conf`) gives that SAME 14 features PLUS + `metadata_csum_seed` and `orphan_file` — two features e2fsprogs added to its + own defaults some time after stock's image was built. That is the "drift + across e2fsprogs versions" the task warned about, caught in the act: left to + defaults, THIS build already diverges from stock's feature set, and a future + e2fsprogs bump could add more. So every feature is forced on or off + explicitly (`^`), not inherited from `mke2fs.conf`. This also overrides + `fs/ext2/Config.in`'s own default of `-O ^64bit` (chosen upstream for + pre-2017.02 U-Boot bootloaders that can't read a 64bit ext4). Irrelevant + here: U-Boot never reads `linux.img` at all (only `uboot.img`/`zImage_dtb` + off the FAT partition — `docs/boot-chain.md`); `linux.img` is loop-mounted + by the KERNEL, which has supported the 64bit feature since 3.18. Stock + itself ships 64bit ON, confirming the kernel side is fine with it. +2. UUID (`-U`) and directory-hash-seed (`-E hash_seed=`) are BOTH + `uuid_generate()` — i.e. `/dev/urandom`-backed and RANDOM — when not given + explicitly (this e2fsprogs's `misc/mke2fs.c:3325` and `:3348`), and the hash + seed is written into the superblock at creation time regardless of whether + any directory actually becomes htree-indexed. Leaving either implicit makes + the image's own superblock bytes non-reproducible even with + `BR2_REPRODUCIBLE=y` and an identical `TARGET_DIR` — `BR2_REPRODUCIBLE` only + pins `SOURCE_DATE_EPOCH` (timestamps, below); it does not touch mke2fs's own + UUID/seed generation. The two fixed values are two separate + `/proc/sys/kernel/random/uuid` draws, pinned once and never regenerated — + deliberately DIFFERENT from stock's own UUID + (`50ef310c-47b9-4c1c-a2fe-d0202d02b6b4`) so a user who still has a stock + SD-card backup lying around never has two filesystems with the identical + UUID visible to the same host at once. +3. `-b 4096`: already what `mke2fs.conf`'s `[defaults]` section would pick for + an image this size, made explicit for the same "don't trust the defaults to + hold across an e2fsprogs bump" reason as (1). + +`SOURCE_DATE_EPOCH` (via `BR2_REPRODUCIBLE`, §2.4) covers the remaining source +of mke2fs non-determinism: this e2fsprogs (`lib/ext2fs/initialize.c`) reads +`SOURCE_DATE_EPOCH` for the filesystem's own created/last-write superblock +timestamps, and `fs/common.mk`'s `ROOTFS_REPRODUCIBLE` hook touches every file +under `TARGET_DIR` to that same timestamp before ANY rootfs image (tar or ext2) +is generated — so file mtimes inside the image are pinned too, and so is file +ORDERING to the extent it's driven by `TARGET_DIR`'s own (stable, +un-mutated-between-builds) directory order — see the P2.5 acceptance run for +the actual two-build byte-identical proof. + +`scripts/check-linux-img.sh` asserts this whole contract against the built +image (label, UUID, hash seed, size, sorted feature set); its expected values +MUST be kept in sync with this fragment by hand — there is no single source +of truth to derive them from at build time. See also `docs/reproducibility.md`. + +### 5.3 P2.1 — the full package set: provenance and preamble + +Source of truth: `docs/package-manifest.md` §6 "Ready-to-paste BR2_PACKAGE_* +list" (P0.7's deliverable). §5.4–§5.23 are that list, applied verbatim, EXCEPT +for the imlib2 loader sub-options (§5.6) which P0.7 did not include — it mapped +SONAMEs, not `dlopen()`'d plugins, and `abi-contract.md` §2.2 explicitly warns +imlib2's loaders are invisible to a DT_NEEDED/SONAME scan (they're dlopen'd +from `usr/lib/imlib2/loaders/*.so` at runtime) and must be turned on by hand or +`menu.png`/`menu.jpg` backgrounds silently fail to load. Verified against +`docs/stock-inventory/shared-libraries.md`'s on-device loader list +(argb/bmp/bz2/ff/gif/ico/id3/jpeg/lbm/png/pnm/tga/xpm/zlib) — everything +except gif/id3/jpeg/png/tiff builds into imlib2 unconditionally with no +Buildroot Config.in gate, so enabling those five reproduces stock's loader set +exactly (`package/imlib2/Config.in`, checked against the then-pinned Buildroot +2026.02.3 and NOT re-checked since the 2026.05 bump — a dated finding, not a +standing guarantee). + +Every symbol was cross-checked against the actual pinned Buildroot tree's +`Config.in` files (not from memory) before being pasted — see the P2.1 task's +verification pass. `BR2_PACKAGE_UTIL_LINUX_BINARIES` IS enabled (§5.11): stock +actually ships the real util-linux mount/umount/blkid/fdisk/dmesg/agetty/... +(2.36.2 ELF binaries, not BusyBox — verified against `work/imgroot`), so +shipping the util-linux programs is the parity-correct choice. The earlier +"covered by BusyBox" note was wrong about what stock shipped. See +`docs/util-linux-parity.md`. Samba's AD DC / ADS / smbtorture sub-options are +deliberately left unset (standalone file server only, manifest §5 Drop list) — +BusyBox and every other package is otherwise the full manifest, ungated. + +`BR2_ENABLE_LOCALE` is deliberately NOT listed even though the manifest +recommends it: it is already `=y` in the built `output/` (glibc's own default +for this toolchain, confirmed in `output/.config`) and it lives in the +*toolchain* Kconfig menu — P1.2's hazard ("Buildroot silently ignores +toolchain menu changes on an incremental build") means touching that menu at +all is a make-clean-and-rebuild-from-scratch decision. Since the effective +value doesn't change (y -> y), adding it buys nothing and only invites that +risk for zero benefit. `BR2_ENABLE_LOCALE` only compiles locale *support* into +glibc; it does not generate any locale *data*. That is a separate knob — §5.44 +— and leaving it empty is what shipped an image with no +`/usr/lib/locale/locale-archive` at all. + +### 5.4 compression + +- `BR2_PACKAGE_ZLIB=y` — meta-prompt (the virtual package). +- `BR2_PACKAGE_ZLIB_NG=y` — NOT `BR2_PACKAGE_ZLIB` alone: the concrete + provider. zlib-ng in `ZLIB_COMPAT` mode, not classic zlib. `package/zlib`'s + Config.in is a virtual package with a choice between `BR2_PACKAGE_LIBZLIB` + and `BR2_PACKAGE_ZLIB_NG`; `ZLIB_NG_ARCH_SUPPORTS` is `default y if BR2_arm` + (and `BR2_aarch64`, so this survives a 64-bit port). `zlib-ng.mk` builds + `-DZLIB_COMPAT=1`, so it installs `libz.so.1` and every consumer follows + transparently. + + BLAST RADIUS, measured on the rig before making the switch. Inside + Main_MiSTer the dynamic libz is used by exactly two things: + `support/uef/uef_reader.cpp` (gzip-wrapped UEF tape images — BBC Micro / + Acorn Electron only), and libpng16/Imlib2 for the OSD background images and + boot logo (`video.cpp:3822+`). It is NOT used for CHD: stock compiles libchdr + in statically and that copy includes ``, whose compat macros rewrite + `inflate -> mz_inflate`, so every CD core decodes through miniz inside the + binary. Verified from the published release binary, not just the Makefile. + + Verified on-device with the library preloaded: PNG decode through imlib2 -> + libpng -> zlib succeeds identically, gzip round-trips, and curl reports + "zlib/1.3.1.zlib-ng" while still working. No package we enable selects + `BR2_PACKAGE_ZLIB_FORCE_LIBZLIB` (only assimp/clamav/quazip do, none of them + ours). + + WHY: measured CHD decode win for the greenfield firmware, which unlike stock + links the SHARED libchdr and so does reach system zlib — audio hunks p90 -10 + to -11%, max -7 to -15%. See `harness/rig/chd-decode-optimization.md` in + Main_MiSTer for the numbers. (`docs/abi-contract.md` records the zlib-ng + 2.3.3 selection.) +- `BR2_PACKAGE_BZIP2=y`, `BR2_PACKAGE_XZ=y`, `BR2_PACKAGE_LZO=y`. +- `BR2_PACKAGE_ZSTD=y` — `libzstd.so.1` + the zstd CLI (upstream has no + sub-option to omit the CLI). Needed by libchdr (CHD v5 zstd hunks) and flips + minizip-ng's `MZ_ZSTD=ON`. +- `BR2_PACKAGE_MINIZIP=y` — IS minizip-ng 4.0.3 (zlib-ng/minizip-ng); the + concrete provider matters here exactly like the ZLIB provider above: this + is NOT the classic zlib-contrib zip.h/unzip.h library (that one is + `BR2_PACKAGE_MINIZIP_ZLIB`, next). Buildroot forces `-DMZ_COMPAT=OFF` + (`work/buildroot/package/minizip/minizip.mk`), so there is no zip.h/unzip.h + compat layer at all — the `mz_zip.h` native API is here for an eventual + Main_MiSTer port to it. Installs `libminizip-ng.so.4` + `minizip-ng.pc`. + Feature set under THIS configuration (`minizip.mk` keys each `MZ_*` feature + off other `BR2_PACKAGE_*` symbols): bzip2 + openssl (pkcrypt/wzaes) + + lzma-via-xz + zlib + zstd (the ZSTD=y above); NO iconv — `BR2_ENABLE_LOCALE=y`, + so minizip's `select BR2_PACKAGE_LIBICONV if !BR2_ENABLE_LOCALE` stays off. +- `BR2_PACKAGE_MINIZIP_ZLIB=y` — the CLASSIC zlib-contrib minizip (zlib 1.3.1's + `contrib/minizip`, autotools) — a SEPARATE package from minizip-ng. SONAME + `libminizip.so.1`, the zip.h/unzip.h API (`zipOpen`/`unzOpen`). Enabled for + backward compatibility: the current Main_MiSTer shared-lib cleanup links + `libminizip.so.1` (a NEEDED entry in the MiSTer binary), so the target must + ship it or MiSTer fails at exec with "cannot open shared object file". It + coexists with minizip-ng — distinct SONAME (`.so.1` vs `-ng.so.4`) and + non-overlapping symbols (`zipOpen`/`unzOpen` vs `mz_*`), so both load + conflict-free. + +### 5.5 Main_MiSTer shared libs + +The BR2_EXTERNAL half of the Main_MiSTer shared-lib refactor (no task ID — +referenced by name): Main stops vendoring `lib/{lzma,zstd,miniz,libchdr}` and +links Buildroot-provided shared libraries; the upstream half (zstd, minizip-ng) +is §5.4. Both packages are authored under `package/`; see +`docs/main-shared-libs.md`. + +- `BR2_PACKAGE_LZMA_SDK=y` — 7-Zip LZMA SDK 26.02 as `liblzma-sdk.so.`; + the full-version SONAME is the deliberate loud-ABI-event policy: the Main + binary lives on `/media/fat` and SURVIVES rootfs reflashes, so an SDK bump + must refuse-to-load, not corrupt (`package/lzma-sdk/lzma-sdk.mk`). +- `BR2_PACKAGE_LIBCHDR=y` — `libchdr.so.0`; commit-pinned past v0.3.0 for the + Findzstd pkg-config fallback (the tag cannot configure against Buildroot's + zstd); system zlib/zstd/lzma-sdk via our 3 patches; exports `chd_*` ONLY + (version script), so no symbol collisions with minizip-ng et al. + +### 5.6 graphics / fonts + +`BR2_PACKAGE_FREETYPE`, `BR2_PACKAGE_LIBPNG`, `BR2_PACKAGE_JPEG` (meta-prompt), +`BR2_PACKAGE_JPEG_TURBO` (default on ARM/NEON; builds `-DWITH_JPEG8=ON` -> +`libjpeg.so.8`, matching stock exactly), `BR2_PACKAGE_TIFF`, `BR2_PACKAGE_GIFLIB`, +`BR2_PACKAGE_IMLIB2` (critical ABI-contract SONAME `libImlib2.so.1`), +`BR2_PACKAGE_IMLIB2_{JPEG,PNG,GIF,TIFF,ID3}` — loader plugins, dlopen'd, NOT +in the manifest's paste list, added per `abi-contract.md`'s explicit warning +(§5.3): without these `menu.png`/background images silently fail to load with +no DT_NEEDED signal — `BR2_PACKAGE_LIBXKBCOMMON`, `BR2_PACKAGE_SDL2`. + +### 5.7 audio + +`BR2_PACKAGE_ALSA_LIB` (provides libasound + libatopology together), +`BR2_PACKAGE_LIBAO`, `BR2_PACKAGE_LIBVORBIS` (provides vorbis + vorbisenc + +vorbisfile together), `BR2_PACKAGE_LIBOGG`, `BR2_PACKAGE_MPG123` (provides +libmpg123 + libout123 together), `BR2_PACKAGE_LIBID3TAG`, +`BR2_PACKAGE_LIBMODPLUG`, `BR2_PACKAGE_FLUIDSYNTH`, +`BR2_PACKAGE_FLUIDSYNTH_ALSA_LIB` (ALSA-seq MIDI backend — needed for stock's +ALSA MIDI device list to match, P3.8). + +### 5.8 MIDI / MT-32 (P3.8) and ALSA CLI tools (P3.15) + +munt (mt32d) + MidiLink reproduce stock's MIDI/MT-32 stack: MidiLink +(`usr/sbin/midilink`, `usr/sbin/mlinkutil`) is the ALSA-seq client that shells +out to mt32d (munt) or fluidsynth on demand. Neither has an upstream Buildroot +package — both authored under `package/`. See `docs/midi-mt32-parity.md`. +`BR2_PACKAGE_MUNT=y`, `BR2_PACKAGE_MIDILINK=y`. + +alsa-utils MIDI tools — stock ships amidi/aplaymidi/arecordmidi/aseqdump/ +aseqnet/aconnect (`docs/stock-inventory/binaries-needed-full.txt`), the tooling +that exercises the ALSA-seq MIDI graph: `BR2_PACKAGE_ALSA_UTILS` + +`_ACONNECT`, `_AMIDI`, `_APLAYMIDI`, `_ARECORDMIDI`, `_ASEQDUMP`, `_ASEQNET`. + +General (non-MIDI) ALSA CLI tools (P3.15) — stock ships all of these +(`docs/stock-inventory/binaries-needed-full.txt`); the P3.8 MIDI pass +deliberately left them for this separate general-ALSA-parity pass. alsactl +(mixer save/restore), alsamixer/amixer (volume), aplay/arecord (`APLAY` +provides both), alsabat (`BAT`), alsaloop, alsatplg, alsaucm, iecset (S/PDIF +status bits), speaker-test (channel test tones) — every one of these is +present in `binaries-needed-full.txt`: `_ALSACTL`, `_ALSALOOP`, `_ALSAMIXER`, +`_ALSATPLG`, `_ALSAUCM`, `_AMIXER`, `_APLAY`, `_BAT`, `_IECSET`, +`_SPEAKER_TEST`. NOT enabled: alsaconf — stock never shipped it (it has an +option here but no stock binary to match). One stock ALSA binary has no parity +path at all: `usr/bin/aserver` IS in stock, but alsa-utils 1.2.15 exposes no +`BR2_PACKAGE_ALSA_UTILS_*` target for it (dropped upstream), so it cannot be +selected — see `docs/midi-mt32-parity.md` section 5. + +### 5.9 crypto / TLS + +`BR2_PACKAGE_OPENSSL=y` (meta-prompt), `BR2_PACKAGE_LIBOPENSSL=y` — NOT +`BR2_PACKAGE_OPENSSL` alone: the concrete provider. 1.1 -> 3.6.2, SONAME +`.so.1.1` -> `.so.3`, harmless (everything rebuilt together, see the risk +table). `BR2_PACKAGE_GNUTLS`, `BR2_PACKAGE_LIBGCRYPT`, `BR2_PACKAGE_LIBSSH2`. +nettle, gmp, libtasn1, libgpg-error, libffi are all pulled in transitively as +dependencies of gnutls/gcrypt/samba4/python3 — do not set separately (the +debug-tooling block, §5.42, relies on that for GMP). + +`BR2_PACKAGE_CA_CERTIFICATES=y` — CA trust store (found missing on hardware): +without it, curl's default CA path `/etc/ssl/certs/ca-certificates.crt` is +absent and every HTTPS verify fails ("error adding trust anchors"), which also +breaks Downloader_MiSTer's HTTPS fetches. Installs the Mozilla bundle as +`ca-certificates.crt` + OpenSSL hashed symlinks (curl + python default +context). The `cacert.pem`/`cert.pem` aliases the Downloader +(`DEFAULT_CACERT_FILE=/etc/ssl/certs/cacert.pem`) and stock expect are added +as overlay symlinks -> `ca-certificates.crt`. Functional parity with stock's +`cacert.pem` CA story. Deliberately kept explicit here even though azcopy +(§5.38) would `select` it, so it survives azcopy being switched off again. + +### 5.10 networking / D-Bus / GLib + +`BR2_PACKAGE_LIBCURL`, `BR2_PACKAGE_LIBCURL_CURL` (installs the `curl` CLI +binary — off by default, stock ships it, community scripts use it), +`BR2_PACKAGE_LIBCURL_OPENSSL` (TLS backend parity: stock's curl links +libcrypto/libssl, not GnuTLS). + +`BR2_PACKAGE_WGET=y` — GNU wget, stock parity restored (issue #130, +2026-09-01). Stock ships a real GNU wget ELF at `usr/bin/wget` linked against +`libgnutls.so.30`, `libnettle.so.8`, `libpcre.so.1`, `libuuid.so.1` and +`libz.so.1` (`docs/stock-inventory/binaries-needed-full.txt:351`), plus GNU +wget's own `/etc/wgetrc` — 4945 bytes, see `etc-configs.md:1097` — a file +BusyBox's applet never reads. Stock's BusyBox 1.33.1 ALSO had the wget applet +compiled in (`busybox-applets.md:278`), but the GNU ELF owned the path, so the +applet was unreachable as `wget` — the same "two providers, one path" shape as +ifup/util-linux/lsof. This image previously shipped ONLY the BusyBox applet, +with `CONFIG_FEATURE_WGET_HTTPS` and `CONFIG_FEATURE_WGET_OPENSSL` both off, so +`SSL_SUPPORTED` was 0 and every https:// URL died at `networking/wget.c:578` +with "wget: not an http or ftp url:" — while curl worked, which is exactly how +issue #130 was reported. The recorded reason for leaving wget out was the +PCRE1 removal note (§5.15: "the only stock consumers were wget/zsh, neither of +which we build"). That premise is stale: this Buildroot's `wget.mk:16` passes +`--disable-pcre` UNCONDITIONALLY, so GNU wget does not want PCRE1 at all, and +`wget.mk:67` gives it `--enable-pcre2` against the `BR2_PACKAGE_PCRE2` we +already ship. Nothing here resurrects PCRE1. TLS backend is GnuTLS, matching +stock, and for free: `wget.mk:26` prefers `BR2_PACKAGE_GNUTLS` over OpenSSL +when both are present, and we set both. Built and readelf'd, not predicted +(2026-09-01): the resolved DT_NEEDED is `libgnutls.so.30`, `libnettle.so.8`, +`libuuid.so.1`, `libz.so.1`, `libc.so.6` and `ld-linux-armhf.so.3` — identical +to stock's — plus `libpcre2-8.so.0` where stock had `libpcre.so.1`, plus +`libunistring.so.5`, which stock's older wget did not link. The last one is +free: `BR2_PACKAGE_LIBUNISTRING` was already set and the .so was already in +the image before this change. libpsl, libidn2 and c-ares stay out +(`wget.mk:18/40/60` take their `--without`/`--disable` branches), which is also +what stock did — none of the three is in stock's list either. The installed +`/etc/wgetrc` is 4945 bytes, byte-for-byte stock's size, and the ARM binary's +`--version` banner reports "+https ... +ssl/gnutls" under qemu-arm. +Prerequisites were already satisfied, nothing else had to change: +`BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y` (§5.15), `BR2_USE_WCHAR=y` and +`BR2_USE_MMU=y`. The colliding BusyBox applet is turned off in +`board/mister/de10nano/busybox.fragment` so this binary wins deterministically +(same idiom as ifup/ifdown and the util-linux block). Real GNU wget 1.25.0 w/ +GnuTLS — https works, `/etc/wgetrc` is read (stock parity). + +`BR2_PACKAGE_DBUS`, `BR2_PACKAGE_DBUS_CPP` (dbusxx-introspect; low-value but +zero-cost parity), `BR2_PACKAGE_DBUS_GLIB`, `BR2_PACKAGE_LIBEVENT`, +`BR2_PACKAGE_LIBNL`, `BR2_PACKAGE_IPTABLES`, `BR2_PACKAGE_LIBGLIB2`, +`BR2_PACKAGE_GOBJECT_INTROSPECTION`. + +### 5.11 util-linux / e2fsprogs / disk & fs tools + +`BR2_PACKAGE_UTIL_LINUX` + `_LIBBLKID`, `_LIBFDISK`, `_LIBMOUNT`, +`_LIBSMARTCOLS`, `_LIBUUID` — each lib sub-option defaults to "n": must be +listed explicitly or the corresponding SONAME won't be built. + +util-linux BINARIES + programs — stock parity (usbmount work). Stock ships +real util-linux 2.36.2 ELF binaries for mount/umount/blkid/fdisk/dmesg/agetty/ +hwclock/... (NOT BusyBox — verified against `work/imgroot`), so we ship them +too (2.41.4 here). The util-linux `mount` matters functionally: it dispatches +`mount -t ntfs` to the `/sbin/mount.ntfs -> ntfs-3g` helper, which BusyBox +mount cannot do (no `CONFIG_FEATURE_MOUNT_HELPERS`) — that is how NTFS USB +drives auto-mount under usbmount, exactly like stock. The overlapping BusyBox +applets are turned off in `board/mister/de10nano/busybox.fragment` so these +win deterministically (same idiom as ifupdown). The libs above are already +selected; BINARIES re-selects them harmlessly. Full stock<->ours program map: +`docs/util-linux-parity.md`. + +- `_BINARIES` — basic set: blkid, blockdev, dmesg, fdisk/sfdisk, findfs, + findmnt, flock, fstrim, getopt, hexdump, lsblk, lscpu, mkswap, + setarch(+linux32/64), setsid, swapon/swapoff, ... (stock's set) +- `_MOUNT` — mount + umount, the functional core (helper dispatch to mount.ntfs) +- `_MOUNTPOINT` — mountpoint +- `_AGETTY` — serial-console getty; inittab uses it, replacing BusyBox getty (stock parity) +- `_HWCLOCK` — hwclock (manual/debug; no S05rtc, like stock; `docs/rtc-parity.md`) +- `_FSCK`, `_PARTX` (addpart/delpart/partx/resizepart), `_SCHEDUTILS` + (chrt/ionice/taskset), `_IRQTOP` (irqtop/lsirq), `_KILL`, `_MORE`, + `_NEWGRP`, `_NOLOGIN`, `_RENAME`, `_SETTERM`, `_SWITCH_ROOT` +- NB: util-linux `raw` (stock had `/sbin/raw`) is intentionally NOT enabled — + its Config.in `depends on !BR2_TOOLCHAIN_HEADERS_AT_LEAST_5_14` and our 6.18 + headers are >= 5.14, so the raw(8) char-device interface (removed from the + kernel in 5.14) is unbuildable. Obsolete; no BusyBox `raw` applet either, so + nothing is lost. + +`BR2_PACKAGE_E2FSPROGS`, `BR2_PACKAGE_PARTED`. + +`BR2_PACKAGE_NTFS_3G=y` — stock has no NTFS driver at all (kernel side); this +is userland-only parity for exFAT/NTFS USB drives via FUSE, matches stock's +ntfs-3g. `BR2_PACKAGE_NTFS_3G_NTFSPROGS=y` — T5 (2026-07-27): mkfs.ntfs/ntfsfix +DID NOT LAND with NTFS_3G alone — a real oversight, not a deliberate omission, +found while auditing stock's util binaries. `BR2_PACKAGE_NTFS_3G=y` alone only +builds the ntfs-3g FUSE driver + mount.ntfs-3g; the rest of ntfsprogs +(mkntfs/mkfs.ntfs, ntfsfix, ntfsclone, ntfsresize, ntfslabel, ...) is gated by +this separate sub-option, which defaults to "n" with no dependency of its own +(`package/ntfs-3g/Config.in:27-30` — "config BR2_PACKAGE_NTFS_3G_NTFSPROGS / +bool 'ntfsprogs' / help / Install NTFS utilities.", no "default", no "depends +on"). Confirmed via the .mk too: without this symbol `ntfs-3g.mk` passes +`--disable-ntfsprogs` to configure (`ntfs-3g.mk:32-34`). PATHS ARE SPLIT, and +not the way "ntfsprogs" suggests — `ntfsprogs/Makefile.am:17` puts `ntfsfix` +(with ntfsinfo/ntfscluster/ntfsls/ntfscat/ntfscmp) in `bin_PROGRAMS` -> +`/usr/bin`, while `:18`'s `sbin_PROGRAMS` holds +mkntfs/ntfslabel/ntfsundelete/ntfsresize/ntfsclone/ntfscp -> `/usr/sbin`, and +the install-exec-hook at `:166-169` adds the `mkfs.ntfs -> mkntfs` symlink +beside mkntfs in sbin. `ntfs-3g.mk` passes no `--exec-prefix` override (unlike +`dosfstools.mk:13`'s `--exec-prefix=/`), so bindir really is `/usr/bin`. Stock +lands exactly the same way — `work/imgroot` has `usr/bin/ntfsfix` and +`usr/sbin/{mkntfs,mkfs.ntfs}`, and NO `usr/sbin/ntfsfix`. `scripts/ci-tests.sh`'s +T5 block asserts both paths in that split. Worth spelling out because the +first draft of that gate asserted `usr/sbin/ntfsfix` — which would have failed +deterministically on the first real build; it was caught in review, before any +build ran, not at runtime. + +`BR2_PACKAGE_KMOD`, `BR2_PACKAGE_INOTIFY_TOOLS`, `BR2_PACKAGE_JQ`, +`BR2_PACKAGE_EXPAT`, `BR2_PACKAGE_POPT`, `BR2_PACKAGE_READLINE`, +`BR2_PACKAGE_NCURSES`. + +`BR2_PACKAGE_NCURSES_WCHAR=y` — WIDE-CHAR ncurses. Not cosmetic — it is an +ABI-contract fix. Plain `BR2_PACKAGE_NCURSES` builds the NARROW +`libncurses.so.6`; the wide `libncursesw.so.6` only comes from `--enable-widec` +(this symbol). `docs/package-manifest.md:193` lists the required SONAME as +`libncursesw.so.6`, stock ships exactly that, and 35 stock binaries (bash, +dialog, clear, dmesg, alsamixer, ...) DT_NEEDED it — so a libncursesw-linked +ARM binary dropped on the device would fail to start against our narrow lib. +This was shipped narrow by omission: the manifest mapped the libncursesw +SONAME to `BR2_PACKAGE_NCURSES` without noting that the "w" requires this +second symbol. It also restores wide-char curses in Python (the build symlinks +`libncurses.so -> libncursesw.so`, so `_curses`/`_curses_panel`/`readline` all +relink against the wide lib): our narrow `_curses` lacks the wide-char +key-read API — the `window.get_wch()` method and its module-level companion +`_curses.unget_wch`, both compiled in only against ncursesw. A TUI that reads +a keystroke via `window.get_wch()` — e.g. to catch the UP arrow — hits +`AttributeError` on narrow ncurses and commonly falls back to line mode, where +the arrow just echoes as `^[[A` instead of being captured. Enabling widec is +what stock has and what makes `window.get_wch()` work. Narrow -> wide is a +clean-rebuild change. + +`BR2_PACKAGE_SLANG`, `BR2_PACKAGE_NEWT`, `BR2_PACKAGE_GPM`, +`BR2_PACKAGE_LIBARCHIVE` (keep the library — samba4 can use it; do NOT package +archivemount itself, see the Drop list §5.29), `BR2_PACKAGE_LIBFUSE` (ditto — +real dependents may want it even though archivemount itself is dropped). + +### 5.12 USB / input + +`BR2_PACKAGE_LIBUSB`, `BR2_PACKAGE_LIBUSB_COMPAT` (legacy libusb-0.1 API shim — +still NEEDed by name, `libusb-0.1.so.4`, in stock's binary set), +`BR2_PACKAGE_LIBEVDEV`, `BR2_PACKAGE_LIBINPUT`, `BR2_PACKAGE_MTDEV`. + +`BR2_PACKAGE_USBMOUNT=y` — USB mass-storage automount, stock parity. Stock +ships the Debian `usbmount` package (udev `RUN+=` rule -> +`/usr/share/usbmount/usbmount`) that mounts sd*/ub* block devices under +`/media/usb0..7` on hotplug and unmounts on removal. Buildroot's usbmount is the +same tool (0.0.22, patched to read udev's `ID_FS_*` env instead of shelling out +to blkid — functionally identical to stock's 0.0.24 script). It needs udev +(`BR2_PACKAGE_HAS_UDEV` — eudev, §5.15) and `select`s +`BR2_PACKAGE_LOCKFILE_PROGS` (-> the liblockfile already enabled, §5.15) for +the lockfile-create serialisation in its add path. run-parts/logger/expr are +BusyBox applets, all present. The stock-tuned `usbmount.conf` (adds +exfat/ntfs/fuseblk + NTFS/fuseblk mount opts, which upstream 0.0.22's default +omits) ships in the rootfs-overlay and overrides the package's default — see +`docs/usb-automount-parity.md`. + +### 5.13 Bluetooth + +`BR2_PACKAGE_BLUEZ5_UTILS=y`. `BR2_PACKAGE_BLUEZ5_UTILS_CLIENT=y` — NOT in the +manifest — discovered during P2.1 verification: DEPRECATED (below) `depends on +BLUEZ5_UTILS_CLIENT || BLUEZ5_UTILS_TOOLS`, and stock ships +`usr/bin/bluetoothctl` (needs CLIENT) + `usr/bin/gatttool` (also needs CLIENT), +per `docs/stock-inventory/binaries-needed-full.txt`. +`BR2_PACKAGE_BLUEZ5_UTILS_TOOLS=y` — NOT in the manifest — the other half of +DEPRECATED's prerequisite; stock also ships hciattach/l2ping which live under +TOOLS. `BR2_PACKAGE_BLUEZ5_UTILS_DEPRECATED=y` — hciconfig/hcitool/sdptool/ +rfcomm/l2ping/hcidump — all present in stock, gated by this option upstream +now. Depends on CLIENT or TOOLS (`package/bluez5_utils/Config.in`) — silently +unsatisfiable without them; confirmed missing from `output/.config` before +this fix. `BR2_PACKAGE_BLUEZ5_UTILS_PLUGINS_SIXAXIS=y` — PS3 controller BT +pairing (selects `_PLUGINS_HID` transitively — don't set that too). Requires +the eudev choice (§5.15). See `docs/bluetooth-parity.md`. + +### 5.14 PAM / capabilities + +`BR2_PACKAGE_LINUX_PAM`, `BR2_PACKAGE_LIBCAP`, `BR2_PACKAGE_LIBCAP_NG`. + +### 5.15 misc small libraries / tools + +- `BR2_PACKAGE_DTC=y` — libfdt. `BR2_PACKAGE_DTC_PROGRAMS=y` — T5 (2026-07-27): + the DTC line alone ships ONLY the library — `package/dtc/Config.in` says so + explicitly ("Note that only the library is installed. If you want the + programs, say 'y' here, and to 'dtc programs', below"). The `dtc` CLI itself + (plus convert-dtsv0/fdtdump/fdtget/fdtput/dtdiff) needs this separate + sub-option, which was never set — so this image had never actually shipped + the `dtc` binary despite DTC=y being on since P2.1. dtdiff additionally + needs bash, already on (`BR2_PACKAGE_BASH=y`, wifi.sh). +- `BR2_PACKAGE_SUDO=y`. +- `BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y` — NOT in the manifest — discovered during + P2.1 verification: `BR2_PACKAGE_I2C_TOOLS` "depends on + BR2_PACKAGE_BUSYBOX_SHOW_OTHERS" (`package/i2c-tools/Config.in`); without + this, I2C_TOOLS=y is silently unsatisfiable and Kconfig drops it with no + error (confirmed: it doesn't land in `output/.config` without this line). + Also gates LSOF (§5.32) and GNU wget (§5.10). +- `BR2_PACKAGE_I2C_TOOLS=y` — for the i2c-gpio RTC add-on, P3.11 + (`docs/rtc-parity.md`). No sub-options gate any of its tools. +- `BR2_PACKAGE_JIMTCL=y` — NOT just an obscure shell — usb_modeswitch's + dispatcher (3G/LTE modem support) needs it. +- `BR2_PACKAGE_LIBLOCKFILE=y`, `BR2_PACKAGE_LIBXML2=y`, `BR2_PACKAGE_FILE=y` (libmagic). +- `BR2_PACKAGE_MEMTOOL=y` — memtool (T3, addon.tar §3c): stock's + `usr/bin/memtool` looked like an unsourceable ARM blob in the first + reconciliation pass ("ARM ELF" with no provenance) — it is not. `strings` on + the stock binary yields pengutronix memtool's exact usage text ("memtool is + divided into subcommands", the "Usage: md [-bwlqsx] REGION" / "Usage: mw + [-bwlqd] OFFSET DATA..." lines), and addon.tar's `usr/bin/md` + `usr/bin/mw` + are symlinks -> memtool (argv[0] dispatch), matching pengutronix's md/mw + subcommand model. Buildroot packages that exact tool (`package/memtool`, + 2018.03.0 — upstream's last release), so this is a plain package enable, not + a (D)-infeasible item. Only `usr/bin/fpga` remains without public source + (`docs/stock-reconciliation.md` §3c). The package installs only + `/usr/bin/memtool` (`bin_PROGRAMS` in its Makefile.am — no symlinks); stock's + md/mw argv[0] sugar is reproduced by two overlay symlinks instead + (`memtool.c:475` dispatches on `basename(argv[0])`, verified in the pinned + 2018.03.0 tarball, so `md`/`mw` and `memtool md`/`memtool mw` are the same + operations). +- `BR2_PACKAGE_PCRE2=y` — `libpcre2-8.so.0`, the PCRE1 replacement. PCRE1 + (`libpcre.so.1`) was REMOVED upstream in Buildroot 2026.05 (EOL, unmaintained; + it is now a `Config.in.legacy` stub that hard-stops the build). Nothing in + this image needs it: the stock MiSTer binary does not link it (verified — no + `-lpcre`, no DT_NEEDED), Python uses its built-in sre engine (not PCRE), and + 2026.05's slang dropped its pcre module (`--with-pcre=no`). The only stock + consumers were wget/zsh — and GNU wget, since re-added, wants PCRE2 (§5.10). + See `docs/package-manifest.md` for the recorded parity deviation. PCRE2 is + already `select`'d transitively by libglib2 and libselinux; listed + explicitly so it can never be silently dropped. +- `BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y` — eudev needs its "/dev + management" choice (`system/Config.in`) switched away from the + `BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_DEVTMPFS` default — NOT in the manifest, + discovered during P2.1 verification: `BR2_PACKAGE_EUDEV=y` alone is silently + unsatisfiable (depends on `BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV`, which + the manifest's paste list never sets) and Kconfig drops it with no error. + This one line cascades to fix THREE other silently-dropped symbols: + `BR2_PACKAGE_LIBINPUT` (depends on `BR2_PACKAGE_HAS_UDEV`, only select'd by + eudev) and `BLUEZ5_UTILS_PLUGINS_SIXAXIS` (same). Confirmed: none of the four + landed in `output/.config` until this line was added. It selects + `BR2_PACKAGE_EUDEV` automatically. +- `BR2_PACKAGE_EUDEV=y` — NOT mdev — PLAN §3 explicit requirement (kept + explicit for readability even though the choice above already selects it). + +### 5.16 lftp + +`BR2_PACKAGE_LFTP=y` — provides all 4 bundled `liblftp-*.so` together. + +### 5.17 Python (A6 / P3.9) and the btctl runtime + +`BR2_PACKAGE_PYTHON3=y` — 3.14.6 — no legacy-version toggle exists in Buildroot +2026.05; see the P3.9 risk entry. Extension modules (P3.9) — match stock's 3.9 +lib-dynload set exactly. SSL + ZLIB are HARD BLOCKERS: without them +Downloader_MiSTer crashes on `import ssl` and cannot read its own +`db.json.zip` (proven under qemu-user, `docs/python-compat.md`). openssl/zlib +target libs are already built. BZIP2/XZ (`_lzma`)/PYEXPAT/READLINE/CURSES round +out stock parity. (SQLITE/DECIMAL deliberately omitted — stock's Python 3.9 +shipped neither.) `BR2_PACKAGE_PYTHON3_{SSL,ZLIB,BZIP2,XZ,PYEXPAT,READLINE,CURSES}=y`. + +btctl runtime (T3, addon.tar §3c). Stock's OSD Bluetooth pairing flow is a +Python script: Main_MiSTer `popen()`s the absolute path `/usr/sbin/btpair` +(`work/Main_MiSTer/menu.cpp:7102`) and later runs "btctl disconnect " +(`input.cpp:5581`), and btpair is a 12-line wrapper whose whole job is running +`btctl pair`. btctl (vendored into the overlay, see §3c) opens with `import +dbus`, `import dbus.service`, `dbus.mainloop.glib` and `from gi.repository +import GLib` — i.e. classic dbus-python plus PyGObject. Stock ships exactly +those bindings for its python3.9 (verified in +`work/imgroot/usr/lib/python3.9/site-packages/`: `dbus/`, `_dbus_bindings.so`, +`_dbus_glib_bindings.so`, `gi/`, `PyGObject-3.36.1.egg-info`). Without both, +btctl dies on its first import and OSD pairing silently does nothing. Cost +check before enabling: python-gobject's one heavyweight dependency +(gobject-introspection, which drags in host-qemu) is ALREADY paid — +`BR2_PACKAGE_GOBJECT_INTROSPECTION=y` in §5.10 — and dbus-python needs only +dbus + libglib2, both long since on. python-dbus-fast/-next (the asyncio +reimplementations Buildroot also carries) were considered and rejected: btctl +uses the dbus-python API surface (`dbus.service.Object` agents, mainloop +glue), and rewriting a proven stock script to a different binding is exactly +the kind of unverifiable-off-target churn this project avoids. +`BR2_PACKAGE_DBUS_PYTHON=y` (`dbus.mainloop.glib` — btctl's bus + agent), +`BR2_PACKAGE_PYTHON_GOBJECT=y` (`gi.repository.GLib` — btctl's main loop). + +### 5.18 Samba + +`BR2_PACKAGE_SAMBA4=y` — a single package covers ~125 of the 251 SONAMEs (see +manifest §1). Deliberately NOT set (standalone file server only, not a domain +controller/member): `BR2_PACKAGE_SAMBA4_AD_DC`, `BR2_PACKAGE_SAMBA4_ADS`, +`BR2_PACKAGE_SAMBA4_SMBTORTURE`. See `docs/samba-parity.md`. + +### 5.19 daemons / user-facing binaries (manifest §2) + +`BR2_PACKAGE_OPENSSH=y`. **`# BR2_PACKAGE_OPENSSH_SANDBOX is not set`** — +deliberately NOT set (Buildroot defaults it to y, i.e. `--with-sandbox`). Our +kernel carries `# CONFIG_SECCOMP is not set` (`linux.config:48`), matching +stock (`docs/stock-inventory/stock-linux.config:592`), so +`prctl(PR_SET_SECCOMP)` returns EINVAL. Through openssh 10.3 that was only a +`debug()` and sshd ran the pre-auth child unsandboxed — which is what this +image has silently done for its entire life; the seccomp sandbox was never +once active here. openssh 10.4 (upstream 7ab700f, "Make failure to set SECCOMP +or NO_NEW_PRIVS fatal") turned it into `fatal()`. The LISTENER still binds and +listens normally, but the pre-auth privsep child of sshd-session then dies +status 255 on every single connection, before any auth — so the box looks +like it is serving SSH while refusing every client, password and key alike. +That regression reached us via Buildroot 2026.05.1 -> 2026.05.2, which bumped +openssh 10.3p1 -> 10.5p1 and so crossed the 10.4 boundary. `--without-sandbox` +selects `SANDBOX_NULL` — verified on the rebuild: `config.h` gets +`#define SANDBOX_NULL 1` and none of sshd, sshd-session or sshd-auth carries a +seccomp string any more. That restores the posture the image actually had all +along (the sandbox never engaged). Setting `CONFIG_SECCOMP=y` instead is the +beyond-parity fix (`post-build.sh:22` already lists it as such), but it would +arm that armhf/glibc syscall allowlist for the first time ever and needs a +real build-and-SSH test, not a hotfix. NB: configure-time flag — changing it +requires `make openssh-dirclean` before the rebuild, or the stale +`--with-sandbox` stamp ships the same broken sshd. See `docs/ssh-ftp-parity.md`. + +`BR2_PACKAGE_PROFTPD=y`. + +`BR2_PACKAGE_WPA_SUPPLICANT=y`; `_NL80211=y` (default y already, listed for +clarity); `_WEXT=y` (stock's interfaces file passes "-D nl80211,wext" — both +drivers needed); `_DEBUG_SYSLOG=y` — compiles in the "-s" (log-to-syslog) +flag. Stock's `/etc/network/interfaces` invokes "wpa_supplicant -s ..."; +without `CONFIG_DEBUG_SYSLOG` the -s is unknown -> wpa_supplicant rejects the +args and dumps its usage text to the console at every boot (once per wlanN +stanza). Enabling it makes -s valid so the pre-up starts cleanly and logs to +syslog, not the console — exact stock parity (P3.4 hardware fix). `_WPA3=y` — +SAE/OWE/DPP — WPA3-Personal support. BEYOND stock (stock's wpa_supplicant 2.9 +had no WPA3, so WPA3 networks were unjoinable). Our 2.11 + the morrownr 88x2bu +driver do SAE over nl80211. Requested for hardware testing on a real WPA3 +network. `_CLI=y` + `_PASSPHRASE=y` — T5: wpa_cli (interactive/scriptable +control of a running wpa_supplicant — status, scan, reassociate, list/select +saved networks) + wpa_passphrase (turns an ASCII passphrase into the PSK hex +blob `wpa_supplicant.conf` wants, so a script never has to embed the plaintext +passphrase). Highest value-per-byte item in the T5 pass and squarely WiFi +work: both are sub-options of the wpa_supplicant package already built, not a +new package. CLI selects `WPA_SUPPLICANT_CTRL_IFACE` (the Unix-socket control +API) automatically — don't also list that symbol by hand, it would just be +redundant with the select (`package/wpa_supplicant/Config.in:137-141`). +Neither has any other dependency (verified against the pinned Config.in). + +WiFi userland parity (P3.4) — the community wifi.sh (Scripts_MiSTer) and the +stock WiFi stack need these; all four are in stock's rootfs. See +`docs/wifi-parity.md`. (`CONFIG_CFG80211_WEXT=y` is already resolved in our +kernel, so iwlist/iwgetid's legacy WEXT ioctls work against the cfg80211-only +Realtek drivers.) `BR2_PACKAGE_BASH=y` (wifi.sh shebang), `BR2_PACKAGE_DIALOG=y` +(wifi.sh interactive menus), `BR2_PACKAGE_WIRELESS_TOOLS=y` + +`_IWCONFIG=y` (iwmulticall: iwconfig + iwlist/iwgetid symlinks), +`BR2_PACKAGE_IW=y` (stock ships `usr/sbin/iw`, nl80211 CLI — parity; the +overlay's `70-persistent-net.rules` pre-up depends on it), +`BR2_PACKAGE_IPROUTE2=y` (stock's `/usr/sbin/ip` — wifi.sh's link up/down +fallback). + +### 5.20 On-device text editors, ifupdown, BusyBox fragment, dhcpcd, ntp, cifs + +Editors (stock parity): stock ships `usr/bin/joe`, `usr/bin/nano` AND +`usr/bin/vim` (P0.3 inventory, `binaries-needed-full.txt:149/218/348`). We +shipped none of them — only BusyBox's built-in `vi`. +`docs/package-manifest.md:665` had flagged this as a deliberate P2.7 +size-budget call ("keep the light ones... if the community expects them"), +left unresolved. Resolved: people SSH into a MiSTer to edit +`wpa_supplicant.conf` / `MiSTer.ini`, and BusyBox vi is a hostile way to do +that for most users. joe: ~0.65 MiB (`docs/stock-inventory/disk-usage.md`), +needs MMU only. nano: small; needs wchar + ncurses (both already on). vim is +NOT enabled — it is the heavy one, and its libgpm dependency is already +satisfied (`BR2_PACKAGE_GPM=y`) if we ever want full parity. +`BR2_PACKAGE_JOE=y`, `BR2_PACKAGE_NANO=y`. + +`BR2_PACKAGE_IFUPDOWN=y` — ifupdown package (stock parity): stock ships the +real ifupdown, not busybox's ifup applet. busybox ifup mangles the interfaces +file's pre-up "$IFACE", so wpa_supplicant is called with bad args and dumps +its usage text twice at every boot. The busybox.fragment disables busybox's +IFUP/IFDOWN so ifupdown's `/sbin/ifup` wins (== `usr/sbin/ifup` under +usr-merge, stock's exact path). P3.4. + +`BR2_PACKAGE_BUSYBOX_CONFIG_FRAGMENT_FILES` — `board/mister/de10nano/busybox.fragment` +on top of `package/busybox/busybox.config`; the applet set this image ships +(and every "collides with a BusyBox applet — disabled in busybox.fragment" +note in this document) is decided there (§5.23). + +`BR2_PACKAGE_DHCPCD=y`, `BR2_PACKAGE_NTP=y` (classic ntpd, matches stock — NOT +chrony/openntpd), `BR2_PACKAGE_CIFS_UTILS=y`. + +### 5.21 Midnight Commander (T3, stock parity, addon.tar §3c UX closure) + +Stock ships mc 4.8.25 (`strings` on `work/imgroot/usr/bin/mc`) as THE on-device +file manager, and it is load-bearing for MiSTer's media-player UX, not just a +convenience: addon.tar overlays `etc/mc/mc.ext` with `Open=` handlers for +aplay/mpg123/vgmplay/timidity/m3u_play/vhd_mount, ships a MiSTer skin, and +`usr/bin/timidity` plays `$MC_EXT_SELECTED` — an mc-set variable, so that +script is mc-integration by construction. Without mc, four of the §3c helpers +lose the UI they were written for. + +Version gap that matters: Buildroot's is 4.8.33, and upstream REPLACED the +`mc.ext` format with `mc.ext.ini` in 4.8.29 — read from the pinned tarball's +own changelog, not from memory: mc-4.8.33 `NEWS:191` is the "Version 4.8.29" +header, `NEWS:203` "Port mc.ext to INI format and rename to mc.ext.ini (#4141, +#3742, #3191)", `NEWS:205` "There is no fallback to previous mc.ext format". +(NOT 4.8.28, whose section starts at `NEWS:249` and still carries plain mc.ext +bugfixes at `NEWS:281-282`.) Stock's `etc/mc/mc.ext` therefore CANNOT be +carried verbatim — 4.8.33 would ignore it outright. The MiSTer handlers are +ported into the overlay's `etc/mc/mc.ext.ini` instead; see that file's header +and `docs/stock-reconciliation.md` §3c for the per-handler mapping. + +Screen backend: ncurses (`BR2_PACKAGE_NCURSES=y`; slang deliberately not +enabled as the backend). mc's `select BR2_PACKAGE_NCURSES_WCHAR if +BR2_PACKAGE_NCURSES` is a no-op here — `NCURSES_WCHAR=y` is already set +(§5.11), so the narrow->wide SONAME/clean-rebuild trap documented there is not +re-triggered by this line. + +RUNTIME DEPENDENCY, worth knowing before someone debugs it the hard way: mc +needs ALL THREE of its XDG dirs to be creatable, not just the one we vendor. +`mc_config_init_config_paths()` (4.8.33 `lib/mcconfig/paths.c:183-188`) builds +`~/.config/mc`, `~/.cache/mc` AND `~/.local/share/mc` via `mc_config_mkdir()` +(`:104-112`, `g_mkdir_with_parents 0700` -> `mc_propagate_error` on failure), +and `src/main.c:315-320` treats that error as fatal (`mc_event_deinit(NULL); +goto startup_exit_falure;`). The overlay ships only +`root/.config/mc/{ini,panels.ini}` — the other two must be created at runtime, +on a root filesystem the kernel mounts READ-ONLY (cmdline `... loop=linux/linux.img +ro rootwait`, `docs/boot-chain.md:323`; the inittab remount is deliberately +commented out at `etc/inittab:53`). What makes mc work at all is +`/etc/profile:31`'s `mount -o remount,rw /`, which runs on the first login +shell. So mc invoked before ANY login shell has run on a fresh boot (e.g. +straight from a Main_MiSTer Scripts entry) dies with "Cannot create +/root/.cache/mc directory". This is PARITY, not a regression: stock's +addon.tar ships those same two mc files and no `~/.cache/mc` or +`~/.local/share/mc` either (`tar tvf` on the pinned archive: under `./root/` +only `.config/mc/{ini,panels.ini}` and `.ssh/environment`), and stock's +`/etc/profile` carries the identical remount at `:23` — so stock behaves +exactly the same way. Deliberately NOT "fixed" by shipping empty +`root/.cache/mc` + `root/.local/share/mc` in the overlay: that would diverge +from stock, and the overlay's rsync chmod (`--chmod=u=rwX,go=rX`, +`system/system.mk:64-68`) would create them 0755 where mc wants 0700. Recorded +here instead; revisit only if a Scripts-launched mc is ever actually wanted. +`BR2_PACKAGE_MC=y` — stock `usr/bin/mc`, file manager + the §3c media-helper +launcher UI. + +### 5.22 NFS client userland (ADR 0022) — reverses P3.10's "kernel-only NFS" call + +The kernel has carried the whole NFS client all along (NFS_FS/V2/V3/V4/V4_1/ +V4_2, SUNRPC, LOCKD_V4, NFS_USE_KERNEL_DNS — `docs/netfs-parity.md`), but with +no mount.nfs helper `mount -t nfs` could not work AT ALL: util-linux's mount +execs `/sbin/mount.` for a network fs, and there is no BusyBox fallback +— its mount applet is disabled outright in our build (`# CONFIG_MOUNT is not +set`, and `# CONFIG_FEATURE_MOUNT_NFS is not set` with it; the latter is +v2/v3-only anyway). Supplying `/sbin/mount.nfs` (+ the `mount.nfs4` symlink) is +the entire point. `BR2_PACKAGE_NFS_UTILS=y`. `BR2_PACKAGE_NFS_UTILS_NFSV4=y` — +NFSv4/4.1/4.2 -> nfsidmap + rpc.idmapd. Buildroot hard-couples +`--enable-nfsv4` to `--enable-blkmapd`, which is why lvm2 gets pulled in +(trimmed below). + +**`# BR2_PACKAGE_NFS_UTILS_RPC_NFSD is not set`** — CLIENT ONLY; this line is +load-bearing, not decoration. Upstream defaults `BR2_PACKAGE_NFS_UTILS_RPC_NFSD` +to y, and leaving it alone would install rpc.nfsd/rpc.mountd/exportfs + an +S60nfs init script, select rpcbind, and — the real trap — fire +`NFS_UTILS_LINUX_CONFIG_FIXUPS`, whose `KCONFIG_ENABLE_OPT` would flip on the +in-kernel NFS *server* underneath our deliberate `# CONFIG_NFSD is not set`. +We are a client; the server stays off on both sides. + +**`# BR2_PACKAGE_LVM2_STANDARD_INSTALL is not set`** — lvm2 is not a feature we +want; it arrives solely as the blkmapd dependency above (blkmapd links +libdevicemapper for the pNFS block layout, which no home NAS uses). Upstream +defaults to installing the full LVM suite; keep only dmsetup + +libdevicemapper so we do not ship an unused volume manager on a games console. + +### 5.23 rsync, BusyBox + +`BR2_PACKAGE_RSYNC=y`. `BR2_PACKAGE_BUSYBOX=y` — 1.38.0 in this Buildroot +(`busybox.mk:7`; `output/build/busybox-1.38.0`), always on. Parity with STOCK's +274-applet set — stock runs 1.33.1, count from its own `busybox --list` under +qemu-arm, see `docs/stock-inventory/busybox-applets.md` — is a P2.3 config +concern, not a package-selection one; the applet set this image actually ships +is decided by `board/mister/de10nano/busybox.fragment` on top of +`package/busybox/busybox.config`. + +### 5.24 P3.1/v9: Realtek USB WiFi — MAINLINE-FIRST out-of-tree driver policy + +EXACTLY ONE out-of-tree Realtek WiFi fork is selected: rtl8852cu-morrownr, for +the RTL8852CU/RTL8832CU (Wi-Fi 6E) — see below. It is the only Realtek USB +chip 6.18.40 cannot drive at all, so it is the only chip that satisfies ADR +0016's exception rule. Until v10.2 this section said "NO out-of-tree Realtek +WiFi fork is selected any more"; that was true when written and is no longer. + +Every Realtek USB chip MiSTer's 5.15 stock drove with a vendor fork is still +handled by an IN-KERNEL driver (`board/mister/de10nano/linux.config`) — +enabling both would bind-fight on the same USB IDs, so each of THOSE forks' +packages remains DISABLED (`# ... is not set`): + +| fork package (not set) | in-kernel driver | +|---|---| +| `BR2_PACKAGE_RTL8188EU_AIRCRACK_NG` (8188eu), `BR2_PACKAGE_RTL8188FU` (8188fu) | rtl8xxxu (`CONFIG_RTL8XXXU=m`, already on; 8710bu too) | +| `BR2_PACKAGE_RTL8821CU_MORROWNR` (8811cu, 8821cu) | rtw88_8821cu (`CONFIG_RTW88_8821CU=m`) | +| `BR2_PACKAGE_RTL88X2BU` (8822bu) | rtw88_8822bu (`CONFIG_RTW88_8822BU=m`, HW-verified WPA3) | +| rtl8814au-morrownr (8814au; package kept, unselected, no not-set line needed) | rtw88_8814au (`CONFIG_RTW88_8814AU=m`, merged in 6.16) | +| `BR2_PACKAGE_RTL8812AU` (8812au) | rtw88_8812au (`CONFIG_RTW88_8812AU=m`, merged in 6.13) | +| `BR2_PACKAGE_RTL8821AU_MORROWNR` (8811au, 8821au) | rtw88_8821au (`CONFIG_RTW88_8821AU=m`, merged in 6.13) | + +The last two rows are the newest: RTL8812AU and RTL8811AU/RTL8821AU were ADR +0016's only standing exceptions ("no mainline USB driver"), and that is simply +no longer true — the shared rtw88_88xxa core landed in 6.13, after that ADR +was written. Mainline goes through mac80211 (WPA3/SAE/PMF work properly, the +concrete defect that drove the 8822bu switch) and stays maintained, whereas +the morrownr forks need hand-written compat patches every kernel bump. +Coverage was diffed, not assumed, and NOTHING is lost. The two forks' USB-ID +tables list 57 IDs, mainline rtw88_8812au + rtw88_8821au list 50, and the 50 +are a strict subset of the 57. Every one of the 7 remaining IDs is claimed by +a DIFFERENT in-kernel driver this image already builds — the forks' tables +simply over-claimed IDs belonging to other chips, and mainline attributes them +correctly: 5 to rtw88_8814au (056e:400b, 056e:400d, 0b05:1817, 2001:331a, +7392:a834 — e.g. 0b05:1817 is the ASUS USB-AC68, a 4x4 RTL8814AU), 1 to +rtl8xxxu (07b8:8179, an RTL8188EUS), and 1 to rtw88_8822bu/8822cu (13b1:0043, +the Linksys WUSB6300 v2 — RTL8822BU; only the v1, 13b1:003f, is a true +8812AU, and mainline's 8812au table does carry it). Verified by grepping the +pinned kernel tree for each ID. See `docs/wifi-parity.md` §6 for the worked +diff. The disabled packages stay sourced (Config.in) as a selectable fallback. + +rtl8188eu-aircrack-ng / rtl8821au-morrownr / rtl8821cu-morrownr / +rtl8814au-morrownr / rtl8852cu-morrownr carry a fork suffix, NOT plain +rtl8188eu/rtl8821au/rtl8821cu — so as not to collide with Buildroot's own +same-named upstream packages (different forks; re-confirmed present on the +pinned tree 2026-07-25) on the Kconfig symbol and Make namespace. +(rtl8814au-morrownr and rtl8852cu-morrownr have no upstream twin to collide +with — re-checked 2026-07-27 — and take the suffix only for a uniform morrownr +naming scheme.) Buildroot's own rtl8188eu/rtl8821au/rtl8821cu/rtl8812au-aircrack-ng +packages are left OFF — our pins stay in control, not Buildroot's release +cadence (A9 reproducibility). + +- RTL8812AU: now the in-kernel mac80211 rtw88_8812au driver + (`CONFIG_RTW88_8812AU` in linux.config, merged upstream in 6.13 via the + shared rtw88_88xxa core), NOT the OOT rtl8812au package. To revert: re-add + `BR2_PACKAGE_RTL8812AU=y` and drop `CONFIG_RTW88_8812AU` from linux.config. +- RTL8814AU: now the in-kernel mac80211 rtw88_8814au driver + (`CONFIG_RTW88_8814AU`, merged upstream in 6.16), NOT the OOT + rtl8814au-morrownr package. The `package/` definition is kept but unselected + (the OOT fork gets no API updates past kernel 6.14; running both would + conflict on the same USB IDs). To revert: re-add + `BR2_PACKAGE_RTL8814AU_MORROWNR=y` and drop `CONFIG_RTW88_8814AU`. +- RTL8811AU/RTL8821AU: now the in-kernel mac80211 rtw88_8821au driver + (`CONFIG_RTW88_8821AU`, same 6.13 rtw88_88xxa core as 8812au), NOT the OOT + rtl8821au-morrownr package. To revert: re-add + `BR2_PACKAGE_RTL8821AU_MORROWNR=y` and drop `CONFIG_RTW88_8821AU`. +- RTL8822BU (0bda:b812): SWITCHED to the MAINLINE rtw88 driver (kernel + `CONFIG_RTW88_8822BU=m`) instead of the out-of-tree 88x2bu. Mainline goes + through mac80211, so WPA3/SAE/PMF work correctly (the out-of-tree 88x2bu + advertised SAE+CMAC but failed WPA3-only association, status_code=1 — + verified on hardware). rtw88 USB support for this chip landed in mainline + ~6.2, AFTER stock's 5.15 froze — which is why the out-of-tree driver was + needed then and isn't now. The out-of-tree package is left OFF to avoid a + bind conflict on the same USB ID; re-enable it (and drop RTW88_8822BU) to + fall back. See `docs/wifi-parity.md`. + +**RTL8852CU / RTL8832CU** (Wi-Fi 6E, 2x2, 2.4/5/6 GHz USB) — +`BR2_PACKAGE_RTL8852CU_MORROWNR=y`, the ONE out-of-tree WiFi fork this image +ships (v10.2). This reverses the "zero out-of-tree WiFi drivers" state v10 +reached, deliberately and under ADR 0016's own unchanged rule: keep a fork +only where mainline has no USB driver for the chip. Mainline 6.18.40 has +none. rtw89 carries the 8852C chip HAL (`rtw8852c.c`, `rtw8852c_rfk.c`, +`rtw8852c_table.c`) but its ONLY bus file for that HAL is the PCIe one — +`rtw8852ce.c` is present, `rtw8852cu.c` does not exist — and the only Kconfig +symbol offered is `RTW89_8852CE`, "depends on PCI" +(`drivers/net/wireless/realtek/rtw89/Kconfig:113-122`). This board has no PCIe +(`CONFIG_PCI` unset), so even that is unreachable. Directory listing checked on +the pinned tree, not assumed; note the same directory DOES ship `rtw8851bu.c` +and `rtw8852bu.c`, so this is an 8852C-specific gap, not "rtw89 has no USB". +Net effect before this line: an RTL8852CU dongle got NO driver whatsoever. It +was the last open USB WiFi gap from the v10.1 audit (`docs/wifi-parity.md` §7). + +Bind conflict: NONE. The fork's tree is multi-chip but upstream enables only +`CONFIG_RTL8852C`, and its USB ID table is `#ifdef`-partitioned per chip, so +the built module claims just nine IDs (0bda:c85a/c832/c85d, 0db0:991d, +2c4e:0127, 3574:6251, 35b2:0502, 35bc:0101, 35bc:0102). All nine were grepped +against `drivers/net/wireless/` and `drivers/bluetooth/` in 6.18.40: zero +matches. Near misses worth knowing: rtw89_8852bu holds 35bc:0100/0108 and +btusb holds 2c4e:0128. The fork's compiled-OUT 8852B/8851B blocks WOULD +collide (with rtw89_8852bu, rtw89_8851bu and mt7921u), which is why the chip +switches must stay as upstream ships them — see +`package/rtl8852cu-morrownr/*.mk`. + +No firmware toggle needed: this vendor tree links its firmware in as a C array +(`LOAD_FW_HEADER_FROM_DRIVER`) instead of calling `request_firmware()`, unlike +mainline rtw89 which needs `BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW89` (already on +for 8851BU/8852BU). Expect a LARGE .ko in return — ~15 MB of the source tree +is firmware arrays; the built size has not been measured. + +Caveats, stated plainly: upstream's README declares 5.15-6.14 as +Realtek-tested and 6.15-7.1 as community-supported, so 6.18.40 is in the +weaker band; and the package needs a `KSRC=` override to build under Buildroot +at all (the driver's `EXTRA_CFLAGS`->`ccflags-y` translation is gated on a +kernel-version probe that looks at the BUILD HOST's `/lib/modules`). Both are +documented with file:line evidence in +`package/rtl8852cu-morrownr/rtl8852cu-morrownr.mk`. To revert: drop the line. +Nothing in linux.config needs changing with it — there is no in-kernel driver +to turn back on. + +### 5.25 P3.2: xone (Xbox One/Series accessory driver, PLAN.md §4.1 class D/E) + +`BR2_PACKAGE_XONE=y`, `BR2_PACKAGE_XOW_FIRMWARE=y`. Commit-pinned, +hash-verified, sourced from dlundqvist/xone — the actively maintained fork; +medusalix/xone (the original, and what stock's fork vendored) is explicitly in +"maintenance mode" per its own README. See `package/xone/xone.mk` for the full +fork-choice comparison. xow-firmware fetches and extracts the Xbox Wireless +Dongle firmware from Microsoft's own driver package at BUILD TIME (never +committed to git, G6) and installs it under both stock's literal filename +(`xow_dongle.bin`, for parity — `docs/stock-inventory/firmware.md`) and the +name this driver fork actually requests (`xone_dongle_02fe.bin`, a symlink to +the same bytes). ACCEPTED maintainer decision, 2026-07-13 — +`docs/decisions/0003-xone-firmware.md`. + +### 5.26 dualsensectl: DualSense operator CLI (userspace, not a driver) + +`BR2_PACKAGE_DUALSENSECTL=y`. Reaches the DualSense features hid-playstation +exposes no interface for at all — adaptive trigger effects, speaker/headphone +routing, output volume, rumble/trigger attenuation, microphone mode and +volume, player/mic LED dimming, BT power-off, firmware info. STRICTLY ADDITIVE +to the DualSense kernel patches (0033 player_id LED, 0037 mic-mute -> BTN_Z, +0042 stock lightbar LED names): those back Main_MiSTer's sysfs-LED and +input-event ABIs, which no userspace hidraw client can serve. +`docs/dualsense-tooling.md` has the analysis; `package/dualsensectl/dualsensectl.mk` +has the pin. + +Nothing invokes it automatically — no init script, no udev rule. It and +hid-playstation both write DS_OUTPUT reports to the same pad and the fields +they share (the lightbar above all) are last-writer-wins, so this stays an +operator tool run from a shell. + +It selects `BR2_PACKAGE_HIDAPI` and `BR2_PACKAGE_DBUS`. Neither appears as its +own line, and neither needs to: dbus is already explicitly set (§5.10), and +savedefconfig omits any symbol a select already forces. Verified by +regenerating: `make savedefconfig` before and after this change differ by +exactly one line, `BR2_PACKAGE_DUALSENSECTL=y`. + +THE SELECTS ARE NOT JUST hidapi+libgudev — READ THIS BEFORE TRIMMING. +hidapi's Config.in carries `select BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_COPY if +BR2_TOOLCHAIN_USES_GLIBC` (for its runtime UTF conversion of USB string +descriptors). This image is glibc and that symbol was OFF, so enabling +dualsensectl flips it on, and with `BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_LIST` empty +that copies ALL of glibc's gconv charset modules to the target: 253 .so files, +~6.4 MiB apparent, more after ext4 4 KiB block rounding. + +That is left ON DELIBERATELY rather than pinned to a minimal list, because it +closes a gap this repo already documented as load-bearing: glibc built these +modules all along (they are in the sysroot) but nothing ever installed them, +so `/usr/lib/gconv` did not exist on the target at all. Stock ships them +(`docs/package-manifest.md` §1 "glibc iconv/gconv charset modules" — libCNS, +libGB, libJIS, libKSC et al are in stock's own SONAME inventory), and that +same doc lists gconv in its "Not recommended to drop (tempting by size, but +load-bearing)" set, reason: "needed for any non-ASCII filename over SMB". So +this is a stock-parity fix that arrived as a side effect — accepted on its own +merits, not smuggled in. Guarding it: `scripts/ci-tests.sh` asserts the modules +are present, so they cannot silently vanish if dualsensectl is ever turned off +again. Pinning `GCONV_LIBS_LIST` to a guessed subset would risk silently +breaking exactly the SMB filename case the manifest calls out. ~6.4 MiB is ~3% +of the last measured 222 MiB of free image space. (This is also why +`BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_*` is a designed divergence between the image +and the kernel-only stack in `scripts/check-config-fragments.sh`, §11.) + +### 5.27 ltunify: Logitech Unifying receiver pairing (userspace, not a driver) + +`BR2_PACKAGE_LTUNIFY=y`. Closes the one Logitech gap the kernel cannot: the +PAIRING HANDSHAKE. Every other part is already covered and needs nothing added +— `CONFIG_HID_LOGITECH_DJ` (linux.config) gives each paired device its own +input node with its own logical VID/PID, which is what Main_MiSTer identifies +pads and keyboards by, and it `select`s `CONFIG_HID_LOGITECH_HIDPP` +(`drivers/hid/Kconfig:697`), which is why HIDPP is =y in the resolved .config +while being absent from our minimal defconfig. Stock has exactly the same four +symbols. A device that came pre-paired in its box therefore works with nothing +installed at all. + +Pairing is the exception: no sysfs knob, no ioctl, no kernel interface of any +kind. ltunify writes the HID++ 1.0 registers itself over `/dev/hidraw*`. +~40 KiB installed and it links against nothing but libc — this is the cheapest +package in the image, not a size question. + +LIMITS ARE REAL AND ARE HANDLED IN THE WRAPPER, NOT HERE. ltunify supports +Unifying (c52b/c532) and Nano (c52f/c534) only; it does NOT support Bolt +(c548) or Lightspeed (c539/c53a/c53f/c543). It also assumes the first hidraw +node it finds is the receiver you meant — wrong when two are plugged in, and +wrong again for a single NANO receiver, which owns two nodes because +`logi_dj_probe`'s "no HID++ collection -> -ENODEV" guard is recvr_type_dj-only. +`/usr/sbin/mister-pair-logitech` (rootfs overlay) groups nodes by physical USB +device, classifies by product ID independently of driver (Bolt is bound by +hid-multitouch here, so a driver-first filter would not even see it), refuses +the unsupported families by name, and pins the choice with ltunify's `-d` +flag. `Scripts/pair_logitech.sh` is its launcher on the data partition, the +same shim shape ADR 0026 established for `check_storage.sh`. + +NOT SOLAAR, which is the maintained tool and does cover Bolt: its only +`console_scripts` entry point routes through `solaar.gtk`, which imports +`solaar.ui`, which requires Gtk 3.0 — on an image with zero X11/GTK packages. +`docs/logitech-pairing.md` §2 has the comparison and the recheck conditions. + +No new selects worth noting: `select BR2_PACKAGE_LIBEXECINFO if +!BR2_TOOLCHAIN_USES_GLIBC` is inert here (this image is glibc), so this is a +one-line change with no transitive tail — unlike dualsensectl above. + +### 5.28 P3.3: /lib/firmware population + +PLAN.md §3/§4.1, module loading & firmware infra — the +module-autoload/depmod/kmod/xz-compress half is already done (§3.5, §5.30). +Source of truth: `docs/firmware-parity.md` (the inventory -> sub-option mapping ++ the built-vs-stock diff). Target: `docs/stock-inventory/firmware.md`'s +66-file inventory (`xow_dongle.bin`, the 67th stock file, is P3.2's +xow-firmware, §5.25, not repeated here). + +linux-firmware itself (`BR2_PACKAGE_LINUX_FIRMWARE`) is a meta-option with no +files of its own — every actual file comes from a sub-option, each picked +because it is the SMALLEST upstream grouping that contains an inventory file +(Buildroot's own file lists are coarse per sub-option, so some non-inventory +sibling files ride along — a documented superset, not a problem; see the +parity doc). `regulatory.db`/`.p7s` come from a SEPARATE package +(wireless-regdb, not linux-firmware — upstream split them out after the +kernel gained direct .db-loading support in 4.15). + +Buildroot stamping trap: changing linux-firmware SUB-options on an incremental +build installs nothing and exits 0 — `make linux-firmware-dirclean` first. + +| symbol | files / reason | +|---|---| +| `_MEDIATEK_MT7601U` | `mt7601u.bin` (top-level, via WHENCE-driven symlink — see parity doc for the build-verified proof) | +| `_MEDIATEK_MT7610E` | `mediatek/mt7610e.bin` | +| `_MEDIATEK_MT7650` | `mt7650.bin` — filed under Buildroot's "Bluetooth firmware" menu (MT7650 is a WiFi+BT combo chip) but is the ONLY toggle that installs this WiFi file; stock's own inventory attributes it to rt2800usb, not to Bluetooth | +| `_MEDIATEK_MT76X2E` | `mediatek/mt7662.bin` + `mediatek/mt7662_rom_patch.bin` (top-level via symlink) — also what the in-tree mt76x2u USB driver requests (`mt76x2/usb_mcu.c`), not a separate `mt7662u.bin` (see parity doc: stock's own `mediatek/mt7662u.bin` / `mt7662u_rom_patch.bin` are the OLD out-of-tree name, superseded, not reproduced) | +| `_MEDIATEK_MT7921` | `WIFI_RAM_CODE_MT7961*.bin` — MT7921U (`mt7921u.ko`, WiFi6 USB; the USB part reports as MT7961) | +| `_MEDIATEK_MT7925` | `WIFI_RAM_CODE_MT7925*.bin` — MT7925U (`mt7925u.ko`, WiFi6E USB) | +| `_RALINK_RT2XX` | `rt2870.bin` (rt2800usb, `FIRMWARE_RT2870`) + siblings | +| `_RTL_81XX` | rtlwifi 8188e/8192c/8192d/8192s/8192eu family | +| `_RTL_87XX` | rtlwifi 8712u/8723a/8723b family | +| `_RTL_87XX_BT` | rtl_bt 8723a/8723b/8723bs/8761a/8761bu family | +| `_RTL_88XX_BT` | `rtl_bt/rtl88*.bin` glob — covers 8812ae/8821a/8821c/8822b/8822cu in one option | +| `_RTL_RTW88` | `rtw88/rtw8822b_fw.bin` etc. — firmware for the MAINLINE rtw88 driver we now use for RTL8822BU (replacing out-of-tree 88x2bu); also covers 8821cu/8822cu rtw88 | +| `_RTL_RTW89` | `rtw89/*.bin` — mainline rtw89 (RTL8851BU/RTL8852BU WiFi6/6E USB) | +| `_ATHEROS_9271` | `ar9271.fw` + `htc_9271*` — ath9k_htc (AR9271 802.11n USB) | +| `_ATHEROS_7010` | `ar7010*.fw` + `htc_7010*` — ath9k_htc (AR7010-based 802.11n USB) | +| `_ATHEROS_9170` | `carl9170-1.fw` — carl9170 (AR9170 802.11n USB) | +| `_MEDIATEK_MT7921_BT` | v10.2 Bluetooth firmware for combo/BT dongles whose driver we already build (`docs/bluetooth-parity.md`); same class of gap as ath3k below — the driver binds, then dies at `request_firmware()`. `mediatek/BT_RAM_CODE_MT7961_1_2_hdr.bin` — the BT half of the MT7921AU combo dongle whose WiFi half we already ship. Requested by `btmtk.c`; without it WiFi works and BT does not | +| `_MEDIATEK_MT7922_BT` | `mediatek/BT_RAM_CODE_MT7922_1_1_hdr.bin` — btusb carries MT7922 USB IDs, so this is a reachable USB path, not just the M.2 part | +| `_MEDIATEK_MT7925_BT` | `mediatek/mt7925/BT_RAM_CODE_MT7925_1_1_hdr.bin` — BT half of the MT7925U combo | +| `_QUALCOMM_6174A_BT` | `qca/rampatch_usb_00000302.bin` + `qca/nvm_usb_00000302.bin` — QCA ROME 6174A over USB. btusb requests exactly the "_usb_" names (`btusb.c`: "qca/rampatch_usb_%08x.bin"), and its QCA path is self-contained — it needs no `CONFIG_BT_QCA`, so this firmware is the only missing piece. 132 KiB | +| `_ATHEROS_6004` | `ath6k/AR6004/hw1.2` + `hw1.3` (132 KiB) — ath6kl_usb (`CONFIG_ATH6KL_USB=m`, new in v10.1; AR6003/AR6004 802.11n USB) | +| `_REDPINE_RS9113` | `rsi/rs9113_*.rps` — rsi_usb | +| `_REDPINE_RS9116` | `rsi/rs9116_wlan.rps`, requested by `rsi_91x_hal.c:35` (both toggles needed; `CONFIG_RSI_USB=m`) | +| `_AR3011` | `ath3k-1.fw` — the ath3k driver (`CONFIG_BT_ATH3K=m`, already on) for AR3011 USB Bluetooth. The driver was built but its firmware was NEVER installed, so every AR3011 dongle failed at `request_firmware()`; this closes that gap | +| `_AR3012_USB` | `ar3k/*.dfu` — AR3012 USB Bluetooth patch/config RAM images, loaded by the same ath3k driver (and by btusb for the newer AR3012 IDs) | +| `_BRCM_BCM43XX` | `brcm/brcmfmac4373.bin` + the 43xx SDIO/PCIe siblings — brcmfmac (`CONFIG_BRCMFMAC=m`, new). Implicitly `select`s `BR2_PACKAGE_LINUX_FIRMWARE_CYPRESS_CYW43XX` (`cypress/cyfmac*`, the same silicon post-acquisition) | +| `_BRCM_BCM43XXX` | `brcm/brcmfmac43143.bin`, `43236b.bin`, `43242a.bin`, `43569.bin` — the four BCM43xx USB parts brcmfmac drives; `select`s `_CYPRESS_CYW43XXX` | +| `BR2_PACKAGE_WIRELESS_REGDB` | `regulatory.db` + `regulatory.db.p7s` (separate from linux-firmware, see above) | + +NOT enabled: `_QUALCOMM_9377_BT`. Its two files are `qca/rampatch_00230302.bin` +/ `nvm_00230302.bin` — the NON-usb names, which only the UART path (hci_qca, +`CONFIG_BT_HCIUART`, not built) ever requests. No consumer here. NOT enabled: +`_LINUX_FIRMWARE_IBT` (Intel Bluetooth, `intel/ibt-*`). 30 MiB, and Intel BT +controllers ship essentially only on M.2 WiFi+BT combo cards, which this board +cannot host — there is no realistic external Intel BT USB dongle. +`CONFIG_BT_INTEL` is nonetheless built because `CONFIG_BT_HCIBTUSB` `select`s +it unconditionally (it cannot be turned off while btusb is on), so this is a +DELIBERATE driver-without-firmware, unlike the ath3k/mt7663 cases which were +accidental. Flip this on if an Intel BT dongle ever needs to work. + +`BR2_PACKAGE_LINUX_FIRMWARE_EXTRA=y` — ten files that NO linux-firmware +sub-option covers, even though upstream linux-firmware carries them and this +project's pinned kernel has an in-tree consumer for each (verified by grep +against the actual built kernel source, not assumed; last checked on 6.18.40 — +re-grep on a kernel bump rather than trusting this line — see +`package/linux-firmware-extra/linux-firmware-extra.mk` and +`docs/firmware-parity.md` for the per-file citation). Same upstream tarball and +hash-pin as linux-firmware itself, just a different subset kept. Four are +stock-parity files; the other five are NOT (stock ships none of them) — +`mediatek/mt7663*` x4 back `CONFIG_MT7663U=m` and `rtlwifi/rtl8192dufw.bin` +backs `CONFIG_RTL8192DU=m`, both enabled beyond stock, and each would +otherwise probe and then fail at `request_firmware()`. See `docs/wifi-parity.md` +§6.2, §7. + +`BR2_PACKAGE_BCM20702_FIRMWARE=y` — Broadcom BCM20702 BT dongle firmware +(P3.14): `brcm/BCM20702A1-0b05-17cb.hcd`, the one brcm .hcd stock ships (35000 +bytes) but mainline linux-firmware lacks. Hash-pinned build-time fetch, never +a committed blob — same maintainer-approved vendor-firmware posture as xow +(ADR 0003). See `package/bcm20702-firmware/`. + +### 5.29 Explicitly NOT carried forward (manifest §5 Drop list) + +A note, not config — nothing is set by it: + +- archivemount (broken in stock; its deps libarchive/libfuse ARE kept, §5.11, for others) +- adplay / adplug / binio — no Buildroot package, no known MiSTer use +- libhid / libhid-detach-device — no Buildroot package, superseded API +- jack1/jack2 (libjack) — dangling in stock, unused by MiSTer +- rtorrent / libtorrent — unused BitTorrent client, SONAME already drifted + +### 5.30 kmod (target half) + +Two different things, despite the shared prefix: `BR2_PACKAGE_HOST_KMOD_XZ` is +HOST kmod ("support xz-compressed modules", so the host depmod that runs at +build time can read the `.ko.xz` we ship) and lives in the board fragment +(§3.5) because the kernel-only stack needs it too; `BR2_PACKAGE_KMOD_TOOLS=y` +is TARGET kmod — installs depmod/insmod/lsmod/modinfo/modprobe/rmmod on the +device — and is image-only. + +### 5.31 T5 — utility binaries stock ships that this image left out (2026-07-27) + +A filename diff of stock's `/bin`,`/sbin`,`/usr/bin`,`/usr/sbin` (`work/imgroot`) +against our `rootfs.tar` found 315 differences. Most are noise — a full Perl +install, python3.9-versioned scripts, GNU long-form duplicates of BusyBox +applets already covered by the util-linux/coreutils blocks elsewhere. The +packages in §5.32–§5.41 are the real gaps: each is confirmed present in this +pinned `work/buildroot/package/` tree, and its Config.in was read (not assumed) +for hidden `select`s, sub-options that default to "n", and toolchain +prerequisites BEFORE being added — three of those reads found a genuine +collision with a BusyBox applet that is already ON in this image (lsof, lsusb, +mkdosfs — plus chvt/deallocvt/openvt/setkeycodes via kbd); see +`board/mister/de10nano/busybox.fragment` for the disables that resolve them, +same non-deterministic-last-install-wins idiom as the existing ifup/ifdown and +util-linux blocks in that file. + +The BusyBox-applet half of this same task (stat, timeout, tac, shuf, comm, +split, expand, groups, nc) lives entirely in busybox.fragment — nothing to +enable in the Buildroot config for those nine. wpa_cli/wpa_passphrase +(`WPA_SUPPLICANT_CLI`/`_PASSPHRASE`) are also part of this task but live with +the rest of the wpa_supplicant block (§5.19); `BR2_PACKAGE_NTFS_3G_NTFSPROGS` +and `BR2_PACKAGE_DTC_PROGRAMS` are likewise placed next to their +already-existing parent lines (§5.11, §5.15) rather than duplicated. + +Explicitly REJECTED (maintainer's call, not a gap missed) — recorded so the +decision is durable and doesn't get "rediscovered" as an oversight later; +full reasoning in `docs/package-manifest.md`'s Drop list (§5): + +- perl — anyone who needs it can build their own image from this repo; no + MiSTer-specific consumer was found (stock's own init/service scripts are + all shell, not Perl — package-manifest.md §5). +- vim — BusyBox vi AND nano (§5.20) already cover on-device editing; vim is + the heaviest of stock's three editors. +- screen — tmux (§5.35) is this image's terminal multiplexer. Not both. +- gdb — (the on-device, TARGET debugger, as opposed to gdbserver) — host gdb + + gdbserver cross-debugging is judged the better shape for this project, + and a target debugger is 4-8 MB. NOTE, so this doesn't read as inconsistent + with §5.42: `BR2_PACKAGE_GDB`/`_GDB_DEBUGGER=y` ARE set, but by the DEBUG + TOOLING block, for the separate, still-open RT-latency investigation + (`docs/debug-tooling.md`) — that is a dated, revert-as-one-unit decision, + not a reversal of this one. +- ltrace — narrow, frequently broken on ARM (a known, longstanding upstream + limitation, not specific to this toolchain); strace (§5.32) supersedes it + for what this image needs. +- unrar — non-free (RARLAB) licence. (The "rule G6" this line used to cite is + about not committing binaries to git, not about licensing — PLAN.md §2; + corrected in passing 2026-07-27.) Not needed anyway: MiSTer release + archives are .7z, not .rar, and `BR2_PACKAGE_7ZIP` (§5.37) closes that gap. + 7-Zip additionally brings RAR/RAR5 **extraction** along for free, under + LGPL-2.1+ with the unRAR restriction (a no-reverse-engineering-of-the-RAR- + compressor clause — see `package/7zip/7zip.mk`'s license comment), which is + a different and far weaker thing than vendoring RARLAB's own non-free + unrar. Compressing to .rar is still not possible, and nothing here needs it. + +### 5.32 T5: process / file / syscall inspection + +- `BR2_PACKAGE_HTOP=y` — interactive process viewer. Needs `BR2_USE_MMU` + (fork()) + dynamic libs (dlopen()) — both already true on this glibc/ARM + target. Selects `BR2_PACKAGE_NCURSES`, already =y, so this is a + zero-marginal-dependency add (`package/htop/Config.in`). +- `BR2_PACKAGE_STRACE=y` — syscall tracer. Was first enabled TEMPORARILY by + the DEBUG TOOLING block (§5.42) for the field-hang/RT-latency work; this + line promotes it to a PERMANENT part of the package set, so strace keeps + shipping once that block is eventually deleted. The old monolith set it + twice (once here, once inside the debug block, "same value, last one wins", + kept so the block stayed contiguous); the fragment sets it ONCE, here — a + second definition is a redefinition `scripts/check-config-fragments.sh` + rejects, and it was also a kconfig "override: reassigning" warning on every + configure. Deleting the debug block therefore no longer removes strace, + which is exactly what T5 intended. +- `BR2_PACKAGE_LSOF=y` — lsof(8). Needs `BR2_USE_MMU` (fork()) and + `BR2_PACKAGE_BUSYBOX_SHOW_OTHERS` (already =y, for i2c-tools) — no new + dependency. COLLIDES with BusyBox's own `lsof` applet (`CONFIG_LSOF=y` in the + base config) — disabled in busybox.fragment, see that file for the full + citation. + +### 5.33 T5: USB / input / joystick & force-feedback + +- `BR2_PACKAGE_USBUTILS=y` — lsusb, usb-devices, lsusb.py (removed by the + package's own install rule when no target python3 — irrelevant here, we DO + ship python3, but the C lsusb is what matters). Needs + `BR2_TOOLCHAIN_HAS_THREADS`, gcc >= 4.9 and `BR2_PACKAGE_HAS_UDEV` (hwdb) — + all already true (eudev is on, P2.1). Selects LIBUSB (already =y). We did + NOT already get lsusb from anywhere else — verified no `BR2_PACKAGE_USBUTILS` + and no busybox-provided lsusb.py equivalent were previously on, so this is a + clean add, not a fix for a regression. COLLIDES with BusyBox's own `lsusb` + applet — disabled in busybox.fragment. +- `BR2_PACKAGE_EVTEST=y` — evtest — dumps `/dev/input/eventN` activity. No + dependencies beyond libc. +- `BR2_PACKAGE_LINUXCONSOLETOOLS=y` + `_JOYSTICK=y` + `_FORCEFEEDBACK=y` — on a + games console, arguably the single most valuable package in this whole T5 + pass: joystick calibration (jstest, jscal, jscal-store/-restore, + evdev-joystick) and force-feedback testing (fftest, ffcfstress, ffmvforce, + ffset). FORCEFEEDBACK needs dynamic libs (already true) and selects + `BR2_PACKAGE_SDL2`, already =y (§5.6) — zero marginal cost. Side effect, not + asked for but harmless: the package's top-level `select + LINUXCONSOLETOOLS_INPUTATTACH if !JOYSTICK && !FORCEFEEDBACK` does NOT fire + (both are on), but INPUTATTACH's OWN "default y" + (`package/linuxconsoletools/Config.in`) still applies since nothing sets it + off — so `inputattach` (legacy serial-joystick/GPS attach helper) lands too, + unconditionally by upstream default. Small, harmless, not worth suppressing. + +### 5.34 T5: filesystem tools (FAT/exFAT) + +Both need `BR2_USE_WCHAR`, already true (glibc). dosfstools' three programs +each default to "n" with NO indication of that in the parent's prompt text — +confirmed by reading `dosfstools.mk` directly: each of +FATLABEL/FSCK_FAT/MKFS_FAT is wrapped in its own `ifeq (...,y)` install guard, +so leaving any one unset means that binary (and its compat symlinks) simply +does not install, silently. All three are wanted (fatlabel, fsck.vfat via +FSCK_FAT, mkfs.vfat via MKFS_FAT are the task's explicit list), so all three +are set. MKFS_FAT's compat symlinks include `mkdosfs` — collides with +BusyBox's own applet of that name, disabled in busybox.fragment. + +- `BR2_PACKAGE_DOSFSTOOLS=y`; `_FATLABEL=y` (fatlabel + dosfslabel compat + symlink); `_FSCK_FAT=y` (fsck.fat, + fsck.vfat/fsck.msdos/dosfsck compat + symlinks); `_MKFS_FAT=y` (mkfs.fat, + mkdosfs/mkfs.msdos/mkfs.vfat compat + symlinks — mkdosfs collision above). +- `BR2_PACKAGE_EXFATPROGS=y` — mkfs.exfat, fsck.exfat, dump.exfat — no + sub-options, no collision (BusyBox has no exFAT support of any kind to + collide with). The stage-1 initramfs builds it too, for the on-demand repair + path (§8.6). + +### 5.35 T5: serial / terminal + +- `BR2_PACKAGE_PICOCOM=y` — minimal serial terminal. No dependencies. Does NOT + collide with BusyBox's `microcom` applet — different binary name. +- `BR2_PACKAGE_LRZSZ=y` — rz/sz (X/Y/Zmodem) — stock ships them + (`docs/stock-reconciliation.md` §3c). Needs `!BR2_STATIC_LIBS` (dynamic, + already true — lrzsz redefines `error()`/`error_at_line()` and clashes with + a static libc's own). Installs rz/sz plus SIX bonus compat symlinks — + lrz/rb/rx -> rz and lsz/sb/sx -> sz, counted off `lrzsz.mk:21-26` one `ln + -sf` at a time (rx/sx are easy to miss) — that stock's addon.tar does not + have: harmless extras, not a divergence that matters. +- `BR2_PACKAGE_TMUX=y` — terminal multiplexer — chosen over screen (see the + rejected list, §5.31; not both). Needs `BR2_USE_MMU` (fork()), `BR2_USE_WCHAR` + (mbtowc()) and `BR2_ENABLE_LOCALE` (runtime UTF-8 locale) — all three + already true (`BR2_GENERATE_LOCALE="en_US.UTF-8"`, §5.44). Selects + `BR2_PACKAGE_LIBEVENT` (already =y, §5.10) and `BR2_PACKAGE_NCURSES` (already + =y) — no new dependency weight. + +### 5.36 T5: network diagnostics + +- `BR2_PACKAGE_ETHTOOL=y` — examine/tune the ethernet NIC. No dependencies. + `ETHTOOL_PRETTY_PRINT` is a sub-option that DOES default to "y" (unlike + dosfstools' three above), so it is not listed separately — confirmed in + `package/ethtool/Config.in`. +- `BR2_PACKAGE_SOCAT=y` — multipurpose socket relay/debug tool. Needs + `BR2_USE_MMU` (fork()), already true. +- `BR2_PACKAGE_TCPDUMP=y` — selects `BR2_PACKAGE_LIBPCAP` automatically + (`package/tcpdump/Config.in`) — not listed separately, it is a plain + `select`, not a default-off sub-option. `TCPDUMP_SMB` ("smb dump support", + the package's own Config.in calls it "possibly-buggy") deliberately left off + — not asked for, adds risk for nothing this image needs. +- `BR2_PACKAGE_IPERF3=y` — active bandwidth measurement. Needs + `BR2_TOOLCHAIN_HAS_ATOMIC` + `_THREADS`, both already true on this glibc/ARM + toolchain. + +### 5.37 T5: archival — `package/7zip` (OURS), not upstream's `p7zip` + +7z support is the highest-value item here: MiSTer release archives are .7z, +so without it on-device extraction of a downloaded release is impossible. + +THIS IS `package/7zip` (OURS), NOT upstream's `package/p7zip` — and the swap is +not a preference, it closes a real hole. The Downloader hardcodes +`/media/fat/linux/7za` and, when that file is absent, DOWNLOADS p7zip 16.02 +(2016-05-21, ARM, dynamically linked) from +`SD-Installer-Win64_MiSTer/raw/master/7za.gz` to fill it. Our build now ships +a statically-linked 7-Zip 26.02 into that exact path via both payload routes, +so the fetch never fires. Full mechanism + evidence: ADR 0023, +`docs/downloader-contract.md` §4. p7zip itself is dead upstream since 16.02 +and carried on only by a community fork at 17.06 (2022), so "the latest +p7zip" would still be four years stale against 7-Zip's own Linux support. + +Same four toolchain deps p7zip had, all still satisfied and all still real +(they are not copied over blindly): `BR2_TOOLCHAIN_HAS_SYNC_4` for +`C/Threads.c:783`'s `__sync_add_and_fetch` on a 4-byte LONG, +`BR2_INSTALL_LIBSTDCPP` because the link driver is g++ (C++ was already +pulled in for Main_MiSTer/T1-T4, §2.1), `BR2_TOOLCHAIN_HAS_THREADS` for the +`-lpthread` the makefile hardcodes, and `BR2_USE_WCHAR` because 7-Zip's +UString is wchar_t-based throughout. + +There is no 7za/7zr Kconfig `choice` to pin here the way p7zip needed: 7-Zip +builds ONE full application (`7zz`) and the old 7za/7zr split is a matter of +which upstream Bundles/ target you compile, not a runtime mode. `7za` is +installed as an alias to it — see `package/7zip/7zip.mk`. That also makes the +old "7za's extra zip/cab/arj/... support is not needed" note moot: it comes +along at zero cost, and zip/lzop below plus the busybox xz/bzip2/gzip applets +stay for the compress-side and applet-parity reasons in their own notes. +`BR2_PACKAGE_7ZIP=y`. + +- `BR2_PACKAGE_ZIP=y` — zip/PKZIP-compatible archiver. No dependencies. + BusyBox has `unzip` but no `zip` applet of its own — no collision. +- `BR2_PACKAGE_LZOP=y` — lzop compressor. Selects `BR2_PACKAGE_LZO`, already =y + (§5.4). Installs ONLY the `lzop` binary (verified by reading the upstream + 1.04 Makefile.am: `bin_PROGRAMS = src/lzop`, no unlzop/lzopcat symlinks) — + does not collide with BusyBox's own `unlzop`/`lzopcat` applets + (decompress-only, different names, still on). BusyBox's OWN `lzop` + (compressing) applet is separately already off in the base config, so + there is nothing to disable even if it did share the name. + +### 5.38 Off-device backup: Azure Storage CLI — package present, NOT enabled + +`# BR2_PACKAGE_AZCOPY is not set`. azcopy (`package/azcopy`, BR2_EXTERNAL — +upstream Buildroot has none) pushes the exFAT data partition's +saves/screenshots/config to Azure Storage straight from the board. It WORKS: +it was built, installed and exercised end-to-end on real hardware (132 MiB +uploaded, md5-verified round trip, incremental `sync`, Azure Files, plan files +on exFAT). `docs/azcopy.md` section 4 has the transcript. + +IT IS OFF BECAUSE OF SIZE, and only because of size. 41,007,016 bytes = +39.1 MiB installed — the second-largest package in the image after samba4's +~49 MiB, and about a fifth of the free space `linux.img` has left +(`scripts/check-size-budget.sh` on the 2026-08-17 build: 195 MiB / 38.1% free). +The budget would still pass at ~30.5% free. "Would still pass" is not the same +as "is worth spending", and for a tool most users will never run it is not: +azcopy ships as a standalone downloadable artifact instead, where the people +who want it pay the 8.3 MiB (xz) download and nobody else pays anything. See +`docs/azcopy.md` section 1. + +WHY IT IS SO BIG, since that is the obvious next question: almost none of it +is AzCopy. Measured by linking each dependency tree on its own for ARMv7 — an +empty Go binary is 1.2 MB, +Azure SDK is 5.5 MB, and +Google Cloud Storage is +27.6 MB. GCS drags in gRPC, protobuf and the Envoy go-control-plane xDS protos, +none of which a MiSTer backup will ever execute. AzCopy's own code is ~1.3 MB +of symbols. `docs/azcopy.md` section 1 also prices the surgery to cut it out +(~20 files, a permanently-carried patch across credential handling). + +TURNING IT ON is one line — replace the not-set line with +`BR2_PACKAGE_AZCOPY=y` and the package builds, installs `/usr/bin/azcopy` and +its `/etc/profile.d/azcopy.sh` defaults, and selects `BR2_PACKAGE_HOST_GO` +(build-time only, nothing from it ships) plus `BR2_PACKAGE_CA_CERTIFICATES` +(already =y in its own right, §5.9, and deliberately kept explicit there so it +survives azcopy being switched off again). Enabling it also pulls host-go's +five-stage from-source bootstrap into the build — measured at ~4.2 min, plus +~12 s to compile azcopy itself, so WALL CLOCK is not the concern. DISK is: +host-go's module cache measured 1.7 GiB, and `docs/ci.md`'s disk-and-cache +budget was written without it. Check that before turning it on in CI. +(`release.yml` enables it for the standalone artifact by appending the line +to `output/.config` after `make de10nano-defconfig` + `olddefconfig`.) + +ARMv7 IS NOT AN UPSTREAM-SUPPORTED TARGET for AzCopy: Microsoft publishes +linux/amd64 and linux/arm64 binaries only, and two defects had to be patched +to build and run at all (`package/azcopy/0001-*`, `0002-*`). + +### 5.39 T5: hardware buses + +dtc (`BR2_PACKAGE_DTC_PROGRAMS`) and i2c-tools (`BR2_PACKAGE_I2C_TOOLS`) are +also part of this task's Group 3 list, but both are handled where their +existing line already lives (§5.15): DTC_PROGRAMS is set right next to +`BR2_PACKAGE_DTC=y` (it was library-only until then), and +`BR2_PACKAGE_I2C_TOOLS=y` was already fully on (P3.11, RTC add-on) with no +sub-options gating any of its tools — nothing to add there. +`BR2_PACKAGE_SPI_TOOLS=y` — spi-config, spi-pipe — Linux spidev command-line +helpers. No dependencies at all beyond autoreconf (host-side only). + +### 5.40 T5: Bluetooth CLI + +`BR2_PACKAGE_BLUEZ_TOOLS=y` — bt-adapter, bt-agent, bt-device, bt-network, +bt-obex. Depends on `BR2_PACKAGE_BLUEZ5_UTILS` (already =y, §5.13), +`BR2_USE_MMU` and `BR2_USE_WCHAR` (both true) and `BR2_TOOLCHAIN_HAS_THREADS` +(true). Selects DBUS, DBUS_GLIB, LIBGLIB2 — ALL THREE already =y (§5.10) — and +READLINE, already =y (§5.11) — so this is a genuinely +zero-marginal-dependency add, every one of its `select`s was already paid +for. NOTE for whoever reads this next to T3: stock's `usr/sbin/btctl`/`btpair` +scripts (vendored, `docs/stock-reconciliation.md` §3c) are dbus-python + +PyGObject talking to bluetoothd directly (§5.17), NOT wrappers around these +bt-* CLI tools — the two are independent Bluetooth control paths that happen +to ship together, not a dependency of one on the other. + +### 5.41 T5: console / keyboard + +`BR2_PACKAGE_KBD=y`. kbd is the SAME upstream package stock's own +loadkeys/setfont/showkey/dumpkeys came from (kbd-2.9.0 here; verified stock's +strings match this family in `board/mister/de10nano/rootfs-overlay/etc/inittab`'s +own note 3). T3 already vendored `etc/kbd.map` and restored the guarded +inittab lines (`[ -x /usr/bin/loadkeys ] && ...`) in anticipation of this line +landing — see `docs/stock-reconciliation.md` §3c ("etc/kbd.map", +"consolefonts" rows) and `etc/inittab`'s note 3. Needs `BR2_USE_MMU` (fork()) +and gcc >= 4.9 (`_Generic`) — both already true. +`--disable-vlock/--disable-tests` (`package/kbd/kbd.mk`) are upstream +Buildroot's own choices, not touched here. COLLIDES with four BusyBox +console-tools applets that are already on — chvt, deallocvt, openvt, +setkeycodes — all four disabled in busybox.fragment; see that file for the +full citation (kbd's own `src/Makefile.am` PROGS list + `configure.ac`'s +`KEYCODES_PROGS` default). + +setfont's font: kbd 2.9.0 ships its own `data/consolefonts/default8x16.psfu` +and installs it to `/usr/share/consolefonts` (`data/Makefile.am:45-49`) — the +exact directory stock's own setfont hardcodes — so bare `setfont` (our guarded +inittab line) resolves without any extra data file needing to be vendored; +matching or not matching stock's exact filename (`default8x16.psfu.gz`) does +not matter either way: setfont's own lookup (kbd-2.9.0 +`src/libkfont/setfont.c:417`, `findfont()` -> `kbdfile_find()`) tries the bare +name AND every configured decompressor's suffix (`kbdfile.c:241-267`, +`maybe_pipe_open()`) before giving up, so it finds `default8x16.psfu` OR +`default8x16.psfu.gz` equally well, transparently decompressing via a pipe if +needed. Whether the installed file actually ends up named .psfu or .psfu.gz +depends on whether THIS build host has gzip at kbd's configure time +(`data/Makefile.am`'s install-consolefonts runs `configure.ac`'s +`enable_compress=auto` -> "gzip -n" check first) — not verified against a real +build for this task; check `output/target/usr/share/consolefonts/` on the next +one if the exact filename ever matters. Either way, functionally identical, +not a gap. + +SIZE, honestly: that one font is not the only thing that lands. kbd's +`install-data-hook` (`data/Makefile.am:73`) unconditionally runs FOUR install +rules — install-keymaps, install-consolefonts, install-consoletrans, +install-unimaps — there is no Buildroot/configure knob to install only the +one font. Read directly from the pinned 2.9.0 tarball: `data/consolefonts` +(209 files, 1.5 MiB), `data/keymaps` (281 files minus a handful ignored by +`IGNORE_KEYMAPS`, 3.1 MiB), `data/consoletrans` (500 KiB), `data/unimaps` +(368 KiB) — ~5.5 MiB of uncompressed source. configure's default +(`enable_compress=auto`) gzips fonts+keymaps at BUILD time if a host gzip +exists (it does, on any real build host), so the INSTALLED size is smaller +than 5.5 MiB, but by how much was not measured here (no build was run for +this task) — do not assume a specific number without checking +`output/target/usr/share/{consolefonts,keymaps,consoletrans,unimaps}/du -sh` +on the next real build. Not a blocker: `./scripts/check-size-budget.sh +output/images/linux.img` on the last built image reports 512 MiB total, +290 MiB USED, 222 MiB / 43.4% FREE against a 15% threshold — so even the +full 5.5 MiB uncompressed worst case is ~2.5% of the headroom. Two caveats on +that number, stated rather than glossed: it is measured on an image built +BEFORE T3/T5's ~21 new packages, so real free space after this lands is +lower; and the 290 MiB figure is the USED half, not the free half (an easy +swap to make — `docs/package-manifest.md`'s Drop-list §5 zoneinfo row is the +companion discussion of what "not a concern" looks like at this scale, and +of why block usage beats the byte column for many-small-files trees like +these). Re-run that script after the next real build rather than trusting +either number here. This is still real data this image did not carry before, +useful beyond the one keymap/font stock actually used, and +`docs/package-manifest.md` §5's consolefonts/keymaps row is updated to say so. + +### 5.42 DEBUG TOOLING — TEMPORARY, REMOVE AS ONE BLOCK + +Everything between the `>>> DEBUG TOOLING` banner and the matching `>>> END +DEBUG TOOLING` banner in the fragment is on-device debugging/profiling +tooling, enabled on request for the field hard-hang investigation and for RT +latency measurement. It is NOT stock parity, it is NOT part of the P2.1 +package manifest, and it is expected to be removed once those investigations +close. Full rationale, per-package sizes, on-device usage and the exact +revert recipe: `docs/debug-tooling.md`. + +TO REVERT: delete the whole block (banner to banner) and re-run `make +de10nano-defconfig && make all`. Nothing outside this block and the matching +`CONFIG_COREDUMP` block in `board/mister/de10nano/linux.config` depends on any +of it. + +The block is deliberately contiguous and `BR2_PACKAGE_`-only so that (a) it +can be deleted with one editor motion, and (b) it cannot evict the CI +cross-toolchain cache: `.github/actions/buildroot-build`'s toolchain +fingerprint filters out every `^BR2_PACKAGE_` line, so adding these costs no +cold 3h rebuild (`docs/ci.md#toolchain-fingerprint`). + +The old monolith warned that `make savedefconfig` DISSOLVES this block: it +rewrote the file from kconfig's own state, which knows nothing about banners — +the comments went, and the symbols scattered into the generated ordering, +after which "delete the block" was no longer a one-motion operation. That +hazard is gone with the fragment split (§1: savedefconfig no longer writes +into a tracked file). + +- `BR2_PACKAGE_GDB=y`, `BR2_PACKAGE_GDB_SERVER=y`, `BR2_PACKAGE_GDB_DEBUGGER=y` + — gdbserver AND the full on-device debugger. Both are asked for explicitly: + gdbserver for the normal cross-debug flow (host cross-gdb over TCP), the + full debugger so a core file can be opened on the device itself with no + host toolchain present, which is the realistic flow for a field hard-hang + report from a beta tester. NOTE on `BR2_PACKAGE_GDB_SERVER`: + `package/gdb/Config.in` `select`s it whenever GDB_DEBUGGER is off, so it + would be implied by GDB alone TODAY — but that select disappears the moment + `GDB_DEBUGGER=y` (which is the case here), so it must be listed explicitly + or enabling the full debugger would silently DROP gdbserver. Setting it is + not redundant. GDB_DEBUGGER pulls in `BR2_PACKAGE_{GMP,MPFR,READLINE,ZLIB}` by + select. Three of the four are already on and stay on after this block is + deleted, but for TWO different reasons, worth keeping straight: readline and + zlib are set EXPLICITLY (§5.11, §5.4); GMP is NOT set at all — it arrives + transitively via gnutls/gcrypt (§5.9, which says do not set it separately). + Only MPFR is genuinely new, and it too arrives transitively rather than + being listed — do NOT add a line for it, or it would outlive this block's + deletion, which is the one thing this arrangement exists to prevent. + `BR2_USE_WCHAR=y` is a GDB_DEBUGGER dependency and is already satisfied + (glibc + `BR2_ENABLE_LOCALE`). `GDB_TUI` / `GDB_PYTHON` are deliberately NOT + enabled: neither was requested and both only add on-device UI weight. +- strace — syscall tracing, no dependencies at all on this toolchain — was in + this block and is now permanent (§5.32); `strace -k` (stack traces on each + syscall) additionally wants `BR2_PACKAGE_LIBUNWIND`, which is deliberately + left off; see `docs/debug-tooling.md` "what is deliberately NOT enabled". +- `BR2_PACKAGE_LINUX_TOOLS_PERF=y`, `BR2_PACKAGE_LINUX_TOOLS_PERF_NEEDS_HOST_PYTHON3=y` + — perf, built from OUR kernel's own `tools/perf` (`BR2_PACKAGE_LINUX_TOOLS_PERF` + selects the `BR2_PACKAGE_LINUX_TOOLS` meta-symbol, which is part of the + linux package, not a standalone one — so perf is rebuilt whenever the + kernel is). The kernel side needs nothing from us: + `package/linux-tools/linux-tool-perf.mk.in` force-enables + `CONFIG_PERF_EVENTS` via `KCONFIG_ENABLE_OPT` at kconfig-fixup time, and in + this tree that fixup is already a no-op — `CONFIG_PERF_EVENTS=y`, + `CONFIG_ARM_PMU=y` and `CONFIG_HW_PERF_EVENTS=y` all resolve on by kconfig + default and are live in the built .config today (verified against + `output/build/linux-/.config`; last confirmed on 6.18.39). + Hardware counters are wired too: `socfpga.dtsi` has the `arm,cortex-a9-pmu` + node with both per-CPU PMU interrupts, so `perf stat` gets real + cycle/instruction counts rather than software events only. + `_NEEDS_HOST_PYTHON3` is NOT optional on this kernel and NOT cosmetic: since + ~6.0 perf generates `pmu-events.c` at build time by running + `tools/perf/pmu-events/jevents.py`, and the pinned kernel still does (that + file is present in the unpacked tree; last confirmed on 6.18.39). Without + this symbol Buildroot never adds host-python3 to `PERF_DEPENDENCIES` and the + perf build fails on whatever python3 the *host* happens to have, or none. +- `BR2_PACKAGE_RT_TESTS=y` — rt-tests — cyclictest et al., the standard + PREEMPT_RT latency harness. This is what actually measures the RT kernel's + wakeup latency on hardware, which `docs/rt-beta-kernel.md` still lists as an + open TODO (the RT kernel boots; its latency has never been measured). + Selects `BR2_PACKAGE_NUMACTL` transitively — again, do not list numactl, it + must disappear with this block. hwlatdetect is a Python script and installs + because `BR2_PACKAGE_PYTHON3=y`. + +### 5.43 System configuration + +Everything from here on lives in Buildroot's *System configuration* menu. In +the old monolith they were once scattered — `BR2_ROOTFS_MERGED_USR` sat on +line 1, ABOVE the file's own header comment, and `BR2_TARGET_GENERIC_ROOT_PASSWD` +was stranded directly under the "explicitly NOT carried forward" note, where +it read as part of the drop list — and were grouped with no value change. +Merged /usr now lives in `common.fragment` (§2.5). NOTE this menu is NOT the +toolchain menu, so the P1.2 "incremental builds silently ignore toolchain +menu changes" hazard (§5.3) does not apply to anything in this section. + +`BR2_TARGET_GENERIC_ROOT_PASSWD=""` — empty = passwordless root, matching +stock. Per-device SSH host keys are generated on first boot instead (ADR 0015; +`docs/ssh-ftp-parity.md`). §10 says why this line is per image fragment rather +than in `common`. + +### 5.44 locale data (stock parity; fixes update_all.sh) + +`BR2_GENERATE_LOCALE="en_US.UTF-8"`. Distinct from `BR2_ENABLE_LOCALE` (=y, +toolchain menu, §5.3): that one compiles locale *support* into glibc, this +one actually *generates* the locale data. With it empty — the Buildroot +default — the Makefile never registers its `GENERATE_GLIBC_LOCALES` +target-finalize hook and never builds host-localedef, so the image shipped +with no `/usr/lib/locale` at all. Our own rootfs-overlay `/etc/profile` +exports `LC_ALL=en_US.UTF-8`, so *every* login shell printed "setlocale: +LC_ALL: cannot change locale (en_US.UTF-8)", and anything calling +`setlocale(LC_CTYPE, "")` hard-failed — notably `update_all.sh`, which died on +`locale.Error: unsupported locale setting` before doing any work. + +Stock's `/usr/lib/locale` is a single 2.9 MB locale-archive +(`docs/stock-inventory/disk-usage.md`), which is exactly the artifact +`support/misc/gen-glibc-locales.mk` produces. en_US.UTF-8 is what +`/etc/profile` asks for and is already in `BR2_ENABLE_LOCALE_WHITELIST` +("C en_US"), so locale-purge keeps it. This lives in the *System +configuration* menu (`work/buildroot/system/Config.in:575`), NOT the toolchain +menu — it only adds a host package and a finalize hook, so it does not +trigger the from-scratch-rebuild hazard. + +### 5.45 timezone / tzdata (stock parity) + +`BR2_TARGET_TZ_INFO=y`, `BR2_TARGET_TZ_ZONELIST="default"`, +`BR2_TARGET_LOCALTIME="Etc/UTC"`. + +We shipped NO tzdata at all: no `/usr/share/zoneinfo`, no `/etc/localtime`. +So the timezone did not survive a reboot and `TZ=America/New_York` could not +resolve. `package-manifest.md` §5's Drop list floated trimming zoneinfo and +hedged — "international users likely rely on the full zoneinfo set for TZ=" — +and that caveat is exactly what bit. Not dropped on purpose; just never +enabled. + +Stock's `/usr/share/zoneinfo` contains BOTH `posix/` and `right/` subtrees +(verified against the extracted stock rootfs), which is precisely what +Buildroot's tzdata installs with the "default" zonelist — so stock *was* +plain Buildroot tzdata, and this reproduces it 1:1. No zone present in stock +is missing from ours. `BR2_TARGET_LOCALTIME="Etc/UTC"` also reproduces stock's +`/etc/timezone` byte-for-byte ("Etc/UTC"). + +Costs ~4.9 MB of ext4 *blocks* (1,191 mostly-tiny zone files, each rounding +up to a 4 KiB block) — NOT the 1.57 MiB that disk-usage.md's byte column +implies. Either way: not a size concern. `./scripts/check-size-budget.sh +output/images/linux.img` on the last built image reports 512 MiB total, +290 MiB USED, 222 MiB / 43.4% FREE against the 15% threshold. (290 is the +used half, not the free half — an earlier revision of this note had the two +swapped. The measurement also predates T3/T5's ~21 new packages, so real free +space after those land is lower; re-run the script, do not trust a number +cached in prose.) + +NOTE: `BR2_TARGET_LOCALTIME` makes tzdata install `/etc/localtime` as a +symlink to `../usr/share/zoneinfo/Etc/UTC`. That is NOT what stock does and +NOT what makes the setting persist. Stock points `/etc/localtime` at a file +on the FAT data partition, which is the only thing that survives reflashing +the rootfs — our rootfs-overlay ships that symlink and overwrites tzdata's, +because Buildroot rsyncs `BR2_ROOTFS_OVERLAY` *after* package install +(`Makefile:816`). See `board/mister/de10nano/rootfs-overlay/etc/localtime`. + +That symlink is dangling on a FRESH card — `/media/fat/linux/timezone` does +not exist yet, so glibc silently falls back to UTC and stays there. The +overlay's `usr/lib/dhcpcd/dhcpcd-hooks/90-timezone` fills it in once, the +first time the box gets an address, from a geo-IP lookup (ADR 0025). It needs +the full zonelist to have a zone to copy, and the `posix/` subtree +specifically — the same path the community `timezone.sh` copies from. + +--- + +## 6. `de25nano.fragment` + +D2.1: a bare developer OS for the Terasic DE25-Nano (Intel/Altera Agilex 5, +HPS = 2x Cortex-A76 + 2x Cortex-A55, aarch64). + +Plan: `docs/de25-nano-tasks.md`, task D2.1 (Phase D2). Decisions: +`docs/de25-implementation-path.md` §1 (the nine owner decisions); ADR 0027 +(multi-board readiness); ADR 0027 Decision 6 as formalised by D2.7 (the +release scope is a BARE DEVELOPER OS); ADR 0029. + +### 6.1 What this is, and — more importantly — what it is not + +This builds an aarch64 toolchain, a mainline 7.2.3 kernel, a minimal BusyBox +ext4 rootfs that boots to a serial login with ethernet up, the bootloader that +loads them (TF-A BL31 inside a mainline U-Boot FIT, §6.9) and the SD-card image +that carries the lot (§6.11). That is the whole scope. There are NO MiSTer +packages here, NO DE10 packages, and nothing beyond what a developer needs to +get a shell on the board. That is not an oversight or a staging state — it is +the accepted release scope for this board until the upstream MiSTer framework +grows an aarch64 story (`de25-nano-tasks.md` D2.7 / Phase D3). + +THE DE10 IS NOT AFFECTED BY THIS FRAGMENT. The de25nano stack is `common` + +`de25nano` (§1); nothing from `de10nano.fragment`, `de10nano-image.fragment`, +`kernel-only.fragment` or `mister_rt.fragment` is in it, and the DE25 gets its +OWN Buildroot output directory (`output-de25/`, `make de25`) exactly the way +the RT variant and the two initramfs stages get theirs. No `BR2_` symbol is +shared between the boards outside `common.fragment`. Two FILES are shared by +path, each with its own reason and its own guard: the kernel-tarball hash +registry (a symlink, §6.3) and the MiSTer kernel-config fragment +(`board/mister/common/linux-mister.fragment`, proved a no-op on the DE10 by +`scripts/check-kernel-fragment-noop.sh`, §6.5). + +WHY THE OLD FILE WAS NOT THE OUTPUT OF `savedefconfig` — and why the fragment +still is not. The DE10 monolith's header explained that it WAS canonical +savedefconfig output and that its comments got dropped on every regeneration. +The DE25 file was hand-written and hand-maintained for the same reason the +DE10's comments kept getting hand-restored: on a board with no hardware +validation yet, the reasoning is the deliverable. It has been round-tripped +through `savedefconfig` to prove every symbol really exists — but the file +itself is not the machine's output. + +ROUND-TRIP RESULT, re-run 2026-09-02 after the bootloader stanza landed +(Buildroot 2026.05.2). savedefconfig ADDED nothing (so no symbol is +implied-but-unstated) and DROPPED exactly six lines as non-divergent from a +kconfig default: + +- `BR2_LINUX_KERNEL_IMAGE`, `BR2_TARGET_ROOTFS_EXT2_LABEL`, + `BR2_TARGET_GENERIC_ROOT_PASSWD`; +- `BR2_TARGET_ARM_TRUSTED_FIRMWARE_BL31` (selected by + `BR2_TARGET_UBOOT_NEEDS_ATF_BL31`), `BR2_TARGET_UBOOT_NEEDS_ATF_BL31_BIN` + (the default of its choice), `BR2_TARGET_UBOOT_USE_DEFCONFIG` (the default + of its choice). + +All six are kept anyway, for the reason §5.2 gives for the DE10's own +EXT2_LABEL line: a Buildroot default is not a promise. The three bootloader +ones are worth the redundancy for a second reason — "BL31 only, no FIP", "BL31 +as a raw `.bin`, not an ELF" and "an in-tree board defconfig, not a custom +config file" are the three facts a reader most needs from that stanza, and +inferring them from a select and two choice defaults is not reading. +`BR2_LINUX_KERNEL_IMAGE` is the sharpest case — the "Kernel binary format" +choice carries `default BR2_LINUX_KERNEL_ZIMAGE if BR2_arm || BR2_armeb` and +NO default for aarch64 (`linux/Config.in:242-244`), so on this architecture it +resolves to whichever entry upstream happens to list first. That is not +something a boot artifact should depend on. (`scripts/check-config-fragments.sh` +(b) now proves each of those six survives olddefconfig on every run, §11.) + +### 6.2 Architecture & toolchain — `BR2_aarch64`, `BR2_cortex_a76_a55`, `BR2_KERNEL_HEADERS_7_0` + +- aarch64, cortex-a76.cortex-a55 big.LITTLE: the Agilex 5 HPS is a 2xA76 + + 2xA55 cluster. `BR2_cortex_a76_a55` is Buildroot's own name for that tuning + target and resolves to `-mcpu=cortex-a76.cortex-a55` + (`work/buildroot/arch/Config.in.arm:474`, `:945`). It selects + `BR2_ARM_CPU_ARMV8A` + `FP_ARMV8` and needs GCC >= 9; this Buildroot's + internal toolchain is GCC 14.x, so that floor is met with room to spare. + NOTE it is a *tuning* choice, not an ISA restriction: the generated code + runs on either cluster, which is the entire point of the big.LITTLE tuple. + **Do NOT "simplify" it to `BR2_cortex_a76`** — that would tune for the big + core only and schedule badly on the A55s. + + There is no NEON/VFP stanza here, unlike the DE10's. On AArch64 Advanced + SIMD and FP are mandatory parts of the base ISA, so Buildroot has no + `BR2_ARM_ENABLE_NEON` / `BR2_ARM_FPU_*` knobs on this architecture at all. + Their absence is correct, not a dropped line (`scripts/lib/board-expectations.sh` + carries the `BR2_ARM_` family prefix for this board precisely so an empty + set is a legitimate match). +- Internal Buildroot toolchain, glibc, with C++ (`BR2_TOOLCHAIN_BUILDROOT_CXX` + comes from `common`, §2.1). glibc is already the default C library for the + internal toolchain, so it is not a line (savedefconfig drops non-divergent + symbols); musl is a project-wide non-goal. +- `BR2_KERNEL_HEADERS_7_0`: pins the headers SERIES explicitly, for exactly + the reason §3.2 spells out at length — do not "fix" it to + `BR2_KERNEL_HEADERS_AS_KERNEL` to keep headers in lockstep with the kernel. + Under AS_KERNEL the kernel version arrives as the free-form string + `BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, which kconfig cannot compare + numerically, so `BR2_TOOLCHAIN_HEADERS_AT_LEAST` silently falls back to its + 2.6 floor and glibc gets configured `--enable-kernel=2.6`: fifteen years of + dead compatibility code and syscall-fallback paths for kernels this board + will never run. + + WHY 7_0 AND NOT 7_2. Buildroot 2026.05.2 offers NO 7.2 headers series. + `package/linux-headers/Config.in.host` tops out at `BR2_KERNEL_HEADERS_7_0` + (`:55-58`, resolving to 7.0.14 at `:477`); the series list is + 5.10/5.15/6.1/6.6/6.12/6.18/7.0. 7_0 is therefore the newest series + Buildroot has that is <= our 7.2.3 kernel, and headers OLDER than the + running kernel is the supported direction — the kernel's uapi is + forward-compatible by guarantee. §3.2 documents the diff-the-uapi + discipline that comes with this; the same discipline applies here on any + kernel or Buildroot bump, and the range to diff is 7.0.14 -> 7.2.3. + + RE-CHECK ON EVERY BUILDROOT BUMP: a Buildroot bump moves the point release + inside a series on its own, and the day Buildroot adds a 7.2 series this + pin should move to it in a deliberate commit. + +### 6.3 Download integrity — `BR2_GLOBAL_PATCH_DIR` (DE25) and the shared hash file + +`BR2_DOWNLOAD_FORCE_CHECK_HASHES` comes from `common` (§2.2). +`BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/patches"` +is load-bearing here for exactly ONE reason, and it is not patches: it is +where Buildroot finds `/linux/linux.hash`, the only thing that +hash-verifies a pinned custom kernel download. Buildroot's own lookup +resolves the kernel's hash file to `linux/linux.hash`, a path that does not +exist in the release (its real hashes live in `linux/from-6.17/linux.hash`, +which that lookup never consults), so without this the 7.2.3 tarball would +download with a "no hash file" WARNING and never be verified — and +`BR2_DOWNLOAD_FORCE_CHECK_HASHES` cannot save you, because it only forces the +checking of hashes that exist. The full mechanism is written up in the hash +file's own header. + +THE HASH FILE IS SHARED WITH THE DE10, BY SYMLINK, ON PURPOSE. +`board/mister/de25nano/patches/linux/linux.hash` is a relative symlink to +`board/mister/de10nano/patches/linux/linux.hash`. That file is already the +repo's kernel-tarball hash registry: it carries BOTH pins (the DE10's 6.18.y +line and the 7.2.y line the RT variant tracks), its header records the +provenance rule for each, and `scripts/hash-sync-kernel.sh` is its single +automated writer. The DE25 pins 7.2.3 — the same tarball the RT variant +already pins — so a second copy of that sha256 could only ever drift out of +sync with the one the sync script maintains. A symlink cannot drift. + +CONSEQUENCE, say it out loud: bumping the DE25 kernel version means editing +`board/mister/de10nano/patches/linux/linux.hash`, a de10nano path, in the same +commit. That cross-board reach is deliberate and is the reason this +paragraph is this long. (The alternative — pointing `BR2_GLOBAL_PATCH_DIR` +straight at the de10nano patches directory — was rejected: it would also hand +this board the DE10's bluez5_utils patch set, and it would make the board +directory non-self-contained, which is the specific coupling +`docs/de25-readiness-ledger.md` exists to stop spreading.) This is also why +`BR2_GLOBAL_PATCH_DIR` is a board symbol and not a `common` one (§10). + +The `de25nano/patches/linux/` directory contains the hash symlink and nothing +else — no global patches are applied to the kernel from here. Carried kernel +patches live in `BR2_LINUX_KERNEL_PATCH` (§6.4) instead. + +### 6.4 Kernel — mainline 7.2.3 + +`BR2_LINUX_KERNEL`, `BR2_LINUX_KERNEL_CUSTOM_VERSION` come from `common` (§2.3); +`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="7.2.3"` is here. + +WHY 7.2 AND NOT THE DE10'S 6.18 (`docs/de25-implementation-path.md` §5, and +decision 7, which supersedes decision 6's openness): +`drivers/clk/socfpga/clk-agilex5.c` does not exist before v6.19. Mainline 6.18 +ships the `intel,agilex5-clkmgr` binding, the clock-ID header and the DT node +— and no driver at all. On a 6.18 base every consumer of `&clkmgr`, mmc0 and +all three gmacs included, defers forever, so the board cannot boot from SD. +Basing on 6.18 would mean carrying a whole SoC clock driver when a mainline +route exists one release later, which decision 5 (mainline-first, strongly) +forbids without a justification that no mainline route existed. 7.2 is chosen +over 6.19 because 6.19 is EOL and because this repo already builds and +patches the 7.2.y line for the RT variant — so the DE25 is a new instance of +an existing pattern rather than a third kernel line. + +The cost, stated honestly: 7.2 is not LTS, so this board inherits the RT +beta's bump treadmill (`docs/rt-beta-kernel.md`). Re-open the choice if +kernel.org designates a 7.x release longterm. The tarball is already +hash-pinned in the shared `linux.hash` (the RT variant tracks the same 7.2.3), +so this costs no new download and no new TOFU value. The DE25 pin has no +Renovate manager today (`renovate.json`'s 6.18 manager deliberately excludes +this file; the 7.2 manager matches only the rt fragment). + +`BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/linux-patches"` +— carried patches for this board. Wired up independently of the series +itself, so adding or removing a patch is a one-file change and never a config +change — Buildroot applies whatever `*.patch` the directory holds, in +`series`/sort order, and is perfectly happy with an empty one. What is +expected to land there, per `docs/de25-implementation-path.md`: decision 8's +sdhci-cadence 40-bit DMA-mask patch — an UPSTREAMABLE carry, to be submitted, +not a permanent fork, and COUPLED to the board DTS (the driver's bare +`cdns,sd4hc` match entry has no `.data`, so the quirk needs a new match entry +and therefore a new compatible string in mmc0); and the subset of the DE10's +40-patch series that D0.3 triaged as shared/portable and that a compile test +against aarch64/7.2 confirms. Do NOT assume the DE10's `linux-patches/` series +applies here: 4 of the 40 differ in content between the main and beta series +alone, and the beta series' 6.18-vs-7.2 re-anchoring lessons apply again for +aarch64. + +### 6.5 Kernel config, image format, device tree, no initramfs + +KERNEL CONFIG = a pinned minimal base + the shared MiSTer driver fragment: +`# BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG is not set`, +`BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y`, +`BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=".../board/mister/de25nano/linux.config"`, +`BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES=".../board/mister/common/linux-mister.fragment"`. + +THIS SUPERSEDES THE WAVE-1 WIRING, which was the kernel's own arm64 +`defconfig` (`BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG`) plus +`board/mister/de25nano/linux.fragment`. The idea there was that arm64 +`defconfig` is the configuration mainline actually CI-tests, so every symbol +we do not name tracks upstream for free. What it actually produced was 1,481 +modules and a 41.9 MB `Image`: mainline's arm64 `defconfig` is a distro kernel +for every SoC ARM ships, and **no fragment can subtract from it** — a +`# CONFIG_X is not set` line in a merged fragment loses to a `select` from +anything the base left on. The rationale, the measurements and the +per-subsystem justification are in `docs/de25-kernel-config.md`; the short +version is 1,481 → 92 modules, 90 MB → 2.4 MiB of installed modules, 41.9 MB → +20.7 MB `Image`, with the DE10's exact installed-module name set. + +So this board now ships a pinned base the way the DE10 does +(`board/mister/de25nano/linux.config`, this board's minimal arm64 + Agilex 5 +base), and the delta over it is one fragment. + +THE FRAGMENT IS SHARED WITH THE DE10, BY PATH, ON PURPOSE. +`board/mister/common/linux-mister.fragment` is the arch-neutral MiSTer +driver/feature set, and it is proven a no-op against the DE10's resolved +6.18.48 config by `scripts/check-kernel-fragment-noop.sh` — so a symbol added +there for this board cannot silently change the DE10's kernel. The base and +the fragment share no symbol. This is the kernel-config counterpart of the +hash-registry symlink in §6.3: one file, two boards, an automated check that +the sharing stays honest. + +NOTE THE SYMBOL NAME — this is `BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG`, and it is +NOT `BR2_LINUX_KERNEL_USE_DEFCONFIG`. That other option means "an in-tree +defconfig NAMED " and appends `_defconfig` to +`BR2_LINUX_KERNEL_DEFCONFIG` (`linux/linux.mk:360-361`), so asking it for +arm64's plain `defconfig` would build `defconfig_defconfig` — a file that does +not exist. (`BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG`, the wave-1 choice, is +the one that means literally `make ARCH=arm64 defconfig` — +`linux.mk:362-372`, and its own help text names ARM64 as the case it exists +for.) Getting this wrong fails late, in the kernel build, not at configure +time. The wave-1 choice is stated as `is not set` rather than simply dropped, +so the change of shape is visible in the fragment rather than inferable from +an absence — and `scripts/check-config-fragments.sh` (b) proves that not-set +line really resolves that way. + +`BR2_LINUX_KERNEL_IMAGE=y` — uncompressed `Image`, not `Image.gz`. This is the +Agilex/U-Boot FIT convention: the FIT image U-Boot loads carries its own +compression metadata, so a pre-gzipped kernel would be either +double-compressed or mislabelled. Whether we later gzip anything is a +phase-2 decision that belongs with the U-Boot and genimage work (D2.2/D2.4). +The Makefile's `de25` recipe asserts `images/Image` exists. + +DEVICE TREE — `BR2_LINUX_KERNEL_DTS_SUPPORT` (from `common`) + +`BR2_LINUX_KERNEL_CUSTOM_DTS_PATH=".../board/mister/de25nano/socfpga_agilex5_de25nano.dts"`: +the DE25-Nano board file, authored on mainline's `socfpga_agilex5.dtsi` (it +`#include`s the dtsi out of the kernel tree, so a custom path does not mean a +from-scratch device tree). Node set and every per-line justification: +`docs/de25-dts-rationale.md`; the node set itself is +`docs/de25-implementation-path.md` §3.1. Depends on the two carried patches in +`board/mister/de25nano/linux-patches` (0101 sdhci-cadence 40-bit mask + +binding, 0102 intel,agilex5-svc match). Ships with the SMMU disabled for wave +1 — see the rationale §4 for why the SMMU-on shape cannot program the fabric +on mainline. CUSTOM_DTS_PATH and INTREE_DTS_NAME are alternatives, not +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. + +### 6.6 Root filesystem — ext4 on p2, modest and plain + +`BR2_TARGET_ROOTFS_EXT2=y`, `BR2_TARGET_ROOTFS_EXT2_4=y`, +`BR2_TARGET_ROOTFS_EXT2_LABEL="rootfs"`, `BR2_TARGET_ROOTFS_EXT2_SIZE="256M"`. +This is a developer OS, not the DE10's shipped `linux.img`. 256 MiB +comfortably holds BusyBox + the toolchain runtime with room for a developer to +scp things in. There is deliberately none of the DE10's ceremony here — no +pinned UUID/hash-seed, no forced feature list, no hard-link to `linux.img` — +because none of it has a contract to satisfy yet: no Downloader channel, no +stock artifact to be byte-compatible with, and no reproducibility lane (D2.8 +is where a release lane, and with it the question of pinning mke2fs's +randomness, gets designed). Buildroot also emits a rootfs tarball by default +(`BR2_TARGET_ROOTFS_TAR` is `default y`); it is left on, since it is nearly +free and is the convenient form for `tar -x` onto a card partition before +genimage exists (D2.4). `BR2_REPRODUCIBLE` comes from `common` — §2.4 records +what it does and does not guarantee here. + +### 6.7 System configuration + +`BR2_ROOTFS_MERGED_USR` comes from `common` (§2.5). + +Serial console — `BR2_TARGET_GENERIC_GETTY_PORT="ttyS0"`, +`BR2_TARGET_GENERIC_GETTY_BAUDRATE_115200=y`. The DE25-Nano's header UART is +the HPS **uart1** (`serial@10c02100`, `snps,dw-apb-uart`, 16550-compatible) — +not uart0, which is the SoC Development Kit's console. The board DTS enables +only uart1 and aliases it `serial0` with `stdout-path = "serial0:115200n8"` +(`board/mister/de25nano/socfpga_agilex5_de25nano.dts`, "Console UART"; every +reference tree agrees, `docs/de25-dts-rationale.md` U1 [V]). It is the only +enabled 8250 port, so it is ttyS0 whichever numbering rule 8250 applies. +115200 8N1 is what every reference asks for and what the board's USB-UART +bridge uses, so anything else means a garbled login prompt rather than a +missing one. This is the ONE line whose failure mode is "the board looks +dead". The kernel side of it comes from the fragment (8250 + 8250_DW + +8250_CONSOLE, all =y) and the `console=` argument comes from U-Boot's +bootargs, which is phase 2 — so a first boot may need `console=ttyS0,115200` +passed by hand. + +`BR2_TARGET_GENERIC_HOSTNAME="de25"`, +`BR2_TARGET_GENERIC_ISSUE="Welcome to MiSTer DE25-Nano (developer OS)"`. + +`BR2_TARGET_GENERIC_ROOT_PASSWD=""` — empty = passwordless root, the same +posture as the DE10 image. On this board it is additionally the only way in: +there is no network provisioning, no `authorized_keys`, and no per-device SSH +host-key machinery yet (ADR 0015 is a DE10 rootfs-overlay feature and is not +carried here). + +### 6.8 Packages — none, deliberately + +BusyBox is Buildroot's own default (`BR2_PACKAGE_BUSYBOX` is `default y` in +`package/busybox/Config.in`) and so does not appear as a line; it is the +entire userland. Nothing else is enabled: no MiSTer packages, no DE10 +packages, no out-of-tree WiFi or controller drivers, no debug tooling. When +something is eventually needed here, add it in a commit that says which task +authorised it — the empty package list is the D2.1/D2.7 scope decision made +visible, and a package added "just to have it" quietly repeals that decision. +The fragment split did not change this: the DE10 package set lives in +`de10nano-image.fragment`, which is not in the DE25 stack (§10). + +ONE STANDING OBLIGATION IS ALREADY BOOKED AGAINST THAT EMPTY LIST, and the +fragment carries it as a WARNING so it cannot be missed at the moment it +matters. `board/mister/de25nano/linux.config` carries +`# CONFIG_SECCOMP is not set` (a MiSTer posture, matching stock and the DE10 — +`docs/de25-kernel-config.md` §3.1; `SECCOMP` is `default y` on arm64, so wave 1 +had it on). `BR2_PACKAGE_OPENSSH_SANDBOX` is `default y` in Buildroot, and +since openssh 10.4 a failed `prctl(PR_SET_SECCOMP)` is `fatal()` rather than +`debug()`. The combination is an `sshd` that binds and listens while killing +every connection preauth, password and key alike — the DE10 hit exactly this +and fixes it with `# BR2_PACKAGE_OPENSSH_SANDBOX is not set` (§5.19 has the +full write-up). Nothing is broken on the DE25 today, because it ships no +openssh; but the day `BR2_PACKAGE_OPENSSH=y` is added to `de25nano.fragment`, +`# BR2_PACKAGE_OPENSSH_SANDBOX is not set` must be added in the same commit. +It is a configure-time flag, so flipping it later also needs +`make openssh-dirclean` or the stale stamp ships the broken sshd. + +### 6.9 Bootloader — ATF BL31 + mainline U-Boot, packaged as `u-boot.itb` + +D2.4's buildable half. Full write-up, including the per-line rationale for +`board/mister/de25nano/uboot.fragment` and the QSPI-write audit table: +`docs/de25-uboot.md`. Contract: `docs/de25-implementation-path.md` §6.1–§6.3; +`docs/de25-boot-chain.md` §2, §3, §5 and the §7 brick-risk register. + +WHAT THIS BUILDS, AND WHAT IT DOES NOT. It builds exactly two artifacts: +`images/bl31.bin` (ATF) and `images/u-boot.itb` (a binman FIT carrying BL31 + +U-Boot proper + our U-Boot dtb). It does NOT build or ship an SPL. On this +board the FSBL is Terasic's U-Boot SPL, resident in QSPI inside the factory +phase-1 bitstream, and it is NEVER touched — posture 1. The SDM on this board +cannot boot from the microSD at all, so the QSPI seam is permanent and any +write to it is brick-class with a JTAG-only recovery. That is why the U-Boot +fragment's largest block is about making a QSPI write structurally impossible +rather than merely unlikely. + +The SPL is nevertheless COMPILED — see `uboot.fragment`'s "SPL" block for the +one-line Kconfig reason (`select BINMAN if SPL_ATF`, and `BINMAN` has no +prompt). Nothing of it is shipped: `BR2_TARGET_UBOOT_SPL` is deliberately +absent from the fragment, so Buildroot copies no `spl/*` file into `images/`. +That is the answer to `de25-implementation-path.md` §8 Q6, and it is negative. + +**ARM Trusted Firmware.** `BR2_TARGET_ARM_TRUSTED_FIRMWARE=y`, +`_CUSTOM_VERSION=y`, `_CUSTOM_VERSION_VALUE="v2.15.0"`, `_PLATFORM="agilex5"`, +`_BL31=y`, `_IMAGES="bl31.bin"`. + +Mainline TF-A v2.15.0. Buildroot 2026.05.2's newest offer is v2.12 +(`boot/arm-trusted-firmware/Config.in`), which has no Agilex 5 platform, so a +custom version is not a preference here — it is the only route. +`plat/intel/soc/agilex5/` exists at v2.15.0 and its `socfpga_plat_def.h` sets +`BL31_BASE 0x80000000`, which is exactly the load/entry address the SoC64 +binman FIT description hardcodes for the `atf` image. Verified against the +tag; the pairing with U-Boot 2026.07 is still [U] — nobody has booted it. + +The version string is the git TAG (`v2.15.0`, with the leading `v`), because +`ARM_TRUSTED_FIRMWARE_SITE_METHOD` is git: Buildroot clones +`git.trustedfirmware.org/TF-A/trusted-firmware-a.git` and generates the +tarball itself. Hash provenance is in the `.hash` file's header. + +BL31 only. No BL2 and no FIP: BL2's job on this SoC is done by the factory +SPL, and a FIP is the packaging format for a chain we do not own. `_BL31` is +selected anyway by `BR2_TARGET_UBOOT_NEEDS_ATF_BL31` below; it is stated in the +fragment because "which ATF images exist" is a fact the configuration should +assert rather than leave to be inferred. `_IMAGES` defaults to `"*.bin"`, which +would copy whatever the platform's release directory happens to contain; naming +the one file we ship keeps `images/` auditable and makes the Makefile's `de25` +assertion and that line describe the same thing. + +**U-Boot.** `BR2_TARGET_UBOOT=y`, `_BUILD_SYSTEM_KCONFIG=y`, +`_CUSTOM_VERSION=y`, `_CUSTOM_VERSION_VALUE="2026.07"`, `_USE_DEFCONFIG=y`, +`_BOARD_DEFCONFIG="socfpga_agilex5"`, `_CONFIG_FRAGMENT_FILES`, +`_CUSTOM_DTS_PATH`, `_NEEDS_ATF_BL31=y`, `_NEEDS_ATF_BL31_BIN=y`, +`_USE_BINMAN=y`, `_NEEDS_OPENSSL=y`, `_FORMAT_ITB=y`, +`# BR2_TARGET_UBOOT_FORMAT_BIN is not set`. + +Mainline v2026.07 (released 2026-07-07; v2026.10 was at -rc when this was +written). Buildroot 2026.05.2 ships 2026.04, so again a custom version. + +NOTE THE BUILD-SYSTEM LINE, it is not optional. +`BR2_TARGET_UBOOT_BUILD_SYSTEM` defaults to KCONFIG *only* if +`BR2_TARGET_UBOOT_LATEST_VERSION` is set (`boot/uboot/Config.in:11-12`); on a +custom version it falls back to LEGACY, which would try `make _config` +and fail on a tree that has had no such target for a decade. + +Mainline has NO DE25-Nano board — `board/terasic/` has `de0-nano-soc`, +`de1-soc`, `de10-nano`, `de10-standard` and `sockit`, and there is no +`configs/*de25*` anywhere in the tree. `socfpga_agilex5_defconfig` (the SoC +Development Kit) is the base; `uboot.fragment` is the whole delta and every +line of it is commented. + +The board device tree, and the `-u-boot.dtsi` that goes with it: Buildroot +copies BOTH files into `arch/arm/dts/` before the build (`uboot.mk`'s +`UBOOT_CUSTOM_DTS_PATH` is a plain `cp -f `); U-Boot then builds +`$(CONFIG_DEFAULT_DEVICE_TREE).dtb` because `scripts/Makefile.dts` adds it to +`dtb-y`, and auto-includes `-u-boot.dtsi` BY NAME. Both files therefore +have to travel together and be named consistently with the fragment's +`CONFIG_DEFAULT_DEVICE_TREE`. They live in a `uboot-dts/` subdirectory because +the U-Boot board file and the KERNEL board file share a basename by convention +and must not share a directory. + +BL31 goes INSIDE the FIT. `_NEEDS_ATF_BL31` makes uboot depend on +arm-trusted-firmware, copies `images/bl31.bin` into the U-Boot build tree +before the build, and passes `BL31=`; binman's `atf` image picks it up as +a blob-ext named `bl31.bin`. The `_BIN` (rather than `_ELF`) form is what the +SoC64 binman description asks for. + +`u-boot.itb` is produced by BINMAN, not by the legacy `u-boot.itb:` Makefile +rule (that one is gated on `U_BOOT_ITS`, set only under the deprecated +`SPL_FIT_GENERATOR`). `BR2_TARGET_UBOOT_USE_BINMAN` tells Buildroot the same +thing — it drops `u-boot.itb` from `UBOOT_MAKE_TARGET`, adds the three host +python packages binman needs (jsonschema, pyyaml, yamllint) and passes +`BINMAN_INDIRS` so binman can find blobs in `images/`. It also selects +`BR2_TARGET_UBOOT_NEEDS_PYTHON3` / `_PYELFTOOLS` / `_PYLIBFDT`, which is why +those three are not separate lines. + +`_NEEDS_OPENSSL` brings host-openssl in for the U-Boot host tools: +`CONFIG_TOOLS_LIBCRYPTO` is `default y` and `mkimage` links libcrypto. Without +it the build silently depends on whatever openssl headers the developer's +machine happens to have, which is exactly the class of thing this project pins. + +Ship the FIT and nothing else. `BR2_TARGET_UBOOT_FORMAT_BIN` is `default y` in +Buildroot and is turned OFF: `u-boot.bin` is a raw image with no place in this +board's boot chain, and an `images/` directory that contains only what goes on +the card is what makes the card-image step's file list reviewable. + +### 6.10 Host tools — `dumpimage`/`mkimage`, with FIT support + +`BR2_PACKAGE_HOST_UBOOT_TOOLS=y`, `BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y`. + +host-uboot-tools gives us `host/bin/dumpimage` and `host/bin/mkimage`. +`dumpimage` is how the FIT's shape is checked against the factory SPL contract +(`de25-implementation-path.md` §6.1) — image list, load addresses, the default +configuration's firmware/loadables/fdt, and the fact that the only integrity +stamp is a crc32 with no rsa key. That check is not decoration: the factory SPL +is built with `FIT_SIGNATURE` on and no keys, so an unsigned crc32 FIT is what +it accepts and a key-requiring one would strand every board. + +FIT_SUPPORT is the trap. It is **not** `default y`: with it off, +`dumpimage -l u-boot.itb` prints nothing at all and exits 0, which is a +verification step that always passes and never checks anything. Found the hard +way on the first build. (It selects `BR2_PACKAGE_HOST_DTC`.) + +### 6.11 SD-card image (D2.4) + +`BR2_PACKAGE_HOST_GENIMAGE=y`, `BR2_PACKAGE_HOST_MTOOLS=y`, +`BR2_PACKAGE_HOST_DOSFSTOOLS=y`, +`BR2_ROOTFS_POST_IMAGE_SCRIPT=".../board/mister/de25nano/post-image.sh"`. + +Two partitions, fixed by the factory SPL's `CONFIG_SPL_FS_FAT` + boot +partition 1: p1 FAT32 (`u-boot.itb`, `Image`, the dtb, +`extlinux/extlinux.conf`), p2 the ext4 root of §6.6, written directly (an +interim p2 decision — `docs/de25-sdcard.md`). There is explicitly NO shared SD +card with the DE10 (ADR 0029 D3). Layout: +`board/mister/de25nano/genimage-sdcard.cfg`; assembled and verified by +`post-image.sh` + `scripts/check-sdcard-de25.sh`. + +host-genimage does NOT pull in mtools or dosfstools itself +(`package/genimage/genimage.mk`: `HOST_GENIMAGE_DEPENDENCIES = host-pkgconf +host-libconfuse`) and genimage's vfat handler shells out to `mcopy` and +`mkdosfs` BY NAME, so on a runner without them the card build fails inside +genimage rather than at configure time. host-e2fsprogs is already implied by +`BR2_TARGET_ROOTFS_EXT2` (`fs/ext2/ext2.mk`), which is where the checker gets +`dumpe2fs` and `e2fsck`; `sfdisk` comes from the host's util-linux, as for the +DE10's checker. + +`BR2_ROOTFS_POST_IMAGE_SCRIPT` is a per-board symbol for the same reason the +DE10's is (§10): both boards set it, to different scripts. Without +`u-boot.itb` in `images/` this FAILS the build by design; +`DE25_ALLOW_NO_UBOOT=1` in the environment downgrades it to a loud skip. + +--- + +## 7. `mister_rt.fragment` + +The RT / Linux-7.2 "beta" kernel variant — the BUILDROOT-config layer of the +variant, layered on the kernel-only stack (`common` + `de10nano` + +`kernel-only`, §4) at build time by `make rt` (Buildroot's +`support/kconfig/merge_config.sh`, §1). Everything not listed in the fragment +is inherited from that stack unchanged — same armv7-a / Cortex-A9 toolchain, +same 6.18-pinned kernel headers (so the userland ABI is identical), +rootfs-tar only. Three things change: the kernel version, its patch set, and +its kernel-config delta. (The base has NO packages, so the old full-image +variant's "disable the 7.x-incompatible OOT WiFi drivers" lines are gone — +there is nothing to disable; the OOT-WiFi story for RT is in +`docs/rt-beta-kernel.md` §4.) The two overrides (version, patch dir) are the +ONE allowlisted redefinition in `scripts/check-config-fragments.sh` (§11). + +⚠ 7.2-rc4 booted and ran MiSTer on real hardware (2026-07-20), and 7.2-rc7 +again on 2026-08-14; the currently pinned 7.2 FINAL line has NOT been booted. +Every version bump re-opens that question — the variant is beta precisely +because the pin moves faster than hardware testing. That is now a slower +clock than it was: as of 2026-08-17 this pin tracks the 7.2.y line, not +mainline, so the next bump is a stable point release rather than the next +-rc. See `docs/rt-beta-kernel.md` for status, design, and the open TODOs. + +Do not confuse the two fragment layers: `configs/mister_rt.fragment` is +Buildroot config (`BR2_*`); `board/mister/de10nano/linux-rt.fragment` (named +by §7.3) is KERNEL config (`CONFIG_*`) and is where `CONFIG_PREEMPT_RT` actually +lives. The Makefile's `rt` recipe asserts `CONFIG_PREEMPT_RT=y` in the built +kernel's `.config` because merge_config.sh only WARNS when a fragment symbol +is dropped and olddefconfig silently discards symbols whose dependencies fail. + +Adding a future kernel variant `foo` = a sibling `configs/mister_foo.fragment` +like this one + `foo`/`foo-clean`/... Makefile targets + nothing else in CI +(the workflows derive the matrix from the fragment glob; §1). + +### 7.1 The 7.2 kernel — `BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="7.2.3"` + +Just override the version value; the base stack already sets +`BR2_LINUX_KERNEL_CUSTOM_VERSION=y`. + +2026-08-17: this pin CROSSED THE -rc BOUNDARY. 7.2 released on 2026-08-16 and +the value became a plain mainline release, not a snapshot. Three things +changed with it, and all three are why the crossing was a deliberate commit +rather than one more automated -rc bump: + +1. The ARTIFACT. `linux/linux.mk` branches on the literal substring "-rc" + (`linux/linux.mk:35`). While it matched, Buildroot fetched a cgit-generated + snapshot — `linux-.tar.GZ` from https://git.kernel.org/torvalds/t. + Without it, the ordinary release tarball `linux-7.2.tar.XZ` comes from + `$(BR2_KERNEL_MIRROR)/linux/kernel/v7.x` — the series directory is computed + as `v$(firstword $(subst ., ,$(LINUX_VERSION))).x`, so a two-component + "7.2" resolves to v7.x correctly. Verified against the real mirror, not + assumed. +2. The PROVENANCE. kernel.org publishes no signed manifest for an -rc, so the + old hash was TOFU. `linux-7.2.tar.xz` IS covered by the PGP-signed + `sha256sums.asc`; `linux.hash`'s own header records the transcription and + the signature check. Strictly stronger — do not reintroduce a TOFU value + here without moving back to an -rc, which this pin no longer does. +3. The KERNEL RELEASE STRING, hence the module directory: 7.2.0-rc7 -> 7.2.0. + 7.2's own Makefile reads VERSION=7 PATCHLEVEL=2 SUBLEVEL=0 EXTRAVERSION= + (checked in the pristine tarball). Note the asymmetry, which is exactly the + thing that looks like a mistake in a diff and is not: the TARBALL is + two-component (`linux-7.2.tar.xz` — kernel.org publishes no linux-7.2.0), + while the kernel it builds calls itself three-component (7.2.0). Both + spellings are correct and neither is a typo for the other. (Point releases + since — 7.2.3 today — are three-component in both places.) + +On a version bump, `make rt-clean` is MANDATORY before `make rt` — the old +kernel tree survives in `output-rt/build/` otherwise and `rt` refuses to guess +which of two trees to validate (see the Makefile's rt recipe). + +COUPLED to `board/mister/de10nano/patches/linux/linux.hash`: the base stack's +`BR2_DOWNLOAD_FORCE_CHECK_HASHES` (§2.2) empties Buildroot's +`BR_NO_CHECK_HASH_FOR` exemption, so the tarball MUST have a sha256 line there +or the build fails closed at download. Bump the version here -> update that +line in the same commit (its header says where the value must come from). +`renovate.json`'s `kernel-rt-7.2` manager bumps this line and +`scripts/hash-sync-kernel.sh --pin=rt` refreshes the hash (it REFUSES any +`-rc` value, by design). + +### 7.2 Beta patch set — `BR2_LINUX_KERNEL_PATCH=".../board/mister/de10nano/linux-patches-beta"` + +`linux-patches-beta/` is a series-file-driven SUBSET of `linux-patches/`: +symlinks to the shared patch files, EXCEPT 0001, 0015, 0030 and 0037, which +are real re-anchored copies — Buildroot applies patches with `patch -F0` +(fuzz zero), and those four patches' 6.18 context or APIs drifted upstream +(see the series file's header; note 0015 was once wrongly believed upstreamed +in 7.2 — it is not: 7.2 has no FAML/FAMR controller types, and 0037 was once +wrongly written off as cosmetic — it is not, it shifts the DualSense button +indices). 0031 was a fifth until 2026-07-25, when the SHARED patch was +re-anchored onto context both trees agree on and the copy became a symlink +again — see the series header for why that is the preferred move over a +re-anchored copy. The shared 6.18 patches are otherwise deliberately +untouched, keeping them byte-identical to stock. + +The series drops exactly ONE shared patch, and only because 7.2 already has +it: `0047-btusb-mercusys-ma530-2c4e-0115`, a backport of mainline ce21a5cf3d1f +(Mercusys MA530/MA550H, USB 2c4e:0115) whose first release IS 7.2. The 6.18 +image needs it because 6.18.y never received the commit; this kernel does +not, and listing it would not be harmlessly redundant — at -F0 against +pristine v7.2 the hunk FAILS ("Hunk #1 FAILED at 786"), which would break the +build. It goes away on its own the day the stock pin leaves 6.18.y. Nothing +else is dropped: all 40 entries (the other 36 shared + the four beta-local +patches 0043/0044/0045 — the UIO set — and 0046, the ramoops crash-record +reservation) apply to 7.2 FINAL at -F0 — verified 2026-08-17 through +Buildroot's own `apply-patches.sh` against a freshly extracted pristine +`linux-7.2.tar.xz` whose sha256 matched the signed manifest: 40/40 applied, +exit 0, ZERO hunks taking fuzz (80 hunks land at an offset, which -F0 +permits). No re-anchor was needed anywhere: the four re-anchored copies +(0001, 0015, 0030, 0037) carry over unchanged and, with all four beta-local +patches, land at zero offset. + +0038-0042 were the last gap — listed nowhere in the beta series from +2026-07-24 until 2026-08-17, on the ASSUMPTION they would need re-anchoring. +Measured, they needed none: plain symlinks, clean at -F0, and `drivers/hid/` +cross-compiles for ARM with all five in (hid-nintendo.o and hid-playstation.o +both build, zero warnings). The three runs that day nest: 34/34 (70 offsets) +on the rc7 -> 7.2 bump, 35/35 (70) once 0046 landed, 40/40 (80) with +0038-0042 symlinked in. See `docs/rt-beta-kernel.md` §2 and §6. And BUILT, not +just applied: `make rt` is green on all 40 from a clean tree (2026-08-17) — +exit 0, release 7.2.0, `CONFIG_PREEMPT_RT=y`, zImage_dtb 9303550 bytes, 90 +modules. Still NOT BOOTED; that is per-version and open. (This note +previously read "drops only 0030 + 0037 ... all 29 listed", which went stale +when those two were re-anchored and re-included; 0037 in particular is NOT +cosmetic — see the series header. It then read "drops NOTHING, full stop" from +2026-08-17 until 2026-08-24, when 0047 landed in the shared dir. The 40/40 +measurement is unaffected: 0047 was never in the series, so the run that +produced it is still a run of the whole series.) + +### 7.3 RT + 7.x kernel-config delta — `BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES` + +`$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/linux-rt.fragment`, layered +on the shared `linux.config` (§3.4). This is where `CONFIG_PREEMPT_RT` lives, +plus the `CONFIG_UIO*` + cmdline set the doorbells need and the `CONFIG_PSTORE*` +set the ramoops node needs (`docs/rt-beta-kernel.md` §8/§9). This symbol is +NEW in the variant, not an override (the base stack sets no kernel-config +fragment, for the `linux-update-defconfig` reason in §3.4). + +--- + +## 8. `mister_initramfs_defconfig` + +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. + +**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 +the trap A1 exists to name. Two configs, one cpio. + +Why this is a whole second Buildroot config and not a flag on the first one: +the two rootfses have opposite requirements. The target rootfs must be +glibc/shared, because the stock MiSTer binary is linked against it (ADR 0001, +abi-contract §1.3). This one must be static, because it lives inside the +zImage and every byte is a byte of kernel. `BR2_STATIC_LIBS` is not even +offered with glibc ("static only needs a toolchain w/ uclibc or musl" — +Buildroot `Config.in:684`), so the C library choice differs too. None of that +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) + +`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). + +### 8.2 Toolchain: musl, static-only — `BR2_TOOLCHAIN_BUILDROOT_MUSL`, `BR2_KERNEL_HEADERS_6_18`, `BR2_STATIC_LIBS` + +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. + +### 8.3 No init system — `BR2_INIT_NONE` + +The kernel execs `/init` from the cpio directly; there is no `/sbin/init`, no +inittab and no S-scripts in stage 1. + +### 8.4 Device nodes: dynamic/devtmpfs — `BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_DEVTMPFS` + +This is not cosmetic. Buildroot's `fs/cpio/cpio.mk` only mknod's +`/dev/console` (c 5 1) in the NON-static branch, and the kernel needs that +node to exist *before* `/init` runs or `/init` has no stdin/stdout/stderr and +the rescue shell is unreachable. Choosing STATIC device creation here would +also make cpio.mk symlink `/init -> sbin/init` instead of leaving ours alone. +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 +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. + +### 8.6 fsck.exfat for the on-demand repair path (ADR 0026) — `BR2_PACKAGE_EXFATPROGS`, `BR2_ROOTFS_POST_BUILD_SCRIPT` + +`/media/fat` is exFAT, has no journal, and is never cleanly unmounted — not +even by the OSD's own "Reboot", which is a direct write to the HPS reset +controller (`Main:fpga_io.cpp:588-605`) that never reaches init's `::shutdown` +entries. So the volume-dirty flag (`fs/exfat/super.c:512`) is effectively +always set and nothing ever repairs the lost clusters an unlucky power cut +leaves behind. + +The initramfs is the ONLY place a repair can run. The root filesystem is +`linux/linux.img`, a file *on* the partition being checked, so once the +system is up the block device can never be released: fsck.exfat's repair mode +opens `O_RDWR|O_EXCL` and a mount holds an exclusive bdev claim +(`fs/super.c:1617`) whether it is ro or rw. Check-then-boot, or not at all. + +It runs ONLY when `Scripts/check_storage.sh` has left a marker, never on a +plain boot — see the `fsck_if_requested()` section of the initramfs `/init` +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`) +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 +`initramfs-verify` asserts `usr/sbin/fsck.exfat` is present and the five are +absent. + +### 8.7 Output: a cpio, uncompressed — `BR2_TARGET_ROOTFS_CPIO`, `# BR2_TARGET_ROOTFS_TAR is not set` + +Uncompressed is deliberate (`docs/boot-chain.md` I3). The cpio ends up inside +the zImage, which is itself LZ4-compressed (`CONFIG_KERNEL_LZ4`, stock parity), +so compressing it here would just be compressing it twice — and it would +additionally require a matching `CONFIG_RD_*` decompressor in the kernel. +`copy` is the default in the kernel's `usr/Makefile` for a plain .cpio, so +this needs nothing on the kernel side. (`external.mk` nonetheless sets the +kernel's `CONFIG_INITRAMFS_COMPRESSION_GZIP` explicitly — its comment explains +the no-default choice trap.) + +### 8.8 Reproducibility (A9) — `BR2_REPRODUCIBLE` + +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. + +--- + +## 9. `mister_installer_defconfig` + +The THROWAWAY INSTALLER OS (TASKS.md P5.3, +`docs/decisions/0020-sdcard-exfat-reformat-installer.md`, PLAN.md §8/§9). A +standalone Buildroot config, built into `output-installer/` by +`scripts/mk-sdcard.sh` (step 1/7) or the Makefile's `installer` escape hatch. + +This config builds ONE artifact: `output-installer/images/rootfs.cpio`, a +static BusyBox + exfatprogs + util-linux(sfdisk) rootfs that +`scripts/mk-sdcard.sh` embeds into a SECOND, dedicated kernel build to produce +the installer's `zImage_dtb`. That image ships as `linux/zImage_dtb` on the +shipped `sdcard.img`'s FAT32 partition — it is NEVER `output/images/zImage_dtb` +(the real MiSTer kernel) and NEVER runs on a card that has already been +installed (ADR 0020 §2.1: the reformat replaces this kernel with the real one, +which is the primary re-run guard). + +WHY THIS EXISTS AT ALL (read ADR 0020 §1 first): mr-fusion's "auto-resize" is +not a filesystem grow-in-place — MiSTer's data partition is exFAT, and Linux +has no resize-in-place tool for exFAT. So a fresh card ships small (fast +write, small download) and an on-device first-boot installer OS repartitions ++ reformats it to fill whatever medium the user actually has, then hands off +to the real MiSTer. This config is that installer OS's rootfs. Its `/init` +(`board/mister/de10nano/installer-overlay/init`) does the +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 +(arch/toolchain/init/device-creation/output/reproducibility all copied +verbatim — §8 has the reasoning behind each) and adds exactly what the +installer's job needs on top: + +- `BR2_PACKAGE_EXFATPROGS` -> mkfs.exfat (ADR 0020 §2 step 3; `-n MiSTer_Data`). + Depends on `BR2_USE_WCHAR`, which the musl toolchain choice already selects. +- `BR2_PACKAGE_UTIL_LINUX` + `_BINARIES` -> sfdisk (repartition to the real + medium size) and blkid (belt-and-suspenders re-run guard, ADR 0020 §2.1: + skip the reformat if the data partition is already exFAT labelled + MiSTer_Data and already holds `linux/linux.img`). Buildroot's util-linux + "basic set" is not further sub-selectable — enabling it for sfdisk also + pulls in blkid, blockdev, dmesg, findfs, hexdump, mkfs, wipefs etc. as a + bundle. That overlaps some BusyBox applets (dmesg, findfs) already in the + stage-1 BusyBox set; the overlap is harmless (both are real, separate + binaries; the installer's `/init` names whichever it wants) and is NOT a + reason to drop either side. +- A few extra BusyBox applets stage 1 does not need: cp (for the payload -> + tmpfs -> exFAT copies), dd (uboot.img -> the 0xA2 partition), reboot (the + final handoff), blockdev and hexdump (MAC-address generation from + `/dev/urandom`). See `board/mister/de10nano/installer-busybox.config`'s + header for exactly which `CONFIG_` symbols that required and why — + `BR2_PACKAGE_BUSYBOX_CONFIG` names that file. + +What it deliberately does NOT add: e2fsprogs. The installer only ever `cp`'s +`linux/linux.img` as an opaque byte blob (never fscks or resizes its ext4 +contents; that partition's size is fixed at build time, only the exFAT data +partition around it grows) and never builds one, so there is nothing for +e2fsprogs to do here. Re-add it if a later `/init` revision needs to +inspect/repair `linux.img`. + +**NOT `BR2_TARGET_ROOTFS_INITRAMFS`. NOT the main config with a flag.** Same +trap A1 names for stage 1 (§8) applies here verbatim: a fourth Buildroot +output dir (`output-installer/`), a fourth small cpio, never the ~300 MB +target rootfs. + +Per-symbol notes beyond §8's: + +- Arch/ABI (§8.1): the installer runs on the same Cortex-A9 the production + system does, briefly, once, in RAM. +- Toolchain (§8.2): everything in this cpio is deleted from RAM within + seconds of the reformat finishing, so there is no ABI surface to keep + glibc-compatible for. `BR2_TOOLCHAIN_USES_MUSL` auto-selects `BR2_USE_WCHAR`, + which is what `BR2_PACKAGE_EXFATPROGS`'s "depends on BR2_USE_WCHAR" needs — + nothing extra to set for that. +- `BR2_INIT_NONE` (§8.3): unlike stage 1, this `/init` never switch_roots into + anything: it does its reformat dance and calls `reboot` directly (ADR 0020 + §2, last step). PID 1 must still never exit without warning — see + `board/mister/de10nano/installer-overlay/init` for the rescue-shell + contract this config's BusyBox set exists to support + (CTTYHACK/SETSID/ASH_TEST/FEATURE_SH_MATH are all carried over from stage 1 + for exactly that reason). +- Device nodes (§8.4): same load-bearing reasoning — `/dev/console` must exist + before `/init` runs, or there is no stdin/stdout/stderr and a rescue shell + is unreachable. +- `BR2_ROOTFS_OVERLAY` = `board/mister/de10nano/installer-overlay`: `/init` + itself. A SEPARATE overlay from stage 1's — this is a different program + with a different job (reformat-and-handoff, not mount-and-switch_root). + This line is the fixed interface the rest of the sdcard-image plumbing + (`mk-sdcard.sh`, the installer kernel relink) targets. +- Output (§8.7): this cpio is embedded inside the installer's own zImage + (itself LZ4-compressed, `CONFIG_KERNEL_LZ4`, stock parity), so compressing + it here would compress it twice and need a matching `CONFIG_RD_*` + decompressor for no benefit. `copy` is the kernel `usr/Makefile`'s default + for a plain .cpio — nothing extra needed on the kernel-config side of the + relink either. +- Reproducibility (§8.8): the cpio ends up embedded in the installer + `zImage_dtb` that ships inside `sdcard.img(.xz)` release assets, so the same + double-build rationale P4.3 applies to the main build applies here too. + +--- + +## 10. Placement decisions + +The split was decided symbol by symbol by reading the old DE10, kernel-only +and DE25 files side by side. Rules applied, in order: + +1. A symbol set identically in ALL THREE (image, kernel-only, DE25) is + `common`, unless it would move a CI cache key (rule 4). +2. A symbol both DE10 stacks need and the DE25 does not, or needs with a + different value, is `de10nano` (board layer). +3. A symbol only the shipped image needs is `de10nano-image`; only the + kernel-only base, `kernel-only`; only the DE25, `de25nano`. +4. The DE10 build must be provably unchanged, INCLUDING its CI cache keys: the + toolchain-fingerprint residue of each stack (stripped, deny-list-filtered, + sorted — `docs/ci.md#toolchain-fingerprint`) must be byte-identical to the + old single file's. It is (§11). +5. The DE25 is a bare developer OS by decision (ADR 0027 D6, ADR 0029): NO + DE10 package or MiSTer symbol may reach its stack, whatever "arch-neutral" + would otherwise suggest. So "common" is the genuinely shared policy, not + the bulk of the DE10 file — the bulk (the package set) is DE10-only by + construction, in `de10nano-image`. + +The judgement calls, each recorded here: + +| Symbol(s) | Placed in | Why not elsewhere | +|---|---|---| +| `BR2_TOOLCHAIN_BUILDROOT_CXX`, `BR2_DOWNLOAD_FORCE_CHECK_HASHES`, `BR2_LINUX_KERNEL`, `BR2_LINUX_KERNEL_CUSTOM_VERSION`, `BR2_LINUX_KERNEL_DTS_SUPPORT`, `BR2_REPRODUCIBLE`, `BR2_ROOTFS_MERGED_USR` | `common` | Set identically by all three old files (rule 1); each has a per-stack rationale in §2. | +| `BR2_TARGET_GENERIC_ROOT_PASSWD=""` | `de10nano-image` **and** `de25nano` (not `common`) | Identical in all three resolved configs (it is the Kconfig default, so the kernel-only stack gets it anyway), and the owner asked for users/passwd in common — but it is neither `BR2_PACKAGE_` nor `BR2_LINUX_KERNEL`, so adding it to the kernel-only stack's text would change the kernel-variant toolchain-fingerprint and bust every variant's host-toolchain cache exactly once (an exact-key cache, no restore-keys). Kept per image fragment so both fingerprints stay byte-identical (rule 4). It is also meaningless for a rootfs that never boots. Revisit if a third image stack appears. | +| `BR2_TARGET_ROOTFS_EXT2`, `_EXT2_4`, `_EXT2_LABEL="rootfs"` | `de10nano-image` **and** `de25nano` (duplicated across sibling stacks) | Identical on both boards but NOT in the kernel-only stack, which is rootfs-tar only by design (§4.2); putting them in `common` would give the kernel-only base an ext4 image (and make `post-image.sh`'s `linux.img` half fire), or need a `# ... is not set` override in `kernel-only` — the one thing fragments must never do (§1). Duplication across mutually exclusive stacks is not an override; the check permits it. | +| `BR2_GLOBAL_PATCH_DIR` | per board | Same purpose (the kernel hash registry) but a different directory on each board, for the reason §6.3 gives (the DE10 dir also carries bluez5 patches). | +| `BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, `BR2_LINUX_KERNEL_PATCH` | per board (`de10nano` / `de25nano`); overridden by `mister_rt.fragment` | Different lines and different series; the only allowlisted redefinition in the tree is rt's (§7). | +| `BR2_PACKAGE_HOST_KMOD_XZ`, `BR2_ROOTFS_POST_IMAGE_SCRIPT` | `de10nano` (board), not `de10nano-image` | The kernel-only stack needs both (build-time depmod of `.ko.xz`; `zImage_dtb` assembly) — §3.5, §3.6. `BR2_ROOTFS_POST_IMAGE_SCRIPT` is now set by BOTH boards, to different scripts (the DE25's assembles the card, §6.11), so it is a per-board symbol on both sides rather than a `common` one. `BR2_PACKAGE_HOST_KMOD_XZ` is set by **both** board fragments (`de10nano` and `de25nano`), each for its own depmod: the shared kernel fragment (`board/mister/common/linux-mister.fragment`) sets `CONFIG_MODULE_COMPRESS_XZ`, so build-time depmod on either board needs host kmod with xz or it silently ships an empty `modules.dep` (§3.5; the DE25 hit exactly that on its first wave-2 card). It is not in `common` for the rule-4 reason above (`common` is in the kernel-only stack's fingerprint text). | +| `BR2_ROOTFS_POST_BUILD_SCRIPT`, `BR2_ROOTFS_OVERLAY` | `de10nano-image` | Image-only by design (§4.2: `post-build.sh` stamps a rootfs that ships). | +| `BR2_TARGET_ROOTFS_EXT2_SIZE`, `_INODE_SIZE`, `_MKFS_OPTIONS` | `de10nano-image` | The DE25's 256 MiB ext4 has none of the DE10's contracts yet (§6.6). | +| The whole package set, `BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV`, `BR2_GENERATE_LOCALE`, `BR2_TARGET_TZ_*`, `BR2_TARGET_LOCALTIME` | `de10nano-image` | Rule 5. Locale/timezone are arch-neutral and the owner's target listed them as common candidates, but the DE25 old file set none of them and giving a bare developer OS tzdata/locale data is a scope decision D2.x should take explicitly, not a side effect of a refactor. | +| `BR2_INIT_NONE`, `BR2_SYSTEM_BIN_SH_NONE`, `# BR2_PACKAGE_BUSYBOX is not set`, `BR2_TARGET_ROOTFS_TAR` | `kernel-only` | Exactly the old kernel defconfig minus what it shared with the image (§4). | +| 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. | +| `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): `common` 7; +`de10nano` 16; `kernel-only` 3 + 1 not-set; `de10nano-image` 257 + 10 not-set; +`de25nano` 45 + 2 not-set. de10nano stack total 280 + 10 not-set — the old +file had 281 assignment lines, of which one was the duplicate `BR2_PACKAGE_STRACE=y`, so +the SET of symbols is identical; de10nano-kernel stack 26 + 1, exactly the +old kernel defconfig's; de25nano stack 52 + 2 not-set, exactly the old DE25 +defconfig's (26 + 0 at the split; the bootloader, host-tool and card stanzas of +§6.9–§6.11 and the kernel-config switch of §6.5 arrived with wave 2 and were +ported symbol-for-symbol, the resulting stack symbol set diffed line-for-line +against the last version of the deleted file). + +--- + +## 11. Checks, golden hashes, and the identity proof + +**`scripts/check-kernel-defconfig-sync.sh`** (text level, no Buildroot; runs +in every kernel leg before any cache restore and in `build.yml`'s `lint-config` +job): §4 lists its four asserts over the merged text of the `de10nano` and +`de10nano-kernel` stacks. `BOARD=` selects the expectation row in +`scripts/lib/board-expectations.sh`; the compared pair is fixed. + +**`scripts/check-config-fragments.sh`** (needs the pinned Buildroot tree, +which it unpacks via `make buildroot-unpack` if absent; config-only, no +compile; ~4 s warm; runs in `lint-config`): for each stack in `stacks.mk` plus +`de10nano-kernel` + every `configs/mister_*.fragment`: + +- (a) no symbol is defined by two fragments of one stack — checked from the + fragment text AND from merge_config.sh's own "redefined" output; + `ALLOWED_OVERRIDES` (rt: `BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, + `BR2_LINUX_KERNEL_PATCH`) is the only exception; +- (b) every effective fragment line survives olddefconfig verbatim — a + `# X is not set` line must come back as exactly that line, so a misspelt + not-set symbol (absent from the resolved config in either form) is caught + too — the silently-dropped-symbol class (unmet dependency, renamed option + after a Buildroot bump, typo); +- (c) the resolved `de10nano` and `de10nano-kernel` configs agree on every + symbol outside `LOCKSTEP_DIVERGENCE_PREFIXES` (packages, init/shell, + rootfs images, overlay/post-build, device creation, system configuration, + `BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_*` — the hidapi select of §5.26 — + `BR2_GDB_VERSION`, `BR2_DEFCONFIG`); +- (d) the sha256 of the NORMALISED resolved `.config` equals + `configs/fragments/golden.sha256` for the pinned `BUILDROOT_VERSION`; +- (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` / + `DE10NANO_KERNEL` stacks' files. A fragment rename that updates `stacks.mk` + would otherwise pass (a)-(d) while the dl-cache keys, Renovate's + `managerFilePatterns`, the workflow path filter and the scripts that read + a pin by filename (`hash-sync-kernel.sh`, `ci-tests.sh`, + `test-initramfs.sh`, `test-sdcard-install.sh`, `export-kernel-tree.sh`, + `lint-kernel-patches.sh`) all went silently stale. + +**Host independence of (d).** `olddefconfig` is run with Buildroot's host +inputs pinned on the make command line (`HOSTARCH=x86_64`, +`HOSTCC_VERSION=14` — Buildroot derives `BR2_HOSTARCH`, +`BR2_HOST_GCC_AT_LEAST_*` and every host-gated package from them, and both are +`:=` variables a command-line assignment overrides), so the resolved config +the check reasons about is a pure function of (fragments, Buildroot version). +Normalisation then keeps only the SET symbols (`BR2_X=…`; `# … is not set` +lines are dropped — lossless for drift, since a symbol flipping on or off +shows as a set line appearing or vanishing, and the not-set list is exactly +where host-gated symbols come and go between machines) and drops, belt and +braces, the set symbols that are host- or checkout-derived even so: +`BR2_HOSTARCH`, `BR2_HOST_GCC_VERSION`, `BR2_HOST_GCC_AT_LEAST_*`; +`BR2_PACKAGE_*_ARCH_SUPPORTS`, `BR2_PACKAGE_HOST_GO_BIN_HOST_ARCH`, +`BR2_PACKAGE_PROVIDES_HOST_RUSTC` (HOSTARCH-derived); the +`depends on BR2_HOST_GCC_AT_LEAST_*` consumers that are `=y` here — +`BR2_PACKAGE_GOBJECT_INTROSPECTION`, `BR2_PACKAGE_HOST_GOBJECT_INTROSPECTION`, +`BR2_PACKAGE_HOST_QEMU*`, `BR2_PACKAGE_LIBGLIB2_BOOTSTRAP`, +`BR2_PACKAGE_PYTHON_GOBJECT` (measured: the only set lines that move between +`HOSTCC_VERSION` 4.9/5/9/14, at the gcc >= 8 floor; the fragment ones are +still proved by (b)); `BR2_VERSION`, `BR2_EXTERNAL_MISTER_*` (git-describe, +absolute path); `BR2_DEFCONFIG`; and the two kernel-version symbols Renovate +moves weekly, `BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE` and kconfig's derived +`BR2_LINUX_KERNEL_VERSION` (landing proved by (b)). Measured 2026-09-02: the +de10nano hash is identical for the real host (gcc 15), `HOSTARCH=aarch64`, +and `HOSTCC_VERSION` 9, 5 and 4.9, and unchanged by bumping the 6.18, rt and +DE25 kernel pins. So the hash moves ONLY when the resolved configuration +really changes. + +**Buildroot bumps.** A bump changes Kconfig defaults and is EXPECTED to move +every hash. Two outcomes, deliberately different: a pinned version with **no +golden line at all** is a `::warning` — the check prints the new lines ready +to paste and the build proceeds, so the automated bump PR still proves it +builds — and `.github/workflows/renovate-hash-sync.yml` case 8 +(`scripts/hash-sync-golden.sh`) records the new lines in that PR by running +this tree's `--update-golden` against the freshly unpacked, hash-verified +tarball. A mismatch against a **recorded** line is drift and fails. By hand: +`scripts/check-config-fragments.sh --update-golden --keep`, read +`output-config-check//normalised.config` against the previous good +run, commit with that reading in the message (`docs/renovate.md` case 8). + +**The identity proof at the split (2026-09-02, Buildroot 2026.05.2).** Before +the monoliths were deleted, each old file was loaded with `make +mister__defconfig` (the rt variant through the old `defconfig + merge + +olddefconfig` recipe) and `savedefconfig`'d; after, each stack was generated +through the new Makefile path. For all four stacks the resolved `.config` +differed only in `BR2_DEFCONFIG` (old: the deleted file's path; new: +`$(CONFIG_DIR)/defconfig`) and `BR2_EXTERNAL_MISTER_VERSION` (git-describe of +the dirty worktree); `savedefconfig` output was byte-identical; the four +golden hashes recorded in `golden.sha256` are byte-equal to the normalised +hashes of the OLD path's configs; and the toolchain-fingerprint residue of +both DE10 stacks is byte-identical to the old files', so no CI cache key +moved. The same identity was checked for `mister_initramfs_defconfig` and +`mister_installer_defconfig` after their comments moved here (only comments +changed; the resolved configs are byte-identical, `BR2_DEFCONFIG` included, +since the files kept their names). + +SINCE THE SPLIT, one golden line has moved on purpose: `de25nano`, 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`. The +`de10nano`, `de10nano-kernel` and `rt` lines are unchanged from the split, and +must stay so — the DE25 shares no stack with them, so a DE25 change that moves +any of the other three is a bug in the change, not in the hash. diff --git a/docs/ci.md b/docs/ci.md index 16b1e45..a80adb0 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -164,7 +164,8 @@ keys/paths, which make targets run) reads the env this step computes once — same compute-once/consume-twice reasoning as the cache keys ([`#cache-keys`](#cache-keys)). -A kernel variant builds `configs/mister_kernel_defconfig` + its fragment: +A kernel variant builds the `de10nano-kernel` fragment stack (`common` + +`de10nano` + `kernel-only`, `configs/fragments/stacks.mk`) + its fragment: `zImage_dtb` plus a depmod'd module tree in `output-/`, sharing `dl/` and ccache with main but its own host-toolchain cache (a cross-toolchain bakes its absolute `O=` path in, so `output/host` and `output-/host` can @@ -175,14 +176,16 @@ the initramfs stage DOES run: every kernel embeds the stage-1 cpio (`external.mk`'s fixup applies to any `BR2_LINUX_KERNEL=y` build; a zImage without it cannot boot). -`configs/mister_kernel_defconfig` is a manually mirrored **copy** of the main -defconfig's toolchain/kernel stanzas. `scripts/check-kernel-defconfig-sync.sh` -asserts the copy has not drifted, and it runs in **two** places: inside -`buildroot-build`'s fingerprint step (before any cache work — a drifted -toolchain stanza would build wrong under a cache key that then pins the wrong -toolchain in) and again as its own step in `build.yml`'s `build` job, which -catches the main-only edit path (main defconfig changed, mirror forgotten) in -seconds rather than waiting for a kernel leg to fail. +The kernel-only stack shares its toolchain/kernel fragments with the image +stack **by construction** (before the 2026-09 fragment split it was a manually +mirrored copy, `configs/mister_kernel_defconfig`). +`scripts/check-kernel-defconfig-sync.sh` asserts that structure still holds — +every toolchain/kernel-family symbol lives in a fragment both stacks use — and +it runs in **two** places: inside `buildroot-build`'s fingerprint step (before +any cache work — a drifted toolchain stanza would build wrong under a cache key +that then pins the wrong toolchain in) and again in `build.yml`'s +`lint-config` job ([`#lint-config`](#lint-config)), which fails the whole run +in seconds rather than waiting for a kernel leg to fail. What still needs a hand-edit for a new variant (nothing above does): the release-notes prose in `release.yml`'s `publish` job (a human-readable @@ -456,20 +459,25 @@ Excluded, with reasons: compiled against), which is **not** excluded and correctly still busts the cache. -Comments are stripped **before** filtering: these defconfigs are heavily +Comments are stripped **before** filtering: the old defconfigs were heavily annotated, with both trailing comments (`BR2_ROOTFS_MERGED_USR=y # why`) and -indented continuation lines, and most of those annotations hang off the -`BR2_PACKAGE_` lines being excluded. Keeping them would mean editing a +indented continuation lines, and most of those annotations hung off the +`BR2_PACKAGE_` lines being excluded (the fragments now carry one-line pointers +into `docs/buildroot-config.md`, but the rule stands). Keeping them would mean editing a comment about a WiFi driver evicts the cross-toolchain and costs a 3h20m rebuild. Only a `#` that begins a line or follows whitespace is treated as a comment marker (values like the ext2 `MKFS_OPTIONS` contain `^` and `,` but never a bare `#`), so this cannot corrupt a symbol's value. -Which defconfig gets fingerprinted is the variant's: main hashes the image -defconfig; a kernel variant hashes the kernel-only base -(`configs/mister_kernel_defconfig` — whose filtered residue is a non-empty -toolchain stanza, so the sentinel assert below holds for it too) and then -appends its fragment's residue. Kernel variants run the SAME strip/filter +Which configuration gets fingerprinted is the variant's: main hashes the +`de10nano` fragment stack; a kernel variant hashes the `de10nano-kernel` stack +(whose filtered residue is a non-empty toolchain stanza, so the sentinel +assert below holds for it too) and then appends its fragment's residue. The +stack's files come from `configs/fragments/stacks.mk` via +`scripts/lib/config-stacks.sh`; they are concatenated before the +strip/filter/sort, and the resulting residue is byte-identical to what the +old single-file defconfigs produced — the 2026-09 fragment split moved +neither cache key. Kernel variants run the SAME strip/filter over the variant's fragment and append. TODAY this appends nothing for `rt` — the fragment only carries `BR2_LINUX_KERNEL_*` lines, all deny-listed above — and that is the deny-list doing its job, not a bug: a kernel-version bump in @@ -1063,14 +1071,42 @@ enough for a defect to sit there for months undetected. ### Kernel-defconfig lockstep check, run twice -Same fail-fast reasoning as the patch lint: the kernel-only base defconfig is -a manually mirrored copy of the main defconfig's toolchain/kernel stanzas -(see [`#variants`](#variants)), and `scripts/check-kernel-defconfig-sync.sh` -asserts the copy has not drifted. The composite action (`buildroot-build`) -runs it for every kernel leg too; running it here, in `build.yml`'s own -`build` job, as well, catches the main-only edit path (main defconfig -changed, mirror forgotten) in seconds, without waiting for a kernel leg to -fail. +Same fail-fast reasoning as the patch lint: the kernel-only base stack shares +its toolchain/kernel fragments with the image stack by construction (see +[`#variants`](#variants)), and `scripts/check-kernel-defconfig-sync.sh` +asserts that structure holds. The composite action (`buildroot-build`) runs it +for every kernel leg too; running it in `build.yml`'s `lint-config` job as +well catches an edit that puts a toolchain/kernel symbol in a one-stack +fragment in seconds, without waiting for a kernel leg to fail. + + +### The `lint-config` job: fragment stacks regenerated before anything builds + +`build.yml` runs `lint-kernel-patches.sh`, `check-kernel-defconfig-sync.sh` +and `scripts/check-config-fragments.sh` in a job of their own (`lint-config`) +that `build-kernel` and `build` both `needs`, so a bad fragment fails the run +in about a minute instead of after an hour of kernel legs. The fragment check +is config-only by design: it unpacks the pinned Buildroot tarball (10 MB, +hash-verified by the wrapper Makefile's `buildroot-unpack`) and runs +kconfig — no toolchain, no packages, no compile. It regenerates every stack +(`de10nano`, `de10nano-kernel`, `de25nano`, and the kernel-only stack + each +`configs/mister_.fragment`) through Buildroot's own `merge_config.sh +-m` + `olddefconfig` and asserts: no symbol defined by two fragments of a +stack (the rt fragment's version/patch-dir overrides are the one allowlist), +every fragment symbol survives `olddefconfig`, the resolved image and +kernel-only configs agree outside a named list of designed divergences, and +the sha256 of each NORMALISED resolved `.config` equals +`configs/fragments/golden.sha256` for the pinned Buildroot version, plus a +guard that every fragment path hard-coded outside `stacks.mk` (the two +`hashFiles()` lists here, Renovate's `managerFilePatterns`, the scripts that +read a pin by filename) still exists and matches the stacks. A Buildroot bump +is expected to move the hashes: a version with **no** golden lines yet is a +`::warning`, not a failure (so the automated bump PR still builds), and +`renovate-hash-sync.yml` case 8 records the new lines in that PR; a mismatch +against a *recorded* line is drift and fails. The hashes are host-independent +by construction (olddefconfig runs with `HOSTARCH`/`HOSTCC_VERSION` pinned, +and the host-derived symbols are excluded from the normalisation) and do not +move on a kernel bump. Full description: `docs/buildroot-config.md` §11. ### Kernel module overlay: download then populate @@ -1798,7 +1834,7 @@ CI cache correctness). What follows here is only the mechanics of how A9 requires: pinned ext4 feature set, fixed UUID/hash-seed, `SOURCE_DATE_EPOCH`, `BR2_REPRODUCIBLE=y` (all landed in P2.5 — -`configs/mister_de10nano_defconfig`) combine to make `linux.img` and +the `de10nano` fragment stack, `docs/buildroot-config.md` §5.2) combine to make `linux.img` and `zImage_dtb` byte-identical across two independent builds of the same commit. P2.5 proved that ONCE, locally, for two image-generation passes over the SAME already-built `output/` tree — it deliberately did not prove it @@ -2219,10 +2255,11 @@ to the same PR) are harmless no-ops once the hash is already correct. [`#renovate-hash-sync-dispatch-trap`](#renovate-hash-sync-dispatch-trap) for why this escape hatch has to exist at all and the trap in how to use it. -`configs/mister_kernel_defconfig` copies the main defconfig's kernel stanza, -so a 6.18 bump touches BOTH files (one Renovate PR, same depName) — both are -listed in `paths:` so a bump is still picked up if a future change ever moves -the kernel version into the copy alone. +The 6.18 kernel pin lives in `configs/fragments/de10nano.fragment` alone since +the 2026-09 fragment split (the kernel-only stack shares that file, so there +is no mirrored copy any more) — that file is what `paths:` lists. Before the +split the main defconfig and its copy `configs/mister_kernel_defconfig` were +both listed, since one Renovate PR touched both. `configs/mister_rt.fragment` **is listed too, as of 2026-08-17** — and it was deliberately absent before that, so a reader coming from an older commit @@ -2736,7 +2773,7 @@ skipped job to notice. `scripts/hash-sync-github-packages.sh` does **not** auto-discover `package/*/*.mk` — see its "Required env" header. **A note on azcopy and this job's disk/cache budget.** `BR2_PACKAGE_AZCOPY` is **not -enabled** in `configs/mister_de10nano_defconfig` (size, see `docs/azcopy.md` §1), so +enabled** in `configs/fragments/de10nano-image.fragment` (size, see `docs/azcopy.md` §1), so `host-go` is not built and none of the below is in the default pipeline today. It matters the moment anyone flips that line — though **not for the reason you would guess**. Wall clock is cheap: the five-stage host-go bootstrap measured **3.0 min**, diff --git a/docs/db-json-versioning.md b/docs/db-json-versioning.md index 9a29dc0..3848ee1 100644 --- a/docs/db-json-versioning.md +++ b/docs/db-json-versioning.md @@ -27,8 +27,7 @@ if current_linux_version == linux['version'][-6:]: ``` `/MiSTer.version` is written at build time by `board/mister/de10nano/post-build.sh`, -which derives its 6-byte `YYMMDD` stamp from `SOURCE_DATE_EPOCH`. `configs/ -mister_de10nano_defconfig` pins `SOURCE_DATE_EPOCH` to **Buildroot's own last-commit +which derives its 6-byte `YYMMDD` stamp from `SOURCE_DATE_EPOCH`. `configs/fragments/de10nano-image.fragment` pins `SOURCE_DATE_EPOCH` to **Buildroot's own last-commit date** — fixed as long as `BUILDROOT_VERSION` doesn't change (that pin is exactly what makes two independent builds of the same commit byte-identical, P2.5/A9's reproducibility requirement). @@ -87,7 +86,7 @@ exact-byte-match hazard" (a mismatched `/MiSTer.version` makes the inequality pe true) — here the mismatch is a **deliberate, chosen trade-off** rather than a whitespace bug, made because the alternative (never offering a new release to an already-updated device) was judged worse. **This should not be considered a closed question.** The -durable fix belongs in `post-build.sh`/`configs/mister_de10nano_defconfig` (P2.6), not +durable fix belongs in `post-build.sh`/`configs/fragments/de10nano-image.fragment` (P2.6), not here: have `post-build.sh` accept a build-time override (e.g. an env var the release workflow sets to that release's own real date) for `/MiSTer.version` specifically, while leaving `SOURCE_DATE_EPOCH` itself pinned for every *other* reproducibility guarantee diff --git a/docs/de25-dts-rationale.md b/docs/de25-dts-rationale.md index dae7abf..5e99f66 100644 --- a/docs/de25-dts-rationale.md +++ b/docs/de25-dts-rationale.md @@ -447,6 +447,13 @@ One consequence worth carrying: **all DRAM lives in `0x8000_0000..0xBFFF_FFFF`, ## 4. The SMMU / `mmc0` decision (implementation path §8 Q2 and §8 Q1) +> **Status: owner decision, ADR 0029 D10 (2026-09-02).** SMMU-off is the intended +> configuration, not a wave-1 expedient: DMA isolation is a non-goal for this image (the DE10's +> Cyclone V has no SMMU, so this is parity, not a regression), and SMMU-off is the precondition +> for mainline `svc` to program the fabric at all. Re-open only if upstream `svc` grows +> IOMMU-aware buffer handling, or the SMMU-off fabric test fails on hardware. + + **Decision: `&smmu { status = "disabled"; }` for wave 1** — MAINLINE 7.2's own default, and a deliberate divergence from all three references, which set it `okay`. **`mmc0` keeps both `iommus = <&smmu 5>` and `dma-coherent`**, and so does every other master in the tree; with the diff --git a/docs/de25-implementation-path.md b/docs/de25-implementation-path.md index add5bc5..713b5c9 100644 --- a/docs/de25-implementation-path.md +++ b/docs/de25-implementation-path.md @@ -330,6 +330,14 @@ Ang Tien Sung / Khairul Anuar Romli, Altera; via Dinh Nguyen), **561 lines**, ma ### 3.1 The nodes, as authored (on a 7.x base) +> **Superseded on two points (2026-09-02).** (1) `&smmu { status = "okay"; }` below is **not** +> what ships: the SMMU is **disabled** by owner decision — ADR 0029 **D10** — because mainline +> `stratix10-svc` hands the SDM physical addresses and cannot program the fabric through a +> translated stream; see [`de25-dts-rationale.md`](de25-dts-rationale.md) §4. (2) The svc +> compatible is **not** overridden; mainline's `intel,agilex5-svc` is kept and carried patch +> `0102` makes it bind (ADR 0029 D8). The listing is kept as the record of the node set. + + ```dts /* mainline 7.x ships /firmware/svc with a compatible that binds no driver. Override it; keep iommus (a top-level property in the binding, legal for any diff --git a/docs/de25-kernel-config.md b/docs/de25-kernel-config.md new file mode 100644 index 0000000..9f28083 --- /dev/null +++ b/docs/de25-kernel-config.md @@ -0,0 +1,622 @@ +# DE25-Nano kernel configuration — the diet, and the shared MiSTer driver set + +**Status:** implemented 2026-09-02 on `feature/de25-wave2`. Supersedes the +`board/mister/de25nano/linux.fragment` era (wave 1, commit `6dd5604`), whose +load-bearing comments were folded into this document when that file was deleted. + +**Files this document describes** + +| File | Layer | What it is | +|---|---|---| +| [`board/mister/de25nano/linux.config`](../board/mister/de25nano/linux.config) | kernel `CONFIG_*` | the DE25's **base**: arm64 + Agilex 5 + boot path + FPGA stack + the "not a distro kernel" exclusions. A **minimal defconfig**, DE10 style. | +| [`board/mister/common/linux-mister.fragment`](../board/mister/common/linux-mister.fragment) | kernel `CONFIG_*` | the **arch-neutral MiSTer personality**, shared: input/HID, Bluetooth, Wi-Fi, USB, sound, filesystems, netfilter, LEDs, RTC. | +| [`configs/fragments/de25nano.fragment`](../configs/fragments/de25nano.fragment) | Buildroot `BR2_*` | names both of the above (§7); rationale in [`docs/buildroot-config.md`](buildroot-config.md) §6.5. | +| [`scripts/check-kernel-fragment-noop.sh`](../scripts/check-kernel-fragment-noop.sh) | check | proves the fragment changes nothing on the DE10 (§6). | + +--- + +## 1. The problem this closes + +Wave 1 built the DE25 kernel as **arm64's in-tree `defconfig` plus a 38-symbol +delta fragment**. That was the right call for a bring-up board — arm64 +`defconfig` is the configuration mainline actually CI-tests, so every symbol not +named tracked upstream for free — and it produced a green build. + +It also produced a **generic distro kernel**, and — much more seriously — a +kernel that was **not a MiSTer kernel at all**. Nearly all of its 1,481 modules +were hardware the DE25 does not have and never will: ~50 unrelated arm64 SoC +families, PCI, ACPI, DRM/GPU, KVM, enterprise NICs, media capture. Meanwhile, +read out of that build's own resolved config +(`output-de25/build/linux-7.2.2/.config`): + +``` +# CONFIG_HIDRAW is not set # CONFIG_INPUT_JOYDEV is not set +# CONFIG_UHID is not set # CONFIG_INPUT_UINPUT is not set +# CONFIG_HID_NINTENDO is not set # CONFIG_JOYSTICK_XPAD (absent -> n) +# CONFIG_HID_PLAYSTATION is not set # CONFIG_HID_GUNCON2/3, HID_FTEC, +# CONFIG_LEDS_USER is not set # HID_GAMECUBE_ADAPTER: not set +# CONFIG_NLS_KOI8_R is not set # CONFIG_MODULE_COMPRESS_XZ (absent -> n) +# CONFIG_AFFS_FS is not set # CONFIG_ATARI_PARTITION (absent -> n) +# CONFIG_USBIP_CORE is not set # CONFIG_HZ_1000 is not set (250 Hz) +CONFIG_SECCOMP=y +``` + +That kernel could not have driven a single MiSTer controller: no `hidraw`, no +`joydev`, no `uinput`, no `hid-nintendo`, no `hid-playstation`, no `xpad`. Five +of the six patch-gated pad drivers were compiled into the tree by the carried +patches and then left switched off (only `HID_VADER4` happened to default on). +`CONFIG_SECCOMP=y` was not itself a fault — it is simply a divergence from the +DE10, and the fragment now brings the DE25 to the DE10's posture, which carries +a defconfig obligation described in §3.1. **None of this is visible in a green +build**, which is exactly why it survived wave 1. + +The owner's framing: *"most module settings etc. should be the same for driver +support between the two kernels."* This is that, implemented and measured. + +| | wave 1 (arm64 `defconfig` + delta fragment) | **wave 2 (this change)** | DE10-Nano, for reference | +|---|---|---|---| +| modules (`=m` symbols) | 1,481 | **92** | 92 | +| installed `.ko.xz` | 1,640 files, **90 MB** | **91 files, 2.44 MiB** | 91 files, 2.23 MiB | +| kernel image | `Image` 41.9 MB | **`Image` 20.7 MB** | `zImage` 9.0 MB (lz4) | + +The module **name sets are now identical**: `comm` over the two boards' +installed `/lib/modules` trees gives 91 in common and **zero** on either side +alone. + +## 2. The split, and the one rule that defines it + +``` + board/mister/de25nano/linux.config board/mister/de10nano/linux.config + ┌──────────────────────────────┐ ┌──────────────────────────────┐ + │ arm64, Agilex 5 IP, boot │ │ arm, Cyclone V IP, boot path,│ + │ path, FPGA/SDM stack, │ │ MiSTer_fb + MiSTer audio, │ + │ distro-cruft exclusions │ │ Cyclone V FPGA mgr/bridges, │ + │ (51 setting lines) │ │ AND all of the shared set │ + └──────────────┬───────────────┘ │ inline (475 setting lines) │ + │ merge_config -m └──────────────┬───────────────┘ + ▼ ┆ does NOT consume + ┌────────────────────────────────────────────────┐ ┆ the fragment yet + │ board/mister/common/linux-mister.fragment │ ┄┄┄┄┄┄┄┄┘ (§6 is what + │ input/HID · Bluetooth · Wi-Fi · USB · sound · │ makes adopting + │ filesystems · netfilter · LEDs · RTC · block · │ it safe) + │ modules · diagnostics (409 setting lines) │ + └────────────────────────────────────────────────┘ +``` + +404 of the fragment's 409 lines appear **verbatim** in the DE10's own file. The +other five are menu-gate sentinels — `USB`, `USB_HID`, `HID`, `INPUT`, `WLAN` — +which the DE10 gets from a Kconfig `default` or, in the USB case, from a +`select`; each was checked against the DE10's **resolved** config instead. +Base and fragment share **no** symbol: one home per symbol, 460 lines total, +zero duplication, so the Buildroot merge emits no warnings by construction. + + +**The rule.** A symbol belongs in the shared fragment if and only if: + +1. it is **not** arch- or SoC-specific — anything naming an on-chip IP block + (`8250_DW`, `I2C_DESIGNWARE_*`, `GPIO_DWAPB`, `DW_WATCHDOG`, + `MMC_SDHCI_CADENCE`, `PL330`, the FPGA manager, the SoC clk/reset drivers) + stays in the board's own file even when both boards happen to have the same + IP; **and** +2. the **DE10's resolved `.config` has exactly that value**; **and** +3. the symbol **exists in both kernel trees** (DE10 is on 6.18.y, DE25 on 7.2.y). + +Rule 2 is what makes the fragment a provable no-op on the DE10 (§6). Rule 3 is +what stops a version-skewed line from being a silent no-op on one board — see §9. + +### Why "resolved `.config`", not "the DE10's defconfig" + +Both, as it happens. All **475** setting lines in +`board/mister/de10nano/linux.config` were first verified to resolve to *exactly* +their stated value in `output/build/linux-6.18.48/.config` — they do, with zero +divergence — so any subset of that file is a no-op by construction. Symbols +added to the fragment that the DE10 defconfig does **not** name (§3.10's `USB`, +`HID`, `USB_HID`, `INPUT`, `WLAN` sentinels) were checked against the resolved +config directly. + +--- + +## 3. The shared fragment, subsystem by subsystem + +409 setting lines. Section numbers below match the section banners in the file. + +### 3.1 Core kernel personality (§1 of the file) +`SYSVIPC`, `HIGH_RES_TIMERS`, the `TASK*`/`TASKSTATS`/`PROFILING`/`RELAY` +accounting set, `IKCONFIG` + `IKCONFIG_PROC`, `LOG_BUF_SHIFT=14`, `CGROUPS` + +`CPUSETS` + `NAMESPACES`, `BLK_DEV_INITRD`, `SYSFS_SYSCALL`, `EXPERT`. + +* `IKCONFIG_PROC` gives `/proc/config.gz`, the only way to answer "what is + actually in the kernel on this board" from the board itself — this project + leans on it repeatedly. +* `HZ_1000` is a **MiSTer latency posture, not a default**: the generic + `kernel/Kconfig.hz` choice defaults to `HZ_250`, so this is a real change on + any base. +* `# CONFIG_SUSPEND is not set` — `SUSPEND` is `default y` wherever + `ARCH_SUSPEND_POSSIBLE`, so the explicit off is required, not decorative. +* **`# CONFIG_SECCOMP is not set` is load-bearing, and it reaches into the + Buildroot configuration.** It is `default y` on arm64 (wave 1 had `SECCOMP=y`) and + matches stock on the DE10. The coupling: `BR2_PACKAGE_OPENSSH_SANDBOX` is + `default y` in Buildroot, and since openssh 10.4 a failed + `prctl(PR_SET_SECCOMP)` is `fatal()` rather than `debug()` — so an image with + SECCOMP off and the sandbox on gets an `sshd` that **binds and listens while + killing every connection preauth**, password and key alike. The DE10 fixes + that with `# BR2_PACKAGE_OPENSSH_SANDBOX is not set` in its own image + fragment (commit `9824cd6`; the line is now + `configs/fragments/de10nano-image.fragment` and the rationale + `docs/buildroot-config.md` §5.19). + + **Action for `configs/fragments/de25nano.fragment`:** it ships no `openssh` + today (the DE25 is a bare BusyBox developer OS, ADR 0027), so nothing is + broken now — but the day `BR2_PACKAGE_OPENSSH=y` is added there, + `# BR2_PACKAGE_OPENSSH_SANDBOX is not set` must be added with it. The + fragment carries a WARNING at its package section saying exactly that + (`docs/buildroot-config.md` §6.8). It is a configure-time flag, so changing it later + also needs `make openssh-dirclean` or the stale stamp ships the broken sshd. + +### 3.2 Modules and the `.ko.xz` layout (§2) +`MODULES`, `MODULE_UNLOAD`, `MODULE_COMPRESS`, `MODULE_COMPRESS_XZ`. +The on-disk module layout is an **ABI contract** +([`abi-contract.md`](abi-contract.md)), not a size tweak; +[`kernel-config-deltas.md`](kernel-config-deltas.md) §3.1 records +`MODULE_COMPRESS_XZ` as one of three symbols `olddefconfig` silently dropped +once already. + +### 3.3 Block layer, partitions, loop (§3) +`PARTITION_ADVANCED` + `ATARI_PARTITION` (MiSTer mounts Atari/Amiga-era disk +images), `BINFMT_MISC`, `BLK_DEV_LOOP` (how `.img`/`.vhd` media are mounted), +`BLK_DEV_RAM` at the DE10's 2 × 8 MiB geometry, `# CONFIG_IOSCHED_BFQ is not set`. + +### 3.4 `/dev` and hotplug (§4) +`DEVTMPFS` + `DEVTMPFS_MOUNT` + `UEVENT_HELPER`/`UEVENT_HELPER_PATH`. + +**These live in the fragment, not in the board base, and that is deliberate.** +They are arch-neutral and both boards need exactly this value, and the project's +standing rule is *one home per symbol* — duplicating a line into both files +creates two places to change it and one place to forget. The consequence is +stated at the top of the board file: **`linux.config` alone does not boot.** The +two files are a pair and `configs/fragments/de25nano.fragment` always names both. + +### 3.5 Networking core and netfilter (§5) +The DE10's exact set: `NET`/`PACKET`/`UNIX`/`INET`, `NET_KEY`(+`_MIGRATE`), +`IP_MULTICAST`, `IP_PNP{,_DHCP,_BOOTP,_RARP}`, `# CONFIG_IPV6 is not set`, +`NETWORK_PHY_TIMESTAMPING`, `VLAN_8021Q`(+`_GVRP`), and the legacy-iptables +netfilter block (conntrack + FTP/IRC/SIP helpers + the `xt_*` matches/targets + +`IP_NF_FILTER` + `IP_NF_TARGET_REJECT`). +`IP_NF_FILTER`/`IP_NF_TARGET_REJECT` are called out in the file because +[`kernel-config-deltas.md`](kernel-config-deltas.md) §3.3 records `olddefconfig` +silently dropping them and taking the whole legacy filter table with them. + +### 3.6 Bluetooth (§6) +`BT`, `BT_RFCOMM`(+`_TTY`), **`BT_HIDP`** — the symbol that turns a paired +DS4/DualSense/Pro Controller into an input device — `BT_HCIBTUSB=m` with +`BT_HCIBTUSB_MTK=y`, `BT_HCIBCM203X=y`, `BT_ATH3K=m`. +`linux-patches/0036` (CSR clone LMP subver) rides on btusb and is carried by +both boards. See [`bluetooth-parity.md`](bluetooth-parity.md). + +### 3.7 Wireless stack and the dongle set (§7, §9) +`CFG80211=m` / `MAC80211=m` (a Wi-Fi dongle is optional hardware; the stack is +~1 MiB resident when loaded), `CFG80211_WEXT` for the older tooling MiSTer +scripts use, and then **every driver the DE10 ships, chip for chip**: ath9k_htc / +carl9170 / ath6kl, brcmfmac, libertas + mwifiex, the whole mt76 USB family +(7601U, 76x0U, 76x2U, 7663U, 7921U, 7925U), rt2x00, rtlwifi + rtl8xxxu, and the +complete mainline **rtw88** (8822BU, 8821CU, 8822CU, 8814AU, 8723DU, 8821AU, +8812AU) and **rtw89** (8851BU, 8852BU) USB sets. ADR 0016 "mainline-first"; the +per-chip coverage table is [`wifi-parity.md`](wifi-parity.md) §6. + +Two structural facts make this set safe to share: + +* **Every `*_SDIO` bus driver is explicitly off.** `BRCMFMAC_SDIO` and + `RSI_SDIO` are `default m` whenever `CONFIG_MMC=y` — which is true on both + boards, for the SD card — so without the explicit off, `olddefconfig` builds + SDIO Wi-Fi drivers for a slot neither board has. The DE10's own file learned + this the hard way ("Verified: without this line it resolved to `=m`"). +* **Every `*_PCIE`/`*E` sibling is unreachable** because neither board sets + `CONFIG_PCI` — that exclusion lives in each board's base file. + +`EEPROM_93CX6` is named explicitly: it is a `select`ed dependency of rt2x00 and +rtl8187 that is otherwise invisible, so its disappearance would be silent. + +### 3.8 Network devices (§8) +`NETDEVICES`, `MACVLAN=y` and `TUN=y` (container/Docker networking, and the only +way to give an emulated NIC its own MAC over wireless — `=y` not `=m` because +the module directory does not match `uname -r`, so autoload is unreliable), +`MARVELL_PHY` + `MICREL_PHY`, the PPP set, `# CONFIG_USB_NET_DRIVERS is not set`, +and the 32 `# CONFIG_NET_VENDOR_* is not set` gates. Those gates are +`bool ... default y`: leaving them absent turns dozens of drivers back on. + +The **PHY drivers are shared, the MAC is not**: which PHY part is fitted is a +DTS/MDIO-ID question and both boards carry both drivers, while `STMMAC_ETH` + +`STMMAC_PLATFORM` + the SoC glue (`DWMAC_SOCFPGA`) are board-file symbols. + +### 3.9 Input (§10) +`INPUT`, `INPUT_MOUSEDEV`, **`INPUT_JOYDEV`**, **`INPUT_EVDEV`**, +**`INPUT_UINPUT`** — the three device classes `MiSTer_Main` and every +controller-mapping tool open, plus the node the pairing/remap helpers write +through. `MOUSE_PS2` and `KEYBOARD_ATKBD` are off (no PS/2 controller on either +board; Keyrah adapters arrive over USB HID). The classic serial/USB joystick set +(`iforce`, `warrior`, `magellan`, `spaceorb`, `spaceball`, `stinger`, `twidjoy`, +`zhenhua`) and `JOYSTICK_XPAD=m` with FF + LEDs. +`linux-patches/0026` (mousedev `EVIOCGRAB`) and `0025` (usbhid jspoll) ride here. + +### 3.10 HID (§11) — every `hid-*` the DE10 enables +All 60-odd `HID_*` drivers verbatim, plus `HIDRAW`, `UHID`, +`HID_BATTERY_STRENGTH`, `HID_PID`, `USB_HIDDEV`, and the force-feedback +sub-options. + +**Sentinels.** `HID` and `USB_HID` are `default y` but are the single point of +failure for the entire controller story, so they are named — an upstream +demotion then shows up as a merge warning rather than as a board with no pads. + +**Six patch-gated symbols**: `HID_GUNCON2` (0010), `HID_GUNCON3` (0011), +`HID_FTEC` (0012), `HID_VADER4` (0013), `HID_GAMECUBE_ADAPTER` + +`_FF` (0014). These do not exist in a stock tree. Both boards carry 0010–0014 +(the DE25's entries are symlinks into the DE10's series, +`board/mister/de25nano/linux-patches/README.md`). **If either board ever drops +one of those patches, the corresponding line here becomes a silent no-op on that +board** — `olddefconfig` discards unknown symbols without a word. + +The *behavioural* HID patches both boards carry (0016–0019, 0022–0024, +0032–0035, 0037–0042) add no Kconfig symbols and need no lines of their own — +they change what an already-enabled driver does. + +### 3.11 USB host (§12) +`USB` itself, `USB_ACM`, `USB_STORAGE`, `USB_UAS`, the `USBIP` trio (the debug +rig forwards a controller from a host PC — [`debug-tooling.md`](debug-tooling.md)), +the USB-serial adapters (CH341/CP210x/FTDI/PL2303 + generic + simple), +`USB_ANNOUNCE_NEW_DEVICES`, `USB_DYNAMIC_MINORS`, and the PHY shims +`USB_ULPI_BUS` / `USB_ULPI` / **`NOP_USB_XCEIV`**. + +**`CONFIG_USB` has no `default` in Kconfig at all.** On the DE10 it is currently +switched on only as a side effect of `select USB` inside `MOUSE_APPLETOUCH` / +`MOUSE_BCM5974` / `MOUSE_SYNAPTICS_USB` — far too fragile to leave implicit for +the bus every MiSTer peripheral arrives on. `NOP_USB_XCEIV` is required by +*both* boards' DTs (`usb-nop-xceiv`). + +The **host controller driver** is board-specific and is not here: `USB_DWC2` on +both boards today, but that is an SoC fact, not a shared one. + +### 3.12 SCSI / USB mass storage (§13) +`SCSI` + `BLK_DEV_SD` + `BLK_DEV_SR` as the transport USB storage rides on, with +`# CONFIG_SCSI_LOWLEVEL is not set` keeping every actual HBA driver out. + +### 3.13 Sound (§14) +`SOUND`, `SND`, `SND_OSSEMUL`, `SND_HRTIMER`, `SND_SEQUENCER`(+`_OSS`), +`SND_DUMMY`, **`SND_USB_AUDIO`**. USB DACs and headsets are the arch-neutral +half of MiSTer's audio story; the DE10's own codec path (`SND_MISTER_AUDIO`, +from `linux-patches/0002`) is patch-gated and board-specific — see §5. + +### 3.14 I2C, GPIO, LEDs, RTC, regulators, watchdog core, hwrng (§15) +Cores and userland ABIs only: `I2C` + `I2C_CHARDEV` + `I2C_SMBUS` + `I2C_GPIO` +(with `# CONFIG_I2C_HELPER_AUTO is not set`), `GPIOLIB` + `GPIO_SYSFS`, +`WATCHDOG`, `REGULATOR` + `REGULATOR_FIXED_VOLTAGE`, `HW_RANDOM`, the LED class +set (`LEDS_CLASS_MULTICOLOR` is what hid-playstation and hid-nintendo register +player/lightbar LEDs through — patches 0032/0033/0041/0042; +`LEDS_BRIGHTNESS_HW_CHANGED` is what 0029 teaches leds-gpio to report; +`LEDS_USER` is `/dev/uleds`), and the three I2C RTC parts MiSTer add-on boards +fit ([`rtc-parity.md`](rtc-parity.md)). + +### 3.15 Filesystems (§16) +`EXT4_FS`, `VFAT_FS` + `FAT_DEFAULT_UTF8`, **`EXFAT_FS`** (ADR 0010 dropped the +out-of-tree driver; `linux-patches/0031` adds the Samsung-symlink behaviour on +both boards), `NTFS3_FS=m`, `FUSE_FS` + `CUSE`, `FSCACHE`, `ISO9660`/`JOLIET`/ +`ZISOFS`/`UDF` for CD images, `AFFS_FS` for Amiga media, `TMPFS`, `CONFIGFS_FS`, +`# CONFIG_DNOTIFY is not set`, the NFS client (ADR 0022) and CIFS/SMB +([`netfs-parity.md`](netfs-parity.md), [`samba-parity.md`](samba-parity.md)), +and the full NLS codepage set. + +`NLS_UTF8` is **not optional** with exfat: its default `iocharset` is `utf8`, and +a missing codepage fails the **mount at runtime**, not the build. + +`ext4`/`vfat`/`exfat` are boot-path filesystems and are `=y` — see §3.4 for why +they live in the fragment rather than the board base. + +### 3.16 Keys and crypto (§17) +`ENCRYPTED_KEYS`, `INIT_STACK_NONE`, and the generic-C algorithms CIFS / NFS / +PPP-MPPE need (`NULL`, `DES`, `CTS`, `XTS`, `SEQIV`, `ECHAINIV`, `MD4`, `MD5`, +`SHA1`, `CRC32C`). **No arch accelerators**: those are per-arch symbol names +(`CRYPTO_AES_ARM` vs `CRYPTO_AES_ARM64_*`) and belong in a board file if wanted. + +### 3.17 Diagnostics (§18) +`PRINTK_TIME`, `DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT`, `MAGIC_SYSRQ`, `DEBUG_FS`, +`FUNCTION_TRACER`, and the P3.13 crash/hang triage set: `PANIC_ON_OOPS` + +`SOFTLOCKUP_DETECTOR` + `WQ_WATCHDOG` + `DETECT_HUNG_TASK`. On a board with a +serial console and no display, a kernel that limps after an oops is strictly +worse than one that panics loudly. + +--- + +## 4. The DE25 base — what is on, and why + +Section numbers match the banners in `board/mister/de25nano/linux.config`. +51 setting lines. + +| § | Group | Notes | +|---|---|---| +| 1 | identity | `LOCALVERSION_AUTO` off (the version string must not depend on a git tree being present — [`reproducibility.md`](reproducibility.md)); `DEFAULT_HOSTNAME="de25"`. | +| 2 | platform / topology | `ARCH_INTEL_SOCFPGA`; `NR_CPUS=4` (2×A76 + 2×A55 — arm64 defaults to **512**); `HOTPLUG_CPU`. | +| 3 | **exclusions** | see §5 below. | +| 4 | SoC clocks | `CLK_INTEL_SOCFPGA` + `CLK_INTEL_SOCFPGA64`. **There is no `CONFIG_CLK_AGILEX5`** — `clk-agilex5.o` is built by the SOCFPGA64 symbol (`drivers/clk/socfpga/Makefile:5-7`), and the driver only exists from v6.19, which is why this board is pinned to 7.2 and not 6.18 ([`de25-implementation-path.md`](de25-implementation-path.md) §5.1). | +| 5 | reset | `RESET_SIMPLE`. The board DTS gives `mmc0` `resets = <&rst SDMMC_RESET>` and the rstmgr is `altr,stratix10-rst-mgr`, matched by `drivers/reset/reset-simple.c:137` — **not** `reset-socfpga.c`, which is `default ARM && ARCH_INTEL_SOCFPGA`, i.e. the DE10's 32-bit path. With no reset provider `mmc0` probe-defers forever with nothing in dmesg naming the reason. | +| 6 | sysmgr, SRAM | `MFD_ALTERA_SYSMGR` — dwmac-socfpga reads the PHY interface mode through `altr,sysmgr-syscon`, so gmac0 does not come up without it. `SRAM` for `ocram@0` (`mmio-sram`). | +| 7 | console | the 8250/8250_DW group on **uart1** (uart0 is the SoCDK's). `=y`, never `=m`: a console that is a module does not exist at panic time. | +| 8 | SD boot | `MMC` → `MMC_SDHCI` → `MMC_SDHCI_PLTFM` → `MMC_SDHCI_CADENCE`, all `=y` because there is **no initramfs** on this board, so a driver that is a module cannot be loaded before the root filesystem it is needed to reach exists. | +| 9 | IOMMU | `ARM_SMMU_V3=y` kept even though the board DTS ships `&smmu { status = "disabled"; }` — so the SMMU-on leg of the §2.6 fabric test is a one-line DTS change with **no kernel rebuild** ([`de25-dts-rationale.md`](de25-dts-rationale.md) §4). | +| 10 | Ethernet MAC | `STMMAC_ETH` + `STMMAC_PLATFORM` + `DWMAC_SOCFPGA`, all `=y`. `DWMAC_SOCFPGA` is `default ARCH_INTEL_SOCFPGA` but tristate, so it would follow `STMMAC_ETH` to `=m` without these lines. | +| 11 | FPGA stack | `FPGA` + `FPGA_BRIDGE` + `FPGA_REGION` + `OF_FPGA_REGION` + `FPGA_MGR_STRATIX10_SOC` + `INTEL_STRATIX10_SERVICE` + `FW_LOADER` + **`OF_OVERLAY`**; `INTEL_STRATIX10_RSU` off. See §4.1 below. | +| 12 | low-speed IP | `GPIO_DWAPB`, `I2C_DESIGNWARE_CORE`/`_PLATFORM`, `DW_WATCHDOG`, and the SPI group (`SPI`, `SPI_DESIGNWARE`, `SPI_DW_MMIO`, `SPI_SPIDEV`, `SPI_MEM` off). | +| 13 | USB controller | `USB_DWC2` + `USB_DWC2_HOST`. Verified against the dtsi, not assumed: `usb0@10b00000` is `compatible = "snps,dwc2"` with a `usb-nop-xceiv` phy (`socfpga_agilex5.dtsi:161-163,483-492`) — **dwc2, not dwc3, not xhci**. | + +### 4.1 The two traps in the FPGA group, restated + +**`OF_OVERLAY` is the single most important line in the base file.** +`OF_FPGA_REGION` is `depends on OF && FPGA_REGION` with **no `select +OF_OVERLAY`**, and with `OF_OVERLAY=n`, `of_overlay_notifier_register()` is a +static-inline stub returning 0. The region driver therefore *registers +successfully at boot, prints nothing wrong, and its notifier can never fire*. A +kernel missing that line is silently non-functional for core loading: no error, +no warning, no reconfiguration ([`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) +§4.1, tagged **[V]** there). + +**`INTEL_STRATIX10_RSU` is off as a posture choice, not an oversight.** It drives +SDM commands that rewrite the QSPI boot firmware. `de25-boot-chain.md`'s +posture-1 contract is that the factory QSPI image is never written by anything we +ship — the QSPI seam is permanent on this board and an interrupted write is a +brick with no recovery path. Not shipping the driver is strictly stronger than +relying on there being no `intel,stratix10-rsu` DT node. + +`FPGA_BRIDGE=y` is required even though **no bridge driver is used** — the +Cyclone V `fpga_bridge0..3` shape has no Agilex analogue and must not be +transliterated — purely because `FPGA_REGION depends on FPGA_BRIDGE`. +`FPGA_MGR_SOCFPGA` and `SOCFPGA_FPGA_BRIDGE` are **Cyclone V / Arria 10 only** +and must never appear in this file. + +--- + +## 5. What is deliberately OFF, and why + +### 5.1 In the DE25 base — the five exclusions that do the diet + +The other ~50 arm64 SoC families (`ARCH_ROCKCHIP`, `ARCH_QCOM`, `ARCH_MEDIATEK`, +…) need **no lines at all**: they are `default n`, and it was arm64's in-tree +`defconfig` — not Kconfig — that switched them on. Writing a minimal defconfig +removes them by construction. The five below are different: each is `default y` +or reachable by default, so each needs saying. + +| Off | Why | +|---|---| +| `EFI` | the DE25 boots via the factory SPL → `u-boot.itb` FIT contract; there is no UEFI in that chain. **This also forecloses ACPI**: on arm64 `ARCH_SUPPORTS_ACPI` is `select`ed only by `EFI` (`arch/arm64/Kconfig:2473`), so with `EFI=n` the entire ACPI menu is structurally unreachable and needs no line of its own. | +| `PCI` | no host bridge is wired and none is in the DTS. Load-bearing for the shared Wi-Fi set: it is what makes every `RTW88_*E` / `BRCMFMAC_PCIE` / ath10k-PCIe sibling unreachable. | +| `VIRTUALIZATION` | `default y` on arm64; nothing here runs guests. | +| `COMPAT` | 32-bit EL0. Buildroot builds one ABI and it is aarch64. | +| `DRM` + `FB` + `MEDIA_SUPPORT` | no display path exists on the DE25 in wave 1, and DRM alone is ~40 MB of modules. | + +### 5.2 DE10 symbols deliberately **not** put in the shared fragment + +Every one of these is a judgement call, and each is listed here so the next +reader can overturn it with evidence rather than rediscover it. + +| DE10 symbol(s) | Why not shared | +|---|---| +| `FB`, `FB_MISTER`, `FRAMEBUFFER_CONSOLE{,_DETECT_PRIMARY}` | `FB_MISTER` comes from `linux-patches/0001`, which the DE25 **does not carry** — it targets the Cyclone V fabric-memory aperture. Naming it would make a line that is a driver on one board and a silent no-op on the other. Until an Agilex 5 framebuffer path exists there is nothing for fbdev to drive. | +| `SND_MISTER_AUDIO` | same shape: `linux-patches/0002`, not carried on the DE25 (its exclusion is an open owner decision, tasks item 5). | +| `CMA`, `CMA_AREAS=7` | not driver support; on the DE10 it is stock-parity carry-over and nothing on the DE25 (no fbdev, no DRM, no V4L) allocates from it. `CMA_SIZE_MBYTES` would reserve DRAM for no consumer. **Revisit the moment a DE25 framebuffer lands.** | +| `CPU_FREQ` + its six governors, `CPU_IDLE`, `CPU_IDLE_GOV_MENU` | the DE10's cpufreq exists to host the Cyclone V overclock driver (`linux-patches/0003`); mainline 7.2 has **no cpufreq driver for Agilex 5** and the board DTS has no OPP table or idle-states, so the whole subsystem would be a userland-visible interface with nothing behind it. | +| `COREDUMP` | the DE10's own file marks this a **temporary** debug divergence from stock, to be reverted as one block ([`debug-tooling.md`](debug-tooling.md)). Carrying a temporary divergence into a shared file entrenches it. (It is `default y` in `fs/Kconfig.binfmt` anyway, so the DE25 gets it regardless.) | +| `FRAME_WARN=1024` | word-size dependent — 1024 is the 32-bit default and would emit `-Wframe-larger-than` noise on arm64, where the default is 2048. | +| `KERNEL_LZ4` | arm64 does not select `HAVE_KERNEL_LZ4`; there is no self-decompressing arm64 kernel. `Image` vs `Image.gz` is a separate open decision (tasks item 3). | +| `LOCALVERSION_AUTO`, `DEFAULT_HOSTNAME` | identity, per board. | +| `SMP`, `NR_CPUS`, `HOTPLUG_CPU` | topology, per board. | +| `SRAM` | binds an `mmio-sram` DT node; that is SoC description, so it sits in each board's own file. | +| `MMC` | boot path **and** SoC: the DE10's host is `MMC_DW`, the DE25's is `MMC_SDHCI_CADENCE`. Splitting the core away from the host would put half a boot path in each file. | +| `STMMAC_ETH`, `OF_OVERLAY`, `FPGA*`, `DMADEVICES`/`PL330_DMA`, `MFD_ALTERA_SYSMGR`, all `SERIAL_8250*`, `SPI*`, `I2C_DESIGNWARE_*`, `GPIO_DWAPB`, `DW_WATCHDOG` | on-chip IP or SoC glue; rule 1. (`OF_OVERLAY` is `=y` on both boards, so moving it to the fragment later would still be a no-op — it lives in the base because the trap that makes it load-bearing is an FPGA-stack fact.) | +| `ARM_THUMBEE`, `UACCESS_WITH_MEMCPY`, `ARM_MODULE_PLTS`, `VFP`, `NEON`, `ARM_CPUIDLE`, `ARM_SOCFPGA_CPUFREQ`, `CRYPTO_AES_ARM`, `UNWINDER_FRAME_POINTER`, `DEBUG_USER`, `SND_ARM` | ARM32-only symbols. `SND_ARM` is the subtle one: it still exists in 7.2 but is `depends on ARM`, so on arm64 the line is silently discarded. | +| `NET_VENDOR_CIRRUS`, `NET_VENDOR_FARADAY` | same trap — both are `depends on ARM`. Caught by the survival check in §8, not by inspection. | + +### 5.3 Not enabled on the DE25 even though the SoC has the hardware + +* **`DW_AXI_DMAC`** — `dmac0`/`dmac1` are `altr,agilex5-axi-dma`,`snps,axi-dma-1.01a` + (`socfpga_agilex5.dtsi:334,353`). The only DT consumers of their `dmas` + properties are `spi0`/`spi1`, both `status = "disabled"` in the base DTS, so + the driver would build and bind with nothing to serve. Wave-1's build did not + have it either. Enable it the day a fabric or SPI DMA consumer is enabled. +* **`I3C`** — `altr,agilex5-dw-i3c-master` nodes exist but the board DTS does not + enable them. +* **`PINCTRL`** — Agilex 5 pinmux is done by the factory SPL; mainline has no + Agilex 5 pinctrl driver and the DTS has no pin nodes. + +--- + +## 6. The DE10 no-op proof + +`scripts/check-kernel-fragment-noop.sh` is the claim of §2 rule 2, executed. + +It deliberately runs kconfig **twice**, because resolving a config outside +Buildroot's environment cannot reproduce toolchain-derived string symbols +exactly (`CONFIG_CC_VERSION_TEXT` comes back empty — the compiler wrapper is +invoked through kconfig's `$(shell,…)`): + +``` +CONTROL : tree/.config -> olddefconfig -> control/.config +TEST : tree/.config + fragment (merge) -> olddefconfig -> test/.config +``` + +Same kconfig binary, same `srctree`, same `ARCH`, same compiler — so every +environment-derived difference cancels and **control vs test is exact**. The +check fails on any `is redefined by fragment` line and on any diff. + +Result, run against the live DE10 tree on 2026-09-02 (verbatim in the wave-2 +report): `merge_config.sh` printed **zero** redefinition lines, and +`diff control/.config test/.config` was **empty**. Against the tree's own +`.config` the only difference is `CONFIG_CC_VERSION_TEXT`, which the control run +reproduces identically — hence the two-run design. + +### 6.1 The merge_config prose trap + +`merge_config.sh` resolves a symbol's "new value" with +`grep -w CONFIG_ `, which matches **prose as well as settings**. +A comment in the fragment that names a symbol the fragment also sets therefore +produces a **false** `Value of CONFIG_ is redefined by fragment` warning on +every single build. This happened once during authoring (a sentinel comment for +the USB symbol) and is now rule 5 in the fragment's header: **name symbols in +comments without the `CONFIG_` prefix.** The check script enforces it. + +--- + +## 7. Buildroot wiring + +``` +# BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG is not set +BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y +BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/linux.config" +BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/common/linux-mister.fragment" +``` + +Note the symbol name: `BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG`, **not** +`BR2_LINUX_KERNEL_USE_DEFCONFIG` (that one names an in-tree defconfig and takes +a bare name in `BR2_LINUX_KERNEL_DEFCONFIG`, `linux/linux.mk:360-361`). + +### 7.1 Firmware — enabled drivers with no blobs + +`configs/fragments/de25nano.fragment` currently selects **no** +`BR2_PACKAGE_LINUX_FIRMWARE_*` at all, while the shared fragment now builds the +whole DE10 Wi-Fi/Bluetooth driver set. Those drivers will bind and then fail at +`request_firmware()`. That is not a regression — wave 1 had exactly the same +drivers as `=m` from arm64 `defconfig`, also without firmware — but it is now a +*deliberate* set, so the decision should be explicit. The DE10's 29 firmware +selections (`configs/fragments/de10nano-image.fragment`, its "/lib/firmware +population" block; `docs/buildroot-config.md` §5.28; **52 MB** installed at +`/lib/firmware`) are the menu to copy from; see +[`firmware-parity.md`](firmware-parity.md) and [`wifi-parity.md`](wifi-parity.md). +This is an **owner decision**, not a config fact: the initial DE25 scope is a +bare developer OS (ADR 0027), and the firmware set is tens of MB of rootfs. + +--- + +## 8. Verification — what was actually measured + +Method (reproducible; the survival check is the important half): + +```sh +# 1. Resolve the pair in a pristine, patched 7.2.2 tree. +tar xf dl/linux/linux-7.2.2.tar.xz +cd linux-7.2.2 +for p in board/mister/de25nano/linux-patches/*.patch; do patch -p1 -F0 -i "$p"; done +cp board/mister/de25nano/linux.config .config +ARCH=arm64 scripts/kconfig/merge_config.sh -m -O . .config \ + board/mister/common/linux-mister.fragment +make ARCH=arm64 olddefconfig + +# 2. THE SURVIVAL CHECK. merge_config only WARNS when a symbol is dropped and +# olddefconfig discards unknown or unsatisfiable symbols in SILENCE, so a +# typo in either file is a no-op, never an error. Every asked-for line must +# appear verbatim in the resolved .config. +cat board/mister/de25nano/linux.config board/mister/common/linux-mister.fragment | + grep -E '^(CONFIG_[A-Za-z0-9_]+=|# CONFIG_[A-Za-z0-9_]+ is not set)' | + while read -r l; do grep -qxF "$l" .config || echo "DROPPED: $l"; done +``` + +**Result 2026-09-02: 460 asked-for symbols (51 base + 409 fragment), zero +dropped** — verified twice, once in the pristine scratch tree above and once +against the real Buildroot build's resolved config +(`output-de25k/build/linux-7.2.2/.config`, after Buildroot's own +`LINUX_KCONFIG_FIXUP_CMDS` step). The two resolutions differ by 35 lines, every +one of them a toolchain-identity symbol (`CC_VERSION_TEXT`, `GCC_VERSION`, +`AS_VERSION`, `LD_VERSION`, `CC_HAS_*`) — the scratch run used the host gcc. + +`merge_config.sh` printed **zero** warnings inside the Buildroot build too: +base and fragment share no symbol (§2), so there is nothing to redefine. + +**RE-VERIFIED AT 7.2.3, 2026-09-02.** Renovate's rt bump moved the shared +`linux.hash` off 7.2.2 and the DE25 pin followed +(`docs/buildroot-config.md` §6.4, and the hazard note in §4 that this bump +turned from hypothetical into history). The whole measurement was re-run +against the real Buildroot build after a `linux-dirclean` on a freshly +downloaded, hash-verified `linux-7.2.3.tar.xz` +(`output-de25/build/linux-7.2.3/.config`): + +| | 7.2.2 (wave 2) | 7.2.3 (re-run) | +|---|---|---| +| asked-for symbols / dropped | 460 (51 + 409) / **0** | 460 (51 + 409) / **0** | +| `merge_config.sh` warnings from `linux-mister.fragment` | 0 | 0 | +| carried patch series | 34/34 at `patch -F0` | 34/34 at `patch -F0`, 0 fuzz, 0 rejects (79 hunks moved by line offset only, which `-F0` permits) | +| `=m` symbols | 92 | 92 | +| installed `.ko.xz` | 91 | 91 | +| installed module bytes | 2.4 MiB | 2,565,868 B (2.4 MiB) | +| `Image` | 20,711,432 B | 20,711,432 B | +| module name set vs the DE10's | identical | identical (compared against the DE10's 6.18.48 tree: 91 vs 91, empty diff both directions) | + +Nothing moved. Note the `Image` size is *coincidentally* unchanged — the +`Image` is not byte-identical (a stable-tree point release changes code), only +the same length. The board device tree **is** byte-identical at 7.2.3, which +says mainline's `socfpga_agilex5.dtsi` did not move in the point release. + +Four candidates were removed during authoring because they did *not* survive, +and each removal is a finding: + +| Removed | Reason | +|---|---| +| `# CONFIG_ACPI is not set` | unreachable once `EFI=n`; documented in §5.1 instead. | +| `# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set` | the symbol was **removed upstream** between 6.18 and 7.2. | +| `# CONFIG_NET_VENDOR_CIRRUS is not set` | `depends on ARM`. | +| `# CONFIG_NET_VENDOR_FARADAY is not set` | `depends on ARM`. | + +## 9. Version skew — the two symbols the fragment must not name + +The DE10 is on 6.18.y and the DE25 on 7.2.y, and the fragment must be valid in +both. Two symbols in the DE10's defconfig are 6.18-only and are therefore +**absent from the fragment**, though the DE10's own file keeps them (which is +still a no-op — the fragment simply does not mention them): + +* **`CONFIG_NFS_V4_1`** — removed as a separate symbol after 6.18; NFSv4.1 is + unconditional in 7.x, where `fs/nfs/Kconfig` offers only `NFS_V4_0` and + `NFS_V4_2`. The fragment sets `NFS_V4` and `NFS_V4_2`, which is the same + capability on both trees. +* **`CONFIG_NF_CT_PROTO_UDPLITE`** — the UDP-Lite conntrack protocol was removed + from `net/netfilter/Kconfig` after 6.18. It was `is not set` on the DE10 anyway. + +## 10. Per-kernel-bump re-check list + +Run **all** of these on any kernel version bump on either board. Each catches a +failure mode that is otherwise silent. + +1. **The survival check (§8 step 2)** on the bumped board. This is the one that + catches a symbol renamed, removed, or newly gated behind an unmet dependency. + Zero `DROPPED:` lines, or an explanation per line in §9. +2. **`scripts/check-kernel-fragment-noop.sh`** after the DE10 kernel build. + A DE10 bump can change a symbol's resolved value (a `default` flip upstream) + and silently break the shared-driver-set claim. +3. **Re-read `docs/de25-patch-portability.md`'s six patch-gated HID symbols** + (§3.10). If a patch stops applying and is dropped, its symbol becomes a + silent no-op on that board. +4. **`grep -c '=m' /.config`** and the installed `/lib/modules` size. + A large jump means an upstream `default` flipped somewhere the minimal + defconfig does not name — the exact failure the wave-1 config had wholesale. +5. **Check the five §5.1 exclusions are still `not set`** in the resolved config. + `EFI` in particular gates ACPI transitively; if upstream ever changes what + selects `ARCH_SUPPORTS_ACPI`, the ACPI menu comes back with no warning. +6. **Confirm `RESET_SIMPLE` is still `y`.** Its `default` clause names + `(ARCH_INTEL_SOCFPGA && ARM64)` explicitly; an upstream refactor of that line + turns `mmc0` into a permanent probe-defer with no diagnostic. +7. **Confirm the sentinels resolved to the value asked for**, not merely that + they are present: `OF_OVERLAY`, `USB`, `HID`, `USB_HID`, `INPUT`, `WLAN`, + `SECCOMP` (off), `MODULE_COMPRESS_XZ`. + +## 11. CI wiring + +`check-kernel-fragment-noop.sh` **needs a configured, built kernel tree** (it +uses that tree's own `scripts/kconfig/conf` and `merge_config.sh`). It therefore +does **not** belong in the lint job, which runs on a bare checkout. + +* **Where:** the DE10 image build job, as a step *after* the kernel is built and + *before* (or alongside) the artifact upload. `output/build/linux-[0-9]*/` is + then guaranteed to exist and to be unique, so the script needs no arguments. +* **Cost:** two `conf --olddefconfig` runs plus a `diff` — **a few seconds**, no + compilation, no download, no cache impact. Actions-minute cost is effectively + zero next to the build it rides on (memory: CI minutes are watched). +* **Not on a fresh runner alone.** On a *developer* machine with several kernel + trees under `output/build/` the script fails closed by design (the Makefile + `rt` recipe's "never the first glob match" rule) and needs `--tree`. In CI + there is exactly one tree. +* **Optional second call** for the RT variant: `--tree output-rt/build/linux-*`, + same argument shape. The fragment is not consumed there today, so this is only + worth adding if the RT kernel ever adopts it. diff --git a/docs/de25-nano-tasks.md b/docs/de25-nano-tasks.md index 18700f8..df761f4 100644 --- a/docs/de25-nano-tasks.md +++ b/docs/de25-nano-tasks.md @@ -313,27 +313,60 @@ so a boot failure there would tell us nothing about our image. - What he can answer for us, in order: factory SPL boots our FIT (D0.1 Q3) → kernel reaches a serial login on our DTS (D2.3) → mmc0 under SMMU (§8 Q2) → the §2.6 fabric-programming test. +## Wave 2 — 2026-09-02 (pre-hardware) — DONE, on `feature/de25-wave2` + +Goal: a card that can go into a borrowed board. Same agent sizing as wave 1; two `fable` +adversarial passes (config refactor; boot path) before anything is called done. + +| Track | Deliverable | Result | +|---|---|---| +| U-Boot + TF-A (D2.4 desk half) | mainline U-Boot v2026.07 + TF-A v2.15.0 as Buildroot packages; `board/mister/de25nano/uboot.fragment`, `uboot-dts/`; `docs/de25-uboot.md` | `u-boot.itb` built by binman and `dumpimage`-verified against the factory SPL contract (crc32 only, no keys; addresses disjoint). QSPI write paths compiled OUT (`CMD_SF`/`MTD`/`UBI`/`ENV_IS_IN_UBI` absent from the resolved config; verified again in the binary's strings). Stock's default boot command contains a `saveenv && ubi part root` leg — gone with `CMD_SF`. `HANDOFF` and `BLOBLIST` off (factory SPL, not ours, runs first). §8 Q6 closed negative: `# CONFIG_SPL is not set` removes the FIT. SD PHY: SoCDK-validated delays, default-speed 25 MHz for first contact. | +| SD card image (D2.4 other half) | `genimage-sdcard.cfg`, `post-image.sh`, `scripts/check-sdcard-de25.sh`, `docs/de25-sdcard.md` | MBR, p1 FAT32 `DE25BOOT` (`u-boot.itb`, `Image`, dtb, `extlinux/extlinux.conf`), p2 = ext4 rootfs written directly (**interim** p2 decision). Checker opens the FIT, allow-lists p1, rejects the DE10 card and every mutated card. `make de25` asserts the card exists and passed. | +| Kernel diet + shared fragment | `board/mister/common/linux-mister.fragment`, `board/mister/de25nano/linux.config`, `scripts/check-kernel-fragment-noop.sh`, `docs/de25-kernel-config.md` | 1,481 → 92 modules, 90 MB → 2.4 MiB, `Image` 41.9 → 20.7 MB; installed module name set identical to the DE10's. Fragment proven a **no-op on the DE10 6.18 tree** (mechanical check; wire it after the DE10 kernel build step). The wave-1 arm64-defconfig kernel had joydev/uinput/hidraw and the pad drivers OFF — invisible in a green build. | +| Config refactor (owner option 2) | `configs/fragments/*` stacks, monoliths deleted, comments → `docs/buildroot-config.md`, `scripts/check-config-fragments.sh` + golden hashes, `lint-config` CI job | PR #137. DE10 resolved config identical old vs new (two independent proofs); fingerprint residue byte-identical; 30+ mutations exercised; kernel-pin bumps don't move the golden, Buildroot bumps warn and are auto-refreshed by hash-sync case 8. | + +### What the boot-path `fable` pass established (for the borrowed-board owner) + +No brick-class or boot-blocking finding. Verified in the built artefacts, not the config: no QSPI +command or driver in U-Boot proper; BL31 issues no QSPI/RSU command at boot; the kernel has no +MTD/spi-nor/RSU driver and its DTB has no flash node; the FIT is unsigned-crc32 at the addresses +the factory SPL expects; nothing writes anything a power cycle does not clear. First-boot +expectations worth knowing: BL31 prints on UART0, so **no `NOTICE: BL31` lines on the header +UART** is normal; capture the SPL's `DDR:` lines (the only real DRAM-size measurement); if +`Retrieving file: /Image` stalls, the SD PHY timing in `uboot-dts/` is the first knob +(`de25-uboot.md` §5.1). + +### Still open after wave 2 + +- p2 filesystem / DE10-style two-stage layout (FAT boot + exFAT data + `linux.img` loop root) + — owner decision; the initramfs route is arch-neutral and the rewritten `loop=` patch + compile-verifies on aarch64, so both are open. Recommendation: first hardware boot on the plain + ext4 card, then switch. +- 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 + and the series/config were re-verified there (34/34 patches at `-F0`, 460 symbols 0 dropped, + `docs/de25-kernel-config.md` §8). Still needs its own Renovate manager, or a hash-sync rule that + keeps every line a fragment still pins, so the next bump is not manual. +- DE25 selects no `linux-firmware`; the shared fragment builds the Wi-Fi/BT drivers that will + ask for it. Owner decision against the bare-developer-OS scope. +- Patch 0002 (MiSTer audio) still excluded; `openssh` will need `_SANDBOX` off when added. + ## 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 work now, in the order that unblocks the most: -Items 1–5 of the original list were executed as wave 1 (above). Remaining, in unblock order: - -1. **U-Boot + TF-A desk build** (D2.4's buildable half, implementation-path §8 Q6): mainline - v2026.07 + TF-A v2.15.0 as Buildroot packages in the DE25 defconfig, `# CONFIG_SPL is not set`, - the §6.2 env fragment with `CONFIG_ENV_IS_IN_UBI` off, `u-boot.itb` shape checked with - `dumpimage` against the factory SPL contract. Then the `fable` pass that gates any image - leaving this machine (rule 2). -2. **`genimage-sdcard-de25.cfg` + `check-sdcard-de25.sh`** (D2.4's other half), so a card can be - written for the borrowed-board test. -3. **Kernel diet**: replace `arm64 defconfig` + fragment with a curated config — 1,481 modules is - not a MiSTer kernel. Also decide `Image` vs `Image.gz`. -4. **`scripts/test-initramfs.sh` aarch64 path** (`qemu-system-aarch64 -M virt`) so userland is - exercised with no board. -5. **Owner decisions**: 0002 audio patch (audit Q8), p2 filesystem, shared-base defconfig - refactor (ADR 0029 "left open"), upstream submission of 0101/0102. -6. **Stand up D0.4** as a `/schedule` routine. +Waves 1 and 2 executed items 1–5 of the original list and the first three of the wave-1 list. +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. +3. **Owner decisions** listed under "Still open after wave 2". +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 later phase must refute, run them **sequentially**, not in parallel. D0.1 was first launched with diff --git a/docs/de25-readiness-ledger.md b/docs/de25-readiness-ledger.md index dd4f0d9..217ab86 100644 --- a/docs/de25-readiness-ledger.md +++ b/docs/de25-readiness-ledger.md @@ -79,6 +79,22 @@ in prose, no action or a one-word reword. ## 3. The ledger — CI +> **Status, 2026-09-02 — the file-path axis this section and §4 describe has since +> been wired.** The three monolithic defconfigs (`configs/mister_de10nano_defconfig`, +> `configs/mister_kernel_defconfig`, `configs/mister_de25nano_defconfig`) were split into +> fragment stacks under `configs/fragments/` (`stacks.mk` says which fragments form +> which configuration; `docs/buildroot-config.md` §1 and §10). Every consumer named +> below by those filenames now reads a fragment: `.github/actions/buildroot-build` +> fingerprints the variant's stack through `scripts/lib/config-stacks.sh`; +> `renovate.json`, `renovate-hash-sync.yml`, `scripts/hash-sync-kernel.sh`, +> `scripts/ci-tests.sh`, `scripts/test-initramfs.sh`, `scripts/test-sdcard-install.sh`, +> `scripts/export-kernel-tree.sh` and `scripts/lint-kernel-patches.sh` read +> `configs/fragments/de10nano.fragment`; `scripts/check-linux-img.sh` cites +> `configs/fragments/de10nano-image.fragment`; `scripts/check-kernel-defconfig-sync.sh` +> compares the two DE10 stacks' merged text. The `Makefile` rows in §4 are superseded +> by `make de10nano-defconfig` / `make de25nano-defconfig`. The line numbers below are +> the pre-split ones and are kept as the record of what was verified. + | file:line | coupling | sev | when you touch this, do this instead | |---|---|---|---| | `.github/actions/buildroot-build/action.yml:190-195` | Toolchain-fingerprint sentinel requires `^BR2_arm` and `^BR2_cortex` or the build fails loud **[V]** — coupling (c) | semantic-blocker | Adopt §5's `BOARD_FINGERPRINT_SENTINELS` row lookup with a validated `board` input; never add a second arch string beside these, and never soften the assert to a warning. | @@ -95,6 +111,9 @@ in prose, no action or a one-word reword. ## 4. The ledger — configs, packages, top level +> See the 2026-09-02 status note at the top of §3: the `configs/*_defconfig` rows below +> describe files that have since become `configs/fragments/*.fragment`. + | file:line | coupling | sev | when you touch this, do this instead | |---|---|---|---| | `package/azcopy/Config.in:64` | `depends on BR2_arm` gates the package to 32-bit ARM **[V]** | semantic-blocker | **Leave it exactly as is** — this decision is already taken, not pending: the DE25 package set drops azcopy entirely because Microsoft ships an official arm64 binary **[V** plan:139**]**. Do not add a `BR2_aarch64` alternative. | diff --git a/docs/de25-sdcard.md b/docs/de25-sdcard.md new file mode 100644 index 0000000..7856b14 --- /dev/null +++ b/docs/de25-sdcard.md @@ -0,0 +1,294 @@ +# DE25-Nano SD card — layout, build, write, first boot + +**Status:** D2.4's card half. The layout below is **implemented and statically verified**; +**nothing here has been on hardware.** Every claim about what the board will do at boot is +inherited from [`de25-boot-chain.md`](de25-boot-chain.md), which is desk research, and is +tagged accordingly. The first card written from this recipe is a **first-contact experiment**, +not a release. + +Files this document describes: + +| File | What it is | +|---|---| +| `board/mister/de25nano/genimage-sdcard.cfg` | the layout, as genimage input | +| `board/mister/de25nano/post-image.sh` | `BR2_ROOTFS_POST_IMAGE_SCRIPT`: generates `extlinux.conf`, runs genimage, runs the checker | +| `scripts/check-sdcard-de25.sh` | fail-closed static verification of the produced image | + +Cross-refs: [`de25-boot-chain.md`](de25-boot-chain.md) §2/§3/§7/§8.3, +[`de25-implementation-path.md`](de25-implementation-path.md) §6/§6.3/§8 Q7, +[ADR 0029](decisions/0029-de25-implementation-path.md) D3/D4, +[`de25-nano-tasks.md`](de25-nano-tasks.md) "Testing on a borrowed board". + +--- + +## 1. Layout + +`output-de25/images/sdcard-de25.img`, MBR, two partitions, ~513 MiB: + +| # | Device | Type | Size | Filesystem | Contents | +|---|---|---|---|---|---| +| — | — | MBR at LBA 0 | 1 MiB gap | — | conventional pre-partition gap; `align = 1M` | +| 1 | `/dev/mmcblk0p1` | `0x0c` (FAT32 LBA), bootable | 256 MiB | FAT32, label **`DE25BOOT`** | `u-boot.itb`, `Image`, `socfpga_agilex5_de25nano.dtb`, `extlinux/extlinux.conf` | +| 2 | `/dev/mmcblk0p2` | `0x83` (Linux) | = `rootfs.ext4` (256 MiB today) | ext4, label **`rootfs`** | Buildroot's `rootfs.ext4`, written verbatim | + +**There is no third partition and no `0xA2` partition.** The `0xA2` type byte is the *Cyclone V +BootROM's* raw-partition contract ([`boot-chain.md`](boot-chain.md) §2.1) and has no meaning on +Agilex 5: here the SDM boots the FSBL out of QSPI and the FSBL reads a **filesystem** — "No 0xA2 +analogue [V]", [`de25-boot-chain.md`](de25-boot-chain.md) §2. `check-sdcard-de25.sh` fails the +image if either appears, because both are reliable symptoms of DE10 layout lore being +transplanted. + +**Nothing on this card references QSPI.** Not `extlinux.conf`, not a U-Boot script, not an +`fw_env.config`. A QSPI write on this board is brick-class with JTAG-and-a-PC recovery and no +RSU safety net (§6, §7 rows 1/5/10/11/12 of the boot-chain doc). The checker greps +`extlinux.conf` for `sf probe`, `ubi` and `mtd`; that grep is thin, and it is deliberately the +*only* automated enforcement that exists today — the wider rule is still policy enforced by +prose (boot-chain §5, §9.3). + +### Why p1 is what it is + +The factory U-Boot SPL — resident in QSPI, **never written by anything we ship** (ADR 0029 D4) +— loads a file called exactly `u-boot.itb` from a FAT filesystem on **partition-table entry 1**, +and places it at `CONFIG_SPL_LOAD_FIT_ADDRESS=0x82000000` +(`CONFIG_SPL_FS_FAT=y`, `SYS_MMCSD_FS_BOOT_PARTITION` default 1; +[`de25-boot-chain.md`](de25-boot-chain.md) §2 step 4, §8.3). Those three facts — FAT, entry 1, +that filename — are the **entire interface** between this card and the board's boot firmware. +Everything else on p1 is read later, by U-Boot proper, and is ours to arrange. + +256 MiB is sized for room, not fit: the payload today is ~42 MiB. The slack is there because a +`core.rbf` and any boot-time fabric bitstream have to live on this same FAT partition, and +growing p1 later means re-writing every card. **Room is not permission:** p1 is an *allow-list* +in the checker, so a file that is not one of the four fails the image. A `uboot.env` in +particular is deliberately **not** shipped ([`de25-uboot.md`](de25-uboot.md) §10) — a seeded +environment file silently overrides the compiled-in one on every already-written card, so the +next release's `bootcmd`/`bootargs` change would be ignored forever. Adding a file to p1 means +editing the genimage `files` list *and* the checker's allow-list in the same commit. + +### `extlinux.conf` + +Generated by `post-image.sh` — it is not a checked-in file, and editing it on a card is a +debugging move rather than a fix. Today it says: + +``` +timeout 10 +default de25 + +label de25 + menu label MiSTer DE25-Nano developer OS + kernel /Image + fdt /socfpga_agilex5_de25nano.dtb + append root=/dev/mmcblk0p2 rw rootwait console=ttyS0,115200 earlycon +``` + +`console=ttyS0,115200` is HPS **uart1** (`serial@10c02100`), aliased `serial0` with +`stdout-path = "serial0:115200n8"` in `board/mister/de25nano/socfpga_agilex5_de25nano.dts`. It is +the only enabled 8250 port, so it is `ttyS0` under any 8250 numbering rule; uart0 is the SoC +Development Kit's console and belongs to a different board. Getting this wrong produces a board +that looks **dead**, not one that prints an error — which is why the checker pins it. + +--- + +## 2. The interim p2 decision — recorded as interim + +**ADR 0029 D3 fixes the partition COUNT and p1's FAT type only.** p2's filesystem is explicitly +still an owner decision: [`de25-implementation-path.md`](de25-implementation-path.md) §6.3 says +so in as many words, and §8 Q7 carries it as an open question ("Which filesystem for p2, and +therefore does the kernel live on p1 or p2?"). + +**The interim decision taken here, for this developer-OS card:** + +> **p2 is the ext4 rootfs written directly, and the kernel mounts it via +> `root=/dev/mmcblk0p2`.** The kernel and DTB live on **p1**, loaded by U-Boot from FAT. + +Why this and not something else, stated as reasoning rather than as a settled decision: + +- It is what Buildroot already builds (`BR2_TARGET_ROOTFS_EXT2` + `_EXT2_4`), so the card needs + no new machinery — no loop-mounted `linux.img`, no stage-1 initramfs, no installer. The DE25's + configuration rationale already says this out loud (`docs/buildroot-config.md` §6.5, "NO STAGE-1 + INITRAMFS ON THIS BOARD"). +- Kernel-on-p1 sidesteps the "can U-Boot read p2?" question entirely, which §6.3 names as the + reason to prefer it. +- The exFAT blocker that shaped the reference implementation's very different card **has + expired** (§6.3: mainline U-Boot has `fs/exfat/` since 2025-03-17), so nothing forces the + single-exFAT-volume design, and nothing forbids it later either. + +**What would re-open it:** the day this card grows a user-visible data volume — the MiSTer +`/media/fat` experience — p2 stops being "just the rootfs" and the owner decision in §8 Q7 has +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**. + +--- + +## 3. MBR, not GPT — and why that is a fail-closed choice + +The only reader that matters for the partition table is Terasic's **factory** U-Boot SPL, and +the question is which partition drivers it was compiled with (`SPL_DOS_PARTITION` vs +`SPL_EFI_PARTITION`). What the dossier actually records: + +- [`de25-boot-chain.md`](de25-boot-chain.md) §8.3 enumerates the SPL's compiled-in contract from + the mainline `socfpga_agilex5_defconfig` — `SPL_LOAD_FIT`, `SPL_LOAD_FIT_ADDRESS=0x82000000`, + `SPL_FS_FAT`, `SYS_MMCSD_FS_BOOT_PARTITION=1`, `SPL_FIT_SIGNATURE`, `ENV_IS_IN_FAT` — and **no + partition-table symbol appears anywhere in it, in either direction**. The published factory SPL + was carved only for its DTB (§8.2); nobody has read its binary for partition support. GPT is + therefore **unproven, not disproven**. +- The one positive partition-table datum anyone has is **MBR**: a physical DE25-Nano has been + observed booting from an MBR-partitioned card — *"Single active MBR partition, exFAT, spanning + the card"*, [`de25-reference-implementation.md`](de25-reference-implementation.md):572 — under + an SPL built from the **same U-Boot 2025.01 vendor tree** the factory SPL comes from (:212; + both identify as `2025.01`, vendor=terasic). + +Two partitions need nothing GPT offers. So the choice is: take the option with evidence behind +it. **If an SPL readback (boot-chain §5) ever proves `SPL_EFI_PARTITION` is compiled in, this +becomes a one-line change in `genimage-sdcard.cfg` — and a change that must be re-tested on +hardware, never waved through at a desk.** + +--- + +## 4. Building the card + +```sh +make de25 +``` + +`BR2_ROOTFS_POST_IMAGE_SCRIPT` runs `board/mister/de25nano/post-image.sh` at the end of the +image step. It: + +1. cross-checks that its own file names still match `genimage-sdcard.cfg` (drift between the two + is a hard error, not a mystery three steps later); +2. asserts `Image`, the DTB and `rootfs.ext4` exist, reporting **all** that are missing; +3. generates `$BINARIES_DIR/extlinux/extlinux.conf`; +4. runs Buildroot's own `support/scripts/genimage.sh -c board/mister/de25nano/genimage-sdcard.cfg`; +5. hands `sdcard-de25.img` to `scripts/check-sdcard-de25.sh`, whose nonzero exit fails the build. + +**The missing-`u-boot.itb` case.** Until D2.2 lands `BR2_TARGET_UBOOT` + +`BR2_TARGET_ARM_TRUSTED_FIRMWARE` there is no FIT in `output-de25/images/`, and a card without +one cannot boot. The **default is to fail the build**. `DE25_ALLOW_NO_UBOOT=1` downgrades that to +"skip the card, build succeeds", with four lines of warning — for kernel/rootfs iteration only: + +```sh +DE25_ALLOW_NO_UBOOT=1 make de25 +``` + +It is an opt-in and never a default, because a silently FIT-less image is exactly the artifact +somebody writes to a card and then debugs at a dead serial console for an hour. + +To re-check an image without rebuilding: + +```sh +scripts/check-sdcard-de25.sh output-de25/images/sdcard-de25.img +``` + +Exit codes: `0` all assertions pass, `1` a contract violation, `2` usage/IO/tooling error. Host +tools (`sfdisk`, mtools' `mdir`/`mcopy`/`mlabel`, `dumpe2fs`, `e2fsck`) are resolved from +`output-de25/host/{bin,sbin}` first and `PATH` second, so any machine that can build the image +can check it. + +--- + +## 5. Writing the card + +> **Writing erases the entire card.** All partitions, not just files. Identify the device with +> `lsblk` (Linux) or `diskutil list` (macOS) before you type it — getting this wrong overwrites +> the wrong disk. + +```sh +sudo dd if=output-de25/images/sdcard-de25.img of=/dev/sdX bs=4M conv=fsync status=progress +sync +``` + +Replace `/dev/sdX` with your card (on macOS `/dev/rdiskN`, after +`diskutil unmountDisk /dev/diskN`). Wait for the command to return fully before removing the +card. balenaEtcher and Raspberry Pi Imager work too — the image is a plain `dd`-able disk image +with no installer stage, unlike the DE10's ([`user/sdcard-flashing.md`](user/sdcard-flashing.md) +describes that different, DE10-only flow — do not follow it for this board). + +**Verify before you switch.** Same discipline this repo uses for every DE10 flash: **keep a +known-good card and do not overwrite it with an unproven build.** On this board that rule is +sharper than usual, for two reasons: + +- The card is the *only* thing you can roll back. If a release ships a `u-boot.itb` the factory + SPL cannot parse, the failure is **strand-class, not brick-class** — QSPI is untouched, so + re-imaging with the *previous* card boots the board again (boot-chain §7 row 2). That escape + hatch only exists if the previous card still exists. +- Without a serial console attached, a stranded-at-SPL board is **indistinguishable from a bad + card**. Attach serial before you conclude anything (§6). + +Use a second card for the new build, boot it, confirm §6's checklist, and only then reuse the +old one. + +--- + +## 6. First-boot serial checklist + +115200 8N1 on the board's UART header (HPS uart1). Watch for these, in order — each line that +does **not** appear localises the failure to one link of the chain +([`de25-boot-chain.md`](de25-boot-chain.md) §2): + +| # | What you should see | If it is missing | +|---|---|---| +| 1 | **SPL banner and DDR init** — the factory SPL running out of QSPI, before anything on the card is read | Nothing on the card is implicated. Check power, the UART cable, and MSEL/SW5 at its factory default (`001`, AS Fast). A board whose QSPI was modified is a different board — §7. | +| 2 | **SPL loading `u-boot.itb`** from `mmc0` | The FAT/entry-1/filename contract failed. Confirm p1 is entry 1, FAT32, and holds `u-boot.itb` (the checker asserts all three) — then suspect the **partition-table type** (§3) or FIT signature policy (boot-chain §7 row 6). This is D0.1 Q3, and it has never been tested on hardware by anyone. | +| 3 | **The `U-Boot 2026.07 …` banner.** Note what you will *not* see: **no `NOTICE: BL31:` lines**. TF-A's Agilex 5 platform prints on **UART0**, not the uart1 header this cable is on ([`de25-uboot.md`](de25-uboot.md) §5 on the uart0/uart1 split), so a silent BL31 is expected and is not evidence of anything | The FIT loaded but its contents did not run: an ATF/U-Boot pairing problem, not a card-layout one (implementation-path §8 Q5). | +| 4 | `Loading Environment from FAT` | **Stop and read boot-chain §7 rows 5/11/12 before proceeding.** `Loading Environment from UBI` means the U-Boot build has `ENV_IS_IN_UBI` compiled in, and the UBI attach path can **write QSPI on an environment LOAD**, with no `saveenv` anywhere. That is the brick-class hazard the whole posture is built around. | +| 5 | **U-Boot picking up `extlinux/extlinux.conf`** on `mmc 0:1` and reporting the `de25` label | The extlinux bootmeth did not find or parse the file. The checker proves the file is there and parses; what it cannot prove is U-Boot's **search path** and **mmc device number** — see §8. | +| 6 | Kernel `Booting Linux on physical CPU`, then `earlycon` output | Kernel loaded but the console argument is wrong, or `Image`/DTB mismatch. | +| 7 | `EXT4-fs (mmcblk0p2): mounted filesystem` | The root device is not there yet or is not what we think. `rootwait` is in the args; a persistent failure here points at mmc0 under the SMMU (implementation-path §8 Q2), not at the card. | +| 8 | `Welcome to MiSTer DE25-Nano (developer OS)` and a login prompt on `ttyS0` | getty/console mismatch. | + +Log the whole session. Several of the dossier's open `[U]`s (Q3 in particular) are answered by +lines 2–3 of a single boot log, and that log is worth more than any further desk work. + +--- + +## 7. If the board is borrowed — do this first + +**The QSPI must hold the factory phase-1 image.** Our card ships `u-boot.itb` only and relies +entirely on the *factory SPL's* contract (FAT on partition 1, FIT at `0x82000000`, boot order +`mmc0`). A modified QSPI carries a **different SPL with a different contract** — the reference +board's, for example, is exFAT-aware and RSU-shaped — so a boot failure on such a board tells us +**nothing** about our image. + +The full procedure, the restore command, the `golden_top_hps.jic` hash to verify first, and the +safety bar that gates any image leaving this machine are in +[`de25-nano-tasks.md`](de25-nano-tasks.md) → **"Testing on a borrowed board before we own one"**. +Read it before shipping anyone a card. The two rules that matter most: + +- **Never run any Terasic demo's `flash_program.bat`**, and never run `flash_erase.bat` as a + first step — it destroys the only known-good baseline that has ever existed for that board + (boot-chain §6's ordering rule, §7 rows 13/16). +- **Record the board revision and the Resource Package version** with any result. A rev-A vs + rev-B mismatch is silent and is §7 row 14. + +--- + +## 8. What the checker proves, and what it cannot + +`check-sdcard-de25.sh` asserts, from the image bytes alone: MBR (and no GPT signature at LBA 1), +exactly two partitions, no `0xA2`, p1 = `0x0c` and ≥ 256 MiB and genuinely FAT32 (read from the +BPB, not trusted from the type byte) and labelled `DE25BOOT` and holding the four files **and +nothing else**; that `u-boot.itb` is byte-identical to the build's own, that `dumpimage -l` +shows the §6.1 FIT contract (`uboot` at `0x80200000`, `atf` at `0x80000000`, `fdt-0`, a default +configuration signed `crc32`), and that the decompiled FIT declares no `rsa`/`required`/`sha` +verification; `extlinux.conf` parses, names a default that resolves to a real label, names a +kernel and fdt that exist on p1, carries `root=/dev/mmcblk0p2`, `console=ttyS0,115200` and +`rootwait`, and mentions no flash machinery; p2 = `0x83`, ext4 (`extent` feature present), +labelled `rootfs`, `e2fsck -fn`-clean; and the whole image within a size budget. + +Two of those exist because a review found the checker passing cards that could not boot: it used +to treat extra p1 files as informational (so a `/boot.scr` carrying a flash-erase rode along — +distro boot runs `scan_dev_for_scripts` right after `scan_dev_for_extlinux`, so it executes the +moment extlinux fails), and it never opened `u-boot.itb` at all (so 4 KiB of `/dev/urandom` +under that name passed, as did a FIT re-signed `sha256,rsa2048` + `required = "conf"` — which the +keyless factory SPL would refuse on every boot). + +It **cannot** prove any of the following, and none of them should be described as verified until +a board says so: + +| Assumption | Owned by | How it gets settled | +|---|---|---| +| The factory SPL reads an **MBR** table | U-Boot track / D2.2 | §3. First boot, or an SPL binary readback. | +| ~~U-Boot's extlinux **search path**~~ — **SETTLED [V]** | — | Closed by the built environment, not inferred: `boot_prefixes="/ /boot/"` and `boot_syslinux_conf="extlinux/extlinux.conf"`, so `/extlinux/extlinux.conf` at the root of p1 is the **first** path tried ([`de25-uboot.md`](de25-uboot.md) §9.1, read from `u-boot-initial-env` of the shipped build). The card layout matches. | +| The card is **`mmc 0`** in U-Boot and **`mmcblk0`** in Linux | U-Boot track / DTS | The factory SPL's boot order names `/soc/mmc0@10808000` (boot-chain §2 step 4), and our DTS enables exactly one SD controller — but neither is a measurement. | +| `u-boot.itb` is the **only** file the SPL wants | frozen contract, boot-chain §8.3 | Already `[V]` at the Kconfig level; `[U]` against the factory flash. | +| ~~A `uboot.env` on p1 is needed~~ — **SETTLED: no** | — | [`de25-uboot.md`](de25-uboot.md) §10 traces the code path: with `ENV_IS_IN_UBI` off there is exactly one env driver compiled in, and a missing file makes `env_fat_load()` load the built-in environment and return `-EIO` with **no write anywhere**. Shipping one is a staleness hazard for no safety gain, so the checker's p1 allow-list now **rejects** `uboot.env`. | +| The image boots at all | nobody, yet | §6. | diff --git a/docs/de25-uboot.md b/docs/de25-uboot.md new file mode 100644 index 0000000..1565968 --- /dev/null +++ b/docs/de25-uboot.md @@ -0,0 +1,787 @@ +# DE25-Nano U-Boot — mainline `u-boot.itb`, and the config that makes a QSPI write impossible + +**Status:** built and shape-verified on 2026-09-02; **never run on hardware**. Every claim below is +tagged **[V]** (read from source, or observed in this build) or **[U]** (unverified — the missing +input is named). Nothing here has touched a DE25-Nano. + +This is D2.4's buildable half (`docs/de25-nano-tasks.md`, "What to do next" item 1). It delivers +`output-de25/images/u-boot.itb` and `output-de25/images/bl31.bin`, and it closes +[`de25-implementation-path.md`](de25-implementation-path.md) §8 **Q6** — in the negative. + +Cross-refs: [`de25-implementation-path.md`](de25-implementation-path.md) §6.1–§6.3, §8 Q5/Q6/Q7; +[`de25-boot-chain.md`](de25-boot-chain.md) §2, §3, §5, §7 (brick-risk register), §8.3, §8.5; +[`de25-dts-rationale.md`](de25-dts-rationale.md) ("Console UART", "Memory"); +[ADR 0029](decisions/0029-de25-implementation-path.md). + +--- + +## 1. The one rule + +The DE25-Nano's QSPI holds the SDM firmware, the phase-1 HPS bitstream (which carries **all** the +DDR and pinmux handoff data), and the factory U-Boot SPL that is this board's FSBL. The SDM cannot +boot from the microSD at all, so that flash is the only thing standing between the board and a +JTAG-and-a-PC recovery, and no power-loss-safe update path for it is demonstrated at this flash size +**[V `de25-boot-chain.md` §8.1, §8.4, §7 rows 1/7/8]**. + +> **Nothing this build produces may write the QSPI, by any mechanism.** + +§7 of this document is the audit that says whether that holds, symbol by symbol. §6.2 of +`de25-implementation-path.md` is why the guard has to be structural rather than procedural: the +danger is not `saveenv`, it is the environment **load** path. + +--- + +## 2. Versions, and where their hashes come from + +| Component | Pin | Why not Buildroot's own | Hash provenance | +|---|---|---|---| +| U-Boot | **v2026.07** (released 2026-07-07) | Buildroot 2026.05.2 ships 2026.04 | **Signed.** `ftp.denx.de/pub/u-boot/u-boot-2026.07.tar.bz2` + its `.sig`; `gpg --verify` → *Good signature from "Thomas Rini "*, EDDSA key `F3CEA8743D60E0192F9B4C7A2BE2A0F50ABFE40A`, fetched by full fingerprint from keys.openpgp.org **[V, done 2026-09-02]** | +| TF-A | **v2.15.0** | Buildroot 2026.05.2 tops out at v2.12, which has **no Agilex 5 platform** — a custom version is the only route, not a preference | **TOFU, honestly labelled.** trustedfirmware.org publishes no release tarballs and no signed manifest. Anchored on annotated tag `v2.15.0` (object `9ad327a8…`) → commit `da738d5eae93af342fdc4995dd3c05acb4c9d757`, confirmed from a **second, independent clone**. The tag *is* PGP-signed (RSA `5D6F8960…`, Olivier Deprez/Arm) but that key is on **neither** keys.openpgp.org nor keyserver.ubuntu.com (both 404, 2026-09-02), so the signature could **not** be verified **[V that it is unverifiable today]** | + +Both hash files live under the existing `BR2_GLOBAL_PATCH_DIR` +(`board/mister/de25nano/patches/`), the same mechanism the kernel's `linux.hash` uses — +`pkg-patch-hash-dirs` (`package/pkg-utils.mk:163`) searches `$(BR2_GLOBAL_PATCH_DIR)//` as well +as the package directory. Each file's header carries the full provenance story; read it before +changing a value. + +`BR2_DOWNLOAD_FORCE_CHECK_HASHES=y` makes both **fail closed**, and the two failures have different +shapes worth knowing: + +- **U-Boot**: `boot/uboot/uboot.hash` exists but only lists 2026.04, so `check-hash` finds a hash + *file* and no matching *line* → exit 3, `ERROR: No hash found for u-boot-2026.07.tar.bz2`. +- **TF-A**: Buildroot ships no ATF hash file at all and explicitly excuses git-generated tarballs + via `BR_NO_CHECK_HASH_FOR`. `BR2_DOWNLOAD_FORCE_CHECK_HASHES` **empties** that variable + (`package/pkg-download.mk:119`), so the excuse does not apply and the same exit 3 results **[V]**. + +**Filename gotcha, ATF only.** Buildroot's git backend names its tarball +`arm-trusted-firmware-v2.15.0-**git4**.tar.gz`, where `4` is `BR_FMT_VERSION_git` — the archive +*format* version. A Buildroot bump that changes it changes both the filename and the hash, and the +build fails closed until both are re-derived **[V]**. + +**The version pairing (U-Boot 2026.07 + TF-A v2.15.0) is [U]** — this is ADR 0029 D4's "left open" +item, and building green does not close it. Terasic and Altera document only vendor forks +(`u-boot-socfpga socfpga_v2023.10` + `arm-trusted-firmware socfpga_v2.10.0`). + +--- + +## 3. Files + +| File | Role | +|---|---| +| `configs/fragments/de25nano.fragment` | the ATF/U-Boot/host-tools stanza (rationale: `docs/buildroot-config.md` §6.9, §6.10) | +| `board/mister/de25nano/uboot.fragment` | the U-Boot Kconfig delta on `socfpga_agilex5_defconfig` (§4) | +| `board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts` | U-Boot board device tree (§5) | +| `board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano-u-boot.dtsi` | U-Boot additions: `stdout-path`, mmc caps, FIT tweaks (§5, §6) | +| `board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch` | the one carried U-Boot patch (§8) | +| `board/mister/de25nano/patches/uboot/uboot.hash` | signed-provenance hash for the 2026.07 tarball | +| `board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash` | TOFU hash for the v2.15.0 git tarball | +| `Makefile` (`de25` recipe) | post-build assertions for `images/bl31.bin` and `images/u-boot.itb` | + +**Why `uboot-dts/` is a subdirectory.** The U-Boot board file and the *kernel* board file share a +basename by convention (`socfpga_agilex5_de25nano.dts`) and are entirely different files: U-Boot's +`socfpga_agilex5.dtsi` and the kernel's are separate upstream files, U-Boot's `mmc0` is +`"altr,agilex5-sd6hc","cdns,sd6hc"` while the kernel has no `mmc0` at all and ours declares the +SD4HC form, and only U-Boot has `-u-boot.dtsi` machinery **[V]**. They must not share a directory. + +--- + +## 4. The fragment, and why each block is there + +The full file is `board/mister/de25nano/uboot.fragment`, and every line in it carries its own +comment; this section is the summary and the *evidence*, not a duplicate. + +### 4.1 Board device tree + +``` +CONFIG_DEFAULT_DEVICE_TREE="socfpga_agilex5_de25nano" +``` + +`scripts/Makefile.dts` does `dtb-y += $(CONFIG_DEFAULT_DEVICE_TREE).dtb` **[V]**, so a `.dts` that +appears in no `arch/arm/dts/Makefile` list is still built. That is what makes carrying a board file +possible **without patching U-Boot**. Buildroot's `BR2_TARGET_UBOOT_CUSTOM_DTS_PATH` is a plain +`cp -f arch/$(UBOOT_ARCH)/dts/` **[V `boot/uboot/uboot.mk`]**, so it happily takes both the +`.dts` and the `-u-boot.dtsi`. + +### 4.2 The environment block — the reason this task exists + +``` +CONFIG_ENV_IS_IN_FAT=y +CONFIG_ENV_FAT_DEVICE_AND_PART="0:1" +# CONFIG_ENV_IS_IN_UBI is not set +# CONFIG_ENV_IS_IN_SPI_FLASH is not set +# CONFIG_ENV_IS_IN_NAND is not set +# CONFIG_ENV_IS_IN_MMC is not set +``` + +The stock `socfpga_agilex5_defconfig` compiles in **both** FAT and UBI. The hazard is not `saveenv`: +`env_ubi_load()` calls `ubi_part()` **unconditionally** at `env/ubi.c:128` whenever the FAT load +fails, and a UBI attach against a **blank** MTD partition succeeds and then *writes a layout volume* +via `create_empty_lvol()` → `create_vtbl()` **[V, `de25-implementation-path.md` §6.2, traced there +against v2026.07 sources]**. A missing `uboot.env` plus a blank QSPI `root` partition therefore +writes boot flash on the very first `env_load()`, with no user action. + +The last three lines are stated even though nothing sets them today: they are what a future +Buildroot or U-Boot default flip would have to get past, and `ENV_IS_IN_SPI_FLASH` would put the +environment *directly* in the QSPI. + +### 4.3 QSPI: three locks on one door + +Driver, stack, commands — see the audit table in §7. Summary: `CADENCE_QSPI` is the only route from +U-Boot to this flash and it is compiled out; the SPI-NOR stack above it is compiled out; every +command that could reach either is compiled out; MTD and UBI are compiled out. + +### 4.4 DRAM — a coupling no earlier document names + +**New finding, and it is load-bearing.** `arch/arm/mach-socfpga/misc.c` `dram_init()` has two +branches **[V, v2026.07]**: + +```c +#if CONFIG_IS_ENABLED(HANDOFF) && IS_ENABLED(CONFIG_ARCH_SOCFPGA_AGILEX5) + ho = bloblist_find(BLOBLISTT_U_BOOT_SPL_HANDOFF, sizeof(*ho)); + if (!ho) + return log_msg_ret("Missing SPL hand-off info", -ENOENT); + gd->ram_size = ho->ram_bank[0].size; +#else + if (fdtdec_setup_mem_size_base() != 0) + return -EINVAL; +#endif +``` + +The bloblist that branch looks for is written by **our** SPL at **our** `CONFIG_BLOBLIST_ADDR`. Our +SPL never runs — the factory SPL does, and it is a Terasic build of U-Boot 2025.01 whose +`BLOBLIST_ADDR` is a compiled-in constant that appears in no artifact we can read. Mainline's socdk +uses `0x7e000`; the reference DE25 tree uses `0x72000` **[V, both defconfigs read]**. If they +disagree, `dram_init()` returns `-ENOENT`, U-Boot dies in its first initcalls, and the failure looks +exactly like a bad card. `bloblist_init()` does *not* save us: on a bad magic at the fixed address +it logs a warning and creates a **new, empty** bloblist **[V `common/bloblist.c`]**, so the handoff +blob is simply absent. + +So the fragment sets `# CONFIG_HANDOFF is not set`, `dram_init()` takes the `fdtdec` branch, and the +size comes from the `/memory` node in our own `u-boot.dtb`. Self-contained; no dependency on a +Terasic constant. The cost is that 1 GiB @ `0x8000_0000` is a *declared* value. + +**Fallback if the hardware says otherwise:** `include/handoff.h` is **byte-identical** between the +reference 2025.01-lineage tree and v2026.07 (`diff -q` → identical) **[V]**, so re-enabling +`CONFIG_HANDOFF=y` with `CONFIG_BLOBLIST_ADDR=0x72000` is a viable second attempt. Try the DTS +constant first. + +### 4.4b …and the bloblist goes with it, which removes a latent overlap + +With `HANDOFF` off nothing in this build reads or writes a bloblist, so +`# CONFIG_BLOBLIST is not set` as well. That is not tidiness. The stock defconfig sets +`BLOBLIST_FIXED` with `ADDR = 0x7e000`, `SIZE = 0x1000`, i.e. the on-chip-RAM region +`0x7E000..0x7EFFF`. TF-A v2.15.0's Agilex 5 platform puts its **secondary-CPU handshake words at the +top of that same page** **[V]**: + +``` +PLAT_HANDOFF_OFFSET = 0x0007F000 agilex5/socfpga_plat_def.h:30 +BL_DATA_LIMIT = PLAT_HANDOFF_OFFSET +PLAT_CPUID_RELEASE = BL_DATA_LIMIT - 16 = 0x7EFF0 +PLAT_SEC_ENTRY = BL_DATA_LIMIT - 8 = 0x7EFF8 common/platform_def.h:125-128 +``` + +and `bl31_plat_setup.c:59` writes `PLAT_SEC_ENTRY`. The declared bloblist region covers both words. + +**Today the overlap is benign** — U-Boot writes only the ~32-byte bloblist header at `0x7E000` and +never grows into the last 16 bytes of the page. But "benign because nothing currently fills the +buffer" is a property of today's blob set, not a guarantee, and what it guards against is a +secondary CPU jumping to a clobbered entry point. Deleting a region nothing uses is strictly better +than reasoning about how full it gets. + +**Checked, not assumed [V]:** with `BLOBLIST` off the config still resolves to `SPL=y`, +`SPL_ATF=y`, `BINMAN=y`, a full U-Boot build completes, and `u-boot.itb` is still produced. +`BLOBLIST_FIXED`, `BLOBLIST_ADDR`, `BLOBLIST_SIZE`, `SPL_BLOBLIST`, `HANDOFF` and `SPL_HANDOFF` all +disappear from the resolved config. The `# CONFIG_HANDOFF is not set` line is kept anyway: the two +lines record two separate decisions, and if a future bump makes something `select BLOBLIST` again, +the HANDOFF line is still what keeps `dram_init()` off the factory SPL's bloblist. + +### 4.5 SPL — §8 Q6, answered + +`de25-implementation-path.md` §6.1 reasoned from the Kconfig graph that `# CONFIG_SPL is not set` +would "genuinely eliminate SPL compilation", flagged **[U]**, "the first thing to check at first +build". **It does not work, and Q6 closes in the negative [V]:** + +``` +config ARCH_SOCFPGA_AGILEX5 + select BINMAN if SPL_ATF # arch/arm/mach-socfpga/Kconfig +``` + +`SPL_ATF` sits inside `menu "SPL configuration options" depends on SPL` (`common/spl/Kconfig:19-20`), +so turning `SPL` off takes `SPL_ATF` with it and `BINMAN` is never selected — and `CONFIG_BINMAN` is +a bool **with no prompt** (`dts/Kconfig:15`), so it cannot be turned back on from a defconfig or a +fragment; kconfig drops the line. No binman, no `u-boot.itb`, and the U-Boot build is otherwise +green. + +**Verified by resolving the config both ways, not merely reasoned [V]:** the same fragment plus one +extra `# CONFIG_SPL is not set` line, run through `merge_config.sh` + `olddefconfig` against +`socfpga_agilex5_defconfig`, gives + +``` +# CONFIG_SPL is not set +CONFIG_SPL_ATF +CONFIG_BINMAN +``` + +against `CONFIG_SPL=y` / `CONFIG_SPL_ATF=y` / `CONFIG_BINMAN=y` as shipped. That is the worst failure shape available, which is why the Makefile now asserts the FIT +exists (§9) and the fragment carries a "do not re-open this" comment. + +**So SPL is compiled and nothing of it is shipped.** That is enforced positively, not by omission: +`BR2_TARGET_UBOOT_SPL` is not set, so `UBOOT_INSTALL_IMAGES_CMDS` copies no `spl/*` file +**[V `boot/uboot/uboot.mk`]**, and `images/` contains no SPL artifact (§9). The factory SPL in QSPI +is untouched, which is the posture-1 contract. + +### 4.6 Filesystems and boot + +`CONFIG_FS_FAT` / `CONFIG_CMD_FAT` (already implied by `BOOT_DEFAULTS_CMDS`, restated because the +env and the boot path both depend on them); `CONFIG_FS_EXFAT=y` (mainline gained `fs/exfat` in +`b86a651b64`, 2025-03-17 — after the reference board's 2025.01 base, which is exactly why that +project hand-rolled libexfat; §8 Q7's p2-filesystem decision is now free of any U-Boot change); +`CONFIG_DISTRO_DEFAULTS=y` (already y, restated because upstream marks it deprecated and this line +is where a future migration to `BOOTSTD_DEFAULTS` starts); and a sane `CONFIG_BOOTARGS` replacing +the stock ramdisk/`nosmp`/Simics string. + +--- + +## 5. The board device tree — why not reuse `socfpga_agilex5_socdk` + +Mainline v2026.07 has **no** DE25-Nano board: `board/terasic/` has de0-nano-soc, de1-soc, de10-nano, +de10-standard and sockit, and there is no `configs/*de25*` anywhere in the tree **[V]**. The choice +was therefore between reusing the SoC Development Kit's device tree and carrying a minimal board +file. **Reuse is not viable, for one line:** + +``` +socfpga_agilex5_socdk.dts: serial0 = &uart0; <- SoCDK console +DE25-Nano: serial0 = &uart1; <- the board's USB-UART header +``` + +`de25-dts-rationale.md` settles the DE25's console as uart1 **[V]**, and the reference DE25 U-Boot +tree aliases `serial0 = &uart1` **[V]**. A device tree cannot be overridden from Kconfig, so booting +socdk's DTB on this board gives a console on a pin nobody wired — a board that looks dead. That is +the whole justification; everything else in our board file follows from keeping it minimal. + +**What we author (both files together are ~1 screen of actual device tree):** + +| Node | Value | Why | +|---|---|---| +| `aliases/serial0` | `&uart1` | the reason the file exists | +| `aliases/mmc0` | `&mmc` | `UCLASS_MMC` carries `DM_UC_FLAG_SEQ_ALIAS`; "0" is load-bearing in `ENV_FAT_DEVICE_AND_PART="0:1"`, in `bootcmd_mmc0`, and in every `load mmc 0:1` on the card. With one controller the answer is 0 anyway — writing it down stops a future second device renumbering it | +| `/memory` | `<0 0x80000000 0 0x40000000>` (1 GiB) | §4.4. Node name has **no** unit address because `fdtdec_setup_mem_size_base()` looks it up by the literal path `/memory` **[V `lib/fdtdec.c:1084`]** | +| `osc1` | `clock-frequency = <25000000>` | the SoC dtsi declares the fixed clock with no rate; the whole tree, including the UART divisor, hangs off it | +| `&uart1` | `status = "okay"` + `bootph-all` | the dtsi ships it disabled; `socfpga_agilex5-u-boot.dtsi` marks `&uart0` `bootph-all`, not uart1, and without the marking there is no pre-relocation console — the exact window a bring-up failure would be diagnosed in | +| `&mmc` | `okay`, `no-mmc`, `no-sdio`, `disable-wp`, `bus-width = <4>`, `cap-sd-highspeed`, `max-frequency = <50000000>`, `bootph-all` | see below | + +### 5.1 The `&mmc` block — board facts vs SoC facts, treated differently + +Those are two different categories and the block splits them deliberately. + +**BOARD facts we do not take from socdk.** socdk declares `sd-uhs-sdr50`/`sd-uhs-sdr104` with +`vqmmc-supply = <&sd_io_1v8_reg>`, whose GPIO is `<&portb 3>` — a **SoC Development Kit wiring +fact**. Driving the wrong GPIO to switch SD bus voltage is a way to break a card, not a way to go +faster, and the reference DE25 tree declares no vqmmc regulator either **[V]**. So: no UHS modes, +no voltage switching, no regulator phandles. + +**SoC facts we do take from socdk.** An earlier draft of this file omitted socdk's `cdns,*` timing +properties on the argument that `drivers/mmc/sdhci-cadence6.c` carries a built-in default for every +one of them. That is true and it was the wrong conclusion: **the driver's defaults are a fallback, +not a validated configuration.** For SD high speed they are + +``` +cdns,phy-dqs-timing-delay-sd-hs 0x00380004 +cdns,phy-gate-lpbk-ctrl-delay-sd-hs 0x01A00040 +cdns,phy-dq-timing-delay-sd-hs 0x00000001 +cdns,ctrl-hrs07-* / cdns,ctrl-hrs16-* (no entry at all) +``` + +**[V `drivers/mmc/sdhci-cadence6.c:75-135`]** — values that no validated Agilex 5 board ships. +socdk's, which are the only Agilex 5 SD timings anyone has run on silicon, are `0x780001`, +`0x81a40040`, `0x10000001`, `hrs16 = 0x101`, `hrs07 = 0xA0001` +**[V `arch/arm/dts/socfpga_agilex5_socdk-u-boot.dtsi:122-134`]**. These are *controller* delays, not +board wiring, so they transfer. We now copy the `sd-ds` and `sd-hs` stanzas verbatim, with the +source named in the file. + +**Speed: default speed only, 25 MHz.** `cap-sd-highspeed` is **not** set and `max-frequency` is +`<25000000>`, so U-Boot proper never leaves DS mode — the slowest and most forgiving SD timing there +is. The cost is nothing that matters: 4-bit DS is about 12.5 MB/s, so the 20 MB `Image` costs under +two seconds, once, per boot. The `sd-hs` values are still declared so that lifting the cap is a +one-line change rather than a research task. + +**This is the first knob to turn if the card misbehaves.** The symptoms to watch for on the first +boot are: `Retrieving file: /Image` stalling or timing out; `mmc_load_image_raw` / `sdhci` timeout +messages; a CRC or checksum complaint from extlinux or from `booti`; or a kernel that starts and +then panics on a corrupt initramfs/rootfs read. Any of those is a timing problem until proven +otherwise, and the order of attack is: + +1. **Already at the safest setting** (DS, 25 MHz, socdk PHY values) — that is what ships. +2. If it still fails, try the *driver-default* PHY values (delete the `cdns,*` lines) — that + isolates "socdk's timings are wrong for this board" from "the card or the socket is the problem". +3. Only then suspect the card itself; `dd if=/dev/mmcblk0 of=/dev/null bs=1M` from Linux is the + independent check, because it exercises the kernel's driver rather than U-Boot's. + +**Lifting it, once a full `dd` of the card reads clean under Linux and U-Boot has booted reliably a +few times:** re-add `cap-sd-highspeed;` and set `max-frequency = <50000000>;` in +`socfpga_agilex5_de25nano-u-boot.dtsi`. That is the whole change — the `sd-hs` PHY block it needs is +already there. Re-test a cold boot and a `Retrieving file:` of the kernel before keeping it. Going +beyond 50 MHz means UHS, which means a `vqmmc` regulator, which means establishing the DE25's real +1.8 V switch GPIO on hardware — a different and much larger job. + +**Deliberate omissions:** no `&qspi` and no flash node (the node stays at the dtsi's `disabled` +default, so even a hypothetically-present driver would not probe — the second lock on §7's door); +no `&nand`; no `&gmac0`/PHY (U-Boot does not need ethernet to load a kernel, and the DE25's PHY +address in U-Boot terms is unverified); no LEDs, watchdogs, timers, i2c, i3c, usb, spi0/spi1. + +**`socfpga_agilex5_de25nano-u-boot.dtsi` must `#include "socfpga_agilex5-u-boot.dtsi"`** — that is +what pulls in `socfpga_soc64_fit-u-boot.dtsi`, the binman description that *is* `u-boot.itb`. +Without it the build produces a working U-Boot binary and **no FIT at all** **[V]**. Every label +that file references (`clkmgr`, `i2c0-3`, `mmc`, `porta`, `portb`, `qspi`, `rst`, `sdr`, `sysmgr`, +`uart0`, `watchdog0`) is defined in the SoC `.dtsi`, so a minimal board file is enough **[V, +checked]**. + +It does **not** carry socdk's `u-boot,spl-boot-order`: that property is read by `board_boot_order()` +in SPL, our SPL never runs, and the factory SPL's order is already known and fixed — +`"/soc/mmc0@10808000", "/soc/spi@108d2000/flash@0", "/soc/nand@10b80000", "/memory"` +**[V `de25-boot-chain.md` §2]**. Restating it would describe a decision we do not get to make, and +it references `&flash0`, which this board file deliberately does not declare. + +--- + +## 5b. What the build produced + +`make de25`, 2026-09-02, green **[V]**: + +| `output-de25/images/` | Size | From | +|---|---|---| +| `u-boot.itb` | 728,168 B (`sha256 49f1c7dd…`, clean build 2026-09-02) | binman, U-Boot v2026.07 | +| `bl31.bin` | 53,304 B (`sha256 863073b2…`) | TF-A v2.15.0, `PLAT=agilex5` | +| `Image` | 20,711,432 B | Linux 7.2.2 (see §12b — the kernel-config switch landed in this pass's defconfig edit) | +| `socfpga_agilex5_de25nano.dtb` | 17,138 B | kernel DTS (D2.3, unchanged here) | +| `rootfs.ext4` → `rootfs.ext2` | 256 MiB | D2.1, unchanged here | + +The FIT's `Created:` timestamp is `Sun Aug 23 16:00:00 2026` — `SOURCE_DATE_EPOCH` from +`BR2_REPRODUCIBLE=y`, not wall-clock, so the artifact is reproducible **[V]**. Confirmed in +practice: repeated `make de25` runs, hours apart and with a full kernel reconfigure between them, +produced byte-identical artifacts — `bl31.bin` is +`sha256 863073b2c0a9489ae04cbf077b5496975f7aa7f925a8397fad705bcb3c390bf1` across every run, and +`u-boot.itb` is byte-stable **within a build tree** — verified twice: two runs in the wave-2 tree +(728,176 B) and, after a full `distclean`, two runs in the clean tree including a +`uboot-dirclean` rebuild (728,168 B, `sha256 49f1c7dd…`) **[V]**. It is **not yet shown to be +byte-stable across clean trees [U]**: the wave-2 tree's FIT and the clean tree's FIT differ by +8 bytes, all of it inside the `uboot` payload (650,056 → 650,048 B; `atf` and `fdt-0` identical +in size and BL31 identical in hash), with the same resolved Buildroot config. The version string +is not the cause (it carries only the pinned `SOURCE_DATE_EPOCH` date). The boot contract is +unaffected — load addresses, config node, crc32-only signature and DTB are what the checker +asserts, not the hash — but the D2.8 release lane should pin this down with two clean CI builds +before it publishes attested hashes. (History: 731,728 B before the review fixes, 728,176 B +after `CONFIG_BLOBLIST` came out and the mmc node grew.) + +**Housekeeping done in the same pass:** `images/socfpga_agilex5_socdk.dtb` was a stale leftover from +before D2.3 (mtime predating this build by hours, from when the defconfig still pointed at +mainline's socdk placeholder). Buildroot never removes a stale artifact from `images/`, the +Makefile's `*.dtb` glob printed it as though it were current, and the card-image step could plausibly +have copied it. Deleted. **Worth a guard**: nothing in the build detects this class of leftover. + +### 5b.1 Two build-cost facts worth knowing before someone thinks the build hung + +- **`BR2_TARGET_UBOOT_USE_BINMAN=y` drags in a Rust toolchain.** It selects + `host-python-jsonschema`, which needs `host-python-rpds-py`, which is a Rust extension, which + needs `host-rust-bin`. On a cold host tree that is the single longest step of the whole DE25 + build and it looks nothing like a bootloader **[V, observed]**. +- **`BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT` is not `default y`, and without it `dumpimage` is + silent.** `dumpimage -l u-boot.itb` printed *nothing* and exited **0** — a verification step that + always passes and never checks anything. That is a worse failure than a crash. Found on the first + build; the symbol is now in the defconfig with a comment saying why **[V]**. + +## 6. The FIT — checked against the factory SPL contract + +### 6.1 What `dumpimage` says + +``` +FIT description: FIT with firmware and bootloader +Created: Sun Aug 23 16:00:00 2026 + Image 0 (uboot) + Description: U-Boot SoC64 + Created: Sun Aug 23 16:00:00 2026 + Type: Standalone Program + Compression: uncompressed + Data Size: 654016 Bytes = 638.69 KiB = 0.62 MiB + Architecture: AArch64 + Load Address: 0x80200000 + Entry Point: unavailable + Hash algo: crc32 + Hash value: 1806e9a2 + Image 1 (atf) + Description: ARM Trusted Firmware + Created: Sun Aug 23 16:00:00 2026 + Type: Firmware + Compression: uncompressed + Data Size: 53304 Bytes = 52.05 KiB = 0.05 MiB + Architecture: AArch64 + OS: ARM Trusted Firmware + Load Address: 0x80000000 + Hash algo: crc32 + Hash value: 690a1fc1 + Image 2 (fdt-0) + Description: socfpga_agilex5_de25nano + Created: Sun Aug 23 16:00:00 2026 + Type: Flat Device Tree + Compression: uncompressed + Data Size: 23176 Bytes = 22.63 KiB = 0.02 MiB + Architecture: Unknown Architecture + Hash algo: crc32 + Hash value: 89c5e5a4 + Default Configuration: 'board-0' + Configuration 0 (board-0) + Description: board_0 + Kernel: unavailable + Firmware: atf + FDT: fdt-0 + Loadables: uboot + Sign algo: crc32:dev + Sign value: unavailable + Timestamp: unavailable +``` + +### 6.2 Contract check, term by term + +| §6.1 contract term | Required | Observed | | +|---|---|---|---| +| image `uboot` | `u-boot-nodtb.bin`, `type=standalone`, `arch=arm64`, `load = 0x80200000` (`CONFIG_TEXT_BASE`) | Standalone Program, AArch64, `0x80200000` | **[V]** | +| image `atf` | `bl31.bin`, `type=firmware`, `os=arm-trusted-firmware`, `load = entry = 0x80000000` | Firmware, OS "ARM Trusted Firmware", load `0x80000000`, and `entry = <0x80000000>` — read from the decompiled FIT, not assumed (`dumpimage -l` does not print `entry` for a firmware image) | **[V]** | +| image `fdt-0` | `u-boot.dtb`, description `"socfpga_socdk"` → **rename per board** | Flat Device Tree, description `socfpga_agilex5_de25nano` | **[V]** | +| config `board-0` | `default`; `firmware="atf" loadables="uboot" fdt="fdt-0"` | exactly that | **[V]** | +| signature | `algo = "crc32"`, no keys | `Sign algo: crc32:dev`, `Sign value: unavailable`; **no rsa anywhere in the file** | **[V]** | + +**The whole FIT structure, decompiled (`dtc -I dtb -O dts`), contains ZERO occurrences of `rsa`, +`required` or `sha*`** — `grep -icE 'rsa|required|sha[0-9]'` → `0` **[V]**. The only integrity +material in the file is three `hash { algo = "crc32"; value = <...>; }` nodes and one +`signature { algo = "crc32"; key-name-hint = "dev"; sign-images = "atf","uboot","fdt-0"; }`. + +The signature term is the one that could strand every board. The factory SPL is built with +`CONFIG_SPL_FIT_SIGNATURE=y` **[V `de25-boot-chain.md` §8.3]**, but the DTB carved from Terasic's +published SPL carries **no `/signature` node and no keys** **[V, same source]**, so +`fit_config_verify_required_sigs()` finds nothing required and an unsigned FIT is accepted. A +key-requiring FIT would fail on every board. Ours has a `crc32` integrity declaration and nothing +else — which is what the contract asks for. + +Also settled at the desk and worth restating: `board_fit_config_name_match()` matches each +configuration node's **`description`** (`"board_%u"` from `socfpga_get_board_id()`), and +`fit_find_config_node()` falls back to `/configurations/default` when nothing matches, so a +**single-config FIT boots correctly regardless of board ID** **[V `de25-implementation-path.md` +§6.1]**. We leave `board-0`'s description at upstream's `board_0` for exactly that reason. + +### 6.3 Address map — does anything collide? + +The SPL stages the whole FIT at `CONFIG_SPL_LOAD_FIT_ADDRESS = 0x82000000` and then copies each +image to its `load` address **[V]**. + +| Region | Range | Size | +|---|---|---| +| BL31 (`atf`, load = entry) | `0x8000_0000` → | 53,304 B (0xD038) | +| BL31 limit (TF-A `socfpga_plat_def.h:150`) | `0x8200_0000` | — | +| U-Boot proper (`uboot`, load) | `0x8020_0000` → | 650,048 B (0x9EB40) | +| FIT staging (SPL load address) | `0x8200_0000` → | 728,168 B (0xB1C68) total | +| DRAM (declared) | `0x8000_0000` – `0xBFFF_FFFF` | 1 GiB | + +**No collision [V]:** BL31 ends far below `0x8020_0000`; U-Boot proper at `0x8020_0000` plus its +size ends far below `0x8200_0000`; the staged FIT starts at `0x8200_0000`, above both, and U-Boot +relocates itself to the top of DRAM immediately afterwards. `TF-A`'s `BL31_LIMIT` (`0x8200_0000`) is +exactly the FIT staging base, so the two never overlap even in principle. + +The runtime kernel addresses are above all of it: `kernel_addr_r=0x82000000`, +`fdt_addr_r=0x86000000`, `scriptaddr=0x81000000` **[V, read from the built default environment]** — +by the time U-Boot proper loads a kernel there, the FIT staging copy is dead. + +--- + +## 7. QSPI-write audit + +The standard is [`de25-boot-chain.md`](de25-boot-chain.md) §7, rows 1, 5, 10, 11, 12. Values read +from the **resolved** `output-de25/build/uboot-2026.07/.config` — not from the fragment, because the +fragment is a request and kconfig is the answer. + +**Read "off" precisely.** For most rows below the symbol is not merely `# ... is not set`: it is +**absent from the resolved config entirely**, because once `CADENCE_QSPI` and the SPI-NOR stack go, +the dependencies of `CMD_SF`, `SPI_FLASH*`, `DM_MTD`, `MTD_UBI`, `CMD_MTD`, `CMD_MTDPARTS`, +`CMD_UBIFS`, `ENV_IS_IN_UBI`, `ENV_IS_IN_SPI_FLASH` and `ENV_IS_IN_NAND` are unmet and kconfig drops +them. That is strictly stronger than "not set" — there is no line to flip **[V, observed]**. The +short form the task's checklist asks for: + +``` +$ grep -E 'CONFIG_ENV_IS_IN|CONFIG_SPL=|CONFIG_CMD_UBI|CONFIG_MTD|CONFIG_CMD_SF|CONFIG_CADENCE_QSPI' \ + output-de25/build/uboot-2026.07/.config +CONFIG_SPL=y +# CONFIG_CMD_UBI is not set +# CONFIG_ENV_IS_IN_EEPROM is not set +CONFIG_ENV_IS_IN_FAT=y +# CONFIG_ENV_IS_IN_EXT4 is not set +# CONFIG_ENV_IS_IN_FLASH is not set +# CONFIG_ENV_IS_IN_MMC is not set +# CONFIG_ENV_IS_IN_NVRAM is not set +# CONFIG_ENV_IS_IN_REMOTE is not set +# CONFIG_MTD is not set +# CONFIG_CADENCE_QSPI is not set +``` + +`CONFIG_ENV_IS_IN_UBI`, `CONFIG_CMD_SF` and every `SPI_FLASH*` symbol do not appear in that output +**because they no longer exist in the config at all**. + +| Symbol | State | Can it write QSPI? | Verdict | +|---|---|---|---| +| `CONFIG_ENV_IS_IN_UBI` | **off** | **Yes — on LOAD, with no user action.** `env_ubi_load()` → `ubi_part()` → UBI attach on a blank MTD → `create_vtbl()` writes a layout volume | closed. §7 row 12's mandatory guard | +| `CONFIG_ENV_IS_IN_SPI_FLASH` | **off** (absent) | Yes — the environment would live in the QSPI | closed | +| `CONFIG_ENV_IS_IN_NAND` / `_MMC` | **off** | no (wrong media) / no | stated so a default flip cannot re-open them silently | +| `CONFIG_ENV_IS_IN_FAT` | **on**, `"0:1"` | no — SD only | the only env location. §5's fifth contract term | +| `CONFIG_CADENCE_QSPI` | **off** | **Yes — this is the only controller driver that reaches the flash** | closed. Lock 1 | +| `CONFIG_DM_SPI_FLASH` / `CONFIG_SPI_FLASH` | **off** (absent) | yes, via `sf`/MTD | closed. Lock 2 | +| `CONFIG_SPI_FLASH_MTD` | **off** | yes | closed | +| `CONFIG_SPI_FLASH_STMICRO` / `_SPANSION` | **off** | the actual `MT25QU128` chip driver | closed | +| `CONFIG_CMD_SF` | **off** | **Yes — `sf erase` / `sf write`.** Note it is `default y if DM_SPI_FLASH`, so it is **on** in the stock config | closed. Lock 3 | +| `CONFIG_CMD_SF_TEST` | **off** | yes — and its help text says "The test is destructive" | closed | +| `CONFIG_CMD_MTD` | **off** | **Yes — `mtd erase` / `mtd write`.** On in the stock config | closed | +| `CONFIG_CMD_MTDPARTS` | **off** | indirectly | closed | +| `CONFIG_CMD_UBI` | **off** | **Yes — `ubi part` auto-formats a blank MTD.** On in the stock config | closed | +| `CONFIG_CMD_UBIFS` | **off** | yes | closed | +| `CONFIG_MTD` / `CONFIG_DM_MTD` / `CONFIG_MTD_UBI` | **off** | the layers the above sit on | closed. Needed the §8 patch | +| `CONFIG_MTD_RAW_NAND` / `CONFIG_CMD_NAND` | **off** | no (no NAND on this board) | closed anyway | +| `CONFIG_SPL_SPI_LOAD`, `SPL_SPI_FLASH_MTD`, `SPL_DM_SPI_FLASH`, `SPL_MTD` | **off** | our SPL never runs, so these are inert either way | closed for tidiness | +| **`bootcmd_qspi`** (default env) | **absent** | **YES, AND THIS IS THE SHARPEST FINDING.** The stock `BOOTENV_DEV_QSPI` body in `include/configs/socfpga_soc64_common.h` is literally `"ubi detach; sf probe && … env select UBI; saveenv && ubi part root && …"` — a QSPI write *inside the default boot command*, reached by falling through `distro_bootcmd`. It is gated on `IS_ENABLED(CONFIG_CMD_SF)`, so turning `CMD_SF` off deletes the boot target **and** the env string | closed **[V, `boot_targets=mmc0` in the built default env]** | +| **`bootcmd_nand`** (default env) | **absent** | same shape, `env select UBI; saveenv; ubi part root` | closed (gated on `CMD_NAND`) | +| `linux_qspi_enable` (default env) | **present** | **Correction to an earlier draft of this table, which said "nothing invokes it".** Something does: `board_prep_linux()` in `arch/arm/mach-socfpga/board.c:194-197` runs `run_command(env_get("linux_qspi_enable"), 0)` on **every FIT-kernel boot**. The argument is the *gate*, not the caller — that block is `if (use_fit && IS_ENABLED(CONFIG_CADENCE_QSPI))`, and `CADENCE_QSPI` is compiled out, so the call site does not exist in our binary **[V, read]**. Belt-and-braces even if it did: the variable's body starts `if sf probe`, and `sf` is not a command here. (We boot via extlinux, not a FIT kernel, so `use_fit` would also be false — but that is the weakest of the three arguments and is not what this row rests on.) | **inert, and it is the Kconfig gate that makes it so** | +| `CONFIG_QSPI_BOOT` | **on** (inherited) | no. Despite the name it is a `boot/Kconfig` media choice consumed **only** by NXP Layerscape and i.MX code — every reference is under `arch/arm/cpu/armv8/fsl-layerscape`, `arch/arm/cpu/armv7/ls102xa` or `arch/arm/mach-imx` **[V, tree-wide grep]** | **inert, argued** | +| `CONFIG_SPI` / `CONFIG_DESIGNWARE_SPI` | **on** | no. A different controller (spi0/spi1 general-purpose pins), not the Cadence QSPI block behind the SDM | **inert, argued** | +| RSU (`cmd/rsu.c`, `CONFIG_CMD_RSU`) | **does not exist** in mainline v2026.07 | — | not applicable **[V, no such file]** | +| `CONFIG_SOCFPGA_SECURE_VAB_AUTH` | **off** | no | — | +| `CONFIG_BLOBLIST` | **off** | not QSPI — but it declared `0x7E000..0x7EFFF`, which covers TF-A's `PLAT_CPUID_RELEASE` (`0x7EFF0`) and `PLAT_SEC_ENTRY` (`0x7EFF8`) | closed. §4.4b — a latent RAM overlap, not a flash one, removed rather than argued | + +**Second lock, outside Kconfig:** our board device tree declares no `&qspi` flash node and leaves +the controller at the SoC dtsi's `status = "disabled"`, so even a driver that somehow returned would +have nothing to bind to (§5). + +**Third lock, outside U-Boot:** §7 row 11 — a Linux-side `fw_setenv` with an `fw_env.config` naming +an MTD device bypasses everything above. Nothing in this build ships `fw_setenv` +(`configs/fragments/de25nano.fragment` has no packages at all), but that is an accident of scope, not +a guard. §5's proposed release-blocking CI check ("the DE25 U-Boot config has `ENV_IS_IN_UBI` unset +and ships no QSPI-write command set") is still **unimplemented**; this table is what it should +assert. + +--- + +## 8. The one carried U-Boot patch + +`board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch`. + +Turning MTD off breaks the build: + +``` +include/configs/socfpga_soc64_common.h:133:19: error: expected '}' before 'CONFIG_MTDIDS_DEFAULT' +``` + +`CFG_EXTRA_ENV_SETTINGS` references `CONFIG_MTDIDS_DEFAULT` and `CONFIG_MTDPARTS_DEFAULT` +unconditionally in **all three** of its variants, and those symbols are +`depends on MTD || SPI_FLASH` (`cmd/Kconfig`) **[V]** — so they simply do not exist once a board +compiles the flash stack out. The patch wraps the two lines in a macro that expands to nothing when +neither symbol is defined. No functional change for any board that has MTD or SPI_FLASH; genuinely +upstreamable (not yet submitted). + +**The alternative, if the owner prefers zero patches:** put `CONFIG_MTD=y` back. With no MTD device +driver compiled in (`CADENCE_QSPI`, all `SPI_FLASH_*`, `MTD_RAW_NAND` off) and no command that can +reach it (`CMD_MTD`, `CMD_MTDPARTS`, `CMD_UBI`, `CMD_SF` off), MTD would register zero devices and +be inert. That is a defensible row in §7 — it is just a weaker one than "absent from the binary", +and the consequence class here is brick-with-JTAG-recovery. **This is an owner call**; it is a +one-line change either way. + +--- + +## 9. Boot flow as shipped + +``` +power-on + -> SDM (hard microcontroller) reads QSPI [factory, never ours] + -> phase-1 HPS bitstream: pinmux + DDR handoff + the FSBL + -> factory U-Boot SPL (Terasic, U-Boot 2025.01) + - initialises DDR, prints its "DDR:" lines + - reads /u-boot.itb from FAT partition 1 of the microSD + (SPL_FS_LOAD_PAYLOAD_NAME under SPL_LOAD_FIT; boot partition 1) + - stages the FIT at 0x82000000, copies: + atf -> 0x80000000 (and enters it) + uboot -> 0x80200000 + fdt-0 -> passed to U-Boot as its control DTB + -> BL31 (TF-A v2.15.0) -> U-Boot proper (v2026.07) [ours] + - console on uart1 @115200 8N1 (serial0) + - DRAM from /memory in its own DTB (1 GiB @ 0x80000000) + - env from mmc 0:1 /uboot.env (FAT; absent is fine, see §10) + - bootcmd = "run distro_bootcmd", boot_targets = "mmc0" + -> scans mmc 0, partition 1 + -> finds /extlinux/extlinux.conf (prefix "/" is tried first) + -> loads /Image and /socfpga_agilex5_de25nano.dtb + -> booti with the extlinux "append" line as bootargs + -> Linux 7.2.2, root=/dev/mmcblk0p2 +``` + +### 9.1 The exact environment we ship + +Read from `u-boot-initial-env` of this build — not inferred **[V]**: + +``` +bootcmd=run distro_bootcmd +distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done +boot_targets=mmc0 +bootcmd_mmc0=devnum=0; run mmc_boot +boot_prefixes=/ /boot/ +boot_syslinux_conf=extlinux/extlinux.conf +bootdelay=5 +bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait +kernel_addr_r=0x82000000 +fdt_addr_r=0x86000000 +scriptaddr=0x81000000 +mmcroot=/dev/mmcblk0p2 +``` + +(Read with `make u-boot-initial-env` in `output-de25/build/uboot-2026.07` — the shipped build tree, +not a scratch copy. `mtdids` and `mtdparts` are **absent** from the environment, which is the visible +effect of the §8 patch.) + +`boot_targets` is **`mmc0` and nothing else** — the `qspi` and `nand` targets are gone with +`CMD_SF`/`CMD_NAND`, which is what deletes `bootcmd_qspi`'s embedded `saveenv`-into-QSPI (§7). + +`scan_dev_for_extlinux` tries `${prefix}extlinux/extlinux.conf` with `boot_prefixes = "/ /boot/"`, +so **`/extlinux/extlinux.conf` at the root of partition 1 is found first** **[V]** — which matches +the card layout. + +### 9.2 Card contract (D2.4's other half — confirmations for that track) + +| Item | Confirmed | Note | +|---|---|---| +| p1 FAT32, MBR (no GPT), label `DE25BOOT` | **yes** | U-Boot reads FAT on an MBR/DOS partition table; `CONFIG_DOS_PARTITION` is on. The label is not used by anything in U-Boot | +| p1 holds `u-boot.itb`, `Image`, `socfpga_agilex5_de25nano.dtb`, `/extlinux/extlinux.conf` | **yes** | `/extlinux/...`, **not** `/boot/extlinux/...` — and `/` is the first prefix tried, so root-level is also the faster path | +| append `root=/dev/mmcblk0p2 rw rootwait console=ttyS0,115200 earlycon` | **yes** | extlinux's `append` **replaces** `bootargs` entirely; the fragment's `CONFIG_BOOTARGS` is only a hand-boot fallback | +| SD enumerates as **`mmc 0`** | **yes**, and now pinned | the board DTS declares `aliases { mmc0 = &mmc; }`; `boot_targets=mmc0`, `bootcmd_mmc0=devnum=0`, `ENV_FAT_DEVICE_AND_PART="0:1"` all agree | +| kernel format | **`Image`**, uncompressed | unchanged from D2.1 (`BR2_LINUX_KERNEL_IMAGE=y`). Nothing here needs `Image.gz`, and switching it would change three files at once | +| **MBR boot flag** | **already correct** | `scan_dev_for_boot_part` runs `part list mmc 0 -bootable devplist` and only falls back to `devplist=1` when **nothing** is bootable **[V, from the built env]**, so the flag is not inert — it *selects* which partition gets scanned. `genimage-sdcard.cfg` sets `bootable = "true"` on **p1 only** **[V, read]**, which is the right answer. The rule to keep: p1 bootable or nothing bootable; never p2 | +| `/extlinux/extlinux.conf` content | **matches** | `post-image.sh` generates `timeout 10` / `default de25` / `kernel /Image` / `fdt /socfpga_agilex5_de25nano.dtb` / `append root=/dev/mmcblk0p2 rw rootwait console=ttyS0,115200 earlycon` **[V, read]** — the same console and root this fragment's fallback `CONFIG_BOOTARGS` names | +| `uboot.env` on p1 | **not shipped, and should not be** | §10 | + +--- + +## 10. The environment file — no seed, and the trace that says why + +The question is whether a missing `uboot.env` on p1 is dangerous. **It is not, once +`ENV_IS_IN_UBI` is off, and the code path is short enough to state in full [V, v2026.07]:** + +1. `env_fat_load()` (`env/fat.c`) does exactly two things on a missing file: prints + `Unable to read "uboot.env" from mmc0:1...` and calls `env_set_default(NULL, 0)`, which loads the + **built-in** environment into RAM. The only I/O it performs is `file_fat_read`. It returns + `-EIO`. **No write, anywhere.** +2. `env_load()` (`env/env.c:172`) iterates the linker list. With `ENV_IS_IN_UBI` off there is + **exactly one** driver compiled in, so there is no second location to fall through to — + `env/ubi.c` is not in the binary at all. +3. On total failure `env_load()` sets `best_prio = 0` and `gd->env_load_prio = 0` + (`env/env.c:222-227`), i.e. FAT. A later `saveenv` therefore prints + `Saving Environment to FAT...` and **creates** the file on p1 — which is §7 row 5's assertion, + satisfied without shipping anything. + +**Recommendation: do not ship a seeded `uboot.env`.** A frozen copy on the card silently *overrides* +the compiled-in default environment forever after, so the next release's `bootcmd`, `bootargs` or +`boot_targets` change would be ignored on every already-written card — a staleness hazard we would +be adding for no safety benefit, since the branch it was meant to guard is not in the binary. If the +owner overrules this, the recipe is +`mkenvimage -s 0x2000 -o uboot.env ` (`CONFIG_ENV_SIZE=0x2000`), with +`BR2_TARGET_UBOOT_INITIAL_ENV=y` producing the exact default text to feed it. **Note that +`mkenvimage` is not built today** — it needs `BR2_PACKAGE_HOST_UBOOT_TOOLS_ENVIMAGE=y`, which is +deliberately off; only `FIT_SUPPORT` is on, for `dumpimage`. + +--- + +## 11. First-boot serial expectations + +Bring-up is a **read-the-console** exercise; nothing below is automatable yet. + +**Capture the `DDR:` lines.** They come from the *factory* SPL's +`drivers/ddr/altera/sdram_agilex5.c`, which derives `hw_size` from `io96b_ctrl->overall_size`, caps +the DT-declared size at it, and prints +`DDR: Warning: DRAM size from device tree (...) exceeds the actual hardware capacity(...)` on a +mismatch **[V `de25-dts-rationale.md`, "Memory"]**. They are the **only** authority on this board's +real DRAM size — every "1 GiB" in this project is a vendor declaration awaiting exactly this +readout. A `DDR: Warning` means the constant in +`board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts` (and the matching one in the kernel +DTS) is wrong. + +Then, in order: + +| Expect | Means | +|---|---| +| any output at all on uart1 @115200 8N1 | the alias/`stdout-path` pair is right. **Silence here is the failure this board file exists to prevent** | +| **NO `NOTICE: BL31: v2.15.0…` lines** | **expected — absence is not failure.** TF-A's Agilex 5 platform registers its console at `PLAT_INTEL_UART_BASE`, which is `PLAT_UART0_BASE = 0x10C02000` **[V `plat/intel/soc/common/include/platform_def.h:156`, `plat/intel/soc/agilex5/include/socfpga_plat_def.h:154`, `bl31_plat_setup.c:61`]** — that is **uart0**, not the DE25's header UART at `0x10C02100`. So BL31 runs and says nothing on the cable you are watching. Do not read a missing BL31 banner as "BL31 did not run"; the thing that proves BL31 ran is the U-Boot banner on the next line, because U-Boot is BL33 and only BL31 gets there. (If you need BL31's own output, uart0 is exposed on the HPS header pins, or `PLAT_INTEL_UART_BASE` can be re-pointed in a TF-A rebuild — neither is needed for a normal bring-up.) | +| `U-Boot 2026.07 …` banner | the FIT parsed, BL31 ran, BL33 entered — i.e. the whole §2 pairing works. This is the [U] that only hardware closes | +| `DRAM: 1 GiB` | `dram_init()` took the `fdtdec` branch (§4.4). If instead U-Boot dies before the banner with `Missing SPL hand-off info`, `CONFIG_HANDOFF` came back on | +| `Loading Environment from FAT... ` then either `OK` or `Unable to read "uboot.env" from mmc0:1...` | §10. **`Loading Environment from UBI` must NEVER appear.** If it does, stop and do not boot again until the config is fixed — that message means the QSPI is being attached | +| `MMC: mmc0@10808000: 0` | the SD controller bound and is device 0 | +| `Scanning mmc 0:1...` / `Found /extlinux/extlinux.conf` | §9's boot path | +| `Retrieving file: /Image` … `Retrieving file: /socfpga_agilex5_de25nano.dtb` | the card layout matches | +| `Starting kernel ...` | hand-off to Linux 7.2.2 | + +**Do not** run `saveenv`, `sf`, `mtd` or `ubi` on the first session. The first three do not exist in +this build; typing them should produce `Unknown command` — which is itself a useful confirmation of +§7 and is worth capturing in the log. + +--- + +## 12. Status ledger + +| Claim | Tag | +|---|---| +| The build produces `images/u-boot.itb` and `images/bl31.bin` from mainline sources | **[V]** — this build | +| The FIT matches the §6.1 factory-SPL contract: images, load addresses, default config, crc32-only signature, no keys | **[V]** — `dumpimage`, §6 | +| No load or save path in this U-Boot can write the QSPI | **[V, config-traced]** — §7 | +| The stock `BLOBLIST_FIXED` region overlapped TF-A's secondary-CPU handshake words, and no longer exists in this build | **[V]** — §4.4b | +| U-Boot's SD access uses socdk's silicon-validated Agilex 5 PHY timings, at default speed only | **[V, config-traced]** — §5.1. Whether those timings suit *this* board is **[U]** until hardware | +| `# CONFIG_SPL is not set` does not work; SPL is compiled and nothing of it is shipped | **[V]** — §4.5, closes §8 Q6 | +| `boot_targets` contains only `mmc0`, and `bootcmd_qspi`'s embedded `saveenv` is gone | **[V]** — built default env | +| U-Boot 2026.07 + TF-A v2.15.0 boot this board under the factory SPL | **[U]** — needs hardware. ADR 0029 D4 | +| The factory SPL accepts our unsigned crc32 FIT | **[U]** — the *published* SPL DTB has no keys **[V]**; the *programmed* flash is unread | +| The SD controller works with our conservative `&mmc` block | **[U]** — needs hardware | +| DRAM is 1 GiB at `0x8000_0000` | **[U, vendor declaration]** — the `DDR:` lines settle it | +| The TF-A v2.15.0 tag signature is authentic | **[U]** — signing key not published on any reachable keyserver | +| Nothing else in the release writes QSPI (Linux side, `fw_setenv`, updater) | **[policy, unenforced]** — `de25-boot-chain.md` §5 | + +--- + +## 12b. One change here that is not about U-Boot + +The DE25's Buildroot configuration (`configs/fragments/de25nano.fragment`) is shared by three +tracks working in parallel, and the kernel track deliberately did not touch it — the kernel-config +commit's message says *"The defconfig switch (custom config + fragment, delete +de25nano/linux.fragment) lands with the U-Boot track's defconfig edit."* So this pass also lands +it: + +``` +# BR2_LINUX_KERNEL_USE_ARCH_DEFAULT_CONFIG is not set +BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y +BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE=".../board/mister/de25nano/linux.config" +BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES=".../board/mister/common/linux-mister.fragment" +``` + +Rationale, measurements and per-subsystem justification are that track's +([`de25-kernel-config.md`](de25-kernel-config.md)), not this one's. +`board/mister/de25nano/linux.fragment` is referenced by nothing once this lands and is `git rm`'d +in the same commit. + +--- + +## 13. Owner decisions this raises + +1. **The carried U-Boot patch (§8)** vs putting `CONFIG_MTD=y` back and arguing inertness. One line + either way. +2. **`CONFIG_HANDOFF` off + a declared 1 GiB (§4.4)** vs `CONFIG_BLOBLIST_ADDR=0x72000` and taking + the measured size from the factory SPL. Recommendation: as shipped; revisit only if the board + reports the wrong size. +3. **The `&mmc` block (§5.1)** — socdk's silicon-validated PHY timings, but default speed only at + 25 MHz. It is the first knob to turn if the card misbehaves, and §5.1 has both the diagnosis + order and the one-line lift back to 50 MHz high speed. Faster than that needs the DE25's real + vqmmc GPIO, which is a hardware-session observation. +4. **No seeded `uboot.env` (§10).** +5. **`CONFIG_FS_EXFAT=y`** anticipates §8 Q7 resolving toward exFAT on p2. If p2 stays ext4 forever, + this line can go. +6. **`DISTRO_DEFAULTS` is deprecated upstream.** Migrating to `BOOTSTD_DEFAULTS` is a separate, + testable change; doing it now would mean bring-up debugs two new things at once. +7. **The §5 CI check** ("`ENV_IS_IN_UBI` unset and no QSPI-write command set") is still + unimplemented. §7 is the assertion list it should encode. diff --git a/docs/debug-tooling.md b/docs/debug-tooling.md index 6ab0f5f..e3dca34 100644 --- a/docs/debug-tooling.md +++ b/docs/debug-tooling.md @@ -54,7 +54,7 @@ $ grep -rn "DEBUG TOOLING" configs/ board/ | Where | What | | --- | --- | -| `configs/mister_de10nano_defconfig` | `BR2_PACKAGE_GDB` + `_GDB_SERVER` + `_GDB_DEBUGGER`, `BR2_PACKAGE_STRACE`, `BR2_PACKAGE_LINUX_TOOLS_PERF` (+ `_NEEDS_HOST_PYTHON3`), `BR2_PACKAGE_RT_TESTS` | +| `configs/fragments/de10nano-image.fragment` | `BR2_PACKAGE_GDB` + `_GDB_SERVER` + `_GDB_DEBUGGER`, `BR2_PACKAGE_LINUX_TOOLS_PERF` (+ `_NEEDS_HOST_PYTHON3`), `BR2_PACKAGE_RT_TESTS`. `BR2_PACKAGE_STRACE` was in the block too until the 2026-09 fragment split: T5 had already promoted strace to a permanent package (its own line in the T5 section), and the fragment sets it once, there — deleting the block no longer removes strace (`docs/buildroot-config.md` §5.32) | | `board/mister/de10nano/linux.config` | `CONFIG_COREDUMP=y` (was `# CONFIG_COREDUMP is not set`) | Nothing else in the tree references any of it. @@ -65,7 +65,7 @@ Nothing else in the tree references any of it. | --- | --- | --- | | gdb / gdbserver | 15.2 | `BR2_GDB_VERSION` default for a GCC >= 9 toolchain (`package/gdb/Config.in.host:77`) | | strace | 7.0 | `package/strace/strace.mk` | -| perf | = the kernel pin (`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, `configs/mister_de10nano_defconfig`) | built from **our own kernel's** `tools/perf`, not a standalone release — so it tracks the pin by construction, never separately (§2) | +| perf | = the kernel pin (`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, `configs/fragments/de10nano.fragment`) | built from **our own kernel's** `tools/perf`, not a standalone release — so it tracks the pin by construction, never separately (§2) | | rt-tests | 2.8 | `package/rt-tests/rt-tests.mk` | | numactl | 2.0.19 | pulled in by rt-tests (`select`) | | mpfr | 4.2.2 | pulled in by the full gdb (`select`) | @@ -133,7 +133,7 @@ which is **part of the `linux` package**, not a standalone one. Consequences: * perf is rebuilt whenever the kernel is, and always matches the running kernel's `tools/perf` — no version skew; -* it is *not* built by the kernel-only `mister_kernel_defconfig` / `make rt` +* it is *not* built by the kernel-only fragment stack (`configs/fragments/kernel-only.fragment`) / `make rt` path, which sets no packages. RT gets `CONFIG_COREDUMP` but not a perf of its own — the single shipped `perf` is the one from the main image's kernel build, and it is what runs under the RT kernel too. The `perf_event_attr` ABI is @@ -247,7 +247,7 @@ actually writes the ELF core file. It turns on for free and **must not** get a line of its own: it is invisible to kconfig while `COREDUMP` is off, so a `savedefconfig` round-trip would drop any line added for it. -`linux.config` is shared with `configs/mister_kernel_defconfig`, so the RT/beta +`linux.config` is shared with the kernel-only stack (`configs/fragments/de10nano.fragment` names it), so the RT/beta kernel gets coredumps too. That is wanted — RT is the variant under active on-hardware investigation. @@ -336,7 +336,7 @@ is a decision on the record rather than an oversight: ## 5. How to revert 1. Delete the whole `DEBUG TOOLING` block from - `configs/mister_de10nano_defconfig` — opening banner (`>>> DEBUG TOOLING — + `configs/fragments/de10nano-image.fragment` — opening banner (`>>> DEBUG TOOLING — TEMPORARY, REMOVE AS ONE BLOCK <<<`) through closing banner (`>>> END DEBUG TOOLING <<<`), inclusive. See §1 for why the two banner lines are not mirror images. @@ -362,7 +362,7 @@ is a decision on the record rather than an oversight: 6. Rebuild: ```sh - make mister_de10nano_defconfig # re-resolve output/.config from the defconfig + make de10nano-defconfig # re-resolve output/.config from the fragment stack make all ``` diff --git a/docs/decisions/0024-mainline-uboot-capability-artifact.md b/docs/decisions/0024-mainline-uboot-capability-artifact.md index f831173..9c3014a 100644 --- a/docs/decisions/0024-mainline-uboot-capability-artifact.md +++ b/docs/decisions/0024-mainline-uboot-capability-artifact.md @@ -133,7 +133,7 @@ including **two genuine upstream defects** in the very defconfig a naive port st (515,141 B, sha256 `e2d46cf9…62ba64`), fetched by hash. ADR 0017 §Decision 5 stands. * **`sdcard.img` still embeds that same stock blob.** ADR 0017 §Decision 4 and ADR 0020 stand; `scripts/mk-sdcard.sh` and `scripts/check-sdcard.sh` are untouched. -* `configs/mister_de10nano_defconfig` gains no `BR2_TARGET_UBOOT*` line. +* `configs/fragments/de10nano-image.fragment` gains no `BR2_TARGET_UBOOT*` line. * **No artifact produced by this work may be named `uboot.img`.** `updateboot` `dd`s `/media/fat/linux/uboot.img` over the `0xA2` partition on every Linux update with no version check, no hash check and no opt-out (boot-chain §5). diff --git a/docs/decisions/0029-de25-implementation-path.md b/docs/decisions/0029-de25-implementation-path.md index 2b6dba0..00e9db2 100644 --- a/docs/decisions/0029-de25-implementation-path.md +++ b/docs/decisions/0029-de25-implementation-path.md @@ -49,6 +49,7 @@ Accept the nine decisions below as the bounding constraints for DE25 wave 1. | D7 | Carry the `sdhci-cadence` 40-bit DMA-mask patch, upstreamably | §1 row 8, §1.1 | *no DP* — instance of D5 | | D8 | Fix mainline's unbindable Agilex 5 svc node **upstream** | §3.1 note | **DP-9** | | D9 | A vendor `svc` carry is a hardware-gated escape hatch | §1 row 9, §2.6 | **DP-9**, **DP-1** | +| D10 | The SMMU ships **disabled**; DMA isolation is a non-goal (DE10 parity) | [`de25-dts-rationale.md`](../de25-dts-rationale.md) §4 | **DP-9**, **DP-1** | ### D1 — Core loading goes through `fpga_manager` + a DT overlay @@ -223,6 +224,38 @@ critical, not merely be what the vendor happens to do. a sizing diff and a TF-A `plat/intel/soc/agilex5` read — unread here **[U]** (§8 Q1/Q5). - **Re-open if.** The test passes — the hatch closes and D5 holds unqualified on the svc layer. +### D10 — The SMMU ships disabled; DMA isolation is a non-goal + +**Decision (owner, 2026-09-02).** The board device tree leaves the Agilex 5 `arm,smmu-v3` at +`status = "disabled"`, every `iommus` reference stays in the tree but is inert, and this is the +intended configuration, not a wave-1 expedient. DMA isolation for peripherals and for the fabric +is **not a goal** of this image: the DE10-Nano's Cyclone V has no SMMU, so a core in the fabric +can already DMA anywhere in RAM on stock, and this is parity with that, not a regression. The +security value an IOMMU carries on a laptop or server does not apply to a games console whose +fabric is loaded by its owner. + +- **Evidence.** Mainline `stratix10-svc` takes the SDM buffer from the `GET_MEM` SMC, keeps + **physical** addresses in its gen_pool and hands them to the SDM raw (no `iommu_map`/`dma_map` + anywhere in the file), while the dtsi's `iommus = <&smmu 10>` attaches the svc device to a + translated default domain. With the SMMU on, mainline can bind the FPGA manager but the SDM's + first read faults: **SMMU-on cannot program the fabric on a mainline kernel** — traced at 7.2.2 + and matching the one failed attempt seen on real silicon **[V, rationale §4.1]**. Terasic's + vendor driver makes SMMU-on work with an IOVA carveout, `iommu_map()` and an SDM remapper + bypass; mainline has none of that. SMMU-off makes the driver's assumption true. +- **Consequences.** Disabling the SMMU is what gives D1 (fpga-manager + overlay on mainline) its + chance, so D10 is a precondition of D1, not a trade against it. The D7 patch is not + load-bearing on the shipped path (all DRAM is below 4 GiB). The runtime equivalent for a test + card is the kernel argument `iommu.passthrough=1` in `extlinux.conf` — the SMMU cannot be + toggled from userspace after boot, but the hardware session can compare both shapes by editing + a text file on the FAT partition. **SMMU-off is unproven, not disproven** (rationale U10): + Terasic's driver refuses to run on Agilex 5 without the SMMU and always disables the SDM + remapper, which mainline never touches; the §2.6 fabric test runs SMMU-off **first** and is + what settles it. +- **Re-open if.** Upstream `stratix10-svc` gains IOMMU-aware buffer handling (then SMMU-on + becomes a free choice and this decision is revisited on its merits, still with isolation as a + non-goal by default); or the SMMU-off fabric test fails on hardware and the vendor remapper + behaviour turns out to be required — that is D9's escape hatch, evaluated then. + ## Decisions deliberately left open None of these is decided here. Each is named so it is not mistaken for settled. @@ -236,6 +269,13 @@ None of these is decided here. Each is named so it is not mistaken for settled. refactoring the DE10 defconfig while bringing up an unbooted board couples two risks and puts DE10 regressions on the DE25's critical path. **Interim, not the end state** — the shared-base refactor is a future ADR, taken once a DE25 has booted and the overlap is measured rather than predicted. + *Addendum 2026-09-02:* the owner took the refactor early, on the condition that the DE10 build be + provably unchanged: all three defconfigs became fragment stacks under `configs/fragments/` + (`docs/buildroot-config.md`; the measured overlap is the seven-symbol `common.fragment`, §10 + there — the DE25 stack shares nothing else with the DE10 and still carries no DE10 package). + The DE10's resolved `.config`, `savedefconfig` and CI cache keys were shown byte-identical + before the monoliths were deleted (§11 there). Not an ADR of its own: no decision recorded + here changed. 3. **The U-Boot / TF-A version pairing.** Mainline U-Boot **v2026.07** + TF-A **v2.15.0** is the recommendation, but the pairing is **unblessed and untested by us [U]** — the vendors document only forks and no DE25-Nano board exists in mainline U-Boot, so we carry the fragment ourselves **[V diff --git a/docs/firmware-parity.md b/docs/firmware-parity.md index 7ac7246..269a221 100644 --- a/docs/firmware-parity.md +++ b/docs/firmware-parity.md @@ -15,7 +15,7 @@ This is the P3.3 deliverable for the **firmware population** half of "Module loading & firmware infra" (TASKS.md). The **module-autoload** half (kmod/depmod/eudev/xz-compression) was already done — see the P3.3 (core) -commit and `configs/mister_de10nano_defconfig`'s `BR2_PACKAGE_HOST_KMOD_XZ` / +commit and `configs/fragments/de10nano-image.fragment`'s `BR2_PACKAGE_HOST_KMOD_XZ` / `BR2_PACKAGE_KMOD_TOOLS` lines. This document covers only `/lib/firmware`. **Target:** `docs/stock-inventory/firmware.md` — the authoritative 66-file @@ -61,7 +61,7 @@ Config.in reasoning: > The original P3.3 run measured 56 present / 10 missing. `brcm/BCM20702A1-0b05-17cb.hcd` > was subsequently sourced by `package/bcm20702-firmware` (P3.14, -> `BR2_PACKAGE_BCM20702_FIRMWARE=y` at `configs/mister_de10nano_defconfig:1202`; +> `BR2_PACKAGE_BCM20702_FIRMWARE=y` at `configs/fragments/de10nano-image.fragment`; > `scripts/ci-tests.sh` fails the build if it is absent from the image), which is what > moves present 56→57 and flagged 2→1. The obsolete count was independently a miscount: > that section lists **seven** files, not six. @@ -140,7 +140,7 @@ driver exists to use them (harmless unused bytes, not a gap). ## defconfig changes -`configs/mister_de10nano_defconfig`, added after the P3.2 block: +`configs/fragments/de10nano-image.fragment`, added after the P3.2 block: ``` BR2_PACKAGE_LINUX_FIRMWARE=y diff --git a/docs/init-parity.md b/docs/init-parity.md index c9be5da..1b58a93 100644 --- a/docs/init-parity.md +++ b/docs/init-parity.md @@ -22,10 +22,10 @@ read from `output/target/` **after** P2.1's full package-set build but **before* this task's overlay was wired in, so the diffs are genuinely against what Buildroot's packages install unprompted, not against a strawman. The overlay itself lives at `board/mister/de10nano/rootfs-overlay/` and is wired via `BR2_ROOTFS_OVERLAY` in -`configs/mister_de10nano_defconfig` (added by this task; previously unset for the +`configs/fragments/de10nano-image.fragment` (added by this task; previously unset for the full-rootfs build — only the initramfs defconfig had its own overlay). -Build verified: `make mister_de10nano_defconfig && make all` completed clean, +Build verified: `make de10nano-defconfig && make all` completed clean, `output/images/rootfs.tar` (180 MB) and `output/images/zImage_dtb` (8,771,237 bytes, `check-zimage-dtb.sh` all-pass) both produced. All claims below were checked against `output/images/rootfs.tar`, extracted fresh, with the actual commands and output @@ -172,7 +172,7 @@ mechanism that wasn't there. It was flagged here rather than silently left out, not affect SSH or networking (P2.3's hard requirements), which is why it wasn't a blocker. **This gap is now closed.** `BR2_PACKAGE_USBMOUNT=y` is set in -`configs/mister_de10nano_defconfig`, the stock-tuned `usbmount.conf` ships in the overlay, +`configs/fragments/de10nano-image.fragment`, the stock-tuned `usbmount.conf` ships in the overlay, and util-linux `mount` plus a `mount.ntfs -> ntfs-3g` helper make NTFS drives mount the way they do on stock. See **`docs/usb-automount-parity.md`** for the full picture. diff --git a/docs/kernel-recon/worker-instructions.md b/docs/kernel-recon/worker-instructions.md index d162231..d0d0620 100644 --- a/docs/kernel-recon/worker-instructions.md +++ b/docs/kernel-recon/worker-instructions.md @@ -5,7 +5,7 @@ vanilla kernel version we currently ship (plan: `MISTER-KERNEL-PATCH-RECON.md` **Analyze ONLY your assigned commit. Never group it with other commits** — grouping is the exact failure mode this project exists to fix. -> **Which vanilla version.** Ground on the version `configs/mister_de10nano_defconfig` pins, +> **Which vanilla version.** Ground on the version `configs/fragments/de10nano.fragment` pins, > not on a version quoted in a doc — the pin moves with stable bumps and the docs lag it. > The original campaign ran against `v6.18.38`; the 2026-07-24 fork-sync increment ran > against **`v6.18.39`** (`docs/kernel-recon/fork-sync-2026-07.md` §5). Check the pin, then @@ -29,7 +29,7 @@ If the diff is huge (thousands of lines, e.g. vendored drivers), do NOT read it | Fork repo | `/mnt/source/Linux-Kernel_MiSTer` (branch `MiSTer-v5.15`) | your commit and its neighbors | | Carried patches | `/mnt/source/Buildroot_MiSTer/board/mister/de10nano/linux-patches/*.patch` (25 files, `0001`–`0031` with gaps) | is this commit carried? grep for symbols/strings from your diff | | Our kernel config | `/mnt/source/Buildroot_MiSTer/board/mister/de10nano/linux.config` | kconfig reconciliation | -| Our Buildroot defconfig | `/mnt/source/Buildroot_MiSTer/configs/mister_de10nano_defconfig` | BR2 packages (some fork drivers now ship as out-of-tree kmod packages, e.g. xone, 8812au — grep `package/` and the defconfig) | +| Our Buildroot defconfig | `/mnt/source/Buildroot_MiSTer/configs/fragments/de10nano.fragment` | BR2 packages (some fork drivers now ship as out-of-tree kmod packages, e.g. xone, 8812au — grep `package/` and the defconfig) | | Stock kernel config | `/mnt/source/Buildroot_MiSTer/docs/stock-inventory/stock-linux.config` | what stock shipped | | Main_MiSTer userspace | `/mnt/source/Main_MiSTer` | userspace coupling: grep input event codes, ioctls, sysfs paths, /dev nodes | | Prior art (**may be wrong**) | `/mnt/source/Buildroot_MiSTer/docs/patch-provenance.md` | record what it claims, then re-derive INDEPENDENTLY | diff --git a/docs/logitech-pairing.md b/docs/logitech-pairing.md index 7099285..9af2bfb 100644 --- a/docs/logitech-pairing.md +++ b/docs/logitech-pairing.md @@ -294,7 +294,7 @@ glibc 2.43): * **`read-dev-usbmon` correctly not built**, and `ltunify --version` reports the pinned SHA rather than an empty string. -`make mister_de10nano_defconfig` against the external tree resolves +`make de10nano-defconfig` against the external tree resolves `BR2_PACKAGE_LTUNIFY=y`, leaves `BR2_PACKAGE_LIBEXECINFO` unselected (correct on a glibc toolchain), and `LTUNIFY_SOURCE` matches the filename on the `.hash` line character for character. diff --git a/docs/main-shared-libs.md b/docs/main-shared-libs.md index f66f182..493a478 100644 --- a/docs/main-shared-libs.md +++ b/docs/main-shared-libs.md @@ -11,7 +11,7 @@ consumer: which package provides what, under which SONAME/header-dir/pkg-config name, and how Main's vendored dirs map onto them. Three of the five packages are upstream Buildroot, enabled straight from -`configs/mister_de10nano_defconfig` (compression block); the other two are +`configs/fragments/de10nano-image.fragment` (compression block); the other two are authored in this tree under `package/` and sourced via the "Main_MiSTer shared libraries" menu in the top-level `Config.in`. diff --git a/docs/midi-mt32-parity.md b/docs/midi-mt32-parity.md index a2b9a35..93c19b7 100644 --- a/docs/midi-mt32-parity.md +++ b/docs/midi-mt32-parity.md @@ -204,7 +204,7 @@ BR2_PACKAGE_ALSA_UTILS_ASEQNET=y (`BAT`), `iecset`, `speaker-test` — are present in stock (`docs/stock-inventory/binaries-needed-full.txt`) but are general ALSA audio parity, not MIDI parity. **P3.15 — General ALSA userland parity** subsequently owned and closed this: -`configs/mister_de10nano_defconfig:422-431` now sets +`configs/fragments/de10nano-image.fragment` now sets `BR2_PACKAGE_ALSA_UTILS_{ALSACTL,ALSALOOP,ALSAMIXER,ALSATPLG,ALSAUCM,AMIXER,APLAY,BAT,IECSET,SPEAKER_TEST}=y` (`TASKS.md` P3.15, marked done). The general ALSA userland parity should pick this up. **A genuine gap in this Buildroot @@ -227,7 +227,7 @@ around (e.g. by hand-adding a custom install rule) for this task. - `package/midilink/{Config.in,midilink.mk,midilink.hash}` — new. - `Config.in` (repo root) — added a `"MIDI / MT-32 (P3.8)"` menu sourcing both new packages' `Config.in`. -- `configs/mister_de10nano_defconfig` — added `BR2_PACKAGE_MUNT=y`, +- `configs/fragments/de10nano-image.fragment` — added `BR2_PACKAGE_MUNT=y`, `BR2_PACKAGE_MIDILINK=y`, `BR2_PACKAGE_ALSA_UTILS=y` + six MIDI-specific `BR2_PACKAGE_ALSA_UTILS_*` suboptions, right after the existing FluidSynth block. diff --git a/docs/package-manifest.md b/docs/package-manifest.md index 1923446..9b52a04 100644 --- a/docs/package-manifest.md +++ b/docs/package-manifest.md @@ -775,7 +775,7 @@ surprise found while adding it):** | `exfatprogs` | `mkfs.exfat`, `fsck.exfat`, `exfatlabel`, `dump.exfat`, `exfat2img`, `tune.exfat` | no sub-options, no collision (BusyBox has no exFAT support at all) | | `ntfs-3g` `NTFSPROGS` sub-option | `mkfs.ntfs` (→ `mkntfs`), `ntfsfix`, + the rest of ntfsprogs | **found missing despite `BR2_PACKAGE_NTFS_3G` already being `=y`** since P2.1 — a real oversight, not a deliberate omission: the sub-option defaults to "n" with no dependency of its own (`package/ntfs-3g/Config.in`), and without it `ntfs-3g.mk` passes `--disable-ntfsprogs`. Fixed in place next to the existing `BR2_PACKAGE_NTFS_3G=y` line | | `tmux` | `tmux` | chosen over `screen` (§5) — not both. Needs `BR2_USE_WCHAR`+`BR2_ENABLE_LOCALE` (both true); selects `LIBEVENT`+`NCURSES`, both already on | -| `strace` | `strace` | **BEYOND STOCK, not a gap closed** — `find work/imgroot -name strace` returns nothing. Already enabled *temporarily* by the `DEBUG TOOLING` block (`docs/debug-tooling.md`); this promotes it to a *permanent* part of the package set so it keeps shipping once that block is eventually deleted (and is what `docs/package-manifest.md` §5's `ltrace` row leans on when it rejects ltrace). Set in both places deliberately (harmless, same value — `make mister_de10nano_defconfig` prints a benign "override: reassigning to symbol BR2_PACKAGE_STRACE") | +| `strace` | `strace` | **BEYOND STOCK, not a gap closed** — `find work/imgroot -name strace` returns nothing. Already enabled *temporarily* by the `DEBUG TOOLING` block (`docs/debug-tooling.md`); this promotes it to a *permanent* part of the package set so it keeps shipping once that block is eventually deleted (and is what `docs/package-manifest.md` §5's `ltrace` row leans on when it rejects ltrace). Set in both places deliberately (harmless, same value — `make de10nano-defconfig` prints a benign "override: reassigning to symbol BR2_PACKAGE_STRACE") | | `lsof` | `usr/bin/lsof` | **An UPGRADE over stock, not parity restoration** — stock's `usr/bin/lsof` is a symlink → `../../bin/busybox`, so stock's provider is the BusyBox applet, and choosing the real lsof is a deliberate divergence (worth it: the applet has no network-socket, NFS, `-p` or `-i` support). Needs `BUSYBOX_SHOW_OTHERS`, already on (for `i2c-tools`); **collides with BusyBox's own `lsof` applet**, already on — disabled in `busybox.fragment` | | `tcpdump` | `tcpdump` | **BEYOND STOCK, not a gap closed** — `find work/imgroot -name tcpdump` returns nothing. Added anyway for on-device WiFi/network debugging (P3.4 territory). Selects `LIBPCAP` automatically; `TCPDUMP_SMB` ("possibly-buggy" per its own Config.in) deliberately left off | | `iperf3` | `iperf3` | **BEYOND STOCK, not a gap closed** — `find work/imgroot -name 'iperf*'` returns nothing (no iperf2 either). Added anyway: throughput measurement is how a WiFi driver change gets judged. Needs `BR2_TOOLCHAIN_HAS_ATOMIC`+`_THREADS`, both true | @@ -1100,7 +1100,7 @@ BR2_PACKAGE_BUSYBOX=y # 1.38.0 in this Buildroot (busybo ``` > ⚠ **This list is deliberately not identical to the live defconfig.** -> `configs/mister_de10nano_defconfig` currently also carries a bannered +> `configs/fragments/de10nano-image.fragment` currently also carries a bannered > `DEBUG TOOLING` block — gdb (+ gdbserver + full debugger), strace, > perf and rt-tests — which is **temporary and out of scope for this manifest**: it is > not stock parity and never claimed to be. Do **not** reconcile the two by @@ -1108,7 +1108,7 @@ BR2_PACKAGE_BUSYBOX=y # 1.38.0 in this Buildroot (busybo > block. Two further, *intentional* divergences: (1) §6's `BR2_PACKAGE_PYTHON3=y` line > predates P3.9 — the live defconfig also sets > `BR2_PACKAGE_PYTHON3_{SSL,ZLIB,BZIP2,XZ,PYEXPAT,READLINE,CURSES}=y` -> (`configs/mister_de10nano_defconfig:705-711`), and `_SSL`/`_ZLIB` are **hard blockers** +> (`configs/fragments/de10nano-image.fragment`), and `_SSL`/`_ZLIB` are **hard blockers** > for Downloader_MiSTer (`docs/python-compat.md`), so do not paste this Python block > without them. (2) The ~21 utility packages added by T3/T5 are documented in §4c above, > not repeated here. §6 remains the P2.1 stock-parity paste list, not a defconfig mirror. diff --git a/docs/python-compat.md b/docs/python-compat.md index a4d9057..b5a6043 100644 --- a/docs/python-compat.md +++ b/docs/python-compat.md @@ -6,7 +6,7 @@ > investigation exactly as it was run, against a build whose Python had almost every > optional C-extension deselected. Its two blocking recommendations were applied in the > same commit that added this document (`a549bd0`): -> `configs/mister_de10nano_defconfig:705-711` now sets +> `configs/fragments/de10nano-image.fragment` now sets > `BR2_PACKAGE_PYTHON3_{SSL,ZLIB,BZIP2,XZ,PYEXPAT,READLINE,CURSES}=y`, and > `scripts/ci-tests.sh`'s "P3.9 — Python & Downloader ABI gate" imports all seven under > `qemu-arm` on every run. **The image no longer ships a Python that cannot `import @@ -388,7 +388,7 @@ I did not make any of these changes — per the task constraints, this is a repo orchestrator to apply and rebuild. **Applied 2026-07-13 (`a549bd0`).** Both MUSTs plus `BZIP2`/`XZ`/`PYEXPAT`/`READLINE`/ -`CURSES` are live at `configs/mister_de10nano_defconfig:705-711` (a `python3-dirclean` +`CURSES` are live at `configs/fragments/de10nano-image.fragment` (a `python3-dirclean` was required to force the rebuild). `SQLITE` and `DECIMAL` were deliberately declined — stock's Python 3.9 shipped neither. diff --git a/docs/renovate.md b/docs/renovate.md index 8e791fa..3356749 100644 --- a/docs/renovate.md +++ b/docs/renovate.md @@ -47,8 +47,8 @@ for the specific pieces most likely to need a fix on the first live run. | Pin | File(s) | Mechanism | Hash companion | |---|---|---|---| -| Buildroot release | `Makefile` (`BUILDROOT_VERSION`) | `customManagers` regex, `github-tags` datasource, `allowedVersions` locked to `2026.05.x` | `BUILDROOT_SHA256` — **auto-refreshed since 2026-08-24** by `renovate-hash-sync.yml` (`hash-sync-buildroot.sh`, case 6) from buildroot.org's GPG-signed `.sign` manifest; **manual** before that date (this row used to say so), and the `make buildroot-showsig` transcription remains the fallback — see below | -| Kernel (6.18.y longterm) | **both** `configs/mister_de10nano_defconfig` *and* `configs/mister_kernel_defconfig` (`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`) | one `customManagers` regex listing both files + a `customDatasources` entry over `kernel.org/releases.json`, filtered to `moniker=longterm` and the `6.18.` prefix; `allowedVersions` locked to `6.18.y` as defense in depth. Same `depName` for both files, so Renovate emits **one PR touching both** | `board/mister/de10nano/patches/linux/linux.hash` — auto-refreshed by `renovate-hash-sync.yml` from kernel.org's signed `sha256sums.asc` | +| Buildroot release | `Makefile` (`BUILDROOT_VERSION`) | `customManagers` regex, `github-tags` datasource, `allowedVersions` locked to `2026.05.x` | `BUILDROOT_SHA256` — **auto-refreshed since 2026-08-24** by `renovate-hash-sync.yml` (`hash-sync-buildroot.sh`, case 6) from buildroot.org's GPG-signed `.sign` manifest; **manual** before that date (this row used to say so), and the `make buildroot-showsig` transcription remains the fallback — see below. **Since 2026-09-02 a second companion:** `configs/fragments/golden.sha256` — the resolved-config hashes `scripts/check-config-fragments.sh` asserts per Buildroot version — is recorded for the new version by case 8 (`hash-sync-golden.sh`) in the same PR; if that case skips, `lint-config` only *warns* on the missing lines and the manual step is `scripts/check-config-fragments.sh --update-golden` + commit | +| Kernel (6.18.y longterm) | `configs/fragments/de10nano.fragment` (`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`) — the ONE file both DE10 stacks share since the 2026-09 fragment split | one `customManagers` regex on that file + a `customDatasources` entry over `kernel.org/releases.json`, filtered to `moniker=longterm` and the `6.18.` prefix; `allowedVersions` locked to `6.18.y` as defense in depth. Same `depName` for both files, so Renovate emits **one PR touching both** | `board/mister/de10nano/patches/linux/linux.hash` — auto-refreshed by `renovate-hash-sync.yml` from kernel.org's signed `sha256sums.asc` | | Kernel (RT/beta, the **7.2 line**) | `configs/mister_rt.fragment` (same symbol, different line) | a **separate** `customManagers` regex + its own `kernelStable72` datasource; `allowedVersions` locked to `/^7\.2(\.\d+)?$/`. Labeled `rt-kernel-pin` + `needs-manual-version-check`. **Rewritten 2026-08-17** when 7.2 released: the datasource was `kernelMainline` (`moniker=mainline`) and the depName `kernel-mainline-rt`. Both were right while 7.2 was in `-rc` and wrong the moment it shipped — mainline moves to 7.3-rc1 about two weeks later, so the old filter would have dragged the variant straight back off the line it had just reached. The filter is now **moniker-agnostic and version-scoped**, because the 7.2 line changes moniker underneath us: today 7.2 is the `mainline` entry and no 7.2.y stable release exists yet, and once 7.2.1 ships it becomes the `stable` entry instead. The matchString accepts two- *and* three-component values for the same reason | `board/mister/de10nano/patches/linux/linux.hash` — **auto-refreshed since 2026-08-17** by `renovate-hash-sync.yml` (`hash-sync-kernel.sh --pin=rt`) from kernel.org's signed `sha256sums.asc`, same as the 6.18 pin. This row says the opposite of what it said before that date, and the reason is that the pin changed sides, not that the rule loosened: an `-rc` is fetched as a cgit `.tar.gz` snapshot upstream signs in no way, so its hash could only be hand-written TOFU; a 7.2.y release is an ordinary `.tar.xz` covered by the signed manifest. The script still **refuses** any `-rc` for either pin, leaving the build to fail closed | | 10 driver commit-SHA pins | `package/{rtl8812au,rtl8814au-morrownr,rtl8821au-morrownr,rtl8821cu-morrownr,rtl8188fu,rtl8188eu-aircrack-ng,rtl88x2bu,rtl8852cu-morrownr,xone,midilink}/*.mk` | `customManagers` regex per package, `git-refs` datasource tracking the upstream default branch's HEAD via `currentDigest` | matching `.hash` file — auto-refreshed by `renovate-hash-sync.yml` | | munt tag pin | `package/munt/munt.mk` | `github-tags` datasource, custom `regex:` versioning for the `munt_MAJOR_MINOR_PATCH` tag scheme | `package/munt/munt.hash` — auto-refreshed | @@ -112,16 +112,19 @@ hash-sync script — kept apart there by major-series line scoping rather than b being different managers; see [`docs/ci.md#renovate-hash-sync-rt-line-clobber`](ci.md#renovate-hash-sync-rt-line-clobber). -Conversely, the two 6.18 files are deliberately in the **same** manager: -`mister_kernel_defconfig` copies the main defconfig's kernel/toolchain stanza -(see its header and `scripts/check-kernel-defconfig-sync.sh`), so they must move -together. Verified against a real Renovate run with both files held back: two -extracted upgrades, exactly **one** branch -(`renovate/kernel-longterm-6.18-6.x`) covering both. - -That lockstep check is what caught the 6.18.38 → 6.18.39 bump landing in only -one of the two files, back when the manager listed just -`mister_de10nano_defconfig`. +Conversely, the 6.18 pin now lives in exactly **one** file, +`configs/fragments/de10nano.fragment`, which both the image stack and the +kernel-only stack include (`configs/fragments/stacks.mk`, +`docs/buildroot-config.md` §1) — so one manager, one file, one branch. Before +the 2026-09 fragment split there were two files (`mister_de10nano_defconfig` +and its hand-mirrored copy `mister_kernel_defconfig`) deliberately in the same +manager so they moved together; a real Renovate run with both held back gave +two extracted upgrades and exactly one branch +(`renovate/kernel-longterm-6.18-6.x`). That lockstep check is what caught the +6.18.38 → 6.18.39 bump landing in only one of the two files, back when the +manager listed just `mister_de10nano_defconfig`; the failure mode is gone by +construction now, and `scripts/check-kernel-defconfig-sync.sh` guards the +construction instead. ### Why `bcm20702-firmware` is a commit pin, not a tag pin @@ -394,6 +397,31 @@ docs/ci.md#renovate-hash-sync-outcomes-gate. **A red PR here is the safe failure mode, not a bug**: it is strictly better than a green PR that quietly ships a wrong/unverified hash. +7. **azcopy's vendored tarball hash** — see the table above. + +8. **The golden config hashes** (`configs/fragments/golden.sha256`) — + **added 2026-09-02** with the fragment split (`docs/buildroot-config.md` + §11). `scripts/check-config-fragments.sh` pins, per `BUILDROOT_VERSION`, + the sha256 of each fragment stack's normalised resolved `.config`; a + Buildroot bump changes Kconfig defaults and is *expected* to move every + one of them. So that a Renovate Buildroot-bump PR still gets its build, + `lint-config` only **warns** when the pinned version has no golden lines + at all (a mismatch against a *recorded* line is still a failure — that is + drift), and this case, `scripts/hash-sync-golden.sh`, runs the **target + branch's** `check-config-fragments.sh --update-golden` through its + Makefile (`make buildroot-unpack` fetches and verifies the new tarball + with the hash case 6 just wrote) and commits the new lines alongside. It + never rewrites lines that already exist for the pinned version, and it is + a no-op on kernel bumps (the kernel-version symbols are excluded from the + normalisation). If it skips (unpack failed, the check found a real + fragment problem), the PR is not red for it — the manual step is: + + ``` + scripts/check-config-fragments.sh --update-golden --keep + # read output-config-check//normalised.config against the previous + # good run, then commit configs/fragments/golden.sha256 saying what changed + ``` + **What it deliberately does NOT fix:** any `-rc` kernel hash (kernel.org signs no manifest for a cgit snapshot — hand-written TOFU only, and `hash-sync-kernel.sh` refuses one at runtime) and `azcopy` (a golang-package: @@ -498,7 +526,10 @@ moved with it. Note what the line number does *not* tell you: **2026.02 was never an LTS.** Buildroot's LTS releases are the February ones on a two-year cadence — **2025.02** was the last, **2027.02** is expected to be the next — so `2026.05` is an interim non-LTS line, chosen for currency, not longevity. When -the next line bump comes, update three things together: `BUILDROOT_VERSION` and +the next line bump comes, update four things together: `BUILDROOT_VERSION` and `BUILDROOT_SHA256` in the `Makefile` (the hash from `make buildroot-showsig`, never from a tarball you just downloaded), this -`allowedVersions` regex, and the row in the table above. +`allowedVersions` regex, the row in the table above, and the golden config +hashes (`scripts/check-config-fragments.sh --update-golden`, case 8 above — +read the normalised-config diff before committing; a line bump is exactly +when defaults move). diff --git a/docs/reproducibility.md b/docs/reproducibility.md index 499035f..a079d9c 100644 --- a/docs/reproducibility.md +++ b/docs/reproducibility.md @@ -26,7 +26,7 @@ concrete reasons, not reproducibility as an abstract virtue: ## How this is delivered -Four mechanisms combine, all landed in P2.5 (`configs/mister_de10nano_defconfig`): +Four mechanisms combine, all landed in P2.5 (`configs/fragments/de10nano-image.fragment`): 1. **`BR2_REPRODUCIBLE=y`.** Buildroot's own reproducible-build mode. Exports `SOURCE_DATE_EPOCH` pinned to Buildroot's own pinned-tree last-commit date (constant @@ -64,7 +64,7 @@ Four mechanisms combine, all landed in P2.5 (`configs/mister_de10nano_defconfig` 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/mister_de10nano_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` step between "clone this commit" and "build this image" — the defconfig fully determines the build. @@ -139,7 +139,7 @@ this section should be updated with: ## Related -- `configs/mister_de10nano_defconfig` — the `BR2_REPRODUCIBLE` + ext4-options block +- `configs/fragments/de10nano-image.fragment` — the `BR2_REPRODUCIBLE` + ext4-options block (search `BR2_REPRODUCIBLE` / `BR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS`). - `scripts/check-linux-img.sh` — asserts the pinned size/label/UUID/hash-seed/feature-set contract (and the ADR 0015 no-baked-keys invariant) on every build; runs automatically diff --git a/docs/rt-beta-kernel.md b/docs/rt-beta-kernel.md index 8bda237..05db0f7 100644 --- a/docs/rt-beta-kernel.md +++ b/docs/rt-beta-kernel.md @@ -63,14 +63,16 @@ alongside the main 6.18 image and selected on-device (§5). The main 6.18 image build is untouched. Since ADR 0021's **2026-07-18 amendment** the variant is a **kernel-only** Buildroot build (no userland): the -shared base `configs/mister_kernel_defconfig` — the main defconfig's toolchain -and kernel stanzas mirrored, `BR2_TARGET_ROOTFS_TAR` only, no packages — with -the per-variant fragment layered on at build time. +shared base is the `de10nano-kernel` fragment stack (`common` + `de10nano` + +`kernel-only`, `configs/fragments/stacks.mk`) — the SAME toolchain and kernel +fragments the image is built from, `BR2_TARGET_ROOTFS_TAR` only, no packages — +with the per-variant fragment layered on at build time +(`docs/buildroot-config.md` §4). | File | Role | |---|---| -| `configs/mister_kernel_defconfig` | The kernel-only base, shared by every kernel variant. Its toolchain/kernel stanzas are a **copy** of `mister_de10nano_defconfig`'s, held in lockstep by `scripts/check-kernel-defconfig-sync.sh` (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 (kernel version → the 7.2 line, currently **`7.2`** — a plain mainline release since 2026-08-17, so Buildroot's `-rc` cgit-snapshot path in `linux/linux.mk` no longer applies and it fetches the ordinary `linux-7.2.tar.xz` from the `v7.x` mirror directory; beta patch dir; kernel-config fragment). Merged onto `mister_kernel_defconfig` via `merge_config.sh`. | +| `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. | | `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. | diff --git a/docs/rtc-parity.md b/docs/rtc-parity.md index c4f8db5..e785ba2 100644 --- a/docs/rtc-parity.md +++ b/docs/rtc-parity.md @@ -164,7 +164,7 @@ applet disabled — matching stock, which shipped util-linux's `hwclock` too. It not wired into boot. See `docs/util-linux-parity.md`.) **No Buildroot `defconfig` changes needed for this task.** The one pre-existing RTC-related -line needed no change: `configs/mister_de10nano_defconfig:647` — +line needed no change: `configs/fragments/de10nano-image.fragment` — `BR2_PACKAGE_I2C_TOOLS=y # for the i2c-gpio RTC add-on, P3.11` (installs `i2cdetect`/ `i2cget`/`i2cset` for bench debugging of the bit-banged bus). diff --git a/docs/samba-parity.md b/docs/samba-parity.md index 1b6692d..5ddc528 100644 --- a/docs/samba-parity.md +++ b/docs/samba-parity.md @@ -48,7 +48,7 @@ build's package install doesn't pre-bake either — fixed in `etc/fstab` - Our Samba version: `SAMBA4_VERSION = 4.23.8` in `work/buildroot/package/samba4/samba4.mk`, built with `--enable-fhs --localstatedir=/var` and no AD DC / ADS / smbtorture - (`configs/mister_de10nano_defconfig`'s existing `BR2_PACKAGE_SAMBA4=y` + (`configs/fragments/de10nano-image.fragment`'s existing `BR2_PACKAGE_SAMBA4=y` block, unchanged by this task). - Built-image ground truth: `work/p3-rootfs/` (an extracted rootfs from a prior build of this repo's *current* overlay+defconfig — verified @@ -235,7 +235,7 @@ directory by then. | `docs/samba-parity.md` | this file | **Not changed:** `overlay/etc/samba/smb.conf`, `etc/init.d/S91smb`, -`configs/mister_de10nano_defconfig` — see §1/§3 for why each is already +`configs/fragments/de10nano-image.fragment` — see §1/§3 for why each is already correct as committed. ## 5. What this task could not verify (needs BUILD / hardware LAN, P3.13) diff --git a/docs/size-budget.md b/docs/size-budget.md index a879fea..1a0adfc 100644 --- a/docs/size-budget.md +++ b/docs/size-budget.md @@ -88,7 +88,7 @@ headroom (60.6% free vs. the 15% floor). ## azcopy (2026-08-17) — packaged, deliberately NOT enabled `package/azcopy` would be the largest single thing added to this image since samba4, -and its size is exactly why `configs/mister_de10nano_defconfig` leaves it switched +and its size is exactly why `configs/fragments/de10nano-image.fragment` leaves it switched off. **None of the figures below are in the shipped image today** — they are what enabling that one line would cost. The full accounting — how the binary was measured, what the strip step does to a Go binary, the build-time diff --git a/docs/ssh-ftp-parity.md b/docs/ssh-ftp-parity.md index 46d2156..5bb0f4d 100644 --- a/docs/ssh-ftp-parity.md +++ b/docs/ssh-ftp-parity.md @@ -211,7 +211,7 @@ No changes made. Notable existing content, confirmed intentional/stock-matching: Module set also matches stock: our defconfig sets only `BR2_PACKAGE_PROFTPD=y`, no `BR2_PACKAGE_PROFTPD_MOD_*` suboption (confirmed: -`grep PROFTPD configs/mister_de10nano_defconfig` → exactly one line). Stock's +`grep PROFTPD configs/fragments/de10nano-image.fragment` → exactly one line). Stock's own `usr/sbin/proftpd` dependency list (`docs/stock-inventory/binaries-needed-full.txt`: `libc.so.6,libcrypt.so.1,libdl.so.2,libpam.so.0` — no libssl, no sqlite, no pcre2) is consistent with the same bare/no-submodule build. @@ -221,7 +221,7 @@ pcre2) is consistent with the same bare/no-submodule build. ### 3.1 Root password — already correctly handled; initial "fix" here was wrong and has been reverted **Corrected after a false start, recorded here so it isn't retried.** -`configs/mister_de10nano_defconfig` has `BR2_TARGET_GENERIC_ROOT_PASSWD=""`. +`configs/fragments/de10nano-image.fragment` has `BR2_TARGET_GENERIC_ROOT_PASSWD=""`. Read in isolation, and per Buildroot's own `system/Config.in` ("If set to empty (the default), then no root password will be set, and root will need no password to log in"), this looks exactly like the bug it would be if diff --git a/docs/stock-reconciliation.md b/docs/stock-reconciliation.md index 0f1e872..070aed2 100644 --- a/docs/stock-reconciliation.md +++ b/docs/stock-reconciliation.md @@ -228,7 +228,7 @@ marked CLOSED here. | `usr/bin/vgmplay`, `usr/bin/VGMPlay.ini` | **C — DECLINED, documented** | Real gap, deliberate decline. VGMPlay (vgmrips/vgmplay, GPL) has no Buildroot package; writing one here could not be build-verified (T3 runs under a no-build constraint), and an untested C package is a worse outcome than an honest absence — it risks breaking `make all` for a niche feature (VGM chiptune playback inside mc). Cost of absence: mc's vgm handler and `m3u_play`'s vgm branch print `not found`; nothing else references it. Revisit as its own small task if VGM playback is ever asked for: pin a release tarball, `Makefile`-type package, install `VGMPlay.ini` beside the binary (it looks for its ini next to `argv[0]`). | | `usr/bin/memtool` | **A — CLOSED** | Not a sourceless blob after all: strings on the stock ELF are pengutronix memtool's exact usage text, and `addon.tar`'s `usr/bin/md`/`mw` are symlinks → `memtool` (argv[0] dispatch — `memtool.c:475` in the pinned 2018.03.0 tarball switches on `basename(argv[0])`). `BR2_PACKAGE_MEMTOOL=y` (Buildroot's own package, same upstream, 2018.03.0 — upstream's last release, confirmed against pengutronix's release directory). The package installs only the `memtool` binary, so stock's `md`/`mw` symlinks are reproduced in the overlay. | | `usr/bin/fpga` | **D — INFEASIBLE, precisely bounded** | Stripped ARM ELF; **no public source found**: GitHub code search for its distinctive strings finds only `Main_MiSTer/fpga_io.cpp` (which shares the literal `"FPGA: Unaligned data, realign to 32bit boundary."`, `fpga_io.cpp:352`) and u-boot's `drivers/fpga/socfpga.c`; no MiSTer-devel repo builds a standalone `fpga` binary. It is a dev-era peek/poke + RBF loader (`Usage(1): %s { address } [ data ]`, `Usage(2): %s { rbf_file }`, mmaps `/dev/mem`). Shipping the blob violates rule G6; recreating it from `fpga_io.cpp` would be new, untestable systems code. **Intent is covered**: core loading = `echo load_core > /dev/MiSTer_cmd` (Main, `input.cpp:6238-6242` — and mc's Enter-on-`.rbf` now does exactly that); peek/poke = `memtool md`/`mw` (above) or busybox `devmem`. Same documentation standard as `docs/firmware-parity.md`'s no-upstream-source firmware. | -| `usr/bin/rz`, `usr/bin/sz` | **A — CLOSED (T5, 2026-07-27)** | `BR2_PACKAGE_LRZSZ=y` — confirmed resolved `=y` in `output/.config` after `make mister_de10nano_defconfig` (`lrzsz.mk` installs exactly `$(TARGET_DIR)/usr/bin/rz` and `.../sz`, matching stock's paths exactly, plus **six** bonus compat symlinks — `lrz`/`rb`/`rx` → `rz` and `lsz`/`sb`/`sx` → `sz`, `lrzsz.mk:21-26` — stock's `addon.tar` does not have). | +| `usr/bin/rz`, `usr/bin/sz` | **A — CLOSED (T5, 2026-07-27)** | `BR2_PACKAGE_LRZSZ=y` — confirmed resolved `=y` in `output/.config` after `make de10nano-defconfig` (`lrzsz.mk` installs exactly `$(TARGET_DIR)/usr/bin/rz` and `.../sz`, matching stock's paths exactly, plus **six** bonus compat symlinks — `lrz`/`rb`/`rx` → `rz` and `lsz`/`sb`/`sx` → `sz`, `lrzsz.mk:21-26` — stock's `addon.tar` does not have). | | `usr/lib/libfluidsynth.so.3.0.0` | **covered (better version)** | The fluidsynth package ships `libfluidsynth.so.3.3.7` + the `libfluidsynth.so.3` SONAME link (verified in target) — same SONAME stock's addon symlinks resolve to, newer revision. Nothing links the full `3.0.0` filename. | | `usr/sbin/fluidsynth` (path diff) | **CLOSED (compat symlink)** | Stock installs the ELF at `/usr/sbin/`, our package at `/usr/bin/`. No shipped caller uses the absolute stock path — midilink builds the command `fluidsynth …` via PATH (`output/build/midilink-*/main.c:154`), the timidity wrapper and `uartmode`'s `killall` go by name, Main never references it — but third-party user scripts may hardcode it, so the overlay adds `usr/sbin/fluidsynth -> /usr/bin/fluidsynth` (same idiom as the existing `usr/sbin/mount.ntfs` link) and the difference is gone outright. | | `etc/asound.conf` | **B — CLOSED** | Vendored byte-identical. This is not optional polish: it routes ALSA's `!default` pcm through a 48 kHz S16_LE `file` plugin writing raw into `/dev/MrAudio`, the in-kernel MiSTer SPI audio ring (`CONFIG_SND_MISTER_AUDIO=y`; device created at `sound/drivers/MiSTer-audio-spi.c:231`) that Main/core mix into HDMI/analog out. **Not** because `hw:0` is missing — `hw:0` exists and *must*: `MiSTer-audio-spi.c` is a chardev SPI driver with no ALSA card at all, so the only card is the patched `snd-dummy` (`CONFIG_SND_DUMMY=y`, `board/mister/de10nano/linux.config:391`, built in; `enable[0]=1` at `sound/drivers/dummy.c:51` registers it as card 0), and this file's own innermost slave is `type hw; card 0` — the `type file` plugin *duplicates* the stream, so card 0 has to accept S16_LE/48 kHz/2ch or the default pcm fails to open outright ([`docs/abi-contract.md`](abi-contract.md) §8.2(a), §8.2(c) — "`CONFIG_SND_DUMMY=y` … *and it must be card 0*", MUST). The real cost of omitting `asound.conf` is that ALSA's default resolves to that dummy card, which discards the samples: the system is **silently mute** (`docs/phase0-review.md:128`, "omit it and the system is silent"). No package installs `/etc/asound.conf` (verified in target), so no shadowing. | @@ -306,7 +306,7 @@ Verified present in the built image: `usr/sbin/ifup` (72536-byte ARM ELF) and `output/target/sbin` is itself a symlink to `usr/sbin` (`BR2_ROOTFS_MERGED_USR=y`), so stock's `/sbin/…` spellings resolve to those same entries. `usr/sbin/iw` (264720-byte ARM ELF, `BR2_PACKAGE_IW=y` at -`configs/mister_de10nano_defconfig:698`). Not yet tested on real hardware — +`configs/fragments/de10nano-image.fragment`). Not yet tested on real hardware — see `docs/wifi-parity.md` §9's checklist. The former "**one path difference**" (stock `/usr/sbin/fluidsynth` vs our diff --git a/docs/uboot-mainline-port.md b/docs/uboot-mainline-port.md index db7775c..df30115 100644 --- a/docs/uboot-mainline-port.md +++ b/docs/uboot-mainline-port.md @@ -19,7 +19,7 @@ identically to the stock 2017.03 fork as the evidence allows, as a **build artif (515,141 B, sha256 `e2d46cf9…62ba64`), fetched by hash. ADR 0017 §Decision-5 stands. * `sdcard.img` keeps embedding that same stock blob. `scripts/mk-sdcard.sh` and `scripts/check-sdcard.sh` are untouched. -* `configs/mister_de10nano_defconfig` gains **no** `BR2_TARGET_UBOOT*` line. +* `configs/fragments/de10nano-image.fragment` gains **no** `BR2_TARGET_UBOOT*` line. * **No artifact this plan produces may ever be named `uboot.img`.** `updateboot` `dd`s `/media/fat/linux/uboot.img` over the `0xA2` partition on every Linux update with no version check, no hash check and no opt-out (boot-chain §5). The build output is diff --git a/docs/usb-automount-parity.md b/docs/usb-automount-parity.md index 7926b07..70e13c5 100644 --- a/docs/usb-automount-parity.md +++ b/docs/usb-automount-parity.md @@ -94,7 +94,7 @@ and are passed straight to ntfs-3g, same as on stock. ## Verification -- **Config resolves.** `make mister_de10nano_defconfig && make olddefconfig` (kconfig +- **Config resolves.** `make de10nano-defconfig && make olddefconfig` (kconfig only, no compile) produces an `output/.config` with `BR2_PACKAGE_USBMOUNT=y` + the auto-`select`ed `BR2_PACKAGE_LOCKFILE_PROGS=y`/`BR2_PACKAGE_LIBLOCKFILE=y`, and all the util-linux program toggles (`…_MOUNT`, `…_BINARIES`, `…_AGETTY`, …) — i.e. the diff --git a/docs/user/faq.md b/docs/user/faq.md index cc8294c..b2afca2 100644 --- a/docs/user/faq.md +++ b/docs/user/faq.md @@ -19,7 +19,7 @@ not yet been exercised on real hardware. Treat anything not listed above as unve practice until proven otherwise on your own hardware. The kernel is pinned to a specific release on the **6.18 LTS** line (which release moves -with upstream stable — `configs/mister_de10nano_defconfig` is the pin), and the 6.18 line +with upstream stable — `configs/fragments/de10nano.fragment` is the pin), and the 6.18 line **has booted on real hardware** — from the CI-built artifact rather than a local build, with every out-of-tree module present, Bluetooth firmware loading, and no kernel BUG/Oops/panic. WiFi is confirmed too: the RTL8822BU auto-connects at boot to a diff --git a/docs/util-linux-parity.md b/docs/util-linux-parity.md index 70f0abe..c9845af 100644 --- a/docs/util-linux-parity.md +++ b/docs/util-linux-parity.md @@ -30,7 +30,7 @@ no new libraries. ## What is enabled -In `configs/mister_de10nano_defconfig`, next to the existing util-linux library +In `configs/fragments/de10nano-image.fragment`, next to the existing util-linux library selections: | Symbol | Programs | @@ -123,7 +123,7 @@ binary used for manual/debug `hwclock` calls. ## Verification -- **kconfig resolves cleanly.** `make mister_de10nano_defconfig && make olddefconfig` +- **kconfig resolves cleanly.** `make de10nano-defconfig && make olddefconfig` (no compile) lands all 16 enabled util-linux program toggles in `output/.config` with nothing silently dropped; the 25 BusyBox `# CONFIG_… is not set` fragment lines all target symbols confirmed present in the built BusyBox `.config` (so none is a diff --git a/docs/version-delta.md b/docs/version-delta.md index ca62b5a..f3d2eca 100644 --- a/docs/version-delta.md +++ b/docs/version-delta.md @@ -19,14 +19,14 @@ security-update path), and it belongs in the release notes. ## The stack > **Ours-side figures last re-read off the built tree on 2026-07-22** (kernel from -> `configs/mister_de10nano_defconfig`; package versions from `output/build/`). They move +> `configs/fragments/de10nano.fragment`; package versions from `output/build/`). They move > whenever Renovate lands a bump — when in doubt, the defconfig and `Makefile` pins are > the ground truth and this table is a summary of them. | Component | Stock (2021.02.4) | Ours (2026.05.2) | Note | |---|---|---|---| | Buildroot | **2021.02.4** | **2026.05.2** | ~5 years of the whole distro | -| Linux kernel | **5.15.1** (forked Nov 2021, **never merged a single 5.15.y**) | **6.18** LTS — the `.y` moves with upstream stable, so the pin (`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, `configs/mister_de10nano_defconfig`) is the only place it is written down; hardware-validated at 6.18.33, 6.18.38 and 6.18.41 | on a stable `.y` line with security backports | +| Linux kernel | **5.15.1** (forked Nov 2021, **never merged a single 5.15.y**) | **6.18** LTS — the `.y` moves with upstream stable, so the pin (`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, `configs/fragments/de10nano.fragment`) is the only place it is written down; hardware-validated at 6.18.33, 6.18.38 and 6.18.41 | on a stable `.y` line with security backports | | glibc | **2.31** | **2.43** | backward-compatible; every stock binary still runs (proven on hardware) | | gcc (toolchain) | 10.x era | **14.4.0** | | diff --git a/docs/wifi-parity.md b/docs/wifi-parity.md index ef8439b..ac584f1 100644 --- a/docs/wifi-parity.md +++ b/docs/wifi-parity.md @@ -157,7 +157,7 @@ Everything below is cited to the actual fetched script, | `udhcpc`/direct `dhcpcd` invocation | v1.x: not called. **v2.3.0 calls all three in order** — `dhcpcd -n` (`:2021`), `udhcpc -n -q -i` (`:2024`), `dhclient` (`:2027`) — each under a timeout, taking the first that succeeds | — | — | **No gap** — we ship `dhcpcd` and `udhcpc`, so the chain succeeds at step 1 or 2 and never reaches `dhclient` (which we do not ship, deliberately: it is ISC's client and adding it to satisfy an unreachable third fallback would be dead weight). The global `S41dhcpcd` daemon still handles the boot path as before. (This row previously read "not called anywhere in the script".) | Three genuinely new Buildroot packages were needed -(`configs/mister_de10nano_defconfig:774-783`, new "P3.4: WiFi userland +(`configs/fragments/de10nano-image.fragment`, new "P3.4: WiFi userland parity (`wifi.sh` contract)" section): `BR2_PACKAGE_BASH`, `BR2_PACKAGE_DIALOG`, `BR2_PACKAGE_WIRELESS_TOOLS` (+`_IWCONFIG`, its own default-y sub-option, listed for clarity per this file's existing @@ -237,7 +237,7 @@ kernel from this era; no separate check needed. ## 4. Files touched by this task -- **Edited** `configs/mister_de10nano_defconfig` — added the "P3.4: WiFi +- **Edited** `configs/fragments/de10nano-image.fragment` — added the "P3.4: WiFi userland parity (`wifi.sh` contract)" section (lines 774-783): `BR2_PACKAGE_BASH=y`, `BR2_PACKAGE_DIALOG=y`, `BR2_PACKAGE_WIRELESS_TOOLS=y` (+`_IWCONFIG=y`), `BR2_PACKAGE_IPROUTE2=y`. No other defconfig lines @@ -944,7 +944,7 @@ on `argv[0]`). `output/target/sbin` is itself a symlink to `usr/sbin` (`BR2_ROOTFS_MERGED_USR=y`), so stock's `/sbin/ifup` and `/sbin/ifdown` spellings resolve to those same two entries. `output/target/usr/sbin/iw` — a 264720-byte ARM ELF, from `BR2_PACKAGE_IW=y` at -`configs/mister_de10nano_defconfig:782`; without it the `pre-up` loop's +`configs/fragments/de10nano-image.fragment`; without it the `pre-up` loop's `iw dev` would be a permanent 20 s no-op. ### Verify-on-hardware (adds to §5's checklist) diff --git a/external.mk b/external.mk index ee6f778..468b94b 100644 --- a/external.mk +++ b/external.mk @@ -29,7 +29,7 @@ include $(sort $(wildcard $(BR2_EXTERNAL_MISTER_PATH)/package/*/*.mk)) # we just point it at a different, much smaller cpio. # # WHY HERE AND NOT IN THE DEFCONFIG. The obvious alternative is -# BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES in configs/mister_de10nano_defconfig. Do +# BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES in configs/fragments/de10nano.fragment. Do # not: package/pkg-kconfig.mk:19-20 makes `make linux-update-defconfig` and # `make linux-savedefconfig` HARD-FAIL ("Unable to perform when fragment files are # set") as soon as any fragment is configured — and those are precisely the commands @@ -48,7 +48,7 @@ include $(sort $(wildcard $(BR2_EXTERNAL_MISTER_PATH)/package/*/*.mk)) # # BR2_LINUX_KERNEL=y alone was the right condition while every output directory # in this tree built for the same armv7 board. It no longer is: -# configs/mister_de25nano_defconfig builds an AARCH64 kernel for a different +# the de25nano fragment stack (configs/fragments/de25nano.fragment) builds an AARCH64 kernel for a different # board (Agilex 5), in output-de25/, and this hook keys on the *symbol*, not on # which defconfig or which O= is in play — so without a second test it would # fire there too and try to embed $(MISTER_INITRAMFS_CPIO) into that kernel. @@ -69,17 +69,17 @@ include $(sort $(wildcard $(BR2_EXTERNAL_MISTER_PATH)/package/*/*.mk)) # THE TEST IS ON THE ARCHITECTURE, not on a board name or a defconfig name, and # that is the point: the thing that makes this hook wrong for the DE25 is not # "it is the DE25", it is that the cpio is armv7 userspace. Every output dir -# this hook is *meant* for -- the main DE10 image, configs/mister_kernel_defconfig +# this hook is *meant* for -- the main DE10 image, the kernel-only stack # and the rt variant built on it -- is BR2_arm=y, and every one of them wants the # cpio. So `BR2_arm` names the actual precondition and needs no maintenance when # a fourth armv7 variant or a second aarch64 board appears. # # Considered and rejected: a BR2_EXTERNAL Config.in symbol (e.g. a # "BR2_PACKAGE_MISTER_EMBED_STAGE1_INITRAMFS" bool) would be more explicit, but -# it would have to be added to configs/mister_de10nano_defconfig AND -# configs/mister_kernel_defconfig to keep them building — editing both files -# that scripts/check-kernel-defconfig-sync.sh locks in lockstep, and changing -# the DE10's toolchain-fingerprint cache key, for zero behavioural difference. +# it would have to be added to configs/fragments/de10nano.fragment (the board +# 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. ifeq ($(BR2_LINUX_KERNEL)$(BR2_arm),yy) diff --git a/package/azcopy/Config.in b/package/azcopy/Config.in index 8ae68ab..bfd72b6 100644 --- a/package/azcopy/Config.in +++ b/package/azcopy/Config.in @@ -19,7 +19,7 @@ config BR2_PACKAGE_AZCOPY IT IS BIG, AND IT IS OFF BY DEFAULT. 39.1 MiB installed makes it the second-largest single package in this image after samba4, about a fifth of the free space linux.img has left, so - configs/mister_de10nano_defconfig deliberately does not enable + configs/fragments/de10nano-image.fragment deliberately does not enable it. Every tagged release instead carries a STATIC build as a standalone azcopy--armv7.xz download (the build-azcopy job in release.yml); static so it survives a rollback to an diff --git a/package/azcopy/azcopy-profile.sh b/package/azcopy/azcopy-profile.sh index 3d27662..a4d52dc 100644 --- a/package/azcopy/azcopy-profile.sh +++ b/package/azcopy/azcopy-profile.sh @@ -21,7 +21,7 @@ # image -- including the default one, which does not build azcopy at all. # # THAT MATTERS MOST IF YOU ARE RUNNING A DOWNLOADED BINARY. The default image -# does not enable this package (see configs/mister_de10nano_defconfig), so the +# does not enable this package (see configs/fragments/de10nano-image.fragment), so the # usual way to have azcopy on a MiSTer is to drop the released binary onto # /media/fat yourself -- in which case this file is NOT present and none of the # defaults below apply. Set them yourself; that is what the copy-paste note diff --git a/package/azcopy/azcopy.hash b/package/azcopy/azcopy.hash index f9715ee..6634bd2 100644 --- a/package/azcopy/azcopy.hash +++ b/package/azcopy/azcopy.hash @@ -45,7 +45,7 @@ # before, it is a couple of minutes. # # # 1. bump AZCOPY_VERSION in azcopy.mk, then, from the repo root: -# make mister_de10nano_defconfig +# make de10nano-defconfig # make azcopy-source # EXPECTED TO FAIL, on the stale hash below # # # 2. read the real hash straight out of the failure. Buildroot prints it: diff --git a/package/azcopy/azcopy.mk b/package/azcopy/azcopy.mk index 18aae6b..33a04a8 100644 --- a/package/azcopy/azcopy.mk +++ b/package/azcopy/azcopy.mk @@ -16,7 +16,7 @@ # subcommand: it is incremental and restartable, which matters a great deal # over a 100 Mbit link driven by a 800 MHz dual-core Cortex-A9. # -# NOT ENABLED BY DEFAULT. configs/mister_de10nano_defconfig leaves +# NOT ENABLED BY DEFAULT. configs/fragments/de10nano-image.fragment leaves # BR2_PACKAGE_AZCOPY unset on size grounds alone: 39.1 MiB installed would make # this the second-largest package in the image after samba4, for a tool most # owners will never run. The package is complete and tested; it is one diff --git a/package/linux-firmware-extra/linux-firmware-extra.mk b/package/linux-firmware-extra/linux-firmware-extra.mk index 054c009..efbcfe0 100644 --- a/package/linux-firmware-extra/linux-firmware-extra.mk +++ b/package/linux-firmware-extra/linux-firmware-extra.mk @@ -27,7 +27,7 @@ # no second version to keep in sync) and removes the duplicate fetch/extract. # `depends on BR2_PACKAGE_LINUX_FIRMWARE` (Config.in) guarantees that tree # exists whenever this package is enabled -- it also means this package no -# longer builds in the kernel-only variants (mister_kernel_defconfig does not +# longer builds in the kernel-only variants (the kernel-only fragment stack does not # enable linux-firmware), where its files landed in a target/ that is thrown # away and only cost a wasted ~557 MiB fetch. diff --git a/renovate.json b/renovate.json index c5a464e..bcd25b9 100644 --- a/renovate.json +++ b/renovate.json @@ -35,11 +35,17 @@ "guessing: fileCount is one per (manager, matched file) PAIR, not per distinct", "file -- scripts/fetch-sdcard-payload.sh is counted three times because three", "managers match it. depCount is one per regex MATCH, which is why it runs one", - "ahead of fileCount here: configs/mister_de10nano_defconfig carries", - "BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE=\"...\" twice -- line 146, the real", - "setting, and line 41 inside a header comment, which a regex manager has no", - "reason to skip. That model was validated by replaying it over the tree at", - "e66b46b, where it reproduces the recorded 18/19 exactly.", + "ahead of fileCount here: until the 2026-09 fragment split", + "configs/mister_de10nano_defconfig carried BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE", + "twice -- the real setting and a verbatim quote inside a header comment, which", + "a regex manager has no reason to skip. That model was validated by replaying it", + "over the tree at e66b46b, where it reproduces the recorded 18/19 exactly.", + "SINCE THE FRAGMENT SPLIT (2026-09): the 6.18 kernel manager matches ONE file,", + "configs/fragments/de10nano.fragment, whose only occurrence of the symbol is the", + "setting itself (the kernel-only stack shares that file, so the second defconfig", + "and its match are gone). By the model above that is fileCount 22 / depCount 22", + "(the 6.18 manager went from 2 files / 3 matches to 1 / 1) -- DERIVED, not", + "measured: re-measure with the dry-run above before trusting either number.", "If `regex` is absent entirely, the custom managers are dead again. Grep the", "LOG_LEVEL=debug output for `no-result` to catch bug-2-style", "datasource failures, which do NOT show up in the extraction stats at all.", @@ -108,10 +114,9 @@ }, { "customType": "regex", - "description": "Bump the pinned 6.18.y kernel (BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE) EVERYWHERE it appears, constrained to the current longterm line by the custom datasource above. BOTH defconfigs are listed on purpose: configs/mister_kernel_defconfig deliberately copies the main defconfig's kernel/toolchain stanza (see its header and scripts/check-kernel-defconfig-sync.sh), so a manager covering only the main one bumps half the repo and leaves the copy behind -- which is exactly what happened on the 6.18.38 -> 6.18.39 bump and is caught, loudly, by the lockstep check. Because both files carry the same depName, Renovate emits ONE PR touching both. configs/mister_rt.fragment carries the same SYMBOL but is deliberately NOT matched here: it pins the mainline -rc line and has its own manager below. The companion tarball hash (board/mister/de10nano/patches/linux/linux.hash) is NOT updated here -- it comes from kernel.org's own signed sha256sums.asc; see .github/workflows/renovate-hash-sync.yml. CI (build.yml, pull_request-triggered) then proves the carried Linux patches still apply against the bumped source -- see docs/renovate.md and PLAN.md \u00a713.", + "description": "Bump the pinned 6.18.y kernel (BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE), constrained to the current longterm line by the custom datasource above. Since the 2026-09 fragment split the pin lives in exactly ONE file, configs/fragments/de10nano.fragment: the shipped image's stack and the kernel-only stack every kernel variant builds on both include that fragment (configs/fragments/stacks.mk), so there is no mirrored copy to bump alongside it any more. (Before the split BOTH configs/mister_de10nano_defconfig and configs/mister_kernel_defconfig were listed, because a manager covering only the main one bumped half the repo and left the copy behind -- which is exactly what happened on the 6.18.38 -> 6.18.39 bump and was caught, loudly, by the lockstep check. That failure mode is gone by construction; scripts/check-kernel-defconfig-sync.sh now guards the construction instead.) configs/mister_rt.fragment and configs/fragments/de25nano.fragment carry the same SYMBOL but are deliberately NOT matched here: both pin the 7.2 line (the rt fragment has its own manager below; the DE25 pin is unmanaged today). The companion tarball hash (board/mister/de10nano/patches/linux/linux.hash) is NOT updated here -- it comes from kernel.org's own signed sha256sums.asc; see .github/workflows/renovate-hash-sync.yml. CI (build.yml, pull_request-triggered) then proves the carried Linux patches still apply against the bumped source -- see docs/renovate.md and PLAN.md \u00a713.", "managerFilePatterns": [ - "/^configs/mister_de10nano_defconfig$/", - "/^configs/mister_kernel_defconfig$/" + "/^configs/fragments/de10nano\\.fragment$/" ], "matchStrings": [ "BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE=\"(?[\\d.]+)\"" diff --git a/scripts/check-config-fragments.sh b/scripts/check-config-fragments.sh new file mode 100755 index 0000000..43d2b6e --- /dev/null +++ b/scripts/check-config-fragments.sh @@ -0,0 +1,462 @@ +#!/usr/bin/env bash +# +# check-config-fragments.sh — regenerate every Buildroot configuration from +# its fragment stack and prove the stack means what it says +# (docs/buildroot-config.md §1, §11). +# +# The monolithic defconfigs were split into configs/fragments/ (stacks.mk +# lists which fragments make which configuration). merge_config.sh + +# olddefconfig is a forgiving pipeline: a fragment can redefine a symbol an +# earlier one set (merge_config.sh only WARNS), and olddefconfig silently +# drops any symbol whose dependencies are unmet or whose name no longer +# exists in this Buildroot. Neither is an error at build time, both ship a +# different image than the fragments describe. This script turns each into a +# CI failure, and pins the resolved DE10 configuration with a golden hash so +# any drift — a Buildroot bump changing a default underneath us, a fragment +# 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 +# de10nano-kernel + configs/mister_.fragment for every kernel +# variant): +# (a) NO REDEFINITION between fragments: a symbol defined by two fragments +# of one stack (same value or not) is a layering smell — a symbol set +# in common and overridden per board should have been board-only. +# Checked from the fragment text (independent of merge_config.sh's +# output format) AND merge_config.sh's own "redefined" warnings are +# captured and must be empty. The ONE designed exception is a kernel +# variant's fragment overriding the kernel version + patch dir +# (mister_rt.fragment) — ALLOWED_OVERRIDES below, per variant. +# (b) EVERY FRAGMENT SYMBOL SURVIVES olddefconfig: each `BR2_X=val` line +# must appear verbatim in the resolved .config, and each +# `# BR2_X is not set` must resolve to exactly that line (a typo'd +# symbol name is absent from the resolved config in EITHER form, and +# kconfig says nothing). This is what catches a dropped symbol (unmet +# dependency, renamed option after a Buildroot bump, typo) that kconfig +# would otherwise discard silently — the DE25 bring-up's +# fragment-vs-resolved comparison, made permanent. +# (c) RESOLVED-LEVEL LOCKSTEP: the de10nano and de10nano-kernel resolved +# configs agree on every symbol except a named list of designed +# divergences (packages, init/shell choice, rootfs image types, system +# configuration, package-driven glibc gconv copy). The text-level half +# of this guarantee (scripts/check-kernel-defconfig-sync.sh) runs +# without a Buildroot tree; this half is what proves the shared +# fragments still resolve identically once package selects are in play. +# (d) GOLDEN HASH: sha256 of the NORMALISED resolved .config equals the +# value recorded in configs/fragments/golden.sha256 for the pinned +# Buildroot version. Normalisation keeps only the SET symbols +# (`BR2_X=...`), and drops what legitimately varies by host or checkout +# (see normalise_config) plus the two kernel-version symbols Renovate +# moves weekly (BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE and kconfig's +# derived BR2_LINUX_KERNEL_VERSION — their landing is already proved by +# (b)), so the hash is stable across machines and across routine +# kernel bumps of every pin (6.18.y, the rt 7.2.y pin, the DE25 pin) +# and moves ONLY when the resolved configuration really changes. +# olddefconfig is also run with the HOST inputs pinned to canonical +# values (HOSTARCH, HOSTCC_VERSION — Buildroot derives BR2_HOSTARCH, +# BR2_HOST_GCC_AT_LEAST_* and every host-gated package from them), so +# the resolved config the check reasons about is a pure function of +# (fragments, Buildroot version), never of the machine running it. +# Two outcomes: a MISMATCH against a recorded line is a failure (the +# configuration drifted; if intended, --update-golden and commit with +# the reason). NO line at all for the pinned Buildroot version is a +# ::warning, not a failure — a Buildroot bump changes defaults and is +# expected to move every hash; the check prints the new lines ready to +# 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. +# +# Cost: needs the pinned Buildroot tree (fetched/unpacked by `make +# buildroot-unpack` if absent — a 10 MB download, no compile beyond +# Buildroot's own kconfig `conf` binary) and runs olddefconfig once per stack. +# Seconds locally; a minute or two on a cold CI runner. No toolchain, no +# packages, no image. +# +# Usage: scripts/check-config-fragments.sh [--update-golden] [--keep] [STACK...] +# STACK limit to the named stack(s) (de10nano, de10nano-kernel, +# de25nano, or a kernel variant name such as rt); default: all. +# --update-golden rewrite configs/fragments/golden.sha256 from the current +# resolved configs instead of asserting against it. +# --keep leave output-config-check/ in place for inspection (it is +# always left in place on failure). +# CHECK_CONFIG_BR_DIR= use an already-unpacked Buildroot tree +# instead of the wrapper's work/buildroot (tests, CI fixtures). +# CHECK_CONFIG_HOSTARCH / CHECK_CONFIG_HOSTCC_VERSION override the pinned +# host inputs (self-tests only: the golden must NOT move). +# +# It also guards the hard-coded fragment PATHS outside stacks.mk (§ "path +# consumers" below): a fragment rename with stacks.mk updated would otherwise +# pass every check while action.yml's hashFiles() lists, renovate.json's +# managerFilePatterns and the scripts that read a pin by filename all went +# silently stale. +# +# Exit: 0 = every stack passes; 1 = an assertion failed; 2 = usage/IO error. + +set -euo pipefail +export LC_ALL=C + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck source=scripts/lib/board-expectations.sh +. "$ROOT/scripts/lib/board-expectations.sh" +# shellcheck source=scripts/lib/config-stacks.sh +. "$ROOT/scripts/lib/config-stacks.sh" + +GOLDEN="$ROOT/configs/fragments/golden.sha256" +CHECK_DIR="$ROOT/output-config-check" + +# Canonical host inputs for olddefconfig (see (d) above). Buildroot computes +# both with `:=` from the real host compiler; a command-line assignment +# overrides that. x86_64 + GCC 14 is what the CI runners and the developer +# hosts have had since the 2026.05 bump; the values only have to be FIXED, +# not true, for the resolved config to be host-independent. +PIN_HOSTARCH="${CHECK_CONFIG_HOSTARCH:-x86_64}" +PIN_HOSTCC_VERSION="${CHECK_CONFIG_HOSTCC_VERSION:-14}" + +# Per-variant allowlist for (a): symbols a kernel variant's fragment is +# EXPECTED to redefine on top of the kernel-only stack. Anything else a +# variant redefines is a failure, so a variant that starts overriding, say, +# the toolchain has to come here and say so. +declare -A ALLOWED_OVERRIDES=( + [rt]="BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE BR2_LINUX_KERNEL_PATCH" +) + +# Designed divergences for (c): symbol-name PREFIXES on which the resolved +# de10nano and de10nano-kernel configs may differ. Everything else must be +# identical. Keep this list short and specific — it is the definition of +# "what a kernel variant is allowed not to share with the image". +LOCKSTEP_DIVERGENCE_PREFIXES=" +BR2_PACKAGE_ +BR2_INIT_ +BR2_SYSTEM_ +BR2_ROOTFS_OVERLAY +BR2_ROOTFS_POST_BUILD_SCRIPT +BR2_ROOTFS_DEVICE_CREATION_ +BR2_TARGET_ROOTFS_ +BR2_TARGET_GENERIC_ +BR2_TARGET_TZ_ +BR2_TARGET_LOCALTIME +BR2_GENERATE_LOCALE +BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_ +BR2_GDB_VERSION +BR2_DEFCONFIG +" + +UPDATE_GOLDEN=false +KEEP=false +only=() +while [ "$#" -gt 0 ]; do + case "$1" in + --update-golden) UPDATE_GOLDEN=true ;; + --keep) KEEP=true ;; + -h|--help) sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + -*) echo "check-config-fragments: unknown option '$1'" >&2; exit 2 ;; + *) only+=("$1") ;; + esac + shift +done + +die() { echo "check-config-fragments: FATAL: $*" >&2; exit 2; } +fail() { echo "FAIL: $*" >&2; rc=1; } +rc=0 + +[ -f "$CONFIG_STACKS_MK" ] || die "missing $CONFIG_STACKS_MK" + +# --- Buildroot tree ----------------------------------------------------------- +# The wrapper Makefile owns fetching/verifying/unpacking the pinned tarball; +# reuse it rather than re-implementing the hash check here. `buildroot-unpack` +# has no hostshim prerequisite and olddefconfig needs none (Buildroot's +# dependencies.sh only runs for build targets). +BR_DIR="${CHECK_CONFIG_BR_DIR:-$ROOT/work/buildroot}" +if [ -z "${CHECK_CONFIG_BR_DIR:-}" ]; then + make -C "$ROOT" --no-print-directory buildroot-unpack >/dev/null || die "make buildroot-unpack failed" +fi +[ -x "$BR_DIR/support/kconfig/merge_config.sh" ] || die "no merge_config.sh under $BR_DIR" +BR_VERSION=$(sed -n -e 's/[[:space:]]*$//' -e 's/^BUILDROOT_VERSION[[:space:]]*?=[[:space:]]*//p' "$ROOT/Makefile" | head -1) +[ -n "$BR_VERSION" ] || die "could not read BUILDROOT_VERSION from Makefile" + +# --- Stack registry ----------------------------------------------------------- +# stacks.mk's stacks, plus one synthetic stack per kernel variant fragment +# (configs/mister_.fragment layered on the kernel-only stack — the same +# registry .github/actions/buildroot-build and scripts/list-kernel-variants.sh +# use). Order: stacks.mk order, then variants sorted by name. +declare -A STACK_FILES=() +stack_order=() +while IFS= read -r var; do + [ -n "$var" ] || continue + label=$(config_stack_label "$var") + STACK_FILES[$label]=$(config_stack_files "$var" | tr '\n' ' ') + stack_order+=("$label") +done < <(config_stack_vars) +[ -n "${STACK_FILES[de10nano-kernel]+set}" ] || die "stacks.mk defines no DE10NANO_KERNEL stack — kernel variants have no base" +shopt -s nullglob +for f in "$ROOT"/configs/mister_*.fragment; do + name="${f#"$ROOT"/configs/mister_}"; name="${name%.fragment}" + STACK_FILES[$name]="${STACK_FILES[de10nano-kernel]}$f " + stack_order+=("$name") +done +shopt -u nullglob + +if [ "${#only[@]}" -gt 0 ]; then + for s in "${only[@]}"; do + [ -n "${STACK_FILES[$s]+set}" ] || die "unknown stack '$s' (known: ${stack_order[*]})" + done + stack_order=("${only[@]}") +fi + +# --- Normalisation for the golden hash ---------------------------------------- +# Keep: every SET symbol (`BR2_X=...`). `# BR2_X is not set` lines are +# dropped from the hash — losslessly for drift detection, since a symbol that +# flips on shows up as a new set line and one that flips off as a vanished +# set line, and the not-set list is exactly where host-gated symbols (a +# package `depends on BR2_HOSTARCH = ...`, a host-gcc floor) appear or +# disappear between machines. Then drop the set symbols that legitimately +# vary by host or checkout even with the host inputs pinned: +# BR2_HOSTARCH, BR2_HOST_GCC_VERSION, BR2_HOST_GCC_AT_LEAST_* the build host +# BR2_PACKAGE_*_ARCH_SUPPORTS, BR2_PACKAGE_HOST_GO_BIN_HOST_ARCH, +# BR2_PACKAGE_PROVIDES_HOST_RUSTC HOSTARCH-derived +# BR2_PACKAGE_(HOST_)GOBJECT_INTROSPECTION, BR2_PACKAGE_HOST_QEMU*, +# BR2_PACKAGE_LIBGLIB2_BOOTSTRAP, BR2_PACKAGE_PYTHON_GOBJECT `depends on +# BR2_HOST_GCC_AT_LEAST_*` consumers that are =y here (measured: these +# are the only set lines that move between HOSTCC_VERSION 5/9/14 — +# the fragment lines among them are still proved by (b)) +# BR2_VERSION, BR2_EXTERNAL_MISTER_* git-describe / absolute path +# BR2_DEFCONFIG savedefconfig's output path +# BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE, BR2_LINUX_KERNEL_VERSION the Renovate- +# managed kernel pin and kconfig's copy of it (landing proved by (b)) +# Everything else is the configuration. +normalise_config() { + grep -E '^BR2_[A-Za-z0-9_]+=' "$1" \ + | grep -vE '^(BR2_HOSTARCH|BR2_HOST_GCC_VERSION|BR2_HOST_GCC_AT_LEAST_|BR2_VERSION=|BR2_EXTERNAL_MISTER_|BR2_DEFCONFIG=|BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE=|BR2_LINUX_KERNEL_VERSION=)' \ + | grep -vE '^(BR2_PACKAGE_[A-Z0-9_]+_ARCH_SUPPORTS=|BR2_PACKAGE_HOST_GO_BIN_HOST_ARCH=|BR2_PACKAGE_PROVIDES_HOST_RUSTC=)' \ + | grep -vE '^(BR2_PACKAGE_GOBJECT_INTROSPECTION=|BR2_PACKAGE_HOST_GOBJECT_INTROSPECTION=|BR2_PACKAGE_HOST_QEMU|BR2_PACKAGE_LIBGLIB2_BOOTSTRAP=|BR2_PACKAGE_PYTHON_GOBJECT=)' +} + +golden_has_version() { # any line recorded for BR_VERSION at all? + [ -f "$GOLDEN" ] && awk -v v="$BR_VERSION" '$1 == v { found = 1 } END { exit !found }' "$GOLDEN" +} + +golden_lookup() { # $1 = stack -> recorded hash for BR_VERSION, or empty + [ -f "$GOLDEN" ] || return 0 + awk -v v="$BR_VERSION" -v s="$1" '$1 == v && $2 == s { print $3 }' "$GOLDEN" | head -1 +} + +# --- Per-stack work ----------------------------------------------------------- +declare -A NEW_GOLDEN=() +declare -A RESOLVED=() +missing_golden=0 +for stack in "${stack_order[@]}"; do + # shellcheck disable=SC2206 # the file list is space-separated on purpose + files=(${STACK_FILES[$stack]}) + echo "==> $stack: ${files[*]#"$ROOT"/}" + for f in "${files[@]}"; do + [ -f "$f" ] || die "$stack: missing fragment $f" + done + odir="$CHECK_DIR/$stack" + rm -rf "$odir"; mkdir -p "$odir" + + # (a) text-level redefinition check, fragment by fragment in merge order. + declare -A seen=() + # shellcheck disable=SC2086 # word splitting of the allowlist is intended + allowed=" ${ALLOWED_OVERRIDES[$stack]:-} " + for f in "${files[@]}"; do + while IFS= read -r line; do + [ -n "$line" ] || continue + sym=$(config_line_symbol "$line") + if [ -n "${seen[$sym]+set}" ]; then + case "$allowed" in + *" $sym "*) ;; + *) fail "$stack: ${f#"$ROOT"/} redefines $sym, already defined by ${seen[$sym]#"$ROOT"/}. A symbol belongs in exactly one fragment of a stack (docs/buildroot-config.md §1); a kernel variant may override only what ALLOWED_OVERRIDES in $(basename "$0") lists." ;; + esac + fi + seen[$sym]="$f" + done < <(config_strip_fragment "$f") + done + unset seen + + # Merge (Buildroot's own tool, the same call the Makefile makes), keeping + # its stdout so its own redefinition warnings can be asserted empty too. + # TMPDIR: merge_config.sh mktemp's under it; keep that inside our dir. + if ! (cd "$BR_DIR" && TMPDIR="$odir" KCONFIG_CONFIG="$odir/.config" \ + ./support/kconfig/merge_config.sh -m -O "$odir" "${files[@]}") >"$odir/merge.log" 2>&1; then + cat "$odir/merge.log" >&2 + die "$stack: merge_config.sh failed" + fi + if grep -q 'is redefined by fragment' "$odir/merge.log"; then + while IFS= read -r sym; do + case "$allowed" in + *" $sym "*) ;; + *) fail "$stack: merge_config.sh reports $sym redefined (see $odir/merge.log)" ;; + esac + done < <(sed -n 's/^Value of \([A-Za-z0-9_]*\) is redefined by fragment .*/\1/p' "$odir/merge.log") + fi + + if ! make -C "$BR_DIR" O="$odir" BR2_EXTERNAL="$ROOT" BR2_DL_DIR="$ROOT/dl" \ + HOSTARCH="$PIN_HOSTARCH" HOSTCC_VERSION="$PIN_HOSTCC_VERSION" \ + olddefconfig >"$odir/olddefconfig.log" 2>&1; then + cat "$odir/olddefconfig.log" >&2 + die "$stack: olddefconfig failed" + fi + resolved="$odir/.config" + RESOLVED[$stack]="$resolved" + + # (b) every effective fragment line survives. "Effective" = the LAST + # definition in merge order, so an allowed variant override is checked + # against the override's value, not the base's. + declare -A effective=() + for f in "${files[@]}"; do + while IFS= read -r line; do + [ -n "$line" ] || continue + effective[$(config_line_symbol "$line")]="$line" + done < <(config_strip_fragment "$f") + done + dropped=0 + for sym in "${!effective[@]}"; do + line="${effective[$sym]}" + case "$line" in + "# "*" is not set") + # The symbol must come back as EXACTLY this line. `=y`/`=m` + # means the not-set was overridden by a select or default; + # absent in both forms means kconfig never heard of the name + # (typo, or the option no longer exists in this Buildroot). + if ! grep -qxF -- "$line" "$resolved"; then + actual=$(grep -E "^(# )?$sym( is not set|=)" "$resolved" || echo "") + fail "$stack: fragment says '$line' but the resolved .config has $actual" + dropped=$((dropped + 1)) + fi + ;; + *) + if ! grep -qxF -- "$line" "$resolved"; then + actual=$(grep -E "^(# )?$sym( is not set|=)" "$resolved" || echo "") + fail "$stack: '$line' did not survive olddefconfig; resolved: $actual" + dropped=$((dropped + 1)) + fi + ;; + esac + done + echo " ${#effective[@]} fragment symbol(s) checked against the resolved .config, $dropped dropped" + unset effective + + # (d) golden hash of the normalised resolved config. + normalise_config "$resolved" >"$odir/normalised.config" + hash=$(sha256sum "$odir/normalised.config" | cut -d' ' -f1) + NEW_GOLDEN[$stack]="$hash" + if [ "$UPDATE_GOLDEN" = false ]; then + want=$(golden_lookup "$stack") + if [ -z "$want" ] && ! golden_has_version; then + # A Buildroot bump: nothing recorded for this version yet. Warn, + # print the line, let the build proceed (see (d) in the header). + echo "::warning::$stack: no golden hash recorded for Buildroot $BR_VERSION in ${GOLDEN#"$ROOT"/} -- a Buildroot bump. Expected line: '$BR_VERSION $stack $hash'. renovate-hash-sync.yml case 8 commits it on a Renovate PR; by hand: '$(basename "$0") --update-golden', then commit with what changed." + missing_golden=$((missing_golden + 1)) + elif [ -z "$want" ]; then + fail "$stack: ${GOLDEN#"$ROOT"/} records Buildroot $BR_VERSION for other stacks but has no line for '$stack' (a new stack, or a deleted line). Run '$(basename "$0") --update-golden' and commit the new line with the reason." + elif [ "$want" != "$hash" ]; then + fail "$stack: resolved configuration DRIFTED — sha256 of the normalised .config is $hash, golden says $want (${GOLDEN#"$ROOT"/}). If the change is intended, run '$(basename "$0") --update-golden' and commit the new hash with a message saying what changed and why; the normalised config is at ${odir#"$ROOT"/}/normalised.config for diffing against the previous good run." + else + echo " golden hash matches ($hash)" + fi + fi +done + +# (c) resolved-level lockstep between the image and the kernel-only base. +if [ -n "${RESOLVED[de10nano]+set}" ] && [ -n "${RESOLVED[de10nano-kernel]+set}" ]; then + div=$(diff \ + <(normalise_config "${RESOLVED[de10nano]}" | sort) \ + <(normalise_config "${RESOLVED[de10nano-kernel]}" | sort) \ + | sed -n 's/^[<>] //p' | sed -e 's/^# \(BR2_[A-Za-z0-9_]*\) is not set$/\1/' -e 's/=.*$//' | sort -u || true) + bad="" + while IFS= read -r sym; do + [ -n "$sym" ] || continue + ok=false + while IFS= read -r pfx; do + [ -n "$pfx" ] || continue + case "$sym" in "$pfx"*) ok=true; break ;; esac + done <<< "$LOCKSTEP_DIVERGENCE_PREFIXES" + [ "$ok" = true ] || bad="$bad $sym" + done <<< "$div" + if [ -n "$bad" ]; then + fail "resolved-level lockstep: de10nano and de10nano-kernel differ on:$bad — outside the designed divergences (LOCKSTEP_DIVERGENCE_PREFIXES in $(basename "$0")). A variant kernel would be built with different toolchain/kernel settings than the image (docs/buildroot-config.md §4)." + else + echo "==> resolved-level lockstep: de10nano and de10nano-kernel agree on every symbol outside the designed divergences ($(printf '%s\n' "$div" | grep -c . ) differing symbols, all allowed)" + fi +elif [ "${#only[@]}" -eq 0 ]; then + fail "resolved-level lockstep: de10nano or de10nano-kernel stack missing from stacks.mk" +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: +# GitHub's hashFiles() (action.yml's two dl-cache keys), Renovate's +# managerFilePatterns, the workflow path filter, and the scripts that read a +# pin off one fragment by filename. A rename that updates stacks.mk passes +# (a)-(d) while all of those go stale. Two asserts: +# 1. every `configs/fragments/` token in the code/CI surface +# (Makefile, scripts/, .github/, renovate.json -- not docs, which may +# legitimately name history) must exist in the tree; +# 2. action.yml's hashFiles() list containing de10nano-image must equal the +# DE10NANO stack's files, and the one containing kernel-only must equal +# the DE10NANO_KERNEL stack's (the format('mister_{0}.fragment') item is +# the variant's own fragment and is skipped). +if [ "${#only[@]}" -eq 0 ]; then + action="$ROOT/.github/actions/buildroot-build/action.yml" + while IFS= read -r tok; do + [ -n "$tok" ] || continue + if [ ! -e "$ROOT/$tok" ]; then + fail "path consumer: '$tok' is named in the code/CI surface but does not exist -- a fragment was renamed or moved without updating every consumer ($(grep -rlF -- "$tok" "$ROOT/Makefile" "$ROOT/scripts" "$ROOT/.github" "$ROOT/renovate.json" | sed "s|^$ROOT/||" | tr '\n' ' '))" + fi + done < <(grep -rhoE 'configs/fragments/[A-Za-z0-9_.\\-]+' "$ROOT/Makefile" "$ROOT/scripts" "$ROOT/.github" "$ROOT/renovate.json" \ + | sed -e 's/\\*\././g' -e 's/[.,:;)]*$//' | sort -u) + if [ -f "$action" ]; then + check_hashfiles() { # $1 = stack var, $2 = distinguishing fragment name + local want got + want=$(config_stack_files "$1" | sed "s|^$ROOT/||" | sort | tr '\n' ' ') + got=$(grep -oE "hashFiles\([^)]*configs/fragments/$2\.fragment[^)]*\)" "$action" \ + | grep -oE "'configs/fragments/[^']+'" | tr -d "'" | sort -u | tr '\n' ' ') + if [ -z "$got" ]; then + fail "path consumer: $action has no hashFiles() list naming configs/fragments/$2.fragment -- the dl-cache key for the $(config_stack_label "$1") stack is gone" + elif [ "$got" != "$want" ]; then + fail "path consumer: $action's hashFiles() list for the $(config_stack_label "$1") stack is [$got] but stacks.mk says [$want] -- keep the two in step (hashFiles cannot read stacks.mk)" + fi + } + check_hashfiles DE10NANO de10nano-image + check_hashfiles DE10NANO_KERNEL kernel-only + else + fail "path consumer: $action not found" + fi +fi + +# --- Golden file -------------------------------------------------------------- +if [ "$UPDATE_GOLDEN" = true ]; then + { + echo "# configs/fragments/golden.sha256 — sha256 of each stack's NORMALISED resolved" + echo "# .config for the pinned Buildroot version (scripts/check-config-fragments.sh" + echo "# (d); docs/buildroot-config.md §11). Regenerate ONLY with" + echo "# scripts/check-config-fragments.sh --update-golden" + echo "# and say in the commit message what changed and why. Columns:" + echo "# " + for stack in "${stack_order[@]}"; do + printf '%s %s %s\n' "$BR_VERSION" "$stack" "${NEW_GOLDEN[$stack]}" + done + # Keep other stacks' lines for this version and every other version's + # lines untouched, so a partial run cannot delete what it did not check. + if [ -f "$GOLDEN" ]; then + awk -v v="$BR_VERSION" -v keep=" ${stack_order[*]} " ' + /^#/ { next } + NF == 3 && !($1 == v && index(keep, " " $2 " ")) { print } + ' "$GOLDEN" + fi + } | awk '/^#/ || !seen[$0]++' >"$GOLDEN.tmp" + mv "$GOLDEN.tmp" "$GOLDEN" + echo "==> wrote ${GOLDEN#"$ROOT"/}" +fi + +if [ "$rc" -eq 0 ]; then + [ "$KEEP" = true ] || rm -rf "$CHECK_DIR" + if [ "$missing_golden" -gt 0 ]; then + echo "check-config-fragments: OK with $missing_golden WARNING(s) — ${#stack_order[@]} stack(s) regenerate cleanly from their fragments, but ${GOLDEN#"$ROOT"/} has no lines for Buildroot $BR_VERSION yet (see the warnings above)" + else + echo "check-config-fragments: OK — ${#stack_order[@]} stack(s) regenerate cleanly from their fragments (Buildroot $BR_VERSION)" + fi +else + echo "check-config-fragments: FAILED — resolved configs left under ${CHECK_DIR#"$ROOT"/}/ for inspection" >&2 +fi +exit "$rc" diff --git a/scripts/check-kernel-defconfig-sync.sh b/scripts/check-kernel-defconfig-sync.sh index 76661f4..4b337bd 100755 --- a/scripts/check-kernel-defconfig-sync.sh +++ b/scripts/check-kernel-defconfig-sync.sh @@ -1,89 +1,112 @@ #!/usr/bin/env bash # # check-kernel-defconfig-sync.sh — lockstep assertion between the kernel-only -# base defconfig and the main image defconfig (docs/rt-beta-kernel.md §2, -# ADR 0021 as amended 2026-07-18). +# base configuration and the main image configuration (docs/rt-beta-kernel.md +# §2, ADR 0021 as amended 2026-07-18, docs/buildroot-config.md §1/§4). # -# configs/mister_kernel_defconfig deliberately COPIES the main defconfig's -# toolchain and kernel stanzas (its header says why). A copy can drift, and the -# failure mode of drift is the quiet kind: a kernel variant built by a -# different toolchain (wrong -mcpu, wrong headers) or from different sources -# than the image its modules are merged into. Nothing at build time compares -# the two files — so this script is that comparison, and it must stay cheap -# enough to run before any cache or build work (it reads two tracked files and -# nothing else). +# HISTORY, because the name predates the layout. Until the fragment split +# (2026-09, docs/buildroot-config.md) configs/mister_kernel_defconfig was a +# hand-mirrored COPY of configs/mister_de10nano_defconfig's toolchain and +# kernel stanzas, and this script compared the two FILES. Both files are gone. +# Today the shipped image and the kernel-only base are two STACKS of +# fragments (configs/fragments/stacks.mk: `common de10nano de10nano-image` +# and `common de10nano kernel-only`) that share the toolchain/kernel +# fragments BY CONSTRUCTION — so the value-drift this script was written for +# can no longer happen by forgetting to mirror an edit. What CAN still happen, +# and what this script now guards, is a toolchain/kernel symbol landing in a +# fragment only ONE stack uses (BR2_KERNEL_HEADERS_6_19=y added to +# de10nano-image.fragment, say): kconfig would accept it, the image and the +# variant kernels would be built by different toolchains, and nothing at build +# time would say so. The comparison is therefore now done over the MERGED +# TEXT of each stack, exactly as it was over the two files, plus one new +# structural assert (0). It stays cheap enough to run before any cache or +# build work: it reads a handful of tracked files and nothing else. # -# What it asserts: -# 1. Every BR2_ symbol DEFINED IN BOTH files carries the identical value. -# Symbols defined in only one file are fine by design (the kernel -# defconfig has no packages; the main defconfig has no BR2_INIT_NONE). -# 2. Sentinel presence: the kernel defconfig still carries the four symbols -# no rewrite of it may lose (arch, CPU, headers series, toolchain C++) — +# What it asserts, over the merged (comment-stripped) text of the two stacks: +# 0. STRUCTURE: every symbol in a toolchain/kernel family (the BOARD row's +# arch families + the common families + the kernel/patch families named +# below) is defined in a fragment BOTH stacks use. That is the +# "shared by construction" property made checkable. +# 1. Every BR2_ symbol DEFINED IN BOTH stacks carries the identical value. +# Symbols defined in only one stack are fine by design (the kernel stack +# has no packages; the image stack has no BR2_INIT_NONE). +# 2. Sentinel presence: the kernel stack still carries the symbols no +# rewrite of it may lose (arch, CPU, headers series, toolchain C++) — # the same fail-loud-on-degenerate-input posture as the toolchain # fingerprint in .github/actions/buildroot-build/action.yml. # 3. Family name-set equality: kconfig CHOICE symbols encode their value in # the symbol NAME (BR2_KERNEL_HEADERS_6_18 vs _6_19, BR2_cortex_a9 vs -# _a7), so a headers or CPU bump in one file DROPS the old name and adds a -# new one — no symbol exists in both files to disagree, and check 1 alone -# provably passes on exactly the drift this script exists to catch. For -# each family such a choice lives in, the set of defined symbol names must -# therefore be identical in both files. +# _a7), so a headers or CPU bump on one side DROPS the old name and adds a +# new one — no symbol exists in both to disagree, and check 1 alone +# provably passes on exactly that drift. For each family such a choice +# lives in, the set of defined symbol names must therefore be identical. # # Comments are stripped with the SAME sed idiom as that action's fingerprint -# step (both files are heavily annotated). That stripping also drops -# `# BR2_FOO is not set` lines — NOT because they are comments to kconfig -# (they are not: `conf --defconfig` parses them as an explicit =n, and the -# kernel defconfig's own `# BR2_PACKAGE_BUSYBOX is not set` is LOAD-BEARING -# exactly that way, per its comment there — do not "clean them up") — but -# because a symbol deliberately =n in one file while set in the other is a -# DESIGNED divergence here (the kernel-only config suppresses what the main -# image wants, BusyBox being the live example), so comparing them would make -# this check cry wolf; the sentinel and family-set asserts below are what -# guard against a stanza vanishing or drifting by rename instead. Values are -# split on the FIRST '=' only: several values legitimately contain '=' (the main -# defconfig's ext2 MKFS_OPTIONS). +# step. That stripping also drops `# BR2_FOO is not set` lines — NOT because +# they are comments to kconfig (they are not: `conf` parses them as an +# explicit =n, and kernel-only.fragment's `# BR2_PACKAGE_BUSYBOX is not set` +# is LOAD-BEARING exactly that way — do not "clean them up") — but because a +# symbol deliberately =n in one stack while set in the other is a DESIGNED +# divergence here (the kernel-only stack suppresses what the image wants, +# BusyBox being the live example), so comparing them would make this check +# cry wolf; the sentinel and family-set asserts guard against a stanza +# vanishing or drifting by rename instead. Values are split on the FIRST '=' +# only: several values legitimately contain '=' (the ext2 MKFS_OPTIONS). +# +# The RESOLVED-config half of the same guarantee (that the two stacks still +# agree after olddefconfig, package-driven selects included) lives in +# scripts/check-config-fragments.sh, which needs a Buildroot tree and so runs +# later in CI. This script is the one that runs before any cache restore. # # Where this runs: # * .github/actions/buildroot-build — for every variant != main, before any # cache restore, so drift dies in seconds instead of 2 hours in. -# * build.yml's `build` job — as a lint next to lint-kernel-patches.sh, so -# drift also fails a main-only change that edits the main defconfig -# without mirroring. +# * build.yml's `lint-config` job — next to lint-kernel-patches.sh and +# check-config-fragments.sh. # * By hand: scripts/check-kernel-defconfig-sync.sh (no arguments). # BOARD= (or a single positional ) selects which board's # EXPECTATION ROW the asserts below use; it does NOT change which two -# files are compared. The compared pair is fixed to the DE10 defconfigs -# named above (the file-path axis is deliberately not wired yet — -# docs/de25-readiness-ledger.md §5.7 risk 3), so BOARD=de25nano today -# asserts aarch64 expectations against the DE10 pair and fails, as it -# should: it is a table self-test, not a DE25 lockstep check. +# stacks are compared. The compared pair is fixed to the DE10 stacks +# named in stacks.mk (there is no DE25 kernel-only stack yet), so +# BOARD=de25nano today asserts aarch64 expectations against the DE10 +# pair and fails, as it should: it is a table self-test, not a DE25 +# lockstep check. # # BOARD selects the per-board sentinel/family tables in scripts/lib/ # board-expectations.sh (docs/de25-readiness-ledger.md §5.2) — an optional -# $1 wins over the BOARD env var if both are given. Defaults to -# "de10nano"; none of the call sites above pass either today, so that -# default is the only path any of them exercise, and it is defined to -# reproduce this file's own former literal sentinel/family lists -# byte-for-byte (§5.6) — the table is not a behaviour change for DE10, only -# a second board's row is new. An unrecognized BOARD is a usage error (exit -# 2 below), never a silent fallback to an existing board's row. +# $1 wins over the BOARD env var if both are given. Defaults to "de10nano"; +# none of the call sites above pass either today. An unrecognized BOARD is a +# usage error (exit 2 below), never a silent fallback to an existing row. # -# Exit: 0 = in lockstep; 1 = drift, a one-sided choice bump, or a missing -# sentinel; 2 = usage/IO error (including an unrecognized BOARD). +# Exit: 0 = in lockstep; 1 = drift, a one-sided choice bump, a missing +# sentinel, or a family symbol in a one-stack fragment; 2 = usage/IO error +# (including an unrecognized BOARD). set -euo pipefail export LC_ALL=C ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -MAIN_DEFCONFIG="$ROOT/configs/mister_de10nano_defconfig" -KERNEL_DEFCONFIG="$ROOT/configs/mister_kernel_defconfig" # shellcheck source=scripts/lib/board-expectations.sh . "$ROOT/scripts/lib/board-expectations.sh" +# shellcheck source=scripts/lib/config-stacks.sh +. "$ROOT/scripts/lib/config-stacks.sh" + +# The two stacks compared. Fixed on purpose (see the header); the fragment +# lists themselves come from stacks.mk, never from here. +MAIN_STACK=DE10NANO +KERNEL_STACK=DE10NANO_KERNEL + +# Families whose symbols must live in a fragment shared by both stacks, on +# top of the BOARD arch row and the common row from board-expectations.sh: +# the kernel stanza and the patch/hash registry. BR2_LINUX_KERNEL covers +# every BR2_LINUX_KERNEL_* symbol (version, patch dir, config file, image +# format, DTS) — any one of them differing between the image and a variant +# kernel is exactly the "built from different sources" failure ADR 0021 +# describes. +SHARED_ONLY_FAMILIES="BR2_LINUX_KERNEL BR2_GLOBAL_PATCH_DIR" # $1 (if given) wins over the BOARD env var; both fall back to "de10nano". -# See this file's header for why the default must stay byte-identical to -# the pre-table literal lists. BOARD="${1:-${BOARD:-de10nano}}" # Both tables are checked, not just the first one the script happens to read: # a board row added to one table and forgotten in the other would otherwise @@ -97,39 +120,77 @@ for table in BOARD_ARCH_SENTINELS BOARD_ARCH_FAMILIES; do done unset -n _tbl -for f in "$MAIN_DEFCONFIG" "$KERNEL_DEFCONFIG"; do - [ -f "$f" ] || { echo "check-kernel-defconfig-sync: FATAL: missing $f" >&2; exit 2; } +[ -f "$CONFIG_STACKS_MK" ] || { echo "check-kernel-defconfig-sync: FATAL: missing $CONFIG_STACKS_MK" >&2; exit 2; } + +mapfile -t main_files < <(config_stack_files "$MAIN_STACK") +mapfile -t kernel_files < <(config_stack_files "$KERNEL_STACK") +if [ "${#main_files[@]}" -eq 0 ] || [ "${#kernel_files[@]}" -eq 0 ]; then + echo "check-kernel-defconfig-sync: FATAL: ${MAIN_STACK}_FRAGMENTS or ${KERNEL_STACK}_FRAGMENTS is empty/missing in configs/fragments/stacks.mk" >&2 + exit 2 +fi +for f in "${main_files[@]}" "${kernel_files[@]}"; do + [ -f "$f" ] || { echo "check-kernel-defconfig-sync: FATAL: missing $f (named in configs/fragments/stacks.mk)" >&2; exit 2; } done # Strip comments/blank lines, keep only BR2_ symbol assignments. Same idiom as # the toolchain-fingerprint step (action.yml): a '#' that begins a line or # follows whitespace starts a comment; no legitimate value carries a bare '#'. strip_config() { - sed -e 's/^[[:space:]]*#.*$//' -e 's/[[:space:]]\+#.*$//' -e 's/[[:space:]]*$//' "$1" \ + sed -e 's/^[[:space:]]*#.*$//' -e 's/[[:space:]]\+#.*$//' -e 's/[[:space:]]*$//' "$@" \ | grep '^BR2_' || true } -main_stripped=$(strip_config "$MAIN_DEFCONFIG") -kernel_stripped=$(strip_config "$KERNEL_DEFCONFIG") +main_stripped=$(strip_config "${main_files[@]}") +kernel_stripped=$(strip_config "${kernel_files[@]}") + +rc=0 + +# --- 0. Structure: family symbols only in fragments BOTH stacks use --------- +# Shared = present in both lists (by path). A family symbol found in any +# other fragment is a toolchain/kernel decision one stack would not see. +shared_files=() +for f in "${main_files[@]}"; do + for g in "${kernel_files[@]}"; do + [ "$f" = "$g" ] && shared_files+=("$f") + done +done +if [ "${#shared_files[@]}" -eq 0 ]; then + echo "FAIL: the $(config_stack_label "$MAIN_STACK") and $(config_stack_label "$KERNEL_STACK") stacks share no fragment at all --" >&2 + echo " the kernel-only base can no longer be in lockstep with the image by construction." >&2 + rc=1 +fi +is_shared() { local s; for s in "${shared_files[@]}"; do [ "$s" = "$1" ] && return 0; done; return 1; } +# shellcheck disable=SC2086 # word splitting over the merged family list is intended +for f in "${main_files[@]}" "${kernel_files[@]}"; do + is_shared "$f" && continue + for family in ${BOARD_ARCH_FAMILIES[$BOARD]} $BOARD_COMMON_FAMILIES $SHARED_ONLY_FAMILIES; do + hits=$(strip_config "$f" | sed -n "s/^\(${family}[A-Za-z0-9_]*\)=.*/\1/p") + [ -n "$hits" ] || continue + echo "FAIL: ${f#"$ROOT"/} defines toolchain/kernel symbol(s) $(printf '%s' "$hits" | tr '\n' ' ')" >&2 + echo " but only ONE of the two stacks uses that fragment. Such symbols belong in a" >&2 + echo " fragment both configs/fragments/stacks.mk stacks list (common or de10nano)," >&2 + echo " or the image and the variant kernels are built from different settings." >&2 + rc=1 + done +done -# --- 2. Sentinels first: a degenerate kernel defconfig must not pass --------- -# (an empty or mis-stripped file would trivially satisfy the "no symbol +# --- 2. Sentinels: a degenerate kernel stack must not pass ------------------ +# (an empty or mis-stripped stack would trivially satisfy the "no symbol # disagrees" check below — same reasoning as the fingerprint's BR2_arm assert). # Merge order is BOARD's arch row first, then the common row — this # reproduces, for BOARD=de10nano, the exact former literal list in the same # order (scripts/lib/board-expectations.sh, §5.3). -rc=0 # shellcheck disable=SC2086 # word splitting over the merged symbol list is intended for must in ${BOARD_ARCH_SENTINELS[$BOARD]} $BOARD_COMMON_SENTINELS; do if ! printf '%s\n' "$kernel_stripped" | grep -q "^${must}"; then - echo "FAIL: sentinel '${must}' is absent from configs/mister_kernel_defconfig --" >&2 - echo " the kernel-only toolchain stanza has been lost or renamed; see that" >&2 - echo " file's LOCKSTEP header." >&2 + echo "FAIL: sentinel '${must}' is absent from the $(config_stack_label "$KERNEL_STACK") stack --" >&2 + echo " the kernel-only toolchain stanza has been lost or renamed; see" >&2 + echo " configs/fragments/de10nano.fragment and docs/buildroot-config.md §3." >&2 rc=1 fi done -# --- 1. Value comparison over the symbols both files define ------------------ +# --- 1. Value comparison over the symbols both stacks define --------------- # awk keyed on the symbol name (text before the FIRST '='), values compared # verbatim. Output: one "SYMBOL | main-value | kernel-value" line per mismatch. mismatches=$(awk ' @@ -146,36 +207,33 @@ mismatches=$(awk ' ' <(printf '%s\n' "$main_stripped") <(printf '%s\n' "$kernel_stripped")) if [ -n "$mismatches" ]; then - echo "FAIL: configs/mister_kernel_defconfig has drifted from configs/mister_de10nano_defconfig." >&2 - echo "Every BR2_ symbol defined in BOTH files must carry the identical value:" >&2 + echo "FAIL: the $(config_stack_label "$KERNEL_STACK") stack disagrees with the $(config_stack_label "$MAIN_STACK") stack." >&2 + echo "Every BR2_ symbol defined in BOTH stacks must carry the identical value:" >&2 printf '%s\n' "$mismatches" >&2 echo "" >&2 - echo "Mirror the main defconfig's value into the kernel defconfig (or vice versa —" >&2 - echo "whichever change was intended) in the same commit. See the LOCKSTEP header in" >&2 - echo "configs/mister_kernel_defconfig." >&2 + echo "A symbol both stacks need belongs in a SHARED fragment (common or de10nano), defined" >&2 + echo "once -- see configs/fragments/stacks.mk and docs/buildroot-config.md §1." >&2 rc=1 fi # --- 3. Family name-set comparison: choice symbols drift by RENAME, not value - # The comparison above is blind to a kconfig CHOICE bump (header §3): switch the -# main defconfig to BR2_KERNEL_HEADERS_6_19=y or BR2_cortex_a7=y and the old -# name simply stops being defined in both files — zero shared symbols disagree, +# image stack to BR2_KERNEL_HEADERS_6_19=y or BR2_cortex_a7=y and the old +# name simply stops being defined in both — zero shared symbols disagree, # and both scenarios were demonstrated to sail through check 1 alone. So for # each family a choice lives in, assert the SET of defined symbol names matches # exactly. The designed one-sided symbols (BR2_INIT_NONE, packages, rootfs # types) share none of these prefixes, so they stay exempt. Both-sides-empty # degenerates to equal sets — that hole is what the presence sentinels above # close. -# Same merge order as the sentinels above: BOARD's arch row first, then the -# common row (scripts/lib/board-expectations.sh, §5.3). # shellcheck disable=SC2086 # word splitting over the merged symbol list is intended for family in ${BOARD_ARCH_FAMILIES[$BOARD]} $BOARD_COMMON_FAMILIES; do main_names=$(printf '%s\n' "$main_stripped" | sed -n "s/^\(${family}[A-Za-z0-9_]*\)=.*/\1/p" | sort) kernel_names=$(printf '%s\n' "$kernel_stripped" | sed -n "s/^\(${family}[A-Za-z0-9_]*\)=.*/\1/p" | sort) if [ "$main_names" != "$kernel_names" ]; then - echo "FAIL: the ${family}* symbol-name sets differ between the two defconfigs." >&2 + echo "FAIL: the ${family}* symbol-name sets differ between the two stacks." >&2 echo "A choice symbol carries its value in its NAME, so a bump/rename on one side" >&2 - echo "is invisible to the shared-value comparison — mirror it in the same commit:" >&2 + echo "is invisible to the shared-value comparison — define it once, in a shared fragment:" >&2 echo " main: $(printf '%s' "${main_names:-}" | tr '\n' ' ')" >&2 echo " kernel: $(printf '%s' "${kernel_names:-}" | tr '\n' ' ')" >&2 rc=1 @@ -195,6 +253,6 @@ shared=$(awk ' ' <(printf '%s\n' "$main_stripped") <(printf '%s\n' "$kernel_stripped")) if [ "$rc" -eq 0 ]; then - echo "check-kernel-defconfig-sync: OK — $shared shared BR2_ symbol(s) agree, all sentinels present, choice-family name sets match." + echo "check-kernel-defconfig-sync: OK — $(config_stack_label "$MAIN_STACK") and $(config_stack_label "$KERNEL_STACK") stacks share ${#shared_files[@]} fragment(s); $shared shared BR2_ symbol(s) agree, all sentinels present, choice-family name sets match, every toolchain/kernel family symbol lives in a shared fragment." fi exit "$rc" diff --git a/scripts/check-kernel-fragment-noop.sh b/scripts/check-kernel-fragment-noop.sh new file mode 100755 index 0000000..5837eea --- /dev/null +++ b/scripts/check-kernel-fragment-noop.sh @@ -0,0 +1,228 @@ +#!/usr/bin/env bash +# +# check-kernel-fragment-noop.sh — prove that the shared MiSTer kernel fragment +# changes NOTHING on the board whose kernel config it was extracted from. +# +# WHY THIS EXISTS +# --------------- +# board/mister/common/linux-mister.fragment is the arch-neutral MiSTer driver +# and feature set: input/HID, Bluetooth, Wi-Fi, USB, sound, filesystems, +# netfilter, LEDs. It was extracted from the DE10-Nano's kernel config and is +# consumed today by the DE25-Nano, which layers it on top of its own minimal +# arm64 base (board/mister/de25nano/linux.config). +# +# The whole value of that arrangement rests on ONE claim: every line in the +# fragment is byte-identical to the corresponding line in the DE10's resolved +# kernel config. That is what makes "both boards have the same driver support" +# checkable, and it is what will let the DE10 adopt the fragment later with a +# provable zero-delta instead of a leap of faith. +# +# A claim that is only asserted in a comment decays. This script is the claim, +# executed. It is designed to be run against a *configured kernel build tree*, +# so it belongs AFTER a kernel build, not in a lint job (see docs/ci.md notes +# in docs/de25-kernel-config.md §10). +# +# HOW IT WORKS — and why it runs kconfig twice +# -------------------------------------------- +# Naively one would merge the fragment onto the tree's .config, resolve, and +# diff against the tree's .config. That has a false-positive: resolving a config +# outside Buildroot's environment cannot reproduce toolchain-derived string +# symbols exactly (CONFIG_CC_VERSION_TEXT comes from `$(CC) --version` run +# through kconfig's $(shell,...) and comes back empty here). So instead: +# +# CONTROL : tree/.config -> olddefconfig -> control/.config +# TEST : tree/.config + fragment (merge) -> olddefconfig -> test/.config +# +# Both runs use exactly the same kconfig binary, srctree, ARCH and compiler, so +# every environment-derived difference cancels. The fragment is a no-op if and +# only if control/.config and test/.config are byte-identical. +# +# It also fails on any merge_config.sh "is redefined by fragment" line. Note the +# gotcha those lines carry: merge_config resolves a symbol's new value with +# `grep -w CONFIG_ `, which matches PROSE as well as settings, so +# a comment in the fragment that names a symbol the fragment also sets produces +# a FALSE redefinition warning. Rule 5 in the fragment's header forbids that; +# this check is what enforces it. +# +# USAGE +# scripts/check-kernel-fragment-noop.sh [--tree DIR] [--fragment FILE] [--keep] +# +# With no arguments it binds to THE kernel tree under output/build/linux-[0-9]* +# — the same "never the first glob match" discipline the Makefile's `rt` recipe +# uses: `linux-[0-9]*` so linux-firmware-*/linux-headers-*/linux-pam-* cannot +# match, zero trees is fatal, and MORE than one tree is fatal too, because a +# stale sibling left by a kernel bump sorts first often enough that picking one +# blindly would validate the wrong kernel and false-pass. +# +# Exit codes: 0 = fragment is a no-op. 1 = drift, or the check could not run. +# +set -euo pipefail + +PROG=${0##*/} +REPO_ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) + +TREE="" +FRAGMENT="$REPO_ROOT/board/mister/common/linux-mister.fragment" +KEEP=0 + +die() { printf '%s: FATAL: %s\n' "$PROG" "$1" >&2; exit 1; } + +usage() { + cat >&2 <<-EOF + usage: $PROG [--tree DIR] [--fragment FILE] [--keep] + + --tree DIR configured kernel build tree to check against. + Default: the unique output/build/linux-[0-9]*/ . + --fragment FILE kernel config fragment to prove is a no-op. + Default: board/mister/common/linux-mister.fragment + --keep keep the scratch directory (prints its path). + EOF + exit 1 +} + +while [ $# -gt 0 ]; do + case "$1" in + --tree) [ $# -ge 2 ] || usage; TREE=$2; shift 2 ;; + --fragment) [ $# -ge 2 ] || usage; FRAGMENT=$2; shift 2 ;; + --keep) KEEP=1; shift ;; + -h|--help) usage ;; + *) printf '%s: unknown argument: %s\n' "$PROG" "$1" >&2; usage ;; + esac +done + +[ -f "$FRAGMENT" ] || die "fragment does not exist: $FRAGMENT" +# Canonicalise now: merge_config.sh runs after a `cd` into the scratch dir, +# where a relative --fragment path would no longer resolve. +FRAGMENT=$(readlink -f "$FRAGMENT") + +# --- bind to THE kernel tree, fail closed on zero or many -------------------- +if [ -z "$TREE" ]; then + # shellcheck disable=SC2207 # paths here are Buildroot-generated, no spaces + trees=($(ls -d "$REPO_ROOT"/output/build/linux-[0-9]*/ 2>/dev/null || true)) + if [ ${#trees[@]} -eq 0 ]; then + printf '%s: FATAL: no kernel tree under %s\n' \ + "$PROG" "$REPO_ROOT/output/build/linux-[0-9]*/" >&2 + printf ' This check needs a CONFIGURED kernel tree; it cannot run on a\n' >&2 + printf ' clean checkout. Build the DE10 image first (make all), or pass\n' >&2 + printf ' --tree DIR.\n' >&2 + exit 1 + elif [ ${#trees[@]} -gt 1 ]; then + printf '%s: FATAL: %d kernel trees under %s/output/build/ -- cannot tell which\n' \ + "$PROG" "${#trees[@]}" "$REPO_ROOT" >&2 + printf ' one is current:\n' >&2 + printf ' %s\n' "${trees[@]}" >&2 + printf ' A stale sibling appears when the kernel version is bumped without\n' >&2 + printf ' discarding the old tree; validating its .config would prove the\n' >&2 + printf ' fragment against the WRONG kernel. Remove the stale tree, or pass\n' >&2 + printf ' --tree DIR explicitly.\n' >&2 + exit 1 + fi + TREE=${trees[0]} +fi +TREE=${TREE%/} +[ -d "$TREE" ] || die "not a directory: $TREE" +# Absolute, because both kconfig runs below execute with cwd inside the scratch +# directory -- a relative --tree would silently resolve to nothing there. +TREE=$(cd -- "$TREE" && pwd) + +CONF="$TREE/scripts/kconfig/conf" +MERGE="$TREE/scripts/kconfig/merge_config.sh" +BASE_CONFIG="$TREE/.config" + +[ -f "$BASE_CONFIG" ] || die "$TREE has no .config -- the tree is not configured." +[ -x "$CONF" ] || die "$CONF is missing or not executable -- the tree was never built." +[ -x "$MERGE" ] || die "$MERGE is missing." + +# --- derive ARCH and version from the .config's generated header ------------- +# e.g. "# Linux/arm 6.18.48 Kernel Configuration" +header=$(sed -n 's|^# Linux/\([a-z0-9_]*\) \([^ ]*\) Kernel Configuration$|\1 \2|p' \ + "$BASE_CONFIG" | head -n1) +[ -n "$header" ] || die "cannot read the arch/version header from $BASE_CONFIG" +ARCH=${header% *} +KERNELVERSION=${header#* } + +# --- find the cross compiler the tree was built with, if we can -------------- +# Only used so that toolchain-derived symbols resolve the same way in BOTH +# runs; since the two runs are compared to each other, a fallback is harmless. +CC_BIN=${CC:-} +LD_BIN=${LD:-} +if [ -z "$CC_BIN" ]; then + host_bin=$(cd -- "$TREE/../.." 2>/dev/null && pwd)/host/bin + for candidate in "$host_bin"/*-linux-*-gcc; do + [ -x "$candidate" ] || continue + CC_BIN=$candidate + LD_BIN=${candidate%-gcc}-ld + break + done +fi +[ -n "$CC_BIN" ] || CC_BIN=gcc +[ -n "$LD_BIN" ] || LD_BIN=ld + +WORK=$(mktemp -d -t kfragnoop.XXXXXXXX) +# shellcheck disable=SC2329 # invoked indirectly, by the EXIT trap below +cleanup() { [ "$KEEP" -eq 1 ] || rm -rf "$WORK"; } +trap cleanup EXIT +mkdir -p "$WORK/control" "$WORK/test" + +export ARCH SRCARCH="$ARCH" KERNELVERSION +# srctree is how kconfig's `conf` finds the top-level Kconfig (and every +# `source`d file) when run from a directory other than the kernel tree: +# zconf_fopen() retries a relative path under $srctree. That is why the two +# olddefconfig runs below can `cd` into scratch dirs that contain only a +# .config and still name a bare "Kconfig". +export srctree="$TREE" +export CC="$CC_BIN" LD="$LD_BIN" +export HOSTCC=${HOSTCC:-gcc} HOSTCXX=${HOSTCXX:-g++} + +printf '%s: tree %s\n' "$PROG" "$TREE" +printf '%s: arch %s (kernel %s)\n' "$PROG" "$ARCH" "$KERNELVERSION" +printf '%s: fragment %s\n' "$PROG" "$FRAGMENT" +printf '\n' + +# --- CONTROL: resolve the tree's own config, untouched ----------------------- +cp -- "$BASE_CONFIG" "$WORK/control/.config" +( cd "$WORK/control" && "$CONF" --olddefconfig Kconfig ) > "$WORK/control.log" 2>&1 \ + || { cat "$WORK/control.log" >&2; die "control olddefconfig failed"; } + +# --- TEST: same config with the fragment merged in --------------------------- +cp -- "$BASE_CONFIG" "$WORK/test/base.config" +printf '===== merge_config.sh -m =====\n' +( cd "$WORK/test" && "$MERGE" -m -O "$WORK/test" base.config "$FRAGMENT" ) \ + > "$WORK/merge.log" 2>&1 || { cat "$WORK/merge.log" >&2; die "merge_config.sh failed"; } +cat "$WORK/merge.log" +printf '===== end merge_config.sh =====\n\n' + +( cd "$WORK/test" && "$CONF" --olddefconfig Kconfig ) > "$WORK/test.log" 2>&1 \ + || { cat "$WORK/test.log" >&2; die "test olddefconfig failed"; } + +status=0 + +if grep -q 'is redefined by fragment' "$WORK/merge.log"; then + printf '%s: FAIL: merge_config.sh reported a REDEFINITION.\n' "$PROG" >&2 + grep -n 'is redefined by fragment' "$WORK/merge.log" >&2 + printf ' Either the fragment genuinely disagrees with %s,\n' "$BASE_CONFIG" >&2 + printf ' or a COMMENT in the fragment names a symbol the fragment also sets\n' >&2 + printf ' (merge_config greps prose too -- rule 5 in the fragment header).\n' >&2 + status=1 +fi + +if ! diff -u "$WORK/control/.config" "$WORK/test/.config" > "$WORK/drift.diff"; then + printf '%s: FAIL: the fragment CHANGES the resolved config of this tree.\n' "$PROG" >&2 + printf ' Every line of %s must be\n' "$FRAGMENT" >&2 + printf ' byte-identical to the corresponding line in this board resolved\n' >&2 + printf ' config, or the two boards no longer share one driver set.\n' >&2 + printf ' --- control (tree as built) vs test (tree + fragment) ---\n' >&2 + cat "$WORK/drift.diff" >&2 + status=1 +fi + +if [ "$status" -eq 0 ]; then + printf '%s: PASS: fragment is a no-op on %s\n' "$PROG" "$TREE" + printf '%s: zero redefinitions, resolved config identical.\n' "$PROG" +fi + +if [ "$KEEP" -eq 1 ]; then + printf '%s: scratch kept at %s\n' "$PROG" "$WORK" +fi + +exit "$status" diff --git a/scripts/check-linux-img.sh b/scripts/check-linux-img.sh index 004a684..80cf7cf 100755 --- a/scripts/check-linux-img.sh +++ b/scripts/check-linux-img.sh @@ -5,14 +5,14 @@ # Two independent things this proves about a built linux.img, neither of # which "it mounted fine once" would catch: # -# 1. PINNED EXT4 FEATURE SET / LABEL / UUID. configs/mister_de10nano_defconfig's -# BR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS spells these out explicitly (its own -# header explains why: e2fsprogs' own mke2fs.conf defaults already drift +# 1. PINNED EXT4 FEATURE SET / LABEL / UUID. configs/fragments/de10nano-image.fragment's +# BR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS spells these out explicitly +# (docs/buildroot-config.md §5.2 explains why: e2fsprogs' own mke2fs.conf defaults already drift # from what stock's linux.img actually has, and a future e2fsprogs bump # could drift further). This script is the build-time proof that the # option string actually landed in the artifact, the same role # check-zimage-dtb.sh plays for the zImage/DTB concatenation contract. -# The expected values below MUST be kept in sync with that defconfig by +# The expected values below MUST be kept in sync with that fragment by # hand -- there is no single source of truth to derive them from at # build time (dumpe2fs reads the finished image, not the mkfs command # line that produced it). @@ -38,7 +38,7 @@ set -eu prog=${0##*/} fail=0 -# --- the pinned contract -- MUST match configs/mister_de10nano_defconfig ---- +# --- the pinned contract -- MUST match configs/fragments/de10nano-image.fragment ---- EXPECT_LABEL="rootfs" EXPECT_UUID="71916572-439f-448e-b8d8-12b0a032fa56" EXPECT_HASH_SEED="9afc615c-c310-4e03-ada9-613522e83ae6" diff --git a/scripts/check-sdcard-de25.sh b/scripts/check-sdcard-de25.sh new file mode 100755 index 0000000..1bc1250 --- /dev/null +++ b/scripts/check-sdcard-de25.sh @@ -0,0 +1,675 @@ +#!/bin/sh +# +# check-sdcard-de25.sh — static verification of a built `sdcard-de25.img` +# (D2.4). No hardware, no boot: everything below is asserted from the raw image +# file alone, the same posture as check-sdcard.sh / check-linux-img.sh / +# check-zimage-dtb.sh. +# +# THIS IS NOT scripts/check-sdcard.sh WITH DIFFERENT CONSTANTS, and it must +# never be refactored into one. That script asserts the Cyclone V BootROM's +# contract: MBR p1 = FAT32, MBR p2 = type 0xA2 whose raw head is a byte-exact +# `uboot.img`. On Agilex 5 that mechanism does not exist — the SDM boots the +# FSBL out of QSPI and the FSBL reads a FILESYSTEM ("No 0xA2 analogue [V]", +# docs/de25-boot-chain.md §2). The two checkers assert opposite things about +# partition 2 on purpose; see docs/de25-readiness-ledger.md coupling (b), which +# says in as many words: write a sibling checker for the Agilex layout instead +# of relaxing the DE10 constants. +# +# The assertions, and why each one exists: +# +# 1. PARTITION TABLE IS MBR, WITH EXACTLY TWO PARTITIONS, AND NO 0xA2. +# MBR because that is the only partition-table type any evidence says a +# DE25-Nano has been seen booting from (board/mister/de25nano/ +# genimage-sdcard.cfg's `partition-table-type` note has the full argument +# and its two citations). Exactly two, because ADR 0029 D3 fixes the +# partition COUNT — a third partition means somebody re-opened a settled +# decision without saying so. No 0xA2, because a 0xA2 partition here is +# DE10 lore transplanted onto a board whose BootROM does not scan for it: +# harmless in itself, and a reliable sign that the DE10's genimage config +# or its `updateboot` habits are being cargo-culted across. +# +# 2. p1 IS FAT32, LABELLED, AND HOLDS THE FOUR FILES THE BOOT CHAIN NEEDS. +# `u-boot.itb` is the whole interface between this card and the board's +# boot firmware: the factory SPL loads it BY NAME from a FAT filesystem on +# partition 1 (CONFIG_SPL_FS_FAT=y, SYS_MMCSD_FS_BOOT_PARTITION=1, +# docs/de25-boot-chain.md §2 step 4 / §8.3). `Image`, the DTB and +# `extlinux/extlinux.conf` are what U-Boot proper then needs. FAT32 +# specifically — not FAT16 — because the partition-type byte says 0x0c and +# a type byte that lies about its filesystem is how a card boots on one +# reader and not another. +# +# p1 IS AN ALLOW-LIST, NOT A REQUIRED-SET: anything beyond those four +# entries fails the image. U-Boot's distro boot runs `scan_dev_for_scripts` +# immediately after `scan_dev_for_extlinux` on the SAME partition, so a +# stray `/boot.scr` is not decoration — it executes the moment extlinux +# fails, before any prompt, with the whole U-Boot command set available. A +# `uboot.env` is refused for a different reason: the card deliberately +# ships none (docs/de25-uboot.md §10), and a seeded one would override the +# compiled-in environment on every already-written card, forever. +# +# 3. u-boot.itb IS THE FIT THIS BUILD PRODUCED, AND ONE THE FACTORY SPL CAN +# ACTUALLY EXECUTE. Three assertions, because the file being present under +# the right name proves nothing about what is inside it: (a) byte-identical +# to the build's own `u-boot.itb`; (b) `dumpimage -l` shows the +# de25-uboot.md §6.1 contract — images `uboot` (load 0x80200000), `atf` +# (load 0x80000000), `fdt-0`, and a default configuration signed `crc32`; +# (c) the decompiled FIT declares no `rsa`/`required`/`sha` +# verification. (c) matters most and is the least obvious: the factory SPL +# is built with `CONFIG_SPL_FIT_SIGNATURE=y` and its control DTB carries NO +# KEYS (boot-chain §7 row 6, §8.3), so a FIT demanding key verification +# strands the board at SPL on EVERY boot — which, with no serial console +# attached, is indistinguishable from a bad card. +# +# 4. extlinux.conf PARSES, NAMES THE RIGHT ROOT DEVICE AND CONSOLE, AND +# MENTIONS NO FLASH. `root=/dev/mmcblk0p2` is the interim p2 decision +# (docs/de25-sdcard.md); `console=ttyS0,115200` is HPS uart1, the only +# enabled 8250 port on this board, and getting it wrong produces a board +# that looks dead rather than one that prints an error. The `sf probe` / +# `ubi` / `mtd` grep is the paper-thin but real enforcement of "nothing on +# this card references QSPI": a QSPI write on this board is brick-class, +# recoverable only with JTAG and a PC, with no RSU safety net +# (docs/de25-boot-chain.md §6, §7 rows 1/5/10/11/12). +# +# 5. p2 IS A CLEAN ext4 LABELLED `rootfs`. Written verbatim from Buildroot's +# rootfs.ext4 (BR2_TARGET_ROOTFS_EXT2 + _EXT2_4). `e2fsck -fn` is the cheap +# proof that the bytes genimage copied are a filesystem and not a truncated +# one; the `extent` feature is what distinguishes an actual ext4 from an +# ext2 image that merely got named .ext4. +# +# 6. THE IMAGE FITS A BUDGET. p1 (256 MiB) + p2 (rootfs.ext4, 256 MiB today) + +# 1 MiB of alignment ≈ 513 MiB. $EXPECT_MAX_IMAGE_BYTES defaults to 768 MiB: +# enough headroom that a modest rootfs bump does not trip it, tight enough +# that a runaway one does. Raise it deliberately, in the commit that grows +# the rootfs — never to make a red run go green. +# +# Usage: +# scripts/check-sdcard-de25.sh [reference-images-dir] +# +# [reference-images-dir] where the PRISTINE build artifacts live — the +# `u-boot.itb` that assertion 3a compares against. +# Defaults to $DE25_REF_DIR, then $BINARIES_DIR, then +# the image's own directory (Buildroot puts both in +# BINARIES_DIR), then /output-de25/images. +# +# Environment overrides (all optional; each is pinned, not derived, and must be +# kept in sync BY HAND with board/mister/de25nano/genimage-sdcard.cfg and +# board/mister/de25nano/post-image.sh — there is no source of truth to read +# them from at check time, the same caveat check-linux-img.sh's header gives): +# $EXPECT_FAT_LABEL default DE25BOOT +# $EXPECT_ROOTFS_LABEL default rootfs +# $EXPECT_DTB_NAME default socfpga_agilex5_de25nano.dtb +# $EXPECT_ROOT_DEV default /dev/mmcblk0p2 +# $EXPECT_CONSOLE default ttyS0,115200 +# $EXPECT_MAX_IMAGE_BYTES default 805306368 (768 MiB) +# $MIN_BOOT_PART_SECTORS default 524288 (256 MiB) +# +# Host tools: sfdisk (util-linux), mtools (mdir/mcopy/mlabel), e2fsprogs +# (dumpe2fs/e2fsck), u-boot-tools (dumpimage), dtc, dd, cmp. Resolved from +# $DE25_HOST_DIR/{bin,sbin}, then BINARIES_DIR/../host/{bin,sbin}, then this +# repo's output-de25/host/{bin,sbin}, then PATH — so a Buildroot build that +# produced the image can always check it: BR2_PACKAGE_HOST_GENIMAGE pulls in +# host-mtools and host-dosfstools, BR2_TARGET_ROOTFS_EXT2 pulls in +# host-e2fsprogs, and BR2_TARGET_UBOOT's FIT support pulls in host-uboot-tools +# (dumpimage) and host-dtc. dumpimage is NOT a distro tool, which is exactly +# why the search list is longer than a bare PATH lookup: an unfindable +# dumpimage would leave assertion 3 unrunnable, and a checker that skips the +# FIT is how a card ships carrying a FIT nobody ever opened. A missing tool is +# exit 2, never a silent skip. Unlike check-sdcard.sh there is NO root +# loop-mount fallback — mtools has never been optional for anyone who can +# build this image. +# +# Exit: 0 = all assertions pass; 1 = a contract violation; 2 = usage/IO/tooling +# error. + +set -eu + +prog=${0##*/} +fail=0 + +# --- pinned layout contract -------------------------------------------------- +BOOT_PART_NUM=1 +BOOT_PART_TYPE=c # 0x0c, FAT32 LBA -- genimage-sdcard.cfg +ROOTFS_PART_NUM=2 +ROOTFS_PART_TYPE=83 # 0x83, Linux -- genimage-sdcard.cfg +EXPECT_PART_COUNT=2 # ADR 0029 D3: two partitions, full stop +FORBIDDEN_PART_TYPE=a2 # the Cyclone V BootROM's type byte; alien here + +: "${EXPECT_FAT_LABEL:=DE25BOOT}" +: "${EXPECT_ROOTFS_LABEL:=rootfs}" +: "${EXPECT_DTB_NAME:=socfpga_agilex5_de25nano.dtb}" +: "${EXPECT_ROOT_DEV:=/dev/mmcblk0p2}" +: "${EXPECT_CONSOLE:=ttyS0,115200}" +: "${EXPECT_MAX_IMAGE_BYTES:=805306368}" +: "${MIN_BOOT_PART_SECTORS:=524288}" + +EXPECT_KERNEL_NAME=Image +EXPECT_FIT_NAME=u-boot.itb +EXTLINUX_PATH=extlinux/extlinux.conf + +# Substrings that must not appear anywhere in extlinux.conf. Matched +# case-insensitively and as plain substrings, deliberately over-broad: there is +# no legitimate word containing "ubi" or "mtd" in a boot configuration for a +# board whose flash we are forbidden to touch. +QSPI_FORBIDDEN='sf probe +ubi +mtd' + +note() { printf ' %s\n' "$*"; } +ok() { printf 'ok %s\n' "$*"; } +bad() { printf 'FAIL %s\n' "$*" >&2; fail=1; } + +usage() { + echo "usage: $prog [reference-images-dir]" >&2 + exit 2 +} + +[ $# -ge 1 ] && [ $# -le 2 ] || usage +img=$1 +ref_dir_arg=${2:-} +[ -f "$img" ] || { echo "$prog: no such file: $img" >&2; exit 2; } + +img_dir=$(CDPATH='' cd -- "$(dirname -- "$img")" && pwd) +script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +repo_root=$(CDPATH='' cd -- "$script_dir/.." && pwd) + +# --- where the PRISTINE build artifacts live (assertion 2a's reference) ------ +# In a real build this is BINARIES_DIR, which is also where sdcard-de25.img +# itself lands -- so $img_dir is the right answer almost always. The explicit +# arg and $DE25_REF_DIR exist for checking an image that has been moved away +# from its build tree (a copy under test, a release artifact). +ref_dir=${ref_dir_arg:-${DE25_REF_DIR:-${BINARIES_DIR:-}}} +if [ -z "$ref_dir" ]; then + if [ -f "$img_dir/$EXPECT_FIT_NAME" ]; then + ref_dir=$img_dir + else + ref_dir="$repo_root/output-de25/images" + fi +fi + +# --- host tools ------------------------------------------------------------- +# Searched in order: $DE25_HOST_DIR (an explicit Buildroot HOST_DIR), the one +# implied by the image's own location (BINARIES_DIR/../host, Buildroot's +# layout), this repo's output-de25/host, then PATH. dumpimage in particular is +# NOT a distro tool -- it comes from host-uboot-tools, which the DE25 build +# enables for exactly this purpose (docs/de25-uboot.md §10) -- so PATH alone +# would leave assertion 2b silently unrunnable, which is how a checker ends up +# passing a FIT it never opened. +tool_dirs="${DE25_HOST_DIR:+$DE25_HOST_DIR/bin $DE25_HOST_DIR/sbin} \ +$img_dir/../host/bin $img_dir/../host/sbin \ +$repo_root/output-de25/host/bin $repo_root/output-de25/host/sbin" + +find_tool() { # find_tool NAME -> absolute path, or exit 2 + for _d in $tool_dirs; do + if [ -x "$_d/$1" ]; then + echo "$_d/$1" + return 0 + fi + done + if command -v "$1" >/dev/null 2>&1; then + command -v "$1" + return 0 + fi + echo "$prog: cannot find '$1' (looked in $tool_dirs and PATH)" >&2 + exit 2 +} + +sfdisk_bin=$(find_tool sfdisk) +mdir_bin=$(find_tool mdir) +mcopy_bin=$(find_tool mcopy) +mlabel_bin=$(find_tool mlabel) +dumpe2fs_bin=$(find_tool dumpe2fs) +e2fsck_bin=$(find_tool e2fsck) +dumpimage_bin=$(find_tool dumpimage) +dtc_bin=$(find_tool dtc) +command -v dd >/dev/null 2>&1 || { echo "$prog: dd not found on PATH" >&2; exit 2; } +command -v cmp >/dev/null 2>&1 || { echo "$prog: cmp not found on PATH" >&2; exit 2; } + +work=$(mktemp -d "${TMPDIR:-/tmp}/check-sdcard-de25.XXXXXX") +# shellcheck disable=SC2329 # invoked indirectly via `trap cleanup EXIT` below +cleanup() { rm -rf "$work"; } +trap cleanup EXIT + +img_bytes=$(wc -c <"$img" | tr -d ' ') +printf '%s: %s (%s bytes)\n' "$prog" "$img" "$img_bytes" + +# ============================================================================= +# 0. Size budget +# ============================================================================= +if [ "$img_bytes" -le "$EXPECT_MAX_IMAGE_BYTES" ]; then + ok "image size $img_bytes bytes <= \$EXPECT_MAX_IMAGE_BYTES ($EXPECT_MAX_IMAGE_BYTES)" +else + bad "image size $img_bytes bytes EXCEEDS \$EXPECT_MAX_IMAGE_BYTES ($EXPECT_MAX_IMAGE_BYTES) -- raise the budget deliberately, in the commit that grew the card" +fi + +# ============================================================================= +# 1. Partition table: MBR, exactly two partitions, no 0xA2 +# ============================================================================= +dump=$("$sfdisk_bin" -d "$img" 2>/dev/null) || + { echo "$prog: sfdisk -d failed on $img (not a partitioned image?)" >&2; exit 2; } + +# sfdisk -d prints `label: dos` for MBR and `label: gpt` for GPT. +label_line=$(printf '%s\n' "$dump" | sed -n 's/^label:[[:space:]]*//p' | head -n1) +if [ "$label_line" = "dos" ]; then + ok "partition table is MBR/dos (the only type a DE25-Nano has been observed booting from -- genimage-sdcard.cfg's partition-table-type note)" +else + bad "partition table is '$label_line', expected 'dos' (MBR). GPT support in the FACTORY SPL is unproven, not disproven -- changing this needs a hardware retest, not a desk decision" +fi + +# Independent of sfdisk's opinion: a real GPT (or a protective-MBR hybrid) +# carries the "EFI PART" signature at LBA 1. Checked separately so a +# hybrid/protective layout cannot pass by looking like plain dos above. +gpt_sig=$(dd if="$img" bs=1 skip=512 count=8 status=none 2>/dev/null | tr -d '\0' || true) +if [ "$gpt_sig" = "EFI PART" ]; then + bad "a GPT header signature ('EFI PART') is present at LBA 1 -- this image is GPT or MBR/GPT hybrid" +else + ok "no GPT header signature at LBA 1" +fi + +# One "N : start=..., size=..., type=..." line per MBR slot, in +# partition-table order, so the Nth match is unambiguously MBR partition N +# regardless of how sfdisk names the device from our image path. +part_lines=$(printf '%s\n' "$dump" | grep -E '^[^[:space:]]+[0-9]+[[:space:]]*:.*start=') || true +part_count=$(printf '%s\n' "$part_lines" | grep -c . || true) +note "sfdisk -d reports $part_count partition(s)" + +if [ "$part_count" -eq "$EXPECT_PART_COUNT" ]; then + ok "exactly $EXPECT_PART_COUNT partitions (ADR 0029 D3)" +else + bad "$part_count partition(s), expected exactly $EXPECT_PART_COUNT (ADR 0029 D3 fixes the partition count)" +fi + +field() { # field LINE NAME -> value + printf '%s\n' "$1" | sed -n "s/.*[[:space:]]$2=[[:space:]]*\\([^,[:space:]]*\\).*/\\1/p" +} +get_part() { # get_part N -> the Nth partition-table line, or empty + printf '%s\n' "$part_lines" | sed -n "${1}p" +} + +a2_seen=0 +_n=0 +while [ "$_n" -lt "$part_count" ]; do + _n=$(( _n + 1 )) + _t=$(field "$(get_part "$_n")" type | tr 'A-F' 'a-f') + [ "$_t" = "$FORBIDDEN_PART_TYPE" ] && a2_seen=1 +done +if [ "$a2_seen" -eq 0 ]; then + ok "no 0x$FORBIDDEN_PART_TYPE partition (there is no BootROM raw-partition scan on Agilex 5 -- boot-chain §2 'No 0xA2 analogue')" +else + bad "a 0x$FORBIDDEN_PART_TYPE partition is present -- that is the Cyclone V BootROM's contract (docs/boot-chain.md §2.1) and it means nothing on this board. DE10 layout lore has been cargo-culted here" +fi + +boot_line=$(get_part "$BOOT_PART_NUM") +rootfs_line=$(get_part "$ROOTFS_PART_NUM") + +boot_start="" +if [ -z "$boot_line" ]; then + bad "MBR partition $BOOT_PART_NUM (FAT boot) not found" +else + boot_start=$(field "$boot_line" start) + boot_size=$(field "$boot_line" size) + boot_type=$(field "$boot_line" type | tr 'A-F' 'a-f') + note "partition $BOOT_PART_NUM: start=$boot_start size=$boot_size(sectors) type=$boot_type" + if [ "$boot_type" = "$BOOT_PART_TYPE" ]; then + ok "partition $BOOT_PART_NUM type=0x$BOOT_PART_TYPE (FAT32 LBA) -- the partition SYS_MMCSD_FS_BOOT_PARTITION=1 makes the factory SPL read" + else + bad "partition $BOOT_PART_NUM type='$boot_type', expected '$BOOT_PART_TYPE' (0x0c FAT32 LBA)" + fi + if [ "$boot_size" -ge "$MIN_BOOT_PART_SECTORS" ]; then + ok "partition $BOOT_PART_NUM size $boot_size sectors >= $MIN_BOOT_PART_SECTORS (256 MiB floor)" + else + bad "partition $BOOT_PART_NUM size $boot_size sectors < $MIN_BOOT_PART_SECTORS (256 MiB floor)" + fi +fi + +rootfs_start="" +rootfs_size="" +if [ -z "$rootfs_line" ]; then + bad "MBR partition $ROOTFS_PART_NUM (ext4 rootfs) not found" +else + rootfs_start=$(field "$rootfs_line" start) + rootfs_size=$(field "$rootfs_line" size) + rootfs_type=$(field "$rootfs_line" type | tr 'A-F' 'a-f') + note "partition $ROOTFS_PART_NUM: start=$rootfs_start size=$rootfs_size(sectors) type=$rootfs_type" + if [ "$rootfs_type" = "$ROOTFS_PART_TYPE" ]; then + ok "partition $ROOTFS_PART_NUM type=0x$ROOTFS_PART_TYPE (Linux)" + else + bad "partition $ROOTFS_PART_NUM type='$rootfs_type', expected '$ROOTFS_PART_TYPE' (0x83 Linux)" + fi +fi + +# ============================================================================= +# 2. p1: FAT32, labelled, and the four files +# ============================================================================= +mt() { # mt TOOL ARGS... -- run an mtools binary against p1 at its byte offset + _tool=$1; shift + MTOOLS_SKIP_CHECK=1 "$_tool" -i "${img}@@${boot_offset}" "$@" +} + +if [ -z "$boot_start" ]; then + bad "cannot inspect p1 -- partition $BOOT_PART_NUM was not found above" +else + boot_offset=$(( boot_start * 512 )) + + # FAT width, read straight out of the BPB rather than trusted from the + # partition-type byte: FAT32's filesystem-type string lives at offset 0x52 + # of the boot sector (FAT12/16 put theirs at 0x36 instead), so this is the + # one place the image itself says which it is. + fat_type=$(dd if="$img" bs=1 skip=$(( boot_offset + 82 )) count=8 status=none 2>/dev/null | tr -d '\0' || true) + case $fat_type in + FAT32*) + ok "p1 filesystem is FAT32 (BPB fs-type = '$fat_type'), matching its 0x0c type byte" + ;; + *) + bad "p1 BPB fs-type at offset 0x52 is '$fat_type', not FAT32 -- mkfs.vfat picks FAT16 at this size unless '-F 32' is forced (genimage-sdcard.cfg extraargs)" + ;; + esac + + if mt "$mlabel_bin" -s :: > "$work/mlabel.out" 2> "$work/mlabel.err"; then + # mlabel prints " Volume label is DE25BOOT " -- note the LEADING + # space and the FAT 11-character padding on the right. Both are + # stripped here; anchoring on a bare "^Volume" silently never matches. + fat_label=$(sed -n 's/^[[:space:]]*Volume label is[[:space:]]*//p' "$work/mlabel.out" \ + | head -n1 | sed 's/[[:space:]]*$//') + if [ "$fat_label" = "$EXPECT_FAT_LABEL" ]; then + ok "p1 volume label = '$EXPECT_FAT_LABEL'" + else + bad "p1 volume label = '${fat_label:-}', expected '$EXPECT_FAT_LABEL'" + fi + else + bad "could not read p1's volume label (mlabel failed)" + sed 's/^/ /' "$work/mlabel.err" >&2 + fi + + actual="$work/p1-inventory.txt" + if mt "$mdir_bin" -b -/ :: > "$work/mdir.out" 2> "$work/mdir.err"; then + tr -d '\r' < "$work/mdir.out" \ + | sed -e 's#^::##' -e 's#^/##' -e '/^$/d' \ + | LC_ALL=C sort -u > "$actual" + note "p1 holds $(wc -l < "$actual" | tr -d ' ') entries" + for want in "$EXPECT_FIT_NAME" "$EXPECT_KERNEL_NAME" "$EXPECT_DTB_NAME" "$EXTLINUX_PATH"; do + if grep -qxF "$want" "$actual"; then + ok "p1 holds $want" + else + bad "p1 is MISSING $want" + fi + done + # p1 IS AN ALLOW-LIST, NOT A REQUIRED-SET. Anything not on the list + # below fails the image, and this is the assertion with the sharpest + # teeth on the card. + # + # Why extras cannot be "informational": U-Boot's distro boot runs + # `scan_dev_for_scripts` IMMEDIATELY AFTER `scan_dev_for_extlinux` on + # the same partition. So a stray `/boot.scr` is not inert decoration + # -- it is code that executes the moment extlinux fails for any + # reason, before anyone gets a prompt, with the full U-Boot command + # set available to it. A `boot.scr` containing a flash-erase command + # is a brick-class payload that this checker used to wave through. + # + # `uboot.env` is on the list of named offenders for a different + # reason: the card DELIBERATELY ships no environment file + # (docs/de25-uboot.md §10). A seeded one silently OVERRIDES the + # compiled-in environment forever after, so the next release's + # bootcmd/bootargs/boot_targets change would be ignored on every + # already-written card. Its appearance means somebody re-opened that + # decision without saying so. + # + # When p1 legitimately grows a file -- a core.rbf, say -- add it here + # and to genimage-sdcard.cfg's `files` list in the same commit. That + # is the point: growing the card is a decision, not an accident. + extras=$(grep -vxF -e "$EXPECT_FIT_NAME" -e "$EXPECT_KERNEL_NAME" \ + -e "$EXPECT_DTB_NAME" -e "$EXTLINUX_PATH" -e 'extlinux/' "$actual" || true) + if [ -z "$extras" ]; then + ok "p1 holds nothing beyond the four expected files (no boot.scr, no *.scr, no uboot.env)" + else + bad "p1 holds entries outside the allow-list -- p1 is an allow-list, not a required-set:" + printf '%s\n' "$extras" | sed 's/^/ + /' >&2 + note "A stray boot.scr / *.scr EXECUTES: distro boot runs scan_dev_for_scripts" + note " right after scan_dev_for_extlinux on this same partition, so it runs the" + note " moment extlinux fails -- with the whole U-Boot command set available." + note "A uboot.env must not ship either: the card deliberately has none" + note " (docs/de25-uboot.md §10); a seeded one overrides the compiled-in" + note " environment on every already-written card, forever." + note "If p1 is meant to grow a file, add it to this allow-list AND to" + note " genimage-sdcard.cfg's files list in the same commit." + fi + else + bad "could not read p1's file inventory (mdir failed -- is p1 a FAT filesystem at all?)" + sed 's/^/ /' "$work/mdir.err" >&2 + : > "$actual" + fi + + # --------------------------------------------------------------------- + # 3. u-boot.itb -- the file the factory SPL actually executes + # --------------------------------------------------------------------- + # Three independent assertions, because each of the first two can be + # satisfied by something that still does not boot: + # + # (a) byte-identical to the build's own u-boot.itb. Catches a swapped, + # truncated or hand-edited FIT outright. + # (b) dumpimage -l: the contract terms from docs/de25-uboot.md §6.1 -- + # images `uboot` (load 0x80200000), `atf` (load 0x80000000) and + # `fdt-0`, plus a default configuration whose signature algo is + # crc32. A FIT that parses but loads U-Boot at the wrong address is + # a silent no-boot. + # (c) NO key-based verification anywhere. The factory SPL is built with + # CONFIG_SPL_FIT_SIGNATURE=y and its control DTB carries NO KEYS + # (docs/de25-boot-chain.md §7 row 6, §8.3), so a FIT that DEMANDS a + # verification the SPL cannot perform strands the board at SPL on + # every boot -- and without a serial console that is + # indistinguishable from a bad card. Grepping the decompiled FIT for + # rsa/required/sha is cruder than parsing it, deliberately: it + # catches the property in any spelling, on images as well as on + # configurations, including nodes dumpimage does not summarise. + itb="$work/u-boot.itb" + if mt "$mcopy_bin" "::/$EXPECT_FIT_NAME" "$itb" > "$work/mcopy-itb.out" 2> "$work/mcopy-itb.err"; then + + # (a) ------------------------------------------------------------- + ref_itb="$ref_dir/$EXPECT_FIT_NAME" + if [ -f "$ref_itb" ]; then + if cmp -s "$itb" "$ref_itb"; then + ok "p1's $EXPECT_FIT_NAME is byte-identical to $ref_itb" + else + bad "p1's $EXPECT_FIT_NAME DIFFERS from $ref_itb -- the card carries a FIT this build did not produce" + fi + else + bad "no reference $EXPECT_FIT_NAME at $ref_itb -- cannot prove the card's FIT is the one this build produced (pass the images dir as argument 2, or set \$DE25_REF_DIR)" + fi + + # (b) ------------------------------------------------------------- + if "$dumpimage_bin" -l "$itb" > "$work/dumpimage.out" 2> "$work/dumpimage.err"; then + di="$work/dumpimage.out" + + check_fit_image() { # check_fit_image NAME EXPECTED_LOAD + if ! grep -qE "^ Image [0-9]+ \($1\)\$" "$di"; then + bad "$EXPECT_FIT_NAME has no image named '$1' (factory-SPL FIT contract, de25-uboot.md §6.1)" + return + fi + _load=$(awk -v n="$1" ' + $0 ~ "^ Image [0-9]+ \\(" n "\\)$" { inb = 1; next } + inb && /^ (Image|Default|Configuration)/ { inb = 0 } + inb && /Load Address:/ { print $3; exit }' "$di") + if [ "$_load" = "$2" ]; then + ok "$EXPECT_FIT_NAME image '$1' loads at $2" + else + bad "$EXPECT_FIT_NAME image '$1' loads at '${_load:-}', expected $2" + fi + } + + check_fit_image uboot 0x80200000 + check_fit_image atf 0x80000000 + + if grep -qE '^ Image [0-9]+ \(fdt-0\)$' "$di"; then + ok "$EXPECT_FIT_NAME has the 'fdt-0' image" + else + bad "$EXPECT_FIT_NAME has no 'fdt-0' image" + fi + + def_cfg=$(sed -n "s/^ Default Configuration: '\\(.*\\)'\$/\\1/p" "$di" | head -n1) + if [ -n "$def_cfg" ]; then + ok "$EXPECT_FIT_NAME names a default configuration: '$def_cfg'" + else + bad "$EXPECT_FIT_NAME names no default configuration -- board_fit_config_name_match() falls back to /configurations/default, and there would be none" + fi + + sign_algo=$(sed -n 's/^ Sign algo:[[:space:]]*//p' "$di" | head -n1) + case $sign_algo in + crc32*) + ok "$EXPECT_FIT_NAME signature algo is crc32 ('$sign_algo') -- an integrity stamp, no keys" ;; + '') + bad "$EXPECT_FIT_NAME's default configuration carries no signature node at all (expected a crc32 integrity stamp)" ;; + *) + bad "$EXPECT_FIT_NAME signature algo is '$sign_algo', expected crc32 -- the factory SPL has FIT_SIGNATURE on with NO keys, so anything else is a card that strands at SPL on every boot" ;; + esac + else + bad "dumpimage -l could not parse p1's $EXPECT_FIT_NAME -- it is not a FIT image" + sed 's/^/ /' "$work/dumpimage.err" >&2 + fi + + # (c) ------------------------------------------------------------- + if "$dtc_bin" -I dtb -O dts -o "$work/itb.dts" "$itb" >/dev/null 2>&1; then + if key_hits=$(grep -inE 'rsa|required|sha[0-9]' "$work/itb.dts"); then + bad "$EXPECT_FIT_NAME declares key-based verification -- the factory SPL has no keys and would refuse to boot it:" + printf '%s\n' "$key_hits" | sed 's/^/ /' >&2 + else + ok "$EXPECT_FIT_NAME declares no rsa/required/sha verification" + fi + else + bad "dtc could not decompile p1's $EXPECT_FIT_NAME as a device tree -- a FIT *is* a DTB, so this is not one" + fi + else + bad "could not read $EXPECT_FIT_NAME from p1" + sed 's/^/ /' "$work/mcopy-itb.err" >&2 + fi + + # --------------------------------------------------------------------- + # 4. extlinux.conf + # --------------------------------------------------------------------- + conf="$work/extlinux.conf" + if mt "$mcopy_bin" "::/$EXTLINUX_PATH" "$conf" > "$work/mcopy.out" 2> "$work/mcopy.err"; then + conf_body=$(tr -d '\r' < "$conf" | sed -e 's/^[[:space:]]*//' -e '/^#/d' -e '/^$/d') + + default_label=$(printf '%s\n' "$conf_body" | sed -n 's/^default[[:space:]]\{1,\}//p' | head -n1) + if [ -n "$default_label" ]; then + ok "extlinux.conf names a default entry: '$default_label'" + if printf '%s\n' "$conf_body" | grep -qE "^label[[:space:]]+$default_label\$"; then + ok "extlinux.conf has a matching 'label $default_label' block" + else + bad "extlinux.conf's 'default $default_label' names a label that does not exist -- U-Boot would find no entry to boot" + fi + else + bad "extlinux.conf has no 'default' directive" + fi + + for key in kernel fdt append; do + if printf '%s\n' "$conf_body" | grep -qE "^${key}[[:space:]]"; then + ok "extlinux.conf has a '$key' directive" + else + bad "extlinux.conf has no '$key' directive" + fi + done + + # The kernel and DTB it names must actually be on this partition. + for key in kernel fdt; do + val=$(printf '%s\n' "$conf_body" | sed -n "s/^${key}[[:space:]]\{1,\}//p" | head -n1) + rel=${val#/} + if [ -z "$rel" ]; then + continue + elif grep -qxF "$rel" "$actual"; then + ok "extlinux.conf's $key '$val' exists on p1" + else + bad "extlinux.conf's $key '$val' is NOT on p1 -- U-Boot would abort mid-boot" + fi + done + + append=$(printf '%s\n' "$conf_body" | sed -n 's/^append[[:space:]]\{1,\}//p' | head -n1) + note "append = $append" + case " $append " in + *" root=$EXPECT_ROOT_DEV "*) + ok "kernel args name root=$EXPECT_ROOT_DEV (the interim p2 decision -- docs/de25-sdcard.md)" ;; + *) bad "kernel args do not name root=$EXPECT_ROOT_DEV" ;; + esac + case " $append " in + *" console=$EXPECT_CONSOLE "*) + ok "kernel args name console=$EXPECT_CONSOLE (HPS uart1, the board's header UART)" ;; + *) bad "kernel args do not name console=$EXPECT_CONSOLE -- a wrong console makes the board look dead rather than print an error" ;; + esac + case " $append " in + *" rootwait "*) ok "kernel args include rootwait (the SD controller probes asynchronously)" ;; + *) bad "kernel args do not include rootwait -- the root device may not exist yet when the kernel looks for it" ;; + esac + + # The QSPI grep. Over-broad on purpose; see QSPI_FORBIDDEN above. + # Iterated with IFS=newline rather than through a pipe, so the hits + # file is written by THIS shell and not by a subshell whose variables + # would evaporate. + hits="$work/qspi-hits.txt" + : > "$hits" + _oldifs=$IFS + IFS=' +' + for pat in $QSPI_FORBIDDEN; do + if grep -qiF -- "$pat" "$conf"; then + printf '%s\n' "$pat" >> "$hits" + fi + done + IFS=$_oldifs + if [ ! -s "$hits" ]; then + ok "extlinux.conf mentions none of: sf probe / ubi / mtd (nothing on this card references QSPI)" + else + bad "extlinux.conf mentions QSPI-flash machinery: $(tr '\n' ' ' < "$hits")" + note "a QSPI write on this board is brick-class with JTAG-and-a-PC recovery and no RSU (boot-chain §6, §7 rows 1/5/10/11/12)" + fi + else + bad "could not read $EXTLINUX_PATH from p1" + sed 's/^/ /' "$work/mcopy.err" >&2 + fi +fi + +# ============================================================================= +# 5. p2: a clean ext4 labelled `rootfs` +# ============================================================================= +if [ -z "$rootfs_start" ]; then + bad "cannot inspect p2 -- partition $ROOTFS_PART_NUM was not found above" +else + p2="$work/p2.img" + # conv=sparse keeps this cheap: a Buildroot rootfs.ext4 is mostly holes. + dd if="$img" of="$p2" bs=512 skip="$rootfs_start" count="$rootfs_size" \ + conv=sparse status=none 2>/dev/null || + { echo "$prog: dd failed extracting partition $ROOTFS_PART_NUM" >&2; exit 2; } + + if hdr=$("$dumpe2fs_bin" -h "$p2" 2>/dev/null); then + ok "p2 is an ext2/3/4 filesystem (dumpe2fs -h succeeded)" + + p2_label=$(printf '%s\n' "$hdr" | sed -n 's/^Filesystem volume name:[[:space:]]*//p') + if [ "$p2_label" = "$EXPECT_ROOTFS_LABEL" ]; then + ok "p2 volume label = '$EXPECT_ROOTFS_LABEL'" + else + bad "p2 volume label = '$p2_label', expected '$EXPECT_ROOTFS_LABEL' (BR2_TARGET_ROOTFS_EXT2_LABEL)" + fi + + features=$(printf '%s\n' "$hdr" | sed -n 's/^Filesystem features:[[:space:]]*//p') + note "p2 features: $features" + case " $features " in + *" extent "*) + ok "p2 has the 'extent' feature -- it is a real ext4, not an ext2 image wearing the name" ;; + *) bad "p2 lacks the 'extent' feature -- BR2_TARGET_ROOTFS_EXT2_4 selects ext4, and this is not one" ;; + esac + + if "$e2fsck_bin" -fn "$p2" > "$work/e2fsck.out" 2>&1; then + ok "p2 is fsck-clean (e2fsck -fn)" + else + bad "e2fsck -fn found problems on p2 (exit $?) -- the partition may be truncated" + sed 's/^/ /' "$work/e2fsck.out" >&2 + fi + else + bad "p2 is not an ext2/3/4 filesystem (dumpe2fs -h failed)" + fi +fi + +# ============================================================================= +if [ "$fail" -eq 0 ]; then + echo "$prog: all assertions passed" +else + echo "$prog: CONTRACT VIOLATED" >&2 +fi +exit "$fail" diff --git a/scripts/ci-tests.sh b/scripts/ci-tests.sh index fd4b8c5..761b8e1 100755 --- a/scripts/ci-tests.sh +++ b/scripts/ci-tests.sh @@ -98,19 +98,19 @@ LINUX_IMG="$IMAGES/linux.img" # fine and only this constant was wrong. A hardcoded version here does not fail # safe -- it fails *misleadingly*, pointing the reader at the wrong subsystem. # -# Read the MAIN image's defconfig specifically, which is what the scoping note +# Read the DE10 board fragment specifically, which is what the scoping note # above is about: configs/mister_rt.fragment overrides this symbol for the RT -# variant, and configs/mister_kernel_defconfig carries a lockstep copy -# (scripts/check-kernel-defconfig-sync.sh asserts those two agree). +# variant; the kernel-only stack shares this very fragment with the image +# (configs/fragments/stacks.mk), so there is no lockstep copy to worry about. # -# Anchored to ^ and taking the last match on purpose: the defconfig explains -# this symbol in a comment that quotes it verbatim, so an unanchored match -# returns two lines -- the exact bug fixed in the hash-sync workflow (#42). +# Anchored to ^ and taking the last match on purpose: a comment quoting the +# symbol verbatim would otherwise match too -- the exact bug fixed in the +# hash-sync workflow (#42). KVER=$(sed -n 's/^BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="\([^"]*\)".*$/\1/p' \ - "$ROOT/configs/mister_de10nano_defconfig" | tail -1) + "$ROOT/configs/fragments/de10nano.fragment" | tail -1) if [ -z "$KVER" ]; then echo "FATAL: could not read BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE from" >&2 - echo " $ROOT/configs/mister_de10nano_defconfig" >&2 + echo " $ROOT/configs/fragments/de10nano.fragment" >&2 exit 1 fi diff --git a/scripts/export-kernel-tree.sh b/scripts/export-kernel-tree.sh index a5753f0..f7efa2d 100755 --- a/scripts/export-kernel-tree.sh +++ b/scripts/export-kernel-tree.sh @@ -156,7 +156,7 @@ set -o pipefail # (shellcheck SC2155), and the rest of scripts/ avoids that pattern. REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" readonly REPO_ROOT -readonly DEFCONFIG="$REPO_ROOT/configs/mister_de10nano_defconfig" +readonly DEFCONFIG="$REPO_ROOT/configs/fragments/de10nano.fragment" readonly HASH_FILE="$REPO_ROOT/board/mister/de10nano/patches/linux/linux.hash" # Committer identity for the generated commits. Patch AUTHORS are preserved by `git am`; diff --git a/scripts/hash-sync-golden.sh b/scripts/hash-sync-golden.sh new file mode 100755 index 0000000..5387309 --- /dev/null +++ b/scripts/hash-sync-golden.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# +# hash-sync-golden.sh — renovate-hash-sync case 8: on a Buildroot bump, +# record the new version's golden config hashes +# (configs/fragments/golden.sha256) so the bump PR is complete on its own. +# +# WHY. scripts/check-config-fragments.sh (d) pins the sha256 of every fragment +# stack's normalised resolved .config per BUILDROOT_VERSION. A Buildroot bump +# changes Kconfig defaults, so it is EXPECTED to move every hash; the check +# therefore treats "no line for this Buildroot version" as a ::warning, not a +# failure, so the automated bump PR still gets its build. This case is what +# turns that warning back into a recorded line: it runs the check's own +# --update-golden against the freshly bumped tree and the workflow commits the +# result alongside the BUILDROOT_SHA256 transcription case 6 just made. +# +# WHAT IT RUNS, AND FROM WHERE. Unlike the other cases this one deliberately +# executes the TARGET BRANCH's scripts/check-config-fragments.sh (through the +# target branch's Makefile: `make buildroot-unpack` fetches + hash-verifies the +# NEW tarball using the hash case 6 wrote), not a copy from .hash-sync-tools: +# the golden file's format and normalisation rules are defined by the check +# script in the same tree, and a golden written by a different version of the +# rules would be wrong by construction. A branch that predates the fragment +# split has no such script and is skipped. +# +# WHEN IT WRITES. Only when golden.sha256 has NO line at all for the tree's +# BUILDROOT_VERSION (a bump). If lines exist, any mismatch is real drift and +# is lint-config's to fail on -- this case must never bless it, so it records +# `skipped` and leaves the file alone. A kernel bump does not move the golden +# (the kernel version symbols are excluded from the normalisation), so on the +# weekly kernel PRs this case is a no-op. +# +# Cost: one 10 MB tarball download plus Buildroot's kconfig `conf` build and +# one olddefconfig per stack -- no toolchain, no package, no compile. +# +# Exit: always 0 on a handled path, same as every other case (see +# hash-sync-kernel.sh's header for why); a non-zero exit is a bug here. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/lib/hash-sync-common.sh +. "$SCRIPT_DIR/lib/hash-sync-common.sh" + +OUTCOME_PIN="golden" +CHANGED_VAR="GOLDEN_CHANGED" + +REPO_ROOT="${1:-$(cd "$SCRIPT_DIR/.." && pwd)}" +[ -d "$REPO_ROOT" ] || { echo "::error::REPO_ROOT '$REPO_ROOT' is not a directory" >&2; exit 2; } + +: "${HASH_SYNC_OUTCOMES_FILE:?HASH_SYNC_OUTCOMES_FILE must be set}" + +main() { + cd "$REPO_ROOT" + + local outcomes_file + outcomes_file="$(hash_sync_resolve_outcomes_file "$HASH_SYNC_OUTCOMES_FILE")" + + local check="scripts/check-config-fragments.sh" + local golden="configs/fragments/golden.sha256" + if [ ! -x "$check" ] || [ ! -f "$golden" ]; then + echo "::notice::$check or $golden not in this checkout (pre-fragment-split branch) -- skipping $OUTCOME_PIN" + hash_sync_record "$outcomes_file" "$OUTCOME_PIN" skipped "$check or $golden not in this checkout" + hash_sync_set_env "$CHANGED_VAR" 0 + exit 0 + fi + + local ver + ver=$(sed -n -e 's/[[:space:]]*$//' -e 's/^BUILDROOT_VERSION[[:space:]]*?=[[:space:]]*//p' Makefile | head -1) + if [ -z "$ver" ]; then + echo "::error::could not extract BUILDROOT_VERSION from Makefile" >&2 + hash_sync_record "$outcomes_file" "$OUTCOME_PIN" failed "could not extract BUILDROOT_VERSION from Makefile" + hash_sync_set_env "$CHANGED_VAR" 0 + exit 0 + fi + + if awk -v v="$ver" '$1 == v { found = 1 } END { exit !found }' "$golden"; then + echo "==> $OUTCOME_PIN: $golden already records Buildroot $ver -- nothing to add (drift, if any, is lint-config's to report, never this case's to bless)" + hash_sync_record "$outcomes_file" "$OUTCOME_PIN" already "golden already recorded for Buildroot $ver" + hash_sync_set_env "$CHANGED_VAR" 0 + exit 0 + fi + + echo "==> $OUTCOME_PIN: no golden lines for Buildroot $ver -- regenerating" + # buildroot-unpack fetches and hash-verifies the NEW tarball with the + # BUILDROOT_SHA256 case 6 just transcribed; if that failed or was skipped, + # this fails closed too and the PR stays red at the same place. + if ! make --no-print-directory buildroot-unpack; then + echo "::warning::make buildroot-unpack failed for Buildroot $ver -- leaving $golden untouched (lint-config will warn, not fail)" + hash_sync_record "$outcomes_file" "$OUTCOME_PIN" skipped "make buildroot-unpack failed for Buildroot $ver" + hash_sync_set_env "$CHANGED_VAR" 0 + exit 0 + fi + if ! "$check" --update-golden; then + # --update-golden still runs (a)-(c); a failure there is a real + # fragment problem the PR must fix, not something to paper over. + echo "::warning::$check --update-golden reported failures -- $golden NOT committed; see the check's output" + git checkout -- "$golden" 2>/dev/null || true + hash_sync_record "$outcomes_file" "$OUTCOME_PIN" skipped "$check failed under Buildroot $ver" + hash_sync_set_env "$CHANGED_VAR" 0 + exit 0 + fi + + if git diff --quiet -- "$golden"; then + hash_sync_record "$outcomes_file" "$OUTCOME_PIN" already "golden unchanged" + hash_sync_set_env "$CHANGED_VAR" 0 + else + echo "Updated $golden:" + git --no-pager diff -- "$golden" + hash_sync_record "$outcomes_file" "$OUTCOME_PIN" refreshed "recorded golden hashes for Buildroot $ver" + hash_sync_set_env "$CHANGED_VAR" 1 + fi +} + +main "$@" diff --git a/scripts/hash-sync-kernel.sh b/scripts/hash-sync-kernel.sh index a670996..8fd4c68 100755 --- a/scripts/hash-sync-kernel.sh +++ b/scripts/hash-sync-kernel.sh @@ -9,7 +9,7 @@ # had when it took no options at all: # # --pin=stable the 6.18.y longterm kernel that the SHIPPED image runs. -# Version read from configs/mister_de10nano_defconfig. +# Version read from configs/fragments/de10nano.fragment. # --pin=rt the RT/beta kernel variant (docs/rt-beta-kernel.md). # Version read from configs/mister_rt.fragment. # @@ -136,7 +136,7 @@ # # Testing against a fixture: point REPO_ROOT at a scratch directory # containing a fake version file for the pin under test -- configs/ -# mister_de10nano_defconfig for `stable`, configs/mister_rt.fragment for `rt` +# fragments/de10nano.fragment for `stable`, configs/mister_rt.fragment for `rt` # (each just needs the one BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="..." line; # including a SECOND, unanchored-looking copy in a comment is exactly the # bug-#42 regression test this case wants) -- plus a fake linux.hash carrying @@ -198,7 +198,7 @@ done # rt pin gets its own, which the .yml's gate must OR in (it does). case "$PIN" in stable) - VERSION_FILE="configs/mister_de10nano_defconfig" + VERSION_FILE="configs/fragments/de10nano.fragment" OUTCOME_PIN="kernel" CHANGED_VAR="PATCH_HASH_CHANGED" ;; diff --git a/scripts/lib/board-expectations.sh b/scripts/lib/board-expectations.sh index 549e601..bb90dcb 100644 --- a/scripts/lib/board-expectations.sh +++ b/scripts/lib/board-expectations.sh @@ -42,14 +42,16 @@ BOARD_COMMON_SENTINELS="BR2_KERNEL_HEADERS BR2_TOOLCHAIN_BUILDROOT_CXX" BOARD_COMMON_FAMILIES="BR2_KERNEL_HEADERS BR2_TOOLCHAIN_BUILDROOT_" # Sentinel symbols check-kernel-defconfig-sync.sh's `for must in ...` assert -# requires present in configs/mister_kernel_defconfig (its :83, pre-change). +# requires present in the kernel-only fragment stack (configs/fragments/ +# stacks.mk DE10NANO_KERNEL_FRAGMENTS; before the 2026-09 fragment split, in +# configs/mister_kernel_defconfig). # Merge order for a consumer is ARCH ROW FIRST, then BOARD_COMMON_SENTINELS — # see that script for why the order matters (byte-identity with today's # literal list, so docs/de25-readiness-ledger.md §5.6's migration check is # meaningful). declare -A BOARD_ARCH_SENTINELS=( [de10nano]="BR2_arm BR2_cortex_a9" - # [V] — matches configs/mister_de25nano_defconfig (D2.1, 2026-09-02): + # [V] — matches configs/fragments/de25nano.fragment (D2.1, 2026-09-02): # BR2_aarch64=y and BR2_cortex_a76_a55=y are its arch/CPU choices. # Change this row in the same commit as any change to those two lines. [de25nano]="BR2_aarch64 BR2_cortex_a76_a55" @@ -61,7 +63,7 @@ declare -A BOARD_ARCH_SENTINELS=( # then BOARD_COMMON_FAMILIES merge order as the sentinels above. declare -A BOARD_ARCH_FAMILIES=( [de10nano]="BR2_arm BR2_ARM_ BR2_cortex" - # [V] — checked against configs/mister_de25nano_defconfig (2026-09-02): + # [V] — checked against configs/fragments/de25nano.fragment (2026-09-02): # it sets BR2_aarch64 and BR2_cortex_a76_a55 and no BR2_ARM_* symbol at # all (AArch64 has no such knobs in Buildroot). BR2_ARM_ is carried over # so that an aarch64 defconfig populating none of it is a legitimate @@ -76,6 +78,7 @@ declare -A BOARD_ARCH_FAMILIES=( # not plain symbol names (hence the leading '^'). declare -A BOARD_FINGERPRINT_SENTINELS=( [de10nano]="^BR2_arm ^BR2_cortex" - # [U] — confirm against configs/mister_de25nano_defconfig when it lands. + # [V] — configs/fragments/de25nano.fragment sets BR2_aarch64=y and + # BR2_cortex_a76_a55=y (2026-09-02). [de25nano]="^BR2_aarch64 ^BR2_cortex" ) diff --git a/scripts/lib/config-stacks.sh b/scripts/lib/config-stacks.sh new file mode 100644 index 0000000..99bf12a --- /dev/null +++ b/scripts/lib/config-stacks.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# +# scripts/lib/config-stacks.sh — read configs/fragments/stacks.mk and strip +# fragment files. Not directly executable; sourced (same convention as +# scripts/lib/board-expectations.sh) by scripts/check-kernel-defconfig-sync.sh +# and scripts/check-config-fragments.sh. The sourcing script must have set +# ROOT to the repository root first. +# +# stacks.mk is the SINGLE source of truth for which fragments make up which +# Buildroot configuration; the top-level Makefile `include`s it and this file +# parses the same `_FRAGMENTS := a b c` lines with sed, so a stack +# can never be defined one way for `make` and another for the checks. Keep +# stacks.mk to that one-line-per-stack shape (docs/buildroot-config.md §1). + +# shellcheck disable=SC2034 # read by the sourcing scripts +CONFIG_STACKS_MK="$ROOT/configs/fragments/stacks.mk" +CONFIG_FRAGMENT_DIR="$ROOT/configs/fragments" + +# Every `` name defined in stacks.mk (DE10NANO, DE10NANO_KERNEL, ...). +config_stack_vars() { + sed -n 's/^\([A-Z0-9_]*\)_FRAGMENTS[[:space:]]*:=.*$/\1/p' "$CONFIG_STACKS_MK" +} + +# $1 = stack var name -> its fragment NAMES, in merge order, one per line. +config_stack_fragments() { + sed -n "s/^$1_FRAGMENTS[[:space:]]*:=[[:space:]]*//p" "$CONFIG_STACKS_MK" \ + | sed -e 's/[[:space:]]*#.*$//' | tr -s ' \t' '\n' | sed '/^$/d' +} + +# $1 = stack var name -> absolute fragment paths, in merge order, one per line. +config_stack_files() { + local n + while IFS= read -r n; do + [ -n "$n" ] || continue + printf '%s\n' "$CONFIG_FRAGMENT_DIR/$n.fragment" + done < <(config_stack_fragments "$1") +} + +# DE10NANO_KERNEL -> de10nano-kernel: the human-facing stack name. +config_stack_label() { + printf '%s\n' "$1" | tr 'A-Z_' 'a-z-' +} + +# Strip comments/blank lines from a fragment, KEEPING `# BR2_X is not set` +# lines (kconfig reads those as an explicit =n — they are configuration, not +# commentary). Output: only `BR2_...=...` and `# BR2_... is not set` lines, +# trailing same-line comments and whitespace removed. Values are otherwise +# verbatim, so a value containing '=' or '#' inside quotes survives — no +# committed value carries a bare ` #`, which is the one thing this would +# mis-strip. +config_strip_fragment() { + sed -e 's/^[[:space:]]*# \(BR2_[A-Za-z0-9_]*\) is not set.*$/@@NOTSET@@\1/' \ + -e 's/^[[:space:]]*#.*$//' \ + -e 's/[[:space:]]\+#.*$//' \ + -e 's/[[:space:]]*$//' \ + -e 's/^@@NOTSET@@\(.*\)$/# \1 is not set/' "$@" \ + | grep -e '^BR2_' -e '^# BR2_' || true +} + +# The symbol NAME of a stripped line (either form). +config_line_symbol() { + case "$1" in + "# "*" is not set") local s="${1#\# }"; printf '%s\n' "${s% is not set}" ;; + *) printf '%s\n' "${1%%=*}" ;; + esac +} diff --git a/scripts/lint-kernel-patches.sh b/scripts/lint-kernel-patches.sh index 9466142..d3792e4 100755 --- a/scripts/lint-kernel-patches.sh +++ b/scripts/lint-kernel-patches.sh @@ -50,7 +50,7 @@ # # Usage: scripts/lint-kernel-patches.sh [patch-dir...] # With no arguments, lints the series named by BR2_LINUX_KERNEL_PATCH in -# configs/mister_de10nano_defconfig, plus the upstream-only series alongside it +# configs/fragments/de10nano.fragment, plus the upstream-only series alongside it # ("-upstream") when that directory exists. # A directory named on the command line must exist and contain patches. # @@ -64,7 +64,7 @@ set -o pipefail # (shellcheck SC2155), and the rest of scripts/ avoids that pattern. REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" readonly REPO_ROOT -readonly DEFCONFIG="$REPO_ROOT/configs/mister_de10nano_defconfig" +readonly DEFCONFIG="$REPO_ROOT/configs/fragments/de10nano.fragment" # Resolve the default from the defconfig rather than hardcoding the path, which is what # the usage text above has always promised and what scripts/export-kernel-tree.sh already diff --git a/scripts/list-kernel-variants.sh b/scripts/list-kernel-variants.sh index 0a61b62..3d2ddc5 100755 --- a/scripts/list-kernel-variants.sh +++ b/scripts/list-kernel-variants.sh @@ -28,12 +28,14 @@ # filesystem accident (e.g. every fragment deleted) and a release/CI run that # silently ships zero kernel variants while looking successful. # -# EXCLUDED ON PURPOSE: configs/mister_kernel_defconfig (the shared kernel-only -# BASE every variant builds against) and configs/mister_initramfs_defconfig -# (the stage-1 initramfs config). Neither carries a `.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 -# instead of relying on an accident of extension. +# 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 +# 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, # a hypothetical configs/mister_main.fragment WOULD match the *.fragment glob @@ -66,11 +68,11 @@ variants=() shopt -s nullglob for f in configs/mister_*.fragment; do case "$f" in - configs/mister_kernel_defconfig | configs/mister_initramfs_defconfig) - # Unreachable given the *.fragment glob above -- neither defconfig - # carries that extension -- but kept explicit per the EXCLUDED ON - # PURPOSE note in the header, so this is documented in code, not - # just prose. + configs/fragments/* | configs/mister_initramfs_defconfig) + # Unreachable given the *.fragment glob above -- the stacks are in a + # subdirectory and the initramfs 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 ;; esac diff --git a/scripts/test-initramfs.sh b/scripts/test-initramfs.sh index 0242289..edaf021 100755 --- a/scripts/test-initramfs.sh +++ b/scripts/test-initramfs.sh @@ -80,14 +80,14 @@ KERNEL_SRC="${TEST_INITRAMFS_KERNEL_SRC:-$ROOT/work/test-initramfs-kernel-src}" # 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/mister_de10nano_defconfig")}" +KERNEL_VERSION="${TEST_INITRAMFS_KERNEL_VERSION:-$(sed -n 's/^BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="\(.*\)"$/\1/p' "$ROOT/configs/fragments/de10nano.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/mister_de10nano_defconfig" >&2 + "could not read BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE from configs/fragments/de10nano.fragment" >&2 exit 2 } KERNEL_TARBALL="${TEST_INITRAMFS_KERNEL_TARBALL:-$ROOT/work/linux-$KERNEL_VERSION.tar.xz}" diff --git a/scripts/test-sdcard-install.sh b/scripts/test-sdcard-install.sh index 557aae0..eb9d3c9 100755 --- a/scripts/test-sdcard-install.sh +++ b/scripts/test-sdcard-install.sh @@ -60,14 +60,14 @@ UBOOT_REF="${UBOOT_REF:-$ROOT/output-sdcard-stage/mister-payload/linux/uboot.img CROSS_COMPILE="${CROSS_COMPILE:-$ROOT/output/host/bin/arm-buildroot-linux-gnueabihf-}" # --- QEMU test kernel (shares scripts/test-initramfs.sh's source tree + config) --- -# Derived from the product defconfig, NOT hardcoded -- same reason as +# Derived from the product board fragment, NOT hardcoded -- same reason as # scripts/test-initramfs.sh (board patch 0031, applied below, tracks the pinned # kernel's APIs; 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"). -KERNEL_VERSION="${TEST_SDCARD_KERNEL_VERSION:-$(sed -n 's/^BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="\(.*\)"$/\1/p' "$ROOT/configs/mister_de10nano_defconfig")}" +KERNEL_VERSION="${TEST_SDCARD_KERNEL_VERSION:-$(sed -n 's/^BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="\(.*\)"$/\1/p' "$ROOT/configs/fragments/de10nano.fragment")}" [ -n "$KERNEL_VERSION" ] || { printf 'test-sdcard-install.sh: FATAL: %s\n' \ - "could not read BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE from configs/mister_de10nano_defconfig" >&2 + "could not read BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE from configs/fragments/de10nano.fragment" >&2 exit 2 } KERNEL_TARBALL="${TEST_SDCARD_KERNEL_TARBALL:-$ROOT/dl/linux-$KERNEL_VERSION.tar.xz}"