diff --git a/.github/actions/buildroot-build/action.yml b/.github/actions/buildroot-build/action.yml index ec2a633..071aaa6 100644 --- a/.github/actions/buildroot-build/action.yml +++ b/.github/actions/buildroot-build/action.yml @@ -36,6 +36,20 @@ description: >- tree in output-/target/. inputs: + board: + description: >- + Which board's arch/toolchain sentinel expectations to check the + toolchain fingerprint against (scripts/lib/board-expectations.sh's + BOARD_FINGERPRINT_SENTINELS, docs/de25-readiness-ledger.md §5.2/§5.5): + "de10nano" (default — behavior and cache key strings identical to + before this input existed). This is the ONLY value accepted today: + BOARD selects an expectation row but does not yet route the + defconfig, cache key, output dir or make target, so any other board + (de25nano included, even though its row exists in the table) is + rejected up front rather than allowed to fail its sentinel late. + Widen BOARD_ROUTED below when board routing lands (ledger §3/§4). + required: false + default: "de10nano" extra-apt-packages: description: >- Space-separated apt packages to install on top of the mandatory build set @@ -101,9 +115,37 @@ runs: shell: bash env: VARIANT: ${{ inputs.variant }} + BOARD: ${{ inputs.board }} run: | set -eu + # shellcheck source=scripts/lib/board-expectations.sh + source scripts/lib/board-expectations.sh + + # --- Which board this run checks the fingerprint against --------------- + # Validated ONCE, here, before any cache/build work -- same idiom as + # the variant validation just below. Two gates, both fail-closed: + # (1) the board must have a row in the registry + # (scripts/lib/board-expectations.sh, ledger §5.5), and + # (2) it must be ROUTED: this action still fingerprints the DE10 + # defconfig, runs the DE10 lockstep check and builds `make all` / + # `make $VARIANT`, so a board that is in the table but not routed + # would fail its sentinel late and misleadingly. BOARD_ROUTED is + # the explicit list of boards this action can actually build; + # extend it in the same change that wires defconfig, cache key, + # output dir and make target per board (ledger §3/§4). + BOARD_ROUTED="de10nano" + if [ -z "${BOARD_FINGERPRINT_SENTINELS[$BOARD]+set}" ]; then + echo "::error::unknown board '$BOARD' -- no row in scripts/lib/board-expectations.sh's BOARD_FINGERPRINT_SENTINELS (known: ${!BOARD_FINGERPRINT_SENTINELS[*]})" >&2 + exit 1 + fi + case " $BOARD_ROUTED " in + *" $BOARD "*) ;; + *) echo "::error::board '$BOARD' has an expectation row but is not routed through this action yet (routed: $BOARD_ROUTED) -- it would fail its arch sentinel after the DE10 defconfig was fingerprinted. Wire the board through defconfig/cache/output/target selection before enabling it here." >&2 + exit 1 ;; + esac + echo "BOARD=$BOARD" >> "$GITHUB_ENV" + # --- Which variant this run builds (see the file header) --------------- # Validated ONCE, here, before any cache/build work — fragment # existence IS the registry, so a new variant needs no edit here. @@ -190,9 +232,13 @@ runs: # Fail loud if the defconfig is ever renamed/reformatted such that this # produces an empty or degenerate fingerprint: a frozen cache key is the # worst outcome available here (see docs/ci.md#toolchain-fingerprint). - # Assert the two lines that no ARM defconfig can lack and that nothing - # legitimate ever removes. - for must in '^BR2_arm' '^BR2_cortex'; do + # Assert the lines BOARD's row says no defconfig for that board can + # lack and that nothing legitimate ever removes (scripts/lib/ + # board-expectations.sh's BOARD_FINGERPRINT_SENTINELS; for the + # default "de10nano" this is the same two patterns as before this + # table existed). + # shellcheck disable=SC2086 # word splitting over the merged pattern list is intended + for must in ${BOARD_FINGERPRINT_SENTINELS[$BOARD]}; do if ! grep -q "$must" .br-toolchain-fingerprint; then echo "::error::toolchain fingerprint is missing '$must' -- the filter in .github/actions/buildroot-build/action.yml has gone stale, so this cache key can no longer detect a toolchain change. Refusing to build rather than risk restoring a wrong compiler." >&2 exit 1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c1e5d20..ee5e779 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ # Run the same checks locally before pushing: # actionlint # from repo root; auto-finds .github/workflows # shellcheck -x $(find scripts -name '*.sh') -# shellcheck -x board/mister/de10nano/fat-payload/Scripts/*.sh +# shellcheck -x board/mister/*/fat-payload/Scripts/*.sh # shellcheck -s sh -x package/azcopy/azcopy-profile.sh # scripts/shellcheck-composite-actions.sh # .github/actions/*/action.yml run: bodies # scripts/test-installer-splash.sh # SD-installer first-boot splash (ADR 0020 §6) @@ -32,33 +32,30 @@ on: paths: - '.github/**' - 'scripts/**' - # Shell that ships on the card / in the rootfs is shellchecked below, so - # a change to it has to be able to trigger this workflow. - - 'board/mister/de10nano/fat-payload/**' - - 'board/mister/de10nano/rootfs-overlay/etc/init.d/**' + # Shell that ships on the card or in the rootfs is shellchecked below, + # and the SD-installer's /init splash is unit-tested below too, so a + # change to either has to be able to trigger this workflow. One glob + # (not the four per-subdir literals this used to be: fat-payload/**, + # rootfs-overlay/etc/init.d/**, post-build.sh, installer-overlay/**), + # so a second board directory can't silently sit outside this trigger + # the way it silently sat outside the shellcheck targets below -- see + # docs/ci.md#lint-ci-overview and docs/de25-readiness-ledger.md §6.5. + - 'board/mister/**' # package/azcopy: azcopy-profile.sh is shellchecked below, and the whole # directory feeds the version/hash consistency gate. - 'package/azcopy/**' - - 'board/mister/de10nano/post-build.sh' - 'install.sh' - 'uninstall.sh' - # The installer /init is not a script/**, but this job is the only thing - # that tests its splash section on a PR (see the unit-test step below). - # Without this path a change to the splash alone would skip its own gate. - - 'board/mister/de10nano/installer-overlay/**' pull_request: paths: - '.github/**' - 'scripts/**' - - 'board/mister/de10nano/fat-payload/**' - - 'board/mister/de10nano/rootfs-overlay/etc/init.d/**' + - 'board/mister/**' # package/azcopy: azcopy-profile.sh is shellchecked below, and the whole # directory feeds the version/hash consistency gate. - 'package/azcopy/**' - - 'board/mister/de10nano/post-build.sh' - 'install.sh' - 'uninstall.sh' - - 'board/mister/de10nano/installer-overlay/**' workflow_dispatch: # Same shape as build.yml / release.yml / reproducibility.yml / publish-db.yml: @@ -158,22 +155,67 @@ jobs: # has to be stated: shellcheck has nothing else to infer the dialect # from. It is installed 0644, so it is deliberately absent from the # executable-bit check below. + # + # The list below used to be eight literal board/mister/de10nano/... paths + # -- silently skipping a second board tree entirely (see + # docs/de25-readiness-ledger.md §6.5). Instead it walks every directory + # under board/mister/ and, per board, tests for each conventional + # relative script path; a board that has not grown a given script yet + # (a board mid-bring-up, e.g. before its post-build.sh lands) is simply + # skipped FOR THAT ONE FILE, same as today's de10nano would be skipped + # for a filename it doesn't ship. What must NOT happen silently is every + # board missing every entry (glob typo, board/mister/ itself vanishing, + # etc.) -- that fails the step outright, per docs/ci.md#lint-ci-overview. - name: shellcheck shipped board scripts run: | set -euo pipefail - shellcheck -x \ - board/mister/de10nano/fat-payload/Scripts/update_linux_modernization.sh \ - board/mister/de10nano/fat-payload/Scripts/check_storage.sh \ - board/mister/de10nano/fat-payload/Scripts/pair_logitech.sh \ - board/mister/de10nano/rootfs-overlay/usr/sbin/mister-fsck-exfat \ - board/mister/de10nano/rootfs-overlay/usr/sbin/mister-pair-logitech - shellcheck -s sh -x \ - install.sh \ - uninstall.sh \ - board/mister/de10nano/post-build.sh \ - board/mister/de10nano/initramfs-post-build.sh \ - board/mister/de10nano/initramfs-overlay/init \ - package/azcopy/azcopy-profile.sh + mapfile -d '' -t boards < <(find board/mister -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) + if [ "${#boards[@]}" -eq 0 ]; then + echo "::error::no board directories found under board/mister -- expected at least board/mister/de10nano" + exit 1 + fi + + # Conventional per-board relative paths, split by dialect exactly as + # de10nano's fixed list above used to be. + bash_relpaths=( + fat-payload/Scripts/update_linux_modernization.sh + fat-payload/Scripts/check_storage.sh + fat-payload/Scripts/pair_logitech.sh + rootfs-overlay/usr/sbin/mister-fsck-exfat + rootfs-overlay/usr/sbin/mister-pair-logitech + ) + sh_relpaths=( + post-build.sh + initramfs-post-build.sh + initramfs-overlay/init + ) + + bash_files=() + sh_files=() + for board in "${boards[@]}"; do + for rel in "${bash_relpaths[@]}"; do + [ -f "$board/$rel" ] && bash_files+=("$board/$rel") + done + for rel in "${sh_relpaths[@]}"; do + [ -f "$board/$rel" ] && sh_files+=("$board/$rel") + done + done + + if [ "${#bash_files[@]}" -eq 0 ] && [ "${#sh_files[@]}" -eq 0 ]; then + echo "::error::none of the conventional shipped-script paths were found under any of: ${boards[*]} -- expected at least de10nano's fat-payload/Scripts/*.sh and rootfs-overlay/usr/sbin/mister-*" + exit 1 + fi + + echo "boards discovered: ${boards[*]}" + echo "bash-dialect shipped board scripts (${#bash_files[@]}):" + printf ' %s\n' "${bash_files[@]}" + echo "sh-dialect shipped board scripts (${#sh_files[@]}):" + printf ' %s\n' "${sh_files[@]}" + + if [ "${#bash_files[@]}" -gt 0 ]; then + shellcheck -x "${bash_files[@]}" + fi + shellcheck -s sh -x install.sh uninstall.sh package/azcopy/azcopy-profile.sh "${sh_files[@]}" # Executable bits on shipped scripts, checked against GIT's recorded mode. # @@ -185,21 +227,50 @@ jobs: # # Reads `git ls-files -s` rather than the worktree, because git's index is # what a fresh clone (and therefore CI and every contributor) actually gets. + # + # must_be_exec used to be eight literal board/mister/de10nano/... paths; + # it now walks board/mister/*/ the same way the shellcheck step above + # does (same conventional-relpath list, same "missing for this board is + # fine, missing for EVERY board is not" empty-set guard) -- see that + # step's comment and docs/de25-readiness-ledger.md §6.5. The init.d + # sweep below was already a real glob (`git ls-files` over a directory), + # so it only needed the board segment opened up. - name: shipped scripts must be executable in git run: | set -euo pipefail - must_be_exec=( - board/mister/de10nano/fat-payload/Scripts/update_linux_modernization.sh - board/mister/de10nano/fat-payload/Scripts/check_storage.sh - board/mister/de10nano/fat-payload/Scripts/pair_logitech.sh - board/mister/de10nano/post-build.sh - board/mister/de10nano/initramfs-post-build.sh - board/mister/de10nano/initramfs-overlay/init - board/mister/de10nano/rootfs-overlay/usr/sbin/mister-fsck-exfat - board/mister/de10nano/rootfs-overlay/usr/sbin/mister-pair-logitech - install.sh - uninstall.sh + mapfile -d '' -t boards < <(find board/mister -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) + if [ "${#boards[@]}" -eq 0 ]; then + echo "::error::no board directories found under board/mister -- expected at least board/mister/de10nano" + exit 1 + fi + + relpaths=( + fat-payload/Scripts/update_linux_modernization.sh + fat-payload/Scripts/check_storage.sh + fat-payload/Scripts/pair_logitech.sh + post-build.sh + initramfs-post-build.sh + initramfs-overlay/init + rootfs-overlay/usr/sbin/mister-fsck-exfat + rootfs-overlay/usr/sbin/mister-pair-logitech ) + + board_files=() + for board in "${boards[@]}"; do + for rel in "${relpaths[@]}"; do + [ -f "$board/$rel" ] && board_files+=("$board/$rel") + done + done + + if [ "${#board_files[@]}" -eq 0 ]; then + echo "::error::none of the conventional shipped-script paths were found under any of: ${boards[*]} -- expected at least de10nano's fat-payload/Scripts/*.sh" + exit 1 + fi + + must_be_exec=(install.sh uninstall.sh "${board_files[@]}") + echo "checking executable bit on:" + printf ' %s\n' "${must_be_exec[@]}" + rc=0 for f in "${must_be_exec[@]}"; do mode=$(git ls-files -s -- "$f" | awk '{print $1}') @@ -218,7 +289,7 @@ jobs: 100755|120000) ;; # regular executable, or a symlink *) echo "::error file=$f::init script mode is $mode, expected 100755"; rc=1 ;; esac - done < <(git ls-files 'board/mister/de10nano/rootfs-overlay/etc/init.d/*') + done < <(git ls-files 'board/mister/*/rootfs-overlay/etc/init.d/*') exit "$rc" # azcopy's hash pin IS refreshed by renovate-hash-sync.yml as of diff --git a/Makefile b/Makefile index 861585c..345915a 100644 --- a/Makefile +++ b/Makefile @@ -160,6 +160,43 @@ 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 +# --- 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), +# different rootfs. It shares with the main build exactly two things: the +# pinned Buildroot tree and the dl/ download cache. +# +# It follows the same trick as initramfs/rt/installer for the same reason: a +# different Buildroot *configuration* needs a different O=. Sharing +# $(OUTPUT_DIR) would clobber the DE10's armv7 toolchain with an aarch64 one — +# and Buildroot cross-toolchains bake their absolute O= path in, so the two can +# 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. +# +# ALSO UNLIKE `rt` and `all`: `de25` does NOT depend on `initramfs`. That cpio +# is an armv7 BusyBox built by configs/mister_initramfs_defconfig, and it exists +# because the DE10's real root is a loop-mounted ext4 image on a FAT partition +# that U-Boot will not load an initrd for (A3, docs/boot-chain.md). The DE25 +# boots a plain ext4 root partition, so there is nothing for a stage 1 to do — +# and embedding armv7 userspace in an aarch64 kernel would produce a kernel that +# panics in a novel and confusing way. external.mk's LINUX_KCONFIG_FIXUP_CMDS +# hook is guarded off for this build; see the guard's comment there. +# +# Scope reminder, because the target name invites the wrong assumption: this is +# a BARE DEVELOPER OS. No MiSTer binaries, no DE10 packages. See ADR 0027 +# Decision 6 and the defconfig's header. +DE25_OUTPUT_DIR := $(ROOT_DIR)/output-de25 + BR_TARBALL := $(DL_DIR)/buildroot-$(BUILDROOT_VERSION).tar.gz BR_DIR := $(WORK_DIR)/buildroot # Two properties this path must have, both learned the hard way: @@ -252,6 +289,13 @@ BR_MAKE_RT = PATH="$(HOSTSHIM_DIR):$$PATH" \ BR_MAKE_INSTALLER = PATH="$(HOSTSHIM_DIR):$$PATH" \ $(MAKE) -C $(BR_DIR) O=$(INSTALLER_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR) +# 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. +BR_MAKE_DE25 = PATH="$(HOSTSHIM_DIR):$$PATH" \ + $(MAKE) -C $(BR_DIR) O=$(DE25_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR) + # 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- @@ -269,6 +313,7 @@ BR_MAKE_INSTALLER = PATH="$(HOSTSHIM_DIR):$$PATH" \ .PHONY: clean distclean .PHONY: initramfs initramfs-clean initramfs-menuconfig initramfs-busybox-menuconfig check-initramfs .PHONY: rt rt-clean rt-menuconfig rt-external-deps rt-legal-info +.PHONY: de25 de25-clean de25-menuconfig de25-linux-menuconfig .PHONY: installer installer-clean installer-menuconfig installer-busybox-menuconfig .PHONY: sdcard .PHONY: zimage-dtb @@ -359,7 +404,7 @@ $(OUTPUT_DIR)/.config: | $(BR_STAMP) # is the only thing that knows what to delete and what to keep, so skipping it # would report success over a still-dirty tree — this bug, again, one layer out. clean: - @if [ ! -d $(BR_DIR) ] && { [ -d $(OUTPUT_DIR) ] || [ -d $(INITRAMFS_OUTPUT_DIR) ] || [ -d $(RT_OUTPUT_DIR) ] || [ -d $(INSTALLER_OUTPUT_DIR) ] || [ -d $(INSTALLER_KERNEL_OUTPUT_DIR) ] || [ -d $(SDCARD_STAGE_DIR) ] || [ -d $(SDCARD_BUILD_DIR) ]; }; then \ + @if [ ! -d $(BR_DIR) ] && { [ -d $(OUTPUT_DIR) ] || [ -d $(INITRAMFS_OUTPUT_DIR) ] || [ -d $(RT_OUTPUT_DIR) ] || [ -d $(INSTALLER_OUTPUT_DIR) ] || [ -d $(DE25_OUTPUT_DIR) ] || [ -d $(INSTALLER_KERNEL_OUTPUT_DIR) ] || [ -d $(SDCARD_STAGE_DIR) ] || [ -d $(SDCARD_BUILD_DIR) ]; }; then \ echo "FATAL: $(BR_DIR) is gone, so Buildroot's own 'clean' cannot run," >&2; \ echo " but an output directory still holds build products. Skipping" >&2; \ echo " would report success over a dirty tree." >&2; \ @@ -371,6 +416,7 @@ clean: @if [ -d $(INITRAMFS_OUTPUT_DIR) ]; then $(BR_MAKE_INITRAMFS) clean; fi @if [ -d $(RT_OUTPUT_DIR) ]; then $(BR_MAKE_RT) clean; fi @if [ -d $(INSTALLER_OUTPUT_DIR) ]; then $(BR_MAKE_INSTALLER) clean; fi + @if [ -d $(DE25_OUTPUT_DIR) ]; then $(BR_MAKE_DE25) clean; fi @rm -rf $(INSTALLER_KERNEL_OUTPUT_DIR) $(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR) @# The extra-modules overlay is a build product (staged module trees), so @# clean takes it wholesale, stamps included — the next `make rt` restages @@ -393,7 +439,8 @@ clean: # nothing-but-the-clone hammer; it takes work/ and dl/ with it. distclean: rm -rf $(OUTPUT_DIR) $(INITRAMFS_OUTPUT_DIR) $(RT_OUTPUT_DIR) \ - $(INSTALLER_OUTPUT_DIR) $(INSTALLER_KERNEL_OUTPUT_DIR) \ + $(INSTALLER_OUTPUT_DIR) $(DE25_OUTPUT_DIR) \ + $(INSTALLER_KERNEL_OUTPUT_DIR) \ $(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR) \ $(EXTRA_MODULES_OVERLAY) $(RT_OVERLAY_STAMP) @@ -630,6 +677,95 @@ rt-clean: fi 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 +# reasons, as $(OUTPUT_DIR)/.config and $(RT_OUTPUT_DIR)/.config above: a +# defconfig 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 +# this class of bug gets), and with no prerequisites an existing .config is +# 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. +# +# 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 + +# Deliberately NOT `de25: initramfs ...` — see DE25_OUTPUT_DIR's header for why +# the stage-1 cpio has no business in an aarch64 kernel. +# +# The post-build assertions are the DE25's equivalent of `rt`'s +# CONFIG_PREEMPT_RT check and `initramfs`'s cpio check: Buildroot exits 0 on +# plenty of configurations that produce no bootable artifact, and on a board +# with no hardware validation yet the build must say so at the end rather than +# leave someone to discover it at a dead serial console. +# +# The .dtb is asserted by GLOB rather than by name on purpose. The name is +# owned by the defconfig (BR2_LINUX_KERNEL_CUSTOM_DTS_PATH names the board +# file, board/mister/de25nano/socfpga_agilex5_de25nano.dts; it was mainline's +# socdk placeholder until D2.3 landed). A hardcoded filename here would fail +# the build on the day the DTS is renamed or a second variant is added, for a +# reason that has nothing to do with what went wrong. Buildroot +# installs the dtb into images/ under its BASENAME (linux/linux.mk:491-497, +# `notdir` unless BR2_LINUX_KERNEL_DTB_KEEP_DIRNAME), so a flat glob sees it +# either way. Zero dtbs IS a failure: BR2_LINUX_KERNEL_DTS_SUPPORT is on, so an +# empty images/*.dtb means the DTS silently did not build. +de25: $(DE25_OUTPUT_DIR)/.config hostshim + $(BR_MAKE_DE25) all + @test -f $(DE25_OUTPUT_DIR)/images/Image || { \ + echo "FATAL: de25 build finished but produced no $(DE25_OUTPUT_DIR)/images/Image" >&2; \ + echo " (BR2_LINUX_KERNEL_IMAGE=y selects the uncompressed aarch64 'Image'" >&2; \ + echo " target -- if the defconfig was changed to Image.gz, change this" >&2; \ + echo " assertion in the same commit.)" >&2; exit 1; } + @set -- $$(ls $(DE25_OUTPUT_DIR)/images/*.dtb 2>/dev/null); \ + if [ $$# -eq 0 ]; then \ + echo "FATAL: de25 build finished but installed no device tree blob into" >&2; \ + echo " $(DE25_OUTPUT_DIR)/images/ -- BR2_LINUX_KERNEL_DTS_SUPPORT is set," >&2; \ + echo " so this means the DTS named by BR2_LINUX_KERNEL_INTREE_DTS_NAME /" >&2; \ + 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; \ + 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 "" + +# Escape hatches for iterating without hand-editing the checked-in defconfig. +# 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. +# +# de25-linux-menuconfig exists as its own target for the same reason +# rt-menuconfig does: the `%:` catch-all would forward a bare +# `make linux-menuconfig` with O=$(OUTPUT_DIR), i.e. against the DE10. +de25-menuconfig: $(DE25_OUTPUT_DIR)/.config hostshim + $(BR_MAKE_DE25) menuconfig + +de25-linux-menuconfig: $(DE25_OUTPUT_DIR)/.config hostshim + $(BR_MAKE_DE25) linux-menuconfig + +# Plain rm -rf, like initramfs-clean/installer-clean and unlike rt-clean: this +# build stages nothing into the extra-modules overlay and contributes nothing +# to output/, so there is no second removal to pair with. +de25-clean: + rm -rf $(DE25_OUTPUT_DIR) + # --- SD-card installer (P5.3, docs/decisions/0020-sdcard-exfat-reformat-installer.md) --- # Builds ONLY the installer initramfs cpio, standalone. scripts/mk-sdcard.sh # builds this exact output dir itself as step 1/7 of `make sdcard` (see @@ -764,7 +900,8 @@ help: @echo " make clean - delete everything the build produced," @echo " KEEPING all .config files" @echo " make distclean - rm -rf output/, output-initramfs/, output-rt/," - @echo " output-installer/, the sdcard staging dirs and" + @echo " output-installer/, output-de25/, the sdcard" + @echo " staging dirs and" @echo " the extra-modules overlay, .config included;" @echo " dl/ is kept (it is a shared cache —" @echo " 'git clean -xfd' takes it)" @@ -791,6 +928,17 @@ help: @echo " from the extra-modules overlay AND output/target" @echo " (Buildroot's overlay rsync never deletes)" @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 " 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/" + @echo "" @echo "zImage_dtb assembly (P1.11):" @echo " make zimage-dtb - cat zImage+DTB and run scripts/check-zimage-dtb.sh" @echo " (runs automatically at the end of 'make all' via" diff --git a/README.md b/README.md index e1175bd..33d1365 100644 --- a/README.md +++ b/README.md @@ -645,6 +645,29 @@ even though the rootfs is mounted read-only (or `/etc/profile`'s login-time options. All of it is annotated in [`PLAN.md` §5](PLAN.md) and [`docs/loop-boot-6.18.md`](docs/loop-boot-6.18.md). +**Forward-looking: the stock patch is 32-bit-ARM-only by construction.** Stock's +`loop_setup()` issues the loop attach as a direct call into the syscall entry points — +`sys_ioctl(device_fd, LOOP_SET_FD, file_fd)` then `sys_close()` — from inside +`init/do_mounts.c`. That compiles on the DE10-Nano's Cortex-A9 only because 32-bit ARM +still defines `sys_ioctl()` as an ordinary C function. Every 64-bit ARM kernel since 4.19 +selects `ARCH_HAS_SYSCALL_WRAPPER`, which turns each syscall into +`__arm64_sys_ioctl(const struct pt_regs *)` and drops the C-callable `sys_*` prototypes +altogether (`include/linux/syscalls.h` declares them only under +`#ifndef CONFIG_ARCH_HAS_SYSCALL_WRAPPER`). On aarch64 the stock patch does not merely +misbehave; it has no symbol to call and cannot build. The kernel's sanctioned +replacements for init-time work — the `init_mount()`, `init_mkdir()`, `init_chdir()` +family in `fs/init.c` — deliberately include no `init_ioctl()`, because an ioctl is +driver-defined and there is nothing generic to wrap, and the internal `vfs_ioctl()` / +`blkdev_ioctl()` paths are private to `fs/` and `block/`. So the only way to keep `loop=` +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, +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 +the kernel carries one patch fewer than it otherwise would. + ### The full SD-card image `make sdcard` produces a complete, `dd`-able `sdcard.img` — a two-partition MBR with the diff --git a/board/mister/de25nano/linux-patches/.gitkeep b/board/mister/de25nano/linux-patches/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/board/mister/de25nano/linux-patches/0010-hid-guncon2.patch b/board/mister/de25nano/linux-patches/0010-hid-guncon2.patch new file mode 120000 index 0000000..10a2cde --- /dev/null +++ b/board/mister/de25nano/linux-patches/0010-hid-guncon2.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0010-hid-guncon2.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0011-hid-guncon3.patch b/board/mister/de25nano/linux-patches/0011-hid-guncon3.patch new file mode 120000 index 0000000..23e78c0 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0011-hid-guncon3.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0011-hid-guncon3.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0012-hid-fanatec.patch b/board/mister/de25nano/linux-patches/0012-hid-fanatec.patch new file mode 120000 index 0000000..9f8faec --- /dev/null +++ b/board/mister/de25nano/linux-patches/0012-hid-fanatec.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0012-hid-fanatec.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0013-hid-flydigi-vader.patch b/board/mister/de25nano/linux-patches/0013-hid-flydigi-vader.patch new file mode 120000 index 0000000..cf00fdf --- /dev/null +++ b/board/mister/de25nano/linux-patches/0013-hid-flydigi-vader.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0013-hid-flydigi-vader.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0014-hid-gamecube-adapter.patch b/board/mister/de25nano/linux-patches/0014-hid-gamecube-adapter.patch new file mode 120000 index 0000000..8144b68 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0014-hid-gamecube-adapter.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0014-hid-gamecube-adapter.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0015-hid-nintendo-nso-famicom.patch b/board/mister/de25nano/linux-patches/0015-hid-nintendo-nso-famicom.patch new file mode 120000 index 0000000..9ebf3bd --- /dev/null +++ b/board/mister/de25nano/linux-patches/0015-hid-nintendo-nso-famicom.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches-beta/0015-hid-nintendo-nso-famicom.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0016-hid-microsoft-elite2-paddles.patch b/board/mister/de25nano/linux-patches/0016-hid-microsoft-elite2-paddles.patch new file mode 120000 index 0000000..351f2e0 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0016-hid-microsoft-elite2-paddles.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0016-hid-microsoft-elite2-paddles.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0017-xpad-mister-deltas.patch b/board/mister/de25nano/linux-patches/0017-xpad-mister-deltas.patch new file mode 120000 index 0000000..3caa495 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0017-xpad-mister-deltas.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0017-xpad-mister-deltas.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0018-hid-controllable-quirk.patch b/board/mister/de25nano/linux-patches/0018-hid-controllable-quirk.patch new file mode 120000 index 0000000..4ea39a2 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0018-hid-controllable-quirk.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0018-hid-controllable-quirk.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0019-hidpp-k400-fn-inversion.patch b/board/mister/de25nano/linux-patches/0019-hidpp-k400-fn-inversion.patch new file mode 120000 index 0000000..af65947 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0019-hidpp-k400-fn-inversion.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0019-hidpp-k400-fn-inversion.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0020-mmc-no-led-on-send-status.patch b/board/mister/de25nano/linux-patches/0020-mmc-no-led-on-send-status.patch new file mode 120000 index 0000000..c8f209c --- /dev/null +++ b/board/mister/de25nano/linux-patches/0020-mmc-no-led-on-send-status.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0020-mmc-no-led-on-send-status.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0022-hid-playstation-ds4-mac-fix.patch b/board/mister/de25nano/linux-patches/0022-hid-playstation-ds4-mac-fix.patch new file mode 120000 index 0000000..a0da087 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0022-hid-playstation-ds4-mac-fix.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0022-hid-playstation-ds4-mac-fix.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0023-hid-wiimote-fixes.patch b/board/mister/de25nano/linux-patches/0023-hid-wiimote-fixes.patch new file mode 120000 index 0000000..f024362 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0023-hid-wiimote-fixes.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0023-hid-wiimote-fixes.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0024-hid-input-keyrah-europe1.patch b/board/mister/de25nano/linux-patches/0024-hid-input-keyrah-europe1.patch new file mode 120000 index 0000000..5caf3c3 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0024-hid-input-keyrah-europe1.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0024-hid-input-keyrah-europe1.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0025-usbhid-jspoll-gamepad.patch b/board/mister/de25nano/linux-patches/0025-usbhid-jspoll-gamepad.patch new file mode 120000 index 0000000..bd3ce27 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0025-usbhid-jspoll-gamepad.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0025-usbhid-jspoll-gamepad.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0026-input-mousedev-eviocgrab.patch b/board/mister/de25nano/linux-patches/0026-input-mousedev-eviocgrab.patch new file mode 120000 index 0000000..e9ebee0 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0026-input-mousedev-eviocgrab.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0026-input-mousedev-eviocgrab.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0027-mt76x2u-release-xbox-adapter-ids.patch b/board/mister/de25nano/linux-patches/0027-mt76x2u-release-xbox-adapter-ids.patch new file mode 120000 index 0000000..33e2eaa --- /dev/null +++ b/board/mister/de25nano/linux-patches/0027-mt76x2u-release-xbox-adapter-ids.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0027-mt76x2u-release-xbox-adapter-ids.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0028-dwc2-fix-unaligned-in-split.patch b/board/mister/de25nano/linux-patches/0028-dwc2-fix-unaligned-in-split.patch new file mode 120000 index 0000000..0f6531c --- /dev/null +++ b/board/mister/de25nano/linux-patches/0028-dwc2-fix-unaligned-in-split.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0028-dwc2-fix-unaligned-in-split.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0029-leds-gpio-brightness-hw-changed.patch b/board/mister/de25nano/linux-patches/0029-leds-gpio-brightness-hw-changed.patch new file mode 120000 index 0000000..94f706b --- /dev/null +++ b/board/mister/de25nano/linux-patches/0029-leds-gpio-brightness-hw-changed.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0029-leds-gpio-brightness-hw-changed.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0030-i2c-designware-quiet-timeout.patch b/board/mister/de25nano/linux-patches/0030-i2c-designware-quiet-timeout.patch new file mode 120000 index 0000000..b4ff098 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0030-i2c-designware-quiet-timeout.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches-beta/0030-i2c-designware-quiet-timeout.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0031-exfat-samsung-symlinks.patch b/board/mister/de25nano/linux-patches/0031-exfat-samsung-symlinks.patch new file mode 120000 index 0000000..e9c8727 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0031-exfat-samsung-symlinks.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0031-exfat-samsung-symlinks.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0032-hid-nintendo-joycon-combo-led.patch b/board/mister/de25nano/linux-patches/0032-hid-nintendo-joycon-combo-led.patch new file mode 120000 index 0000000..128918b --- /dev/null +++ b/board/mister/de25nano/linux-patches/0032-hid-nintendo-joycon-combo-led.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0032-hid-nintendo-joycon-combo-led.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0033-hid-playstation-dualsense-player-id-led.patch b/board/mister/de25nano/linux-patches/0033-hid-playstation-dualsense-player-id-led.patch new file mode 120000 index 0000000..47bf833 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0033-hid-playstation-dualsense-player-id-led.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0033-hid-playstation-dualsense-player-id-led.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0034-hid-nintendo-nes-famicom-stock-ab-mapping.patch b/board/mister/de25nano/linux-patches/0034-hid-nintendo-nes-famicom-stock-ab-mapping.patch new file mode 120000 index 0000000..2f12700 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0034-hid-nintendo-nes-famicom-stock-ab-mapping.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0034-hid-nintendo-nes-famicom-stock-ab-mapping.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0035-hid-nintendo-home-led-nonfatal.patch b/board/mister/de25nano/linux-patches/0035-hid-nintendo-home-led-nonfatal.patch new file mode 120000 index 0000000..e9db425 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0035-hid-nintendo-home-led-nonfatal.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0035-hid-nintendo-home-led-nonfatal.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0036-btusb-csr-clone-lmp-subver-2512.patch b/board/mister/de25nano/linux-patches/0036-btusb-csr-clone-lmp-subver-2512.patch new file mode 120000 index 0000000..769da19 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0036-btusb-csr-clone-lmp-subver-2512.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0036-btusb-csr-clone-lmp-subver-2512.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0037-hid-playstation-dualsense-mute-btn-z.patch b/board/mister/de25nano/linux-patches/0037-hid-playstation-dualsense-mute-btn-z.patch new file mode 120000 index 0000000..06991e6 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0037-hid-playstation-dualsense-mute-btn-z.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches-beta/0037-hid-playstation-dualsense-mute-btn-z.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0038-hid-nintendo-nso-genesis-bt-pid.patch b/board/mister/de25nano/linux-patches/0038-hid-nintendo-nso-genesis-bt-pid.patch new file mode 120000 index 0000000..f22750e --- /dev/null +++ b/board/mister/de25nano/linux-patches/0038-hid-nintendo-nso-genesis-bt-pid.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0038-hid-nintendo-nso-genesis-bt-pid.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0039-hid-nintendo-nso-n64-genesis-stock-button-mapping.patch b/board/mister/de25nano/linux-patches/0039-hid-nintendo-nso-n64-genesis-stock-button-mapping.patch new file mode 120000 index 0000000..8178d4f --- /dev/null +++ b/board/mister/de25nano/linux-patches/0039-hid-nintendo-nso-n64-genesis-stock-button-mapping.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0039-hid-nintendo-nso-n64-genesis-stock-button-mapping.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0040-hid-nintendo-imu-name-suffix.patch b/board/mister/de25nano/linux-patches/0040-hid-nintendo-imu-name-suffix.patch new file mode 120000 index 0000000..e769b13 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0040-hid-nintendo-imu-name-suffix.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0040-hid-nintendo-imu-name-suffix.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0041-hid-nintendo-stock-led-classdev-names.patch b/board/mister/de25nano/linux-patches/0041-hid-nintendo-stock-led-classdev-names.patch new file mode 120000 index 0000000..d8f3326 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0041-hid-nintendo-stock-led-classdev-names.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0041-hid-nintendo-stock-led-classdev-names.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0042-hid-playstation-stock-lightbar-led-names.patch b/board/mister/de25nano/linux-patches/0042-hid-playstation-stock-lightbar-led-names.patch new file mode 120000 index 0000000..d342a73 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0042-hid-playstation-stock-lightbar-led-names.patch @@ -0,0 +1 @@ +../../de10nano/linux-patches/0042-hid-playstation-stock-lightbar-led-names.patch \ No newline at end of file diff --git a/board/mister/de25nano/linux-patches/0101-mmc-sdhci-cadence-agilex5-40-bit-dma-mask.patch b/board/mister/de25nano/linux-patches/0101-mmc-sdhci-cadence-agilex5-40-bit-dma-mask.patch new file mode 100644 index 0000000..466a6a4 --- /dev/null +++ b/board/mister/de25nano/linux-patches/0101-mmc-sdhci-cadence-agilex5-40-bit-dma-mask.patch @@ -0,0 +1,175 @@ +From 10fade754cb1294e18c675486fc72e41825d425b Mon Sep 17 00:00:00 2001 +From: "Michael C. Ferguson" +Date: Wed, 2 Sep 2026 00:45:51 -0500 +Subject: [PATCH 1/1] mmc: sdhci-cadence: limit Agilex 5 SD4HC to a 40-bit DMA + mask + +The Cadence SD4HC macro integrated on Intel/Altera Agilex 5 exposes only +40 of the 64 descriptor address bits to the HPS interconnect, but its +capability register still advertises 64-bit addressing. The driver binds +it today through the generic "cdns,sd4hc" entry, which carries no driver +data, so sdhci_setup_host() reaches sdhci_set_dma_mask() with +SDHCI_USE_64_BIT_DMA set and installs DMA_BIT_MASK(64). + +Nothing then keeps DMA addresses inside the controller's wired range. +With the SMMU enabled the IOVA allocator hands out addresses top-down, so +the very first mapping tends to land above bit 39; the controller drops +the high bits and the resulting access is reported by arm-smmu-v3 as an +"input address caused fault" (F_TRANSLATION) with no mmc-shaped +diagnostic anywhere. With the SMMU disabled the same truncation +corrupts DMA to physical memory above 1 TiB. + +Give the macro its own compatible with driver data that installs a +40-bit mask through the existing ->set_dma_mask hook, as sdhci-tegra.c +does (sdhci-of-esdhc.c narrows the ls1043a/ls1046a eSDHC to 40 bits too, +but from ->enable_dma). The hook runs inside sdhci_setup_host() in place +of sdhci_set_dma_mask(), which is the only point early enough: the mask +has to be right before the ADMA descriptor table is allocated, and any +dma_set_mask_and_coherent() call made in probe before sdhci_add_host() +would simply be overwritten by sdhci_set_dma_mask(). Because the hook +replaces sdhci_set_dma_mask() outright, a failed 40-bit mask falls back +to PIO rather than to a 32-bit retry; that is acceptable here, since a +40-bit mask cannot fail on this SoC's bus. + +SDHCI_QUIRK2_BROKEN_64_BIT_DMA is deliberately not used. It would clear +SDHCI_USE_64_BIT_DMA and fall back to a 32-bit mask and 32-bit ADMA2 +descriptors. The 64-bit descriptor format works on this controller -- +only the address range is narrower -- and a 32-bit mask would bounce +every transfer through swiotlb on a SoC whose DRAM base is 0x8000_0000. +40 bits covers the whole Agilex 5 physical address map. + +The binding gains the new string in the vendor enum, so the + + compatible = "intel,agilex5-sd4hc", "cdns,sd4hc"; + +form used by Agilex 5 board device trees is dtbs_check-clean. Boards +that keep only the generic fallback are unaffected and keep today's +behaviour. + +The binding also gains four properties this integration needs and the +schema never described: Agilex 5 wires two clocks to the macro (biu and +ciu) where the schema allowed one, and the controller sits behind an +arm-smmu-v3 on an I/O-coherent port. Without them a conforming device +tree trips "clocks: ... is too long" and an unevaluatedProperties error +naming clock-names, iommus and dma-coherent. No existing board is +constrained: clocks keeps minItems: 1, clock-names is optional and +likewise minItems: 1, and iommus and dma-coherent are optional. +dma-coherent is already declared by nine other mmc bindings +(arasan,sdhci.yaml, fsl,esdhc.yaml, sdhci-am654.yaml, ...) and +"iommus: maxItems: 1" matches usb/dwc2.yaml, which describes another +Agilex 5 peripheral behind the same SMMU. + +Mainline currently ships no Agilex 5 mmc node, so nothing in tree +changes. Carried locally against Linux 7.2.2 for the DE25-Nano pending +upstream submission; it is an upstream candidate as-is, and whether and +when to post it to linux-mmc is the maintainer's call. + +Signed-off-by: Michael C. Ferguson +--- + .../devicetree/bindings/mmc/cdns,sdhci.yaml | 15 ++++++- + drivers/mmc/host/sdhci-cadence.c | 41 +++++++++++++++++++ + 2 files changed, 55 insertions(+), 1 deletion(-) + +diff --git a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml +index 6c7317d..10666b0 100644 +--- a/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml ++++ b/Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml +@@ -14,6 +14,7 @@ properties: + items: + - enum: + - amd,pensando-elba-sd4hc ++ - intel,agilex5-sd4hc + - microchip,mpfs-sd4hc + - microchip,pic64gx-sd4hc + - mobileye,eyeq-sd4hc +@@ -28,11 +29,23 @@ properties: + maxItems: 1 + + clocks: +- maxItems: 1 ++ minItems: 1 ++ maxItems: 2 ++ ++ clock-names: ++ minItems: 1 ++ items: ++ - const: biu ++ - const: ciu + + resets: + maxItems: 1 + ++ iommus: ++ maxItems: 1 ++ ++ dma-coherent: true ++ + # PHY DLL input delays: + # They are used to delay the data valid window, and align the window to + # sampling clock. The delay starts from 5ns (for delay parameter equal to 0) +diff --git a/drivers/mmc/host/sdhci-cadence.c b/drivers/mmc/host/sdhci-cadence.c +index 435603c..d1ad6ab 100644 +--- a/drivers/mmc/host/sdhci-cadence.c ++++ b/drivers/mmc/host/sdhci-cadence.c +@@ -6,6 +6,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -500,6 +501,42 @@ static const struct sdhci_cdns_drv_data sdhci_eyeq_drv_data = { + }, + }; + ++/* ++ * The SD4HC instance integrated on Intel/Altera Agilex 5 drives only 40 of the ++ * 64 ADMA2 descriptor address bits onto the HPS interconnect. The capability ++ * register still advertises 64-bit addressing, so sdhci_set_dma_mask() would ++ * install a 64-bit DMA mask and let the allocator (or, with the SMMU enabled, ++ * the IOVA allocator, which allocates top-down) hand out an address above bit ++ * 39. That address is silently truncated by the controller, which shows up as ++ * an arm-smmu-v3 "input address caused fault" (F_TRANSLATION) rather than as ++ * anything mmc-shaped. ++ * ++ * Cap the mask at 40 bits instead of setting SDHCI_QUIRK2_BROKEN_64_BIT_DMA: ++ * the 64-bit descriptor format itself works, it is only the address range that ++ * is narrower, and forcing the 32-bit fallback would bounce every transfer on ++ * a SoC whose DRAM starts at 0x8000_0000. ++ */ ++static int sdhci_cdns_agilex5_set_dma_mask(struct sdhci_host *host) ++{ ++ return dma_set_mask_and_coherent(mmc_dev(host->mmc), DMA_BIT_MASK(40)); ++} ++ ++static const struct sdhci_ops sdhci_cdns_agilex5_ops = { ++ .set_clock = sdhci_set_clock, ++ .get_timeout_clock = sdhci_cdns_get_timeout_clock, ++ .set_bus_width = sdhci_set_bus_width, ++ .set_dma_mask = sdhci_cdns_agilex5_set_dma_mask, ++ .reset = sdhci_reset, ++ .platform_execute_tuning = sdhci_cdns_execute_tuning, ++ .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, ++}; ++ ++static const struct sdhci_cdns_drv_data sdhci_cdns_agilex5_drv_data = { ++ .pltfm_data = { ++ .ops = &sdhci_cdns_agilex5_ops, ++ }, ++}; ++ + static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = { + .pltfm_data = { + .ops = &sdhci_cdns_ops, +@@ -653,6 +690,10 @@ static const struct of_device_id sdhci_cdns_match[] = { + .compatible = "mobileye,eyeq-sd4hc", + .data = &sdhci_eyeq_drv_data, + }, ++ { ++ .compatible = "intel,agilex5-sd4hc", ++ .data = &sdhci_cdns_agilex5_drv_data, ++ }, + { .compatible = "cdns,sd4hc" }, + { /* sentinel */ } + }; diff --git a/board/mister/de25nano/linux-patches/0102-firmware-stratix10-svc-match-agilex5-svc.patch b/board/mister/de25nano/linux-patches/0102-firmware-stratix10-svc-match-agilex5-svc.patch new file mode 100644 index 0000000..aa48cbd --- /dev/null +++ b/board/mister/de25nano/linux-patches/0102-firmware-stratix10-svc-match-agilex5-svc.patch @@ -0,0 +1,52 @@ +From 882e32be97592b169d9ab0bdd069a3a3225bc3d0 Mon Sep 17 00:00:00 2001 +From: "Michael C. Ferguson" +Date: Wed, 2 Sep 2026 00:23:29 -0500 +Subject: [PATCH 1/1] firmware: stratix10-svc: match intel,agilex5-svc + +arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi has carried + + firmware { + svc { + compatible = "intel,agilex5-svc"; + ... + }; + }; + +since the SoC was added, and Documentation/devicetree/bindings/firmware/ +intel,stratix10-svc.yaml lists "intel,agilex5-svc" in its compatible +enum -- but stratix10_svc_drv_match[] has never held that string. The +node is therefore inert on every released kernel: stratix10_svc_init()'s +of_platform_populate() skips it, no service driver is created, and every +would-be client of the SDM (fpga-mgr, rsu, fcs) has nothing to attach to. +Because the node is a lone compatible with no fallback string, the OF +core cannot rescue it either. + +Add the match entry. This is a pure match-table addition and changes no +behaviour for the two strings already listed; the Agilex 5 SDM speaks the +same SMC/mailbox protocol the driver already implements for Agilex, so no +per-compatible driver data is needed and none is added. + +With this in place, boards no longer need to override the DT compatible to +"intel,agilex-svc" purely to get a driver bound. + +Carried locally against Linux 7.2.2 for the DE25-Nano pending upstream +submission; it is an upstream candidate as-is, and whether and when to +post it to linux-fpga is the maintainer's call. + +Signed-off-by: Michael C. Ferguson +--- + drivers/firmware/stratix10-svc.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c +index 5e20057..568c998 100644 +--- a/drivers/firmware/stratix10-svc.c ++++ b/drivers/firmware/stratix10-svc.c +@@ -1911,6 +1911,7 @@ EXPORT_SYMBOL_GPL(stratix10_svc_free_memory); + static const struct of_device_id stratix10_svc_drv_match[] = { + {.compatible = "intel,stratix10-svc"}, + {.compatible = "intel,agilex-svc"}, ++ {.compatible = "intel,agilex5-svc"}, + {}, + }; + diff --git a/board/mister/de25nano/linux-patches/README.md b/board/mister/de25nano/linux-patches/README.md new file mode 100644 index 0000000..28b00e5 --- /dev/null +++ b/board/mister/de25nano/linux-patches/README.md @@ -0,0 +1,144 @@ +# DE25-Nano kernel patch series — what is here and why + +**Base:** mainline Linux **7.2.2**, aarch64 (Agilex 5). See +[`docs/de25-implementation-path.md`](../../../../docs/de25-implementation-path.md) §5 for the +version pin. + +**Shape.** This directory follows the same house pattern as +[`board/mister/de10nano/linux-patches-beta/`](../../de10nano/linux-patches-beta/): every patch +shared with the DE10 is a **relative symlink** into the DE10 series, so there is exactly one copy +of each shared patch in the tree and a fix to it lands on both boards at once. Only genuinely +DE25-specific patches are real files here. + +**Numbering.** The shared series keeps its DE10 4-digit prefixes (`0010`–`0042`) so +`support/scripts/apply-patches.sh` applies it in the same order on both boards. DE25-local +patches start at **`0101`**, leaving `0043`–`0099` free for anything the DE10 series adds later +without renumbering. + +**Where each symlink points.** Most shared patches are byte-identical between +`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.) + +That choice is not cosmetic — the shipped 6.18-anchored copies **hard-fail** on 7.2.2 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. + +--- + +## Audit mapping — all 40 rows of `docs/de25-patch-portability.md` §2, plus two later additions + +Source of the verdicts: [`docs/de25-patch-portability.md`](../../../../docs/de25-patch-portability.md) +(D0.3, desk audit 2026-08-21). "Series" is that document's second verdict. + +| Audit # | Patch | Audit verdict / series | Here? | Reason | +|---|---|---|---|---| +| 1 | `0001-fbdev-add-MiSTer_fb-driver` | board-specific / de10-only | **excluded** | DE10 fabric frame-reader at `0x22000000` + GIC SPI 40; also arm-only `NO_IRQ` and a `MEMREMAP_WT` correctness argument that is false on arm64 (audit §4.1). | +| 2 | `0002-sound-add-MiSTer-audio-spi-and-snd-dummy-MiSTer-model` | portable-**with-rework** / shared (gated) | **excluded** | The only non-`portable-as-is` row in the shared set. It does **not compile on aarch64** (`&MrBufferInfo.addr`, an `unsigned int *`, passed where `dma_alloc_coherent()` wants `dma_addr_t *`), and the audit shows the C fix alone is wrong — the field is a 32-bit FPGA **wire** descriptor and Agilex 5 DRAM starts at `0x80000000` (§4.4). Audit §7 Q8 makes "widen the descriptor or constrain the ring below 4 GiB" an **open owner decision**, and ADR 0027 scopes the first DE25 image to a bare developer OS with no MiSTer binaries, so nothing here consumes `/dev/MrAudio`. **Re-add once Q8 is decided and the type/descriptor fix exists** — at that point it is a shared patch behind a defconfig gate, per audit §3.2(a) Option 1. | +| 3 | `0003-cpufreq-cyclone5-de10nano-overclock` | board-specific / de10-only | **excluded** | Cyclone V gen5 clock-manager MMIO at fixed offsets; Agilex 5 has a different clkmgr. | +| 4 | `0004-dts-de10nano-MiSTer` | board-specific / de10-only | **excluded** | The DE10 board DTS. The DE25 gets its own arm64 DTS (separate work item). | +| 5 | `0010-hid-guncon2` | portable-as-is / shared | **included** | Raw `usb_driver` on generic USB/input APIs. | +| 6 | `0011-hid-guncon3` | portable-as-is / shared | **included** | Standalone USB interrupt-URB HID driver; USB-generic. | +| 7 | `0012-hid-fanatec` | portable-as-is / shared | **included** | USB HID force-feedback; no MMIO/DMA/endianness/pointer-size exposure. | +| 8 | `0013-hid-flydigi-vader` | portable-as-is / shared | **included** | Bluetooth HID remap driver; `BTN_GRIP*` are arch-independent UAPI. | +| 9 | `0014-hid-gamecube-adapter` | portable-as-is / shared | **included** | Generic USB-HID driver over `u8` buffers. | +| 10 | `0015-hid-nintendo-nso-famicom` | portable-as-is / shared | **included (beta copy)** | Two enums, two helpers, two dispatch arms, one button table. The beta copy is re-anchored for 7.x; functional hunks are byte-identical. | +| 11 | `0016-hid-microsoft-elite2-paddles` | portable-as-is / shared | **included** | HID usage mapping of an 8-bit paddle bitmask. | +| 12 | `0017-xpad-mister-deltas` | portable-as-is / shared | **included** | Endianness already handled via `le16_to_cpup()`. | +| 13 | `0018-hid-controllable-quirk` | portable-as-is / shared | **included** | Two device-ID table rows. | +| 14 | `0019-hidpp-k400-fn-inversion` | portable-as-is / shared | **included** | HID++ feature logic; single-byte payloads. | +| 15 | `0020-mmc-no-led-on-send-status` | portable-as-is / shared | **included** | One conditional in core `mmc_start_request()`; host-driver-agnostic. | +| 16 | `0022-hid-playstation-ds4-mac-fix` | portable-as-is / shared | **included** | Probe error-handling policy only. | +| 17 | `0023-hid-wiimote-fixes` | portable-as-is / shared | **included** | Bluetooth HID input mapping only. | +| 18 | `0024-hid-input-keyrah-europe1` | portable-as-is / shared | **included** | One byte in the `hid_keyboard[256]` scancode table. | +| 19 | `0025-usbhid-jspoll-gamepad` | portable-as-is / shared | **included** | One `case` in `usbhid_start()`'s polling switch. | +| 20 | `0026-input-mousedev-eviocgrab` | portable-as-is / shared | **included** | `EVIOCGRAB`'s argument is a bool, not a pointer — no `compat_ptr()` 32-vs-64 hazard. | +| 21 | `0027-mt76x2u-release-xbox-adapter-ids` | portable-as-is / shared | **included** | Deletes two `USB_DEVICE()` rows; a USB ID table has no architecture. | +| 22 | `0028-dwc2-fix-unaligned-in-split` | portable-as-is / shared | **included** | Real generic dwc2 bug; Agilex 5 also declares `snps,dwc2`. | +| 23 | `0029-leds-gpio-brightness-hw-changed` | portable-as-is / shared | **included** | Generic `leds-gpio`/LED-class change; inert until a DTS wires an activity LED. | +| 24 | `0030-i2c-designware-quiet-timeout` | portable-as-is / shared | **included (beta copy)** | Generic Synopsys DW I2C, which Agilex 5's HPS I2C also uses. Beta copy is re-anchored on 7.x's renamed `i2c_dw_init()`; the changed line is byte-identical. | +| 25 | `0031-exfat-samsung-symlinks` | portable-as-is / shared | **included** | Filesystem-format code; `inode_nohighmem()` is a no-op without HIGHMEM (normal on arm64). | +| 26 | `0032-hid-nintendo-joycon-combo-led` | portable-as-is / shared | **included** | Virtual `led_classdev` as a userspace mailbox. | +| 27 | `0033-hid-playstation-dualsense-player-id-led` | portable-as-is / shared | **included** | LED-class/HID only. | +| 28 | `0034-hid-nintendo-nes-famicom-stock-ab-mapping` | portable-as-is / shared | **included** | Userspace ABI (stock A/B order), not cosmetic — audit §5.2. | +| 29 | `0035-hid-nintendo-home-led-nonfatal` | portable-as-is / shared | **included** | Only *partially* fixed upstream; the registration path is still fatal. Carry. | +| 30 | `0036-btusb-csr-clone-lmp-subver-2512` | portable-as-is / shared | **included** | One `else if` on `le16_to_cpu(rp->lmp_subver)`. | +| 31 | `0037-hid-playstation-dualsense-mute-btn-z` | portable-as-is / shared | **included (beta copy)** | **Functional, not cosmetic**: `BTN_Z` shifts every higher `EV_KEY` ordinal, so the shipped `gamecontrollerdb` `platform:MiSTer` rows depend on it. Beta copy differs by hunk offsets only. | +| 32 | `0038-hid-nintendo-nso-genesis-bt-pid` | portable-as-is / shared | **included** | `hdev->product` rewrite before `devm_input_allocate_device()`. | +| 33 | `0039-hid-nintendo-nso-n64-genesis-stock-button-mapping` | portable-as-is / shared | **included** | Static mapping-table reassignment (userspace ABI). | +| 34 | `0040-hid-nintendo-imu-name-suffix` | portable-as-is / shared | **included** | One format-string token Main_MiSTer filters on. | +| 35 | `0041-hid-nintendo-stock-led-classdev-names` | portable-as-is / shared | **included** | `devm_kasprintf()` format restoring stock LED names. | +| 36 | `0042-hid-playstation-stock-lightbar-led-names` | portable-as-is / shared | **included** | LED-class/HID only. | +| 37 | `0043-dts-uio-doorbells` | board-specific / de10-only *(beta)* | **excluded** | Eight `generic-uio` nodes on Cyclone V GIC SPI 48–55; DP-9 adopts Agilex-native idioms instead. `CONFIG_CMDLINE_EXTEND` does not exist on arm64. | +| 38 | `0044-dts-uio-fpga-regions` | board-specific / de10-only *(beta)* | **excluded** | Cyclone V lwhps2fpga/f2sdram apertures; depends on the `mem=511M` bootarg. | +| 39 | `0045-uio-writecombine` | **portable-as-is** / de10-only *(beta)* | **excluded** | Generic and arch-independent, and it would *work* on arm64 — but per DP-9 it has nothing to attach to until a DE25 GHRD exists. Audit §3.2(b)/§7 Q10 keeps "split the generic half to shared/upstream" open as an owner decision; if that is taken, this becomes a candidate for this directory. | +| 40 | `0046-dts-ramoops` | board-specific / de10-only *(beta)* | **excluded** | Every number derives from `mem=511M`, MiSTer's `0x1FFFF000` mailbox and ARM32's HIGHMEM model. The *capability* is worth more on DE25, but the arithmetic must be re-derived (DRAM at `0x80000000`, `svcbuffer@0` already reserved, no HIGHMEM on arm64). | +| — | `0047-btusb-mercusys-ma530-2c4e-0115` | *post-audit (added 2026-09-02)* | **excluded** | Not in the audit; it is a **backport of a mainline commit that is already in v7.2**. Its own header says "DELETE THIS PATCH the moment the kernel pin leaves 6.18.y for 7.2 or newer — at that point the ID is in-tree and re-adding it would collide." This board is on 7.2.2, so the ID is already present. (It is likewise absent from `linux-patches-beta/series`.) | +| — | `0021` | — | n/a | No such patch; the DE10 series has never had one. | + +### DE25-local patches (not in the audit — new work) + +| # | Patch | Why | +|---|---|---| +| 101 | `0101-mmc-sdhci-cadence-agilex5-40-bit-dma-mask` | Gives `intel,agilex5-sd4hc` its own `of_device_id` entry whose driver data installs a 40-bit DMA mask via the existing `sdhci_ops->set_dma_mask` hook. Addresses `docs/de25-implementation-path.md` §8 Q2 (the leading hypothesis for the mmc0/`arm-smmu-v3` `F_TRANSLATION` fault): mainline takes `DMA_BIT_MASK(64)` where the controller drives only 40 address bits. Its binding hunk also completes `Documentation/devicetree/bindings/mmc/cdns,sdhci.yaml` for this integration — the new compatible string, `clocks` widened to 2 with `clock-names` (`biu`, `ciu`), and `iommus`/`dma-coherent` declared — which is what makes the DE25 `mmc0` node dtbs_check-clean (`docs/de25-dts-rationale.md` §2.4). Upstreamable as-is; carried locally pending submission. | +| 102 | `0102-firmware-stratix10-svc-match-agilex5-svc` | One-line match-table addition so mainline's `intel,agilex5-svc` DT node (shipped in `socfpga_agilex5.dtsi`, listed in the binding, matched by no driver) actually binds. `docs/de25-implementation-path.md` §3.1 bullet 2. Removes the need for a DTS-side compatible override. Upstreamable as-is; carried locally pending submission. | + +### Counts + +| | | +|---|---| +| Audit rows | 40 | +| Included from the audit | **32** (all 32 `portable-as-is` + `shared` rows) | +| Excluded from the audit | **8** — 7 `de10-only` (`0001` `0003` `0004` `0043` `0044` `0045` `0046`) + `0002` (`portable-with-rework`, deferred) | +| Post-audit patches considered | 1 (`0047`, excluded — already upstream at 7.2) | +| DE25-local patches | **2** (`0101`, `0102`) | +| **Total applied here** | **34** | + +## Note for the DE25 DTS + +`0101` adds `intel,agilex5-sd4hc` to the **vendor `enum` inside the `items:` list** in +`cdns,sdhci.yaml`, which is the two-element form: + +```dts +compatible = "intel,agilex5-sd4hc", "cdns,sd4hc"; +``` + +**The order is load-bearing for `dtbs_check`** — the vendor string first, `cdns,sd4hc` second, and +both present. A single-string `compatible = "intel,agilex5-sd4hc"` would bind the driver but fail +the schema; `compatible = "cdns,sd4hc"` alone stays schema-clean but binds the generic entry and +therefore **does not get the 40-bit mask**. + +The same binding hunk also declares the four properties the Agilex 5 `mmc0` node needs and the +schema previously lacked, so the node may carry all of them and stay clean: + +- `clocks` — now `minItems: 1, maxItems: 2`, and `clock-names` accepts `biu`, `ciu` in that order; +- `iommus` — `maxItems: 1` (same form as `usb/dwc2.yaml` for another Agilex 5 peripheral); +- `dma-coherent` — `true` (as nine other `mmc/*.yaml` bindings already declare). + +Nothing here is made mandatory, so the in-tree `cdns,sd4hc` boards are unaffected — verified: +`uniphier-ld20-ref.dtb` and `elba-asic.dtb` both build `CHECK_DTBS=y`-clean with the patch applied. + +`0102` means the DTS does **not** need to override `/firmware/svc`'s compatible to +`"intel,agilex-svc"`; mainline's `"intel,agilex5-svc"` now binds directly, and the binding's +`allOf` requirement that `iommus` be present for that string continues to hold. + +## Verification + +Both DE25-local patches were generated with `git format-patch` against a pristine +`linux-7.2.2` tree and re-checked with `git apply --check`. The whole directory applies with +Buildroot's own applier at its `patch -F0` (zero-fuzz) setting: + +``` +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, +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 +(CHKDT / LINT / STYLE / example DTC all pass). diff --git a/board/mister/de25nano/linux.fragment b/board/mister/de25nano/linux.fragment new file mode 100644 index 0000000..1c0d235 --- /dev/null +++ b/board/mister/de25nano/linux.fragment @@ -0,0 +1,240 @@ +# +# 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/linux/linux.hash b/board/mister/de25nano/patches/linux/linux.hash new file mode 120000 index 0000000..4438d0e --- /dev/null +++ b/board/mister/de25nano/patches/linux/linux.hash @@ -0,0 +1 @@ +../../../de10nano/patches/linux/linux.hash \ No newline at end of file diff --git a/board/mister/de25nano/socfpga_agilex5_de25nano.dts b/board/mister/de25nano/socfpga_agilex5_de25nano.dts new file mode 100644 index 0000000..9f1af14 --- /dev/null +++ b/board/mister/de25nano/socfpga_agilex5_de25nano.dts @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Terasic DE25-Nano — Intel/Altera Agilex 5 (A5EB013BB23BE4S) + * + * Board device tree for MAINLINE Linux 7.2.x. Authored against + * arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi as shipped in 7.2.2; every + * node below is reconciled against the driver mainline actually binds, not + * against what a vendor tree binds. See docs/de25-dts-rationale.md for the + * node-by-node provenance table and the SMMU/mmc0 decision. + * + * Reference files this was harvested from (all read-only, none copied + * verbatim): + * TERASIC github.com/terasic/linux-socfpga @ de25-nano-6.12.11-lts + * arch/arm64/boot/dts/intel/socfpga_agilex5_de25_nano.dts (+ .dtsi) + * ALTERA github.com/altera-fpga/linux-socfpga @ socfpga-6.18.20-lts + * arch/arm64/boot/dts/intel/socfpga_agilex5_de25_nano.dts (+ .dtsi) + * FRIEND /mnt/source/de25-linux (6.18.38 + vendor backports) + * arch/arm64/boot/dts/intel/socfpga_agilex5_de25_nano.dts (+ .dtsi) + * MAINLINE linux-7.2.2 arch/arm64/boot/dts/intel/socfpga_agilex5{,_socdk}.dts + * + * Copyright (C) 2023, Intel Corporation (upstream socfpga_agilex5) + * Copyright (C) 2025, Altera Corporation (DE25-Nano board wiring) + */ + +#include "arm64/intel/socfpga_agilex5.dtsi" + +/ { + /* + * MAINLINE + TERASIC + ALTERA + FRIEND all use the SoCDK compatible. + * It is factually wrong for a DE25-Nano, but the Agilex 5 board list in + * Documentation/devicetree/bindings/arm/altera.yaml is a closed five-value + * enum, so a "terasic,de25-nano" string would fail dtbs_check and buy + * nothing: arm64 has no machine-match table, so no code reads this. + * Board identity lives in "model" until an upstream altera.yaml patch lands. + */ + model = "Terasic DE25-Nano"; + compatible = "intel,socfpga-agilex5-socdk", "intel,socfpga-agilex5"; + + aliases { + /* Console UART: uart1 (serial@10c02100). TERASIC, ALTERA and FRIEND + * all agree; MAINLINE's socdk board uses uart0, which is a different + * board. See docs/de25-dts-rationale.md §"Console UART". */ + serial0 = &uart1; + /* ethernet0 is LOAD-BEARING, not cosmetic: U-Boot's + * fdt_fixup_ethernet() walks /aliases for ethernetN and writes + * $ethaddr into the node it names. Same lesson as the DE10 + * (docs/dts-comparison.md §3.3). TERASIC + ALTERA + FRIEND. */ + ethernet0 = &gmac0; + /* ALTERA only. Pins the sole enabled i2c adapter to /dev/i2c-1 + * instead of letting it float on dynamic numbering — the DE10's + * A14 lesson (docs/dts-comparison.md §2) is that i2c adapter + * numbering is worth making deterministic. */ + i2c1 = &i2c1; + }; + + chosen { + stdout-path = "serial0:115200n8"; /* TERASIC + ALTERA + FRIEND */ + /* + * No bootargs. TERASIC and FRIEND carry a stale string whose + * console=/earlycon= name uart0's address (0x10c02000) while their + * own stdout-path names uart1 — self-contradictory, and dead in any + * case because U-Boot's fdt_chosen() overwrites /chosen/bootargs + * from $bootargs. ALTERA's 2025 in-house file dropped it; so do we. + */ + }; + + leds { + compatible = "gpio-leds"; + + /* + * Node name from ALTERA. TERASIC/FRIEND call it "hps0", which does + * not match leds-gpio.yaml's child pattern "(^led-[0-9a-f]$|led)" + * and is therefore rejected by that binding's additionalProperties. + * The class-device name comes from "label", not the node name, so + * /sys/class/leds/hps_led0 is unchanged — same ABI as the DE10. + */ + led-0 { + label = "hps_led0"; + gpios = <&portb 17 GPIO_ACTIVE_LOW>; /* TERASIC + ALTERA + FRIEND */ + }; + }; + + /* + * MAINLINE's socfpga_agilex5.dtsi has no memory node, so the board file + * must supply one. 1 GiB at 0x8000_0000 matches the *UM* and the DE25 + * U-Boot DTS constant (de25-uboot-socfpga:.../socfpga_agilex5_de25_nano- + * u-boot.dtsi, "/* 1GB *\/ reg = <0 0x80000000 0 0x40000000>"). + * + * Be precise about what that is: a DECLARED CONSTANT, not a measurement. + * The real size comes from the IO96B controller at runtime — + * sdram_agilex5.c computes hw_size from io96b_ctrl->overall_size, caps + * the DT value at it, and prints "DDR: Warning ..." on any mismatch — + * and U-Boot then rewrites Linux's node wholesale from bi_dram + * (arch/arm/lib/bootm-fdt.c fdt_fixup_memory_banks(), + * CONFIG_ARCH_FIXUP_FDT_MEMORY default y). So this value is a fallback + * that only matters if that fixup does not run. + * + * It is still the right value to state. TERASIC and FRIEND hard-code + * 2 GiB, which over-claims on a 1 GiB board; ALTERA and MAINLINE's socdk + * declare size 0, which boots nothing if the fixup is skipped. + * Under-claiming degrades gracefully where neither of those does. + * First-boot action: capture U-Boot's "DDR:" lines and confirm. + * + * Note the consequence for DMA: all DRAM lives in + * 0x8000_0000..0xBFFF_FFFF, i.e. entirely inside 32 bits. + */ + memory@80000000 { + device_type = "memory"; + reg = <0x0 0x80000000 0x0 0x40000000>; + }; + + /* + * FPGA region. Absent from every mainline base and authored here + * (docs/de25-implementation-path.md §3.1). + * + * Deliberately NO fpga-bridges property and NO bridge nodes: the + * Cyclone V fpga_bridge0..3 shape has no Agilex analogue and must not be + * transliterated (docs/de25-fpga-reconfig.md §4.2). The SDM handles + * bridge state itself. + * + * config-complete-timeout-us is deliberately NOT set here — it belongs on + * the per-core OVERLAY, not on the base region (docs/de25-fpga-reconfig.md + * §4.3); of-fpga-region.c only reads it from the overlay's target. + */ + base_fpga_region: fpga-region { + compatible = "fpga-region"; + #address-cells = <0x2>; + #size-cells = <0x2>; + fpga-mgr = <&fpga_mgr>; + }; +}; + +/* + * MAINLINE 7.x ships /firmware/svc with compatible = "intel,agilex5-svc", + * method = "smc", memory-region = <&service_reserved> and iommus = <&smmu 10>. + * ALL of that is inherited unchanged — including the compatible. + * + * DEPENDENCY, stated plainly: "intel,agilex5-svc" is in the binding's enum but + * has never been in stratix10_svc_drv_match[], so on a STOCK 7.2 kernel this + * node is inert — of_platform_populate() skips it, no service device is + * created, and the fpga-mgr child below has nothing to attach to. This DTS + * therefore depends on linux-patches/0102, which adds the match entry (or on + * the equivalent landing upstream). If 0102 is ever dropped, the fallback is a + * one-line override here to compatible = "intel,agilex-svc", which is in both + * the match table and the enum; see docs/de25-dts-rationale.md. + * + * Keeping the agilex5 string also keeps the binding's allOf satisfied: it + * *requires* iommus for that compatible, and the inherited iommus = <&smmu 10> + * supplies it. + * + * Three structural constraints ride on this subtree and are all satisfied by + * MAINLINE's node shape, which we inherit rather than re-author: + * 1. the node must literally be named "svc" — stratix10-soc.c:471 + * of_find_node_by_name(NULL, "svc") + * 2. its parent must literally be named "firmware" — stratix10-svc.c:2086 + * of_find_node_by_name(NULL, "firmware") + * 3. method = "smc" must be present or probe fails -ENXIO — get_invoke_func() + */ +&{/firmware/svc} { + /* + * Two-string fallback form. OF matching walks the whole compatible list + * (drivers/of/base.c __of_device_is_compatible), and stratix10-soc.c's + * match table carries no per-compatible .data and never branches on which + * entry matched, so a fallback match is functionally identical to an exact + * one. This binds the STOCK, unpatched driver. + * + * It does warn under dtbs_check today because + * intel,stratix10-soc-fpga-mgr.yaml is still a plain two-value enum; + * Khairul's v6 binding patch, which turns it into the items/oneOf form, + * has not landed at 7.2 or at master. Accepted per + * docs/de25-implementation-path.md §2.5 — the warning disappears when that + * binding lands, and dropping the agilex5 string would lose SoC identity. + */ + fpga_mgr: fpga-mgr { + compatible = "intel,agilex5-soc-fpga-mgr", + "intel,agilex-soc-fpga-mgr"; + }; +}; + +/* + * SMMU: DISABLED for wave 1 — a deliberate divergence from TERASIC, ALTERA and + * FRIEND, all three of which set status = "okay". This is MAINLINE 7.2's own + * default (the node is enabled at master), and it is not a convenience: with + * the SMMU ON, mainline's FPGA-configuration path CANNOT work on this SoC. + * + * Why, traced through mainline 7.2.2 source: + * - stratix10-svc.c:865-869 takes the shared-buffer address straight out of + * the INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM SMC return (res.a1/res.a2). + * - :956 devm_memremap()s that PHYSICAL address. + * - :1873-1876 gen_pool_virt_to_phys() puts PHYSICAL addresses + * in the pool; :1780 copies one into pdata->paddr. + * - :607 (RECONFIG_DATA_SUBMIT) and :655 (FCS) hand that raw physical + * address to the SDM as SMC argument a1. + * - grep iommu_map|dma_map_single|dma_alloc over the whole file -> 0 hits. + * Meanwhile the svc device inherits iommus = <&smmu 10>, and + * arm_smmu_def_domain_type() (arm-smmu-v3.c:4294-4304) returns 0 for anything + * non-PCI, so the core gives it the build default IOMMU_DEFAULT_DMA_STRICT — + * a TRANSLATED domain over an empty page table. Those physical addresses are + * then unmapped IOVAs and the first RECONFIG_DATA_SUBMIT faults. + * + * That SDM traffic really does traverse the SMMU is not an assumption: it is + * exactly what Terasic's vendor svc compensates for (IOVA carveout + + * iommu_map(), a +0x80000000 offset, and an SDM remapper disable), none of + * which exists in mainline at 6.18.44, 7.2 or master. + * + * SMMU-OFF is therefore the only configuration in which mainline's svc layer + * can possibly program this fabric. It is not *proven* to work either — the + * vendor driver hard-fails without the SMMU, and mainline never touches the + * remapper — but it is the leg to test first (de25-implementation-path.md + * §2.6 step 4), and the closest supporting evidence is the friend's + * iommu.passthrough=1 cold boot reaching a login prompt. + * + * Every iommus property in the tree is KEPT and simply goes inert: + * of_iommu_xlate() returns -ENODEV for an unavailable IOMMU node + * (drivers/iommu/of_iommu.c:28-29) and of_iommu_configure() treats -ENODEV as + * "this device has no IOMMU", not as an error. Every master then DMAs + * physically, which is safe here because all DRAM is below 4 GiB. + * + * ALTERNATIVE, for the second leg of the §2.6 test — swap the line below for: + * + * &smmu { status = "okay"; }; + * + * and change NOTHING else. Every iommus property (gmac0 1, mmc0 5, usb0 6, + * dmac0 8, dmac1 9, svc 10) is already present and correct for that shape, and + * it must stay that way: arm_smmu_init_initial_stes() fills EVERY stream-table + * entry with arm_smmu_make_abort_ste() (arm-smmu-v3.c:1925-1934), so under an + * ENABLED SMMU a master with no iommus property does not bypass — it aborts. + */ +&smmu { + status = "disabled"; +}; + +&soc { + /* + * mmc0 is absent from EVERY mainline base (6.18.44, 7.2, master) and is + * the SD-boot gate. Authored here in SD4HC form. + * + * reg / interrupts / resets / clocks / iommus / dma-coherent are + * byte-identical in TERASIC (:851-865) and FRIEND (:384-398), and ALTERA + * agrees on all of them except the reset list. ALTERA's node is NOT + * usable: it declares "altr,agilex5-sd6hc","cdns,sd6hc", and cdns,sd6hc + * exists nowhere in mainline — it targets Altera's out-of-tree SD6HC + * driver rewrite (docs/de25-implementation-path.md §4.1). + * + * The compatible must be exactly this pair, vendor string FIRST: + * that is the items:[enum, const] form linux-patches/0101 adds to + * cdns,sdhci.yaml, and with 0101 applied the first entry wins and + * installs the 40-bit DMA mask. A lone "cdns,sd4hc" would still bind — + * its absent .data is an explicitly supported path, sdhci_cdns_probe() + * falls back to &sdhci_cdns_drv_data on NULL — but it would bind + * SILENTLY WITHOUT the mask, which is the §8 Q2 failure. On a stock + * 7.2.2 without 0101 the node degrades to exactly that, i.e. to today's + * behaviour, rather than failing to probe. + */ + mmc0: mmc@10808000 { + compatible = "intel,agilex5-sd4hc", "cdns,sd4hc"; + reg = <0x10808000 0x1000>; /* TERASIC + ALTERA + FRIEND */ + interrupts = ; /* TERASIC + ALTERA + FRIEND */ + resets = <&rst SDMMC_RESET>; /* TERASIC + FRIEND */ + clocks = <&clkmgr AGILEX5_L4_MP_CLK>, /* biu — TERASIC + ALTERA + FRIEND */ + <&clkmgr AGILEX5_SDMCLK>; /* ciu — TERASIC + ALTERA + FRIEND */ + clock-names = "biu", "ciu"; + + /* + * SMMU decision (docs/de25-implementation-path.md §8 Q2). KEPT, but + * INERT as shipped: &smmu is disabled above, so of_iommu_xlate() + * returns -ENODEV and mmc0 DMAs physically. The property stays + * because it is correct hardware description and because it is + * mandatory the moment anyone flips &smmu to "okay" — under an + * enabled SMMU an undescribed master aborts rather than bypasses. + * With the SMMU off, the §8 Q2 40-bit truncation cannot bite at all: + * every DRAM address on this board is below 4 GiB. linux-patches/0101 + * is still correct and still wanted — it is what makes the SMMU-on leg + * of the §2.6 test survivable — it is just not load-bearing here. + */ + iommus = <&smmu 5>; /* TERASIC + ALTERA + FRIEND */ + /* + * KEPT. Corroborated inside MAINLINE itself: the sibling nand + * controller on this same SoC carries dma-coherent in + * socfpga_agilex5.dtsi:315, which is upstream's own statement that + * Agilex 5 HPS peripheral masters are coherent. All three vendor + * trees agree. + * + * Caveat now that the SMMU is off: the vendor trees all assert this + * with the SMMU ON, where cacheability of the access comes from the + * STE/IOMMU_CACHE attributes rather than from the master's own + * dma-coherent property, so their evidence does NOT transfer + * unchanged to the SMMU-off shape. Tagged [U2]: this must be + * re-checked by data integrity test in the shipped (SMMU-off) + * configuration, not inferred. If reads come back corrupt, DELETE + * THIS LINE FIRST — treating a coherent master as non-coherent is + * always correct, merely slower; the converse silently corrupts. + */ + dma-coherent; + + /* ---- board wiring below this line ---- */ + + bus-width = <4>; /* TERASIC + ALTERA + FRIEND */ + + /* + * FIRST-BOOT CLOCK CAP. Mainline's sdhci-cadence programs NONE of the + * 40 cdns,phy-* values the vendor trees carry (its property table knows + * only eleven cdns,phy-input-delay-* / cdns,phy-dll-delay-* names, + * sdhci-cadence.c:108-119), so Linux inherits whatever PHY state U-Boot + * left behind rather than configuring it. The ONLY boot of this board + * on a mainline sdhci-cadence driver — FRIEND, + * socfpga_agilex5_de25_nano.dts:110-126 — reached that state only after + * dropping high-speed advertisement and capping the clock at 25 MHz, + * following corrupted SD SCR reads. Start where the one working data + * point is. Lift this once a sustained dd read/write is clean; see + * docs/de25-dts-rationale.md [U3]. + */ + max-frequency = <25000000>; /* FRIEND */ + + /* + * KEPT even though FRIEND drops it, because dropping it would be + * theatre: sdhci.c:4572 sets MMC_CAP_SD_HIGHSPEED from the capability + * register's SDHCI_CAN_DO_HISPD (bit 21) regardless of DT, and our + * sdhci-caps-mask does not clear that bit. max-frequency above is the + * property that actually constrains the bus. If a bench test ever + * needs genuine default-speed-only, the real lever is widening the caps + * mask to <0x00002000 0x0020ff00>, not deleting this line. + */ + cap-sd-highspeed; /* TERASIC + ALTERA */ + disable-wp; /* TERASIC + ALTERA + FRIEND — no WP switch wired */ + no-1-8-v; /* TERASIC + FRIEND — 3.3V-only signalling */ + no-sdio; /* TERASIC + FRIEND — SD card slot only */ + + /* + * sdhci-caps / sdhci-caps-mask are LIVE on mainline: sdhci.c's + * __sdhci_read_caps() applies them to SDHCI_CAPABILITIES{,_1}. + * The uint64 is . 0xc800 in caps bits 15:8 sets the + * base clock to 0xc8 = 200 MHz; sdhci_cdns_ops has no + * .get_max_clock, so if the capability register reported 0 here + * probe would fail outright with "Hardware doesn't specify base + * clock frequency" (-ENODEV). Both vendor trees set this, so it is + * treated as load-bearing rather than decorative. + * The caps1 mask clears bit 13 (SDHCI_USE_SDR50_TUNING). + * Values from TERASIC; ALTERA masks 0x2007 instead, additionally + * removing SDR50/SDR104/DDR50 — moot for us because no-1-8-v already + * bars every UHS mode. + */ + sdhci-caps = <0x00000000 0x0000c800>; + sdhci-caps-mask = <0x00002000 0x0000ff00>; + + status = "okay"; + }; +}; + +&gmac0 { + status = "okay"; /* TERASIC + ALTERA + FRIEND */ + /* "rgmii", not "rgmii-id": ALTERA's file states the TX/RX delays are on + * the PCB, so asking the PHY for internal delay as well would double it. + * Same trap as the DE10's gmac1 (docs/dts-comparison.md §3.2). */ + phy-mode = "rgmii"; /* TERASIC + ALTERA + FRIEND */ + phy-handle = <&emac0_phy0>; + max-frame-size = <9000>; /* TERASIC + ALTERA + FRIEND */ + + /* + * Node name "mdio0" is kept deliberately. It does not match mdio.yaml's + * $nodename pattern, but it is what MAINLINE's own + * socfpga_agilex5_socdk.dts uses, so the warning is shared with the + * in-tree board and is not ours. stmmac finds this node by scanning + * children for compatible = "snps,dwmac-mdio" + * (stmmac_platform.c stmmac_of_get_mdio()), never by node name, so the + * name is cosmetic at runtime. + */ + mdio0 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "snps,dwmac-mdio"; + + /* + * FIXED relative to all three reference files, which write + * "ethernet-phy@0 { reg = <1>; }" — a unit-address/reg mismatch that + * dtc reports under -Wunit_address_vs_reg. PHY address 1 is the real + * value (all three agree on reg); only the unit address was wrong. + * Provably a no-op: of_mdiobus_register() addresses the PHY from + * "reg", never from the unit address. + */ + emac0_phy0: ethernet-phy@1 { + reg = <1>; + }; + }; +}; + +&gpio1 { + status = "okay"; /* TERASIC + ALTERA + FRIEND — portb, used by led-0 */ +}; + +&i2c1 { + status = "okay"; /* TERASIC + ALTERA + FRIEND */ +}; + +&uart1 { + status = "okay"; /* TERASIC + ALTERA + FRIEND — console */ +}; + +&usb0 { + status = "okay"; /* TERASIC + ALTERA + FRIEND */ + /* dwc2 property, live on mainline (Documentation/.../usb/dwc2.yaml). + * No dr_mode: none of the three reference files sets one, so dwc2 reads + * the OTG capability out of the hardware. Flagged [U] in the rationale. */ + disable-over-current; +}; + +/* + * Watchdogs. MAINLINE leaves all five disabled; TERASIC, ALTERA and FRIEND all + * enable all five. Kept at parity deliberately: dw_wdt sets WDOG_HW_RUNNING + * when it finds the watchdog already started by firmware and the watchdog core + * then pets it until userspace opens the device. A watchdog started by the SPL + * whose node is DISABLED in Linux is never petted and resets the board, so + * enabling costs nothing and not enabling could cost a boot. + * + * Dropped from all five: nothing. Dropped from watchdog4: TERASIC's + * "disable-over-current", which is a copy-paste of the usb0 property onto a + * watchdog — meaningless to snps,dw-wdt, absent from its binding, and carried + * unchanged into ALTERA and FRIEND. + */ +&watchdog0 { + status = "okay"; +}; + +&watchdog1 { + status = "okay"; +}; + +&watchdog2 { + status = "okay"; +}; + +&watchdog3 { + status = "okay"; +}; + +&watchdog4 { + status = "okay"; +}; + +&osc1 { + /* + * MAINLINE declares osc1 as a fixed-clock with clock-frequency = <0>; + * it is the root of the whole peripheral clock tree, so leaving it at 0 + * yields a zero rate everywhere downstream. 25 MHz per TERASIC + ALTERA + * + FRIEND (and the DE25-Nano's 25 MHz HPS oscillator). + */ + clock-frequency = <25000000>; +}; diff --git a/configs/mister_de25nano_defconfig b/configs/mister_de25nano_defconfig new file mode 100644 index 0000000..a0b779a --- /dev/null +++ b/configs/mister_de25nano_defconfig @@ -0,0 +1,338 @@ +# 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/docs/de25-dts-rationale.md b/docs/de25-dts-rationale.md new file mode 100644 index 0000000..dae7abf --- /dev/null +++ b/docs/de25-dts-rationale.md @@ -0,0 +1,617 @@ +# DE25-Nano device tree — rationale (D2.3 desk half) + +The file this document justifies is +[`board/mister/de25nano/socfpga_agilex5_de25nano.dts`](../board/mister/de25nano/socfpga_agilex5_de25nano.dts), +a **mainline** board device tree for the Terasic DE25-Nano on Linux 7.2.x. + +The device tree is the highest-risk file on a new board: a wrong value here does not fail the +build, it fails the *board*, and several of the specific mistakes available on this SoC are +silent. This document is the evidence that each node is right, and the record of what was +deliberately **not** copied from the three reference trees that already carry a complete +DE25-Nano DTS. + +**Nothing below was written from memory.** Every reference value is quoted from the file named +beside it; every mainline claim is a line citation into `linux-7.2.2` as unpacked from this +repo's own `dl/linux/linux-7.2.2.tar.xz`; every validation result is pasted from a command +transcribed in §1. + +Read alongside [`de25-implementation-path.md`](de25-implementation-path.md) §2–§4 and §8, and +[`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) §4. + +--- + +## 0. The four trees + +| Column | What it is | +|---|---| +| **MAINLINE** | `linux-7.2.2` — `arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi` (951 lines) and the in-tree board file `socfpga_agilex5_socdk.dts`. The base we `#include` and the baseline we compare warnings against. | +| **TERASIC** | `github.com/terasic/linux-socfpga` @ `de25-nano-6.12.11-lts` — `socfpga_agilex5_de25_nano.dts` (207 lines) + its 1255-line `.dtsi`. The board vendor's own BSP; the SD4HC form. | +| **ALTERA** | `github.com/altera-fpga/linux-socfpga` @ `socfpga-6.18.20-lts` (default branch, `d8e46bd82a1e`) — `socfpga_agilex5_de25_nano.dts` (206 lines) + its `.dtsi`. Altera's in-house cleanup of Terasic's file; newest, and the best *wiring* reference (§4 of the implementation path). Its `mmc0` is **not** usable — see §3. | +| **FRIEND** | `/mnt/source/de25-linux` (6.18.38 + vendor backports) — `socfpga_agilex5_de25_nano.dts` (262 lines) + its 1016-line `.dtsi`. TERASIC plus MiSTer-specific additions. The only tree with an *observed SD boot on real DE25 silicon*. | + +`OURS` = `MAINLINE` + the board file in this repo. No SoC `.dtsi` is patched: everything is +authored in the board file, by reference (`&label`) or by path (`&{/firmware/svc}`). + +--- + +## 1. Method (reproducible) + +Nothing here needs a cross compiler — DTB generation and schema checking are host-only. + +```sh +# 0. A private, writable copy of the pinned kernel. Do NOT use output-rt/build/linux-7.2.1; +# that is a live DE10 build tree. +mkdir -p /mnt/source/de25-work/t3 +tar -xf dl/linux/linux-7.2.2.tar.xz -C /mnt/source/de25-work/t3/ +cd /mnt/source/de25-work/t3/linux-7.2.2 + +# 1. Drop the board file in and register it. +cp /mnt/source/Buildroot_MiSTer/board/mister/de25nano/socfpga_agilex5_de25nano.dts \ + arch/arm64/boot/dts/intel/ +sed -i 's#\t\t\t\tsocfpga_agilex5_socdk.dtb \\#\t\t\t\tsocfpga_agilex5_de25nano.dtb \\\n&#' \ + arch/arm64/boot/dts/intel/Makefile + +# 2. Kconfig only needs HOSTCC, so allnoconfig works with no aarch64 toolchain present. +make ARCH=arm64 HOSTCC=gcc allnoconfig + +# 3. dtc. Note the target path is relative to arch/arm64/boot/dts. +make ARCH=arm64 HOSTCC=gcc intel/socfpga_agilex5_de25nano.dtb # default DTC_FLAGS +make ARCH=arm64 HOSTCC=gcc W=1 intel/socfpga_agilex5_de25nano.dtb +make ARCH=arm64 HOSTCC=gcc W=2 intel/socfpga_agilex5_de25nano.dtb +# ... and the same three against the in-tree baseline: +make ARCH=arm64 HOSTCC=gcc W=2 intel/socfpga_agilex5_socdk.dtb + +# 4. dtbs_check. Run it BOTH ways. The carried patches change the *bindings*, so the +# result is meaningless unless you say which tree you measured. +source /mnt/source/de25-work/venv/bin/activate # dtschema 2026.6 +rm -f arch/arm64/boot/dts/intel/socfpga_agilex5_de25nano.dtb \ + Documentation/devicetree/bindings/processed-schema.json +make ARCH=arm64 HOSTCC=gcc CHECK_DTBS=y intel/socfpga_agilex5_de25nano.dtb # stock 7.2.2 + +for p in 0101-mmc-sdhci-cadence-agilex5-40-bit-dma-mask \ + 0102-firmware-stratix10-svc-match-agilex5-svc; do + patch -p1 < /mnt/source/Buildroot_MiSTer/board/mister/de25nano/linux-patches/$p.patch +done +rm -f arch/arm64/boot/dts/intel/socfpga_agilex5_de25nano.dtb \ + Documentation/devicetree/bindings/processed-schema.json +make ARCH=arm64 HOSTCC=gcc CHECK_DTBS=y intel/socfpga_agilex5_de25nano.dtb # what we ship + +# 5. Structural check: decompile what was BUILT and verify the load-bearing node names and +# that the fpga-mgr phandle resolves. +dtc -I dtb -O dts -o de25nano.decompiled.dts \ + arch/arm64/boot/dts/intel/socfpga_agilex5_de25nano.dtb +``` + +`make ARCH=arm64 defconfig` is **not** usable here (no aarch64 compiler on the host), and is not +needed: `allnoconfig` satisfies kbuild, and the `%.dtb` rule does not depend on `CC`. + +--- + +## 2. Results + +### 2.1 `dtc` + +| | default `DTC_FLAGS` (what `make dtbs` uses) | `W=1` | `W=2` | +|---|---|---|---| +| MAINLINE baseline (`socfpga_agilex5_socdk.dtb`, unpatched) | **0** | **0** | 6 | +| **OURS** (`socfpga_agilex5_de25nano.dtb`) | **0** | **0** | 6 | +| **new warnings introduced** | **0** | **0** | **0** | + +The six `W=2` warnings are all `property_name_chars_strict` on `snps,wr_osr_lmt` / +`snps,rd_osr_lmt` in the three `stmmac-axi-config` nodes of the **shared** +`socfpga_agilex5.dtsi` (`:583,:584,:696,:697,:809,:810`). They are present in the unpatched +baseline, they are upstream's, none of them is ours. Verbatim: + +``` +arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi:583.5-28: Warning (property_name_chars_strict): + /soc@0/ethernet@10810000/stmmac-axi-config:snps,wr_osr_lmt: Character '_' not recommended in property name +[... five more, identical shape, at :584 :696 :697 :809 :810] +``` + +The `unit_address_vs_reg` warning that a naive transcription of the reference files *would* +have introduced (`ethernet-phy@0 { reg = <1>; }` — present in TERASIC, ALTERA **and** FRIEND) +was fixed before it was committed; see §5 D3. + +### 2.2 `dtbs_check` + +**Against stock 7.2.2 (no carried patches): 9 warnings.** +**Against 7.2.2 + `0101` + `0102` — the tree we actually ship: 5 warnings.** + +| # | Warning (abridged; verbatim text in §2.3) | Node | Class | Disposition | +|---|---|---|---|---| +| 1 | `fpga-mgr:compatible:0: 'intel,agilex5-soc-fpga-mgr' is not one of [...]` (via `intel,stratix10-svc.yaml`) | `/firmware/svc/fpga-mgr` | **(a)** | Expected per implementation-path §2.5 | +| 2 | `fpga-mgr:compatible: [...] is too long` (via `intel,stratix10-svc.yaml`) | `/firmware/svc/fpga-mgr` | **(a)** | Same | +| 3 | `compatible:0: 'intel,agilex5-soc-fpga-mgr' is not one of [...]` (via `intel,stratix10-soc-fpga-mgr.yaml`) | `/firmware/svc/fpga-mgr` | **(a)** | Same | +| 4 | `compatible: [...] is too long` (via `intel,stratix10-soc-fpga-mgr.yaml`) | `/firmware/svc/fpga-mgr` | **(a)** | Same | +| 5 | `failed to match any schema with compatible: ['intel,agilex5-soc-fpga-mgr', 'intel,agilex-soc-fpga-mgr']` | `/firmware/svc/fpga-mgr` | **(a)** | Summary line for 1–4 | +| 6 | `mmc@10808000: compatible:0: 'intel,agilex5-sd4hc' is not one of [...]` | `/soc@0/mmc@10808000` | **(a)** | **Gone with `0101`** | +| 7 | `failed to match any schema with compatible: ['intel,agilex5-sd4hc', 'cdns,sd4hc']` | `/soc@0/mmc@10808000` | **(a)** | Summary line for 6; **gone with `0101`** | +| 8 | `mmc@10808000: clocks: [[7, 40], [7, 78]] is too long` | `/soc@0/mmc@10808000` | **(a)** | **Gone with `0101`** — its binding hunk now widens `clocks` | +| 9 | `mmc@10808000: Unevaluated properties are not allowed ('clock-names', 'dma-coherent', 'iommus' were unexpected)` | `/soc@0/mmc@10808000` | **(a)** | **Gone with `0101`** — it now declares all three | + +Counts, stock 7.2.2: **(a) 9 · (b) 0 · (c) 0**. +Counts, 7.2.2 + `0101` + `0102` (shipped): **(a) 5 · (b) 0 · (c) 0**. + +Class key, per the task's definitions: + +- **(a)** *expected*: the DT form is the forward-correct one; the *binding* has not caught up. + All five residual warnings are the single known issue from implementation-path §2.5 — the + two-string `fpga-mgr` compatible against a binding that is still a flat `enum`, reported once + by each of the two schemas that validate that node and once as a summary. +- **(b)** *inherited from the mainline dtsi*: **none**. Verified by running the same command on + the stock in-tree board — `make ARCH=arm64 CHECK_DTBS=y intel/socfpga_agilex5_socdk.dtb` + emits **zero** warnings on 7.2.2. Every warning above is attributable to a node we authored. +- **(c)** *ours to fix*: **none remain**. Four were found and fixed during authoring, before the + file was written out — the `ethernet-phy@0`/`reg = <1>` mismatch (§5 D3), the `hps0` LED child + node name, which `leds-gpio.yaml`'s child pattern `(^led-[0-9a-f]$|led)` rejects outright + under its `additionalProperties: false` (§5 D2), the `mmc0@` node name (§5 D1) and the + `reset-names`/`fifo-depth` properties `cdns,sdhci.yaml` does not declare (§3). + +An earlier revision of this document classified the four `mmc0` warnings as a separate class +"(c′)" — *ours, deliberate, fixable only in a binding we already carry a patch to*. That class +is now **empty**: `linux-patches/0101`'s binding hunk carries the `clocks`/`clock-names`/ +`iommus`/`dma-coherent` additions described in §2.4, so all four are gone from the shipped +tree. The classification is kept in the record because it is the shape this kind of finding +takes, and because §2.4's argument is what justified the DT keeping those properties. + +### 2.3 Verbatim output (7.2.2 + `0101` + `0102`) + +``` +arch/arm64/boot/dts/intel/socfpga_agilex5_de25nano.dtb: svc (intel,agilex5-svc): fpga-mgr:compatible:0: 'intel,agilex5-soc-fpga-mgr' is not one of ['intel,stratix10-soc-fpga-mgr', 'intel,agilex-soc-fpga-mgr'] + from schema $id: http://devicetree.org/schemas/firmware/intel,stratix10-svc.yaml +arch/arm64/boot/dts/intel/socfpga_agilex5_de25nano.dtb: svc (intel,agilex5-svc): fpga-mgr:compatible: ['intel,agilex5-soc-fpga-mgr', 'intel,agilex-soc-fpga-mgr'] is too long + from schema $id: http://devicetree.org/schemas/firmware/intel,stratix10-svc.yaml +arch/arm64/boot/dts/intel/socfpga_agilex5_de25nano.dtb: fpga-mgr (intel,agilex5-soc-fpga-mgr): compatible:0: 'intel,agilex5-soc-fpga-mgr' is not one of ['intel,stratix10-soc-fpga-mgr', 'intel,agilex-soc-fpga-mgr'] + from schema $id: http://devicetree.org/schemas/fpga/intel,stratix10-soc-fpga-mgr.yaml +arch/arm64/boot/dts/intel/socfpga_agilex5_de25nano.dtb: fpga-mgr (intel,agilex5-soc-fpga-mgr): compatible: ['intel,agilex5-soc-fpga-mgr', 'intel,agilex-soc-fpga-mgr'] is too long + from schema $id: http://devicetree.org/schemas/fpga/intel,stratix10-soc-fpga-mgr.yaml +arch/arm64/boot/dts/intel/socfpga_agilex5_de25nano.dtb: /firmware/svc/fpga-mgr: failed to match any schema with compatible: ['intel,agilex5-soc-fpga-mgr', 'intel,agilex-soc-fpga-mgr'] +``` + +Stock 7.2.2 additionally emits, and `0101` removes all four: + +``` +... mmc@10808000 (intel,agilex5-sd4hc): compatible:0: 'intel,agilex5-sd4hc' is not one of ['amd,pensando-elba-sd4hc', ...] +... /soc@0/mmc@10808000: failed to match any schema with compatible: ['intel,agilex5-sd4hc', 'cdns,sd4hc'] +... mmc@10808000 (intel,agilex5-sd4hc): clocks: [[7, 40], [7, 78]] is too long +... mmc@10808000 (intel,agilex5-sd4hc): Unevaluated properties are not allowed ('clock-names', 'dma-coherent', 'iommus' were unexpected) +``` + +### 2.4 Both remaining and removed warnings are *binding* gaps, and both fixes are verified + +This was tested, not asserted. + +**The four `mmc0` warnings — fixed, in tree.** `cdns,sdhci.yaml` declared `clocks: maxItems: 1` +and no `clock-names`/`iommus`/`dma-coherent`, under `unevaluatedProperties: false`. The +additions now carried by `linux-patches/0101` (whose binding hunk already edits that file to add +`intel,agilex5-sd4hc` to the vendor enum) are: + +```yaml + clocks: + minItems: 1 + maxItems: 2 + + clock-names: + minItems: 1 + items: + - const: biu + - const: ciu + + iommus: + maxItems: 1 + + dma-coherent: true +``` + +Precedent for each: `clocks`/`clock-names` because the Agilex 5 integration genuinely wires two +clocks (§3, mmc0 row); `iommus: maxItems: 1` because `dwc2.yaml:93` declares exactly that for +the same reason on the same SoC; `dma-coherent: true` because nine existing `mmc/*.yaml` +bindings already do (`arasan,sdhci.yaml:127`, `fsl,esdhc.yaml:78`, `sdhci-am654.yaml:53`, …). +**Verified**: with `0101` applied, `mmc@10808000` produces zero warnings. + +**The five `fpga-mgr` warnings — not fixable here.** Restructuring +`intel,stratix10-soc-fpga-mgr.yaml`'s `compatible` from a flat two-value `enum` into the +`oneOf`/`items` shape that Khairul's v6 binding patch proposes (implementation path §2.5) clears +**all five** — confirmed by applying that shape to the scratch tree and re-running, then +reverting. That patch has not landed at 7.2 or at `master`, which is exactly why §2.5 +recommends accepting the warning rather than dropping the SoC-specific string. We do not carry a +local binding patch for it because, unlike the `mmc0` case, there is a live upstream series we +would be duplicating and then have to un-carry. + + +### 2.5 Structural check on the built blob + +`dtc -I dtb -O dts` of what was actually built: + +``` + firmware { + svc { + compatible = "intel,agilex5-svc"; + method = "smc"; + memory-region = <0x03>; + iommus = <0x04 0x0a>; + fpga-mgr { + compatible = "intel,agilex5-soc-fpga-mgr", "intel,agilex-soc-fpga-mgr"; + phandle = <0x17>; + }; + }; + }; +... + fpga-region { + compatible = "fpga-region"; + #address-cells = <0x02>; + #size-cells = <0x02>; + fpga-mgr = <0x17>; + }; +``` + +Phandles resolved out of the blob: + +| phandle | resolves to | +|---|---| +| `0x17` | `/firmware/svc/fpga-mgr` ✔ — `fpga-region`'s `fpga-mgr` points at the manager | +| `0x04` | `/soc@0/iommu@16000000` (smmu) | +| `0x03` | `/reserved-memory/svcbuffer@0` (`service_reserved`) | +| `0x06` | `/soc@0/rstmgr@10d11000` | +| `0x07` | `/soc@0/clock-controller@10d10000` (clkmgr) | +| `0x16` | `/soc@0/gpio@10c03300/gpio-controller@0` (`portb`) | + +Confirmed: the node is literally named **`svc`**, under a node literally named **`firmware`**, +with `method = "smc"` — the three constraints `s10_init()` +(`drivers/fpga/stratix10-soc.c:471`), `stratix10_svc_init()` +(`drivers/firmware/stratix10-svc.c:2086`) and `get_invoke_func()` impose, none of which is +expressed as a compatible and all of which are easy to lose when authoring by hand. + +**Coverage audit — every enabled DMA master still carries an `iommus` phandle**, even though +`&smmu` is disabled and they are all inert as shipped. This is what makes the SMMU-on leg of +implementation-path §2.6 step 4 a genuine one-line change (§4.3): + +| enabled master | `iommus` (as built) | status | +|---|---|---| +| `/soc@0/ethernet@10810000` | `<&smmu 1>` | okay | +| `/soc@0/mmc@10808000` | `<&smmu 5>` | okay | +| `/soc@0/usb@10b00000` | `<&smmu 6>` | okay | +| `/soc@0/dma-bus@10db0000/dma-controller@0` | `<&smmu 8>` | (no status = enabled) | +| `/soc@0/dma-bus@10db0000/dma-controller@10000` | `<&smmu 9>` | (no status = enabled) | +| `/firmware/svc` | `<&smmu 10>` | (no status = enabled) | +| `/soc@0/iommu@16000000` (the SMMU itself) | — | **disabled** | + +All six `iommus` phandles resolve to `0x04` = `/soc@0/iommu@16000000`, confirmed with `fdtget` +against the built blob. + +Node statuses, read back out of the blob with `fdtget`: `serial@10c02100` okay, +`serial@10c02000` disabled, `ethernet@10810000` okay, `usb@10b00000` okay, `i2c@10c02900` okay, +`gpio@10c03300` okay, `gpio@10c03200` disabled, **`iommu@16000000` disabled**, `mmc@10808000` +okay (with `max-frequency = <0x17d7840>` = 25 000 000), `spi@108d2000` (qspi) disabled, +`watchdog@10d00200`…`watchdog@10d00600` okay, `nand-controller@10b80000` disabled. + +--- + +## 3. Node-by-node + +`—` = absent from that tree. "MAINLINE" means `socfpga_agilex5.dtsi` at 7.2.2 unless a board +file is named. + +| Node / property | Source | Verdict | Why | +|---|---|---|---| +| `#include "socfpga_agilex5.dtsi"` | all three | **kept** | The board file patches no SoC `.dtsi`. Everything is a reference or a path override. | +| `model` | — | **changed** | `"Terasic DE25-Nano"`. All three references say `"SoCFPGA Agilex5 Terasic DE25-Nano"`; ours is the board name, which is what `/proc/device-tree/model` should read. | +| root `compatible` | all three | **kept** (`intel,socfpga-agilex5-socdk`, `intel,socfpga-agilex5`) | Factually wrong — this is not an SoCDK — but `Documentation/devicetree/bindings/arm/altera.yaml:109-117` is a **closed five-value enum** for Agilex 5 boards, so `terasic,de25-nano` would fail dtbs_check for zero gain: arm64 has no `DT_MACHINE_START` table, so nothing matches on it. Board identity lives in `model`. The honest fix is a one-line upstream `altera.yaml` patch; see §7 U6. | +| `aliases/serial0 = &uart1` | TERASIC + ALTERA + FRIEND | **kept** | See "Console UART" below. | +| `aliases/ethernet0 = &gmac0` | TERASIC + ALTERA + FRIEND | **kept** | **Load-bearing.** U-Boot's `fdt_fixup_ethernet()` walks `/aliases` for `ethernetN` and writes `$ethaddr` into the node it names. Without it the MAC is never injected — the identical DE10 lesson, [`dts-comparison.md`](dts-comparison.md) §3.3. | +| `aliases/i2c1 = &i2c1` | ALTERA only | **kept** | Pins the one enabled adapter to `/dev/i2c-1` instead of dynamic numbering. The DE10's A14 ([`dts-comparison.md`](dts-comparison.md) §2) is the standing lesson that i²C adapter numbering is worth making deterministic. Either way it lands inside `0..2`. | +| `chosen/stdout-path` | all three | **kept** | `"serial0:115200n8"`. | +| `chosen/bootargs` | TERASIC + FRIEND | **dropped** | Self-contradictory *and* dead. Their string sets `console=`/`earlycon=` to `0x10c02000` (= **uart0**) while their own `stdout-path` resolves to uart1, and U-Boot's `fdt_chosen()` overwrites `/chosen/bootargs` from `$bootargs` regardless. ALTERA's 2025 in-house file already dropped it. | +| `leds { compatible = "gpio-leds" }` | all three | **kept** | | +| LED child node name | ALTERA (`led-0`) | **changed** from TERASIC/FRIEND (`hps0`) | `leds-gpio.yaml`'s child pattern is `(^led-[0-9a-f]$|led)`; `hps0` matches neither, so the node is rejected by that binding's `additionalProperties: false`. Provably a no-op: the class-device name comes from `label`, so `/sys/class/leds/hps_led0` is unchanged — same ABI as the DE10. | +| `label = "hps_led0"`, `gpios = <&portb 17 GPIO_ACTIVE_LOW>` | all three | **kept** | Identical in all three. | +| `linux,default-trigger = "mmc0"` | — (DE10 stock has it) | **not added** | Out of wave-1 scope (bare developer OS, no MiSTer binaries). One line to add later; the trigger resolves because the mmc core registers a simple trigger named after `dev_name(&host->class_dev)`. | +| `memory@80000000` | — in MAINLINE dtsi | **authored** | See "Memory" below. | +| `base_fpga_region: fpga-region` | TERASIC + ALTERA `.dtsi`; absent from MAINLINE at every base | **authored** | Implementation path §3.1. `compatible = "fpga-region"`, `#address-cells`/`#size-cells` = 2, `fpga-mgr = <&fpga_mgr>`. `fpga-region.yaml` requires exactly `compatible` + `fpga-mgr`; both present. | +| `fpga-bridges`, any bridge node | — | **deliberately absent** | The Cyclone V `fpga_bridge0..3` shape has no Agilex analogue and must not be transliterated ([`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) §4.2). | +| `config-complete-timeout-us` | — | **deliberately absent** | It belongs on the per-core **overlay**, not the base region ([`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) §4.3). `of-fpga-region.c` reads it from the overlay's target node. | +| `/firmware/svc` `compatible` | MAINLINE | **kept unchanged** (`intel,agilex5-svc`) | See "The svc node" below. Earlier drafts of implementation-path §3.1 called for overriding this to `intel,agilex-svc`; that is now the *fallback*, not the plan. | +| `/firmware/svc` `method`, `memory-region`, `iommus` | MAINLINE | **kept, inherited** | Not restated in the board file. `method = "smc"` is required or `get_invoke_func()` fails probe `-ENXIO`; `iommus` is *required by the binding* for the agilex5 string (`intel,stratix10-svc.yaml`'s `allOf`), and is inert as shipped (§4.3). **`memory-region` is dead on mainline** — see [U9]: the driver uses whatever BL31 answers to `FPGA_CONFIG_GET_MEM` (`stratix10-svc.c:865-876`, `:952-960`) and never consults the `service_reserved` `no-map` region. | +| `fpga_mgr` child | TERASIC/ALTERA (single string) | **changed** | Two-string fallback `"intel,agilex5-soc-fpga-mgr", "intel,agilex-soc-fpga-mgr"`. Binds the **stock** driver: `s10_of_match[]` (`drivers/fpga/stratix10-soc.c:448-452`) carries no `.data` and never branches on which entry matched, and OF matching walks the whole list. Costs a transient dtbs_check warning (§2.5 of the implementation path); avoids a carried match-table line forever. | +| `altr,smmu_enable_quirk` on svc / fpga_mgr | TERASIC | **dropped** | Vendor-live, mainline-inert: `grep -rn smmu_enable_quirk` over mainline 7.2.2 → zero hits. It gates SDM DMA setup in *Terasic's* `stratix10-svc.c`; carrying it onto a mainline driver does nothing. Its existence is evidence for implementation-path §2.6, not a property to copy. | +| `interrupts`/`interrupt-parent` on svc | TERASIC | **dropped** | Not in `intel,stratix10-svc.yaml`, not read by mainline's `stratix10-svc.c`. | +| `hwmon` / `temp_volt` child of svc | TERASIC + FRIEND | **dropped** | `compatible = "intel,soc64-hwmon"` exists nowhere in mainline (`drivers/hwmon/`, `Documentation/devicetree/bindings/hwmon/` → zero hits at 7.2.2). The whole `&temp_volt { voltage { … } temperature { … } }` block — 60 lines in both references — binds nothing. Revisit if an SDM hwmon driver lands. | +| `fcs-hal` / `fcs-crypto` children | TERASIC | **dropped** | `intel,agilex5-soc-fcs-hal` likewise absent from mainline. | +| `&smmu` status | all three set `okay` | **changed → `disabled`** | The one design divergence from every reference. Mainline's svc layer hands the SDM **raw physical addresses** and never calls `iommu_map`/`dma_map`, while the inherited `iommus = <&smmu 10>` puts the svc device on a *translated* default domain — so SMMU-on cannot program the fabric on a mainline kernel. Full trace in §4.1. Also MAINLINE 7.2's own default. Every `iommus` property in the tree is kept and goes inert (§4.3), so the SMMU-on leg of the §2.6 test is a one-line change. | +| **`mmc0`** — `compatible` | TERASIC + FRIEND | **kept exactly** | `"intel,agilex5-sd4hc", "cdns,sd4hc"`, vendor string **first**. That order is the `items: [enum, const]` form `0101` adds to `cdns,sdhci.yaml`, and with `0101` the first entry wins and installs the 40-bit DMA mask. ALTERA's `"altr,agilex5-sd6hc","cdns,sd6hc"` is unusable: `cdns,sd6hc` exists nowhere in mainline (implementation path §4.1). A lone `cdns,sd4hc` would still bind — `sdhci_cdns_probe()` falls back to `&sdhci_cdns_drv_data` when `of_device_get_match_data()` returns NULL (`sdhci-cadence.c:561-563`) — but **silently without the mask**, which is the §8 Q2 failure. | +| `mmc0` `reg`, `interrupts` | TERASIC + ALTERA + FRIEND (identical) | **kept** | `0x10808000 0x1000`, `GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH`. | +| `mmc0` node name | — | **changed** | `mmc@10808000`, not the references' `mmc0@10808000`. Generic node name per `mmc-controller.yaml`; the vendor name is not a legal generic-node name and buys nothing. | +| `mmc0` `resets` | TERASIC + FRIEND | **kept** | `<&rst SDMMC_RESET>`. Inert on this board — `sdhci-cadence` takes the reset only under `MMC_CAP_HW_RESET` (eMMC) — but correct hardware description. ALTERA's three-entry list (`COMBOPHY_RESET`, `SDMMC_OCP_RESET`) targets their SD6HC rewrite. | +| `mmc0` `reset-names = "reset"` | TERASIC + FRIEND | **dropped** | Not declared in `cdns,sdhci.yaml` (would trip `unevaluatedProperties`), and the driver looks the reset up with `id = NULL`, i.e. by index. | +| `mmc0` `fifo-depth = <0x800>` | TERASIC + ALTERA + FRIEND | **dropped** | Dead. `grep fifo-depth drivers/mmc/host/sdhci-cadence.c` → zero hits; it is a `dw_mmc` property that travelled here by copy. Same class as the DE10's `speed-mode`/`timeouts` ([`dts-comparison.md`](dts-comparison.md) §4 D3/D4). | +| `mmc0` `#address-cells`/`#size-cells` | all three | **dropped** | The node has no children. Pure `avoid_unnecessary_addr_size` noise. | +| `mmc0` `clocks` + `clock-names` | all three (identical) | **kept, both entries** | `<&clkmgr AGILEX5_L4_MP_CLK>, <&clkmgr AGILEX5_SDMCLK>` / `"biu", "ciu"`. Mainline only ever uses index 0 — `devm_clk_get_enabled(dev, NULL)` (`sdhci-cadence.c:557`) — and on Agilex 5 the second is inert anyway: gate clocks register with `agilex_gateclk_ops` (`clk-gate-s10.c:279`, defined `:117`), which has **no `.enable`/`.disable`**, so `clk_prepare_enable()` is a no-op *and* `clk_disable_unused()` cannot turn `sdmclk` off. Kept because it is the truthful hardware description, it matches all three references including the one that boots, and the binding — not the DT — is what needs widening (§2.4). | +| `mmc0` `iommus` | all three | **kept, inert as shipped** | `&smmu` is disabled, so `of_iommu_xlate()` returns `-ENODEV` and mmc0 DMAs physically. Kept because it is correct hardware description and mandatory the moment `&smmu` is flipped to `okay`. See §4.3. | +| `mmc0` `dma-coherent` | all three | **kept, with [U2] re-opened** | Corroborated by MAINLINE's own `nand` node (`socfpga_agilex5.dtsi:315`). But the vendors assert it under SMMU-**on**, where cacheability comes from the STE/`IOMMU_CACHE` attributes rather than from this property, so their evidence does not transfer to the shipped SMMU-off shape. Must be re-verified by data-integrity test, not inherited. See §4.5. | +| `mmc0` `bus-width = <4>`, `disable-wp` | all three | **kept** | 4-bit microSD, no write-protect switch wired. | +| `mmc0` `cap-sd-highspeed` | TERASIC + ALTERA (FRIEND drops it) | **kept** | Dropping it would be theatre, not caution: `sdhci.c:4572` sets `MMC_CAP_SD_HIGHSPEED` from the capability register's `SDHCI_CAN_DO_HISPD` (bit 21) **regardless of DT**, and our `sdhci-caps-mask` does not clear that bit. `max-frequency` above is the property that actually constrains the bus. If a bench test ever needs genuine default-speed-only, the real lever is widening the caps mask to `<0x00002000 0x0020ff00>`. | +| `mmc0` `no-1-8-v` | TERASIC + FRIEND | **kept** | 3.3V-only signalling. Also the reason we can safely omit ALTERA's `vqmmc-supply` level-shifter regulator (below). | +| `mmc0` `no-sdio` | TERASIC + FRIEND | **kept** | SD card slot only; MiSTer WiFi is USB. | +| `mmc0` `sd-uhs-sdr50` | TERASIC | **dropped** | Contradicts `no-1-8-v` in the same node: every UHS mode needs 1.8V signalling, which `MMC_CAP2_NO_1_8_V` bars. Inert, and confusing to leave in. | +| `mmc0` `sdhci-caps` / `sdhci-caps-mask` | TERASIC (ALTERA has a wider mask) | **kept, TERASIC's values** | **Live on mainline and probably load-bearing.** `__sdhci_read_caps()` (`drivers/mmc/host/sdhci.c:4161-4186`) applies them to `SDHCI_CAPABILITIES{,_1}`; the uint64 is ``. `0xc800` in caps bits 15:8 sets the base clock to `0xc8` = 200 MHz, and `sdhci_cdns_ops` has **no `.get_max_clock`**, so a zero base-clock field would fail probe outright with `"Hardware doesn't specify base clock frequency"` / `-ENODEV` (`sdhci.c:4448-4462`). Both vendors set it; treated as load-bearing rather than decorative. The caps1 mask clears bit 13 (`SDHCI_USE_SDR50_TUNING`). ALTERA masks `0x2007`, additionally removing SDR50/SDR104/DDR50 — moot under `no-1-8-v`. | +| `mmc0` 40 × `cdns,phy-*` / `cdns,hrs*` | TERASIC + FRIEND | **dropped** | **Dead devicetree on a mainline driver.** `sdhci-cadence`'s property table knows only eleven `cdns,phy-input-delay-*` / `cdns,phy-dll-delay-*` names (`sdhci-cadence.c:108-119`); not one of the forty is among them. Implementation path §2.2 makes the same call, and notes the consequence: the friend's working SD path already runs on the driver's **default** PHY configuration, which is what we inherit. | +| `mmc0` `vmmc-supply` / `vqmmc-supply` + `sd_emmc_power` / `sd_io_1v8_reg` regulators | ALTERA only | **dropped** | Would mean authoring a `regulator-fixed` and a `regulator-gpio` (on `portb 3`) that no other reference has and no bench test has exercised. Under `no-1-8-v` the level shifter never has to switch, and U-Boot's own SD boot from the same card demonstrates the hardware default is the 3.3V state. Adding an untested GPIO-driven regulator to the SD path is precisely the change that turns a working boot into a non-booting one. Flagged §7 U4. | +| `mmc0` `max-frequency` | FRIEND `25000000` (ALTERA `200000000`; TERASIC none) | **taken from FRIEND** | **First-boot risk control.** Mainline's `sdhci-cadence` programs **none** of the 40 `cdns,phy-*` values the vendor trees carry, so Linux inherits whatever PHY state U-Boot left rather than configuring it. The only boot of this board on a **mainline** sdhci-cadence driver — FRIEND, `socfpga_agilex5_de25_nano.dts:110-126` — reached that state only after dropping high-speed advertisement and capping the clock at 25 MHz, following corrupted SD SCR reads. Start where the one working data point is; lift once a sustained `dd` is clean ([U3]). ALTERA's `200000000` is a no-op ceiling. | +| `&gmac0` `status`, `phy-mode`, `phy-handle`, `max-frame-size` | all three (identical) | **kept** | `phy-mode = "rgmii"`, **not** `"rgmii-id"`: ALTERA's file states the TX/RX delays are on the PCB, so asking the PHY for internal delay too would double it — the same trap as the DE10's `gmac1` ([`dts-comparison.md`](dts-comparison.md) §3.2). | +| `&gmac0` `mdio0` node name | all three **and MAINLINE's own socdk** (`socfpga_agilex5_socdk.dts:51`) | **kept** | Does not match `mdio.yaml`'s `$nodename` pattern, but it is what the in-tree board uses, so the shape is upstream's, not ours — and dtbs_check does not in fact flag it. Cosmetic at runtime: `stmmac_of_get_mdio()` (`stmmac_platform.c:295-318`) finds the node by scanning children for `compatible = "snps,dwmac-mdio"`, never by name. | +| `ethernet-phy@0 { reg = <1>; }` | all three | **changed → `ethernet-phy@1`** | A unit-address/`reg` mismatch that dtc reports under `-Wunit_address_vs_reg`. PHY address **1** is the real value (all three agree on `reg`); only the unit address was wrong. Provably a no-op — `of_mdiobus_register()` addresses the PHY from `reg`. See §5 D3. | +| `&gmac1`, `&gmac2` | — | **left disabled** | One RJ45 on this board; MAINLINE's default is disabled. | +| `&gpio1 { status = "okay" }` | all three | **kept** | Provides `portb`, which `led-0` uses. `gpio0` stays disabled (all three). | +| `&i2c1 { status = "okay" }` | all three | **kept** | The only i²C bus enabled ⇒ exactly one adapter, pinned to 1 by the alias. | +| `&uart1 { status = "okay" }` | all three | **kept** | Console. `uart0` left disabled (MAINLINE default) — see "Console UART". | +| `&usb0 { status = "okay"; disable-over-current }` | all three | **kept** | `disable-over-current` is live (`Documentation/devicetree/bindings/usb/dwc2.yaml:89`). No `dr_mode`: none of the three sets one, so dwc2 reads the OTG capability out of the hardware. Flagged §7 U5. | +| `&watchdog0..4 { status = "okay" }` | all three | **kept, all five** | Deliberate parity with a safety argument: `dw_wdt` sets `WDOG_HW_RUNNING` when it finds the watchdog already started by firmware, and the watchdog core then pets it until userspace opens the device. A watchdog started by the SPL whose node is **disabled** in Linux is never petted and resets the board. Enabling costs a `/dev/watchdogN`; not enabling could cost a boot. | +| `disable-over-current` on `&watchdog4` | TERASIC → ALTERA → FRIEND | **dropped** | A copy-paste of the `usb0` property onto a watchdog. Meaningless to `snps,dw-wdt`, absent from its binding, propagated unchanged through all three trees. | +| `&osc1 { clock-frequency = <25000000> }` | all three | **kept** | MAINLINE declares `osc1` as a `fixed-clock` with `clock-frequency = <0>` (`socfpga_agilex5.dtsi:139-143`); it is the root of the peripheral clock tree, so leaving it at 0 gives a zero rate everywhere downstream. Exactly the `osc1` question [`dts-comparison.md`](dts-comparison.md) §4.1 asked for the DE10 — and here, unlike there, the answer is that it *does* need setting. | +| `&qspi` + `flash@0` + partitions | ALTERA only | **dropped** | Not in the §3.1 node set; TERASIC and FRIEND both leave QSPI disabled. Enabling it exposes the **factory boot image** to `/dev/mtd` writes, and the QSPI contents are the board's un-recoverable-without-Quartus state ([`de25-boot-chain.md`](de25-boot-chain.md) §7 rows 14/15). Nothing in wave 1 needs it. | +| `mister_fb`, `ascal_scratch`, `x86ram`, `mister_fb_mem` reserved regions | FRIEND only | **dropped** | ADR 0027 scopes wave 1 to a bare developer OS with **no MiSTer binaries**. These describe a fabric/Main_MiSTer memory map that does not exist yet, and `de25-reference-implementation.md` already flags the friend's 2 GiB assumption underneath `x86ram@b0000000` as unverified. | +| `&i2c1`'s vendor `status` whitespace, `&mmc` label | — | n/a | We define our own label `mmc0`; the references' `&mmc` label lives in a `.dtsi` we do not patch. | + +### The svc node — a dependency, stated plainly + +We keep MAINLINE's `compatible = "intel,agilex5-svc"` exactly as the `.dtsi` ships it, with its +inherited `iommus = <&smmu 10>` (which the binding's `allOf` **requires** for that string), and +add only the `fpga_mgr` child. + +That string is in `intel,stratix10-svc.yaml`'s enum but has **never** been in +`stratix10_svc_drv_match[]` — not at 6.18.44, not at 7.2.2, not at `master`. On a stock kernel +the node is therefore **inert**: `stratix10_svc_init()`'s `of_platform_populate()` skips it, no +service device is created, and the `fpga_mgr` child has nothing to attach to. Because it is a +lone compatible with no fallback string, the OF core cannot rescue it either. + +**This DTS therefore depends on `linux-patches/0102`**, which adds the one match-table line (or +on the equivalent landing upstream — it is a one-line upstream fix and worth sending). + +**Fallback if `0102` is ever dropped:** re-add the override that earlier drafts of +implementation-path §3.1 called for — + +```dts +&{/firmware/svc} { + compatible = "intel,agilex-svc"; /* in the match table AND in the enum */ + ... +}; +``` + +— which binds the stock driver and is schema-clean. Mainline branches on neither string +(implementation path §8 Q3), so the two are equivalent *against mainline*; they stop being +equivalent the moment anyone ports the vendor's agilex5-specific svc behaviour, which is why +keeping the SoC-accurate string plus a one-line patch is the better shape. + +### Console UART — a documented deviation + +The task brief said "uart0 status okay (console)". **The file enables `uart1` instead**, and +leaves `uart0` at MAINLINE's disabled default. The evidence: + +- TERASIC, ALTERA and FRIEND *all three* set `aliases { serial0 = &uart1; }` and + `&uart1 { status = "okay"; }`, and none of them enables uart0. +- MAINLINE's `socfpga_agilex5_socdk.dts` uses uart0 — but that is a **different board**; HPS + UART pinmux is board wiring, not SoC wiring. +- The one apparent counter-evidence is TERASIC's/FRIEND's `bootargs` naming `0x10c02000` + (uart0). It is self-contradictory with their own `stdout-path` in the same node, and ALTERA's + 2025 in-house rewrite **deleted the bootargs string entirely** while keeping + `serial0 = &uart1` — the strongest single signal that the bootargs were stale SoCDK residue. +- UART addresses are identical across MAINLINE, TERASIC and FRIEND (`uart0` = `serial@10c02000`, + `uart1` = `serial@10c02100`), so this is a pure wiring question, not an addressing one. + +**Settled `[V]`, not merely likely.** The DE25 **U-Boot** tree closes it independently of any +Linux DTS: `de25-uboot-socfpga:arch/arm/dts/socfpga_agilex5_de25_nano.dts:11` has +`serial0 = &uart1`, and `...-u-boot.dtsi` sets `stdout-path = "serial0:115200n8"` — and the +friend booted Linux over that console. A board whose bootloader console is uart1 does not have +its Linux console on uart0. Promoted from `[U]` in §7. + +### Memory + +MAINLINE's `.dtsi` has no memory node, so the board file must supply one. + +| Tree | value | +|---|---| +| TERASIC, FRIEND | `memory { reg = <0 0x80000000 0 0x80000000>; }` — 2 GiB, and a node name with no unit address | +| ALTERA, MAINLINE socdk | `memory@80000000 { reg = <0x0 0x80000000 0x0 0x0>; }` — size 0, "we expect the bootloader to fill in the reg" | +| **OURS** | `memory@80000000 { reg = <0x0 0x80000000 0x0 0x40000000>; }` — **1 GiB** | + +1 GiB at `0x8000_0000` matches the *UM* and the DE25 **U-Boot** DTS — +`de25-uboot-socfpga:arch/arm/dts/socfpga_agilex5_de25_nano-u-boot.dtsi`, which carries +`memory { /* 1GB */ reg = <0 0x80000000 0 0x40000000>; }`. + +**Be precise about what that is.** It is a *declared constant in a bootloader device tree*, not +a measurement, and [`de25-boot-chain.md`](de25-boot-chain.md) §3's `[V SPL-dtb]` tag overstates +it — a correction owed, recorded in §8. The real size is discovered at runtime by the IO96B +controller: `drivers/ddr/altera/sdram_agilex5.c` computes `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 +mismatch. U-Boot then rewrites Linux's node wholesale from `bi_dram` — +`arch/arm/lib/bootm-fdt.c` → `fdt_fixup_memory_banks()`, under `CONFIG_ARCH_FIXUP_FDT_MEMORY` +(default `y`). + +So the value here only matters if that fixup does not run. It is still the right value to state: +TERASIC and FRIEND's 2 GiB over-claims, ALTERA's and MAINLINE socdk's size 0 boots nothing +without the fixup, and under-claiming degrades gracefully where neither of those does. + +**First-boot action: capture U-Boot's `DDR:` lines.** They are the only authority on the real +size, and a `DDR: Warning` there is the signal that any of these DTS constants is wrong. + +`de25-reference-implementation.md` asked this of the friend's tree ("Does the DE25-Nano HPS +actually have 2 GiB of DRAM?"). Every DE25 bootloader source says 1 GiB, so his Linux node +over-claims 2× — but "1 GiB" is itself a vendor declaration awaiting the IO96B readout, not a +measurement, and this document should not launder one into the other. + +One consequence worth carrying: **all DRAM lives in `0x8000_0000..0xBFFF_FFFF`, entirely inside +32 bits.** That is what makes the SMMU-off escape hatch in §4 safe. + +--- + +## 4. The SMMU / `mmc0` decision (implementation path §8 Q2 and §8 Q1) + +**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 +SMMU off those `iommus` properties are simply inert. + +An earlier revision of this document had this the other way round — SMMU on, with SMMU-off +offered as an "escape hatch". **That framing was inverted and is corrected here.** SMMU-on is +not a working configuration on mainline; SMMU-off is the only one that can be. + +### 4.1 Why SMMU-on cannot work on a mainline kernel + +Traced through `linux-7.2.2` source, not inferred: + +| Step | Where | What it does | +|---|---|---| +| 1 | `drivers/firmware/stratix10-svc.c:865-869` | Takes the shared-buffer address straight out of the `INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM` SMC return (`res.a1`/`res.a2`) | +| 2 | `:956` | `devm_memremap()`s that **physical** address | +| 3 | `:1873-1876` | `gen_pool_virt_to_phys()` — the pool hands out **physical** addresses; `:1780` copies one into `pdata->paddr` | +| 4 | `:607` (`COMMAND_RECONFIG_DATA_SUBMIT` → `INTEL_SIP_SMC_FPGA_CONFIG_WRITE`), `:655` (FCS) | Passes that raw physical address to the SDM as SMC argument `a1` | +| 5 | whole file | `grep -c 'iommu_map\|dma_map_single\|dma_alloc'` → **0** | + +Meanwhile the svc device inherits `iommus = <&smmu 10>` from the MAINLINE dtsi, and +`arm_smmu_def_domain_type()` (`drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:4294-4304`) returns +`0` for anything that is not a PCI device — so the IOMMU core applies the build default, +`IOMMU_DEFAULT_DMA_STRICT` (`drivers/iommu/Kconfig:100,111`), i.e. a **translated** domain over +a page table nothing has populated. SID 10 therefore gets an S1-translate STE, and the physical +addresses from step 4 are unmapped IOVAs. **The first `RECONFIG_DATA_SUBMIT` faults.** + +That SDM traffic genuinely traverses the SMMU is not an assumption — it is precisely what +Terasic's vendor `stratix10-svc.c` compensates for: an IOVA carveout plus explicit `iommu_map()` +calls, a `+0x80000000` address offset, and an `INTEL_SIP_SMC_SDM_REMAPPER_CONFIG` remapper +disable, none of which exists anywhere in mainline at 6.18.44, 7.2 or `master`. Their driver is +the evidence *for* the mechanism, and its absence upstream is the evidence *against* SMMU-on. + +This also **sharpens implementation-path §8 Q1** and the one observed on-hardware failure it +records: a `RECONFIG_REQUEST` timeout on the mainline path is exactly what steps 1–5 plus a +translated default domain predict. + +### 4.2 SMMU-off is the leg to test first — and it is not proven either + +Stated honestly: SMMU-off removes the *identified* fault, it does not establish that mainline +can program this fabric. + +- **For**: it is the only shape in which the svc layer's own addressing is self-consistent; it + is MAINLINE 7.2's shipped default; and the closest supporting data point is the friend's cold + boot with `iommu.passthrough=1` reaching a login prompt. +- **Against**: Terasic's vendor driver *hard-fails* without the SMMU (its agilex5 probe path + returns `-ENODEV` absent `altr,smmu_enable_quirk`), and mainline never touches the SDM + remapper at all. Neither observation transfers cleanly, because the two drivers are doing + different things, but neither can be waved away. + +**Test order for implementation-path §2.6 step 4: SMMU-off first, SMMU-on second.** §2.6 +currently presents the two legs as symmetric; they are not, and the ordering matters because a +SMMU-on failure carries no information (it is predicted) while an SMMU-off failure is real news. + +### 4.3 Why every `iommus` property stays anyway + +With `&smmu` disabled, `of_iommu_xlate()` returns `-ENODEV` for the unavailable IOMMU node +(`drivers/iommu/of_iommu.c:28-29`) and `of_iommu_configure()` treats `-ENODEV` as "this device +has no IOMMU", not as an error. Every `iommus` phandle in the tree therefore goes inert at once, +with no per-node edits, and every master DMAs physically. + +Deleting them would be actively wrong, because the SMMU-on leg of the test must be a **one-line +change**: `arm_smmu_init_initial_stes()` fills *every* stream-table entry with +`arm_smmu_make_abort_ste()` (`arm-smmu-v3.c:1925-1934`, called from `:1955` for two-level tables +and `:4489` for linear ones), so under an enabled SMMU a master with no `iommus` property does +not bypass — it **aborts**. The tree as shipped is correct for both configurations; only +`&smmu`'s `status` selects between them. + +### 4.4 What this does to §8 Q2 (the `mmc0` DMA width) + +With the SMMU off, the §8 Q2 failure mode **cannot occur**: the mechanism is the IOVA allocator +handing out an address above bit 39 that a 40-bit-wired controller truncates, and with no +translation there are no IOVAs. Every DRAM address on this board is inside +`0x8000_0000..0xBFFF_FFFF` (§3, Memory), i.e. well inside 32 bits. + +`linux-patches/0101` is still correct and still wanted — it is what makes the SMMU-on leg +survivable, and it is what makes the two-string compatible schema-clean — it is simply not +load-bearing in the shipped configuration. + +### 4.5 `dma-coherent` — kept, with a caveat that is new + +Kept, corroborated inside MAINLINE itself: the sibling `nand` controller on this same SoC +carries `dma-coherent` in `socfpga_agilex5.dtsi:315`. + +**The caveat the SMMU flip introduces:** all three vendor trees assert `dma-coherent` on `mmc0` +with the SMMU **on**, where the cacheability of an access is determined by the STE / `IOMMU_CACHE` +attributes rather than by the master's own `dma-coherent` property. Their evidence therefore does +**not** transfer unchanged to the SMMU-off shape, and the mainline `nand` precedent — which is +about the SoC's interconnect rather than about translation — is now doing more of the work than +it was. The friend's `SETUP.md:139-142` is a live warning in the same area: with +`iommu.passthrough=1`, SDHCI ADMA "can corrupt early SD init" after a JTAG full-SOF load, which +is an SMMU-off ADMA integrity failure whatever its root cause. + +So [U2] is **re-opened in the shipped shape**: the integrity check must be run with the SMMU off, +not inherited from the vendors' SMMU-on configuration. If reads come back corrupt, **delete +`dma-coherent` first** — treating a coherent master as non-coherent is always correct and merely +slower; the converse silently corrupts. + + +## 5. Deliberate divergences from the reference files + +Every difference a node-by-node diff against TERASIC/ALTERA/FRIEND turns up, accounted for. +D1–D6 are changes of *form*; the content drops are in §3. + +| # | Divergence | Why | +|---|---|---| +| D1 | `mmc0@10808000` → **`mmc@10808000`** | Generic node name (`mmc-controller.yaml`). The device is named from the translated `reg`, not the node name, so `10808000.mmc` either way. | +| D2 | LED child `hps0` → **`led-0`** (ALTERA's form) | `leds-gpio.yaml` child pattern `(^led-[0-9a-f]$|led)`; `hps0` is rejected under `additionalProperties: false`. ABI unchanged: `/sys/class/leds/hps_led0` comes from `label`. | +| D3 | `ethernet-phy@0 { reg = <1>; }` → **`ethernet-phy@1 { reg = <1>; }`** | Silences `-Wunit_address_vs_reg`, which is the one dtc warning a verbatim transcription would have added. Provably a no-op: `of_mdiobus_register()` addresses PHYs from `reg`. All three references carry the mismatch. | +| D4 | `memory { … }` → **`memory@80000000 { … }`**, and 2 GiB → **1 GiB** | Unit address for dtc; size from the factory SPL DTB (§3, Memory). `memory@80000000` is still found by both consumers — Linux scans `device_type = "memory"`, and U-Boot's `fdt_fixup_memory_banks()` uses libfdt's `fdt_subnode_offset()`, whose name comparison accepts a trailing `@unit-address`. | +| D5 | `model` `"SoCFPGA Agilex5 Terasic DE25-Nano"` → **`"Terasic DE25-Nano"`** | Cosmetic; the board's name rather than a compilation of SoC and board. | +| D6 | `&watchdog4` loses `disable-over-current` | A watchdog has no over-current line. Copy-paste from `usb0`, propagated through all three trees. | +| D7 | `&smmu` `okay` → **`disabled`** | The one *design* divergence, not a form one. Mainline's svc layer cannot work under a translated domain (§4.1). Also MAINLINE 7.2's own default. All `iommus` properties retained so the reverse is one line. | +| D8 | `mmc0` gains **`max-frequency = <25000000>`** | FRIEND's value. The only mainline-driver boot of this board needed it; mainline programs none of the PHY timing the vendors declare (§3, `max-frequency` row). Lift once [U3] clears. | + +--- + +## 6. What this document does **not** prove + +- **That the board boots.** Nothing here has run on silicon. This is a desk validation: the DTB + compiles clean, validates against the bindings modulo two known binding gaps, and its + load-bearing node names and phandles resolve. Every runtime claim is a citation into driver + source, not an observation. +- **That the fabric can be programmed.** Implementation path §8 Q1 is untouched by this file and + remains **low confidence**; the `fpga-region`/`fpga_mgr` nodes here are the *binding* half of + §2.6's four-step test, which is expected to pass, not the *programming* half, which is the + actual experiment. +- **That `mmc0` DMAs correctly.** §4.4/§4.5 argue the shape is right and name the fallbacks; + [U2] (coherency, in the SMMU-off shape) and [U3] (clock cap) both stay open. §8 Q2's own + failure mode is out of reach in the shipped configuration, which is a change of exposure, not + a proof of correctness. +- **That SMMU-off *works*.** §4.1 rules SMMU-on out from source. It does not follow that + SMMU-off succeeds — Terasic's vendor driver hard-fails without the SMMU, and mainline never + touches the SDM remapper. [U10] is the live question and §2.6 step 4 is the test. +- **PHY link.** `phy-mode = "rgmii"` with the delays on the PCB is ALTERA's claim, taken on + their authority and cross-checked against the other two references. Not measured. + +--- + +## 7. Open `[U]` — what could not be settled without hardware + +| # | Question | Why it matters | How it is settled | +|---|---|---|---| +| ~~U1~~ | ~~Is the console really `uart1`?~~ | — | **Closed `[V]`.** The DE25 U-Boot tree sets `serial0 = &uart1` and `stdout-path = "serial0:…"` (`de25-uboot-socfpga:.../socfpga_agilex5_de25_nano.dts:11`, `...-u-boot.dtsi`), and the friend booted Linux on that console. Not to be re-litigated. | +| **U2** | Is the SDMMC master really cache-coherent **with the SMMU off**? | `dma-coherent` on a non-coherent master is silent data corruption. The vendors' evidence is all SMMU-**on**, where cacheability comes from the STE/`IOMMU_CACHE` attributes, not this property — so it does not transfer to the shipped shape (§4.5). The friend's `SETUP.md:139-142` records ADMA corrupting early SD init under `iommu.passthrough=1`. | Sustained `dd` read/write + checksum in the **shipped SMMU-off** configuration. If corrupt, delete `dma-coherent` first — the non-coherent treatment is always correct, merely slower. | +| **U3** | Can the 25 MHz clock cap be lifted? | Shipped capped, following the only mainline-driver boot of this board. Mainline programs none of the 40 vendor `cdns,phy-*` values, so PHY state is whatever U-Boot left. | Once a sustained `dd` read/write is clean at 25 MHz, raise in steps (or delete `max-frequency`) and re-run. Watch for `unrecognised SCR structure version` / `-EINVAL` at init, which is the symptom the friend hit. | +| **U4** | Does the SD I/O rail need ALTERA's `regulator-gpio` on `portb 3`? | ALTERA wires `vqmmc-supply` to a 1.8V/3.3V level shifter; we rely on `no-1-8-v` plus the boot-default state. | Only matters if UHS is ever wanted. Until then the shifter never switches. | +| **U5** | `dr_mode` for `usb0` | Unset in all three references, so dwc2 reads OTG capability from the hardware. A MiSTer image wants host mode. | Observe `/sys/class/udc` and whether hubs enumerate; add `dr_mode = "host"` if OTG guesses wrong. | +| **U6** | Should the root compatible say `terasic,de25-nano`? | Currently claims to be an SoCDK. Nothing on arm64 reads it, but it is wrong. | A one-line upstream patch to `Documentation/devicetree/bindings/arm/altera.yaml`, then a one-line DTS change. | +| **U7** | Does `sdhci-caps`' 200 MHz base clock match the silicon? | If the capability register already reports a *different* non-zero base, we override a correct value with a vendor constant and every derived card clock is off — including the 25 MHz cap, which would really be 12.5 MHz if the true base were 100 MHz. | `cat /sys/kernel/debug/mmc0/ios` on first boot; compare `dmesg`'s reported max clock against a raw read of `SDHCI_CAPABILITIES` before the override. | +| **U8** | Do the residual `fpga-mgr` binding warnings get fixed upstream? | Until then this DTB carries 5 warnings and a CI gate would have to allow-list them. | Watch Khairul's v6 fpga-mgr binding series (implementation path §8 Q4). The `mmc0` half is already closed by `0101`. Re-check on every kernel bump. | +| **U9** | **Does BL31 hand back the svc buffer we think it does?** | `memory-region = <&service_reserved>` is **dead on mainline**: `stratix10-svc.c:865-876` takes the address from the `FPGA_CONFIG_GET_MEM` SMC and `:952-960` `memremap`s *that*, never consulting the `no-map` reserved region. If our `u-boot.itb`'s BL31 answers with a region other than `0x8000_0000 + 32 MiB`, the driver memremaps **live kernel RAM** and the SDM writes into it. | `dyndbg='file stratix10-svc.c +p'` on the kernel command line, then compare the driver's `"SM software provides paddr"` / `"reserved memory ... paddr"` debug line against `/proc/device-tree/reserved-memory/svcbuffer@0/reg`. Do this **before** the first reconfiguration attempt. | +| **U10** | Can mainline's svc program the fabric at all, SMMU off? | §4.2. SMMU-on is ruled out by source; SMMU-off is unproven in both directions. | Implementation path §2.6 step 4, **run SMMU-off first**. A SMMU-on failure carries no information; a SMMU-off failure is real news. | + + +## 8. Corrections owed to sibling documents + +| Document | Claim | Correction | +|---|---|---| +| [`de25-implementation-path.md`](de25-implementation-path.md) §3.1 | The authored node set overrides `/firmware/svc`'s compatible to `"intel,agilex-svc"` | Superseded. We keep MAINLINE's `"intel,agilex5-svc"` and carry `linux-patches/0102` (a one-line match-table addition) instead. The override is retained as the documented fallback. Rationale: the vendor treats the agilex5 string as semantic, so a kernel that will one day want that distinction should not have the DT lie about the SoC. | +| [`de25-implementation-path.md`](de25-implementation-path.md) §2.5 | The two-string fpga-mgr form "warns" under `dtbs_check` | Confirmed and quantified: **five** warning lines, from **two** schemas (`intel,stratix10-svc.yaml` validating the child in place, and `intel,stratix10-soc-fpga-mgr.yaml` validating it standalone), plus the "failed to match any schema" summary. Simulating Khairul's v6 `oneOf`/`items` shape clears all five (§2.4). | +| [`de25-reference-implementation.md`](de25-reference-implementation.md) (open question, line ~664) | "Does the DE25-Nano HPS actually have 2 GiB of DRAM? His memory node hard-codes `reg = <0 0x80000000 0 0x80000000>`…" | **Answered as far as any desk source can: every DE25 bootloader source says 1 GiB.** His Linux node over-claims 2×, and his `x86ram@b0000000 + 0x10000000` would sit past the top of RAM. Our node states 1 GiB — but see the row below: "1 GiB" is a vendor *declaration*, not a measurement. | +| [`de25-boot-chain.md`](de25-boot-chain.md) §3 | "the factory SPL DTB's memory node reads `reg = <0x0 0x80000000 0x0 0x40000000>` = 1 GiB at 0x8000_0000 **[V SPL-dtb]**" | **The `[V]` overstates it.** That is a *constant in Terasic's U-Boot device tree* (`de25-uboot-socfpga:arch/arm/dts/socfpga_agilex5_de25_nano-u-boot.dtsi`, commented `/* 1GB */`), not a readout. The authoritative size comes from the IO96B controller at runtime: `drivers/ddr/altera/sdram_agilex5.c` derives `hw_size` from `io96b_ctrl->overall_size`, caps the DT value at it, and prints `DDR: Warning …` on mismatch. Downgrade to `[V, vendor DTS constant] / [U, hardware]` and capture U-Boot's `DDR:` lines on first boot. | +| [`de25-implementation-path.md`](de25-implementation-path.md) §3.1 and §2.6 step 4 | `&smmu { status = "okay"; }` is part of the authored node set, and the two SMMU legs of the programming test are symmetric | **Both corrected.** Mainline's `stratix10-svc` hands the SDM raw physical addresses with no `iommu_map`/`dma_map` anywhere, while the inherited `iommus = <&smmu 10>` puts the svc device on a *translated* default domain — so **SMMU-on cannot program the fabric on a mainline kernel** (§4.1, traced at 7.2.2). Wave 1 ships `status = "disabled"`. The two legs are therefore **not** symmetric: SMMU-off must be run **first**, because a SMMU-on failure is predicted by source and carries no information. | +| [`de25-implementation-path.md`](de25-implementation-path.md) §8 Q2 | The `mmc0` DMA-width question is the leading first-boot risk | Still real, but **not reachable in the shipped configuration**: with the SMMU off there are no IOVAs to truncate and all DRAM is below 4 GiB (§4.4). `linux-patches/0101` remains correct and wanted — it is what makes the SMMU-on leg survivable — but it is not load-bearing for first boot. The *actual* leading first-boot SD risk is PHY timing, which mainline does not program at all; hence the 25 MHz cap ([U3]). | +| [`de25-implementation-path.md`](de25-implementation-path.md) §3.1 | `memory-region = <&service_reserved>` is part of the svc contract | **It is inert on mainline.** `stratix10-svc.c:865-876` takes the buffer from the `FPGA_CONFIG_GET_MEM` SMC and `:952-960` `memremap`s that; the `no-map` reserved region is never consulted. Whether BL31 actually answers with `service_reserved`'s range is a **hardware check that must precede the first reconfiguration attempt** — [U9]. | +| [`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) §4.2 | The svc node needs `compatible = "intel,agilex-svc"` authored on top of the in-tree dtsi | Only true on a 6.18 base, where the whole subtree is authored. On 7.x the subtree already exists and is inherited; the only additions are the `fpga_mgr` child and the root `fpga-region`. That section's own `[U]` note about the missing `smmu` node is resolved on 7.2: the node exists with `status = "disabled"`, and the board file deliberately leaves it disabled for wave 1 (§4.1); the SMMU-on form is kept only as the labelled alternative for the second test leg. | diff --git a/docs/de25-nano-tasks.md b/docs/de25-nano-tasks.md index 27f9dd2..18700f8 100644 --- a/docs/de25-nano-tasks.md +++ b/docs/de25-nano-tasks.md @@ -261,22 +261,78 @@ its own evidence base. --- +## Wave 1 — 2026-09-02 (pre-hardware) + +Executed on branch `feature/de25-wave1` on top of the D0/D1 recon (PR #132). Agent sizing per +track: routine shell/CI edits on Sonnet, build/DTS/patch/ADR authoring on Opus, adversarial +verification on Fable, environment prep on Haiku. + +| Track | Task | Deliverable | Status | +|---|---|---|---| +| 1 | D1.2 implement | `scripts/lib/board-expectations.sh`; `check-kernel-defconfig-sync.sh` + `buildroot-build/action.yml` table-driven, DE10 default byte-identical | **DONE** `1f9374c` — §5.6 checks 1/2/3/5 run; no-arg and BOARD=de10nano byte-identical; `bogus` exits 2 | +| 2 | D2.1 | `configs/mister_de25nano_defconfig`, `board/mister/de25nano/linux.fragment`, `make de25` family, external.mk initramfs-hook guard | **DONE** `6dd5604` — toolchain 8 min; **full build green**: `Image` 41.9 MB, ext4 rootfs, 34/34 patches applied at -F0, both carried patches compiled. Kernel is arm64 `defconfig` + fragment (1,481 modules, 90 MB) — a diet is wave-2 work | +| 3 | D2.3 desk half | `board/mister/de25nano/socfpga_agilex5_de25nano.dts` + `docs/de25-dts-rationale.md`; dtc + dtbs_check | **DONE** `d8ad032`+`ae86dbe` — dtc 0 warnings; dtbs_check 5 (all the fpga-mgr binding gap); **SMMU shipped disabled** after the Fable review (see below); `socfpga_agilex5_de25nano.dtb` (17,138 B) built through Buildroot via `BR2_LINUX_KERNEL_CUSTOM_DTS_PATH` — note the `arm64/intel/…` include-prefix form, because Buildroot copies a custom DTS into the `dts/` root, not `dts/intel/` | +| 4 | patch series | `board/mister/de25nano/linux-patches/` (shared symlinks + 0101 sdhci-cadence 40-bit mask + 0102 svc match) | **DONE** `e47cb3e`+`6f50981` — 32 symlinks (3 to the 7.x-anchored beta copies, load-bearing) + 0101/0102; 0002 audio and 0047 excluded (README maps every row) | +| 5 | D4 ADR | `docs/decisions/0029-de25-implementation-path.md` (Proposed) | **DONE** `f156140` — Proposed; four source inconsistencies resolved explicitly; open decisions listed | +| 6 | ledger §6.5 | `lint.yml` board-agnostic, fails loudly on an empty set | **DONE** `6f55523` — discovery + empty-set guard; de10nano file set unchanged, verified by running both steps | + +### What the Fable review changed (2026-09-02) + +The adversarial pass on the DTS and the two patches produced one design-level finding that +inverts §3.1's default: **with the SMMU enabled, mainline `stratix10-svc` cannot program the +fabric** — it 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. Binding +and programming are different claims; SMMU-on satisfies only the first. Wave 1 therefore ships +`&smmu { status = "disabled"; }` with every `iommus` left in place (inert via `of_iommu`'s +`-ENODEV`), and the §2.6 fabric test runs in that shape first. SMMU-off is **unproven, not +disproven** (`de25-dts-rationale.md` U10). Other actions taken: `max-frequency = 25 MHz` on +mmc0 for first boot, the 1 GiB memory node downgraded from "measured" to "Terasic's U-Boot DTS +constant" (U-Boot rewrites `/memory` from IO96B anyway), a BL31 `GET_MEM`-vs-`service_reserved` +check added as U9 to run *before* the first reconfiguration attempt, and the uart1 console +promoted to [V]. + +### Testing on a borrowed board before we own one + +A friend's DE25-Nano can run our SD images, with one condition that follows directly from +[`de25-boot-chain.md`](de25-boot-chain.md) §2: **the QSPI must hold the factory phase-1 +image.** Our card ships `u-boot.itb` only and relies 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 instance, is exFAT-aware and RSU-shaped — +so a boot failure there would tell us nothing about our image. + +- **Restore is the same tool he already used to modify it**: Quartus Programmer over the on-board + USB-Blaster III, `quartus_pgm -m jtag -c 1 -o "pvi;golden_top_hps.jic"`, from the Resource + Package (`…/GHRD/output_files/program_qspi_flash/`). Verify the file first: + `golden_top_hps.jic` is 16,777,447 B, sha256 `e3d20c2d…38a4` (full hash in boot-chain §8). +- **Doing this on his board also closes an open [U] of ours**: that the *published* JIC boots a + physical board at all (boot-chain §8, "an archived copy"). Record the board revision. +- **Safety bar before any image reaches him** (rule 2 of this plan): a `fable` adversarial pass on + the U-Boot env fragment proving `CONFIG_ENV_IS_IN_UBI` is off (implementation-path §6.2 — + a *load-path* QSPI write, not a save-path one) and that nothing in the image can write QSPI. +- 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. + ## 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: -1. **Implement D1.2** (the arch-assert generalization), then **D2.1** — the aarch64 defconfig built - green on 7.2. D2.1 is blocked on D1.2 and on nothing else. Local build; keep it off CI. -2. **Author the DE25 DTS** (D2.3's desk half) and validate with `dtc` + `dtbs_check`. Node set in - `de25-implementation-path.md` §3.1; harvest board wiring from Altera's in-house DE25 board file - but take `mmc0` in **SD4HC** form — `cdns,sd6hc` is nowhere in mainline and buys a carried - driver rewrite. -3. **Compile-test the D0.3 shared-series patches against aarch64/7.2.** Highest de-risk per unit - of effort; converts desk verdicts into compile-verified ones. -4. **An ADR for the nine owner decisions.** They are load-bearing and currently live only in - `de25-implementation-path.md` §1 — ADR 0027 predates all of them. -5. **Fix `lint.yml`'s silent failure** (ledger finding) — it affects the DE10 today. +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. Sequencing note learned the hard way on 2026-08-21: when a research phase feeds a claim set that a diff --git a/docs/decisions/0029-de25-implementation-path.md b/docs/decisions/0029-de25-implementation-path.md new file mode 100644 index 0000000..2b6dba0 --- /dev/null +++ b/docs/decisions/0029-de25-implementation-path.md @@ -0,0 +1,286 @@ +# ADR 0029 — DE25-Nano implementation path: the nine decisions that bound wave 1 + +**Status:** Proposed (2026-09-02) — the nine decisions below were taken by @mcfbytes between +2026-08-19 and 2026-08-22 during D0/D1; this ADR formalises them for acceptance and makes no new one. +**Extends:** [ADR 0027](0027-de25-nano-multi-board-readiness.md). **Supersedes nothing.** Each +decision refines an ADR 0027 disposition point (DP-x) or Decision — mapped in the index table; where +no DP covers one, this ADR says so rather than back-fitting a DP. +**Impact (when acted on):** `configs/mister_de25nano_defconfig`, `board/mister/de25nano/*`, +[`patch-provenance.md`](../patch-provenance.md) (two rows), `scripts/check-kernel-defconfig-sync.sh`. +**No DE10 file changes.** +**Source of record:** [`de25-implementation-path.md`](../de25-implementation-path.md) §1, §1.1 (desk +analysis 2026-08-22, **no hardware touched**), over [`de25-boot-chain.md`](../de25-boot-chain.md) and +[`de25-fpga-reconfig.md`](../de25-fpga-reconfig.md); bare section refs are to the former, whose +**[V]** (read from a named source) / **[U]** (unverified, input named) tags are kept below. +**Related:** [ADR 0016](0016-mainline-first-wifi-drivers.md) (mainline-first precedent), [ADR 0019](0019-exfat-symlinks-carried-patch.md) (carries), +[ADR 0021](0021-rt-kernel-first-class-ci.md) (variant machinery), [ADR 0024](0024-mainline-uboot-capability-artifact.md) (mainline U-Boot). + +--- + +## Context + +ADR 0027 committed the project to a *posture* — same repo, staged phases, guarded couplings — and +deliberately took no technical position on how a DE25 boots or reconfigures. It predates all of D0. +Phases D0/D1 then completed (2026-08-21/22, [`de25-nano-tasks.md`](../de25-nano-tasks.md) "Execution +status") and the owner took nine decisions against their findings, which live only as a table in a +desk-analysis document. They foreclose options the task list still calls open, and two are the +difference between a board that boots from SD and one that does not. + +Two D0 findings frame the set, neither known when ADR 0027 was accepted. **`clk-agilex5.c` does not +exist in 6.18** — it lands in v6.19 **[V §0 Finding 1, §5.1]**, so on a 6.18 base every `&clkmgr` +consumer, `mmc0` included, defers forever. And **binding the mainline FPGA-manager stack is settled; +*programming* the fabric through it is not** **[V §0 Finding 2, §2.6]**: Terasic's vendor `svc` does +Agilex-5-specific SDM plumbing (IOMMU attach, IOVA carveout, `+0x80000000` offset, remapper bypass) +that mainline lacks at 6.18.44 *and* v7.2, and the one mainline-path attempt on silicon timed out on +`RECONFIG_REQUEST` and wedged the board. Confidence: **low**. + +## Decision + +Accept the nine decisions below as the bounding constraints for DE25 wave 1. + +| # | Decision | Source | ADR 0027 anchor | +|---|---|---|---| +| D1 | Core loading = `fpga_manager` + DT overlay | §1 row 1 | refines **DP-9** | +| D2 | Scope is "make the hardware available"; Main_MiSTer loads | §1 row 2 | **DP-1**, 0027 **Decision 6** | +| D3 | Two partitions (p1 FAT, p2 the rest); no card shared with a DE10 | §1 rows 3–4 | **DP-3**, **DP-1** | +| D4 | Posture 1: nothing we ship writes the factory QSPI | §6 pre; boot-chain §4, §10 | **is** DP-1's posture call | +| D5 | Mainline-first: a carry needs "no mainline route existed" | §1 row 5 | **DP-1** | +| D6 | Kernel pinned to **mainline 7.2**, not 6.18 | §1 rows 6–7, §5 | *no DP* — nearest DP-5/DP-9 | +| 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** | + +### D1 — Core loading goes through `fpga_manager` + a DT overlay + +**Decision.** Fabric reconfiguration is driven by the kernel FPGA-manager/FPGA-region stack with a DT +overlay carrying `firmware-name`; U-Boot-side core loading, RSU slots and QSPI-resident cores are out. + +- **Evidence.** The overlay notifier is the *only* mainline trigger — `firmware-name` is read solely + inside `of_fpga_region_parse_ov()`, so a base-tree `fpga-region` programs nothing at boot **[V §7 + option (c)]**; DP-9's premise was confirmed by D0.2 **[V]**; RSU is out on sizing (a 256 MB + reference layout against this board's 16 MB) **[V boot-chain §8.4]**. +- **Consequences.** The RT beta's UIO doorbell patches (0043–0045) are not ported (DP-9). The kernel + needs `CONFIG_FPGA_BRIDGE=y` *and* `CONFIG_OF_OVERLAY=y`, which `OF_FPGA_REGION` does **not** + select — without it the notifier is a stub and reconfiguration silently never fires **[V §3.1]**. +- **Re-open if.** The §2.6 test fails (→ D9). Scope limit: D1 forecloses U-Boot as the *core-switching* + mechanism, not whether a boot-time phase-2 `core.rbf` ships at all — boot-chain §7 row 15 **[U]**. + +### D2 — Scope is "make the hardware available"; Main_MiSTer does the loading + +**Decision.** We ship kernel + DTS plumbing so an fpga-manager and an fpga-region probe and are +reachable from userspace, plus a minimal trigger; no core loader, switching worker or staging script. + +- **Evidence.** Mainline exposes no writable attribute anywhere — `fpga_mgr_attrs[]` is three + `DEVICE_ATTR_RO` entries, `fpga_region` one, `OF_CONFIGFS` does not exist **[V §7]**. So "available" + needs some non-mainline code: a ~95-line out-of-tree GPL misc driver calling `of_overlay_fdt_apply()`. +- **Consequences.** Wave-1 releases carry no MiSTer binaries (0027 Decision 6 — a bare developer OS). + Overlay choice, `.rbf` staging, switch sequencing and quiescing the fabric client are Main_MiSTer's. + That module is the only carried item that fully satisfies D5 — the tension §7 names, not one hidden. +- **Re-open if.** Mainline gains a writable fpga-manager attribute or an overlay loader (delete the + module); or an upstream framework port states a different contract — the upstream owner's call. + +### D3 — Two partitions (p1 FAT, p2 the rest), and no card shared with a DE10 + +**Decision.** The DE25 SD card is two partitions — p1 FAT holding what the factory SPL must find, p2 +everything else — and a card that works in both boards is an explicit non-goal. + +- **Evidence.** The factory SPL loads `u-boot.itb` **by name from a FAT filesystem** on partition 1 + (`CONFIG_SPL_FS_FAT=y`, `SYS_MMCSD_FS_BOOT_PARTITION` default 1), and there is **no 0xA2 analogue** + **[V boot-chain §2 step 4 and close]**. +- **Consequences.** No cross-board layout compromises; `boot-chain.md` §2's partition-order lore does + not transfer. DP-3 (A/B slots) stays tabled on the owner's "pull the card" rationale. +- **Re-open if.** Nothing plausible for p1. p2's filesystem and kernel-on-p1-vs-p2 are **not** decided + here — §6.3 is explicit that D3 "fixes the partition count and p1's FAT type only". + +### D4 — Posture 1: nothing we ship writes the factory QSPI + +**Decision.** We pin to the factory QSPI image (SDM firmware + phase-1 bitstream + embedded U-Boot +SPL) and never write it; we build `u-boot.itb` only, and our whole interface to the board's boot +firmware is one FIT file, by name, on the FAT partition. + +- **Evidence.** The analogue of the DE10 "stock `uboot.img`, byte-identical" posture, **chosen + fail-closed, not forced by impossibility** (boot-chain §9.2 corrected "close to forced"); its + sharpest sub-risk, SPL FIT-signature enforcement, tests **negative** on the published SPL **[V + boot-chain §4 posture 1, §7 row 6]**. The strongest guard is a defconfig line, not a habit: on a + failed FAT env load `env_ubi_load()` calls `ubi_part()` unconditionally, and a UBI attach on a blank + MTD **writes a layout volume into QSPI on the load path, with no `saveenv` anywhere** **[V, + code-traced, §6.2 — promotes ledger row 12 from [U]]**. +- **Consequences.** Our U-Boot fragment carries `# CONFIG_ENV_IS_IN_UBI is not set` with the comment + that survives editing (§6.2); our DTS omits the QSPI node or enables it read-only (boot-chain §7 row + 10); any `fw_env.config` names only the FAT `uboot.env` (row 11). Posture 2 is **bench-only** and + never an update-channel artifact; Terasic's DDR/pinmux handoff lives inside the bitstream and is not + ours to change **[V boot-chain §8.6]**. +- **Re-open if.** A sub-16 MB power-loss-safe RSU layout is ever *proven* (boot-chain §8.4 residual) — + posture 2/3 becomes shippable and this must re-open rather than be treated as settled; or if D2.2 + finds the factory SPL rejects our FIT (§8.3 blocker 1 **[U]**). + +### D5 — Mainline-first, strongly + +**Decision.** A carried patch requires justification that **no mainline route existed** — not that it +was easier, and not that the vendor does it that way. + +- **Evidence.** The route exists and is cheap: a two-string fallback compatible binds the stock + `stratix10-svc`, `stratix10-soc` and `sdhci-cadence` drivers with zero driver patches — OF matching + walks the whole `compatible` list and no table keys behaviour on the matched entry **[V §2.1, from + `drivers/of/base.c:338-356` and all three tables]**. +- **Consequences.** Altera's `mmc0` (`altr,agilex5-sd6hc`/`cdns,sd6hc`) must **not** be copied — + `cdns,sd6hc` exists nowhere in mainline; take Altera's board `.dts` as the wiring reference and + `mmc0` in the SD4HC form **[V §4.1]**, and read vendor trees for *behaviour*, not just nodes **[V + §4.2]**. Three node *names* are load-bearing: `svc`, its parent `firmware`, and `method = "smc"`. +- **Re-open if.** Never as a blanket rule; re-applied per item. D7, D8 and D9 are its instances. + +### D6 — The kernel is pinned to mainline 7.2, not 6.18 + +**Decision.** The DE25 pins to mainline **7.2**, the line this repo already builds for the DE10 RT +beta (superseding the earlier, weaker "kernel version open in our favour up to and including 7.2"). + +- **Evidence.** The 6.18 case collapses on its own terms **[V §5.1]**: `clk-agilex5.c` lands in v6.19, + so a 6.18 DE25 carries a whole SoC clock driver (847 vendor lines on the reference board) for what + mainline provides one release later — the carry D5 forbids. Cost is bounded: all 40 of our patches + applied to **Linux 7.2 final** at `-F0`, zero fuzz **[V rt-beta-kernel.md:222]** — ARM32, so + aarch64 is **[U]** (Q8). +- **Consequences.** The DE25 inherits the RT beta's treadmill (7.2 is not LTS): `rt-clean`, TOFU-hash + re-verification, per-version boot proof. Sharing that line keeps the repo at **two** kernel lines + and lets one bump serve both. No version fixes reconfiguration: `grep iommu|REMAPPER|dma_addr_offset` + in `stratix10-svc.c` is **zero hits** at 6.18.44 and v7.2 **[V]**. +- **Re-open if** (§5.3, verbatim): kernel.org designates a 7.x release longterm — pin that one; or 7.3 + ships with the `smmu` node already enabled *and* any Agilex 5 `mmc0` node. + +### D7 — Carry the `sdhci-cadence` 40-bit DMA-mask patch, as an upstreamable carry + +**Decision.** We carry a `sdhci-cadence` patch capping this controller's DMA mask at 40 bits, as an +*upstreamable* carry to be submitted, not a permanent fork — accepted under D5 because no mainline +route exists. + +- **Evidence.** Terasic's driver gives `intel,agilex5-sd4hc` a match entry carrying + `SDHCI_QUIRK2_40_BIT_DMA_MASK`, a quirk absent from mainline at 6.18.44, v7.2 and `master`, so + mainline takes the `DMA_BIT_MASK(64)` branch on a controller the vendor caps; under SMMU translation + an IOVA above the wired bits truncates — an `F_TRANSLATION` fault **[V §5.1, §8 Q2]**. **The 40-bit + value is a hypothesis inferred from the vendor quirk, untested on hardware [U]** (§2.6 item 2). +- **Consequences.** Patch and DTS are **coupled**: `sdhci-cadence.c:656` matches `cdns,sd4hc` with no + `.data`, so the patch adds a *new* match entry carrying data and the DTS must declare that new + string first in `mmc0`'s list **[V §1.1]** — neither half works alone. +- **Re-open if.** §8 Q2's five-leg boot test shows no fault, or that `iommus`/`dma-coherent` presence + is the real discriminator — then the patch is dropped. + +#### D6 and D7 fix two *independent* problems — do not conflate them + +They read as one fix and are not. Dropping the 7.2 pin leaves a board that does not boot from SD +(§1.1). Dropping the sdhci patch does **not** break the *shipped* wave-1 boot path: with the SMMU +disabled there are no IOVAs to truncate and all DRAM sits below 4 GiB +([`de25-dts-rationale.md`](../de25-dts-rationale.md) §4.4) — 0101 is what makes the future SMMU-on +test leg survivable, and it is carried now so that leg is a one-line DTS change. + +| Problem | What fixes it | What does **not** fix it | +|---|---|---| +| `clkmgr` never probes, so every `&clkmgr` consumer (`mmc0`, gmac) defers forever | **The 7.2 pin.** `clk-agilex5.c` exists in 7.2 and matches `"intel,agilex5-clkmgr"` with real `.data` (`drivers/clk/socfpga/clk-agilex5.c:544`) **[V]**; 6.18.44 has no such file **[V]** | The sdhci patch. Entirely unrelated code path. | +| `mmc0` DMA faults through the SMMU (mainline takes the 64-bit branch; the vendor caps the controller at 40 bits) — **reachable only with the SMMU enabled**, which wave 1 does not ship | **The carried `sdhci-cadence` patch.** | The 7.2 pin. A vendor-vs-mainline driver delta, **not** a 6.18 regression — it travels forward to 6.19 and 7.2 unchanged **[V]** | + +D8's one-liner is a *third* independent thing: it fixes fpga-mgr/svc **binding**, touches neither DMA +nor clocks, and is carried locally (0102) pending an owner decision on upstream submission. + +### D8 — Fix mainline's unbindable Agilex 5 svc node upstream, one match-table line + +**Decision.** Mainline ships an Agilex 5 `/firmware/svc` node whose `compatible` matches no driver on +any released kernel through v7.2 or `master`. We **carry the one-line match-table fix locally** +(`board/mister/de25nano/linux-patches/0102`) and keep mainline's own `intel,agilex5-svc` compatible +in the DTS, so the device tree does not misdescribe the SoC; overriding the compatible to +`intel,agilex-svc` is retained only as the documented fallback if 0102 is ever dropped. Whether and +when 0102 is submitted upstream is an owner decision (see "Decisions deliberately left open"). + +- **Evidence.** `stratix10_svc_drv_match` has never contained `intel,agilex5-svc` (`grep -c agilex5` → + 0 at v7.2 and `master`) **[V §3, §3.1]**. The override is schema-clean — `intel,agilex-svc` is in + the binding's `enum` and `iommus` is top-level, so `iommus = <&smmu 10>` stays legal, while the + two-string form fails `dtbs_check` **[V §3.1]**. Keeping the agilex5 string (with 0102) also + satisfies the binding's `allOf`, which *requires* `iommus` for that string, without a workaround. +- **Consequences.** The fpga-mgr *child* differs: use the two-string fallback and accept a transient + `dtbs_check` **warning** until the v6 binding lands — **correcting** [`de25-fpga-reconfig.md`](../de25-fpga-reconfig.md) + §4.2's "free and forward-compatible" claim, true at runtime but not schema-clean **[V §2.5]**. + Whether dropping the agilex5 string while keeping `iommus` is *functionally* equivalent is **[U]** (Q3). +- **Re-open if.** Upstream adds per-compatible `.data` to `s10_of_match` or `stratix10_svc_drv_match` + — D5's "a fallback is free" argument then stops holding and §2.1 must be re-derived. **Watch both + tables on every kernel bump** (§8 Q10). + +### D9 — A vendor `stratix10-svc` carry is a hardware-gated escape hatch, not a plan + +**Decision.** Carrying Terasic's `stratix10-svc.c` behaviour is permitted **only if** on-hardware +testing proves mainline `svc` cannot program the fabric, and only for capability critical to running +MiSTer — and the same bar applies to any vendor-kernel behaviour: it must buy a real win on something +critical, not merely be what the vendor happens to do. + +- **Evidence.** The gate is §2.6's four-step test: boot the 7.2 kernel with **no behavioural svc + patches** — 0102 (match-table only) stays applied, because without it the shipped DTS's + `intel,agilex5-svc` node binds nothing and the test would stop at a binding failure that says + nothing about mainline's programming path — confirm `fpga0`/`region0` probe (the binding half, + expected to pass), then apply an overlay with `firmware-name` and watch for + `SVC_STATUS_BUFFER_DONE` or the `RECONFIG_REQUEST` timeout. **SMMU disabled first** (the shipped + shape); the SMMU-enabled leg is predicted to fail by source (mainline svc passes physical + addresses, [`de25-dts-rationale.md`](../de25-dts-rationale.md) §4.1) and is run second only to + confirm that prediction **[V §2.6, corrected]**. +- **Consequences.** **Run this test first in the D2 hardware session**; nothing else depends on it + (clock driver, SD controller, DTS node set, U-Boot, partition layout are independent). If it fails, + mainline-first is refuted *on the svc layer only* and the real choice graduates to its own ADR, with + 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. + +## Decisions deliberately left open + +None of these is decided here. Each is named so it is not mistaken for settled. + +1. **p2's filesystem, and therefore whether the kernel lives on p1 or p2** (§8 Q7, §6.3). D3 fixes the + partition count and p1's FAT type only; mainline U-Boot now reads exFAT (`CONFIG_FS_EXFAT`) **[V]**, + so this is a project decision, not a capability gap. +2. **Whether the DE10 and DE25 defconfigs are refactored into a shared base + per-board fragments.** + The owner's goal is maximal reuse, and the readiness ledger finds most machinery board-agnostic. + Wave 1 nevertheless introduces `configs/mister_de25nano_defconfig` **standalone, on purpose**: + 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. +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 + §6]**. Settled by D2.2 (§8 Q5) and a desk build (§8 Q6, `# CONFIG_SPL is not set`). +4. **Whether either patch is submitted upstream.** D7 and D8 are both *intended* as upstream + contributions, and that intent is part of their justification. **Owner approval is required before + any mailing-list post** — nothing goes to `linux-fpga`, `linux-mmc` or any list without it. + +Still tabled from ADR 0027 and untouched here: DP-2, DP-3, DP-6, DP-7, DP-8, DP-10. Also open: +boot-chain §7 row 15 — whether a phase-2 `core.rbf` from our own Quartus compilation may be paired +with the factory phase-1 **[U]**. + +## Relationship to the DE10 build + +**The invariant: nothing here changes DE10 behaviour.** Every decision is scoped to a board that does +not yet build in this repo. The DE10 keeps its 6.18 pin, its `zImage_dtb` concatenation contract, its +0xA2 layout, its stock-`uboot.img` byte-identity and its release namespace; D6's 7.2 pin reuses the +*RT variant's* existing line and does not move the DE10 stable pin. + +**The parity-with-stock methodology is unchanged.** DE10 work continues to shadow a stock image and be +judged against it ([`abi-contract.md`](../abi-contract.md), [`stock-reconciliation.md`](../stock-reconciliation.md), +the parity dossiers). It does not transfer and is not weakened: per DP-1 there **is no stock to +shadow** on the DE25 — whatever ships first *is* the reference — which is why the DE25 gets D5 where +the DE10 gets bug-for-bug fidelity. Two standards, each correct for its board. + +## Alternatives rejected + +**Amend ADR 0027 in place.** 0027 deliberately took no technical position; amending it would blur what +was known on 2026-08-19 against what D0 found. A separate dated ADR keeps that auditable. + +**Wait for hardware before recording any of it.** The decisions are already taken and already +foreclosing options in the task list. Recording them does not make them verified — each says its [U]. + +**Record only the hardware-independent ones** (D3, D5, D6, D8) — which drops exactly the four carrying +the most risk. An ADR that records only the safe calls is not a decision record. + +## Consequences + +- Nothing builds or ships on acceptance. The first code this authorises is a DE25 defconfig and board + directory that no CI lane runs per-PR (ADR 0027 Decision 5). +- Four obligations land on whoever opens D2: run §2.6's test **first**; add the + `OF_OVERLAY`/`FPGA_BRIDGE` sentinels to the per-board defconfig check; add the two match-table + watches to the kernel-bump checklist (§8 Q10); and write `# CONFIG_ENV_IS_IN_UBI is not set` into + the U-Boot fragment before any board is powered with our FIT on the card. +- Two `patch-provenance.md` rows are owed (D7's carry, D8's upstream fix) before either lands, and + boot-chain §10's request that "the DP-1 ADR must record posture 1 as chosen fail-closed, with a + proven sub-16 MB RSU layout as its explicit revisit trigger" is discharged by D4. +- If the §2.6 test fails, D9 opens a successor ADR; D1–D8 stand regardless of its outcome. diff --git a/external.mk b/external.mk index 0136520..ee6f778 100644 --- a/external.mk +++ b/external.mk @@ -44,7 +44,44 @@ include $(sort $(wildcard $(BR2_EXTERNAL_MISTER_PATH)/package/*/*.mk)) # ################################################################################ -ifeq ($(BR2_LINUX_KERNEL),y) +# WHY THE ARCH TEST — added with the DE25-Nano target (D2.1). +# +# 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 +# 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. +# +# Two things would go wrong, one loudly and one not: +# 1. LOUDLY, and only by luck: the fixup hard-fails if the cpio is absent, so +# a DE25 build in a tree that had never run `make initramfs` would die with +# an error message telling the developer to build a stage-1 initramfs their +# board does not have and does not want. +# 2. QUIETLY, which is the real hazard: in a tree that HAS run `make +# initramfs` (i.e. any tree that has built the DE10 image — so, every +# developer's, and CI's), the cpio exists and the fixup succeeds. The +# aarch64 kernel then ships an armv7 BusyBox as its initramfs, boots, runs +# /init, and fails at the first exec with a message about the *binary* +# rather than about the build. A green build that produces that is worse +# than no build. +# +# 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 +# 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. +# Revisit if a third board ever needs a stage-1 cpio of its own architecture. +ifeq ($(BR2_LINUX_KERNEL)$(BR2_arm),yy) # Overridable so CI can build the two stages in separate workspaces. MISTER_INITRAMFS_CPIO ?= $(BR2_EXTERNAL_MISTER_PATH)/output-initramfs/images/rootfs.cpio @@ -77,4 +114,4 @@ endef # LZ4-compressed zImage to squeeze (LZ4 optimises for decode speed, not ratio). LINUX_KCONFIG_FIXUP_CMDS += $(sep)$(MISTER_LINUX_INITRAMFS_FIXUP) -endif # BR2_LINUX_KERNEL +endif # BR2_LINUX_KERNEL && BR2_arm diff --git a/scripts/check-kernel-defconfig-sync.sh b/scripts/check-kernel-defconfig-sync.sh index b68ce5f..76661f4 100755 --- a/scripts/check-kernel-defconfig-sync.sh +++ b/scripts/check-kernel-defconfig-sync.sh @@ -50,9 +50,26 @@ # drift also fails a main-only change that edits the main defconfig # without mirroring. # * 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. +# +# 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. # # Exit: 0 = in lockstep; 1 = drift, a one-sided choice bump, or a missing -# sentinel; 2 = usage/IO error. +# sentinel; 2 = usage/IO error (including an unrecognized BOARD). set -euo pipefail export LC_ALL=C @@ -61,6 +78,25 @@ 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" + +# $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 +# surface as a bare `set -u` unbound-variable trace at the later expansion +# instead of a usage error naming the board and the table. +for table in BOARD_ARCH_SENTINELS BOARD_ARCH_FAMILIES; do + if ! declare -n _tbl="$table" 2>/dev/null || [ -z "${_tbl[$BOARD]+set}" ]; then + echo "check-kernel-defconfig-sync: FATAL: unknown board '$BOARD' -- scripts/lib/board-expectations.sh's $table has no row for it. Known boards: ${!BOARD_ARCH_SENTINELS[*]}" >&2 + exit 2 + fi +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; } done @@ -79,8 +115,12 @@ kernel_stripped=$(strip_config "$KERNEL_DEFCONFIG") # --- 2. Sentinels first: a degenerate kernel defconfig must not pass --------- # (an empty or mis-stripped file 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 -for must in BR2_arm BR2_cortex_a9 BR2_KERNEL_HEADERS BR2_TOOLCHAIN_BUILDROOT_CXX; do +# 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 @@ -126,7 +166,10 @@ fi # 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. -for family in BR2_arm BR2_ARM_ BR2_cortex BR2_KERNEL_HEADERS BR2_TOOLCHAIN_BUILDROOT_; do +# 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 diff --git a/scripts/lib/board-expectations.sh b/scripts/lib/board-expectations.sh new file mode 100644 index 0000000..549e601 --- /dev/null +++ b/scripts/lib/board-expectations.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# +# scripts/lib/board-expectations.sh — per-board expected-symbol tables for +# the arch/toolchain guards in scripts/check-kernel-defconfig-sync.sh and +# .github/actions/buildroot-build/action.yml. Not directly executable; +# sourced, same convention as scripts/lib/hash-sync-common.sh and +# scripts/ci-lib.sh (see either file's own header for the sourcing shape: +# a repo-root-relative `source scripts/lib/board-expectations.sh` from a +# workflow/composite-action `run:` block, or +# `. "$SCRIPT_DIR/lib/board-expectations.sh"` from another script under +# scripts/, resolved off ${BASH_SOURCE[0]}, not CWD). +# +# docs/de25-readiness-ledger.md §5.2 is this file's spec; §5.3-§5.4 explain +# each row, §5.7 the fail-closed properties both consumers must preserve. +# +# Adding a board means adding a row to EVERY array below — there is +# deliberately no fallback row and no wildcard match. An inference from an +# existing row (e.g. "this board is also aarch64, so reuse that row") is +# exactly the failure this design forbids: a missing row must be a loud +# error, never a silent, possibly-wrong inherited guess. See §5.2's "Why an +# explicit BOARD string key, not something derived". +# +# Both consumers key on an explicit BOARD string (env var or argument), +# defaulting to "de10nano" — NOT derived from a defconfig filename stem +# (that conflates the board axis with the variant axis +# .github/actions/buildroot-build/action.yml already has) and NOT derived +# by reading BR2_arm vs BR2_aarch64 back out of a defconfig and picking a +# generic row (that would let a future board silently inherit an existing +# one). Each row is an affirmative, reviewed claim. +# +# shellcheck disable=SC2034 # every name below (BOARD_COMMON_SENTINELS, +# BOARD_COMMON_FAMILIES, BOARD_ARCH_SENTINELS, BOARD_ARCH_FAMILIES, +# BOARD_FINGERPRINT_SENTINELS) is read by the sourcing consumers +# (scripts/check-kernel-defconfig-sync.sh, .github/actions/ +# buildroot-build/action.yml), not by this file itself, so shellcheck's +# single-file analysis can't see the use. + +# Arch-independent sentinels/families every board shares (KERNEL_HEADERS is a +# choice under package/linux-headers, TOOLCHAIN_BUILDROOT_CXX is under +# package/gcc — neither varies by board, so neither is duplicated per row). +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). +# 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): + # 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" +) + +# Family PREFIXES for the `for family in ...` symbol-name-set assert (its +# :129, pre-change) — see that script's header §3 for why a kconfig CHOICE +# needs a name-set comparison, not a value comparison. Same arch-row-first, +# 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): + # 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 + # empty-set match, not a failure; both 32- and 64-bit cores share the + # BR2_cortex_* namespace. + [de25nano]="BR2_aarch64 BR2_ARM_ BR2_cortex" +) + +# Toolchain-fingerprint sentinel PATTERNS for +# .github/actions/buildroot-build/action.yml's `for must in ...` assert (its +# :190-195, pre-change) — grep -q patterns against the stripped fingerprint, +# 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. + [de25nano]="^BR2_aarch64 ^BR2_cortex" +)