From 1b2486800abdd29246914d93cdc49e15965c1be9 Mon Sep 17 00:00:00 2001 From: "Michael C. Ferguson" Date: Mon, 14 Sep 2026 11:42:01 -0500 Subject: [PATCH 1/3] docs(uboot): fold the ADR 0023 draft into the plan, re-pin at 2026.07, and lay out the desk plan for both boards The never-merged branch docs/adr-0023-uboot-mainline-handoff (491c0d9, 2026-07-25) proposed an ADR whose number was later taken by the 7-Zip ADR. ADR 0024 absorbed its conclusion (carry the fork's qts/*.h) but not its evidence or its gate. Both land here, in docs/uboot-mainline-port.md: - section 3.2a: the binary-level proof that the shipped SPL carries the fork's handoff tables (sys_mgr_init_table @0x0AAC8, IOCSR chains 0-2 @0x096B8/0x09718/0x097F0 found; mainline's absent), with the packing method so it stays reproducible, and what it does NOT prove; - section 6: a handoff-equality check that runs the same search against our built SPL -- no hardware, and it catches the one silent-brick delta a cold-boot test cannot. Version drift: Buildroot 2026.08 bundles U-Boot 2026.07, so the plan's "LATEST resolves to 2026.04" is stale. Re-pinned at 2026.07 (the DE25's tarball) after re-checking deltas 1-4 in that tarball: the missing raw-mode line, the dead TARGET_SOCFPGA_GEN5 guard (board.c:214-215), the 64-bit division in fs/exfat/time.c and FPGAPORTRST=0x1FF are all still there. The pin now rides the Buildroot bump, guarded by the resolved .config assertion plus the handoff check instead of a frozen version. Owner decisions of 2026-09-14, one rule -- mirror stock: the warm-reboot bridge behaviour is carried as the fork's C change (d6010efe50), the fork's QTS values are carried unmodified, and the fork's `mt` command is carried (23 lines) so the default environment is byte-identical to stock's and the parity check becomes a plain cmp. ADR 0024's header records the amendments; its body is untouched. docs/uboot-tasks.md is rewritten as the desk plan for BOTH boards: the DE10 U-series (wave A fans out to 13 agents, wave B integrates then verifies with a Fable pass; U6 hardware deferred indefinitely) and a new DE25 DU-series (the QSPI-audit build assertion, the now-redundant custom pin, FIT cross-tree reproducibility, TF-A signature, the section 13 decisions). Five owner questions are listed up front; nothing is started. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015UwVRTNY37gE8GQjEqvU5k --- PLAN.md | 2 +- TASKS.md | 4 +- docs/de25-nano-tasks.md | 4 + ...0024-mainline-uboot-capability-artifact.md | 18 +- docs/uboot-mainline-port.md | 180 ++++-- docs/uboot-tasks.md | 563 ++++++++++++------ 6 files changed, 559 insertions(+), 212 deletions(-) diff --git a/PLAN.md b/PLAN.md index 9741fd39..579629ed 100644 --- a/PLAN.md +++ b/PLAN.md @@ -677,7 +677,7 @@ one-line change, and CI tells us immediately if a patch stopped applying. > **[2026-07-28] This section is superseded from "Phase 5 path" onward by > [ADR 0024](docs/decisions/0024-mainline-uboot-capability-artifact.md).** The from-source -> bootloader is now built from **mainline U-Boot 2026.04**, not the 2017.03 fork, as a +> bootloader is now built from **mainline U-Boot 2026.07** (2026.04 when first decided), not the 2017.03 fork, as a > non-shipping capability artifact — the mainline port surface has been measured rather > than estimated, and it is smaller than this section assumed. **Do not add the `u-boot/` > submodule.** Design and evidence: [`docs/uboot-mainline-port.md`](docs/uboot-mainline-port.md). diff --git a/TASKS.md b/TASKS.md index cc3d1b7f..29b1bcba 100644 --- a/TASKS.md +++ b/TASKS.md @@ -1072,7 +1072,7 @@ the U-Boot half re-decided 2026-07-28 per Phase 5 adds a **full flashable SD-card image** (kernel + `linux.img` + bootloader + mr-fusion-parity payload) so a fresh card can be written without mr-fusion or the Windows SD installer. That is what remains here. **The from-source bootloader moved out of this -phase entirely** — it is now built from **mainline U-Boot 2026.04** (not the 2017.03 fork, +phase entirely** — it is now built from **mainline U-Boot 2026.07** (not the 2017.03 fork, and with no `u-boot/` submodule) and tracked in [`docs/uboot-tasks.md`](docs/uboot-tasks.md). §8's posture is unchanged: highest blast radius, everything here is opt-in, separate from `linux.img` updates, gated on a @@ -1081,7 +1081,7 @@ drilled recovery procedure. **The default channel keeps shipping the stock - [ ] **P5.1 / P5.2 — the from-source U-Boot → moved to [`docs/uboot-tasks.md`](docs/uboot-tasks.md)** **Superseded by [ADR 0024](docs/decisions/0024-mainline-uboot-capability-artifact.md).** - The from-source bootloader is now built from **mainline U-Boot 2026.04**, not the 2017.03 + The from-source bootloader is now built from **mainline U-Boot 2026.07**, not the 2017.03 fork, and it is a non-shipping capability artifact. **Do not add the `u-boot/` submodule** — the mainline build uses Buildroot's own hash-verified tarball, and `UBOOT_OVERRIDE_SRCDIR` (which the old P5.1 specified) would have silently skipped the diff --git a/docs/de25-nano-tasks.md b/docs/de25-nano-tasks.md index 20808ed2..83a0d838 100644 --- a/docs/de25-nano-tasks.md +++ b/docs/de25-nano-tasks.md @@ -410,6 +410,10 @@ Remaining, in unblock order: 3. **Owner decisions still open**: a Renovate manager for the DE25 kernel pin; upstream submission of 0101/0102; patch 0002 (audio). Hardware is expected after the owner's vacation (ordered on return). + **2026-09-14:** the DE25 U-Boot's remaining *desk* work (the §7 QSPI-audit CI check, the + redundant custom version pin now that Buildroot 2026.08 bundles 2026.07, FIT cross-tree + reproducibility, the TF-A signature, `docs/de25-uboot.md` §13's seven decisions) is planned + as the DU-series in [`docs/uboot-tasks.md`](uboot-tasks.md), alongside the DE10's. 4. **Stand up D0.4** as a `/schedule` routine. Sequencing note learned the hard way on 2026-08-21: when a research phase feeds a claim set that a diff --git a/docs/decisions/0024-mainline-uboot-capability-artifact.md b/docs/decisions/0024-mainline-uboot-capability-artifact.md index 9c3014a1..fdf28ef2 100644 --- a/docs/decisions/0024-mainline-uboot-capability-artifact.md +++ b/docs/decisions/0024-mainline-uboot-capability-artifact.md @@ -1,6 +1,22 @@ # ADR 0024 — The from-source U-Boot is built from **mainline**, not the 2017.03 fork, and exists as a non-shipping capability artifact (supersedes ADR 0017 §Decision 1–3) -**Status:** Accepted (2026-07-28) — decided by @mcfbytes +**Status:** Accepted (2026-07-28) — decided by @mcfbytes. +**Amended (2026-09-14) by @mcfbytes, recorded in [`docs/uboot-mainline-port.md`](../uboot-mainline-port.md) +(revision note) and executed by [`docs/uboot-tasks.md`](../uboot-tasks.md):** §Decision 1's pin +is now **2026.07** — Buildroot 2026.08 bundles it, so `BR2_TARGET_UBOOT_LATEST_VERSION=y` +resolves there and the pin rides the Buildroot bump, guarded by the resolved-`.config` +assertion and a new handoff-equality check rather than by a frozen version (deltas 4a–4d +re-verified in the 2026.07 tarball). §Decision 5 is amended: the fork's `mt` command is +**carried** (23 lines), so the default environment is byte-identical to stock's and the parity +check is a plain `cmp`; the `itest` rewrite is the documented fallback. The plan's open +questions on the warm-reboot bridge behaviour and the QTS scalar values are decided by one +rule — **mirror stock**: carry the fork's C change and the fork's values unmodified. The +never-merged ADR 0023 draft (branch `docs/adr-0023-uboot-mainline-handoff`; its number was +reused by the 7-Zip ADR) is folded into the plan's §3.2a/§6 and the branch deleted. The +artifact still ships nowhere; the owner has said a custom DE10-Nano bootloader will likely +never ship unless stock's stops working, and the hardware gate (§Decision 6) is deferred +indefinitely, not waived. Whether the build joins `mister_de10nano_defconfig` (amending +"What does not change", third bullet) is an open owner question — plan §9 item 9. **Supersedes:** [ADR 0017](0017-uboot-from-mister-fork-full-sd-image.md) §Decision items **1** (build the fork, not mainline), **2** (the `u-boot/` submodule pin), and **3** (behavioural parity against the fork as the acceptance test). ADR 0017 §Decision **4** diff --git a/docs/uboot-mainline-port.md b/docs/uboot-mainline-port.md index 3af42cff..9b3e4818 100644 --- a/docs/uboot-mainline-port.md +++ b/docs/uboot-mainline-port.md @@ -6,11 +6,27 @@ **Specification being reproduced:** [`docs/boot-chain.md`](boot-chain.md) — cited by section throughout; nothing from it is restated here. +> **Revision 2026-09-14.** Three things changed since this plan was written, none of them +> the verdict. (1) **The pin is 2026.07, not 2026.04:** Buildroot 2026.08 (the repo's pin) +> bundles U-Boot 2026.07 with its own hash line, so `BR2_TARGET_UBOOT_LATEST_VERSION=y` now +> resolves there — the same tarball the DE25-Nano already builds. Deltas #1–#4 were re-checked +> in the 2026.07 tarball on 2026-09-14: the missing raw-mode line, the dead `TARGET_SOCFPGA_GEN5` +> guard (`board.c:214-215`), the 64-bit `/` and `%` in `fs/exfat/time.c:129,147-148`, and +> `FPGAPORTRST=0x1FF` are all still there. (2) **The owner decided the open questions by one +> rule — mirror stock** (§9, items 2 and 3, and the `mt` question in §3.4): the warm-reboot +> bridge behaviour is carried as the fork's C change, the fork's QTS values are carried +> unmodified, and `mt` is carried as a command so the environment text is byte-identical to +> stock's. (3) **§3.2a and §6 fold in the never-merged ADR 0023 draft** (branch +> `docs/adr-0023-uboot-mainline-handoff`, commit `491c0d9`, 2026-07-25; its number was reused by +> the 7-Zip ADR and the branch was deleted on 2026-09-14): the binary-level proof that the +> shipped SPL carries the fork's handoff, and the no-hardware handoff-equality gate that proof +> makes possible. Execution: [`docs/uboot-tasks.md`](uboot-tasks.md). + --- ## 1. What this is, and what it is not -Build **mainline U-Boot 2026.04** for the MiSTer, configured to behave as close to +Build **mainline U-Boot 2026.07** (2026.04 when first written; see the revision note) for the MiSTer, configured to behave as close to identically to the stock 2017.03 fork as the evidence allows, as a **build artifact only**. **Not changing, and gated so it stays that way:** @@ -97,6 +113,50 @@ not fight future churn. And the fork and mainline **imported the board independe (fork `c7ed0834ac` 2017-03-27; mainline `6bd041f00d` 2017-04-18) — v2017.03 has no DE10-Nano at all. +### 3.2a The shipped SPL provably carries the fork's handoff — binary evidence + +*Folded in from the ADR 0023 draft (2026-07-25); reproduced there against the pinned stock +`uboot.img`, sha256 `e2d46cf9…62a64`, SPL copy 0 = bytes `0x00000`–`0x0FFFF`.* + +§3.2 is a source-level diff. It leaves one inference open: that the stock binary was built +from the fork's headers rather than from something else at the same path. The handoff is +constant tables the SPL writes to registers verbatim, so that inference is directly +checkable with no disassembler — pack each `qts/*.h` initialiser and search the SPL bytes: + +| Table | Fork bytes | Mainline bytes | +|---|---|---| +| `sys_mgr_init_table` (pinmux, `const u8[207]`) | **found @ `0x0AAC8`** | not found | +| `iocsr_scan_chain0_table` (24 × u32) | **found @ `0x096B8`** | not found | +| `iocsr_scan_chain1_table` (54 × u32) | **found @ `0x09718`** | not found | +| `iocsr_scan_chain2_table` (30 × u32) | **found @ `0x097F0`** | not found | +| `iocsr_scan_chain3_table` (524 × u32) | found @ `0x09868` | found — identical tables | +| `ac_rom_init` (36 × u32) | found @ `0x090F8` | found — identical | +| `inst_rom_init` (127 × u32) | found @ `0x094BC` | found — identical | + +Method, so this stays checkable: parse the `{…}` initialisers out of each header; pack the +`iocsr_scan_chain*_table`, `ac_rom_init` and `inst_rom_init` arrays as **little-endian u32**; +pack `sys_mgr_init_table` as **raw bytes** (it is `const u8[]` — packing it as u32 is why it +appears absent on a naive first pass); search SPL copy 0 for each byte string. Normalise +`CONFIG_HPS_*` → `CFG_HPS_*` before comparing define *names* across the two trees. + +What this does and does not establish, stated plainly: + +* It **proves** the four divergent tables in the shipped SPL are the fork's, which pins the + blob to the fork source a second, independent way (the first is boot-chain §3.1's env-blob + fingerprint). It also proves the DDR sequencer microcode has not diverged — only the data + fed to it. +* It **does not** prove the scalar `#define`s (the PLL counts, `FPGAPORTRST`): those compile + to instruction immediates and are not greppable. They rest on the source diff plus the + double pin above. +* It **does not** prove that a 2026 SPL consuming the same tables behaves as the 2017.03 SPL + did. The gen5 calibration path has nine years of upstream change; the risk is far narrower + than "unknown DDR configuration", not zero. Hardware only. + +The payoff is §6's **handoff-equality gate**: the same search run against *our* built SPL +catches the whole class of "the build silently picked up the wrong `qts/*.h`" — the failure +mode most likely to look like success on a board — with no hardware at all. That is the one +delta of §3.1 whose omission a cold-boot smoke test could not catch. + ### 3.3 Three further divergences, none previously documented * **uImage entry point.** Mainline sets `ih_ep = CONFIG_TEXT_BASE` (`0x01000040`) via @@ -118,7 +178,15 @@ DE10-Nano at all. ### 3.4 `mt` — solved, and testable on stock hardware first -Stock's `fpgacheck` uses `mt`, a MiSTer-only command (boot-chain §3.3). The replacement is +> **Decided 2026-09-14 (mirror stock; amends ADR 0024 §Decision 5):** carry the fork's `mt` +> command instead of rewriting `fpgacheck`. It is 23 lines of C in `cmd/mem.c` (fork +> `8dcc3484`, `do_mem_mt` + one `U_BOOT_CMD`; port is `cmd_tbl_t` → `struct cmd_tbl`), it +> never leaves the tree, and with it the environment text is **byte-identical to stock's** +> (21 entries, 1,150 B), so §6's environment check becomes a plain `cmp` instead of an +> allowed-delta list. The `itest` rewrite below stays documented for two reasons: it is the +> fallback if `mt` ever fails to port, and it is the free stock-hardware smoke test in §8. + +Stock's `fpgacheck` uses `mt`, a MiSTer-only command (boot-chain §3.3). The alternative is **`itest.l * == `** — verified *by execution*, not inference: a verifier built a real U-Boot 2026.04 sandbox binary and ran the rewritten `fpgacheck` through all three warm-reboot dispatch cases of boot-chain §6.1, confirming the exit-status sense matches @@ -163,9 +231,11 @@ Measured against `work/buildroot` (2026.05.1), not recalled: * `BR2_TARGET_UBOOT_LATEST_VERSION=y` is the **only** choice for which Buildroot hash-verifies the tarball (`uboot.mk:41-43` adds `BR_NO_CHECK_HASH_FOR` for every - `CUSTOM_*`). It resolves to **2026.04** (`Config.in:88`), whose sha256 - `ac7c04b8…f2fd` was confirmed byte-identical to `boot/uboot/uboot.hash`. This matches the - repo's hash-pinning convention for free. + `CUSTOM_*`). Under Buildroot 2026.05.1 it resolved to 2026.04 (sha256 `ac7c04b8…f2fd`, + confirmed byte-identical to `boot/uboot/uboot.hash`); under the repo's current Buildroot + 2026.08 it resolves to **2026.07** (`Config.in:88`, sha256 `78e8bfc3…243e` — the same line + `board/mister/de25nano/patches/uboot/uboot.hash` carries, GPG-verified against the release + signature). This matches the repo's hash-pinning convention for free. * No `.sfp` format exists in the menu (`Config.in:373-551`) → `BR2_TARGET_UBOOT_FORMAT_CUSTOM` + `_CUSTOM_NAME="u-boot-with-spl.sfp"`. **No custom make target is needed**: upstream `Kconfig:528` sets `CONFIG_BUILD_TARGET="u-boot-with-spl.sfp"` for gen5 and folds it into @@ -193,23 +263,27 @@ Measured against `work/buildroot` (2026.05.1), not recalled: ## 4. Design -**Source.** Mainline U-Boot **2026.04** via `BR2_TARGET_UBOOT_LATEST_VERSION=y` — hash-verified -by Buildroot, no submodule, no vendored tree, standing rule 1 satisfied trivially. -**Pin deliberately, do not float:** 2026.04 is the last release where §3.1 delta #2 is a -one-line fix in a *known* place. A U-Boot bump must re-verify deltas #1 and #2 against the -resolved `.config`, because #1 is a Kconfig `choice` whose default can flip with no diff in -our files. +**Source.** Mainline U-Boot **2026.07** via `BR2_TARGET_UBOOT_LATEST_VERSION=y` — hash-verified +by Buildroot, no submodule, no vendored tree, standing rule 1 satisfied trivially, and the +same tarball the DE25-Nano builds. +**The pin rides the Buildroot bump, and every bump re-opens deltas #1 and #2:** #1 is a +Kconfig `choice` whose default can flip with no diff in our files, and #2 is a carried patch +against a known line. That is why the resolved-`.config` assertion and the handoff gate of +§6 run in the build recipe, not in a checklist: a Buildroot bump that moves U-Boot fails the +build loudly instead of shipping a silently different bootloader. -**Layering**, mirroring the RT kernel's two-layer model exactly: +**Layering**, mirroring the DE25-Nano's U-Boot wiring (post-ADR 0030 layout; the original +table named a separate `configs/mister_uboot_defconfig` + `output-uboot/`, which predates the +committed-defconfig layout — see `docs/uboot-tasks.md` U1 for the placement decision): | Layer | File | Contents | |---|---|---| -| Buildroot config | `configs/mister_uboot_defconfig` | `BR2_TARGET_UBOOT*`, toolchain, no rootfs | -| U-Boot config | `board/mister/de10nano/uboot-mister.fragment` | the `CONFIG_*` deltas of §3.1/§3.3 | -| Environment | `board/mister/de10nano/uboot-mister.env` | stock's env as text, `itest`-rewritten `fpgacheck` | -| Patches | `board/mister/de10nano/uboot-patches/` | the two upstream fixes + the QTS headers | +| Buildroot config | `BR2_TARGET_UBOOT*` lines in the DE10 defconfig (U1 decides which one) | source pin, board defconfig, fragment, env file, `FORMAT_CUSTOM` name | +| U-Boot config | `board/mister/de10nano/uboot.fragment` | the `CONFIG_*` deltas of §3.1/§3.3, headed like `board/mister/de25nano/uboot.fragment` | +| Environment | `board/mister/de10nano/uboot.env` | stock's 21 entries as text, byte-identical (via `BR2_TARGET_UBOOT_DEFAULT_ENV_FILE`) | +| Patches | `board/mister/de10nano/patches/uboot/` | via `BR2_GLOBAL_PATCH_DIR`, exactly as the kernel's `patches/linux/` — the two upstream fixes, the QTS headers, `mt`, the bridge behaviour | -**Output artifact:** `output-uboot/images/u-boot-with-spl.sfp`. Never `uboot.img` (§1). +**Output artifact:** `images/u-boot-with-spl.sfp`. Never `uboot.img` (§1). **Patches carried** — each needs a CONTRIBUTING.md provenance header, using `board/mister/de10nano/linux-patches/0001-fbdev-add-MiSTer_fb-driver.patch` as the template: @@ -218,9 +292,21 @@ our files. (§3.1 #2). **Upstream this.** 2. `0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch` — `do_div()` in `fs/exfat/time.c` (§3.1 #3). **Upstream this.** -3. `0003-board-terasic-de10-nano-mister-qts-handoff.patch` — the four QTS headers (§3.2). - Never upstreamable; MiSTer is a different FPGA design on the same board. -4. Warm-reboot bridge behaviour (§3.3) — **carrier undecided**, see §9. +3. `0003-board-terasic-de10-nano-mister-qts-handoff.patch` — the four QTS headers (§3.2), + **the fork's values unmodified** (`FPGAPORTRST=0x3FFF`, both s2f clock counts, the three + pinmux bits, all 32 IOCSR words — decided 2026-09-14, mirror stock). Never upstreamable; + MiSTer is a different FPGA design on the same board. Gated by §6's handoff check. +4. `0004-cmd-mem-add-mt-memory-test-against-value.patch` — the fork's `mt` (§3.4). Provenance: + fork commit that introduced `do_mem_mt` (find it with `git log -S do_mem_mt` in + `work/U-Boot_MiSTer`). +5. `0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch` — the fork's + `d6010efe50` (Sorgelig, 2017-03-27, one line: `socfpga_bridges_reset(0)` at the end of + `arch_early_init_r`) re-expressed against mainline's `misc_gen5.c:185-210`, whose + `arch_early_init_r` ends in `socfpga_bridges_reset(1)`. **Decided 2026-09-14: carried as C, + mirror stock** — the env-script route was assessed as not equivalent (§3.3). The porting + agent must diff the fork's `socfpga_bridges_reset(0)` body (its user-mode test and which + bridges it releases) against mainline's `do_bridge_reset(1, mask)` and carry the *behaviour*, + not the line. Note that patches 1–3 are *behaviour* changes, not build fixes. ADR 0017 restricted `uboot-patches/` to build fixes only; that restriction was written for a fork build where @@ -280,18 +366,31 @@ the SPL (assert on the resolved `.config` **and** on `nm`, not on the defconfig) **Environment parity.** Extract the raw `default_environment[]` symbol (locate with `nm -S u-boot`, read `u-boot.bin` at `addr - CONFIG_TEXT_BASE`) — **not** -`u-boot-initial-env`, which is sorted and can be stale. Compare **entry by entry**, not with -`cmp`: the two `itest` rewrites add 9 bytes each, so the blob is 1,168 B where stock's is -1,150 B. A plain `cmp` is achievable only by emitting literal `mt` text that mainline cannot -execute — that trade is rejected. +`u-boot-initial-env`, which is sorted and can be stale. With `mt` carried (§3.4, decided +2026-09-14) the blob must be **byte-identical to stock's** (21 entries, 1,150 B): a plain +`cmp` against the blob extracted from the stock `uboot.img` at `0x28018`, and an entry-by-entry +report only as the diagnostic when `cmp` fails. *(Original design, kept for the fallback: with +the `itest` rewrite the blob is 1,168 B and the comparison is entry-by-entry against an +allowed-delta list of exactly the two `fpgacheck` entries.)* + +**Handoff equality** (folded in from the ADR 0023 draft, §3.2a): extract SPL copy 0 from the +built `.sfp` and from the stock `uboot.img`; pack the seven tables of §3.2a from the carried +`qts/*.h` and assert every one is found in **both** SPLs. The offsets may differ (code layout); +presence and byte-equality may not. This needs no hardware and catches the one silent-brick +delta a cold-boot test cannot. The stock blob is fetched by hash the way `release.yml` already +does (`scripts/fetch-sdcard-payload.sh`, `STOCK_UBOOT_SHA256`). + +**Command table.** Every one of stock's 69 commands present (boot-chain §3.3's list, `mt` +included); extra mainline commands are allowed and listed. **Allowed diffs** (enumerate, explain individually, or fail): version string and build timestamp; uImage `ih_ep` `0x01000040` vs `0x00000000` (§3.3 — with the mixing hazard -documented); the two `fpgacheck` entries rewritten `mt`→`itest`; total size; code layout. +documented); total size; code layout; table offsets inside the SPL. -**Forbidden diffs:** any other environment entry differing in name, value or order; a -missing command from stock's 69-entry table; any layout/offset change; a load-address -change; any SPL header field change; `USE_SECTOR` reappearing. +**Forbidden diffs:** any environment byte; a missing command from stock's 69-entry table; +any handoff table absent from or differing in the built SPL; any layout/offset change of +the four SPL copies or the uImage; a load-address change; any SPL header field change; +`USE_SECTOR` reappearing. Reconcile one doc nit while here: boot-chain §3.1 says the env blob is "20 entries, 1,149 bytes"; direct extraction gives **21 entries, 1,150 bytes** (`0x28018–0x28495` inclusive is @@ -354,12 +453,14 @@ serial output is `preloader_console_init()` at `:157`): 1. **Nothing has been run on a DE10-Nano.** Every claim above is source-level or build-artifact-level. "It boots" is a per-build claim, exactly like the RT kernel pin. -2. **The warm-reboot bridge fix (§3.3): carried C patch (`d6010efe50`) or `bridge enable` in - `fpgacheck`'s middle branch?** The env route keeps the delta out of C but was assessed as - *not equivalent*. Decide explicitly and write it down — a cold-boot test cannot catch this. -3. **Does the `GENERALIO3`/`GENERALIO4` pinmux difference actually break anything?** And is - `FPGAPORTRST=0x3FFF` genuinely required, or merely what MiSTer's Quartus project emitted? - Both are unverified on hardware and must not be reported either way. +2. ~~**The warm-reboot bridge fix (§3.3): carried C patch (`d6010efe50`) or `bridge enable` in + `fpgacheck`'s middle branch?**~~ **Decided 2026-09-14 by the owner: carried C patch, mirror + stock** (§4 patch 5). The env route was assessed as not equivalent, and a cold-boot test + cannot catch getting this wrong. +3. ~~**Does the `GENERALIO3`/`GENERALIO4` pinmux difference actually break anything?** And is + `FPGAPORTRST=0x3FFF` genuinely required?~~ **Decided 2026-09-14 by the owner: carry the + fork's values unmodified, mirror stock** (§4 patch 3). Whether either is *required* stays + unverified and must not be reported either way; it no longer affects execution. 4. **What size `0xA2` partition do mr-fusion and the Windows SD installer create?** Ours is 4 MiB; the in-the-wild value is unknown and `updateboot` `dd`s with no size check. 5. **Does mainline's exFAT driver handle the variant `mkfs.exfat -L MiSTer_Data` produces** @@ -369,6 +470,13 @@ serial output is `preloader_console_init()` at `:157`): the 16 MB budget of boot-chain §7.3. Set it to `0x4000000` and move on, but confirm. 7. **Will upstream take the two fixes?** Both are real mainline bugs. Landing them removes two carried patches from the highest-blast-radius component. -8. **Renovate:** with `LATEST_VERSION=y` there is no version string to track — does the pin +8. ~~**Renovate:** with `LATEST_VERSION=y` there is no version string to track — does the pin ride the Buildroot bump, or does it need its own manager plus a "re-verify deltas #1/#2" - gate? + gate?~~ **Answered 2026-09-14:** it rides the Buildroot bump (2026.08 already moved it to + 2026.07), and the "re-verify" gate is the U3 resolved-`.config` assertion plus the §6 + handoff check, both of which run inside the build. No separate manager. +9. **Should the DE10 U-Boot build live in the shipping `mister_de10nano_defconfig`** (one image, + one CI build, the artifact built on every PR so it cannot rot; amends ADR 0024's "gains no + `BR2_TARGET_UBOOT*` line") **or in its own defconfig and output tree** (isolated, manual CI + lane, a second toolchain build)? Owner decision; `docs/uboot-tasks.md` U1 recommends the + former. diff --git a/docs/uboot-tasks.md b/docs/uboot-tasks.md index f81fb8d9..58dff965 100644 --- a/docs/uboot-tasks.md +++ b/docs/uboot-tasks.md @@ -1,200 +1,419 @@ -# U-Boot — execution tasks +# U-Boot — desk plan and execution tasks, both boards -Execution contract for the mainline U-Boot capability artifact. **Design and evidence live -in [`docs/uboot-mainline-port.md`](uboot-mainline-port.md); the decision lives in -[ADR 0024](decisions/0024-mainline-uboot-capability-artifact.md).** This file is only the -ordered work. +Execution contract for the from-source bootloaders. **Design and evidence live in +[`docs/uboot-mainline-port.md`](uboot-mainline-port.md) (DE10-Nano) and +[`docs/de25-uboot.md`](de25-uboot.md) (DE25-Nano); the decisions live in +[ADR 0024](decisions/0024-mainline-uboot-capability-artifact.md) and +[ADR 0029](decisions/0029-de25-implementation-path.md).** This file is only the ordered work. Split out of `TASKS.md` deliberately: that file and `PLAN.md` are already large, and the U-Boot narrative is self-contained. `TASKS.md` P5.1/P5.2 redirect here and carry no U-Boot detail of their own. **P5.3 (`sdcard.img`) and P5.4 remain in `TASKS.md`** — they are about the SD image, which keeps embedding the stock blob and is not affected by this -work. +work. The DE25's card and kernel tracks stay in `docs/de25-nano-tasks.md`. -Conventions (model routing, `[HW]`/`[NET]` flags, sizes, standing rules) are inherited -verbatim from [`TASKS.md` §0](../TASKS.md). +--- + +## The one-line summary + +Build mainline U-Boot **2026.07** for the DE10-Nano so that it behaves **like stock's 2017.03 +fork in every way the evidence can check**, as a build artifact that ships nowhere; finish the +DE25-Nano's `u-boot.itb` desk work; and do **all of it without a board.** + +## The owner's direction (2026-09-14) + +* **The DE10-Nano will very likely never ship a custom U-Boot.** Stock's works. The target is + a from-source bootloader that is *as close as possible to what stock does*, kept buildable + "just in case" and so that the whole image — bootloader included — exists from source. +* **Where the plan had a choice, mirror stock.** That decided the warm-reboot bridge behaviour + (carry the fork's C change), the QTS scalars (carry the fork's values), and `mt` (carry the + command, so the environment is byte-identical). `docs/uboot-mainline-port.md` §3.4, §4, §9. +* **No hardware session is planned for either board.** The DE10 hardware gate (U6) is deferred + indefinitely, not waived; the DE25 board is on order with no date. Every task below is desk + work with a desk-checkable acceptance test. Nothing below is allowed to say "verified on + hardware". + +## Conventions + +Inherited verbatim from [`TASKS.md` §0](../TASKS.md): **[HAIKU]** mechanical work, +**[SONNET]** implementation, **[OPUS]** analysis and hard debugging; escalate one tier after two +failed acceptance runs; **[NET]** needs downloads; sizes S (< half day), M (a day-ish), L +(multi-day). Two additions used by the DE25 waves and here: + +* **[FABLE]** — adversarial review only. Reads the artifact and the claims, tries to break + them, never edits. Used where a wrong result would look like success on a board. +* **Fan-out** — tasks in the same wave share no inputs and run concurrently. A wave's + integration task runs alone. Wave A is 13 agents; with the wave-B/C tails the whole plan + is under the session's 15-agent workflow guideline if wave B waits for wave A. + +Every patch carries a full CONTRIBUTING §2 provenance header, modelled on +`board/mister/de10nano/linux-patches/0001-fbdev-add-MiSTer_fb-driver.patch`. Every claim a +task records is tagged **[V]** (observed in this build or read from source, with the path) +or **[U]** (unverified, missing input named) — the `docs/de25-uboot.md` discipline. + +**Reference material already on disk** (outside the repo; do not commit any of it): +`work/U-Boot_MiSTer` = the fork at `8dcc3484`; `work/uboot-proper.bin` = stock's U-Boot proper +(252,933 B); the stock `uboot.img` is fetched by hash through +`scripts/fetch-sdcard-payload.sh` (`STOCK_UBOOT_SHA256`); `dl/uboot/u-boot-2026.07.tar.bz2` = +the pinned tarball; `/mnt/source/uboot-mainline/verify-qts/qtsdiff.py` = the QTS header +parser/differ; `/mnt/source/uboot-mainline/env-layout-parity/mister.env.txt` = a stock-env +text file that already built byte-identical; `/mnt/source/uboot-research/parity-check/*.py` += Python reference implementations of the structural checks (SPL header, uImage, env scan, +command table). The Python is reference, not deliverable: repo scripts are POSIX `sh` +(`scripts/check-zimage-dtb.sh` house style), with one sanctioned exception named in U4b. --- -> **Layout note (2026-09-11, ADR 0030):** file and target names in this document predate the -> refactor that replaced the fragment stacks with committed `configs/mister_*_defconfig` files and -> Kconfig profiles, moved the RT kernel into `package/linux-rt` (`output/build/linux-rt-*`, no -> `output-rt/`), the stage-1 initramfs into `package/mister-initramfs`, and retired the -> kernel-variant CI matrix, `scripts/list-kernel-variants.sh`, `check-kernel-defconfig-sync.sh` -> and `scripts/lib/board-expectations.sh`. Read the paths here as of their date; the current -> layout is README "Building it yourself" and `docs/ci.md` "The pipeline today". +## Owner decisions needed before kickoff -## The one-line summary +Answer in one line each; silence takes the recommendation. -Build mainline U-Boot **2026.04** for the DE10-Nano, configured to behave like stock's -2017.03 fork, as a **build artifact that ships nowhere**. The default channel keeps -shipping the stock `uboot.img` byte-identical. Five deltas are mandatory; three of them are -silent-brick if omitted. +1. **Where does the DE10 U-Boot build live?** *(plan §9 item 9)* + **Recommended: in `configs/mister_de10nano_defconfig`**, so there is one image, one CI + build, and the artifact is built on every PR and cannot rot. Cost: ~2 minutes per CI build + (host-openssl + U-Boot; the tarball rides the `dl/` cache). It amends ADR 0024's "gains no + `BR2_TARGET_UBOOT*` line" bullet, which was written to keep the artifact out of the + *release*, and `mk-release.sh` stages named files only, so nothing changes in what ships. + Alternative: its own defconfig + `O=output-uboot` + a manual `workflow_dispatch` lane — a + second toolchain build and a new Makefile target ADR 0030 just removed the like of. +2. **DE25: drop the custom U-Boot pin?** Buildroot 2026.08 bundles the same 2026.07 with a + hash line, so `BR2_TARGET_UBOOT_CUSTOM_VERSION` + `patches/uboot/uboot.hash` are redundant. + **Recommended: switch to `LATEST_VERSION=y`** so both boards share one pin that rides the + Buildroot bump, guarded by DU2's resolved-config check (the reason the custom pin was chosen + — "fail closed on a bump" — is then served by the check, not the pin). Alternative: keep the + deliberate pin and its hash file. TF-A stays custom either way (Buildroot's latest is v2.12). +3. **`mt` carried, not rewritten** — decided by the mirror-stock rule; this is the veto point. +4. **Outbound mail.** U7 and DU5 *prepare* upstream submissions; nothing is sent to the U-Boot + list without an explicit go. +5. **`docs/de25-uboot.md` §13 items 1–6** — DU4 lists a recommendation per item. --- -## Tasks - -- [ ] **U0 — ADR + redirects** — [HAIKU] — Size S — Depends: — - Land [ADR 0024](decisions/0024-mainline-uboot-capability-artifact.md); annotate ADR 0017 - as superseded-in-part (§Decision 1–3 only; 4 and 5 stand); redirect `PLAN.md` §8 and - `TASKS.md` P5.1/P5.2 here **without** copying the narrative into them; add both new docs - to the README documentation map. - **Done when:** no file in the repo still instructs a reader to build the fork or to add a - `u-boot/` submodule, and `docs/uboot-mainline-port.md` is reachable from the README. - -- [ ] **U1 — [NET] Buildroot skeleton** — [SONNET] — Size M — Depends: U0 - `configs/mister_uboot_defconfig` (Buildroot-config layer) + `make uboot` / `make uboot-clean` - building into `O=output-uboot`. Model the Makefile target on **`installer`** - (`Makefile:604-623`), **not** `rt` — `rt`'s `merge_config.sh` step and module-tree staging - are irrelevant here, since the U-Boot fragment is applied by Buildroot itself and the - artifact is standalone. Do not forget the `$(UBOOT_DEFCONFIG): ;` empty rule - (`Makefile:113-115` pattern) that keeps the catch-all target-forwarding rule from - intercepting it. - Required Buildroot options, all verified against `work/buildroot` 2026.05.1: - `BR2_TARGET_UBOOT=y`, `BR2_TARGET_UBOOT_LATEST_VERSION=y` (the **only** choice that - hash-verifies the tarball — `uboot.mk:41-43` — resolving to 2026.04 per `Config.in:88`), - `BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y`, `BR2_TARGET_UBOOT_USE_DEFCONFIG=y` with +## DE10-Nano — the U-series + +### Wave A — fan out (13 agents, no inter-dependencies) + +- [x] **U0 — ADR + redirects** — DONE (ADR 0024 landed 2026-07-28; amended 2026-09-14 with the + version drift, the mirror-stock decisions and the ADR 0023 fold-in). + +- [ ] **U1 — Buildroot wiring** — [SONNET] — Size M — Depends: owner decision 1 + Assuming decision 1's recommendation: add to `configs/mister_de10nano_defconfig` + `BR2_TARGET_UBOOT=y`, `BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y`, + `BR2_TARGET_UBOOT_LATEST_VERSION=y` (2026.07 under Buildroot 2026.08 — the only source choice + Buildroot hash-verifies), `BR2_TARGET_UBOOT_USE_DEFCONFIG=y`, `BR2_TARGET_UBOOT_BOARD_DEFCONFIG="socfpga_de10_nano"`, - `BR2_TARGET_UBOOT_FORMAT_CUSTOM=y` + `_CUSTOM_NAME="u-boot-with-spl.sfp"` (no `.sfp` - entry exists in the format menu, `Config.in:373-551`), `BR2_TARGET_UBOOT_NEEDS_OPENSSL=y`. - **`BR2_TARGET_UBOOT_ALTERA_SOCFPGA_IMAGE_CRC` must stay OFF** — mainline already wraps the - SPL with `mkimage -T socfpgaimage` (`scripts/Makefile.xpl:436-441`) and enabling it - double-wraps via host `mkpimage` (`uboot.mk:561-579`). No custom make target is needed: - upstream `Kconfig:528` sets `CONFIG_BUILD_TARGET="u-boot-with-spl.sfp"` for gen5 and - `uboot.mk:66` already calls `all`. - **Done when:** `make uboot` from a clean tree produces - `output-uboot/images/u-boot-with-spl.sfp`, and `make uboot-clean` removes `output-uboot/`. - -- [ ] **U2 — The five deltas** — [OPUS] — Size L — Depends: U1 - Author the U-Boot-config layer `board/mister/de10nano/uboot-mister.fragment` (wired via - `BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES`, `Config.in:140-145`), the environment - `board/mister/de10nano/uboot-mister.env`, and the patches in - `board/mister/de10nano/uboot-patches/` (picked up by `BR2_TARGET_UBOOT_PATCH`, - `Config.in:103-113` / `uboot.mk:342-354`; honours a `series` file, applies at fuzz zero). - Head the fragment with the same "which layer is which" note `package/linux-rt/Config.in:19-21` - uses for the kernel. Every patch carries a full CONTRIBUTING §2 provenance header — use - `board/mister/de10nano/linux-patches/0001-fbdev-add-MiSTer_fb-driver.patch` as the - template. Per ADR 0024 §Decision 3, behaviour changes **are** permitted here; each must be - enumerated in `docs/uboot-mainline-port.md` §4. - The five deltas, with full evidence in `docs/uboot-mainline-port.md` §3.1: - (a) `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y`; - (b) the dead-hook guard fix at `arch/arm/mach-socfpga/board.c:214`; - (c) `CONFIG_FS_EXFAT=y` + the `do_div()` fix in `fs/exfat/time.c`; - (d) the fork's four `board/terasic/de10-nano/qts/*.h` with `s/CONFIG_HPS_/CFG_HPS_/`; - (e) the environment, via `CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE` (`env/Kconfig:771`). - Also apply the stock-parity config deltas: `CONFIG_ENV_OFFSET=0x200` / - `CONFIG_ENV_SIZE=0x1000` (so `updateboot`'s sector-1 wipe keeps working — boot-chain §5 - Consequence (b)), `CONFIG_BOOTDELAY=0`, `CONFIG_AUTOBOOT_KEYED=y`, - `CONFIG_AUTOBOOT_STOP_STR="\e"`, `CONFIG_SYS_BOOTM_LEN=0x4000000`, - `CONFIG_TEXT_BASE=0x01000040`. Disable `CONFIG_TOOLS_MKEFICAPSULE` (host-only tool; it is - the sole reason a `gnutls` host dependency appears) and drop the unused SPL SPI/QSPI stack - (`SPL_SPI`, `SPL_SPI_FLASH_SUPPORT`, `SPL_DM_SPI`, `SPL_SPI_LOAD`), which takes SPL - headroom from 12.6 % to 30.4 % under the hard 64 KiB BootROM ceiling. - Use `itest.l * == ` for the `fpgacheck` rewrite. **Do not use `setexpr` + - `test -eq`** — `env_set_hex` writes bare lowercase hex and `test -eq` parses base-0, so - the comparison is false forever and every warm reboot silently takes the cold path. - **Done when:** the resolved `.config` assertion of U3 passes and `nm` proves - `board_spl_mmc_get_uboot_raw_sector` is linked into the SPL. - -- [ ] **U3 — Resolved-`.config` assertion in the build recipe** — [SONNET] — Size S — Depends: U2 - Assert on the **resolved `.config`**, never the defconfig or the fragment: the SPL - raw-mode selector is a Kconfig `choice` whose default can flip on a U-Boot bump with no - diff in our files, and `merge_config.sh` only *warns* when a fragment symbol is dropped - while `olddefconfig` silently discards symbols whose dependencies fail. This is the exact - hazard the `rt` recipe already guards against — model the check on `Makefile:504-521`, - including its single-tree uniqueness guard. - Assert at minimum: `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y`, + `BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES=".../board/mister/de10nano/uboot.fragment"`, + `BR2_TARGET_UBOOT_DEFAULT_ENV_FILE=".../board/mister/de10nano/uboot.env"` (Buildroot sets + `CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE` + the absolute path — `uboot.mk:414-422`), + `BR2_TARGET_UBOOT_NEEDS_OPENSSL=y`, `BR2_TARGET_UBOOT_FORMAT_CUSTOM=y` + + `_CUSTOM_NAME="u-boot-with-spl.sfp"`; **leave `BR2_TARGET_UBOOT_SPL` and + `BR2_TARGET_UBOOT_ALTERA_SOCFPGA_IMAGE_CRC` unset** (the latter double-wraps the SPL — + plan §3.6). Patches come from `BR2_GLOBAL_PATCH_DIR/uboot/`, already + `board/mister/de10nano/patches/`, exactly like `patches/linux/`; retire the empty + `board/mister/de10nano/uboot-patches/.gitkeep` and its four references (`PLAN.md:548,734,971`, + `board/mister/de10nano/linux-rt.fragment:49`, `docs/init-parity.md:134`). Ship a **stub** + `uboot.fragment` (header comment only, in the `de25nano/uboot.fragment` style, including its + merge_config comment-line editing rule) and a **stub** `uboot.env` (stock's `bootcmd` line + only) so the build is green before wave B fills them. Keep the defconfig canonical + (`make savedefconfig`, then `scripts/check-defconfigs.sh` — every line must survive into + the resolved `.config`). Extend `scripts/ci-tests.sh`: assert + `images/u-boot-with-spl.sfp` exists, assert **no `images/uboot.img`** and no `.sfp` in the + release stage (`mk-release.sh` stages named files; assert it anyway — plan §1's naming rule). + If decision 1 goes the other way: the same lines in `configs/mister_de10nano_uboot_defconfig` + with the DE10 toolchain block and no rootfs, plus a `uboot` target in the Makefile modelled + on `de25`. + **Done when:** `make` from a configured tree emits `images/u-boot-with-spl.sfp`; `ci-tests.sh` + passes; `check-defconfigs.sh` green; CI build time delta recorded in the PR. + +- [ ] **U2a — Patches 0001/0002, the two upstream fixes** — [SONNET] — Size S — Depends: — + `0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch`: `arch/arm/mach-socfpga/board.c:214-215` + `CONFIG_TARGET_SOCFPGA_{ARRIA10,GEN5}` → `CONFIG_ARCH_SOCFPGA_{ARRIA10,GEN5}` (fallout of + `62f7a94602`). `0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch`: `fs/exfat/time.c:129,147-148` + via `do_div()`/`div_u64`-style helpers so `CONFIG_FS_EXFAT` links on 32-bit ARM. Build-test + both **outside Buildroot** against `dl/uboot/u-boot-2026.07.tar.bz2` with the repo's cross + toolchain (`output/host/bin/arm-buildroot-linux-gnueabihf-*`) and `socfpga_de10_nano_defconfig` + + `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y` + `CONFIG_FS_EXFAT=y`. Provenance + headers cite the upstream commits and say "to be submitted upstream (U7)". + **Done when:** both apply at `-F0`; `nm spl/u-boot-spl` shows `board_spl_mmc_get_uboot_raw_sector` + **[V]** (absent without 0001); the `FS_EXFAT` build links **[V]**. + +- [ ] **U2b — Patch 0003, the fork's QTS handoff** — [SONNET] — Size S — Depends: — + `0003-board-terasic-de10-nano-mister-qts-handoff.patch`: the four `board/terasic/de10-nano/qts/*.h` + from `work/U-Boot_MiSTer@8dcc3484`, `s/CONFIG_HPS_/CFG_HPS_/`, **values unmodified** (decided: + `FPGAPORTRST=0x3FFF`, both s2f clock counts, the three pinmux bits, the 32 IOCSR words). + Verify with `/mnt/source/uboot-mainline/verify-qts/qtsdiff.py`: patched tree vs fork = **zero** + differences after the rename; patched tree vs pristine 2026.07 = exactly plan §3.2's list. + Provenance: fork `dadd1c8978` ("Use SPL config from DE10 FB project") and plan §3.2/§3.2a. + **Done when:** the two `qtsdiff.py` runs say exactly that **[V]**, recorded in the patch header. + +- [ ] **U2c — Patch 0004, the fork's `mt`** — [SONNET] — Size S — Depends: — + `0004-cmd-mem-add-mt-memory-test-against-value.patch`: port `do_mem_mt` + its `U_BOOT_CMD` + from `work/U-Boot_MiSTer/cmd/mem.c:158-180,1259-1263` (`cmd_tbl_t` → `struct cmd_tbl`, + `simple_strtoul` → `hextoul`); find the introducing fork commit with `git log -S do_mem_mt` + for the header. Test **by execution** in a U-Boot sandbox build (`make sandbox_defconfig`, + host gcc — plan §3.4 did this for `itest`): `mw.l`, then `mt.l` equal and unequal, and prove + the hush exit-status sense with `if mt.l …; then echo T; else echo F; fi` for both cases — + this is the whole `fpgacheck` contract (boot-chain §6.1). + **Done when:** the sandbox transcript is in the patch header **[V]**; the command table gains + `mt` with stock's help string. + +- [ ] **U2d — Patch 0005, the warm-reboot bridge behaviour** — [OPUS] — Size M — Depends: — + The fork's `d6010efe50` (Sorgelig, 2017-03-27) adds one line, `socfpga_bridges_reset(0);`, at + the end of `arch_early_init_r` in the 2017.03 `misc.c`. Mainline's `misc_gen5.c:185-210` + `arch_early_init_r` ends in `socfpga_bridges_reset(1)`. The line does not port; the + **behaviour** must. Read the fork's `socfpga_bridges_reset(int)` body (`misc.c:437-464` in the + fork — its FPGA-user-mode test and which bridges and `l3regs` remap bits it releases) and + mainline's `do_bridge_reset()` / `socfpga_bridges_reset()` in `misc_gen5.c`, and write the + smallest patch that gives mainline the fork's post-reset state when the fabric is already + configured. Write the equivalence argument **into the patch header and plan §3.3**, including + what it does at cold boot (fabric unconfigured) — it must be a no-op there. Hardware cannot + check this; the argument is the deliverable. + **Done when:** the patch builds; `objdump -d u-boot` shows `arch_early_init_r` reaching the + release path **[V]**; the argument survives U9. + +- [ ] **U2e — The fragment** — [SONNET] — Size M — Depends: — + `board/mister/de10nano/uboot.fragment`, headed like the DE25's (which layer is which; the + merge_config comment-line rule). Every line a delta on `socfpga_de10_nano_defconfig`, each with + its plan §3.1/§3.3 citation: `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y`; + `CONFIG_FS_EXFAT=y`; **environment mirrors stock** — `CONFIG_ENV_IS_IN_MMC=y`, + `CONFIG_ENV_OFFSET=0x200`, `CONFIG_ENV_SIZE=0x1000` (so `updateboot`'s sector-1 wipe keeps + the "effective env = defaults + `u-boot.txt`" invariant, boot-chain §5 Consequence (b)); + `CONFIG_BOOTDELAY=0`, `CONFIG_AUTOBOOT_KEYED=y`, `CONFIG_AUTOBOOT_STOP_STR="\e"`; + `CONFIG_SYS_BOOTM_LEN=0x4000000`; `CONFIG_TEXT_BASE=0x01000040`; `# CONFIG_TOOLS_MKEFICAPSULE + is not set` (drops the `gnutls` host dep); the unused SPL SPI/QSPI stack off (`SPL_SPI`, + `SPL_SPI_FLASH_SUPPORT`, `SPL_DM_SPI`, `SPL_SPI_LOAD` — SPL headroom 12.6 % → 30.4 %, plan + §3.5). Do **not** strip mainline's extra commands to chase stock's 69: the parity check + tolerates extras, and every removal is a chance to drop something stock's env calls. Resolve + it with `merge_config.sh` + `olddefconfig` against the pristine tarball and diff the resolved + `.config` against the defconfig's resolution: every fragment symbol must survive. + **Done when:** the resolved `.config` carries every symbol U3 will assert **[V]**; the + merge report shows zero dropped symbols. + +- [ ] **U2f — The environment file** — [SONNET] — Size S — Depends: — + `board/mister/de10nano/uboot.env`: stock's 21 entries, verbatim, in U-Boot's `.env` text + format, with `mt` kept (U2c). Start from + `/mnt/source/uboot-mainline/env-layout-parity/mister.env.txt` (it already built + byte-identical once); the reference text is boot-chain §3.1/§4. **The acceptance test is a + byte compare**: run the file through U-Boot's own `scripts/env2string.awk` flow (a sandbox or + the 2026.07 tree's `include/generated/env.txt` rule) and `cmp` the resulting + `default_environment[]` bytes against the blob at `0x28018` of the stock `uboot.img` + (1,150 B, 21 entries — plan §6's corrected constant; **including the malformed entry-15 + fingerprint** of boot-chain §3.1, which the text format must reproduce exactly). Record in the + file header that `CONFIG_ENV_SIZE=0x1000` (U2e) bounds it. + **Done when:** `cmp` is silent **[V]**. + +- [ ] **U4a — `scripts/check-uboot-parity.sh`** — [OPUS] — Size M — Depends: — + House style of `scripts/check-zimage-dtb.sh`: POSIX `sh`, `set -eu`, header naming the + contract and citing boot-chain sections, `Usage: check-uboot-parity.sh `, + `Exit: 0 = pass, 1 = contract violation, 2 = usage/IO error`, `note()`/`ok()`/`bad()`. Structural + (hard fail): four byte-identical 64 KiB SPL copies; Altera header at `+0x40` (validation + `0x31305341`, `length_u32`, checksum); legacy uImage magic at `0x40000` with recomputed header + and payload CRCs; `load=0x01000040`; total size closes the file; SPL size against + `tools/spl_size_limit`. Environment: locate `default_environment[]` in the built binary + (`nm -S u-boot` → `addr - CONFIG_TEXT_BASE` into `u-boot.bin`) and in stock's (`0x28018`), + **`cmp` them**; on mismatch print the entry-by-entry diff as the diagnostic. Command table: + every stock command name present (extract both tables the way + `/mnt/source/uboot-research/parity-check/cmdtbl.py` does), extras listed. Allowed diffs + (version string, build timestamp, `ih_ep` `0x01000040` vs `0`, total size, code layout) are + named in the output. **Self-test fixtures, as a `scripts/test-uboot-checks.sh` in the `scripts/test-*.sh` + style:** stock-vs-stock must pass (the `.sfp` argument may be a stock `uboot.img`), + and a mutated copy (one env byte flipped; one SPL copy differing; uImage CRC wrong) must fail + with the right message. Runs today against the stock blob alone; U2g points it at the build. + **Done when:** fixtures pass/fail as listed **[V]**; shellcheck clean. + +- [ ] **U4b — `scripts/check-uboot-handoff.sh`** — [SONNET] — Size M — Depends: — + The plan §3.2a/§6 handoff-equality gate. Inputs: the carried `qts/*.h` (from the patch or the + patched tree), the built `.sfp`, the stock `uboot.img`. Pack the seven tables (u32 LE for the + `iocsr_scan_chain*`, `ac_rom_init`, `inst_rom_init` arrays; **raw bytes** for + `sys_mgr_init_table`) and assert each is found in SPL copy 0 of **both** images; offsets are + reported, not compared. **Sanctioned style exception:** the packing is done by a small + `python3` helper (`scripts/lib/qts-tables.py`, parser lifted from `qtsdiff.py`) called from + the POSIX `sh` wrapper — Buildroot already requires host `python3`, so it adds no dependency; + say so in the header. Fixtures: stock `uboot.img` must pass with the fork's headers (expected + offsets are plan §3.2a's table) and **fail** with pristine mainline's headers — that failing + run is the whole point, keep its transcript in `docs/verification/uboot-mainline.md`. + **Done when:** both fixture runs behave **[V]**; shellcheck clean. + +- [ ] **U8 — Docs debt** — [HAIKU] — Size S — Depends: — + boot-chain §3.1's "20 entries, 1,149 bytes" → **21 entries, 1,150 bytes** (plan §6; the ELF + symbol is 1,151); README phase-5 row (`README.md:167`) and the documentation-map rows + (`README.md:1022-1023`) say "2026.07, in progress, ships nowhere"; `PLAN.md` §8 / `TASKS.md` + P5.1-P5.2 pointers checked. No narrative copied anywhere. + **Done when:** `git grep "2026.04"` finds only historical measurements in the plan. + +- [ ] **U7-prep — Upstream submissions, drafted** — [SONNET] — Size S — Depends: U2a + Two `git format-patch` mails with cover text in U-Boot list style (`scripts/get_maintainer.pl` + for the socfpga custodian Cc), one per fix, ready in `docs/verification/uboot-upstream/` or + a PR comment. **Not sent** — owner decision 4. After a send, the carried patches' provenance + headers gain the list URL. + **Done when:** the two mails exist and `checkpatch.pl` is clean. + +### Wave B — integrate (one agent), then verify (three in parallel) + +- [ ] **U2g — Integration build** — [SONNET] — Size M — Depends: U1, U2a–U2f + Drop the five patches into `board/mister/de10nano/patches/uboot/` (with a `series` file if + ordering matters), replace the stubs with the real fragment and env, `make uboot-dirclean + uboot-rebuild`. Record: applies at `-F0`; `nm` shows the hook; `spl/u-boot-spl.bin` size + and headroom (plan §3.5); `u-boot-with-spl.sfp` size; the resolved `.config`'s values for + every U3 symbol. Start `docs/verification/uboot-mainline.md` (house style of + `docs/verification/sdcard-payload.md`): every allowed diff enumerated and explained, both + U4 scripts' transcripts, the U4b negative run. + **Done when:** `check-uboot-parity.sh` and `check-uboot-handoff.sh` pass against the build + and the stock blob **[V]**; the verification doc exists. + +- [ ] **U3 — Resolved-`.config` assertion inside the build** — [SONNET] — Size S — Depends: U2g + Not a checklist: an `UBOOT_POST_BUILD_HOOKS` append from `external.mk` (the same trick that + fixed dhcpcd's `CONF_OPTS`) that greps `$(@D)/.config` and fails the build naming the delta + and its plan §3.1 row. Assert at minimum + `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y`, `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION_TYPE=0xa2`, - `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR` **not** set, `CONFIG_FS_EXFAT=y`, - `CONFIG_ENV_OFFSET=0x200`, `CONFIG_TEXT_BASE=0x01000040`, `CONFIG_SPL_PAD_TO=0x10000`, - `CONFIG_ARCH_SOCFPGA_GEN5=y`. - **Done when:** deleting any one delta from the fragment makes `make uboot` fail loudly, - with a message that names the delta and cites its `docs/uboot-mainline-port.md` §3.1 row. - -- [ ] **U4 — `scripts/check-uboot-parity.sh`** — [OPUS] — Size M — Depends: U3 - House style of `scripts/check-zimage-dtb.sh`: POSIX `sh`, `set -eu`, a header naming the - contract and citing boot-chain sections, `Usage:`, `Exit: 0 = pass, 1 = contract - violation, 2 = usage/IO error`, `note()`/`ok()`/`bad()`. Takes the `.sfp` path as an - argument. Full check list in `docs/uboot-mainline-port.md` §6. - Structural (hard fail): four byte-identical 64 KiB SPL copies; Altera header at `+0x40` - (validation `0x31305341`, `length_u32`, checksum); legacy uImage magic at `0x40000` with - recomputed header and payload CRCs; `load=0x01000040`; total size closes the file exactly; - SPL size against `tools/spl_size_limit`. - Environment: extract the raw `default_environment[]` symbol (`nm -S u-boot`, read - `u-boot.bin` at `addr - CONFIG_TEXT_BASE`) — **not** `u-boot-initial-env`, which is sorted - and can be stale. Compare **entry by entry** against stock's 21 entries; a plain `cmp` is - impossible and that trade is rejected (ADR 0024 §Decision 5). - While here, reconcile a `boot-chain.md` §3.1 nit: it records the env blob as "20 entries, - 1,149 bytes"; direct extraction gives **21 entries, 1,150 bytes** (`0x28018–0x28495` - inclusive is `0x47E`; the ELF symbol is 1,151). Byte-identity was never in doubt — the - constant is. - **Done when:** it passes against the U2 artifact, every allowed diff is enumerated with a - per-item explanation in `docs/verification/uboot-mainline.md`, and an unexplained delta - fails the run. - -- [ ] **U5 — CI** — [SONNET] — Size S — Depends: U4 - New `.github/workflows/uboot.yml`. **`workflow_dispatch` is the primary trigger**, plus - `pull_request` scoped by `paths:` to the five U-Boot inputs. Cite `reproducibility.yml:26-32` - in the header for the manual-first posture and `lint.yml:26-36` for the path scoping, and - state the budget reasoning explicitly — Actions minutes are a real constraint here. This - is **not** a kernel variant: it must not join `build.yml`'s matrix (which is derived from - `configs/mister_*.fragment` by `scripts/list-kernel-variants.sh`) and must not drag in the + `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR` **unset**, `CONFIG_FS_EXFAT=y`, + `CONFIG_ENV_IS_IN_MMC=y`, `CONFIG_ENV_OFFSET=0x200`, `CONFIG_ENV_SIZE=0x1000`, + `CONFIG_TEXT_BASE=0x01000040`, `CONFIG_SPL_PAD_TO=0x10000`, `CONFIG_ARCH_SOCFPGA_GEN5=y`, + `CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE=y`, `CONFIG_CMD_MEMORY=y`. Same hook runs + `nm` for `board_spl_mmc_get_uboot_raw_sector`. Guard it on the DE10 defconfig (the DE25 has + its own, DU2). This is what makes the Buildroot-bump-moves-U-Boot case fail loudly. + **Done when:** deleting any one fragment line makes `make uboot-rebuild` fail with a message + naming it **[V]** (do it for three of them and record the output). + +- [ ] **U5 — CI** — [SONNET] — Size S — Depends: U2g + With decision 1's recommendation there is **no new workflow**: the build already runs U-Boot, + `verify-image` already runs `ci-tests.sh`. Add to `ci-tests.sh` a "U-Boot" section that runs + both U4 scripts against `images/u-boot-with-spl.sfp` and the stock blob fetched by hash + (`fetch-sdcard-payload.sh`'s path, `dl/`-cached; if the owner wants `ci-tests.sh` to stay + offline, put the two runs in `release.yml` next to `check-sdcard.sh`, which already has the + blob). Extend `scripts/lint-kernel-patches.sh` (the `lint-config` job) to `patches/uboot/`. If decision 1 went the + other way: a `workflow_dispatch`-first `uboot.yml`, `pull_request` scoped by `paths:` to the + U-Boot inputs (`reproducibility.yml:26-32` posture, `lint.yml:26-36` scoping), never in the kernel gate. - **Done when:** a manual run builds the artifact and runs `check-uboot-parity.sh`; a PR - touching only kernel files does not trigger it. - -- [ ] **U6 — [HW] Hardware matrix and recovery drill** — human + [OPUS] — Size L — Depends: U5 - **Gate. Nothing is flashed until every item below is done, in order.** - 1. U1–U5 green; `docs/verification/uboot-mainline.md` complete. - 2. **Measure the real card's `0xA2` partition size** (`sfdisk -l /dev/mmcblk0` on the test - MiSTer at `192.168.0.161`) and confirm it exceeds the built image. `updateboot` `dd`s - with no size check, and our own `genimage-sdcard.cfg:132-135` value (4 MiB) says - nothing about what mr-fusion or the Windows SD installer create. - 3. **Free de-risking step:** smoke-test the `itest`-rewritten `fpgacheck` on hardware under - the **stock** bootloader via `u-boot.txt`. `itest` exists in the stock 2017.03 binary, - so the single largest environment rewrite can be validated with zero brick exposure. - 4. A second SD card known-good, plus a written recovery procedure **executed once from an - actually-bricked state**. Recovery is always "rewrite the card from another machine": - the DE10-Nano has no HPS-attached flash (`nand@ff900000` / `spi@ff705000` are - `status = "disabled"` in `work/stock.dts:742-770`) and nothing programs fuses, so - "brick" here never means a dead board. - 5. Serial console attached for the first boot. - Matrix: cold boot to menu; `u-boot.txt` override honoured; **warm-reboot core handoff** - (load a core → Main_MiSTer warm-reboots → fabric still live); an `updateboot` flash + - env-wipe cycle leaving the board bootable. - **Done when:** the matrix and a successful real recovery drill are logged in - `docs/testlogs/uboot-mainline.md`. - -- [ ] **U7 — [NET] Send the two upstream fixes** — [SONNET] — Size S — Depends: U2 - Both are genuine mainline bugs and landing them removes carried patches from the - highest-blast-radius component in the system. - (a) `arch/arm/mach-socfpga/board.c:214` — `CONFIG_TARGET_SOCFPGA_GEN5` → - `CONFIG_ARCH_SOCFPGA_GEN5` (and the Arria10 sibling), fallout from the rename in - `62f7a94602`. Also fixes in-tree Arria10. - (b) `fs/exfat/time.c:129,147-149` — 64-bit division via `do_div()` so `CONFIG_FS_EXFAT` - links on 32-bit ARM. No 32-bit board in tree enables it, which is why nobody has hit it. - **Done when:** both are posted to the U-Boot list with the socfpga custodian on Cc, and - the carried patches in `uboot-patches/` cite the submission in their provenance headers. + **Done when:** a PR touching only U-Boot inputs runs the checks; the Actions-minutes delta is + in the PR description. + +- [ ] **U9 — Adversarial review** — [FABLE] — Size M — Depends: U2g (runs alongside U3/U5) + Reads, does not edit. Against plan §8's failure taxonomy, patch by patch and symbol by symbol: + can the built `.sfp` present as a boot on a board while being wrong? Specifically: the U2d + bridge argument at cold boot and at warm reboot; whether the env `cmp` could pass while the + *effective* env differs (`CONFIG_ENV_*` defaults, `env import` behaviour, `hush` version + differences since 2017); whether anything in the SPL could touch `0x1FFFF000`–`0x1FFFFFFF` + (the mailbox, boot-chain §6.3) before U-Boot proper; whether the `ih_ep`/`ih_load` mixing + hazard (plan §3.3) is documented where a future flasher would read it. Findings go to + `docs/verification/uboot-mainline.md` as a section; each is fixed or recorded [U] with the + missing input named. + **Done when:** no finding is left unlabelled. + +### Deferred — not in this plan + +- [ ] **U6 — [HW] Hardware matrix and recovery drill** — deferred indefinitely (owner, + 2026-09-14). The text of the gate stands unchanged in plan §8 for the day stock's `uboot.img` + "turns into a pumpkin": measured `0xA2` partition size; the `itest` fallback smoke-tested under + stock via `u-boot.txt`; second card + drilled recovery; serial console; cold boot, `u-boot.txt` + override, **warm-reboot core handoff**, an `updateboot` cycle. Until then, nothing this plan + builds is flashed, published, or named `uboot.img`. +- [ ] **U7 — send** — on the owner's go only. + +--- + +## DE25-Nano — the DU-series (desk half of D2.2/D2.4) + +Everything `docs/de25-uboot.md` §12 tags **[U]** needs the board and stays [U]. What follows is +the rest. + +### Wave A — fan out (runs with the DE10's wave A) + +- [ ] **DU1 — One U-Boot pin for both boards** — [HAIKU] — Size S — Depends: owner decision 2 + If recommended: in `configs/mister_de25nano_defconfig` replace + `BR2_TARGET_UBOOT_CUSTOM_VERSION=y` + `_VALUE="2026.07"` with `BR2_TARGET_UBOOT_LATEST_VERSION=y`; + `git rm board/mister/de25nano/patches/uboot/uboot.hash` (its header's reason — "Buildroot's + hash file has no line for our tarball" — is no longer true; say so in the commit); keep the + defconfig canonical (`make O=output-de25 savedefconfig`, `scripts/check-defconfigs.sh`). TF-A keeps its custom pin and hash. **Prove nothing moved:** `make O=output-de25 + uboot-dirclean uboot-rebuild` before and after, `sha256sum images/u-boot.itb` identical + (`BR2_REPRODUCIBLE=y`, per-tree identity is [V] in `de25-uboot.md` §5b). + **Done when:** the two hashes match **[V]**; `check-defconfigs.sh` green; `de25-uboot.md` §2's + version table updated. + +- [ ] **DU2 — The QSPI-write audit as a build assertion** — [SONNET] — Size S — Depends: — + `de25-uboot.md` §13 item 7 and `de25-boot-chain.md` §5's final bullet: the rule exists only + as sentences. Encode §7's table as an `UBOOT_POST_BUILD_HOOKS` append in `external.mk` + guarded on the DE25 defconfig: every §7 symbol **absent or unset** in the resolved `.config` + (`ENV_IS_IN_UBI`, `ENV_IS_IN_SPI_FLASH`, `ENV_IS_IN_NAND`, `ENV_IS_IN_MMC`, `CADENCE_QSPI`, + `DM_SPI_FLASH`, `SPI_FLASH*`, `CMD_SF*`, `CMD_MTD*`, `CMD_UBI*`, `CMD_NAND`, `MTD`, `DM_MTD`, + `MTD_UBI`, `MTD_RAW_NAND`, `HANDOFF`, `BLOBLIST`), `ENV_IS_IN_FAT=y`, and `strings u-boot.itb` + free of `sf probe`, `ubi part`, `mtdparts`. Plus the Linux side of `de25-boot-chain.md` §7 + row 11 in `ci-tests.sh`'s DE25 section: no `fw_env.config` in the DE25 rootfs names an MTD + device. Cite the two docs in the hook's error text. + **Done when:** re-adding `CONFIG_ENV_IS_IN_UBI=y` to the fragment fails the build naming + `de25-uboot.md` §7 **[V]**; `de25-boot-chain.md` §5's final bullet and its §9.3 open-concern row flip to + [V] with the commit cited. + +- [ ] **DU3 — FIT reproducibility across clean trees** — [SONNET] — Size S — Depends: DU1 + `de25-uboot.md` §5b tags cross-tree identity [U]. Two clean `O=` trees on one commit, same + toolchain, `sha256sum` of `u-boot.itb` and `bl31.bin`; if they differ, `dumpimage -l` and + `diffoscope`-style section comparison to name the source (build path, timestamp, + `SOURCE_DATE_EPOCH`). Local only — do not add a DE25 leg to `reproducibility.yml` (Actions + budget); record the method so the D2.8 release lane can repeat it. + **Done when:** §5b's [U] becomes [V] or a named cause. + +- [ ] **DU4 — §13 decisions dispositioned** — [HAIKU] — Size S — Depends: owner decision 5 + Recommendations, one per `de25-uboot.md` §13 item: (1) keep the carried mtdids/mtdparts + guard patch — one line, inert argument not needed; (2) `HANDOFF` off + declared 1 GiB as + shipped; (3) `&mmc` at 25 MHz as shipped; (4) no seeded `uboot.env`; (5) keep `FS_EXFAT=y` + (ADR 0029 D11 made the DE10-style two-stage layout the target, and its data partition is + exFAT); (6) stay on `DISTRO_DEFAULTS` until first boot, but add a one-line note naming + `BOOTSTD_DEFAULTS` as the migration target in the fragment; (7) = DU2. Write each disposition + into §13 with the date; nothing else changes. + **Done when:** §13 has a disposition line per item. + +- [ ] **DU5-prep — Upstream the mtdids guard** — [SONNET] — Size S — Depends: — + `0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch` (`de25-uboot.md` §8 calls it + upstreamable, not submitted). Same shape as U7-prep: a list-ready mail, `checkpatch.pl` + clean, **not sent** without the owner's go. + **Done when:** the mail exists. + +- [ ] **DU6 — TF-A tag signature** — [SONNET] [NET] — Size S — Depends: — + §12 says the v2.15.0 tag's signing key was on no reachable keyserver. Try the + trustedfirmware.org release announcement / the project's published key file / a WKD lookup; + if found, verify the tag and record the fingerprint in + `board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash`'s header the + way `uboot.hash` did; if not, record **where** it was looked for and that the hash is TOFU. + **Done when:** the hash file header says one or the other with the evidence. + +### Wave B + +- [ ] **DU7 — Boot-path re-review** — [FABLE] — Size S — Depends: DU1, DU2 (may run as part of U9) + The wave-2 `fable` boot-path pass was against the custom-pin build. Re-run its checklist + (`de25-nano-tasks.md` "What the boot-path fable pass established") against the DU1 artifact: + no QSPI command or driver in U-Boot proper, unsigned-crc32 FIT at the factory SPL's addresses, + nothing persistent written. Add: does DU2's hook actually fire on the DE25 tree and not the + DE10's? + **Done when:** the pass is logged in `de25-nano-tasks.md` under a wave-4 heading. --- -## Open questions +## Dependency graph -Tracked in `docs/uboot-mainline-port.md` §9. The two that most affect execution order: +``` +owner decisions 1, 2, 5 ──┐ + ▼ +Wave A (parallel): U1 U2a U2b U2c U2d U2e U2f U4a U4b U8 U7-prep │ DU1 DU2 DU4 DU5-prep DU6 + │ │ + ▼ ▼ +Wave B: U2g (integration, alone) DU3 (after DU1) + │ + ┌───────────┼───────────┐ + ▼ ▼ ▼ + U3 U5 U9 [FABLE] ── DU7 [FABLE] + │ + (owner go) U7 send / DU5 send +Deferred: U6 [HW] +``` -1. **The warm-reboot bridge fix** — carried C patch (`d6010efe50`) or `bridge enable` in - `fpgacheck`'s middle branch? The env route keeps the delta out of C but was assessed as - *not* equivalent. **Decide before U2 and write the reasoning down**; a cold-boot test - cannot catch getting this wrong. -2. **Is `FPGAPORTRST=0x3FFF` genuinely required**, or merely what MiSTer's Quartus project - emitted? Unverified on hardware, and must not be asserted either way. Same for the - `GENERALIO3`/`GENERALIO4` pinmux difference. +Wave A: 16 agents if every task is its own agent; U8, DU4 and DU5-prep are small enough to +share one Haiku/Sonnet, bringing it to 13. Waves B and C are 1 + 3 + 1. No agent needs a board, +a serial cable, or network beyond the pinned tarball and the hash-fetched stock blob. --- ## Status -Nothing below U0 has been started. **Nothing in this plan has been run on a DE10-Nano** — -every claim in the design doc is source-level or build-artifact-level. "It boots" is a -per-build claim, exactly like the RT kernel pin. +**2026-09-14:** plan written; nothing below U0 started. Both boards' U-Boot work is desk-only by +the owner's direction, and "it boots" remains a per-build claim that only hardware can make. From 933a2d23072e188d487a43ac1215bed9749a0798 Mon Sep 17 00:00:00 2001 From: "Michael C. Ferguson" Date: Mon, 14 Sep 2026 13:27:29 -0500 Subject: [PATCH 2/3] uboot: wave A of the desk plan -- DE10 wiring, checks, DE25 pin + QSPI audit hook Executes docs/uboot-tasks.md wave A (13 tasks, each independently verified). DE10-Nano (ships nowhere; builds as part of the regular image): - configs/mister_de10nano_defconfig: BR2_TARGET_UBOOT via Buildroot's LATEST_VERSION pin (2026.07), socfpga_de10_nano + our fragment + our env text file, FORMAT_CUSTOM u-boot-with-spl.sfp. NEEDS_GNUTLS is TEMPORARY: the pristine board defconfig resolves TOOLS_MKEFICAPSULE=y (host tool, needs gnutls); the real fragment turns it off and U2g drops the line again. Stub uboot.fragment / uboot.env hold the build green until the real ones land (staged, verified, integrated by U2g). - scripts/check-uboot-parity.sh + test-uboot-parity.sh: structural parity (4x SPL, Altera header, uImage CRCs, load address), the default environment cmp'd against stock's 1,150-byte blob, command table coverage. 39 fixture assertions pass against the stock blob. - scripts/check-uboot-handoff.sh + scripts/lib/qts-tables.py + test-uboot-handoff.sh: the ADR 0023 handoff-equality gate. The fork's seven QTS tables are found in stock's SPL copy 0 at the plan's offsets; pristine mainline's headers fail on the four diverged tables. - docs/uboot-mainline-port.md 3.3: the warm-reboot bridge argument. - uboot-patches/.gitkeep retired; patches live in patches/uboot/. DE25-Nano: - configs/mister_de25nano_defconfig: LATEST_VERSION instead of the custom 2026.07 pin; patches/uboot/uboot.hash removed (Buildroot 2026.08 carries the same hash line). u-boot.itb byte-identical before/after (f4e5c924...). TF-A stays on its custom v2.15.0 pin. - external.mk: MISTER_UBOOT_DE25_QSPI_AUDIT, an UBOOT_POST_BUILD_HOOKS hook guarded on the DE25 board defconfig, fails the build if the fragment asks for, or the resolved .config carries, any docs/de25-uboot.md section 7 QSPI symbol, if ENV_IS_IN_FAT is not y, or if u-boot.itb carries a QSPI-write command string beyond the one documented gate-closed linux_qspi_enable line. Negative test (ENV_IS_IN_UBI=y in the fragment) fails naming section 7. - scripts/ci-tests.sh: the Linux-side half (no fw_env.config naming an MTD/UBI device in the DE25 rootfs). - arm-trusted-firmware.hash: the v2.15.0 tag signature is verified against the tagger's self-published key; trust anchor stated. Staged outside the repo for U2g: the five DE10 patches, the real fragment and env (byte-identical to stock's blob), and drafted-not-sent upstream mails for the two U-Boot fixes and the DE25 mtdids guard. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015UwVRTNY37gE8GQjEqvU5k --- PLAN.md | 6 +- board/mister/de10nano/linux-rt.fragment | 2 +- board/mister/de10nano/uboot-patches/.gitkeep | 0 board/mister/de10nano/uboot.env | 16 + board/mister/de10nano/uboot.fragment | 27 + .../arm-trusted-firmware.hash | 149 +++- .../mister/de25nano/patches/uboot/uboot.hash | 44 -- configs/mister_de10nano_defconfig | 8 + configs/mister_de25nano_defconfig | 11 +- docs/init-parity.md | 2 +- docs/uboot-mainline-port.md | 36 + external.mk | 161 ++++ scripts/check-uboot-handoff.sh | 126 ++++ scripts/check-uboot-parity.sh | 689 ++++++++++++++++++ scripts/ci-tests.sh | 42 ++ scripts/lib/qts-tables.py | 238 ++++++ scripts/test-uboot-handoff.sh | 162 ++++ scripts/test-uboot-parity.sh | 370 ++++++++++ 18 files changed, 2011 insertions(+), 78 deletions(-) delete mode 100644 board/mister/de10nano/uboot-patches/.gitkeep create mode 100644 board/mister/de10nano/uboot.env create mode 100644 board/mister/de10nano/uboot.fragment delete mode 100644 board/mister/de25nano/patches/uboot/uboot.hash create mode 100755 scripts/check-uboot-handoff.sh create mode 100755 scripts/check-uboot-parity.sh create mode 100755 scripts/lib/qts-tables.py create mode 100755 scripts/test-uboot-handoff.sh create mode 100755 scripts/test-uboot-parity.sh diff --git a/PLAN.md b/PLAN.md index 579629ed..137d0b2f 100644 --- a/PLAN.md +++ b/PLAN.md @@ -545,7 +545,7 @@ mister-linux/ │ │ ├── 0012-hid-fanatec.patch │ │ ├── 0013-hid-flydigi-vader.patch │ │ └── 0020-usb-storage-blacklist-realtek-cdrom.patch -│ ├── uboot-patches/ # empty in v1; P5: build fixes ONLY, never +│ ├── patches/uboot/ # empty in v1; P5: build fixes ONLY, never │ │ # behaviour changes (ADR 0017) │ ├── rootfs-overlay/ │ │ ├── etc/init.d/S??… # BusyBox init scripts (parity with stock) @@ -731,7 +731,7 @@ so the full-image build stays reproducible even if the upstream branch moves or * Buildroot builds it from the submodule: `BR2_TARGET_UBOOT` + `UBOOT_OVERRIDE_SRCDIR` pointing at `u-boot/` (via `BR2_PACKAGE_OVERRIDE_FILE`), starting from the fork's own `MiSTer_defconfig`; output `u-boot-with-spl.sfp`, renamed `uboot.img`. The -`uboot-patches/` directory mirrors the kernel model but is reserved for **build fixes +`patches/uboot/` directory mirrors the kernel model but is reserved for **build fixes only** — a 2017 codebase may need coaxing under a 2026 toolchain — never behaviour changes. * **A byte-identical rebuild is impossible and is not the goal** (`docs/boot-chain.md` §3.2: compiled-in non-UTC timestamp, exact 2020 Arm toolchain). The default Downloader @@ -968,7 +968,7 @@ criteria — lives in `TASKS.md`. |Rootfs exceeds the image budget|Medium|Grow `linux.img` to 512 MiB; audit assumptions about 400 MB.| |Boot regression from the initramfs|Low|Measurable; budget in §11.| |Bricking via U-Boot|**Critical**|Deferred to P5, opt-in, recovery documented **and drilled**. ADR 0017 shrinks the exposure: P5 builds the same source commit stock already runs, not a mainline port.| -|**[ADR 0017] 2017-era U-Boot fails to build under a 2026 toolchain**|Medium|Expected and contained: build fixes only in `uboot-patches/` (provenance-documented, never behaviour changes); worst case, pin the Arm GNU 10.2-2020.11 toolchain the stock binary used (`docs/boot-chain.md` §3.2).| +|**[ADR 0017] 2017-era U-Boot fails to build under a 2026 toolchain**|Medium|Expected and contained: build fixes only in `patches/uboot/` (provenance-documented, never behaviour changes); worst case, pin the Arm GNU 10.2-2020.11 toolchain the stock binary used (`docs/boot-chain.md` §3.2).| |Community fragmentation / abandonment|**High**|Be strictly drop-in. Ship a working artifact before making an argument. **If nobody will commit to tracking 6.18.y stable for years, do not start.**| That last one is not a joke. A stale fork is worse than no fork, because it splits the diff --git a/board/mister/de10nano/linux-rt.fragment b/board/mister/de10nano/linux-rt.fragment index d4340e23..42f2089e 100644 --- a/board/mister/de10nano/linux-rt.fragment +++ b/board/mister/de10nano/linux-rt.fragment @@ -46,7 +46,7 @@ CONFIG_UIO_PDRV_GENIRQ=y # # This fragment is also the RT line's ONLY bootargs surface. There is no # per-line U-Boot environment in this repo: U-Boot is stock and unmodified for -# both kernels (uboot-patches/ is empty; docs/rt-beta-kernel.md §5), and +# both kernels (patches/uboot/ is empty; docs/rt-beta-kernel.md §5), and # /media/fat/linux/u-boot.txt selects only WHICH zImage loads — it carries no # command-line content. The real cmdline arrives via U-Boot's stock # `setenv bootargs` plus its /chosen FDT fixup (docs/boot-chain.md §4.2). diff --git a/board/mister/de10nano/uboot-patches/.gitkeep b/board/mister/de10nano/uboot-patches/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/board/mister/de10nano/uboot.env b/board/mister/de10nano/uboot.env new file mode 100644 index 00000000..b4dd1658 --- /dev/null +++ b/board/mister/de10nano/uboot.env @@ -0,0 +1,16 @@ +# uboot.env -- DE10-Nano default environment text file. +# +# Consumed via BR2_TARGET_UBOOT_DEFAULT_ENV_FILE, which Buildroot wires to +# CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE + CONFIG_ENV_DEFAULT_ENV_TEXT_FILE +# (uboot.mk:414-422 in the pinned Buildroot tree). U-Boot's own reader +# (env/Kconfig: "lines containing key=value pairs, blank lines and lines +# beginning with # are ignored") strips this header before the build, same +# as mkenvimage. +# +# STUB, wired by U1 (docs/uboot-tasks.md): only stock's first environment +# entry so the file is non-empty and the build is green. The real content -- +# stock's remaining 20 entries verbatim, including the malformed entry-15 +# fingerprint, byte-identical per docs/boot-chain.md §3.1 and +# docs/uboot-mainline-port.md §6 -- is U2f's deliverable, integrated by U2g. +# Do not add entries here. +bootcmd=mw 0xff709004 0x800; run mmcload; run mmcboot diff --git a/board/mister/de10nano/uboot.fragment b/board/mister/de10nano/uboot.fragment new file mode 100644 index 00000000..3316861f --- /dev/null +++ b/board/mister/de10nano/uboot.fragment @@ -0,0 +1,27 @@ +# uboot.fragment -- DE10-Nano deltas on mainline's socfpga_de10_nano_defconfig. +# +# Applied by Buildroot on top of BR2_TARGET_UBOOT_BOARD_DEFCONFIG +# ("socfpga_de10_nano") via BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES: +# merge_config.sh, then olddefconfig. Every line will be a DELTA -- if a value +# matches what the board defconfig already resolves to, restate it only where +# the restatement is the point (see `board/mister/de25nano/uboot.fragment`'s +# environment block for the pattern). +# +# STUB, wired by U1 (docs/uboot-tasks.md): this file carries no CONFIG_ lines +# yet so BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES resolves to a real, mergeable +# (empty) fragment and `make` is green before the real deltas land. The real +# content -- SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE, FS_EXFAT, the +# ENV_IS_IN_MMC/ENV_OFFSET/ENV_SIZE block, BOOTDELAY/AUTOBOOT, SYS_BOOTM_LEN, +# TEXT_BASE, dropping TOOLS_MKEFICAPSULE, and the SPL SPI/QSPI headroom cut -- +# is U2e's deliverable (docs/uboot-mainline-port.md §3.1/§3.3/§3.5/§3.6), +# integrated by U2g. Do not add deltas here. +# +# EDITING RULE, learned the hard way (carried from the DE25's fragment): +# never start a COMMENT line with "# CONFIG_" unless it really is +# an "is not set" directive. support/kconfig/merge_config.sh greps this file +# for every symbol it is changing and echoes the matching lines back at you, +# so a prose line that begins that way turns the merge report into nonsense +# -- and a line that happens to end in "is not set" would be parsed as a real +# directive (SED_CONFIG_EXP2 is: caret, hash, space, the symbol name, then +# the literal words "is not set" anchored to end of line). Indent such prose, +# or lead with a word. diff --git a/board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash b/board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash index 708f8d4d..66719d72 100644 --- a/board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash +++ b/board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash @@ -24,37 +24,134 @@ # both change, and the build will fail closed until they are re-derived. That # is the intended behaviour; do not "fix" it by loosening the check. # -# WHERE THIS HASH COMES FROM -- read before changing it, and note honestly -# what it is and is not. +# WHERE THIS HASH COMES FROM -- read before changing it. # -# It is a TRUST-ON-FIRST-USE value, unlike the U-Boot hash next door. # trustedfirmware.org publishes no release tarballs and no signed checksum -# manifest, so there is no signed artifact to transcribe from. What exists is -# an annotated, PGP-SIGNED git tag, and the chain that value rests on is: +# manifest, so there is no signed artifact to transcribe the way uboot.hash +# next door does. What exists is an annotated, PGP-SIGNED git tag -- and as of +# 2026-09-14 that signature VERIFIES. (This file used to say the key was +# unfindable and the value trust-on-first-use. That was wrong; see "WHERE THE +# KEY CAME FROM" below for where it actually lives and how far it goes.) +# +# The chain the value below rests on: # # 1. Buildroot cloned https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git # over TLS and checked out tag v2.15.0 -# (dl/arm-trusted-firmware/git, verified 2026-09-02). +# (dl/arm-trusted-firmware/git, 2026-09-02, re-checked 2026-09-14). # 2. That tag is annotated object 9ad327a8d124ce82002614c23e33992d4de6f7cf, # tagged "Trusted Firmware-A release v2.15.0" by Olivier Deprez -# , and it points at commit -# da738d5eae93af342fdc4995dd3c05acb4c9d757. -# 3. The SAME commit id was obtained from an INDEPENDENT clone made in a -# separate working directory on the same day, so the value below is not -# anchored on a single fetch. -# 4. The tag carries a PGP signature by RSA key -# 5D6F896043ADEFDF7B76BFAA89C08CFDB8673E0C. That signature could NOT be -# verified: the key is published on neither keys.openpgp.org nor -# keyserver.ubuntu.com (both returned 404 on 2026-09-02). Say so plainly -# rather than implying a verification that did not happen. -# 5. sha256 of the tarball Buildroot then generated from that checkout. -# -# TO UPGRADE THIS TO A VERIFIED CHAIN: obtain the TF-A release key through a -# channel you trust (the trustedfirmware.org project pages, or an Arm-published -# keyring), import it, run `git tag -v v2.15.0` in dl/arm-trusted-firmware/git, -# and record the result here in place of item 4. -# -# ON A VERSION BUMP: re-derive both lines. The cheap, honest way is to set this -# hash to all zeros, run `make de25`, and transcribe the "got" value the -# check-hash error prints -- after re-doing steps 1-4 for the new tag. +# , pointing at commit +# da738d5eae93af342fdc4995dd3c05acb4c9d757. Two further independent +# clones -- a separate working directory on 2026-09-02, and a fresh +# GitHub-mirror clone (github.com/TrustedFirmware-A/trusted-firmware-a) +# on 2026-09-14 -- produced the same two object ids. +# 3. The tag's PGP signature VERIFIES against RSA-4096 key +# +# 5D6F 8960 43AD EFDF 7B76 BFAA 89C0 8CFD B867 3E0C +# +# (short id 89C08CFDB8673E0C, created 2024-11-21, no expiry, uid +# "Olivier Deprez "). Transcript, run in +# dl/arm-trusted-firmware/git on 2026-09-14 against a keyring holding +# only that key, and reproduced identically in the fresh clone: +# +# $ git tag -v v2.15.0 +# gpg: Signature made Fri May 29 03:08:11 2026 CDT +# gpg: using RSA key 5D6F896043ADEFDF7B76BFAA89C08CFDB8673E0C +# gpg: Good signature from "Olivier Deprez " [unknown] +# gpg: WARNING: This key is not certified with a trusted signature! +# gpg: There is no indication that the signature belongs to the owner. +# Primary key fingerprint: 5D6F 8960 43AD EFDF 7B76 BFAA 89C0 8CFD B867 3E0C +# ... exit status 0 +# +# The WARNING is about local trust (the key is not certified in this +# keyring), not about the signature: "Good signature" is the verdict. +# 4. The tarball hashed below is the signed tag's tree. Extracting +# arm-trusted-firmware-v2.15.0-git4.tar.gz and running +# `diff -r -q --exclude=.git ` reports +# exactly one line -- "Only in : contrib" -- and no differing +# file. contrib/ holds four UNINITIALISED submodule gitlinks (mode +# 160000: contrib/libtl, contrib/libeventlog, contrib/libtpm, +# contrib/mbed-tls) that `git archive` never packs. Every regular file is +# identical. Note the consequence: this hash covers the TF-A tree proper +# and NO submodule content. +# 5. sha256 of that tarball, recomputed 2026-09-14, is still the value +# below, and dl/arm-trusted-firmware/git still resolves +# v2.15.0^{commit} to da738d5eae93af342fdc4995dd3c05acb4c9d757. +# +# WHERE THE KEY CAME FROM, AND HOW FAR TO TRUST IT -- the honest part. +# +# The key is NOT on a keyserver and NOT in a project keyring. Looked for and +# not found on 2026-09-14: +# +# - keys.openpgp.org: GET /vks/v1/by-fingerprint/5D6F8960...B8673E0C +# -> HTTP 404, body "No key found for fingerprint +# 5D6F896043ADEFDF7B76BFAA89C08CFDB8673E0C". +# - keyserver.ubuntu.com: GET +# /pks/lookup?op=get&search=0x5D6F8960...B8673E0C&options=mr +# -> HTTP 404, body "Not Found". +# - WKD, advanced method: openpgpkey.arm.com does not resolve (NXDOMAIN, +# `getent hosts` exit 2) -- arm.com has no WKD subdomain delegation. +# - WKD, direct method: https://arm.com/.well-known/openpgpkey/hu/ +# whq38t37sw5p3bk4y1en5k6dkdawy4j7?l=olivier.deprez -> HTTP 302 to +# https://www.arm.com/.well-known/openpgpkey/hu/whq38t37sw5p3bk4y1en5k6dkdawy4j7 +# -- arm.com's generic bare-domain redirect, which also drops the ?l= +# parameter; a compliant WKD response is the key bytes, never a redirect +# -- and that target then times out having received zero bytes. +# The hu-part is the zbase32 SHA-1 of the lowercased local part +# "olivier.deprez", cross-checked two ways: an implementation that +# reproduces the RFC test vector joe.doe -> +# iy9q119eutrkn8s1mk4r39qejnbu3n5q, and `gpg --with-wkd-hash --list-keys`, +# which prints whq38t37sw5p3bk4y1en5k6dkdawy4j7@arm.com. (An earlier +# revision of this header quoted a different hu-part; it was not the WKD +# hash of any plausible local part and has been struck.) +# - The v2.15.0 release announcement +# (https://www.trustedfirmware.org/blog/tf-a-v2_15/) names no signing key, +# fingerprint or verification step. +# - The project's own tree: `git grep -l "BEGIN PGP PUBLIC KEY"` in the +# v2.15.0 checkout returns nothing, and neither docs/process/security.rst +# nor docs/about/release-information.rst carries a key or fingerprint. +# +# It IS published -- self-published by the tagger, on his GitHub account: +# +# https://github.com/odeprez.gpg +# https://api.github.com/users/odeprez/gpg_keys +# +# Fetched 2026-09-14 (sha256 of the ASCII blob +# 376b5e967b33832c31e89cb131147228aa209670e1be098c4f010d41b4f5a225). The blob +# carries three keys for uid "Olivier Deprez "; the +# 2020 and 2022 ones expired in 2022 and 2024, and 5D6F8960...B8673E0C is the +# current one. The API reports it as key_id 89C08CFDB8673E0C, created +# 2024-11-21, "expires_at": null, "can_sign": true, bound to the +# GitHub-VERIFIED email olivier.deprez@arm.com. +# +# What that buys, and what it does not: +# + The signature verifies (step 3) and the tarball is that tag's tree +# (step 4). Whoever holds this key made the v2.15.0 release tag. +# + The account is corroborated: GitHub attributes Olivier Deprez's TF-A +# commits to login "odeprez" (checked ba3972a2, 3cbc3f96, fa5070b0 -- all +# three report "author": {"login": "odeprez"}), and he is a listed +# maintainer in docs/about/maintainers.rst. GitHub's own API also reports +# tag object 9ad327a8... as {"verified": true, "reason": "valid", +# "verified_at": "2026-05-29T09:11:19Z"}. +# - The trust anchor is GitHub account control plus TLS to github.com. It is +# NOT a trustedfirmware.org keyring, NOT a keyserver or WKD hit, and NOT a +# web-of-trust path: `gpg --list-sigs` shows the key carries only +# self-signatures, so no third party vouches for it. +# +# So this is a SIGNATURE-VERIFIED chain anchored on a self-published key: one +# step weaker than uboot.hash's next door (a maintainer key pulled by full +# fingerprint from keys.openpgp.org), one step stronger than the +# trust-on-first-use value this file recorded before 2026-09-14. Do not move +# the wording in either direction without re-running the commands above. +# +# TO STRENGTHEN IT FURTHER: a trustedfirmware.org-published release keyring, +# or the key appearing on keys.openpgp.org under the verified arm.com address, +# would replace the GitHub anchor. Neither exists today. +# +# ON A VERSION BUMP: re-derive both lines, and re-do steps 1-5 for the new tag +# -- `git tag -v` included, which now has a key to check against (import +# https://github.com/odeprez.gpg, or whatever key the new tagger publishes; +# confirm the fingerprint above still matches). The cheap way to get the hash +# value itself is to set it to all zeros, run `make de25`, and transcribe the +# "got" value the check-hash error prints. sha256 b807d8bf190b7c46b4260729b4bbe2f7df83cd1086edcb181d66595ae6598d23 arm-trusted-firmware-v2.15.0-git4.tar.gz diff --git a/board/mister/de25nano/patches/uboot/uboot.hash b/board/mister/de25nano/patches/uboot/uboot.hash deleted file mode 100644 index 68c7dc92..00000000 --- a/board/mister/de25nano/patches/uboot/uboot.hash +++ /dev/null @@ -1,44 +0,0 @@ -# U-Boot tarball hash for the pinned BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE. -# -# WHY THIS FILE EXISTS -# Buildroot ships boot/uboot/uboot.hash, but it only ever carries the ONE -# version that release bundles — 2026.04 in Buildroot 2026.05.2. We pin 2026.07 -# (docs/de25-uboot.md), so that file has no line for our tarball. -# -# That is not a "no hash file" WARNING, it is a HARD FAILURE, and the -# difference matters. support/download/check-hash counts hash FILES and hash -# LINES separately: with at least one hash file present but no line matching -# the downloaded basename it exits 3 ("No hash found for ..."), and -# BR2_DOWNLOAD_FORCE_CHECK_HASHES=y (set in the defconfig) empties -# BR_NO_CHECK_HASH_FOR so nothing can excuse it. So the build fails closed -# until this file exists — which is the intended behaviour, and the reason a -# U-Boot version bump MUST update this file in the same commit. -# -# Buildroot finds it because pkg-patch-hash-dirs (package/pkg-utils.mk:163) -# searches $(BR2_GLOBAL_PATCH_DIR)// as well as the package directory, and -# the defconfig points BR2_GLOBAL_PATCH_DIR at board/mister/de25nano/patches. -# Same mechanism as the kernel's linux.hash next door; that file's header has -# the long version. -# -# WHERE THIS HASH COMES FROM — read before changing it. -# Fetched 2026-09-02 from the U-Boot release server: -# -# https://ftp.denx.de/pub/u-boot/u-boot-2026.07.tar.bz2 -# https://ftp.denx.de/pub/u-boot/u-boot-2026.07.tar.bz2.sig -# -# and the detached signature was VERIFIED, not merely downloaded: -# -# gpg: Signature made Mon Jul 6 19:02:11 2026 CDT -# gpg: using EDDSA key F3CEA8743D60E0192F9B4C7A2BE2A0F50ABFE40A -# gpg: issuer "trini@konsulko.com" -# gpg: Good signature from "Thomas Rini " -# -# Thomas Rini is the U-Boot maintainer and the signer of every U-Boot release -# tarball; the key was retrieved from keys.openpgp.org by full fingerprint. -# The sha256 below is of the tarball that signature covers. This is therefore a -# SIGNED provenance chain, not a trust-on-first-use value — do not downgrade it -# to "locally computed" on the next bump without re-doing the verification. -# -# The gpl-2.0.txt line is for `legal-info` only, and Buildroot's own -# boot/uboot/uboot.hash already carries it; it is not repeated here. -sha256 78e8bfc382fe388f9b55aa1daf8c563522a037779b5d4c349d1415e381f1243e u-boot-2026.07.tar.bz2 diff --git a/configs/mister_de10nano_defconfig b/configs/mister_de10nano_defconfig index 48cde535..2604e65f 100644 --- a/configs/mister_de10nano_defconfig +++ b/configs/mister_de10nano_defconfig @@ -57,6 +57,14 @@ BR2_TARGET_ROOTFS_EXT2=y BR2_TARGET_ROOTFS_EXT2_4=y BR2_TARGET_ROOTFS_EXT2_SIZE="512M" BR2_TARGET_ROOTFS_EXT2_MKFS_OPTIONS="-U 71916572-439f-448e-b8d8-12b0a032fa56 -E hash_seed=9afc615c-c310-4e03-ada9-613522e83ae6 -b 4096 -O has_journal,ext_attr,resize_inode,dir_index,filetype,extent,64bit,flex_bg,sparse_super,large_file,huge_file,dir_nlink,extra_isize,metadata_csum,^metadata_csum_seed,^orphan_file" +BR2_TARGET_UBOOT=y +BR2_TARGET_UBOOT_BOARD_DEFCONFIG="socfpga_de10_nano" +BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/uboot.fragment" +BR2_TARGET_UBOOT_DEFAULT_ENV_FILE="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/uboot.env" +BR2_TARGET_UBOOT_NEEDS_OPENSSL=y +BR2_TARGET_UBOOT_NEEDS_GNUTLS=y +BR2_TARGET_UBOOT_FORMAT_CUSTOM=y +BR2_TARGET_UBOOT_FORMAT_CUSTOM_NAME="u-boot-with-spl.sfp" BR2_PACKAGE_HOST_KMOD_XZ=y BR2_PACKAGE_MISTER_FIRMWARE=y BR2_PACKAGE_MISTER_DRIVERS=y diff --git a/configs/mister_de25nano_defconfig b/configs/mister_de25nano_defconfig index bd9eb6c7..3148fc95 100644 --- a/configs/mister_de25nano_defconfig +++ b/configs/mister_de25nano_defconfig @@ -14,6 +14,14 @@ # # Passwordless root (BR2_TARGET_GENERIC_ROOT_PASSWD "") is Buildroot's default # and so leaves no line here; it is deliberate (ADR 0015). +# +# U-Boot rides Buildroot's own BR2_TARGET_UBOOT_LATEST_VERSION pin (2026.07 +# under Buildroot 2026.08) instead of a custom version + a board-local hash +# file, same as the DE10 (docs/uboot-tasks.md DU1, 2026-09-14): Buildroot +# 2026.08 bundles the same 2026.07 tarball with its own hash line, so the +# custom pin and board/mister/de25nano/patches/uboot/uboot.hash were +# redundant. TF-A stays on its own custom pin -- Buildroot's latest tops out +# at v2.12, which has no Agilex 5 platform. BR2_aarch64=y BR2_cortex_a76_a55=y BR2_KERNEL_HEADERS_7_1=y @@ -45,9 +53,6 @@ BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_VERSION_VALUE="v2.15.0" BR2_TARGET_ARM_TRUSTED_FIRMWARE_PLATFORM="agilex5" BR2_TARGET_ARM_TRUSTED_FIRMWARE_IMAGES="bl31.bin" BR2_TARGET_UBOOT=y -BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y -BR2_TARGET_UBOOT_CUSTOM_VERSION=y -BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2026.07" BR2_TARGET_UBOOT_BOARD_DEFCONFIG="socfpga_agilex5" BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/uboot.fragment" BR2_TARGET_UBOOT_NEEDS_OPENSSL=y diff --git a/docs/init-parity.md b/docs/init-parity.md index 1ca802a2..57892210 100644 --- a/docs/init-parity.md +++ b/docs/init-parity.md @@ -131,7 +131,7 @@ both shapes; folding avoids a second file and keeps the ordering trivial to read commented/off in the package default). 6. `etc/ssh_keys/.gitkeep` ships the empty mount point in the overlay (git cannot track empty directories; this repo's own convention — see - `uboot-patches/.gitkeep`, `linux-patches/.gitkeep`, `patches/.gitkeep` — already + `patches/uboot/.gitkeep`, `linux-patches/.gitkeep`, `patches/.gitkeep` — already uses marker files for exactly this). 7. Verified **zero** `ssh_host_*` files anywhere in the built and extracted image (see the report's Check 3). diff --git a/docs/uboot-mainline-port.md b/docs/uboot-mainline-port.md index 9b3e4818..deb1a346 100644 --- a/docs/uboot-mainline-port.md +++ b/docs/uboot-mainline-port.md @@ -176,6 +176,42 @@ delta of §3.1 whose omission a cold-boot smoke test could not catch. mainline's `misc_gen5.c:188-214` calls only `socfpga_bridges_reset(1)`. **A cold-boot smoke test cannot catch this** — it is the warm-reboot core-handoff path. +**2026-09-14 — the warm-reboot half is closed (task U2d, patch 0005).** The fork's one line +ports verbatim after all: mainline's gen5 `socfpga_bridges_reset(int)` still takes the same +argument, and its `enable == 0` arm is a near-textual match for the fork's, so +`0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch` adds the same +`socfpga_bridges_reset(0);` at the end of `arch_early_init_r()` — now in `misc_gen5.c`, which +`d1c559af5f` split out of `misc.c` in 2017. Correcting a citation above: the fork's +`misc.c:437-464` is `do_bridge` (the first half of this bullet); the fork's +`socfpga_bridges_reset(int)` body is `reset_manager.c:93-119`, against mainline's +`reset_manager_gen5.c:89-117`. **Equivalence [V], by source:** both write `0` to sysmgr +`iswgrp_handoff[0]` and `0x19` (`LWHPS2FPGA|HPS2FPGA|OCRAM`) to `iswgrp_handoff[1]` (sysmgr +`+0x80`/`+0x84` in both trees), both then test `fpgamgr_test_fpga_ready()` — character-identical +in the two `fpga_manager.c` files — and on success both write `0` to `brgmodrst` (rstmgr `+0x1c`) +and `0x19` to the L3 remap at `0xff800000`; mainline reaches the same values through +`socfpga_bridges_set_handoff_regs(false, false, false)`. The call sits last in +`arch_early_init_r()` in both, and `arch_early_init_r` precedes `console_init_r` in both +`board_r.c` sequences, so the "FPGA not ready" message behaves as on stock. The three +surrounding deltas are inert: `brgmodrst` implements only bits 2:0 (reset ids 96/97/98), so the +fork's `0xffffffff` and mainline's `0x7` in the `enable == 1` arm are the same state and this +patch clears the register anyway; mainline's extra `L3REGS = 0x1` write there is overwritten by +the `0x1` both trees write moments later in the remap-zero step; and +`socfpga_is_booting_from_fpga()` is false for this build (`__image_copy_start = 0x01000040`), so +that step writes the fork's plain `0x1`. **Cold boot is a no-op [V]:** the ready test fires +before `brgmodrst` and the remap are touched, and the only consumer of `iswgrp_handoff[0..1]` +is `do_bridge_reset(1, …)`, which rewrites and re-reads both registers itself +(`misc_gen5.c:253-257`) before using them — `[2]`/`[3]`, the values the SPL actually hands off, +are never touched. **What this is not:** the `enable == 0` arm has no caller anywhere in a +pristine v2026.07 (`misc_gen5.c:203`, `spl_gen5.c:98`, `socfpga_gen5.c:209` all pass 1), so the +patch makes existing mainline code reachable rather than adding any. Build-checked with the +repo cross toolchain on `socfpga_de10_nano_defconfig`: a normalised whole-binary disassembly +diff differs in exactly one function, `arch_early_init_r`, which gains +`movs r0, #0 / bl socfpga_bridges_reset`; `spl/u-boot-spl.bin` is byte-identical but for the +build timestamp **[V]**. Hardware still owes all three boot claims — that a fabric-preserving +warm reset reaches this code with the FPGA reporting user mode, that a core stays reachable +from Linux across it, and that cold boot still loads `menu.rbf` unchanged **[U]**; U6 is +deferred. + ### 3.4 `mt` — solved, and testable on stock hardware first > **Decided 2026-09-14 (mirror stock; amends ADR 0024 §Decision 5):** carry the fork's `mt` diff --git a/external.mk b/external.mk index dddba47a..e2b7268e 100644 --- a/external.mk +++ b/external.mk @@ -59,3 +59,164 @@ include $(sort $(wildcard $(BR2_EXTERNAL_MISTER_PATH)/package/*/*.mk)) ifeq ($(BR2_PACKAGE_DHCPCD),y) DHCPCD_CONFIG_OPTS += --with-hooks=ntp.conf --with-eghooks=yp.conf endif + +################################################################################ +# +# U-Boot / DE25-Nano: the QSPI-write audit as a build assertion. +# +# docs/de25-uboot.md section 7 is a hand-audited table of every U-Boot Kconfig +# symbol, command and default-environment string that could reach the DE25's +# QSPI flash (SDM firmware + the phase-1 HPS bitstream + the factory SPL -- +# no power-loss-safe update path on this board, so a write there is +# brick-class with JTAG-only recovery). docs/de25-boot-chain.md section 5's +# final bullet and its section 9.3 open-concern row both record, as of +# 2026-08-21, that "no release writes QSPI" is "a policy, enforced by prose +# only" -- no CI check pins a DE25 U-Boot config to the audited state. This +# hook is that check, moved from prose into the build (docs/uboot-tasks.md +# DU2): a wrong fragment request, a wrong resolved .config, or a QSPI-write +# command string reaching u-boot.itb, fails `make uboot-rebuild` outright, +# naming both docs. +# +# Same mechanism as the dhcpcd block above: BR2_EXTERNAL_MKS is included by +# Buildroot's own Makefile AFTER every package/*.mk (work/buildroot/Makefile: +# package includes first, then $(BR2_EXTERNAL_MKS)), so UBOOT_POST_BUILD_HOOKS +# already carries its Buildroot-default value here, and appending to it is +# legal. The hook body is $(call)ed from $(BUILD_DIR)/%/.stamp_built's own +# recipe (package/pkg-generic.mk), where $(@D) is the package's build +# directory ($(BUILD_DIR)/uboot-) -- no Buildroot patch, no U-Boot +# patch. +# +# Guarded on the DE25 U-Boot board defconfig specifically, not merely +# "aarch64": the repo's other board-distinguishing idiom is by architecture +# (package/mister-initramfs/mister-initramfs.mk:35, BR2_arm vs BR2_aarch64), +# but BR2_TARGET_UBOOT_BOARD_DEFCONFIG says directly "this is the DE25 U-Boot +# build", which is the fact this hook actually depends on. It is therefore +# inert on the DE10 tree, where that symbol is "socfpga_de10_nano": `make +# O=output printvars VARS='UBOOT_%_HOOKS MISTER_UBOOT_%'` on the DE10 tree +# prints neither UBOOT_POST_BUILD_HOOKS nor any MISTER_UBOOT_* variable, so +# the hook is never appended there [V, evaluated 2026-09-14; the DE10 tree was +# not rebuilt]. +# +# THREE THINGS ARE AUDITED, and the first exists because of a trap: +# +# 1. The FRAGMENT (board/mister/de25nano/uboot.fragment). A fragment line +# asking for one of these symbols does NOT necessarily show up in the +# resolved .config: ENV_IS_IN_UBI `depends on MTD_UBI` and `depends on +# CMD_UBI` (u-boot env/Kconfig), both off here, so kconfig SILENTLY DROPS +# the request -- merge_config.sh prints an "override" warning and the +# resolved .config gains no line at all. de25-uboot.md section 7 says +# exactly this ("strictly stronger than 'not set' -- there is no line to +# flip"), which is good news for safety and bad news for a checker that +# only reads the resolved config: the request would pass unnoticed today +# and become live the moment some other change satisfied its +# dependencies. A fragment that asks to re-open a brick-class hazard is a +# defect whether or not kconfig happened to grant it, so it fails here. +# +# 2. The RESOLVED .config ($(@D)/.config) -- section 7's table proper, +# including CONFIG_ENV_IS_IN_FAT=y, the one permitted env location. +# +# 3. The built u-boot.itb, by `strings`: the default environment is compiled +# in as text, and section 7's sharpest finding (the stock `bootcmd_qspi`, +# `"ubi detach; sf probe && ... saveenv && ubi part root"`) is a QSPI +# write living in a STRING, not in a symbol. One documented exception is +# allowed and is pinned by its exact head -- see the comment on +# QSPI_ENV_EXEMPT below. +# +################################################################################ +ifeq ($(call qstrip,$(BR2_TARGET_UBOOT_BOARD_DEFCONFIG)),socfpga_agilex5) + +# Section 7's symbol table, as one extended-regex alternation. Anchored with +# "^CONFIG_" and followed by "=" at the use site, so CONFIG_MTD matches +# CONFIG_MTD= and CONFIG_MTD_UBI= (its own alternative) but never +# CONFIG_SPL_MTD= or CONFIG_BLOBLIST_SIZE=. +MISTER_DE25_QSPI_SYMS = ENV_IS_IN_UBI|ENV_IS_IN_SPI_FLASH|ENV_IS_IN_NAND|ENV_IS_IN_MMC|CADENCE_QSPI|DM_SPI_FLASH|SPI_FLASH[A-Z_]*|CMD_SF[A-Z_]*|CMD_MTD[A-Z_]*|CMD_UBI[A-Z_]*|CMD_NAND|MTD|DM_MTD|MTD_UBI|MTD_RAW_NAND|HANDOFF|BLOBLIST + +# Command strings that must not appear in the built FIT, plus the erase/write +# verbs that would make one of them dangerous. Section 7 names the first three. +MISTER_DE25_QSPI_STRS = sf probe|sf erase|sf write|sf update|ubi part|ubi create|mtdparts|mtd erase|mtd write|nand erase|nand write + +# The ONE permitted `sf probe` occurrence, pinned by the head of its line. +# +# DEVIATION FROM docs/uboot-tasks.md DU2, stated plainly: DU2 asks for a +# u-boot.itb "free of `sf probe`". The good build is not, and cannot be +# without a U-Boot patch: include/configs/socfpga_soc64_common.h defines the +# default-env variable `linux_qspi_enable` unconditionally (unlike +# bootcmd_qspi, which is gated on CONFIG_CMD_SF and is genuinely absent here +# [V, checked]), so the string is compiled into every socfpga_soc64 build. Its +# CALLER is gated: board_prep_linux() (arch/arm/mach-socfpga/board.c:194-197) +# runs it under `if (use_fit && IS_ENABLED(CONFIG_CADENCE_QSPI))`, and +# CADENCE_QSPI is one of the symbols check 2 above has already proven absent +# -- so this hook only ever reaches the strings check with that gate proven +# closed in the same run. That is section 7's own "inert, and it is the +# Kconfig gate that makes it so" row, re-derived per build instead of +# asserted once in prose. +# +# The exemption is deliberately the narrowest that lets the good build pass: +# ONE occurrence, on a line starting with this exact text. Any second +# occurrence, any `sf probe` elsewhere, and any edit to the head of this +# variable (a bump that rewrites it, or someone splicing a write into the +# front of it) fails the build and forces a re-audit. Closing the deviation +# properly means guarding linux_qspi_enable under CONFIG_CADENCE_QSPI in the +# carried section-8 U-Boot patch -- upstreamable, out of DU2's scope, noted +# for DU5-prep. +MISTER_DE25_QSPI_ENV_EXEMPT = linux_qspi_enable=if sf probe; then echo Enabling QSPI at Linux DTB + +define MISTER_UBOOT_DE25_QSPI_AUDIT + @set -eu; \ + cfg='$(@D)/.config'; \ + itb='$(@D)/u-boot.itb'; \ + frags='$(UBOOT_KCONFIG_FRAGMENT_FILES)'; \ + syms='$(MISTER_DE25_QSPI_SYMS)'; \ + strs='$(MISTER_DE25_QSPI_STRS)'; \ + exempt='$(MISTER_DE25_QSPI_ENV_EXEMPT)'; \ + doc1='docs/de25-uboot.md section 7'; \ + doc2='docs/de25-boot-chain.md section 5'; \ + for f in $$frags; do \ + [ -f "$$f" ] || continue; \ + hits=$$(grep -E "^[[:space:]]*CONFIG_($$syms)=" "$$f" || true); \ + [ -n "$$hits" ] || continue; \ + echo "MiSTer QSPI-write audit FAILED ($$doc1, $$doc2): $$f asks to enable a symbol this board must never carry --" >&2; \ + echo "$$hits" | sed 's/^/ /' >&2; \ + echo " A fragment request is a defect even when kconfig drops it for unmet dependencies" >&2; \ + echo " (ENV_IS_IN_UBI depends on MTD_UBI and CMD_UBI, both off here), because the resolved" >&2; \ + echo " .config would then look clean while the file still asks to re-open a brick-class" >&2; \ + echo " hazard. See $$doc1 and $$doc2." >&2; \ + exit 1; \ + done; \ + if [ ! -f "$$cfg" ]; then \ + echo "MiSTer QSPI-write audit: $$cfg not found -- cannot check against $$doc1 / $$doc2" >&2; \ + exit 1; \ + fi; \ + hits=$$(grep -E "^CONFIG_($$syms)=" "$$cfg" || true); \ + if [ -n "$$hits" ]; then \ + echo "MiSTer QSPI-write audit FAILED ($$doc1, $$doc2): the following symbols must be absent or unset in the resolved config --" >&2; \ + echo "$$hits" | sed 's/^/ /' >&2; \ + exit 1; \ + fi; \ + if ! grep -q '^CONFIG_ENV_IS_IN_FAT=y$$' "$$cfg"; then \ + echo "MiSTer QSPI-write audit FAILED ($$doc1, $$doc2): CONFIG_ENV_IS_IN_FAT=y is required -- FAT on the SD card is the only environment location this board may use" >&2; \ + exit 1; \ + fi; \ + if [ ! -f "$$itb" ]; then \ + echo "MiSTer QSPI-write audit: $$itb not found -- cannot check for embedded QSPI-write strings ($$doc1)" >&2; \ + exit 1; \ + fi; \ + hits=$$(strings "$$itb" | grep -E "$$strs" | sed '/^$$/d' || true); \ + bad=$$(printf '%s\n' "$$hits" | sed '/^$$/d' | grep -v -F -e "$$exempt" || true); \ + nallowed=$$(printf '%s\n' "$$hits" | sed '/^$$/d' | grep -c -F -e "$$exempt" || true); \ + nverbs=$$(printf '%s\n' "$$hits" | sed '/^$$/d' | grep -F -e "$$exempt" | grep -oE "$$strs" | grep -c . || true); \ + if [ -n "$$bad" ] || [ "$$nallowed" -gt 1 ] || [ "$$nverbs" -gt 1 ]; then \ + echo "MiSTer QSPI-write audit FAILED ($$doc1, $$doc2): u-boot.itb carries a QSPI command string that is not the one documented, gate-closed exception --" >&2; \ + printf '%s\n' "$$hits" | sed '/^$$/d;s/^/ /' | cut -c1-120 >&2; \ + echo " The only permitted occurrence is ONE line, carrying ONE of these verbs, beginning:" >&2; \ + echo " $$exempt" >&2; \ + echo " ($$doc1's linux_qspi_enable row: inert only because CADENCE_QSPI is compiled out," >&2; \ + echo " which this hook has just re-proved against the resolved .config.)" >&2; \ + exit 1; \ + fi; \ + echo "MiSTer QSPI-write audit ($$doc1, $$doc2): PASS -- fragment, resolved .config and u-boot.itb all clean" +endef + +UBOOT_POST_BUILD_HOOKS += MISTER_UBOOT_DE25_QSPI_AUDIT + +endif # BR2_TARGET_UBOOT_BOARD_DEFCONFIG = socfpga_agilex5 (DE25-Nano) diff --git a/scripts/check-uboot-handoff.sh b/scripts/check-uboot-handoff.sh new file mode 100755 index 00000000..31b02d58 --- /dev/null +++ b/scripts/check-uboot-handoff.sh @@ -0,0 +1,126 @@ +#!/bin/sh +# +# check-uboot-handoff.sh — the handoff-equality gate for the DE10-Nano's +# mainline U-Boot port (docs/uboot-mainline-port.md Sec 3.2a, Sec 6; +# docs/uboot-tasks.md U4b). +# +# WHY THIS EXISTS. Sec 3.2a's binary evidence pins the SHIPPED stock SPL to +# the fork's QTS handoff headers by packing each of the seven handoff tables +# (pinmux, four IOCSR scan chains, two DDR sequencer ROMs) out of +# `qts/*.h` and finding the exact byte string inside SPL copy 0. The same +# check run against a build we control catches the one failure mode a +# cold-boot smoke test cannot: the build silently picking up the wrong +# `qts/*.h` tree (e.g. a rebase that reverts the carried headers, or a stray +# `-I` that shadows them with mainline's own). That failure mode looks +# EXACTLY like success — the build is green, the image is the right size, +# nothing about the build log says anything is wrong — right up until DDR +# calibration or the HPS-FPGA bridges misbehave on real silicon. +# +# WHAT THIS DOES NOT DO. Per Sec 3.2a: this proves each table's bytes are +# PRESENT in each image's SPL copy 0, at whatever offset the compiler put it +# — offsets are code layout and MUST NOT be compared between the two images, +# only reported. It does not touch the QTS scalar #defines (FPGAPORTRST, the +# PLL counts): those compile to instruction immediates and are not +# greppable — Sec 3.2a is explicit about that limit. +# +# HOW. The packing and byte-string search is done by a small python3 helper, +# scripts/lib/qts-tables.py (sanctioned style exception — see that file's own +# header for why: Buildroot already requires host python3, so this adds no +# dependency). This script is the POSIX sh front end: it validates arguments, +# runs that helper once, and turns its machine-readable report into this +# repo's usual ok()/bad() house style (scripts/check-zimage-dtb.sh). +# +# Usage: scripts/check-uboot-handoff.sh +# +# directory containing the qts/*.h headers to check (e.g. the +# carried fork headers, copied out of the patched tree). +# , +# two U-Boot images whose SPL copy 0 must both contain every +# table packed from — typically a freshly built +# `u-boot-with-spl.sfp` and the reference stock `uboot.img`. +# A fixture run may legitimately pass the SAME file twice (no +# built .sfp exists yet, or to prove the harness itself works). +# +# Exit: 0 = every table found in SPL copy 0 of both images, 1 = at least one +# table is missing from at least one image (named below) — a real +# assertion failure, 2 = usage/IO/parse error. + +set -eu + +prog=${0##*/} +scriptdir=$(cd -- "$(dirname -- "$0")" && pwd) +helper="$scriptdir/lib/qts-tables.py" + +usage() { + echo "usage: $prog " >&2 + exit 2 +} + +note() { printf ' %s\n' "$*"; } +ok() { printf 'ok %s\n' "$*"; } +bad() { printf 'FAIL %s\n' "$*" >&2; } + +[ $# -eq 3 ] || usage +qts_dir=$1 +image_a=$2 +image_b=$3 + +[ -d "$qts_dir" ] || { echo "$prog: no such directory: $qts_dir" >&2; exit 2; } +[ -f "$image_a" ] || { echo "$prog: no such file: $image_a" >&2; exit 2; } +[ -f "$image_b" ] || { echo "$prog: no such file: $image_b" >&2; exit 2; } +command -v python3 >/dev/null 2>&1 || { echo "$prog: python3 not found on PATH" >&2; exit 2; } +[ -f "$helper" ] || { echo "$prog: helper missing: $helper" >&2; exit 2; } + +printf '%s: qts-dir=%s\n' "$prog" "$qts_dir" +printf '%s: image-a=%s\n' "$prog" "$image_a" +printf '%s: image-b=%s\n' "$prog" "$image_b" + +tmpout=$(mktemp) || { echo "$prog: mktemp failed" >&2; exit 2; } +trap 'rm -f "$tmpout"' EXIT + +helper_rc=0 +python3 "$helper" "$qts_dir" "$image_a" "$image_b" >"$tmpout" 2>&1 || helper_rc=$? + +if [ "$helper_rc" -eq 2 ]; then + echo "$prog: qts-tables.py usage/IO/parse error:" >&2 + sed 's/^/ /' "$tmpout" >&2 + exit 2 +fi + +if [ "$helper_rc" -ne 0 ] && [ "$helper_rc" -ne 1 ]; then + echo "$prog: qts-tables.py exited $helper_rc unexpectedly:" >&2 + sed 's/^/ /' "$tmpout" >&2 + exit 2 +fi + +missing=0 +found=0 +while IFS=' ' read -r table image status offset nbytes nwords; do + case $table in + SUMMARY) continue ;; + esac + case $status in + FOUND) + found=$((found + 1)) + ok "$table @ $offset in $image ($nbytes bytes, $nwords elements)" + ;; + MISSING) + missing=$((missing + 1)) + bad "$table NOT FOUND in $image ($nbytes bytes, $nwords elements packed)" + ;; + *) + echo "$prog: unrecognised report line from qts-tables.py: $table $image $status" >&2 + exit 2 + ;; + esac +done <"$tmpout" + +note "$found found, $missing missing (7 tables x 2 images = 14 checks expected)" + +if [ "$missing" -eq 0 ]; then + echo "$prog: all seven handoff tables present in SPL copy 0 of both images" + exit 0 +else + echo "$prog: HANDOFF MISMATCH — $missing table/image pair(s) missing, named above" >&2 + exit 1 +fi diff --git a/scripts/check-uboot-parity.sh b/scripts/check-uboot-parity.sh new file mode 100755 index 00000000..65d38c6a --- /dev/null +++ b/scripts/check-uboot-parity.sh @@ -0,0 +1,689 @@ +#!/bin/sh +# +# check-uboot-parity.sh — assert that a from-source U-Boot for the DE10-Nano is +# interchangeable with the stock 2017.03 fork blob (plan §6). +# +# WHY THIS EXISTS. The DE10-Nano's bootloader is the one part of the image that +# cannot be tested by booting it: a wrong SPL header, a short SPL copy or a +# changed environment does not fail a build, it bricks a card that can only be +# recovered with a second reader. The owner's target for the mainline build is +# "as close as possible to what stock does" (docs/uboot-tasks.md), so parity +# with the shipped blob IS the specification, and this script is how that +# specification is checked without a board. +# +# WHAT IT CHECKS, and where each rule comes from: +# +# Structural (docs/uboot-mainline-port.md §6, boot-chain §2) +# * four byte-identical 64 KiB SPL copies at 0x00000/0x10000/0x20000/0x30000 +# — the BootROM tries each in turn, so they are a redundancy contract, not +# a layout accident; +# * the Altera/socfpga SPL header at +0x40: validation word 0x31305341, +# length_u32, and the header checksum recomputed exactly as +# `sfp_hdr_checksum` computes it (u-boot 2026.07 tools/socfpgaimage.c:121, +# whose `while (--len)` at :126 sums only the first 9 of the 12 header +# bytes — reproduce the tool, not the intent), plus the payload CRC32 that +# `pbl_crc32` (tools/pbl_crc32.c:42) stores at the end of the payload; +# * a legacy uImage at 0x40000 whose header CRC and payload CRC recompute, +# whose ih_load is 0x01000040, and whose payload closes the file exactly; +# * SPL payload size against the 64 KiB slot (and against $SPL_SIZE_LIMIT / +# u-boot:tools/spl_size_limit when the caller has one — plan §3.5: 2017.03 +# does NOT enforce it and silently truncates length_u32 instead). +# +# Environment (plan §6, boot-chain §3.1) +# The default_environment[] blob must be BYTE-IDENTICAL to stock's — 21 +# entries, 1,150 B, malformed entry 15 and all. With the fork's `mt` command +# carried (plan §3.4, decided 2026-09-14) there is no allowed delta left, so +# this is a plain cmp; the entry-by-entry diff is printed only as the +# diagnostic when the cmp fails. Stock's blob lives at offset 0x28018 of the +# U-Boot-proper payload (i.e. 0x68058 in the .img); the built blob is located +# from the ELF (`nm -S u-boot` -> addr - ih_load) when one is passed, and by +# scanning for the blob otherwise. Note that u-boot-initial-env is NOT used: +# it is sorted and can be stale. +# +# Command table (plan §6, boot-chain §3.3) +# Every one of stock's 69 command names must exist in the built binary +# (`mt` included); extra mainline commands are allowed and listed. Both +# tables are recovered the same way: the longest run of `struct cmd_tbl` +# records that validate as a linker list (U_BOOT_CMD; the records are +# emitted sorted by name into .u_boot_list_2_cmd_2_*). The third word of the +# record is `int repeatable` in the fork and the `cmd_rep` function pointer +# in mainline, so both spellings are accepted — see recname() below. This is +# the check that a `git bisect`-sized Kconfig slip silently breaks. +# +# Allowed diffs, named in the output rather than ignored (plan §6): version +# string and build timestamp, uImage ih_ep 0x01000040 vs 0, total size, code +# layout and table offsets. Forbidden: any environment byte, a missing stock +# command, a layout/offset change of the SPL copies or the uImage, a load +# address change, any SPL header field change. +# +# NOT CHECKED HERE: the QTS handoff tables (§3.2a) — that is +# scripts/check-uboot-handoff.sh, which needs the carried qts/*.h as a third +# input. This script and that one are the two halves of plan §6. +# +# All the binary arithmetic is done in awk with no bitwise builtins — POSIX awk +# has none — fed from `od`. That keeps the script POSIX sh with no host +# dependency beyond coreutils and awk; the only optional tool is `nm`, and only +# for the third argument. Verified to give identical results under gawk, mawk, +# the one-true awk (nawk) and this image's own BusyBox awk (via qemu-arm). +# +# Usage: scripts/check-uboot-parity.sh [u-boot-elf] +# the image under test (u-boot-with-spl.sfp, or a uboot.img) +# the reference blob (scripts/fetch-sdcard-payload.sh fetches +# it by hash as STOCK_UBOOT_SHA256) +# [u-boot-elf] optional: the built u-boot ELF, used to locate +# default_environment[] exactly instead of by scan +# Env: +# NM nm to use for the ELF (default: nm) +# SPL_SIZE_LIMIT byte limit for the SPL payload, e.g. the output of the +# U-Boot tree's tools/spl_size_limit (default: unset, only +# the 64 KiB slot is enforced) +# Exit: 0 = parity holds, 1 = a contract violation, 2 = usage/IO error. + +set -eu + +# Byte-wise, locale-independent: `sort`/`comm` must agree on collation and no +# text comparison here is linguistic (same reason as scripts/check-abi.sh:78). +export LC_ALL=C + +SPL_COPIES=4 +SPL_SLOT=65536 # 0x10000 — one BootROM SPL slot +UIMG_OFF=262144 # 0x40000 — where the legacy uImage starts +UIMG_HDR=64 # sizeof(image_header_t) +SPL_HDR_OFF=64 # 0x40 — struct socfpga_header inside the SPL +SPL_VALIDATION=31305341 # 'A' 'S' '0' '1' little-endian +UIMG_MAGIC=27051956 # IH_MAGIC, big-endian +TEXT_BASE=16777280 # 0x01000040 — ih_load, and the U-Boot proper link base +STOCK_ENV_OFF=163864 # 0x28018 in the U-Boot proper payload (plan §6) +STOCK_ENV_LEN=1150 # 21 entries incl. the terminating NUL (plan §6) + +prog=${0##*/} +fail=0 + +usage() { + echo "usage: $prog [u-boot-elf]" >&2 + exit 2 +} + +note() { printf ' %s\n' "$*"; } +ok() { printf 'ok %s\n' "$*"; } +bad() { printf 'FAIL %s\n' "$*" >&2; fail=1; } +die() { echo "$prog: $*" >&2; exit 2; } + +hx() { printf '0x%08x' "$1"; } + +# rdbytes FILE OFFSET LEN -> the bytes as decimal, one field per byte +rdbytes() { od -An -tu1 -v -j "$2" -N "$3" "$1"; } + +# u32le/u32be/u16le/u8 FILE OFFSET -> decimal value of the integer at OFFSET +u32le() { + _b=$(od -An -tx1 -j "$2" -N 4 -v "$1" | tr -d ' \n') + [ ${#_b} -eq 8 ] || die "short read of 4 bytes at offset $2 of $1" + printf '%d' "0x$(echo "$_b" | cut -c7-8)$(echo "$_b" | cut -c5-6)$(echo "$_b" | cut -c3-4)$(echo "$_b" | cut -c1-2)" +} +u32be() { + _b=$(od -An -tx1 -j "$2" -N 4 -v "$1" | tr -d ' \n') + [ ${#_b} -eq 8 ] || die "short read of 4 bytes at offset $2 of $1" + printf '%d' "0x$_b" +} +u16le() { + _b=$(od -An -tx1 -j "$2" -N 2 -v "$1" | tr -d ' \n') + [ ${#_b} -eq 4 ] || die "short read of 2 bytes at offset $2 of $1" + printf '%d' "0x$(echo "$_b" | cut -c3-4)$(echo "$_b" | cut -c1-2)" +} +u8() { + _b=$(od -An -tx1 -j "$2" -N 1 -v "$1" | tr -d ' \n') + [ ${#_b} -eq 2 ] || die "short read of 1 byte at offset $2 of $1" + printf '%d' "0x$_b" +} + +# --- CRC32, two flavours, in portable awk ------------------------------------ +# MODE=zlib : the reflected CRC32 of include/u-boot/crc.h, used by mkimage for +# the uImage header and payload CRCs. +# MODE=pbl : tools/pbl_crc32.c:42-57 — poly 0x04c11db7, MSB-first, seeded +# 0xffffffff and complemented on the way out, used by +# tools/socfpgaimage.c for the SPL payload. +# Both are table-driven and use an explicit xor table, because POSIX awk has no +# bitwise operators (gawk/mawk/busybox awk all agree on plain arithmetic). +# shellcheck disable=SC2016 # this is an awk program, not shell: $f/$1 are awk's +CRC_AWK=' +function xor32(a, b, r, m, i) { + r = 0; m = 1 + for (i = 0; i < 4; i++) { + r += X8[(a % 256) * 256 + (b % 256)] * m + a = int(a / 256); b = int(b / 256); m *= 256 + } + return r +} +BEGIN { + for (i = 0; i < 16; i++) for (j = 0; j < 16; j++) { + v = 0; p = 1; a = i; b = j + for (k = 0; k < 4; k++) { + if ((a % 2) != (b % 2)) v += p + a = int(a / 2); b = int(b / 2); p *= 2 + } + X4[i * 16 + j] = v + } + for (i = 0; i < 256; i++) for (j = 0; j < 256; j++) + X8[i * 256 + j] = X4[int(i / 16) * 16 + int(j / 16)] * 16 + X4[(i % 16) * 16 + (j % 16)] + if (MODE == "zlib") + for (i = 0; i < 256; i++) { + c = i + for (k = 0; k < 8; k++) + if (c % 2) c = xor32(3988292384, int(c / 2)); else c = int(c / 2) + T[i] = c + } + else + for (i = 0; i < 256; i++) { + m = i * 16777216 + for (k = 0; k < 8; k++) + if (m >= 2147483648) m = xor32((m * 2) % 4294967296, 79764919) + else m = (m * 2) % 4294967296 + T[i] = m + } + crc = 4294967295 + n = 0 +} +{ + for (f = 1; f <= NF; f++) { + b = $f + 0 + if (MODE == "zlib") crc = xor32(T[X8[(crc % 256) * 256 + b]], int(crc / 256)) + else crc = xor32((crc * 256) % 4294967296, T[X8[int(crc / 16777216) * 256 + b]]) + n++ + } +} +# Print in two 16-bit halves: a CRC >= 2^31 through one %x saturates to +# 0x7fffffff in busybox awk (its printf casts to int). +END { v = 4294967295 - crc; printf "%04x%04x %d\n", int(v / 65536), v % 65536, n } +' + +# crc32 FILE OFFSET LEN -> "hexcrc bytecount" (reflected/zlib flavour) +crc32() { rdbytes "$1" "$2" "$3" | awk -v MODE=zlib "$CRC_AWK"; } +# pblcrc FILE OFFSET LEN -> "hexcrc bytecount" (socfpga SPL flavour) +pblcrc() { rdbytes "$1" "$2" "$3" | awk -v MODE=pbl "$CRC_AWK"; } + +# --- The U-Boot proper payload: environment blob and command table ----------- +# Fed the payload bytes as decimal (od -tu1), with BASE = the link address of +# byte 0 (ih_load). +# +# MODE=env reads default_environment[] from HINT (the ELF-derived payload +# offset) when there is one, and otherwise finds it: anchor on the first of +# "bootcmd=" / "bootargs=" / "baudrate=" that starts immediately after a NUL -- +# the NUL matters, or mainline's "distro_bootcmd=" masquerades as the anchor and +# the blob is read from its middle -- then walk back over whole printable +# NUL-terminated entries and forward to the empty entry that terminates the +# blob. The walk-back can still overshoot into neighbouring .rodata strings, so +# the anchored result is reported alongside the ELF one, never instead of it. +# +# MODE=cmd recovers the U_BOOT_CMD linker list: the longest run of struct +# cmd_tbl records that validate, trying the three plausible ARM32 strides +# (CONFIG_SYS_LONGHELP and CONFIG_AUTO_COMPLETE each add a word). +# shellcheck disable=SC2016 # ditto: an awk program quoted whole +PAY_AWK=' +function w32(o) { return b[o] + b[o+1] * 256 + b[o+2] * 65536 + b[o+3] * 16777216 } +function inrange(p) { return (p >= BASE && p < BASE + n) } +function cstr(ptr, max, o, s, c, l) { + if (ptr == 0) return "" + o = ptr - BASE + if (o < 0 || o >= n) return BAD + s = ""; l = 0 + while (l < max) { + c = b[o + l] + if (c == 0) return s + if (c < 32 || c > 126) return BAD + s = s sprintf("%c", c); l++ + } + return BAD +} +# One U_BOOT_CMD record. The third word is `int repeatable` in the 2017.03 fork +# (0 or 1) and the `cmd_rep` FUNCTION POINTER in mainline, which replaced it +# (u-boot:include/command.h) — accept either, or neither table is found. +function recname(o, stride, np, cp, ma, rp, up, j, nm) { + if (o < 0 || o + stride > n) return "" + np = w32(o) + if (!inrange(np)) return "" + cp = w32(o + 12) + if (!inrange(cp)) return "" + ma = w32(o + 4) + if (ma < 1 || ma > 255) return "" + rp = w32(o + 8) + if (rp > 1 && !inrange(rp)) return "" + for (j = 16; j < stride; j += 4) { + up = w32(o + j) + if (up != 0 && !inrange(up)) return "" + } + nm = cstr(np, 16) + if (nm == "" || nm == BAD || nm ~ /[ \t]/) return "" + up = w32(o + 16) + if (up != 0 && cstr(up, 512) == BAD) return "" + return nm +} +# Walk the NUL-separated entries from S to the empty entry that terminates the +# blob. Returns the blob length (entries plus that terminator), or 0 with EERR +# set. ECNT gets the entry count; with EMIT, each entry is printed. +function walkfwd(s, emit, p, e, i, t, cnt, term) { + cnt = 0; p = s; term = 0 + while (p < n) { + e = p + while (e < n && b[e] != 0) e++ + if (e >= n) break + if (e == p) { term = 1; break } + t = "" + for (i = p; i < e; i++) { + if (b[i] < 32 || b[i] > 126) { t = BAD; break } + t = t sprintf("%c", b[i]) + } + if (t == BAD) { EERR = "a non-printable byte at payload offset " i; return 0 } + if (emit) printf "ENTRY %d %d %s\n", cnt, p, t + cnt++ + p = e + 1 + } + if (!term) { EERR = "the blob at payload offset " s " has no NUL-NUL terminator"; return 0 } + ECNT = cnt + return p + 1 - s +} +# Find the blob without an ELF: anchor on a well-known entry that must start +# right after a NUL (so "distro_bootcmd=" cannot masquerade as "bootcmd="), +# then walk back over whole printable entries. AHITS gets the match count. +function anchor( o, i, k, np, pat, m, cand, ok, p, c) { + split("bootcmd= bootargs= baudrate=", pat, " ") + for (k = 1; k <= 3; k++) { + np = length(pat[k]); m = -1; AHITS = 0 + for (o = 1; o + np <= n; o++) { + if (b[o-1] != 0 || b[o] != ORD[substr(pat[k], 1, 1)]) continue + ok = 1 + for (i = 1; i < np; i++) + if (b[o+i] != ORD[substr(pat[k], i+1, 1)]) { ok = 0; break } + if (!ok) continue + AHITS++ + if (m < 0) m = o + } + if (m >= 0) { AWHAT = pat[k]; break } + } + if (m < 0) return -1 + while (m > 0) { + if (b[m-1] != 0) break + p = m - 2 + while (p >= 0 && b[p] != 0) p-- + if (p < 0) break + c = cstr(BASE + p + 1, m - p - 1) + if (c == "" || c == BAD) break + m = p + 1 + } + return m +} +{ for (f = 1; f <= NF; f++) b[n++] = $f + 0 } +END { + BAD = sprintf("%c", 1) + for (i = 32; i < 127; i++) ORD[sprintf("%c", i)] = i + if (MODE == "env") { + a = anchor() + if (a >= 0) { + alen = walkfwd(a, 0) + if (alen > 0) printf "ENVA %d %d %d %s %d\n", a, alen, ECNT, AWHAT, AHITS + } + s = (HINT >= 0) ? HINT : a + if (s < 0) { print "ENVERR no bootcmd=/bootargs=/baudrate= entry to anchor on"; exit 0 } + blen = walkfwd(s, 1) + if (blen == 0) { print "ENVERR " EERR; exit 0 } + printf "ENV %d %d %d\n", s, blen, ECNT + exit 0 + } + # MODE == cmd + best = 0; bs = 0; bstride = 0 + for (si = 0; si < 3; si++) { + stride = 24 + si * 4 + for (o = 0; o + stride <= n; o += 4) { + if (recname(o, stride) == "") continue + run = 1; s = o + for (p = o - stride; p >= 0; p -= stride) { + if (recname(p, stride) == "") break + run++; s = p + } + for (p = o + stride; p + stride <= n; p += stride) { + if (recname(p, stride) == "") break + run++ + } + if (run > best) { best = run; bs = s; bstride = stride } + } + } + if (best < 2) { print "CMDERR no command table found"; exit 0 } + printf "TABLE %d %d %d\n", best, bs, bstride + for (i = 0; i < best; i++) printf "NAME %s\n", recname(bs + i * bstride, bstride) +} +' + +# payscan FILE PAYLOAD_OFF PAYLOAD_LEN BASE MODE [ENV_HINT] +# ENV_HINT is the payload offset of default_environment[] when the ELF gave us +# one; -1 means "find it yourself". +payscan() { + rdbytes "$1" "$2" "$3" | + awk -v BASE="$4" -v MODE="$5" -v HINT="${6:--1}" "$PAY_AWK" +} + +# --------------------------------------------------------------------------- +[ $# -eq 2 ] || [ $# -eq 3 ] || usage +built=$1 +stock=$2 +elf=${3:-} + +for f in "$built" "$stock" ${elf:+"$elf"}; do + [ -f "$f" ] || die "no such file: $f" + [ -r "$f" ] || die "not readable: $f" +done + +tmp=$(mktemp -d "${TMPDIR:-/tmp}/check-uboot-parity.XXXXXX") || die "mktemp failed" +trap 'rm -rf "$tmp"' EXIT HUP INT TERM + +bsize=$(wc -c < "$built" | tr -d ' ') +ssize=$(wc -c < "$stock" | tr -d ' ') +printf '%s: built %s (%s bytes)\n' "$prog" "$built" "$bsize" +printf '%s: stock %s (%s bytes)\n' "$prog" "$stock" "$ssize" +if [ -n "$elf" ]; then printf '%s: elf %s\n' "$prog" "$elf"; fi + +min=$((UIMG_OFF + UIMG_HDR)) +[ "$bsize" -gt "$min" ] || die "$built is $bsize bytes, too small to hold an SPL region and a uImage" +[ "$ssize" -gt "$min" ] || die "$stock is $ssize bytes, too small to be the stock uboot.img" + +# --- 1. The legacy uImage at 0x40000 ---------------------------------------- +echo +echo "[1] legacy uImage header at $(hx $UIMG_OFF)" + +smagic=$(u32be "$stock" "$UIMG_OFF") +[ "$(printf '%08x' "$smagic")" = "$UIMG_MAGIC" ] || + die "reference $stock has no uImage magic at $(hx $UIMG_OFF) — wrong file?" +sload=$(u32be "$stock" $((UIMG_OFF + 16))) +ssz=$(u32be "$stock" $((UIMG_OFF + 12))) +note "stock: ih_size=$ssz ih_load=$(hx "$sload") ih_ep=$(hx "$(u32be "$stock" $((UIMG_OFF + 20)))")" + +bmagic=$(u32be "$built" "$UIMG_OFF") +if [ "$(printf '%08x' "$bmagic")" = "$UIMG_MAGIC" ]; then + ok "uImage magic 0x$UIMG_MAGIC present at $(hx $UIMG_OFF)" +else + bad "no uImage magic at $(hx $UIMG_OFF) (found $(hx "$bmagic")) — the SPL region is the wrong size" + echo "$prog: CONTRACT VIOLATED" >&2 + exit 1 +fi + +bhcrc=$(u32be "$built" $((UIMG_OFF + 4))) +btime=$(u32be "$built" $((UIMG_OFF + 8))) +bsz=$(u32be "$built" $((UIMG_OFF + 12))) +bload=$(u32be "$built" $((UIMG_OFF + 16))) +bep=$(u32be "$built" $((UIMG_OFF + 20))) +bdcrc=$(u32be "$built" $((UIMG_OFF + 24))) +bos=$(u8 "$built" $((UIMG_OFF + 28))) +barch=$(u8 "$built" $((UIMG_OFF + 29))) +btype=$(u8 "$built" $((UIMG_OFF + 30))) +bcomp=$(u8 "$built" $((UIMG_OFF + 31))) +bname=$(dd if="$built" bs=1 skip=$((UIMG_OFF + 32)) count=32 2>/dev/null | tr -d '\0') +note "ih_name = \"$bname\"" +note "ih_time = $btime (allowed diff: build timestamp)" +note "ih_size = $bsz ih_load = $(hx "$bload") ih_ep = $(hx "$bep")" +note "os/arch/type/comp = $bos/$barch/$btype/$bcomp (want 17/2/5/0 = U-Boot/ARM/firmware/none)" + +if [ "$bos" -eq 17 ] && [ "$barch" -eq 2 ] && [ "$btype" -eq 5 ] && [ "$bcomp" -eq 0 ]; then + ok "uImage os/arch/type/comp match stock's IH_OS_U_BOOT/IH_ARCH_ARM/IH_TYPE_FIRMWARE/IH_COMP_NONE" +else + bad "uImage os/arch/type/comp = $bos/$barch/$btype/$bcomp, stock is 17/2/5/0" +fi + +if [ "$bload" -eq "$TEXT_BASE" ]; then + ok "ih_load = $(hx "$bload") — the SPL of the fork jumps to ih_load (common/spl/spl.c:110)" +else + bad "ih_load = $(hx "$bload"), must be $(hx $TEXT_BASE): stock's SPL would jump into the wrong address" +fi + +if [ "$bep" -eq 0 ] || [ "$bep" -eq "$TEXT_BASE" ]; then + ok "ih_ep = $(hx "$bep") (allowed diff: mainline sets CONFIG_TEXT_BASE, the fork leaves 0 — plan §3.3)" + [ "$bep" -eq 0 ] || note "mixing warning: a stock SPL reads ih_load, a mainline SPL reads ih_ep (common/spl/spl_legacy.c:57) — do not pair an SPL and a uImage from different builds" +else + bad "ih_ep = $(hx "$bep"): neither 0 (fork) nor $(hx $TEXT_BASE) (mainline) — a mainline SPL would jump there" +fi + +if [ $((UIMG_OFF + UIMG_HDR + bsz)) -eq "$bsize" ]; then + ok "total size closes the file: $(hx $UIMG_OFF) + $UIMG_HDR + $bsz = $bsize" +else + bad "ih_size $bsz does not close the file: $(hx $UIMG_OFF) + $UIMG_HDR + $bsz = $((UIMG_OFF + UIMG_HDR + bsz)), file is $bsize" +fi + +# header CRC: the 64-byte header with its own ih_hcrc field zeroed +hcrc=$({ + dd if="$built" bs=1 skip="$UIMG_OFF" count=4 2>/dev/null + printf '\000\000\000\000' + dd if="$built" bs=1 skip=$((UIMG_OFF + 8)) count=56 2>/dev/null +} | od -An -tu1 -v | awk -v MODE=zlib "$CRC_AWK" | cut -d' ' -f1) +if [ "$hcrc" = "$(printf '%08x' "$bhcrc")" ]; then + ok "uImage header CRC recomputes: 0x$hcrc" +else + bad "uImage header CRC is $(hx "$bhcrc") but recomputes to 0x$hcrc" +fi + +# payload CRC +crcres=$(crc32 "$built" $((UIMG_OFF + UIMG_HDR)) "$bsz") +dcrc=${crcres%% *} +dread=${crcres##* } +if [ "$dread" -ne "$bsz" ]; then + bad "uImage payload is short: read $dread of $bsz bytes" +elif [ "$dcrc" = "$(printf '%08x' "$bdcrc")" ]; then + ok "uImage payload CRC recomputes over all $bsz bytes: 0x$dcrc" +else + bad "uImage payload CRC is $(hx "$bdcrc") but recomputes to 0x$dcrc — the payload was edited after mkimage" +fi + +# --- 2. The four SPL copies -------------------------------------------------- +echo +echo "[2] SPL region: $SPL_COPIES copies of $SPL_SLOT bytes" + +dd if="$built" bs="$SPL_SLOT" skip=0 count=1 of="$tmp/spl0.bin" 2>/dev/null +i=1 +while [ "$i" -lt "$SPL_COPIES" ]; do + dd if="$built" bs="$SPL_SLOT" skip="$i" count=1 of="$tmp/spl$i.bin" 2>/dev/null + if cmp -s "$tmp/spl0.bin" "$tmp/spl$i.bin"; then + ok "SPL copy $i at $(hx $((i * SPL_SLOT))) is byte-identical to copy 0" + else + bad "SPL copy $i at $(hx $((i * SPL_SLOT))) differs from copy 0 — the BootROM fallback copies are not interchangeable" + cmp "$tmp/spl0.bin" "$tmp/spl$i.bin" 2>&1 | sed 's/^/ /' >&2 || true + fi + i=$((i + 1)) +done + +# --- 3. The socfpga SPL header ---------------------------------------------- +echo +echo "[3] socfpga SPL header at +$(hx $SPL_HDR_OFF) of copy 0" + +val=$(u32le "$built" "$SPL_HDR_OFF") +ver=$(u8 "$built" $((SPL_HDR_OFF + 4))) +flags=$(u8 "$built" $((SPL_HDR_OFF + 5))) +lenu32=$(u16le "$built" $((SPL_HDR_OFF + 6))) +zero=$(u16le "$built" $((SPL_HDR_OFF + 8))) +cks=$(u16le "$built" $((SPL_HDR_OFF + 10))) +splen=$((lenu32 * 4)) +note "validation=$(hx "$val") version=$ver flags=$flags length_u32=$lenu32 ($splen bytes) zero=$zero checksum=0x$(printf '%04x' "$cks")" + +if [ "$(printf '%08x' "$val")" = "$SPL_VALIDATION" ]; then + ok "validation word 0x$SPL_VALIDATION present — the BootROM will accept this SPL" +else + bad "validation word is $(hx "$val"), must be 0x$SPL_VALIDATION ('A''S''0''1')" +fi +if [ "$ver" -eq 0 ] && [ "$flags" -eq 0 ] && [ "$zero" -eq 0 ]; then + ok "SPL header version/flags/zero are stock's 0/0/0" +else + bad "SPL header version/flags/zero = $ver/$flags/$zero, stock is 0/0/0" +fi + +# tools/socfpgaimage.c:63-72 — sums header bytes 0..8 only (the while(--len) +# pre-decrement drops the last byte of the 10 it means to read). +calc=$(rdbytes "$built" "$SPL_HDR_OFF" 9 | awk '{for(i=1;i<=NF;i++)s+=$i}END{printf "%04x\n", s % 65536}') +if [ "$calc" = "$(printf '%04x' "$cks")" ]; then + ok "header checksum recomputes: 0x$calc (socfpgaimage.c off-by-one reproduced)" +else + bad "header checksum is 0x$(printf '%04x' "$cks") but recomputes to 0x$calc" +fi + +if [ "$splen" -ge $((SPL_HDR_OFF + 12)) ] && [ "$splen" -le "$SPL_SLOT" ]; then + ok "SPL payload length $splen bytes is inside the $SPL_SLOT-byte slot (headroom $((SPL_SLOT - splen)) bytes, $((splen * 100 / SPL_SLOT))% used)" + crcoff=$((splen - 4)) + stored=$(u32le "$built" "$crcoff") + crcres=$(pblcrc "$built" 0 "$crcoff") + got=${crcres%% *} + gread=${crcres##* } + if [ "$gread" -ne "$crcoff" ]; then + bad "SPL payload is short: read $gread of $crcoff bytes" + elif [ "$got" = "$(printf '%08x' "$stored")" ]; then + ok "SPL payload CRC32 over [0,$crcoff) recomputes: 0x$got (stored at +$(hx $crcoff))" + else + bad "SPL payload CRC32 is $(hx "$stored") but recomputes to 0x$got — copy 0 is corrupt" + fi + if rdbytes "$built" "$splen" $((SPL_SLOT - splen)) | awk '{for(i=1;i<=NF;i++)if($i!=0){exit 1}}'; then + ok "the rest of copy 0 ($((SPL_SLOT - splen)) bytes) is zero padding" + else + bad "copy 0 has non-zero bytes after the payload end ($(hx $splen)) — mkimage padding changed" + fi +else + bad "SPL length_u32 says $splen bytes, outside [$((SPL_HDR_OFF + 12)), $SPL_SLOT] — 2017.03's mkimage truncates length_u32 silently (plan §3.5), so treat this as corruption" +fi + +if [ -n "${SPL_SIZE_LIMIT:-}" ]; then + if [ "$splen" -le "$SPL_SIZE_LIMIT" ]; then + ok "SPL payload $splen <= SPL_SIZE_LIMIT $SPL_SIZE_LIMIT (headroom $((SPL_SIZE_LIMIT - splen)) bytes)" + else + bad "SPL payload $splen > SPL_SIZE_LIMIT $SPL_SIZE_LIMIT — mainline's SPL_SIZE_CHECK would have failed this build" + fi +else + note "SPL_SIZE_LIMIT unset: only the $SPL_SLOT-byte slot was enforced. Pass the U-Boot tree's \`tools/spl_size_limit\` output to check the link-time limit too (plan §3.5: stock's 45,820 B, mainline 57,006 B of 62,752 B)." +fi + +# --- 4. Environment parity --------------------------------------------------- +echo +echo "[4] default_environment[] parity" + +spay=$((UIMG_OFF + UIMG_HDR)) +bpay=$((UIMG_OFF + UIMG_HDR)) + +payscan "$stock" "$spay" "$ssz" "$sload" env > "$tmp/stock.env.txt" || die "env scan of $stock failed" +if grep -q '^ENVERR' "$tmp/stock.env.txt"; then + die "$(sed -n 's/^ENVERR //p' "$tmp/stock.env.txt") in the reference $stock" +fi +sed -n 's/^ENVWARN / warning: /p' "$tmp/stock.env.txt" +sread=$(awk '$1=="ENV"{print $2}' "$tmp/stock.env.txt") +slen=$(awk '$1=="ENV"{print $3}' "$tmp/stock.env.txt") +scnt=$(awk '$1=="ENV"{print $4}' "$tmp/stock.env.txt") +note "stock env: payload offset $(hx "$sread"), $slen bytes, $scnt entries (file offset $(hx $((spay + sread))))" +if [ "$sread" -eq "$STOCK_ENV_OFF" ] && [ "$slen" -eq "$STOCK_ENV_LEN" ]; then + ok "stock env is where plan §6 says it is: $(hx $STOCK_ENV_OFF), $STOCK_ENV_LEN bytes" +else + note "reference note: stock env is at $(hx "$sread")/$slen B, plan §6 records $(hx $STOCK_ENV_OFF)/$STOCK_ENV_LEN B — the reference blob is not the one the plan measured" +fi + +bread="" +esize="" +if [ -n "$elf" ]; then + nmbin=${NM:-nm} + if nmline=$("$nmbin" -S "$elf" 2>/dev/null | awk '$4=="default_environment"{print $1" "$2; found=1} END{exit !found}'); then + eaddr=$((0x$(echo "$nmline" | cut -d' ' -f1))) + esize=$((0x$(echo "$nmline" | cut -d' ' -f2))) + bread=$((eaddr - bload)) + if [ "$bread" -lt 0 ] || [ $((bread + esize)) -gt "$bsz" ]; then + bad "the ELF puts default_environment outside the uImage payload — $(hx "$eaddr") size $esize is not inside [$(hx "$bload"), +$bsz) — wrong ELF for this image?" + bread="" + esize="" + else + note "$nmbin -S: default_environment at $(hx "$eaddr") size $esize -> payload offset $(hx "$bread")" + fi + else + note "$nmbin -S found no default_environment in $elf (stripped, or a different link) — falling back to the scan" + fi +fi + +payscan "$built" "$bpay" "$bsz" "$bload" env "${bread:--1}" > "$tmp/built.env.txt" || die "env scan of $built failed" +if grep -q '^ENVERR' "$tmp/built.env.txt"; then + bad "$(sed -n 's/^ENVERR //p' "$tmp/built.env.txt") in $built — no environment blob to compare" +else + boff=$(awk '$1=="ENV"{print $2}' "$tmp/built.env.txt") + blen=$(awk '$1=="ENV"{print $3}' "$tmp/built.env.txt") + bcnt=$(awk '$1=="ENV"{print $4}' "$tmp/built.env.txt") + note "built env: payload offset $(hx "$boff"), $blen bytes, $bcnt entries (file offset $(hx $((bpay + boff))))" + if [ -n "$bread" ]; then + note "located from the ELF symbol; the scan is only a cross-check" + fi + aoff=$(awk '$1=="ENVA"{print $2}' "$tmp/built.env.txt") + if [ -n "$aoff" ]; then + note "scan: $(awk '$1=="ENVA"{printf "anchored on \"%s\" (%d match(es) in the payload), blob at 0x%08x, %d bytes, %d entries", $5, $6, $2, $3, $4}' "$tmp/built.env.txt")" + if [ "$aoff" -ne "$boff" ]; then + note "the scan and the ELF disagree ($(hx "$aoff") vs $(hx "$boff")) — the ELF wins; a scan-only run of this image would compare the wrong bytes" + fi + elif [ -z "$bread" ]; then + note "scan: no usable anchor beyond the blob it found" + fi + if [ -n "$esize" ] && [ "$esize" -ne "$blen" ]; then + note "the ELF symbol is $esize B and the blob is $blen B — the symbol also covers the string literal's own trailing NUL and any alignment padding after the NUL-NUL terminator (plan §6: stock is 1,150 B of a 1,151 B symbol)" + fi + + dd if="$stock" bs=1 skip=$((spay + sread)) count="$slen" of="$tmp/stock.env.bin" 2>/dev/null + dd if="$built" bs=1 skip=$((bpay + boff)) count="$blen" of="$tmp/built.env.bin" 2>/dev/null + if [ "$blen" -ne "$slen" ]; then + bad "environment is $blen bytes, stock's is $slen bytes ($bcnt entries vs $scnt)" + fi + if cmp -s "$tmp/stock.env.bin" "$tmp/built.env.bin"; then + ok "environment is byte-identical to stock: $slen bytes, $scnt entries (\`mt\` carried, plan §3.4 — no allowed delta)" + else + bad "environment differs from stock's — every byte of default_environment[] is a forbidden diff (plan §6)" + cmp "$tmp/stock.env.bin" "$tmp/built.env.bin" 2>&1 | sed 's/^/ /' >&2 || true + echo " entry-by-entry diagnostic (-stock +built):" >&2 + sed -n 's/^ENTRY [0-9]* [0-9]* //p' "$tmp/stock.env.txt" > "$tmp/stock.entries" + sed -n 's/^ENTRY [0-9]* [0-9]* //p' "$tmp/built.env.txt" > "$tmp/built.entries" + diff -u "$tmp/stock.entries" "$tmp/built.entries" 2>&1 | sed 's/^/ /' >&2 || true + fi +fi + +# --- 5. Command table -------------------------------------------------------- +echo +echo "[5] command table" + +payscan "$stock" "$spay" "$ssz" "$sload" cmd > "$tmp/stock.cmd.txt" || die "command scan of $stock failed" +if grep -q '^CMDERR' "$tmp/stock.cmd.txt"; then + die "no command table found in the reference $stock" +fi +payscan "$built" "$bpay" "$bsz" "$bload" cmd > "$tmp/built.cmd.txt" || die "command scan of $built failed" + +sed -n 's/^NAME //p' "$tmp/stock.cmd.txt" | sort -u > "$tmp/stock.names" +snames=$(wc -l < "$tmp/stock.names" | tr -d ' ') +note "stock: $(awk '$1=="TABLE"{print $2}' "$tmp/stock.cmd.txt") entries at payload offset $(hx "$(awk '$1=="TABLE"{print $3}' "$tmp/stock.cmd.txt")"), stride $(awk '$1=="TABLE"{print $4}' "$tmp/stock.cmd.txt") (allowed diff: offset and stride)" + +if grep -q '^CMDERR' "$tmp/built.cmd.txt"; then + bad "no command table found in $built" +else + sed -n 's/^NAME //p' "$tmp/built.cmd.txt" | sort -u > "$tmp/built.names" + bnames=$(wc -l < "$tmp/built.names" | tr -d ' ') + note "built: $(awk '$1=="TABLE"{print $2}' "$tmp/built.cmd.txt") entries at payload offset $(hx "$(awk '$1=="TABLE"{print $3}' "$tmp/built.cmd.txt")"), stride $(awk '$1=="TABLE"{print $4}' "$tmp/built.cmd.txt")" + comm -23 "$tmp/stock.names" "$tmp/built.names" > "$tmp/missing" + comm -13 "$tmp/stock.names" "$tmp/built.names" > "$tmp/extra" + if [ -s "$tmp/missing" ]; then + bad "$(wc -l < "$tmp/missing" | tr -d ' ') of stock's $snames commands are missing: $(tr '\n' ' ' < "$tmp/missing")" + else + ok "all $snames stock command names are present ($bnames total in the build)" + fi + if grep -qx 'mt' "$tmp/built.names"; then + ok "\`mt\` is present — stock's fpgacheck runs unmodified (plan §3.4)" + else + bad "\`mt\` is missing: stock's fpgacheck would fail on every boot (plan §3.4)" + fi + if [ -s "$tmp/extra" ]; then + note "extra commands, allowed: $(tr '\n' ' ' < "$tmp/extra")" + else + note "no extra commands beyond stock's $snames" + fi +fi + +# --- 6. Verdict -------------------------------------------------------------- +echo +echo "[6] allowed diffs (plan §6): version string and build timestamp; uImage" +echo " ih_ep $(hx $TEXT_BASE) vs 0x00000000; total image size; code layout and" +echo " table offsets inside the SPL; extra commands. Everything else above is" +echo " a hard contract." + +if [ "$fail" -eq 0 ]; then + echo "$prog: parity holds" +else + echo "$prog: CONTRACT VIOLATED" >&2 +fi +exit "$fail" diff --git a/scripts/ci-tests.sh b/scripts/ci-tests.sh index 7ae655c0..14d929ff 100755 --- a/scripts/ci-tests.sh +++ b/scripts/ci-tests.sh @@ -348,6 +348,48 @@ else fi fi +# ============================================================================= +section "DE25-Nano -- QSPI-write audit, Linux side (de25-boot-chain.md section 7 row 11)" +# ============================================================================= +# The U-Boot-side half of this audit lives in external.mk's +# MISTER_UBOOT_DE25_QSPI_AUDIT hook (docs/uboot-tasks.md DU2), which runs +# inside the U-Boot build and asserts the resolved .config and the built +# u-boot.itb. This is the half that hook cannot see: a rootfs-shipped +# fw_env.config whose device line names an MTD device lets Linux-side +# `fw_setenv`/libubootenv write QSPI directly, without U-Boot involved -- +# row 5's CONFIG_ENV_IS_IN_UBI=n guard does not protect this path, because +# fw_setenv does not consult U-Boot's compiled-in env driver at all +# (de25-boot-chain.md section 7 row 11: "Brick-class and silent"). +# +# The grep is deliberately broad and fail-closed: any uncommented line naming +# "mtd" OR "ubi" fails. A UBI volume (/dev/ubi0_0) is the Linux-side twin of +# row 5's hazard -- fw_setenv writing a UBI volume attaches and writes the +# same QSPI MTD -- so it is caught by the same check rather than left to a +# second one. +DE25_TARGET="$ROOT/output-de25/target" +if [ ! -d "$DE25_TARGET" ]; then + skip "DE25 rootfs: no fw_env.config names an MTD or UBI device" "no $DE25_TARGET -- the DE25 stack has not been built" +else + _de25_fwenv_n=0 + _de25_bad_n=0 + while IFS= read -r _de25_f; do + _de25_fwenv_n=$((_de25_fwenv_n + 1)) + _de25_hits=$(grep -viE '^[[:space:]]*#' "$_de25_f" | grep -iE 'mtd|ubi' || true) + if [ -n "$_de25_hits" ]; then + note "$_de25_f:" + printf '%s\n' "$_de25_hits" | while IFS= read -r _de25_hl; do note " $_de25_hl"; done + _de25_bad_n=$((_de25_bad_n + 1)) + fi + done < <(find "$DE25_TARGET" -name 'fw_env.config' -type f 2>/dev/null) + if [ "$_de25_fwenv_n" -eq 0 ]; then + pass "DE25 rootfs: no fw_env.config shipped -- nothing to audit" + elif [ "$_de25_bad_n" -gt 0 ]; then + fail "DE25 rootfs: fw_env.config names an MTD or UBI device" "de25-boot-chain.md section 7 row 11 -- fw_setenv would reach QSPI directly, bypassing every U-Boot-side guard (and external.mk's MISTER_UBOOT_DE25_QSPI_AUDIT cannot see this file); see the file(s) noted above" + else + pass "DE25 rootfs: fw_env.config present but names no MTD or UBI device ($_de25_fwenv_n file(s))" + fi +fi + # ============================================================================= section "ABI / stock-binary smoke (P2.2 + P2.8 core checks)" # ============================================================================= diff --git a/scripts/lib/qts-tables.py b/scripts/lib/qts-tables.py new file mode 100755 index 00000000..db359417 --- /dev/null +++ b/scripts/lib/qts-tables.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +"""qts-tables.py -- pack the seven Altera QTS handoff tables +(docs/uboot-mainline-port.md Sec 3.2a) out of a `qts/*.h` header directory and +search for each one inside SPL copy 0 (the first 0x10000 bytes) of one or more +U-Boot images. + +WHY THIS IS PYTHON, NOT sh (sanctioned style exception -- docs/uboot-tasks.md +U4b). Every other check script in this repo is POSIX sh (scripts/ +check-zimage-dtb.sh house style): no dependency beyond the host shell and +coreutils. This one packs C initialiser lists into little-endian machine words +and does a substring search over an 8 KiB-ish binary window -- doable in sh +with od/printf, but not legibly, and Buildroot already requires host python3 +for its own build (support/scripts/*, and this repo's own scripts/ +gen-db-json.py, scripts/check-db-json-schema.py, scripts/db_entity_contract.py +already run under it in CI). So this one piece is a small python3 module, +invoked from the POSIX sh wrapper (scripts/check-uboot-handoff.sh), which does +everything else -- argument validation, ok()/bad() reporting, exit-code +discipline -- in the repo's usual house style. + +WHERE THE PARSER CAME FROM. The C-initialiser-list extraction below (parse_arrays) +is lifted, not reinvented, from the research spike's own diff tool: +/mnt/source/uboot-mainline/verify-qts/qtsdiff.py (outside the repo, gitignored by +being outside it; never committed). Only the array half is kept -- qtsdiff.py +also diffs #define scalars between two trees, which this tool has no need for, +since it is proving *presence* of specific named arrays in a binary, not +diffing two source trees against each other. The comment-stripping and +brace-balancing logic is otherwise unchanged from that tool. + +WHAT THIS PROVES AND DOES NOT. Per plan Sec 3.2a: packing a table byte-identically +and finding it inside SPL copy 0 proves the SPL was built from a `qts/*.h` tree +whose value for that table matches what was packed. It says nothing about +*where* in the image it lives (offsets are reported, never compared between +images -- code layout differs) and nothing about the scalar #defines that never +appear in the binary as byte strings (those compile to instruction immediates; +Sec 3.2a is explicit that this method cannot touch them). + +THE SEVEN TABLES (name -> C element type -> packing): + sys_mgr_init_table u8 raw bytes, one byte per element + iocsr_scan_chain0_table u32 little-endian, 4 bytes per element + iocsr_scan_chain1_table u32 little-endian, 4 bytes per element + iocsr_scan_chain2_table u32 little-endian, 4 bytes per element + iocsr_scan_chain3_table u32 little-endian, 4 bytes per element + ac_rom_init u32 little-endian, 4 bytes per element + inst_rom_init u32 little-endian, 4 bytes per element + +Usage: + qts-tables.py [ ...] + + a directory containing the four `qts/*.h` headers (any subset + that between them define all seven tables above; which .h file + holds which table is not assumed -- every *.h directly inside + the directory is parsed and their array definitions merged). + one or more U-Boot images (a built `u-boot-with-spl.sfp` or a + stock `uboot.img`). SPL copy 0 is taken as the image's own + first 0x10000 bytes, per boot-chain's four-copy SPL layout. + +Output (stdout, tab-separated, one line per table x image, table order fixed +as listed above, image order as given on the command line): + + \t\t\t\t\t + +followed by one summary line: + + SUMMARY\ttables=\timages=\tmissing= + +Exit: 0 = every table found in every image, 1 = at least one (table, image) +pair is missing -- a real assertion failure, not a crash, 2 = usage/parse/IO +error (bad arguments, a table not DEFINED anywhere under , an image +that does not exist or is shorter than the SPL-copy window). +""" + +import glob +import os +import re +import struct +import sys + +SPL_COPY_SIZE = 0x10000 # boot-chain: four byte-identical 64 KiB SPL copies + +# (name, C element type). Order matches docs/uboot-mainline-port.md Sec 3.2a's table. +TABLES = [ + ("sys_mgr_init_table", "u8"), + ("iocsr_scan_chain0_table", "u32"), + ("iocsr_scan_chain1_table", "u32"), + ("iocsr_scan_chain2_table", "u32"), + ("iocsr_scan_chain3_table", "u32"), + ("ac_rom_init", "u32"), + ("inst_rom_init", "u32"), +] + + +def parse_arrays(path): + """Extract every `TYPE name[] = { ... };` initialiser in a C header as + {name: [raw element token, ...]}. Lifted from qtsdiff.py's parse() (see + module docstring) -- comments are stripped from the whole file first (so a + per-element trailing comment like `0, /* EMACIO0 */` does not need + special-casing), then each `[...] = {` is brace-balanced across lines and + split on commas. + """ + txt = open(path, encoding="utf-8", errors="replace").read() + txt = re.sub(r"/\*.*?\*/", "", txt, flags=re.S) + txt = re.sub(r"//[^\n]*", "", txt) + arrays = {} + lines = txt.split("\n") + i = 0 + while i < len(lines): + line = lines[i] + if "[" in line and "=" in line and re.search(r"\[\s*\]\s*=", line): + m = re.search(r"([A-Za-z_][A-Za-z0-9_]*)\s*\[\s*\]\s*=", line) + if m: + name = m.group(1) + body = line[line.index("=") + 1:] + depth = body.count("{") - body.count("}") + while depth > 0 or "{" not in body: + i += 1 + if i >= len(lines): + break + body += "\n" + lines[i] + depth = body.count("{") - body.count("}") + inner = body[body.index("{") + 1: body.rindex("}")] + words = [w.strip() for w in inner.replace("\n", " ").split(",") if w.strip()] + arrays[name] = words + i += 1 + return arrays + + +def load_arrays(qts_dir): + """Merge the array definitions of every *.h directly inside qts_dir.""" + merged = {} + headers = sorted(glob.glob(os.path.join(qts_dir, "*.h"))) + if not headers: + raise LookupError("no *.h files found directly inside %r" % (qts_dir,)) + for h in headers: + merged.update(parse_arrays(h)) + return merged + + +def pack_table(words, kind): + vals = [int(w, 0) for w in words] + if kind == "u8": + return bytes(v & 0xFF for v in vals) + if kind == "u32": + return b"".join(struct.pack(" {name: (packed_bytes, n_elements)} for all of TABLES. + + Raises LookupError naming any table not DEFINED anywhere under qts_dir -- + that is a usage/input error (exit 2), distinct from a table that parses + fine but is simply absent from an image's bytes (exit 1). + """ + arrays = load_arrays(qts_dir) + missing_defs = [name for name, _kind in TABLES if name not in arrays] + if missing_defs: + raise LookupError( + "not defined in any *.h under %r: %s" % (qts_dir, ", ".join(missing_defs)) + ) + packed = {} + for name, kind in TABLES: + words = arrays[name] + packed[name] = (pack_table(words, kind), len(words)) + return packed + + +def spl_copy0(image_path): + with open(image_path, "rb") as f: + data = f.read(SPL_COPY_SIZE) + if len(data) < SPL_COPY_SIZE: + raise LookupError( + "%r is only %d bytes, shorter than one SPL copy (0x%x)" + % (image_path, len(data), SPL_COPY_SIZE) + ) + return data + + +def main(argv): + prog = "qts-tables.py" + if len(argv) < 3: + sys.stderr.write( + "usage: %s [ ...]\n" % (prog,) + ) + return 2 + + qts_dir, images = argv[1], argv[2:] + + if not os.path.isdir(qts_dir): + sys.stderr.write("%s: no such directory: %s\n" % (prog, qts_dir)) + return 2 + + try: + packed = pack_tables(qts_dir) + except (LookupError, ValueError, OSError) as e: + sys.stderr.write("%s: %s\n" % (prog, e)) + return 2 + except SyntaxError as e: + sys.stderr.write("%s: parse error: %s\n" % (prog, e)) + return 2 + + copies = {} + for image in images: + if not os.path.isfile(image): + sys.stderr.write("%s: no such file: %s\n" % (prog, image)) + return 2 + try: + copies[image] = spl_copy0(image) + except LookupError as e: + sys.stderr.write("%s: %s\n" % (prog, e)) + return 2 + + missing = 0 + for name, _kind in TABLES: + blob, n_elements = packed[name] + for image in images: + offset = copies[image].find(blob) + if offset >= 0: + print( + "%s\t%s\tFOUND\t0x%05x\t%d\t%d" + % (name, image, offset, len(blob), n_elements) + ) + else: + missing += 1 + print( + "%s\t%s\tMISSING\t-\t%d\t%d" + % (name, image, len(blob), n_elements) + ) + + print( + "SUMMARY\ttables=%d\timages=%d\tmissing=%d" + % (len(TABLES), len(images), missing) + ) + + return 1 if missing else 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/scripts/test-uboot-handoff.sh b/scripts/test-uboot-handoff.sh new file mode 100755 index 00000000..a76883be --- /dev/null +++ b/scripts/test-uboot-handoff.sh @@ -0,0 +1,162 @@ +#!/bin/sh +# +# test-uboot-handoff.sh — fixture proof for scripts/check-uboot-handoff.sh +# (docs/uboot-tasks.md U4b; docs/uboot-mainline-port.md Sec 3.2a, Sec 6). +# +# Two fixtures, both run against the pinned stock `uboot.img` +# (sha256 e2d46cf9fe1ec40ca2c9c7409870249f267e06f70e5736dc6d30b4e21fe62a64, +# fetched elsewhere by scripts/fetch-sdcard-payload.sh's STOCK_UBOOT_SHA256 -- +# this script does not fetch it itself, see STOCK_UBOOT_IMG below): +# +# 1. PASS -- the carried fork's `qts/*.h` (work/U-Boot_MiSTer, commit +# 8dcc3484) must find all seven handoff tables in the stock blob's SPL +# copy 0, at exactly the offsets plan Sec 3.2a lists. This is the +# positive proof that the stock binary was built from these headers. +# +# 2. FAIL -- pristine mainline 2026.07's OWN `qts/*.h` (extracted fresh +# from the pinned dl/uboot/u-boot-2026.07.tar.bz2 tarball, never the +# carried/patched copy) must NOT find four of the seven tables -- +# `sys_mgr_init_table` and IOCSR chains 0-2, the values MiSTer +# deliberately replaced (plan Sec 3.2) -- while still finding the other +# three (chain3, ac_rom_init, inst_rom_init: proven never to have +# diverged). This negative run is the whole point of the gate: it +# proves check-uboot-handoff.sh can actually detect the wrong headers +# landing in a build, not just rubber-stamp whatever tree it is given +# -- the one failure mode a cold-boot smoke test cannot catch (plan +# Sec 3.2a's payoff paragraph). Its transcript is printed verbatim, +# delimited, for docs/verification/uboot-mainline.md to quote later. +# +# Neither fixture builds anything or touches a board (plan Sec 7, Sec 8). +# +# Deliberately not `set -e`: both fixtures must run and be judged even if +# one behaves unexpectedly, so the report shows the whole picture in one +# pass (same rationale as scripts/test-ntp-kick.sh and scripts/ +# test-timezone.sh). +# +# Environment overrides (all optional; defaults match this repo/session): +# FORK_QTS_DIR directory holding the carried fork's qts/*.h. +# Default: work/U-Boot_MiSTer/board/terasic/de10-nano/qts +# (the read-only fork checkout at commit 8dcc3484). +# UBOOT_TARBALL the pinned mainline U-Boot source tarball -- used ONLY +# to extract ITS OWN qts/*.h for fixture 2, never built. +# Default: dl/uboot/u-boot-2026.07.tar.bz2 +# STOCK_UBOOT_IMG the reference stock uboot.img both fixtures run +# against (sha256 e2d46cf9...62a64 expected; not +# reverified here -- that is fetch-sdcard-payload.sh's +# job, this script only reads whatever path it is given). +# Default: /mnt/source/uboot-wave-a/stock/mister-payload/linux/uboot.img +# +# Usage: scripts/test-uboot-handoff.sh +# Exit: 0 = both fixtures behaved exactly as documented above, 1 = a fixture +# behaved unexpectedly (each such case named on stderr as a FAIL +# line), 2 = usage/IO/setup error -- a fixture input is missing or the +# tarball has no de10-nano qts/*.h members. A setup error is a +# repo/environment problem, not a finding about the check script. + +set -u + +prog=${0##*/} +scriptdir=$(cd -- "$(dirname -- "$0")" && pwd) +repo_root=$(cd -- "$scriptdir/.." && pwd) +check="$scriptdir/check-uboot-handoff.sh" + +: "${FORK_QTS_DIR:=$repo_root/work/U-Boot_MiSTer/board/terasic/de10-nano/qts}" +: "${UBOOT_TARBALL:=$repo_root/dl/uboot/u-boot-2026.07.tar.bz2}" +: "${STOCK_UBOOT_IMG:=/mnt/source/uboot-wave-a/stock/mister-payload/linux/uboot.img}" + +fail=0 + +note() { printf ' %s\n' "$*"; } +ok() { printf 'ok %s\n' "$*"; } +bad() { printf 'FAIL %s\n' "$*" >&2; fail=1; } + +[ -x "$check" ] || { echo "$prog: missing or not executable: $check" >&2; exit 2; } +[ -d "$FORK_QTS_DIR" ] || { echo "$prog: FORK_QTS_DIR not found: $FORK_QTS_DIR" >&2; exit 2; } +[ -f "$UBOOT_TARBALL" ] || { echo "$prog: UBOOT_TARBALL not found: $UBOOT_TARBALL" >&2; exit 2; } +[ -f "$STOCK_UBOOT_IMG" ] || { echo "$prog: STOCK_UBOOT_IMG not found: $STOCK_UBOOT_IMG" >&2; exit 2; } + +workdir=$(mktemp -d) || { echo "$prog: mktemp -d failed" >&2; exit 2; } +trap 'rm -rf "$workdir"' EXIT + +echo "$prog: extracting pristine mainline qts/*.h from $UBOOT_TARBALL" +members=$(tar -tjf "$UBOOT_TARBALL" | grep -E '/board/terasic/de10-nano/qts/[^/]+\.h$') +if [ -z "$members" ]; then + echo "$prog: no board/terasic/de10-nano/qts/*.h members in $UBOOT_TARBALL" >&2 + exit 2 +fi +printf '%s\n' "$members" | tar -xjf "$UBOOT_TARBALL" -C "$workdir" -T - +pristine_qts_dir=$(find "$workdir" -type d -name qts | head -n1) +[ -n "$pristine_qts_dir" ] || { echo "$prog: extraction did not produce a qts/ directory" >&2; exit 2; } +note "fork qts dir: $FORK_QTS_DIR" +note "pristine qts dir: $pristine_qts_dir" +note "stock uboot.img: $STOCK_UBOOT_IMG" + +# --- Fixture 1: fork headers must PASS, at plan Sec 3.2a's exact offsets --- +echo +echo "$prog: === fixture 1 (PASS expected): fork qts/*.h vs stock uboot.img ===" +rc1=0 +out1=$("$check" "$FORK_QTS_DIR" "$STOCK_UBOOT_IMG" "$STOCK_UBOOT_IMG" 2>&1) || rc1=$? +printf '%s\n' "$out1" +echo "$prog: fixture 1 exit=$rc1" + +if [ "$rc1" -eq 0 ]; then + ok "fixture 1 exited 0 (PASS), as expected" +else + bad "fixture 1 exited $rc1, expected 0 -- the fork's own headers must pass against the stock blob built from them" +fi + +for pair in \ + 'sys_mgr_init_table:0x0aac8' \ + 'iocsr_scan_chain0_table:0x096b8' \ + 'iocsr_scan_chain1_table:0x09718' \ + 'iocsr_scan_chain2_table:0x097f0' \ + 'iocsr_scan_chain3_table:0x09868' \ + 'ac_rom_init:0x090f8' \ + 'inst_rom_init:0x094bc' +do + table=${pair%%:*} + offset=${pair#*:} + case $out1 in + *"$table @ $offset in"*) ok "fixture 1: $table @ $offset matches plan Sec 3.2a" ;; + *) bad "fixture 1: $table not reported at $offset (plan Sec 3.2a)" ;; + esac +done + +# --- Fixture 2: pristine mainline headers must FAIL, naming 4 tables --- +echo +echo "$prog: === fixture 2 (FAIL expected): pristine mainline qts/*.h vs stock uboot.img ===" +rc2=0 +out2=$("$check" "$pristine_qts_dir" "$STOCK_UBOOT_IMG" "$STOCK_UBOOT_IMG" 2>&1) || rc2=$? +echo "$prog: ---- BEGIN fixture 2 transcript (verbatim; for docs/verification/uboot-mainline.md) ----" +printf '%s\n' "$out2" +echo "$prog: ---- END fixture 2 transcript ----" +echo "$prog: fixture 2 exit=$rc2" + +if [ "$rc2" -eq 1 ]; then + ok "fixture 2 exited 1 (FAIL), as expected -- pristine mainline headers must NOT pass" +else + bad "fixture 2 exited $rc2, expected 1" +fi + +for table in sys_mgr_init_table iocsr_scan_chain0_table iocsr_scan_chain1_table iocsr_scan_chain2_table; do + case $out2 in + *"$table NOT FOUND in"*) ok "fixture 2: $table correctly reported NOT FOUND (diverged, plan Sec 3.2)" ;; + *) bad "fixture 2: $table was not reported as missing (expected absent from the stock blob)" ;; + esac +done + +for table in iocsr_scan_chain3_table ac_rom_init inst_rom_init; do + case $out2 in + *"$table @"*) ok "fixture 2: $table still found (never diverged, plan Sec 3.2)" ;; + *) bad "fixture 2: $table unexpectedly not found (it never diverged, plan Sec 3.2)" ;; + esac +done + +echo +if [ "$fail" -eq 0 ]; then + echo "$prog: both fixtures behaved as documented" + exit 0 +else + echo "$prog: TEST FAILURE -- see FAIL lines above" >&2 + exit 1 +fi diff --git a/scripts/test-uboot-parity.sh b/scripts/test-uboot-parity.sh new file mode 100755 index 00000000..975fba0a --- /dev/null +++ b/scripts/test-uboot-parity.sh @@ -0,0 +1,370 @@ +#!/bin/sh +# +# test-uboot-parity.sh — fixture proof for scripts/check-uboot-parity.sh +# (docs/uboot-tasks.md U4a; docs/uboot-mainline-port.md Sec 6). +# +# WHY THIS EXISTS. check-uboot-parity.sh is a gate that will normally say +# "parity holds", and a gate that only ever passes is indistinguishable from a +# gate that cannot fail. There is no board and, until U2g, no built .sfp +# either, so the only way to show the checker can actually detect the three +# brick-class defects it exists for is to manufacture them. Each fixture below +# is one of those defects, injected into a COPY of the pinned stock uboot.img, +# and each case asserts both the exit status and the specific message — a +# fixture that fails for the wrong reason is not a passing fixture. +# +# Isolation is deliberate. Every mutation that lands inside the U-Boot proper +# payload also invalidates the uImage payload CRC, which would let a checker +# "detect" an environment edit purely by accident. So the environment and +# command-table fixtures RE-STAMP the payload CRC after mutating, and then +# assert that the uImage CRC line still passes — proving the environment and +# command-table checks stand on their own. The re-stamping CRC32 is taken from +# gzip's trailer (a gzip member stores the CRC32 of the uncompressed data), +# which is a completely independent implementation of the same CRC from the one +# the checker computes in awk; the two agreeing on the unmutated blob is itself +# a cross-check of the checker's arithmetic. +# +# The fixtures (all desk work — no build, no board, nothing fetched): +# +# 1. stock-vs-stock must PASS (exit 0). The .sfp argument may be a +# stock uboot.img; parity with itself is trivially +# true, and anything the checker gets wrong about +# the shipped blob shows up here first. +# 2. env-byte-flipped one byte of default_environment[] altered, CRC +# re-stamped -> exit 1, "environment differs", +# entry-by-entry diagnostic printed, uImage CRC +# still ok. +# 3. spl-copy-differs one byte of SPL copy 2's zero padding set -> +# exit 1, "SPL copy 2 ... differs from copy 0". +# 4. uimage-crc-wrong one payload byte flipped, CRC NOT re-stamped -> +# exit 1, "uImage payload CRC ... recomputes to". +# 5. cmd-table-broken the `mt` command record's name pointer zeroed, +# CRC re-stamped -> exit 1, commands missing and +# `mt` called out by name (plan Sec 3.4). +# 6. ih-ep-bogus ih_ep set to a value that is neither the fork's +# 0 nor mainline's CONFIG_TEXT_BASE, header CRC +# re-stamped -> exit 1. Proves the ih_ep allowed +# diff (plan Sec 3.3) is bounded to those two. +# 7. missing-file exit 2, not 1: an absent input is an IO error, +# not a contract violation. +# 8. truncated-file exit 2 for the same reason. +# 9. elf-right the optional third argument: a stub ELF that +# declares default_environment at the address +# stock's would have must be believed, and its +# 1,151-byte symbol reconciled with the 1,150-byte +# blob (plan Sec 6's constant nit). +# 10. elf-wrong the same stub linked elsewhere -> exit 1, the +# checker says so instead of reading rubbish. +# +# Sections [1]-[6] are fixtures 1-6; [7] holds fixtures 7 and 8, [8] holds 9 +# and 10. +# +# Fixtures 2-6 target byte offsets that are properties of the PINNED stock blob +# (sha256 e2d46cf9...62a64), so this script checks that hash before trusting +# them and every offset is guarded at the point of use (e.g. fixture 5 resolves +# the record's name pointer and confirms it reads "mt" before zeroing it). +# +# Deliberately not `set -e`: every case must run and be judged even if an +# earlier one behaves unexpectedly, so one pass shows the whole picture (same +# rationale as scripts/test-ntp-kick.sh, test-timezone.sh, test-uboot-handoff.sh). +# +# Usage: scripts/test-uboot-parity.sh [stock-uboot.img] +# Env: +# STOCK_UBOOT_IMG the reference blob, if not given as $1. Default: +# /output-sdcard-stage/mister-payload/linux/uboot.img, +# where scripts/fetch-sdcard-payload.sh stages it. +# WORKDIR where the fixtures are written. Default: a mktemp -d that +# is removed on exit. Set it to keep the fixtures and logs. +# Exit: 0 = every case behaved, 1 = a case did not, 2 = setup/IO error. + +set -u + +export LC_ALL=C # byte-wise, like the checker it drives + +STOCK_SHA256=e2d46cf9fe1ec40ca2c9c7409870249f267e06f70e5736dc6d30b4e21fe62a64 +STOCK_SIZE=515141 + +# Offsets inside the pinned blob. PAY is where the U-Boot proper payload starts +# (0x40000 uImage + 64-byte header); the other two are payload-relative and are +# re-derived in docs/uboot-mainline-port.md Sec 6 / this task's evidence. +PAY=262208 # 0x40040 +ENV_OFF=163864 # 0x28018 — default_environment[], 1150 B, 21 entries +MT_REC=211336 # 0x33988 — the `mt` record in the U_BOOT_CMD linker list + # (table at payload 0x33410, 28-byte stride, 51st entry) +SPL_SLOT=65536 +UIMG_OFF=262144 # 0x40000 + +prog=${0##*/} +here=$(cd -- "$(dirname -- "$0")" && pwd) || exit 2 +root=$(cd -- "$here/.." && pwd) || exit 2 +CHECK=$here/check-uboot-parity.sh + +pass=0 +fail=0 +skip=0 + +note() { printf ' %s\n' "$*"; } +ok() { pass=$((pass + 1)); printf 'ok %s\n' "$*"; } +bad() { fail=$((fail + 1)); printf 'FAIL %s\n' "$*" >&2; } +skipped() { skip=$((skip + 1)); printf 'skip %s\n' "$*"; } +die() { echo "$prog: $*" >&2; exit 2; } + +stock=${1:-${STOCK_UBOOT_IMG:-$root/output-sdcard-stage/mister-payload/linux/uboot.img}} +[ -x "$CHECK" ] || die "$CHECK is missing or not executable" +[ -f "$stock" ] || die "stock uboot.img not found at $stock + pass it as \$1 or \$STOCK_UBOOT_IMG, or stage it with scripts/fetch-sdcard-payload.sh" + +got=$(sha256sum "$stock" | cut -d' ' -f1) +[ "$got" = "$STOCK_SHA256" ] || + die "$stock is sha256 $got, not the pinned $STOCK_SHA256 — the fixture offsets below are properties of that exact blob" +[ "$(wc -c < "$stock" | tr -d ' ')" -eq "$STOCK_SIZE" ] || die "$stock is not $STOCK_SIZE bytes" + +if [ -n "${WORKDIR:-}" ]; then + mkdir -p "$WORKDIR" || die "cannot create WORKDIR $WORKDIR" + W=$WORKDIR + keep=1 +else + W=$(mktemp -d "${TMPDIR:-/tmp}/test-uboot-parity.XXXXXX") || die "mktemp -d failed" + keep=0 + trap 'rm -rf "$W"' EXIT HUP INT TERM +fi + +printf '%s: checker %s\n' "$prog" "$CHECK" +printf '%s: stock %s (sha256 ok)\n' "$prog" "$stock" +printf '%s: fixtures in %s%s\n\n' "$prog" "$W" "$([ "$keep" -eq 1 ] && echo ' (kept)')" + +# --- byte-level fixture surgery --------------------------------------------- + +# u32le FILE OFF / u32be FILE OFF -> decimal +u32le() { + _b=$(od -An -tx1 -j "$2" -N 4 -v "$1" | tr -d ' \n') + printf '%d' "0x$(echo "$_b" | cut -c7-8)$(echo "$_b" | cut -c5-6)$(echo "$_b" | cut -c3-4)$(echo "$_b" | cut -c1-2)" +} + +# putbytes FILE OFF B0 [B1...] — write decimal bytes at OFF, in place +putbytes() { + _f=$1 + _o=$2 + shift 2 + for _b in "$@"; do + # shellcheck disable=SC2059 # the octal escape IS the format string: + # printf '\\ooo' is the POSIX way to emit one arbitrary byte, and + # printf '%c' would go through the locale (awk's would emit UTF-8). + printf "\\$(printf '%03o' "$_b")" + done | dd of="$_f" bs=1 seek="$_o" conv=notrunc 2>/dev/null +} + +# put32be FILE OFF VALUE +put32be() { + putbytes "$1" "$2" \ + $(( ($3 / 16777216) % 256 )) $(( ($3 / 65536) % 256 )) \ + $(( ($3 / 256) % 256 )) $(( $3 % 256 )) +} + +# flipbyte FILE OFF MASK — xor one byte in place +flipbyte() { + _v=$(od -An -tu1 -j "$2" -N 1 -v "$1" | tr -d ' \n') + putbytes "$1" "$2" $(( _v ^ $3 )) +} + +# crc32 FILE OFF LEN -> lowercase hex, from gzip's trailer (RFC 1952 Sec 2.3.1: +# the last 8 bytes of a member are CRC32 then ISIZE, both little-endian). +crc32() { + dd if="$1" bs=1 skip="$2" count="$3" 2>/dev/null | gzip -cn | tail -c 8 | + od -An -tx1 -v | awk '{printf "%s%s%s%s\n", $4, $3, $2, $1}' +} + +# repair_dcrc FILE — re-stamp the uImage payload CRC after mutating the payload +repair_dcrc() { + _sz=$(od -An -tx1 -j $((UIMG_OFF + 12)) -N 4 -v "$1" | tr -d ' \n') + _sz=$(printf '%d' "0x$_sz") + _c=$(crc32 "$1" $((UIMG_OFF + 64)) "$_sz") + put32be "$1" $((UIMG_OFF + 24)) "$(printf '%d' "0x$_c")" +} + +# repair_hcrc FILE — re-stamp the uImage header CRC (computed over the 64-byte +# header with its own ih_hcrc field zeroed), after mutating a header field +repair_hcrc() { + { + dd if="$1" bs=1 skip="$UIMG_OFF" count=4 2>/dev/null + printf '\000\000\000\000' + dd if="$1" bs=1 skip=$((UIMG_OFF + 8)) count=56 2>/dev/null + } | gzip -cn | tail -c 8 | od -An -tx1 -v | + awk '{printf "%s%s%s%s\n", $4, $3, $2, $1}' > "$W/.hcrc" + put32be "$1" $((UIMG_OFF + 4)) "$(printf '%d' "0x$(cat "$W/.hcrc")")" +} + +# --- case plumbing ----------------------------------------------------------- + +# run NAME ARGS... -> runs the checker, logs to $W/NAME.log, sets $rc +run() { + _n=$1 + shift + "$CHECK" "$@" > "$W/$_n.log" 2>&1 + rc=$? +} + +want_rc() { # want_rc NAME WANT + if [ "$rc" -eq "$2" ]; then + ok "$1: exit $rc" + else + bad "$1: exit $rc, wanted $2" + sed 's/^/ /' "$W/$1.log" + fi +} + +want_say() { # want_say NAME TEXT... + _n=$1 + shift + if grep -qF -- "$*" "$W/$_n.log"; then + ok "$_n: says \"$*\"" + else + bad "$_n: never says \"$*\"" + sed 's/^/ /' "$W/$_n.log" + fi +} + +want_quiet() { # want_quiet NAME TEXT... — must NOT appear + _n=$1 + shift + if grep -qF -- "$*" "$W/$_n.log"; then + bad "$_n: unexpectedly says \"$*\"" + sed 's/^/ /' "$W/$_n.log" + else + ok "$_n: does not say \"$*\"" + fi +} + +# --- 1. stock vs stock ------------------------------------------------------- +echo "[1] stock-vs-stock: a stock uboot.img is in parity with itself" +run stock-vs-stock "$stock" "$stock" +want_rc stock-vs-stock 0 +want_say stock-vs-stock "parity holds" +want_say stock-vs-stock "all 69 stock command names are present" +want_say stock-vs-stock "environment is byte-identical to stock: 1150 bytes, 21 entries" +want_say stock-vs-stock "SPL copy 3 at 0x00030000 is byte-identical to copy 0" +want_say stock-vs-stock "header checksum recomputes: 0x01e0" +want_say stock-vs-stock "SPL payload CRC32 over [0,45816) recomputes: 0x35391b6b" +want_say stock-vs-stock "uImage payload CRC recomputes over all 252933 bytes: 0xce778166" +echo + +# --- 2. one environment byte flipped ---------------------------------------- +echo "[2] env-byte-flipped: one byte of default_environment[], payload CRC re-stamped" +cp "$stock" "$W/env-flip.img" || die "cp failed" +flipbyte "$W/env-flip.img" $((PAY + ENV_OFF + 9)) 1 # "console" -> "bonsole" +repair_dcrc "$W/env-flip.img" +run env-flip "$W/env-flip.img" "$stock" +want_rc env-flip 1 +want_say env-flip "environment differs from stock" +want_say env-flip "entry-by-entry diagnostic" +want_say env-flip "+bootargs=bonsole=ttyS0,115200" +want_say env-flip "uImage payload CRC recomputes" # the re-stamp worked: isolated +want_say env-flip "CONTRACT VIOLATED" +echo + +# --- 3. one SPL copy differs ------------------------------------------------- +echo "[3] spl-copy-differs: one byte of copy 2's zero padding" +cp "$stock" "$W/spl2.img" || die "cp failed" +putbytes "$W/spl2.img" $((2 * SPL_SLOT + 49152)) 255 +run spl2 "$W/spl2.img" "$stock" +want_rc spl2 1 +want_say spl2 "SPL copy 2 at 0x00020000 differs from copy 0" +want_say spl2 "SPL copy 1 at 0x00010000 is byte-identical to copy 0" +want_say spl2 "SPL payload CRC32 over [0,45816) recomputes" # copy 0 untouched +echo + +# --- 4. the uImage payload CRC is wrong ------------------------------------- +echo "[4] uimage-crc-wrong: one payload byte flipped, CRC left stale" +cp "$stock" "$W/dcrc.img" || die "cp failed" +flipbyte "$W/dcrc.img" $((PAY + 4096)) 255 +run dcrc "$W/dcrc.img" "$stock" +want_rc dcrc 1 +want_say dcrc "the payload was edited after mkimage" +want_say dcrc "environment is byte-identical to stock" # env untouched: isolated +echo + +# --- 5. a command disappears from the table --------------------------------- +echo "[5] cmd-table-broken: the \`mt\` record's name pointer zeroed, CRC re-stamped" +cp "$stock" "$W/nomt.img" || die "cp failed" +namep=$(u32le "$W/nomt.img" $((PAY + MT_REC))) +nameoff=$((PAY + namep - 16777280)) # 16777280 = 0x01000040 = ih_load +if [ "$namep" -lt 16777280 ] || [ "$nameoff" -ge "$STOCK_SIZE" ]; then + bad "cmd-table-broken: the record at payload 0x$(printf '%x' "$MT_REC") holds no in-range name pointer" +elif [ "$(dd if="$W/nomt.img" bs=1 skip="$nameoff" count=2 2>/dev/null)" != "mt" ]; then + bad "cmd-table-broken: the record at payload 0x$(printf '%x' "$MT_REC") is not \`mt\` — blob layout changed" +else + note "record at payload 0x$(printf '%x' "$MT_REC") -> name 0x$(printf '%x' "$namep") = \"mt\", zeroing the pointer" + putbytes "$W/nomt.img" $((PAY + MT_REC)) 0 0 0 0 + repair_dcrc "$W/nomt.img" + run nomt "$W/nomt.img" "$stock" + want_rc nomt 1 + want_say nomt "commands are missing" + want_say nomt "\`mt\` is missing" + want_say nomt "uImage payload CRC recomputes" # re-stamp worked + want_say nomt "environment is byte-identical to stock" # env untouched +fi +echo + +# --- 6. ih_ep is neither the fork's 0 nor mainline's CONFIG_TEXT_BASE ------- +echo "[6] ih-ep-bogus: ih_ep outside the two allowed values, header CRC re-stamped" +cp "$stock" "$W/ihep.img" || die "cp failed" +put32be "$W/ihep.img" $((UIMG_OFF + 20)) 291 # 0x00000123 +repair_hcrc "$W/ihep.img" +run ihep "$W/ihep.img" "$stock" +want_rc ihep 1 +want_say ihep "ih_ep = 0x00000123: neither 0 (fork)" +want_say ihep "uImage header CRC recomputes" # re-stamp worked: isolated +echo + +# --- 7/8. IO errors are exit 2, not exit 1 ---------------------------------- +echo "[7] missing-file and truncated-file are usage/IO errors" +run missing "$W/does-not-exist.sfp" "$stock" +want_rc missing 2 +want_say missing "no such file" +want_quiet missing "CONTRACT VIOLATED" + +dd if="$stock" of="$W/short.img" bs=1 count=1024 2>/dev/null +run short "$W/short.img" "$stock" +want_rc short 2 +want_say short "too small" +echo + +# --- 8. the optional u-boot ELF argument (fixtures 9 and 10) ---------------- +# U2g will pass the built ELF as $3 so default_environment[] is located by +# symbol instead of by scan. There is no U-Boot ELF on disk yet, so the case is +# made from a two-line assembly stub that declares the symbol at the address +# stock's would have (ih_load 0x01000040 + the 0x28018 payload offset) — `nm -S` +# is all the checker reads from it, so the stub is a faithful stand-in. +echo "[8] the optional u-boot ELF argument (nm -S default_environment)" +if command -v as >/dev/null 2>&1 && command -v ld >/dev/null 2>&1; then + cat > "$W/fakeenv.s" <<-ASM + .section .env, "a" + .globl default_environment + .type default_environment, %object + .size default_environment, 1151 + default_environment: + .space 1151 + ASM + printf 'SECTIONS { . = 0x01028058; .env : { *(.env) } }\n' > "$W/right.ld" + printf 'SECTIONS { . = 0x02000000; .env : { *(.env) } }\n' > "$W/wrong.ld" + if as -o "$W/fakeenv.o" "$W/fakeenv.s" 2>"$W/as.log" && + ld -T "$W/right.ld" -o "$W/right.elf" "$W/fakeenv.o" 2>>"$W/as.log" && + ld -T "$W/wrong.ld" -o "$W/wrong.elf" "$W/fakeenv.o" 2>>"$W/as.log"; then + run elf-right "$stock" "$stock" "$W/right.elf" + want_rc elf-right 0 + want_say elf-right "default_environment at 0x01028058 size 1151 -> payload offset 0x00028018" + want_say elf-right "the ELF symbol is 1151 B and the blob is 1150 B" + + run elf-wrong "$stock" "$stock" "$W/wrong.elf" + want_rc elf-wrong 1 + want_say elf-wrong "the ELF puts default_environment outside the uImage payload" + else + skipped "elf-arg: as/ld could not build the stub ($(tr '\n' ' ' < "$W/as.log"))" + fi +else + skipped "elf-arg: no as/ld on this host" +fi +echo + +printf '%d passed, %d failed, %d skipped\n' "$pass" "$fail" "$skip" +[ "$fail" -eq 0 ] || echo "$prog: the checker did not behave as specified" >&2 +[ "$fail" -eq 0 ] From 903e3b5682d1f89e88c6767070086b437d26f9f5 Mon Sep 17 00:00:00 2001 From: "Michael C. Ferguson" Date: Mon, 14 Sep 2026 16:10:10 -0500 Subject: [PATCH 3/3] uboot: waves B/C -- DE10 mainline U-Boot integrated and gated; DE25 desk work closed; DE25 build repaired DE10-Nano (builds on every image; ships nowhere): - board/mister/de10nano/patches/uboot/: 0001 dead raw-sector hook guard, 0002 exFAT 64-bit division (both to be submitted upstream, drafts not sent), 0003 the fork's QTS handoff headers unmodified, 0004 the fork's `mt`, 0005 the fork's release-bridges-after-reset behaviour re-expressed against misc_gen5.c, 0006 bound the Linux boot map at 64 MiB. - uboot.fragment / uboot.env: the real deltas and stock's 21 env entries; the built default_environment[] is byte-identical to stock's 1,150 B. BR2_TARGET_UBOOT_NEEDS_GNUTLS gone (TOOLS_MKEFICAPSULE is off). - external.mk: MISTER_UBOOT_DE10_CONFIG_ASSERT -- the resolved .config and the SPL's raw-sector hook asserted at build time; deleting a fragment line fails the build naming it. - scripts/ci-tests.sh: offline U-Boot checks on every run (structure, handoff tables in our SPL, no uboot.img / no .sfp in a release stage); release.yml runs the full stock comparison where the blob already is. lint-kernel-patches.sh lints both boards' U-Boot series. - docs/verification/uboot-mainline.md: what was built, every allowed diff, both check transcripts, the negative handoff run, and a section-11 adversarial review (bridge argument, effective-env, mailbox region, ih_ep/ih_load mixing hazard, watchdog takeover). DE25-Nano: - The factory loader's FIT acceptance closed by reference: the FIT on Terasic's factory SD image is crc32-only, unsigned, same layout as ours (de25-uboot.md 12; de25-boot-chain.md 7 row 6, 8.3). - patches/arm-trusted-firmware/0001: BL31 boot+crash console to UART1, the header UART -- the one change Terasic's own BL31 makes. BL31 lines are now expected at bring-up (de25-uboot.md 11). - FIT byte-identical across three clean trees (DU3); section 13 decisions dispositioned (DU4); wave-4 boot-path re-review logged (DU7). - Field notes from another DE25-Nano port recorded as device facts (de25-boot-chain.md 7b): no fpga load from U-Boot, HPS-first images, post-JTAG SD quirks, random HPS MAC, unsigned bitstreams accepted. - docs/de25-nano-overview.md: "how is this going to work", the chain link by link, blockers ranked, first-session plan; README row. - Kernel pin 7.2.5 (was 7.2.3 with no hash line after the RT bump), the hash file symlinked to package/linux-rt/linux-rt.hash, and the DE25 defconfig added to the kernel-rt-7.2 Renovate manager so both move in one PR. All 34 DE25 patches apply with offsets, no fuzz. - package/linux-rt/linux-rt.mk: name LINUX_RT_KCONFIG_FILE only when the package is enabled. pkg-kconfig registers `: | linux-rt-patch` for every package naming a kconfig file, so the DE25 tree's main kernel config depended on an empty-version RT download; `make de25` had been broken since ADR 0030. Full make de25 now green, check-sdcard-de25.sh passes. References to the private third-party DE25 trees removed from the DE25 docs and DTS comments; facts kept, Terasic's public branches cited where the facts are Terasic's. Gates run locally: check-defconfigs OK (3/3), test-uboot-parity 39/0, test-uboot-handoff both fixtures, ci-tests.sh PASS (183+), lint-kernel- patches 46/46 git-am-able, shellcheck clean, make de25 green. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015UwVRTNY37gE8GQjEqvU5k --- .github/workflows/release.yml | 47 + PLAN.md | 4 +- README.md | 8 +- TASKS.md | 3 +- ...cfpga-fix-dead-raw-sector-hook-guard.patch | 109 ++ ...at-fix-64-bit-division-on-32-bit-arm.patch | 148 ++ ...terasic-de10-nano-mister-qts-handoff.patch | 524 +++++++ ...mem-add-mt-memory-test-against-value.patch | 175 +++ ...ges-after-reset-if-fpga-in-user-mode.patch | 236 +++ ...o-bound-the-linux-boot-map-at-64-mib.patch | 109 ++ board/mister/de10nano/uboot.env | 110 +- board/mister/de10nano/uboot.fragment | 378 ++++- ...-agilex5-route-bl31-console-to-uart1.patch | 56 + .../mister/de25nano/patches/linux/linux.hash | 2 +- ...fpga_soc64-guard-mtdids-mtdparts-env.patch | 4 +- .../de25nano/socfpga_agilex5_de25nano.dts | 77 +- .../uboot-dts/socfpga_agilex5_de25nano.dts | 9 +- configs/mister_de10nano_defconfig | 1 - configs/mister_de25nano_defconfig | 2 +- docs/boot-chain.md | 27 +- docs/buildroot-config.md | 65 +- docs/de25-boot-chain.md | 46 +- docs/de25-dts-rationale.md | 94 +- docs/de25-implementation-path.md | 88 +- docs/de25-nano-overview.md | 226 +++ docs/de25-nano-tasks.md | 141 +- docs/de25-reference-implementation.md | 465 +++--- docs/de25-uboot.md | 122 +- docs/uboot-tasks.md | 85 +- docs/verification/uboot-mainline.md | 1273 +++++++++++++++++ external.mk | 98 ++ package/linux-rt/linux-rt.mk | 8 + renovate.json | 6 +- scripts/check-uboot-parity.sh | 283 ++-- scripts/ci-tests.sh | 160 +++ scripts/lint-kernel-patches.sh | 47 +- 36 files changed, 4606 insertions(+), 630 deletions(-) create mode 100644 board/mister/de10nano/patches/uboot/0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch create mode 100644 board/mister/de10nano/patches/uboot/0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch create mode 100644 board/mister/de10nano/patches/uboot/0003-board-terasic-de10-nano-mister-qts-handoff.patch create mode 100644 board/mister/de10nano/patches/uboot/0004-cmd-mem-add-mt-memory-test-against-value.patch create mode 100644 board/mister/de10nano/patches/uboot/0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch create mode 100644 board/mister/de10nano/patches/uboot/0006-configs-socfpga-de10-nano-bound-the-linux-boot-map-at-64-mib.patch create mode 100644 board/mister/de25nano/patches/arm-trusted-firmware/0001-plat-intel-agilex5-route-bl31-console-to-uart1.patch create mode 100644 docs/de25-nano-overview.md create mode 100644 docs/verification/uboot-mainline.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0095a910..00008b82 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,6 +120,53 @@ jobs: cp -f "output/images/$img.xz" dist/ ls -la dist/ + # docs/uboot-tasks.md U5: the DE10's mainline U-Boot ships nowhere and + # `scripts/ci-tests.sh` (the "Run parity suite" step above) only has + # the offline structural/built-SPL-only subset of check-uboot-parity.sh + # / check-uboot-handoff.sh -- the stock uboot.img does not exist on + # disk until the sdcard step just above fetches and verifies it. This + # is the one place that download is already paid for, so every tagged + # release proves FULL parity (environment + command table + handoff + # tables against the real stock blob) without a second fetch and + # without any PR paying for it. + # + # This step is ALSO the only place the "ships nowhere" half of owner + # decision 1 can actually be enforced in CI: dist/ is created by + # scripts/mk-release.sh, which runs AFTER verify-image, so ci-tests.sh's + # own release-stage assertion can only ever SKIP on a runner. Here dist/ + # is fully populated (mk-release.sh's assets plus the sdcard image the + # step above copied in), so the grep has teeth. + - name: Verify U-Boot (DE10) parity against the stock blob (docs/uboot-tasks.md U5) + env: + STOCK_UBOOT_IMG: ${{ github.workspace }}/output-sdcard-stage/mister-payload/linux/uboot.img + run: | + set -eu + shopt -s nullglob # so an unmatched glob counts 0, not 1 literal + uboot_trees=(output/build/uboot-*) + [ "${#uboot_trees[@]}" -eq 1 ] || + { echo "::error::expected exactly one output/build/uboot-*, found ${#uboot_trees[@]}"; exit 1; } + uboot_tree=${uboot_trees[0]} + [ -f "$STOCK_UBOOT_IMG" ] || + { echo "::error::no stock uboot.img at $STOCK_UBOOT_IMG -- the sdcard step above should have staged it"; exit 1; } + + # "ships nowhere", asserted against the real, finished release stage. + echo "--- release stage (dist/) must carry no bootloader artifact ---" + ls -la dist/ + stage_hits=$(find dist -maxdepth 1 \( -name 'uboot.img' -o -name '*.sfp' \)) + [ -z "$stage_hits" ] || + { echo "::error::owner decision 1 says the DE10's mainline U-Boot ships nowhere, but the release stage carries: $(echo "$stage_hits" | tr '\n' ' ')"; exit 1; } + echo "ok: no uboot.img/*.sfp in dist/" + + spl_limit=$("$uboot_tree/tools/spl_size_limit") + SPL_SIZE_LIMIT="$spl_limit" scripts/check-uboot-parity.sh \ + output/images/u-boot-with-spl.sfp \ + "$STOCK_UBOOT_IMG" \ + "$uboot_tree/u-boot" + scripts/check-uboot-handoff.sh \ + "$uboot_tree/board/terasic/de10-nano/qts" \ + output/images/u-boot-with-spl.sfp \ + "$STOCK_UBOOT_IMG" + - name: Release overview (job summary) env: RELEASE_DATE: ${{ steps.release.outputs.release_date }} diff --git a/PLAN.md b/PLAN.md index 137d0b2f..df1fc0d0 100644 --- a/PLAN.md +++ b/PLAN.md @@ -677,7 +677,9 @@ one-line change, and CI tells us immediately if a patch stopped applying. > **[2026-07-28] This section is superseded from "Phase 5 path" onward by > [ADR 0024](docs/decisions/0024-mainline-uboot-capability-artifact.md).** The from-source -> bootloader is now built from **mainline U-Boot 2026.07** (2026.04 when first decided), not the 2017.03 fork, as a +> bootloader is now built from **mainline U-Boot 2026.07** (2026.04 when first decided; +> re-pinned to 2026.07 on 2026-09-14 — see `docs/uboot-mainline-port.md`'s revision note), +> not the 2017.03 fork, as a > non-shipping capability artifact — the mainline port surface has been measured rather > than estimated, and it is smaller than this section assumed. **Do not add the `u-boot/` > submodule.** Design and evidence: [`docs/uboot-mainline-port.md`](docs/uboot-mainline-port.md). diff --git a/README.md b/README.md index 296c00a7..baa5141b 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ document records what changed and which rows here it dates). | **2 — Rootfs & testing** | ✅ Complete | Buildroot 2026.08, glibc 2.44, reproducible ext4 image with full SBOM; menu and cores load on hardware — the ABI contract holds *in practice*, not just on paper | | **3 — Module packages & HW matrix** | ✅ Complete | Wi-Fi, Bluetooth, controllers and special devices packaged; hardware-validated **for the chips actually present on the one test board**. The v10/v10.1/v10.2 driver + firmware expansion (Broadcom, Wi-Fi 6/6E, MediaTek, Atheros USB, Redpine) is packaged and mostly CI-asserted but **not** hardware-validated — see the [ledger](#hardware-validation-ledger) and the [chipset table](#wi-fi-and-bluetooth-hardware-support). The remaining matrix rows (Samba, MIDI) are build/CI-verified only | | **4 — Release & sustainability** | 🔄 In progress | CI/CD, `db.json` distribution, beta program, governance, publication gate | -| **5 — Full SD image & U-Boot** | 🔄 Partially landed | `sdcard.img` builds, and `release.yml` verifies it with `scripts/check-sdcard.sh` ([ADR 0020](docs/decisions/0020-sdcard-exfat-reformat-installer.md)); U-Boot-from-source is planned but not started — now targeting **mainline U-Boot**, not the 2017.03 fork ([ADR 0024](docs/decisions/0024-mainline-uboot-capability-artifact.md), [plan](docs/uboot-mainline-port.md), [tasks](docs/uboot-tasks.md)) — and the SD image has not been flashed to a fresh card on hardware (P5.4) | +| **5 — Full SD image & U-Boot** | 🔄 Partially landed | `sdcard.img` builds, and `release.yml` verifies it with `scripts/check-sdcard.sh` ([ADR 0020](docs/decisions/0020-sdcard-exfat-reformat-installer.md)); U-Boot from source is now building on every DE10 build, **mainline 2026.07**, ships nowhere ([ADR 0024](docs/decisions/0024-mainline-uboot-capability-artifact.md), [plan](docs/uboot-mainline-port.md), [tasks](docs/uboot-tasks.md), [verification](docs/verification/uboot-mainline.md)) — and the SD image has not been flashed to a fresh card on hardware (P5.4) | ### Hardware validation ledger @@ -1019,8 +1019,10 @@ Start here if you want to run it: [**one-command install**](#install-it-on-a-rea |---|---| | [`docs/abi-contract.md`](docs/abi-contract.md) | What the kernel and rootfs must honor for the stock binary to run | | [`docs/boot-chain.md`](docs/boot-chain.md) | U-Boot contract and its kernel-config implications | -| [`docs/uboot-mainline-port.md`](docs/uboot-mainline-port.md) | The plan for a mainline U-Boot built to behave like stock's — design, measured port surface, brick modes. Ships nowhere | -| [`docs/uboot-tasks.md`](docs/uboot-tasks.md) | Execution tasks for the above (U0–U7), split out of `TASKS.md` | +| [`docs/uboot-mainline-port.md`](docs/uboot-mainline-port.md) | The plan for a mainline U-Boot (2026.07) built to behave like stock's — design, measured port surface, brick modes; in progress, ships nowhere | +| [`docs/de25-nano-overview.md`](docs/de25-nano-overview.md) | **DE25-Nano, start here** — how the board boots, what we build for it, what is verified, what could still stop it; links to every DE25 document | +| [`docs/uboot-tasks.md`](docs/uboot-tasks.md) | Execution tasks for both boards (U0–U9 for the DE10, DU1–DU7 for the DE25), split out of `TASKS.md` | +| [`docs/verification/uboot-mainline.md`](docs/verification/uboot-mainline.md) | Mainline U-Boot 2026.07 for the DE10-Nano: built artifact measurements, structural checks against stock, allowed diffs, and verification evidence | | [`docs/downloader-contract.md`](docs/downloader-contract.md) | How the on-device Downloader decides to update | | [`docs/package-manifest.md`](docs/package-manifest.md) | All 251 stock SONAMEs mapped to a package, with major-bump flags | | [`docs/stock-inventory/`](docs/stock-inventory/) | The audited inventory of the stock image everything above is measured against | diff --git a/TASKS.md b/TASKS.md index 29b1bcba..644c1800 100644 --- a/TASKS.md +++ b/TASKS.md @@ -1086,7 +1086,8 @@ drilled recovery procedure. **The default channel keeps shipping the stock submodule** — the mainline build uses Buildroot's own hash-verified tarball, and `UBOOT_OVERRIDE_SRCDIR` (which the old P5.1 specified) would have silently skipped the patch step. Design and evidence: [`docs/uboot-mainline-port.md`](docs/uboot-mainline-port.md). - Execution: [`docs/uboot-tasks.md`](docs/uboot-tasks.md), tasks U0–U7. + Execution: [`docs/uboot-tasks.md`](docs/uboot-tasks.md), tasks U0–U9 for the DE10-Nano + (and DU1–DU7 for the DE25-Nano's own `u-boot.itb`). **Unchanged:** the default channel keeps shipping the stock `uboot.img` byte-identical (P4.4), and `sdcard.img` keeps embedding that same stock blob (ADR 0017 §Decision 4/5 still stand). P5.3 and P5.4 below are unaffected. diff --git a/board/mister/de10nano/patches/uboot/0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch b/board/mister/de10nano/patches/uboot/0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch new file mode 100644 index 00000000..2e2e1c5f --- /dev/null +++ b/board/mister/de10nano/patches/uboot/0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch @@ -0,0 +1,109 @@ +From: Michael C. Ferguson +Date: Mon, 14 Sep 2026 00:00:00 +0000 +Subject: [PATCH] arm: socfpga: fix dead raw-sector hook guard after the + TARGET->ARCH rename + +board_spl_mmc_get_uboot_raw_sector() is the hook that adds the +0x200 +sector offset a `u-boot-with-spl.sfp` image needs when U-Boot proper is +loaded from a raw MMC partition (CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_ +PARTITION_TYPE=y, boot-chain.md section 2.1): the image is four 64 KiB +SPL copies followed by U-Boot proper, and SPL loads U-Boot proper from +that fixed offset inside the partition rather than from its start. + +The hook has been compiled out since 2026-02-13 for every Gen5/Arria10 +SoCFPGA board, DE10-Nano included, and nothing has noticed because the +fallback it silently leaves in place -- common/spl/spl_mmc.c's __weak +board_spl_mmc_get_uboot_raw_sector(), which just returns raw_sect +unchanged -- still lets SPL locate *a* U-Boot proper image; it is just +the wrong one (SPL copy 0 instead of the real payload after it), which +is a boot-time brick, not a build-time failure. + +The break: commit 62f7a94602 ("Replace TARGET namespace and cleanup +properly", 2026-02-13) renamed the SoCFPGA family Kconfig symbols from +CONFIG_TARGET_SOCFPGA_* to CONFIG_ARCH_SOCFPGA_* across the tree, but +missed the #if guard added ten weeks earlier by commit 1cf1b504f4 +("arch: arm: mach-socfpga: Adjust a raw sectors for MMC loading of +U-Boot from SPL", 2025-12-11), which still reads: + + #if (defined(CONFIG_TARGET_SOCFPGA_ARRIA10) || \ + defined(CONFIG_TARGET_SOCFPGA_GEN5)) && defined(CONFIG_XPL_BUILD) + +CONFIG_TARGET_SOCFPGA_ARRIA10 and CONFIG_TARGET_SOCFPGA_GEN5 are gone +from Kconfig -- these two lines are the only remaining references to +either symbol in the whole tree -- so the #if is permanently false and +board_spl_mmc_get_uboot_raw_sector() never compiles for any Gen5 or +Arria10 target. Confirmed still present, byte-for-byte, at this +repo's mainline-board-support mirror of u-boot/u-boot.git HEAD +5c215cb75c3723cbf77c36cbac3e60b001721c79 (2026-07-27); the wider plan +research (docs/uboot-mainline-port.md section 3.1 row 2) independently +found it unfixed at v2026.10-rc1 too. One-line-per-symbol rename fix: +s/CONFIG_TARGET_SOCFPGA_/CONFIG_ARCH_SOCFPGA_/ on both lines. + +Verified by rebuild against u-boot-2026.07 (socfpga_de10_nano_defconfig ++ CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y, this repo's +arm-buildroot-linux-gnueabihf- cross toolchain), task U2a: + + * Without this patch: `nm spl/u-boot-spl` still lists the symbol -- + the __weak fallback chain (spl_mmc_get_uboot_raw_sector :322 -> + board_spl_mmc_get_uboot_raw_sector :316 -> + arch_spl_mmc_get_uboot_raw_sector :310, all three __weak, all in + common/spl/spl_mmc.c) means the *name* is never absent -- but as + type W (weak). objdump shows + board_spl_mmc_get_uboot_raw_sector as a single `b.w` tail-branch + into arch_spl_mmc_get_uboot_raw_sector, whose body is `mov r0, r1; + bx lr`: raw_sect passes straight through unmodified and no +0x200 + is ever added. + * With this patch: `nm spl/u-boot-spl` lists it as type T (this + board.c definition, strong, overriding the weak default) -- + alongside the still-weak `W spl_mmc_get_uboot_raw_sector`, so an + assertion must match `T board_spl_mmc_get_uboot_raw_sector` + exactly, never a bare grep for "raw_sector" -- and + objdump of the function is `add.w r0, r1, #0x200; bx lr` -- + (CONFIG_SPL_PAD_TO * 4) / 512 = (0x10000 * 4) / 512 = 0x200 with + this defconfig, matching boot-chain.md section 2.1 exactly. + +Provenance +---------- +Origin: No existing upstream commit -- this is a follow-up fix to + a gap left by 62f7a94602 and 1cf1b504f4 (both cited above + and unmodified by this patch). Written against + u-boot-2026.07 (dl/uboot/u-boot-2026.07.tar.bz2) for this + task; not derived from any other tree. +Author: Michael C. Ferguson +Copyright: The surrounding file (arch/arm/mach-socfpga/board.c) is + upstream U-Boot, SPDX GPL-2.0+; this hunk carries no new + copyright claim, it corrects two macro names. +Upstream status: Not in mainline. Two U-Boot commits away from being a + trivial fix (see "The break" above) that a socfpga + maintainer could merge in one line-for-line review. To be + submitted upstream (U7): a submission is drafted under + docs/verification/uboot-upstream/ by task U7-prep but, per + owner decision 4, deliberately NOT sent to any mailing + list -- U7 itself is "send, on the owner's go only". +Disposition: Bug fix, brick-class (docs/uboot-mainline-port.md section + 3.1 row 2: "With #1 applied, raw_sect is 0 and the SPL + would load from the *start* of the 0xA2 partition -- i.e. + SPL copy 0"). Required for this repo's mainline U-Boot + build artifact (task U2g) to boot at all; carried until + upstream picks it up. + +Signed-off-by: Michael C. Ferguson +--- + arch/arm/mach-socfpga/board.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm/mach-socfpga/board.c b/arch/arm/mach-socfpga/board.c +index 4d7f0b9..fd7f15b 100644 +--- a/arch/arm/mach-socfpga/board.c ++++ b/arch/arm/mach-socfpga/board.c +@@ -211,8 +211,8 @@ void lmb_arch_add_memory(void) + } + #endif + +-#if (defined(CONFIG_TARGET_SOCFPGA_ARRIA10) || \ +- defined(CONFIG_TARGET_SOCFPGA_GEN5)) && defined(CONFIG_XPL_BUILD) ++#if (defined(CONFIG_ARCH_SOCFPGA_ARRIA10) || \ ++ defined(CONFIG_ARCH_SOCFPGA_GEN5)) && defined(CONFIG_XPL_BUILD) + unsigned long board_spl_mmc_get_uboot_raw_sector(struct mmc *mmc, + unsigned long raw_sect) + { diff --git a/board/mister/de10nano/patches/uboot/0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch b/board/mister/de10nano/patches/uboot/0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch new file mode 100644 index 00000000..bff6a93d --- /dev/null +++ b/board/mister/de10nano/patches/uboot/0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch @@ -0,0 +1,148 @@ +From: Michael C. Ferguson +Date: Mon, 14 Sep 2026 00:00:00 +0000 +Subject: [PATCH] fs: exfat: fix 64-bit division on 32-bit arm + +CONFIG_FS_EXFAT fails to link on any 32-bit ARM target: fs/exfat/time.c +divides and takes the modulus of a `time_t unix_time` directly, and +time_t is `long long` -- a genuine 64-bit type -- even on 32-bit ARM +(include/linux/types.h: `typedef long long time_t;`). U-Boot carries its +own arch/arm/lib helpers rather than linking libgcc, and the set is +incomplete: `__aeabi_uldivmod` (unsigned) is there, the SIGNED +`__aeabi_ldivmod` a signed time_t needs is not, so every one of those +operators becomes an undefined reference at link time: + + arm-buildroot-linux-gnueabihf-ld: fs/exfat/time.o: in function + `exfat_unix2exfat': fs/exfat/time.c:129: undefined reference to + `__aeabi_ldivmod' + ... (and fs/exfat/time.c:147, :148, :149, one call per site) + +No 32-bit U-Boot board in the tree has ever enabled CONFIG_FS_EXFAT, so +nobody has hit this; the only prior use of fs/exfat is on 64-bit +targets, where these are native machine divisions. It matters here +because ADR 0020 reformats the MiSTer's data partition to exFAT, so a +mainline U-Boot needs exFAT read support on this 32-bit board +(docs/uboot-mainline-port.md section 3.1 row 3). + +The fix is the standard one for a 64-by-constant division on a 32-bit +target that must not pull in libgcc: do_div() (include/div64.h), used +throughout U-Boot and Linux for exactly this. do_div(n, base) replaces +its 64-bit first argument in place with the quotient and returns the +32-bit remainder, entirely with 32-bit arithmetic -- no __aeabi_ldivmod +call. exfat_unix2exfat() only ever needs one 64-bit divide (splitting +`unix_time` into whole days and a remaining seconds-of-day count); doing +that once with do_div() and deriving hour/minute/two-second-tick/ +centisecond from the resulting u32 remainder keeps every later +division a plain 32-bit one, which this toolchain compiles inline. + +exfat_exfat2unix(), the other direction in this file, is untouched: its +only division (`centisec / 100`) is already on a uint8_t promoted to +plain `int`, not on a time_t, so it was never a 64-bit division and +never failed to link. + +Verified by rebuild against u-boot-2026.07 (socfpga_de10_nano_defconfig ++ CONFIG_FS_EXFAT=y, this repo's arm-buildroot-linux-gnueabihf- cross +toolchain), task U2a: without this patch the `u-boot`/`spl/u-boot-spl` +link fails with the `__aeabi_ldivmod` errors above; with it, the build +runs clean through `SOCBOOT u-boot-with-spl.sfp`. A first-cut version of +this same do_div() approach was written and built green independently +during the plan's research phase (docs/uboot-mainline-port.md section +3.1 row 3; scratch tree /mnt/source/uboot-mainline/verify-behaviour/ +u-boot-2026.04-exfatfix, log .../exfat2.log) -- this patch is written +fresh against 2026.07 for this task, not copied from that tree, but +independently arrives at the same technique and confirms it still +builds green on the newer tarball. + +Provenance +---------- +Origin: No existing upstream commit; a previously-unreported + upstream bug found during this project's mainline-U-Boot + research (docs/uboot-mainline-port.md section 3.1 row 3). + Written against u-boot-2026.07 + (dl/uboot/u-boot-2026.07.tar.bz2) for this task. +Author: Michael C. Ferguson +Copyright: fs/exfat/time.c carries "Free exFAT implementation, + Copyright (C) 2010-2023 Andrew Nayenko" (GPLv2+, per the + file's own header) for the surrounding code; this hunk + adds no new copyright claim. +Upstream status: Not in mainline as of u-boot-2026.07 / HEAD + 5c215cb75c3723cbf77c36cbac3e60b001721c79 (2026-07-27). To + be submitted upstream (U7): a submission is drafted under + docs/verification/uboot-upstream/ by task U7-prep but, per + owner decision 4, deliberately NOT sent to any mailing + list -- U7 itself is "send, on the owner's go only". +Disposition: Bug fix, brick-class for this port + (docs/uboot-mainline-port.md section 3.1 row 3): without + it, this repo's mainline U-Boot cannot read the exFAT + data partition ADR 0020 requires and cannot load + zImage_dtb. Carried until upstream picks it up. + +Signed-off-by: Michael C. Ferguson +--- + fs/exfat/time.c | 29 ++++++++++++++++++++++++----- + 1 file changed, 24 insertions(+), 5 deletions(-) + +diff --git a/fs/exfat/time.c b/fs/exfat/time.c +index f2217dd..097ab54 100644 +--- a/fs/exfat/time.c ++++ b/fs/exfat/time.c +@@ -20,6 +20,7 @@ + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + ++#include + #include "exfat.h" + + /* timezone offset from UTC in seconds; positive for western timezones, +@@ -118,6 +119,7 @@ void exfat_unix2exfat(time_t unix_time, le16_t* date, le16_t* time, + uint16_t day, month, year; + uint16_t twosec, min, hour; + int days; ++ u32 secs_today; + int i; + + /* time before exFAT epoch cannot be represented */ +@@ -126,7 +128,24 @@ void exfat_unix2exfat(time_t unix_time, le16_t* date, le16_t* time, + + unix_time -= shift; + +- days = unix_time / SEC_IN_DAY; ++ /* ++ * unix_time is a 64-bit time_t (include/linux/types.h: "typedef long ++ * long time_t") even here on 32-bit ARM, and arch/arm/lib has only ++ * the UNSIGNED __aeabi_uldivmod, not __aeabi_ldivmod, so a plain ++ * signed 64-bit '/' or '%' is an undefined reference -- nothing ++ * caught it before because no 32-bit U-Boot board previously enabled ++ * CONFIG_FS_EXFAT. do_div() is the do_div()/div_u64-style 64-by-32 ++ * divide used throughout U-Boot/Linux for exactly this: it replaces ++ * its first argument with the quotient and returns the 32-bit ++ * remainder, without a libgcc call. Doing that split once here and ++ * working only in u32 below keeps every remaining division 32-bit. ++ */ ++ { ++ u64 whole_days = (u64)unix_time; ++ ++ secs_today = do_div(whole_days, SEC_IN_DAY); ++ days = (int)whole_days; ++ } + year = (4 * days) / (4 * 365 + 1); + days -= year * 365 + LEAP_YEARS(year); + month = 0; +@@ -144,14 +163,14 @@ void exfat_unix2exfat(time_t unix_time, le16_t* date, le16_t* time, + } + day = days + 1; + +- hour = (unix_time % SEC_IN_DAY) / SEC_IN_HOUR; +- min = (unix_time % SEC_IN_HOUR) / SEC_IN_MIN; +- twosec = (unix_time % SEC_IN_MIN) / 2; ++ hour = secs_today / SEC_IN_HOUR; ++ min = (secs_today % SEC_IN_HOUR) / SEC_IN_MIN; ++ twosec = (secs_today % SEC_IN_MIN) / 2; + + *date = cpu_to_le16(day | (month << 5) | (year << 9)); + *time = cpu_to_le16(twosec | (min << 5) | (hour << 11)); + if (centisec) +- *centisec = (unix_time % 2) * 100; ++ *centisec = (secs_today % 2) * 100; + + /* record our local timezone offset in exFAT (15 minute increment) format */ + *tzoffset = (uint8_t)(-exfat_timezone / 60 / 15) | 0x80; diff --git a/board/mister/de10nano/patches/uboot/0003-board-terasic-de10-nano-mister-qts-handoff.patch b/board/mister/de10nano/patches/uboot/0003-board-terasic-de10-nano-mister-qts-handoff.patch new file mode 100644 index 00000000..3afdd56d --- /dev/null +++ b/board/mister/de10nano/patches/uboot/0003-board-terasic-de10-nano-mister-qts-handoff.patch @@ -0,0 +1,524 @@ +From dadd1c89786c14b4b783f30279a0c8d76c5b6770 Mon Sep 17 00:00:00 2001 +From: Sorgelig +Date: Sat, 1 Apr 2017 08:07:48 +0800 +Subject: [PATCH] board: terasic: de10-nano: carry MiSTer's QTS handoff data + +Replace mainline's four Terasic DE10-Nano QTS handoff headers +(board/terasic/de10-nano/qts/{sdram,pll,pinmux,iocsr}_config.h) with +the MiSTer fork's, renaming CONFIG_HPS_ -> CFG_HPS_ to match +mainline's board CONFIG_* -> CFG_* migration. Values are carried +unmodified. + +MiSTer replaced the Quartus-generated HPS<->FPGA handoff it had +imported with the board (c7ed0834ac, "Add DE10-nano board", +2017-03-27) with its own five days later (dadd1c8978, "Use SPL +config from DE10 FB project", Sorgelig, 2017-04-01) because +MiSTer's FPGA fabric is a different Quartus project than Terasic's +reference design on the same board. The two diverge only in the +HPS<->FPGA interface: every DDR3 timing, geometry, ODT/PHY control and +both DDR sequencer ROMs (ac_rom_init 36 words, inst_rom_init 127 +words) are identical, and iocsr_scan_chain3_table -- the 524-word +DDR3 HPS I/O bank -- is identical too. So this is not a DDR bring-up +hazard; it is the interface that lets MiSTer's fabric use the SDRAM +controller and its GPIO/I2C/SPI pin muxing at all. + +Divergent, value-for-value (fork -> pristine mainline v2026.07, names +already normalized to CFG_HPS_): + + CFG_HPS_SDR_CTRLCFG_FPGAPORTRST 0x3FFF -> 0x1FF (sdram_config.h) + REG_FILE_INIT_SEQ_SIGNATURE 0x555504a0 -> 0x555504a1 (sdram_config.h, write-only sequencer stamp; see below) + CFG_HPS_PERPLLGRP_S2FUSER1CLK_CNT 511 -> 19 (pll_config.h) + CFG_HPS_SDRPLLGRP_S2FUSER2CLK_CNT 4 -> 5 (pll_config.h) + sys_mgr_init_table[35] GENERALIO3 0 -> 1 (pinmux_config.h) + sys_mgr_init_table[36] GENERALIO4 0 -> 1 (pinmux_config.h) + sys_mgr_init_table[201] I2C3USEFPGA 0 -> 1 (pinmux_config.h) + iocsr_scan_chain0_table 12 of 24 words differ (iocsr_config.h) + iocsr_scan_chain1_table 18 of 54 words differ (iocsr_config.h) + iocsr_scan_chain2_table 2 of 30 words differ (iocsr_config.h) + iocsr_scan_chain3_table 0 of 524 words differ (iocsr_config.h -- DDR3 I/O bank, identical) + +That is 39 changed value lines and nothing else: this patch deliberately +keeps mainline's line-1 SPDX tag form and mainline's whitespace rather +than importing the fork's 2017-era in-comment tag, so every line it +touches is a value. See "Non-value lines" below. + +FPGAPORTRST releases 14 FPGA->SDRAM port resets under the fork's value +vs 9 under mainline's (0x3FFF = 14 bits set, 0x1FF = 9). It lives in +sdram_config.h and reaches hardware on both sides of the handoff +[V, v2026.07 source]: the SPL stashes it in +SYSMGR.ISWGRP.HANDOFF.FPGA2SDR (drivers/ddr/altera/sdram_gen5.c:466) +and, when the fabric is already programmed (fpgamgr_test_fpga_ready()), +write-verifies it straight into the SDRAM controller's fpgaport_rst +register (sdram_gen5.c:471-472); U-Boot proper then caches that same +handoff word (arch/arm/mach-socfpga/misc_gen5.c:199-201) and +do_bridge_reset() writes it back to fpgaport_rst whenever it re-enables +the bridges (misc_gen5.c:262-263). So it is a controller register, not +a DDR timing -- but accepting mainline's value would leave five fabric +ports in reset and MiSTer cores could lose SDRAM access. (Plan S3.2 +cites sdram_gen5.c:466 for the SPL write; to be exact, :466 is the +sysmgr stash and :471 the controller write, both in the SPL.) The +pinmux and IOCSR divergence is the GPIO/I2C/SPI FPGA-vs-HPS routing +MiSTer's fabric depends on. + +REG_FILE_INIT_SEQ_SIGNATURE is the Quartus generator's stamp, and it +is *not* dead source: v2026.07's arch/arm/mach-socfpga/wrap_sdram_config.c:301 +feeds it into misc_config.reg_file_init_seq_signature, and +drivers/ddr/altera/sequencer.c:3781-3785 initialize_reg_file() writes +it verbatim to the DDR sequencer's register-file signature register +(&sdr_reg_file->signature) on every SPL DDR bring-up. It is +functionally inert only because nothing reads it back: that writel is +the sole reference to sdr_reg_file->signature anywhere in drivers/ or +arch/arm/mach-socfpga/ in this tree [V, grepped]. Carrying the fork's +value therefore changes one word written to one status register and +nothing else -- but "not consumed by the SPL" would be the wrong way +to say it, because the SPL does consume and write it. + +Accepting mainline's values here would be silently wrong for this +board's actual FPGA design, not a bug fix -- these headers can never +be upstreamed; mainline correctly serves Terasic's own reference +design on the same physical board. Gated by the boot-chain's +handoff-equality check (docs/uboot-mainline-port.md S3.2a), which +greps a built SPL for these exact byte patterns so a build that +silently picks up the wrong qts/*.h fails loudly instead of looking +like success on a board. + +Owner decision 2026-09-14 (mirror stock wherever the plan had a +choice): carry the fork's values unmodified rather than reconcile +them with mainline's Terasic defaults. + +Provenance +---------- +Origin: dadd1c89786c14b4b783f30279a0c8d76c5b6770 + "Use SPL config from DE10 FB project." + MiSTer-devel U-Boot fork, 2017-04-01. +Author: Sorgelig +Upstream: No, and not submittable: MiSTer's FPGA fabric is a + different Quartus project than Terasic's own reference + design on the same board, so mainline's headers are + correct for mainline's board and this repo's are + correct for MiSTer's. +Disposition: Carry (docs/uboot-mainline-port.md S3.1 item 4, S3.2, + S3.2a). Not a defect -- MiSTer-specific FPGA routing + data, carried unmodified per the 2026-09-14 owner + decision to mirror stock. + +Snapshot: The four headers as they stand at fork HEAD + 8dcc3484aac6f07314538e82530d446083085e12 ("Adjust flag + for cold boot.", 2021-11-12), copied from + work/U-Boot_MiSTer (this repo's read-only reference + copy of the fork) -- not the dadd1c8978 tree verbatim. + dadd1c8978 is cited as Origin because it is the commit + that replaced Terasic's handoff with MiSTer's own; the + bytes carried here are the fork's current ones, per the + pinned reference "the MiSTer fork at commit 8dcc3484". + Exactly three commits touch board/terasic/de10-nano/qts/ + between the two, all in pinmux_config.h, and each was + diffed [V]: + f7d1761b89 ("Switch i2c to gpio mode for smbus + compatibilty.", 2018-02-10) -- GENERALIO3, GENERALIO4 + 1 -> 0. These are two of the three pinmux words that + still differ from mainline, matching plan S3.2's + attribution. + 0130b494b1 ("Update pin mux config.", 2018-04-03) -- + UART1USEFPGA 0 -> 1, SPIM0USEFPGA 0 -> 1. + 02ea7ac01f ("Enable i2c2 bridge.", 2021-03-15) -- + I2C2USEFPGA 0 -> 1. + The last three flips all converge ON mainline's values + (mainline v2026.07 has UART1USEFPGA=1, SPIM0USEFPGA=1, + I2C2USEFPGA=1 [V]), which is why they do not appear in + the divergence list. The third remaining pinmux delta, + I2C3USEFPGA, comes from NO fork commit: it is an + import-vs-import difference. The fork's import + c7ed0834ac ("Add DE10-nano board", 2017-03-27) has + sys_mgr_init_table[201] I2C3USEFPGA = 0, and that word + has never been touched since (0 at c7ed0834ac, + dadd1c8978^, dadd1c8978 and 8dcc3484 alike [V, git + show :.../pinmux_config.h in work/U-Boot_MiSTer]); + mainline's independent import 6bd041f00d ("arm: + socfpga: add cyclone5 based de10-nano board", + 2017-04-18) has 1, as does v2026.07 [V]. dadd1c8978's + own pinmux change was I2C2USEFPGA 1 -> 0 only, later + flipped back by 02ea7ac01f. +Attribution: Every delta in the list above, traced to the commit + that introduced it by running qtsdiff.py over the + renamed headers at c7ed0834ac, dadd1c8978 and 8dcc3484 + against each other and against pristine v2026.07 [V]: + dadd1c8978 -- FPGAPORTRST (0x311 at the fork's + import -> 0x3FFF), REG_FILE_INIT_SEQ_SIGNATURE + (0x555504a1 -> 0x555504a0), PERPLLGRP_S2FUSER1CLK_CNT + (19 -> 511), SDRPLLGRP_S2FUSER2CLK_CNT (5 -> 4), + and all 32 divergent iocsr chain0/1/2 words + (12/18/2; at the import those three chains equalled + mainline's, and no later fork commit touches + iocsr_config.h, pll_config.h or sdram_config.h). + The same commit also rewrote 33 words of chain3, + the DDR3 I/O bank, from the import's values TO + mainline's -- which is why chain3 is identical + today. + f7d1761b89 -- GENERALIO3, GENERALIO4 (1 -> 0). + c7ed0834ac vs 6bd041f00d, import vs import, no fork + commit involved -- I2C3USEFPGA (0 vs 1). + Also re-verified here rather than taken from plan + S3.2: mainline's four headers have had no value change + since 6bd041f00d -- qtsdiff of that commit's headers + (renamed) against v2026.07 reports zero differing + defines and zero differing words in every array [V] -- + so this carry forfeits no upstream fix stream. +Rename: CONFIG_HPS_ -> CFG_HPS_ everywhere a name has that + prefix (mainline's board CONFIG_* -> CFG_* migration). + Textual only, confirmed value-preserving by the + verification below. pinmux_config.h has no #define + with that prefix at all (it is an array, sys_mgr_init_table), + and 4 of iocsr_config.h's SCANCHAIN*_LENGTH defines + likewise carry no value change from the rename. +Non-value lines: none. The fork's copies also differ from mainline's + in seven places that carry no value (11 fork-side + lines, 5 patched-side lines, 16 unified-diff lines) -- + all four files put + the SPDX tag inside the comment block (the 2017 form) + where mainline has it on line 1, iocsr_config.h and + pll_config.h have an extra blank line before the closing + #endif, and pinmux_config.h has two spaces before the + SPIM0USEFPGA comment where mainline has one. None is + carried. Mainline's own generator for these headers, + arch/arm/mach-socfpga/qts-filter.sh:26,69,105,148, emits + the line-1 form, and u-boot's scripts/checkpatch.pl + flags the other one ("Misplaced SPDX-License-Identifier + tag - use line 1 instead"), so importing it would add + four checkpatch warnings for no value [V: the 4 + "Misplaced SPDX" warnings before, 0 after -- totals + 3 errors/7 warnings/0 checks to 3 errors/3 warnings/0 + checks, remeasured at integration; the 3 remaining + errors are the Origin:/Snapshot: sha citations, which + follow this repo's 0001-patch template, not u-boot's + commit-reference style]. Both qtsdiff runs + below are byte-identical with or without these + lines -- qtsdiff parses defines and arrays, not comments. + +Verification [V] (docs/uboot-mainline-port.md S3.2/S3.2a; the +normalization qtsdiff.py applies is exactly the CONFIG_HPS_ -> +CFG_HPS_ rename, nothing else -- +/mnt/source/uboot-mainline/verify-qts/qtsdiff.py, unmodified. +Transcripts are indented two spaces so that a later `git commit +--amend` or interactive rebase, whose default cleanup=strip drops +every line starting with '#', cannot silently delete the '### file' +headings and with them this evidence.) +---------------------------------------------------------------------- + +1) Patched tree vs fork HEAD 8dcc3484 -- expect zero differences, + proving the rename alone (no value) separates the two trees: + + $ python3 qtsdiff.py /qts /qts PATCHED FORK-8dcc3484 + ### sdram_config.h + PATCHED: 157 defines, 2 arrays ({'ac_rom_init': 36, 'inst_rom_init': 127}) + FORK-8dcc3484: 157 defines, 2 arrays ({'ac_rom_init': 36, 'inst_rom_init': 127}) + common defines: 157 DIFFERING: 0 + array ac_rom_init: 36 words, 0 differ (0.00%) + array inst_rom_init: 127 words, 0 differ (0.00%) + + ### pll_config.h + PATCHED: 68 defines, 0 arrays ({}) + FORK-8dcc3484: 68 defines, 0 arrays ({}) + common defines: 68 DIFFERING: 0 + + ### pinmux_config.h + PATCHED: 0 defines, 1 arrays ({'sys_mgr_init_table': 207}) + FORK-8dcc3484: 0 defines, 1 arrays ({'sys_mgr_init_table': 207}) + common defines: 0 DIFFERING: 0 + array sys_mgr_init_table: 207 words, 0 differ (0.00%) + + ### iocsr_config.h + PATCHED: 4 defines, 4 arrays ({'iocsr_scan_chain0_table': 24, 'iocsr_scan_chain1_table': 54, 'iocsr_scan_chain2_table': 30, 'iocsr_scan_chain3_table': 524}) + FORK-8dcc3484: 4 defines, 4 arrays ({'iocsr_scan_chain0_table': 24, 'iocsr_scan_chain1_table': 54, 'iocsr_scan_chain2_table': 30, 'iocsr_scan_chain3_table': 524}) + common defines: 4 DIFFERING: 0 + array iocsr_scan_chain0_table: 24 words, 0 differ (0.00%) + array iocsr_scan_chain1_table: 54 words, 0 differ (0.00%) + array iocsr_scan_chain2_table: 30 words, 0 differ (0.00%) + array iocsr_scan_chain3_table: 524 words, 0 differ (0.00%) + + +RESULT: zero differences in every define and every array, all four +files -- the rename carried no value. + +2) Patched tree vs pristine mainline v2026.07 -- expect exactly plan + S3.2's divergence list: + + $ python3 qtsdiff.py /qts /qts PATCHED PRISTINE-2026.07 + ### sdram_config.h + PATCHED: 157 defines, 2 arrays ({'ac_rom_init': 36, 'inst_rom_init': 127}) + PRISTINE-2026.07: 157 defines, 2 arrays ({'ac_rom_init': 36, 'inst_rom_init': 127}) + common defines: 157 DIFFERING: 2 + !! CFG_HPS_SDR_CTRLCFG_FPGAPORTRST: PATCHED=0x3FFF PRISTINE-2026.07=0x1FF (dec 16383 vs 511) + !! REG_FILE_INIT_SEQ_SIGNATURE: PATCHED=0x555504a0 PRISTINE-2026.07=0x555504a1 (dec 1431635104 vs 1431635105) + array ac_rom_init: 36 words, 0 differ (0.00%) + array inst_rom_init: 127 words, 0 differ (0.00%) + + ### pll_config.h + PATCHED: 68 defines, 0 arrays ({}) + PRISTINE-2026.07: 68 defines, 0 arrays ({}) + common defines: 68 DIFFERING: 2 + !! CFG_HPS_PERPLLGRP_S2FUSER1CLK_CNT: PATCHED=511 PRISTINE-2026.07=19 (dec 511 vs 19) + !! CFG_HPS_SDRPLLGRP_S2FUSER2CLK_CNT: PATCHED=4 PRISTINE-2026.07=5 (dec 4 vs 5) + + ### pinmux_config.h + PATCHED: 0 defines, 1 arrays ({'sys_mgr_init_table': 207}) + PRISTINE-2026.07: 0 defines, 1 arrays ({'sys_mgr_init_table': 207}) + common defines: 0 DIFFERING: 0 + array sys_mgr_init_table: 207 words, 3 differ (1.45%) + [35] PATCHED=0 PRISTINE-2026.07=1 + [36] PATCHED=0 PRISTINE-2026.07=1 + [201] PATCHED=0 PRISTINE-2026.07=1 + + ### iocsr_config.h + PATCHED: 4 defines, 4 arrays ({'iocsr_scan_chain0_table': 24, 'iocsr_scan_chain1_table': 54, 'iocsr_scan_chain2_table': 30, 'iocsr_scan_chain3_table': 524}) + PRISTINE-2026.07: 4 defines, 4 arrays ({'iocsr_scan_chain0_table': 24, 'iocsr_scan_chain1_table': 54, 'iocsr_scan_chain2_table': 30, 'iocsr_scan_chain3_table': 524}) + common defines: 4 DIFFERING: 0 + array iocsr_scan_chain0_table: 24 words, 12 differ (50.00%) + [6] PATCHED=0x00060180 PRISTINE-2026.07=0x00020080 + [8] PATCHED=0x18000000 PRISTINE-2026.07=0x08000000 + [9] PATCHED=0x00018060 PRISTINE-2026.07=0x00018020 + [12] PATCHED=0x000300C0 PRISTINE-2026.07=0x00010040 + [13] PATCHED=0x0C030000 PRISTINE-2026.07=0x04010000 + [14] PATCHED=0x0C000000 PRISTINE-2026.07=0x04000000 + [15] PATCHED=0x00000030 PRISTINE-2026.07=0x00000010 + [16] PATCHED=0x0000C030 PRISTINE-2026.07=0x00004010 + [19] PATCHED=0x06018000 PRISTINE-2026.07=0x02008000 + [20] PATCHED=0x06000000 PRISTINE-2026.07=0x02000000 + [21] PATCHED=0x00000018 PRISTINE-2026.07=0x00000008 + [22] PATCHED=0x00006018 PRISTINE-2026.07=0x00002008 + array iocsr_scan_chain1_table: 54 words, 18 differ (33.33%) + [1] PATCHED=0x300C0000 PRISTINE-2026.07=0x10040000 + [2] PATCHED=0x300000C0 PRISTINE-2026.07=0x100000C0 + [3] PATCHED=0x000000C0 PRISTINE-2026.07=0x00000040 + [4] PATCHED=0x000300C0 PRISTINE-2026.07=0x00010040 + [12] PATCHED=0x000300C0 PRISTINE-2026.07=0x00010040 + [14] PATCHED=0x0C000000 PRISTINE-2026.07=0x04000000 + [15] PATCHED=0x00000030 PRISTINE-2026.07=0x00000010 + [16] PATCHED=0x0000C030 PRISTINE-2026.07=0x00004010 + [37] PATCHED=0x00C03000 PRISTINE-2026.07=0x00401000 + [42] PATCHED=0x00601806 PRISTINE-2026.07=0x00600802 + [44] PATCHED=0x80600000 PRISTINE-2026.07=0x80200000 + [45] PATCHED=0x80000601 PRISTINE-2026.07=0x80000600 + ... 6 more + array iocsr_scan_chain2_table: 30 words, 2 differ (6.67%) + [4] PATCHED=0x000300C0 PRISTINE-2026.07=0x0C0300C0 + [28] PATCHED=0x00C0300C PRISTINE-2026.07=0x00C01004 + array iocsr_scan_chain3_table: 524 words, 0 differ (0.00%) + + +RESULT: matches plan S3.2's table exactly -- FPGAPORTRST, both +S2FUSERnCLK counts, the same 3 pinmux bits at the same array indices +(35/36/201 = GENERALIO3/GENERALIO4/I2C3USEFPGA), the same 32-of-108 +IOCSR word split across chains 0/1/2 (12+18+2 = 32 of 24+54+30 = 108), +chain 3 untouched, and the REG_FILE_INIT_SEQ_SIGNATURE stamp (Quartus +generator stamp; the SPL writes it verbatim to the DDR sequencer's +register-file signature register -- sequencer.c initialize_reg_file() +-- and nothing in the tree reads it back). No difference outside that +list was observed, and the patch changes no line that is not one of +these 39 values. + +Not proven by this (docs/uboot-mainline-port.md S3.2a, unchanged by +this patch): that a 2026 SPL consuming these tables behaves as the +2017.03 SPL did, and that the scalar #defines above (not greppable -- +they compile to instruction immediates) survive to the built SPL +byte-for-byte. Both rest on this source diff plus S3.2a's binary +search against the *stock* uboot.img; this task builds no SPL. [U]: +handoff-equality against our own built SPL is U9/U6, not this task. + +Signed-off-by: Sorgelig +Signed-off-by: Michael C. Ferguson +--- + board/terasic/de10-nano/qts/iocsr_config.h | 64 ++++++++++----------- + board/terasic/de10-nano/qts/pinmux_config.h | 6 +- + board/terasic/de10-nano/qts/pll_config.h | 4 +- + board/terasic/de10-nano/qts/sdram_config.h | 4 +- + 4 files changed, 39 insertions(+), 39 deletions(-) + +diff --git a/board/terasic/de10-nano/qts/iocsr_config.h b/board/terasic/de10-nano/qts/iocsr_config.h +index 1555500..f5a4823 100644 +--- a/board/terasic/de10-nano/qts/iocsr_config.h ++++ b/board/terasic/de10-nano/qts/iocsr_config.h +@@ -18,32 +18,32 @@ const unsigned long iocsr_scan_chain0_table[] = { + 0xC0000000, + 0x0000003F, + 0x00008000, +- 0x00020080, ++ 0x00060180, + 0x18060000, +- 0x08000000, +- 0x00018020, ++ 0x18000000, ++ 0x00018060, + 0x00000000, + 0x00004000, +- 0x00010040, +- 0x04010000, +- 0x04000000, +- 0x00000010, +- 0x00004010, ++ 0x000300C0, ++ 0x0C030000, ++ 0x0C000000, ++ 0x00000030, ++ 0x0000C030, + 0x00002000, + 0x00020000, +- 0x02008000, +- 0x02000000, +- 0x00000008, +- 0x00002008, ++ 0x06018000, ++ 0x06000000, ++ 0x00000018, ++ 0x00006018, + 0x00001000, + }; + + const unsigned long iocsr_scan_chain1_table[] = { + 0x00100000, +- 0x10040000, +- 0x100000C0, +- 0x00000040, +- 0x00010040, ++ 0x300C0000, ++ 0x300000C0, ++ 0x000000C0, ++ 0x000300C0, + 0x00008000, + 0x00060180, + 0x20000000, +@@ -51,11 +51,11 @@ const unsigned long iocsr_scan_chain1_table[] = { + 0x00000080, + 0x00020000, + 0x00004000, +- 0x00010040, ++ 0x000300C0, + 0x10000000, +- 0x04000000, +- 0x00000010, +- 0x00004010, ++ 0x0C000000, ++ 0x00000030, ++ 0x0000C030, + 0x00002000, + 0x00020000, + 0x06018000, +@@ -76,22 +76,22 @@ const unsigned long iocsr_scan_chain1_table[] = { + 0x00002000, + 0x00000400, + 0x00000000, +- 0x00401000, ++ 0x00C03000, + 0x00000003, + 0x00000000, + 0x00000000, + 0x00000200, +- 0x00600802, ++ 0x00601806, + 0x00000000, +- 0x80200000, +- 0x80000600, +- 0x00000200, ++ 0x80600000, ++ 0x80000601, ++ 0x00000601, + 0x00000100, +- 0x00300401, +- 0xC0100400, +- 0x40100000, +- 0x40000300, +- 0x000C0100, ++ 0x00300C03, ++ 0xC0300C00, ++ 0xC0300000, ++ 0xC0000300, ++ 0x000C0300, + 0x00000080, + }; + +@@ -100,7 +100,7 @@ const unsigned long iocsr_scan_chain2_table[] = { + 0x00000000, + 0x0FF00000, + 0x00000000, +- 0x0C0300C0, ++ 0x000300C0, + 0x00008000, + 0x00080000, + 0x18060000, +@@ -124,7 +124,7 @@ const unsigned long iocsr_scan_chain2_table[] = { + 0x00000000, + 0x03000000, + 0x0000800C, +- 0x00C01004, ++ 0x00C0300C, + 0x00000800, + }; + +diff --git a/board/terasic/de10-nano/qts/pinmux_config.h b/board/terasic/de10-nano/qts/pinmux_config.h +index 6a9c415..7a375b0 100644 +--- a/board/terasic/de10-nano/qts/pinmux_config.h ++++ b/board/terasic/de10-nano/qts/pinmux_config.h +@@ -42,8 +42,8 @@ const u8 sys_mgr_init_table[] = { + 0, /* GENERALIO0 */ + 1, /* GENERALIO1 */ + 1, /* GENERALIO2 */ +- 1, /* GENERALIO3 */ +- 1, /* GENERALIO4 */ ++ 0, /* GENERALIO3 */ ++ 0, /* GENERALIO4 */ + 0, /* GENERALIO5 */ + 0, /* GENERALIO6 */ + 1, /* GENERALIO7 */ +@@ -208,7 +208,7 @@ const u8 sys_mgr_init_table[] = { + 1, /* UART1USEFPGA */ + 0, /* CAN1USEFPGA */ + 0, /* USB1USEFPGA */ +- 1, /* I2C3USEFPGA */ ++ 0, /* I2C3USEFPGA */ + 1, /* I2C2USEFPGA */ + 0, /* I2C1USEFPGA */ + 0, /* SPIM1USEFPGA */ +diff --git a/board/terasic/de10-nano/qts/pll_config.h b/board/terasic/de10-nano/qts/pll_config.h +index 794fa20..cf69bb8 100644 +--- a/board/terasic/de10-nano/qts/pll_config.h ++++ b/board/terasic/de10-nano/qts/pll_config.h +@@ -34,7 +34,7 @@ + #define CFG_HPS_PERPLLGRP_PERQSPICLK_CNT 511 + #define CFG_HPS_PERPLLGRP_PERNANDSDMMCCLK_CNT 4 + #define CFG_HPS_PERPLLGRP_PERBASECLK_CNT 4 +-#define CFG_HPS_PERPLLGRP_S2FUSER1CLK_CNT 19 ++#define CFG_HPS_PERPLLGRP_S2FUSER1CLK_CNT 511 + #define CFG_HPS_PERPLLGRP_DIV_USBCLK 0 + #define CFG_HPS_PERPLLGRP_DIV_SPIMCLK 0 + #define CFG_HPS_PERPLLGRP_DIV_CAN0CLK 4 +@@ -53,7 +53,7 @@ + #define CFG_HPS_SDRPLLGRP_DDR2XDQSCLK_PHASE 0 + #define CFG_HPS_SDRPLLGRP_DDRDQCLK_CNT 1 + #define CFG_HPS_SDRPLLGRP_DDRDQCLK_PHASE 4 +-#define CFG_HPS_SDRPLLGRP_S2FUSER2CLK_CNT 5 ++#define CFG_HPS_SDRPLLGRP_S2FUSER2CLK_CNT 4 + #define CFG_HPS_SDRPLLGRP_S2FUSER2CLK_PHASE 0 + + #define CFG_HPS_CLK_OSC1_HZ 25000000 +diff --git a/board/terasic/de10-nano/qts/sdram_config.h b/board/terasic/de10-nano/qts/sdram_config.h +index abf29f2..317072d 100644 +--- a/board/terasic/de10-nano/qts/sdram_config.h ++++ b/board/terasic/de10-nano/qts/sdram_config.h +@@ -53,7 +53,7 @@ + #define CFG_HPS_SDR_CTRLCFG_EXTRATIME1_CFG_EXTRA_CTL_CLK_RD_TO_WR_DIFF_CHIP 2 + #define CFG_HPS_SDR_CTRLCFG_FIFOCFG_INCSYNC 0 + #define CFG_HPS_SDR_CTRLCFG_FIFOCFG_SYNCMODE 0 +-#define CFG_HPS_SDR_CTRLCFG_FPGAPORTRST 0x1FF ++#define CFG_HPS_SDR_CTRLCFG_FPGAPORTRST 0x3FFF + #define CFG_HPS_SDR_CTRLCFG_LOWPWREQ_SELFRFSHMASK 3 + #define CFG_HPS_SDR_CTRLCFG_LOWPWRTIMING_AUTOPDCYCLES 0 + #define CFG_HPS_SDR_CTRLCFG_LOWPWRTIMING_CLKDISABLECYCLES 8 +@@ -149,7 +149,7 @@ + #define IO_SHIFT_DQS_EN_WHEN_SHIFT_DQS 0 + #define MAX_LATENCY_COUNT_WIDTH 5 + #define READ_VALID_FIFO_SIZE 16 +-#define REG_FILE_INIT_SEQ_SIGNATURE 0x555504a1 ++#define REG_FILE_INIT_SEQ_SIGNATURE 0x555504a0 + #define RW_MGR_MEM_ADDRESS_MIRRORING 0 + #define RW_MGR_MEM_DATA_MASK_WIDTH 4 + #define RW_MGR_MEM_DATA_WIDTH 32 diff --git a/board/mister/de10nano/patches/uboot/0004-cmd-mem-add-mt-memory-test-against-value.patch b/board/mister/de10nano/patches/uboot/0004-cmd-mem-add-mt-memory-test-against-value.patch new file mode 100644 index 00000000..93a1ba8a --- /dev/null +++ b/board/mister/de10nano/patches/uboot/0004-cmd-mem-add-mt-memory-test-against-value.patch @@ -0,0 +1,175 @@ +From c0ed23f52e203d704c0b149408f2cc0b1bb48939 Mon Sep 17 00:00:00 2001 +From: Sorgelig +Date: Fri, 7 Apr 2017 12:46:47 +0800 +Subject: [PATCH] cmd: mem: add mt, memory test against value + +Add the fork's "mt" command: compare the value at a memory address +against a given value and return 0 (shell "true") if equal, 1 (shell +"false") if not. Standard U-Boot has no equivalent -- itest.l compares +two operands with an explicit relational operator, but nothing in +mainline does a bare equality test suitable for direct use in a hush +"if" without an intervening setexpr/test pair. + +Stock's fpgacheck (include/configs/socfpga_de10_nano.h) depends on this +exact command and this exact exit-status sense: + + fpgacheck=if mt 0x1FFFFF08 0xBEEFB001;then mw 0x1FFFFF08 0; + if mt 0x1FFFF000 0x87654321;then mw 0x1FFFF000 0; + env import -t 0x1FFFF004;run fpgaload;fi;else run fpgaload;fi + +Carrying this command instead of rewriting fpgacheck around itest.l +keeps the environment text byte-identical to stock's (21 entries, +1,150 B -- docs/uboot-mainline-port.md 3.4), which turns the +environment-parity check in docs/boot-chain.md 6 into a plain cmp +instead of an allowed-delta list. + +Origin +------ +Fork commit c0ed23f52e203d704c0b149408f2cc0b1bb48939, "Implement simple +memory test against value.", Sorgelig , +2017-04-07, MiSTer-devel/U-Boot_MiSTer (the checkout at +work/U-Boot_MiSTer; 2017.03 base, fork head 8dcc3484aa). +Author: Sorgelig +Upstream status: Not merged, and not upstreamable as a generic U-Boot + command -- it exists only because stock's fpgacheck + script depends on this exact name and exit-status sense; + mainline's itest.l already covers the general case. +Disposition: Carry (owner decision 2026-09-14, "mirror stock" -- + docs/uboot-mainline-port.md 3.4, amending ADR 0024 + Decision 5). ADR 0024 had picked the itest.l rewrite of + fpgacheck instead; that path is kept documented as the + fallback and as the free stock-hardware smoke test + (docs/uboot-mainline-port.md 3.4, 8) but is not what + ships. + +Forward-port: 2017.03 -> 2026.07, Michael C. Ferguson, 2026-09-14, task + U2c. Two renames only, no behaviour change: + cmd_tbl_t -> struct cmd_tbl (typedef + retired between 2017.03 and + 2026.07) + simple_strtoul(x,NULL,16) -> hextoul(x,NULL) (thin + wrapper introduced later; + every other size/address/ + value parse in this file's + do_mem_mw already uses it) + Placement mirrors the fork exactly: do_mem_mt right after + do_mem_md and before do_mem_mm, and the mt U_BOOT_CMD + right after md's and before mm's. Help strings are + stock's, unmodified: "memory test against value" / + "[.b, .w, .l] address value". + +Verified by execution -- U-Boot sandbox build +---------------------------------------------- +Built as a throwaway host binary (not the target), against the +2026.07 tarball plus only this patch: `make sandbox_defconfig && make +NO_SDL=1` (mkeficapsule's gnutls host dependency and the sandbox EFI +capsule-on-disk test images are irrelevant to this command and were +disabled/allowed-missing for this scratch build only -- +CONFIG_TOOLS_MKEFICAPSULE, CONFIG_EFI_CAPSULE_ON_DISK, +BINMAN_ALLOW_MISSING=1 -- nothing here touches the patch or the +image this repo ships). Driven non-interactively with `./u-boot -c +''`; DRAM/device-model banner elided below. + + => help mt + mt - memory test against value + + Usage: + mt [.b, .w, .l] address value + + => mw.l 1000 BEEFB001; mt.l 1000 BEEFB001; echo rc=$? + rc=0 + + => mw.l 1000 BEEFB001; mt.l 1000 12345678; echo rc=$? + rc=1 + + => mw.l 1000 BEEFB001; if mt.l 1000 BEEFB001; then echo T; else echo F; fi + T + + => mw.l 1000 BEEFB001; if mt.l 1000 12345678; then echo T; else echo F; fi + F + +rc=0 on equality (memcmp() == 0, shell "true", hush THEN branch), rc=1 +on inequality (shell "false", hush ELSE branch) -- exactly the sense +docs/boot-chain.md 6.1's three-way fpgacheck dispatch requires for both +the outer 0x1FFFFF08/0xBEEFB001 check and the inner +0x1FFFF000/0x87654321 check. Full build/run log: +/mnt/source/uboot-wave-a/U2c/build5.log, +/mnt/source/uboot-wave-a/U2c/transcript.txt. + +checkpatch.pl --strict flags four ERROR-level findings inside +do_mem_mt itself: two single-line "if (...) return ...;" statements, +one assignment inside an if condition (the same cmd_get_data_size() +line as one of those), and "return (rc);"'s redundant parens. All four +are carried unchanged from the fork's own 2017-04-07 code, not +introduced by this port -- fixing them was out of scope for "port +do_mem_mt", and "mirror stock wherever there is a choice" (this doc's +header) argues for the byte-for-byte-equivalent port over a stylistic +rewrite. (A fifth finding, a CHECK on the U_BOOT_CMD() line, is not +specific to this hunk -- every other U_BOOT_CMD() invocation already in +this file trips the same check.) Not a blocker: this patch is not +upstream material (next paragraph), so checkpatch is informational +here, not a gate. + +Not upstream: MiSTer-only command, drafts only per owner decision 4 +(nothing is sent to any mailing list). + +Signed-off-by: Sorgelig +Signed-off-by: Michael C. Ferguson +--- + cmd/mem.c | 36 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 36 insertions(+) + +diff --git a/cmd/mem.c b/cmd/mem.c +index 68eb6989..0a90f934 100644 +--- a/cmd/mem.c ++++ b/cmd/mem.c +@@ -114,6 +114,36 @@ static int do_mem_md(struct cmd_tbl *cmdtp, int flag, int argc, + return (rc); + } + ++/* Memory Test ++ * ++ * Syntax: ++ * mt{.b, .w, .l, .q} {addr} {value} ++ */ ++static int do_mem_mt(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ ulong addr, val; ++ ++ int size; ++ int rc = 0; ++ ++ if (argc < 3) return CMD_RET_USAGE; ++ ++ if ((size = cmd_get_data_size(argv[0], 4)) < 0) return 1; ++ ++ addr = hextoul(argv[1], NULL); ++ addr += base_address; ++ ++ val = hextoul(argv[2], NULL); ++ ++ const void *buf = map_sysmem(addr, size); ++ ++ rc = memcmp(buf, &val, size) ? 1 : 0; ++ ++ unmap_sysmem(buf); ++ return (rc); ++} ++ + static int do_mem_mm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) + { +@@ -1318,6 +1348,12 @@ U_BOOT_CMD( + "[.b, .w, .l" HELP_Q "] address [# of objects]" + ); + ++U_BOOT_CMD( ++ mt, 3, 1, do_mem_mt, ++ "memory test against value", ++ "[.b, .w, .l] address value" ++); ++ + U_BOOT_CMD( + mm, 2, 1, do_mem_mm, + "memory modify (auto-incrementing address)", diff --git a/board/mister/de10nano/patches/uboot/0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch b/board/mister/de10nano/patches/uboot/0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch new file mode 100644 index 00000000..ce614faa --- /dev/null +++ b/board/mister/de10nano/patches/uboot/0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch @@ -0,0 +1,236 @@ +From d6010efe50464c85ccec0fc4e020ca80251ed3d9 Mon Sep 17 00:00:00 2001 +From: Sorgelig +Date: Mon, 27 Mar 2017 11:35:31 +0800 +Subject: [PATCH] arm: socfpga: gen5: release the FPGA bridges after reset + if the FPGA is in user mode + +MiSTer restarts the HPS without reconfiguring the fabric, and that is the +normal case, not an edge case. `fpgacheck` (docs/boot-chain.md §6.1) has three +dispatch outcomes; in the middle one — Main_MiSTer set the warm flag at +`0x1FFFFF08` to `0xBEEFB001` but staged no new core at `0x1FFFF000` — U-Boot +deliberately leaves the running core alone and never runs `fpgaload`. Since +`bridge enable` lives only inside `fpgaload`, nothing in that path releases the +HPS↔FPGA bridges. `arch_early_init_r()` has just put all three of them *into* +reset (`socfpga_bridges_reset(1)`), so Linux and Main_MiSTer would come up to a +fabric they cannot reach at `0xC0000000` / `0xFF200000`. + +Stock's bootloader closes this by calling `socfpga_bridges_reset(0)` at the end +of `arch_early_init_r()`. Do the same here. + +A cold boot is unaffected: `socfpga_bridges_reset(0)` tests +`fpgamgr_test_fpga_ready()` and returns before touching `brgmodrst` or the L3 +remap when the fabric is unconfigured, which is exactly the state a cold boot +is in at this point — `fpga load` has not run yet. + +Provenance +---------- +Origin: d6010efe50464c85ccec0fc4e020ca80251ed3d9 + "Enable bridges after reset if FPGA is in user mode." + MiSTer-devel/U-Boot_MiSTer, 2017-03-27, against v2017.03. + Local clone: work/U-Boot_MiSTer @ 8dcc3484. +Author: Sorgelig +Upstream: No, and never submitted. Upstream's gen5 `arch_early_init_r()` + has never called `socfpga_bridges_reset(0)`, before or after the + split: `git log -p --follow arch/arm/mach-socfpga/misc_gen5.c` + over that file's whole history (33 commits on the file itself + at v2026.07, 71 with --follow, reaching back through the + `d1c559af5f` split into misc.c) contains exactly one such + call and it is + `socfpga_bridges_reset(1)`, and the origin commit itself adds the + line as a `+` against upstream-derived v2017.03 code. That is why + the `enable == 0` arm of that function has **no caller at all** in + a pristine v2026.07 tree: all three call sites — + `misc_gen5.c:203`, `spl_gen5.c:98` and + `drivers/fpga/socfpga_gen5.c:209` — pass 1. This patch is + therefore not new code; it makes existing mainline code + reachable. +Disposition: Carry. It encodes a MiSTer boot-flow requirement — a warm HPS + reset that preserves the fabric — that no upstream SoCFPGA board + expresses, and stock's env (`fpgacheck`/`fpgaload`) is what makes + it load-bearing. U7 does not propose it upstream. +Forward-port: 2017.03 → 2026.07, Michael C. Ferguson, 2026-09-14, task U2d + (docs/uboot-tasks.md). The origin commit is one line in + `arch/arm/mach-socfpga/misc.c`; upstream `d1c559af5f` ("arm: + socfpga: Restructure misc driver", Ley Foon Tan, 2017-04-26) + split the gen5 half of that file out to `misc_gen5.c`, so the + line lands there instead. The origin commit also deleted two + now-removed `CONFIG_` defines from + `include/configs/socfpga_de10_nano.h`; that hunk is 2017-era + Kconfig migration debris and is dropped. The explanatory comment + above the call is added by this forward-port and is not + Sorgelig's text. +Decision: Owner, 2026-09-14 (docs/uboot-tasks.md, "mirror stock"): carry + the fork's warm-reboot bridge behaviour rather than accept + mainline's. docs/uboot-mainline-port.md §3.3 records the + argument below. + +The equivalence argument +------------------------ +The origin commit's *line* ports verbatim; what has to be proved is that +2026.07's `socfpga_bridges_reset(0)` leaves the same machine state that +2017.03's did. It does, and the two bodies are a near textual match. + +Fork, `arch/arm/mach-socfpga/reset_manager.c:93-119` (`8dcc3484`): + + l3mask = LWHPS2FPGA|HPS2FPGA|OCRAM = 0x19 + writel(0, &sysmgr_regs->iswgrp_handoff[0]); + writel(l3mask, &sysmgr_regs->iswgrp_handoff[1]); + if (!fpgamgr_test_fpga_ready()) { printf("... FPGA not ready ..."); return; } + writel(0, &reset_manager_base->brg_mod_reset); + writel(l3mask, SOCFPGA_L3REGS_ADDRESS); + +Mainline, `arch/arm/mach-socfpga/reset_manager_gen5.c:89-117` (v2026.07): + + socfpga_bridges_set_handoff_regs(false, false, false); + → brgmask = 0 + → l3rmask = OCRAM|HPS2FPGA|LWHPS2FPGA = 0x19 + → writel(0, sysmgr + SYSMGR_ISWGRP_HANDOFF_OFFSET(0)); + → writel(0x19, sysmgr + SYSMGR_ISWGRP_HANDOFF_OFFSET(1)); + if (!fpgamgr_test_fpga_ready()) { printf("... FPGA not ready ..."); return; } + writel(0, rstmgr + RSTMGR_GEN5_BRGMODRST); + writel(0x19, SOCFPGA_L3REGS_ADDRESS); + +Same four registers, same four values, same guard, same order: + + * `iswgrp_handoff[0..1]` are at sysmgr `+0x80`/`+0x84` in both trees + (fork `mach/system_manager.h:51` — `u32 iswgrp_handoff[8]` at 0x80; + mainline `mach/system_manager_gen5.h:22,37`). + * `brg_mod_reset` is rstmgr `+0x1c` in both (fork + `mach/reset_manager.h:26`; mainline `RSTMGR_GEN5_BRGMODRST 0x1c`). + * `SOCFPGA_L3REGS_ADDRESS` is `0xff800000` in both, and is the same register + as `nic301_regs->remap` (offset 0 of `struct nic301_registers`) in both. + * `fpgamgr_test_fpga_ready()`'s FUNCTION BODY is character-for-character + identical in the two trees (`arch/arm/mach-socfpga/fpga_manager.c`; the + files themselves differ in the SPDX line, `` vs `` + and DECLARE_GLOBAL_DATA_PTR): init_done sampled twice, then + `fpgamgr_get_mode() == FPGAMGRREGS_MODE_USERMODE`. + +Position in the boot flow is equivalent too. In both trees the call is the last +statement of `arch_early_init_r()`, after the FPGA device is registered, and +`arch_early_init_r` sits at the same point of the board_r init sequence +relative to `console_init_r` (fork `common/board_r.c:818` vs `:866`; mainline +`common/board_r.c:683` vs `:734`), so the "FPGA not ready" message reaches the +console exactly as it does on stock. + +Three surrounding deltas exist between the two trees. None of them changes the +state this patch produces, and none is introduced by this patch: + + 1. `socfpga_bridges_reset(1)`, earlier in the same function, writes + `0xffffffff` to `brgmodrst` on the fork and `0x7` on mainline. `brgmodrst` + implements exactly three bits — HPS2FPGA, LWHPS2FPGA, FPGA2HPS, reset ids + 96/97/98 = bank 3 bits 0/1/2 in + `dts/upstream/include/dt-bindings/reset/altr,rst-mgr.h:58-61` — so the + two writes set the same implemented state, and this patch then clears the + register to 0 in both cases anyway. + 2. Mainline's `socfpga_bridges_reset(1)` additionally writes + `L3REGS_REMAP_OCRAM_MASK` (0x1) to the L3 remap; the fork's does not. Both + trees then write 0x1 to that same register a few statements later + (mainline inside `socfpga_sdram_remap_zero()`, the fork inline in + `arch_early_init_r`), and on the path this patch adds it is overwritten + with 0x19. Transient only. + 3. Mainline's `socfpga_sdram_remap_zero()` ORs in `0x8` when + `socfpga_is_booting_from_fpga()`. That predicate is + `__image_copy_start >= 0xc0000000 && < 0xfc000000`; this build's + `__image_copy_start` is `0x01000040` (SDRAM), so it is false and the write + is the fork's plain `0x1`. + +At cold boot the patch is a no-op in the sense that matters — no register any +later code reads is left in a state that changes behaviour: + + * the early return fires before `brgmodrst` and the L3 remap are touched, so + the bridges stay in reset exactly as unpatched mainline leaves them; + * `iswgrp_handoff[0]`/`[1]` *are* written (0 and 0x19) before the guard, in + both trees. The only consumer of those two registers in the whole tree is + `do_bridge_reset(1, …)` — the `bridge enable` that `fpgaload` runs on the + cold path — and it calls `socfpga_bridges_set_handoff_regs()` itself and + then re-reads both registers (`misc_gen5.c:253-257`) before using them, so + it recomputes the identical values regardless; + * `iswgrp_handoff[2]`/`[3]` — the FPGAINTFGRP_MODULE and `fpgaport_rst` + values the SPL actually hands off, and the ones `bridge enable` consumes + unmodified — are not touched by `socfpga_bridges_reset()` at all. + +Verification, this build +------------------------ +Cross toolchain `arm-buildroot-linux-gnueabihf-` (gcc 15.3.0), +`socfpga_de10_nano_defconfig`, v2026.07 from `dl/uboot/u-boot-2026.07.tar.bz2` +(`CONFIG_TOOLS_MKEFICAPSULE` off so the host build needs no gnutls — a host-tool +symbol with no target effect; it is a `board/mister/de10nano/uboot.fragment` +line anyway, task U2e). Built clean; `CONFIG_ARCH_EARLY_INIT_R=y` in the +resolved config, so `arch_early_init_r()` is on the init sequence. + +`objdump -d u-boot --disassemble=arch_early_init_r`, patched: + + 100279a: 2000 movs r0, #0 + 100279c: f000 f8d0 bl 1002940 + 10027a0: 2000 movs r0, #0 + 10027a2: bd38 pop {r3, r4, r5, pc} + +and the callee it reaches (unchanged by this patch): + + 1002940 : + 1002944: b158 cbz r0, 100295e ; enable == 0 -> release + 100295e: 4602 mov r2, r0 ; f2h = false + 1002960: 4601 mov r1, r0 ; lwh2f = false + 1002962: bl 1002908 ; h2f = false + 1002966: bl 1002af8 + 100296a: cbnz r0, 1002978 ; ready -> release path + 1002970: ldr r1, [pc, #28] ; not ready: printf, return + 1002974: b.w 104dbcc + 1002978: ... + 100297c: bl 10022b0 + 1002980: str r4, [r0, #28] ; brgmodrst (+0x1c) = 0 + 1002986: movs r2, #25 ; 0x19 + 1002988: ldr r3, [pc, #0] ; 0xff800000 + 100298a: b.n 100295a ; str r2,[r3] -> L3 remap = 0x19 + +and `socfpga_bridges_set_handoff_regs` writes sysmgr `+0x80`/`+0x84`: + + 1002922: str.w r4, [r0, #128] @ 0x80 ; brgmask = 0 + 100292e: str.w r5, [r0, #132] @ 0x84 ; l3rmask = 0x19 + +A normalised whole-binary disassembly diff (addresses and PC-relative literals +stripped) of unpatched vs patched `u-boot` differs in exactly one function, +`arch_early_init_r`: the added `bl socfpga_bridges_reset`, the re-materialised +`movs r0, #0` return value, and one dropped alignment `nop`. Every other +instruction in the 533,592-byte U-Boot proper is unchanged. `spl/u-boot-spl.bin` +is byte-identical apart from the two digits of the build-time stamp, confirming +this touches U-Boot proper only. + +What this does NOT establish +---------------------------- +Everything above is source and object-code equality. It is not a boot. Only +hardware can confirm (a) that a warm reset with the fabric preserved actually +reaches this code with the FPGA still reporting user mode, (b) that a core is +reachable from Linux across that reset, and (c) that a cold boot still prints +nothing new and still loads `menu.rbf`. The DE10 hardware gate (docs/uboot-tasks.md +U6) is deferred, so this patch ships in a build artifact that boots no board. + +Signed-off-by: Sorgelig +Signed-off-by: Michael C. Ferguson +--- + arch/arm/mach-socfpga/misc_gen5.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/arch/arm/mach-socfpga/misc_gen5.c b/arch/arm/mach-socfpga/misc_gen5.c +index ae33051..4c2a1db 100644 +--- a/arch/arm/mach-socfpga/misc_gen5.c ++++ b/arch/arm/mach-socfpga/misc_gen5.c +@@ -207,6 +207,18 @@ int arch_early_init_r(void) + /* Add device descriptor to FPGA device table */ + socfpga_fpga_add(&altera_fpga[0]); + ++ /* ++ * If the fabric is already configured -- a warm reset with the FPGA ++ * still in user mode -- take the bridges back out of reset and remap ++ * them into the memory map before handing over, so that the design in ++ * the fabric stays reachable across the reset. socfpga_bridges_reset(0) ++ * tests fpgamgr_test_fpga_ready() itself and returns without touching ++ * brgmodrst or the L3 remap when the fabric is unconfigured, so this is ++ * a no-op on a cold boot, where the bridges stay in reset until the ++ * FPGA is programmed and 'bridge enable' is issued. ++ */ ++ socfpga_bridges_reset(0); ++ + return 0; + } + diff --git a/board/mister/de10nano/patches/uboot/0006-configs-socfpga-de10-nano-bound-the-linux-boot-map-at-64-mib.patch b/board/mister/de10nano/patches/uboot/0006-configs-socfpga-de10-nano-bound-the-linux-boot-map-at-64-mib.patch new file mode 100644 index 00000000..c9bb1362 --- /dev/null +++ b/board/mister/de10nano/patches/uboot/0006-configs-socfpga-de10-nano-bound-the-linux-boot-map-at-64-mib.patch @@ -0,0 +1,109 @@ +From: Michael C. Ferguson +Date: Mon, 14 Sep 2026 00:00:00 +0000 +Subject: [PATCH] configs: socfpga_de10_nano: bound the Linux boot map at + 64 MiB, as stock does + +bootz relocates the flattened device tree before jumping to the kernel +(boot/image-fdt.c boot_relocate_fdt(), reached from +arch/arm/lib/bootm.c boot_prep_linux() -> image_setup_linux()). With no +`fdt_high` in the environment it asks the LMB for the highest free +4 KiB-aligned block below env_get_bootm_mapsize() + env_get_bootm_low(). +That size is CFG_SYS_BOOTMAPSZ when a board defines it, and otherwise +env_get_bootm_size(), i.e. the `bootm_size` variable or, failing that, +gd->ram_size capped at gd->ram_top (boot/image-board.c:127-163). + +Stock's 2017.03 bootloader defines CONFIG_SYS_BOOTMAPSZ as 64 MiB +(include/configs/socfpga_common.h:22 at v2017.03, carried unchanged in +the MiSTer fork), so the DTB the MiSTer kernel receives has always been +copied to just under 64 MiB -- inside the 511 MiB the kernel is told +about with mem=511M (docs/boot-chain.md section 6.4), and in memory +nothing in the fabric uses. + +Mainline removed the define in faea9e7a78 ("arm: socfpga: remove +CONFIG_SYS_BOOTMAPSZ", Simon Goldschmidt, 2019-01-09) and replaced it +with a `bootm_size=0xa000000` entry in socfpga_common.h's default +environment so users could override it. This build replaces that +default environment wholesale with stock's 21 entries, byte-identical +(board/mister/de10nano/uboot.env), which carry neither `bootm_size` nor +`fdt_high`; so on v2026.07 as configured here the bound silently falls +to the whole 1 GiB and the DTB is placed at the top of DDR, just below +U-Boot's own reserved region -- above the mem=511M line, inside the +half of DDR that MiSTer cores own, and 60-odd MiB away from where every +stock boot has put it. The kernel maps the blob through the fixed FDT +fixmap (arch/arm/kernel/head.S, FDT_FIXED_BASE) so it can still parse +it there, but the placement is a behaviour the environment parity +check cannot see, and "mirror stock wherever there is a choice" +(docs/uboot-tasks.md, owner decisions) says to put it back. + +Restoring the define is the one-line, environment-preserving fix: the +board header is the layer upstream itself uses for this symbol (some +65 board headers in v2026.07 still define CFG_SYS_BOOTMAPSZ) and it +overrides the missing env entry without adding a byte to the +environment. Effect on the boot path: bootz reserves the zImage at +$loadaddr (bootz_setup() -> lmb_reserve) and the DTB then lands in the +highest free block below 64 MiB, as on stock. No other consumer: +CFG_SYS_BOOTMAPSZ is read only by env_get_bootm_mapsize(), whose other +caller (boot_ramdisk_high) is inert without an initrd. + +Provenance +---------- +Origin: 5095ee088df05bd49f87721fe244ae1b3972b546 ("arm: socfpga: + Split SoCFPGA configuration", Pavel Machek, 2014-09-08) + introduced the 64 MiB CONFIG_SYS_BOOTMAPSZ into + include/configs/socfpga_common.h; stock MiSTer builds from + the fork's v2017.03-based tree with that line in place + (work/U-Boot_MiSTer @ 8dcc3484, socfpga_common.h:18). + faea9e7a78d78b178a26198ab8459f6daf423792 removed it upstream + on 2019-01-09. +Author: Michael C. Ferguson + (the define's value is upstream's own 2014 line; the + placement in the board header and the comment are new). +Upstream status: Not applicable. Upstream removed the define on purpose + in favour of an environment default; this patch exists + only because this build ships a fixed environment without + that entry. Never to be submitted. +Disposition: Carry -- owner decision 2026-09-14 (docs/uboot-tasks.md, + "mirror stock"): the DTB placement is a boot-path behaviour + the fork had and mainline-as-configured lost. Found by the + U9 adversarial review (docs/verification/uboot-mainline.md, + U9 section, finding on the effective environment). + +Verification, this build +------------------------ +Read out of the built ELF, before and after (task U9, cross toolchain +arm-buildroot-linux-gnueabihf- 15.3.0, v2026.07 plus patches 0001-0005): +before, env_get_bootm_mapsize() is `env_get("bootm_mapsize")` then a +tail branch to env_get_bootm_size(); after, it returns the constant +0x04000000 when the variable is unset. The transcript is in the U9 +section of docs/verification/uboot-mainline.md. spl/u-boot-spl.bin is +unaffected -- the symbol is consumed by U-Boot proper only. + +What this does NOT establish: that a kernel boots with the DTB at either +address. No hardware session is planned (docs/uboot-tasks.md U6). + +Signed-off-by: Michael C. Ferguson +--- + include/configs/socfpga_de10_nano.h | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/include/configs/socfpga_de10_nano.h b/include/configs/socfpga_de10_nano.h +index dfc22cf..12b9e55 100644 +--- a/include/configs/socfpga_de10_nano.h ++++ b/include/configs/socfpga_de10_nano.h +@@ -10,6 +10,16 @@ + /* Memory configurations */ + #define PHYS_SDRAM_1_SIZE 0x40000000 /* 1GiB */ + ++/* ++ * Bound the Linux boot map at 64 MiB, as stock's 2017.03 bootloader does ++ * (socfpga_common.h CONFIG_SYS_BOOTMAPSZ there). bootz relocates the DTB into ++ * this window, i.e. below the mem=511M line the MiSTer kernel is booted with, ++ * instead of to the top of the 1 GiB, inside the half of DDR the FPGA owns. ++ * Mainline dropped the define for a bootm_size default-environment entry ++ * (faea9e7a78) that the byte-identical stock environment cannot carry. ++ */ ++#define CFG_SYS_BOOTMAPSZ (64 * 1024 * 1024) ++ + /* The rest of the configuration is shared */ + #include + diff --git a/board/mister/de10nano/uboot.env b/board/mister/de10nano/uboot.env index b4dd1658..11d6550c 100644 --- a/board/mister/de10nano/uboot.env +++ b/board/mister/de10nano/uboot.env @@ -1,16 +1,100 @@ -# uboot.env -- DE10-Nano default environment text file. +# uboot.env -- DE10-Nano default environment, stock byte-identical (task U2f). # -# Consumed via BR2_TARGET_UBOOT_DEFAULT_ENV_FILE, which Buildroot wires to -# CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE + CONFIG_ENV_DEFAULT_ENV_TEXT_FILE -# (uboot.mk:414-422 in the pinned Buildroot tree). U-Boot's own reader -# (env/Kconfig: "lines containing key=value pairs, blank lines and lines -# beginning with # are ignored") strips this header before the build, same -# as mkenvimage. +# Format: U-Boot's CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE text format, consumed +# by the top-level Makefile's filechk_defaultenv.h recipe (NOT +# scripts/env2string.awk, which builds CONFIG_EXTRA_ENV_TEXT for a different +# knob). That recipe: drops every line starting with "#" and every blank +# line, appends one synthetic blank line, turns each remaining newline into a +# NUL, then strips a literal backslash immediately followed by that NUL and +# any whitespace after it (Makefile:2348's sed is 's/\\\x0\s*//g' -- the +# line-continuation join; unused below, every entry here is single-line). +# The result becomes include/generated/defaultenv_autogenerated.h, spliced +# verbatim into default_environment[] (include/env_default.h). # -# STUB, wired by U1 (docs/uboot-tasks.md): only stock's first environment -# entry so the file is non-empty and the build is green. The real content -- -# stock's remaining 20 entries verbatim, including the malformed entry-15 -# fingerprint, byte-identical per docs/boot-chain.md §3.1 and -# docs/uboot-mainline-port.md §6 -- is U2f's deliverable, integrated by U2g. -# Do not add entries here. +# Origin: stock's compiled-in environment, U-Boot_MiSTer fork commit 8dcc3484 +# (work/U-Boot_MiSTer, READ ONLY reference), include/configs/ +# socfpga_de10_nano.h:22-60 via include/env_default.h's fixed-prefix +# + CONFIG_EXTRA_ENV_SETTINGS ordering. Verified by direct extraction +# from the shipped binary (docs/boot-chain.md section 3.1): the 21 +# entries below, in this exact order, are byte-for-byte what +# work/uboot-proper.bin (stock U-Boot proper, no SPL) contains at +# file offset 0x28018-0x28495 inclusive (1,150 B; boot-chain.md's +# "1,149 bytes" is the doc nit plan section 6 and task U8 correct). +# The identical bytes sit inside the shipped +# stock/mister-payload/linux/uboot.img at offset 0x68058, i.e. +# 0x28018 plus that image's SPL-and-legacy-header prefix (4 x 64 KiB +# SPL copies = 0x40000, plus the 0x40-byte legacy uImage header = +# 0x40040) -- NOT at 0x28018 of uboot.img directly, which is inside +# the SPL's Thumb code. docs/uboot-tasks.md and +# docs/uboot-mainline-port.md section 6 both write the offset as "of +# the stock uboot.img"; that is imprecise (it is uboot-proper.bin's +# offset, or uboot.img's offset plus the prefix) -- flagged here for +# U8, not fixed here (out of this task's file scope). +# Author/copyright of the original values: Sorgelig +# (U-Boot_MiSTer). Carried verbatim -- owner decision (uboot-tasks.md +# "OWNER DECISIONS" (3)): "the environment must be byte-identical to +# stock's". Mirror stock wherever there is a choice. +# Upstream status: N/A. This is MiSTer-board-specific environment content, +# not a code change; nothing here is submitted upstream (U-Boot +# mainline's CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE mechanism that +# consumes it is already mainline, unmodified). +# Disposition: carried so the built default_environment[] blob matches +# stock's byte-for-byte. Wired in by Buildroot, not by the +# fragment: configs/mister_de10nano_defconfig's +# BR2_TARGET_UBOOT_DEFAULT_ENV_FILE names this file, and +# UBOOT_KCONFIG_FIXUP_CMDS (boot/uboot/uboot.mk) writes +# CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE=y plus the absolute +# CONFIG_ENV_DEFAULT_ENV_TEXT_FILE path from it AFTER the fragments +# are merged -- so those two symbols are deliberately absent from +# board/mister/de10nano/uboot.fragment (U2e), which owns the +# storage side only: CONFIG_ENV_IS_IN_MMC=y, CONFIG_ENV_OFFSET=0x200, +# CONFIG_ENV_SIZE=0x1000. +# +# CONFIG_ENV_SIZE=0x1000 (4096 B, U2e) bounds default_environment[] +# (static_assert in include/env_default.h). This file's derived blob is +# 1,150 B (21 entries, including the malformed entry below, plus the +# 2-NUL end-of-list terminator) -- 2,946 B of headroom inside the 4 KiB +# envelope for a runtime `saveenv`. +# +# Entry 15, "bootm $loadaddr - $fdt_addr", has NO "=" -- reproduce it +# EXACTLY, do not add one and do not delete the line. It is a stock source +# bug (an orphaned CONFIG_EXTRA_ENV_SETTINGS string, socfpga_de10_nano.h:50) +# preserved in the shipped binary; boot-chain.md section 3.1 traces it +# through U-Boot's himport_r() "no '=' before the NUL separator" branch +# (lib/hashtable.c) into a no-op delete-of-a-variable-that-does-not-exist -- +# harmless, and a fingerprint that the binary was built from this exact +# header. It is also NOT a "#" or blank line, so the filechk recipe above +# passes it through unchanged like any other entry. +# +# mt: entry 11 (fpgacheck) calls the fork's `mt` command twice +# (0x1FFFFF08/0x1FFFF000 mailbox polling, boot-chain.md section 6). `mt` +# is a C command carried by patch 0004 (task U2c), not environment content +# -- but this environment is dead on arrival without it: `fpgacheck` runs +# unconditionally from `bootcmd`, so a build carrying this file without +# U2c's patch fails `command not found` (or worse, silently mis-evaluates +# the hush `if`) at every cold boot. +# +# No comment or blank line survives into default_environment[] (both are +# stripped before the NUL join above) -- this header costs zero bytes of +# the 1,150. +bootargs=console=ttyS0,115200 $v loop.max_part=8 mem=511M memmap=513M$511M bootcmd=mw 0xff709004 0x800; run mmcload; run mmcboot +bootdelay=0 +baudrate=115200 +bootfile=fitImage +loadaddr=0x01000000 +loadaddr=0x01000000 +bootimage=/linux/zImage_dtb +fdt_addr=100 +fpgadata=0x02000000 +core=menu.rbf +fpgacheck=if mt 0x1FFFFF08 0xBEEFB001;then mw 0x1FFFFF08 0;if mt 0x1FFFF000 0x87654321;then mw 0x1FFFF000 0;env import -t 0x1FFFF004;run fpgaload;fi;else run fpgaload;fi +fpgaload=load mmc 0:$mmc_boot $fpgadata $core;fpga load 0 $fpgadata $filesize;bridge enable;mw 0x1FFFF000 0;mw 0xFFD05054 0 +scrtest=if test -e mmc 0:$mmc_boot /linux/u-boot.txt;then load mmc 0:$mmc_boot $loadaddr /linux/u-boot.txt;env import -t $loadaddr;fi +ethaddr=02:03:04:05:06:07 +bootm $loadaddr - $fdt_addr +mmc_boot=1 +mmcroot=/dev/mmcblk0p1 +v=loglevel=4 +mmcboot=setenv bootargs console=ttyS0,115200 $v loop.max_part=8 mem=511M memmap=513M$511M root=$mmcroot loop=linux/linux.img ro rootwait;bootz $loadaddr - $fdt_addr +mmcload=mmc rescan;run fpgacheck;run scrtest;load mmc 0:$mmc_boot $loadaddr $bootimage;setexpr.l fdt_addr $loadaddr + 0x2C;setexpr.l fdt_addr *$fdt_addr + $loadaddr diff --git a/board/mister/de10nano/uboot.fragment b/board/mister/de10nano/uboot.fragment index 3316861f..537b7b0e 100644 --- a/board/mister/de10nano/uboot.fragment +++ b/board/mister/de10nano/uboot.fragment @@ -1,27 +1,355 @@ # uboot.fragment -- DE10-Nano deltas on mainline's socfpga_de10_nano_defconfig. # -# Applied by Buildroot on top of BR2_TARGET_UBOOT_BOARD_DEFCONFIG -# ("socfpga_de10_nano") via BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES: -# merge_config.sh, then olddefconfig. Every line will be a DELTA -- if a value -# matches what the board defconfig already resolves to, restate it only where -# the restatement is the point (see `board/mister/de25nano/uboot.fragment`'s -# environment block for the pattern). -# -# STUB, wired by U1 (docs/uboot-tasks.md): this file carries no CONFIG_ lines -# yet so BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES resolves to a real, mergeable -# (empty) fragment and `make` is green before the real deltas land. The real -# content -- SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE, FS_EXFAT, the -# ENV_IS_IN_MMC/ENV_OFFSET/ENV_SIZE block, BOOTDELAY/AUTOBOOT, SYS_BOOTM_LEN, -# TEXT_BASE, dropping TOOLS_MKEFICAPSULE, and the SPL SPI/QSPI headroom cut -- -# is U2e's deliverable (docs/uboot-mainline-port.md §3.1/§3.3/§3.5/§3.6), -# integrated by U2g. Do not add deltas here. -# -# EDITING RULE, learned the hard way (carried from the DE25's fragment): -# never start a COMMENT line with "# CONFIG_" unless it really is -# an "is not set" directive. support/kconfig/merge_config.sh greps this file -# for every symbol it is changing and echoes the matching lines back at you, -# so a prose line that begins that way turns the merge report into nonsense -# -- and a line that happens to end in "is not set" would be parsed as a real -# directive (SED_CONFIG_EXP2 is: caret, hash, space, the symbol name, then -# the literal words "is not set" anchored to end of line). Indent such prose, -# or lead with a word. +# WHICH LAYER IS WHICH +# +# layer 1 configs/socfpga_de10_nano_defconfig -- 72 lines, shipped by U-Boot +# v2026.07 itself, named here by BR2_TARGET_UBOOT_BOARD_DEFCONFIG +# ("socfpga_de10_nano"). Nothing in this repo edits it. +# layer 2 THIS FILE -- named by BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES and +# merged on top of layer 1 by BUILDROOT'S OWN copy of the kconfig +# merge script: work/buildroot/package/pkg-kconfig.mk:67 runs +# support/kconfig/merge_config.sh -m -O <.config> \ +# +# resolved against the BUILDROOT tree, not U-Boot's. (U-Boot ships +# the same script at scripts/kconfig/merge_config.sh; diffing the two +# shows they differ only in Buildroot's extra "-e " +# option, the mktemp location, a rewrap of the usage text about +# CONFIG_ (Buildroot :38-39 vs U-Boot :37) and the final make +# line. The merge and report logic quoted in the editing rules +# below is identical in both, so reproducing a merge locally +# with either one is valid.) +# layer 3 Buildroot again, AFTER the fragments -- pkg-kconfig.mk:177 runs +# UBOOT_KCONFIG_FIXUP_CMDS (boot/uboot/uboot.mk:586, defined at +# :416-423), which writes ENV_USE_DEFAULT_ENV_TEXT_FILE and +# ENV_DEFAULT_ENV_TEXT_FILE from BR2_TARGET_UBOOT_DEFAULT_ENV_FILE, +# and only then does olddefconfig run (pkg-kconfig.mk:178). Those two +# symbols are therefore DELIBERATELY ABSENT from this file: Buildroot +# owns them, setting them here would be overwritten, and the +# environment text itself lives in board/mister/de10nano/uboot.env +# (U2f). +# +# Every directive below is a DELTA on layer 1's resolved .config, with three +# deliberate restatements -- ENV_IS_IN_MMC, TEXT_BASE, CMD_MEMORY -- each called +# out where it appears. Layer 1 already resolves those three to exactly the +# value written here; the lines exist so that a future U-Boot bump which moves a +# Kconfig default breaks the U3 resolved-.config assertion loudly instead of +# drifting silently. +# +# Design: docs/uboot-mainline-port.md sections 3.1 (the five must-carry deltas), +# 3.3 (three further divergences), 3.5 (size and headroom), 3.6 +# (Buildroot wiring) and 9 Q6 (the one still-open question a line here +# answers provisionally); +# docs/boot-chain.md sections 2.1 (the type-0xA2 SPL contract), 3.1 (the +# 21-entry stock default environment), 4 (bootcmd / mmcload / mmcboot) +# and 5 (updateboot's sector-1 env wipe, Consequence (b)). +# +# EDITING RULE, part 1 -- carried from board/mister/de25nano/uboot.fragment. +# Never start a COMMENT line with "# CONFIG_" unless it really is an +# "is not set" directive. merge_config.sh's SED_CONFIG_EXP2 is: caret, hash, +# space, the symbol name, then the literal words "is not set" anchored to end of +# line (Buildroot's copy, line 113), so a prose line shaped like that is parsed +# as a real directive. Indent such prose, or lead it with a word. +# +# EDITING RULE, part 2 -- found while writing this file, not yet documented on +# the de25nano copy. The rule is not only about line starts. To build its +# "Previous value" / "New value" report, merge_config.sh runs a plain +# grep -w +# (Buildroot's copy, line 132; its guard and the baseline-side read are at +# :130-131), so a prose sentence ANYWHERE that spells +# out, prefix and all, a symbol this file actually sets makes that grep return +# two lines and the report glues them into a garbled "New value". The merged +# .config is unaffected -- only the report is -- but a garbled report is exactly +# the noise that could hide a genuinely dropped symbol, which is the one thing +# the report exists to catch. So, throughout the prose below, every symbol this +# file sets is written by its BARE name with no "CONFIG_" prefix; the prefix +# appears only on the directive lines themselves. + +################################################################################ +# 1. SPL raw-mode selector -- plan section 3.1, delta #1 +################################################################################ +# +# Layer 1 selects no member of the "Method for locating next phase of boot" +# choice (common/spl/Kconfig:551-577), and that choice carries a default only +# for MVEBU (:553), so kconfig falls through to its first visible member, +# SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR. Observed in the baseline .config: +# +# SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR=y +# SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR=0x200 +# +# (the value from common/spl/Kconfig:587, "default 0x200 if ARCH_SOCFPGA_GEN5 +# || ARCH_AT91" -- quoted in full; the AT91 arm is irrelevant here). +# That is an absolute LBA: the MBR is ignored entirely and +# mmc_load_image_raw_partition is not even linked in, which breaks the +# type-0xA2 contract of boot-chain section 2.1. +# +# PLAN DISCREPANCY, flagged for U9: section 3.1 row 1 states the +# fall-through lands at absolute LBA 0x400. In the pinned v2026.07 tree it +# is 0x200 -- the Kconfig line cited above, and the baseline .config value +# quoted above. The nature of the delta is unchanged (an absolute sector +# instead of an MBR scan); only the plan's number needs correcting. +# +# Selecting USE_PARTITION_TYPE instead makes the SPL scan the MBR for the first +# partition of type 0xA2 -- which is what stock does. Three consequences, all +# read out of the resolved .config rather than assumed: +# +# * USE_SECTOR, the sibling member of the same choice, goes to +# "is not set". It is still PRESENT in the resolved .config, as n; it does +# not vanish. (U3 asserts it unset, which this satisfies.) +# * SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR and +# SYS_MMCSD_RAW_MODE_U_BOOT_DATA_PART_OFFSET, which both carry +# "depends on SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR" (common/spl/Kconfig:581 +# and :598), DROP OUT of the resolved .config completely -- present in the +# baseline, absent afterwards. This is what plan section 3.1 means by "the +# SECTOR symbol disappears outright", and it is what makes the feared +# double-count to +0x400 unreachable. +# * SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION_TYPE needs no line here. It is +# already 0xa2 in the baseline, from arch/arm/mach-socfpga/Kconfig:37-38 +# ("config SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION_TYPE / default 0xa2"), and +# it stays 0xa2 afterwards. +# +# The chosen member carries "depends on DOS_PARTITION" (common/spl/Kconfig:571); +# DOS_PARTITION is already y in the baseline, so no extra line is needed. +CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y + +################################################################################ +# 2. exFAT -- plan section 3.1, delta #3 +################################################################################ +# +# Stock's U-Boot reads exFAT: the fork replaced the FAT driver with ChaN FatFs +# and sets fs/fat/ffconf.h:212 "#define _FS_EXFAT 1"; the literal string EXFAT +# is in the shipped binary. Mainline's fs/fat/fat.c:223-226 accepts only a +# "FAT" or "FAT32" type string and rejects everything else, and ADR 0020's data +# partition is exFAT -- so a mainline U-Boot +# without this line cannot load zImage_dtb at all. +# +# FS_EXFAT has no Kconfig dependency (fs/exfat/Kconfig:1-5, a bare bool with an +# "imply CMD_FS_GENERIC if CMDLINE"), so this one line is sufficient at config +# time. Its real dependency is a BUILD-time one and lives outside this file: +# mainline's fs/exfat/time.c does 64-bit division and fails to link on 32-bit +# ARM (undefined reference to __aeabi_ldivmod), which the carried do_div() +# patch fixes. No 32-bit board in tree enables this symbol, which is why nobody +# upstream has hit it. +CONFIG_FS_EXFAT=y + +################################################################################ +# 3. Environment location -- mirror stock exactly +# plan section 3.3 bullet 2; boot-chain section 5, Consequence (b) +################################################################################ +# +# Layer 1's values come from configs/socfpga_de10_nano_defconfig lines 7-8 and +# nowhere else (arch/arm/mach-socfpga/Kconfig contains no ENV_SIZE or +# ENV_OFFSET line at all): +# +# ENV_SIZE=0x2000 defconfig:7 +# ENV_OFFSET=0x4400 defconfig:8 +# +# Stock instead lands on byte offset 512 -- sector 1, "just after the MBR" -- +# with size 4096, via socfpga_common.h:200-207 in the fork (quoted in +# boot-chain section 5). +# +# This is not cosmetic. updateboot runs on EVERY Downloader linux update and +# does exactly one thing to the raw card before reflashing the boot partition: +# +# dd if=/dev/zero of=/dev/mmcblk0 bs=512 seek=1 count=1 +# +# i.e. it zeroes bytes 512-1023. On stock that lands on the FIRST 512 bytes of +# the environment blob, destroying the CRC32 in its first four bytes; the next +# boot fails the CRC check and falls back to the compiled-in default +# environment. That is what guarantees boot-chain section 5's Consequence (b): +# "the effective U-Boot environment is always built-in defaults + u-boot.txt", +# which /init and every Phase-5 design already rely on. +# +# With mainline's 0x4400 the environment occupies bytes 0x4400-0x63FF, so +# updateboot's wipe of bytes 512-1023 MISSES IT ENTIRELY: a previously saved +# environment would survive the update intact, quietly voiding Consequence (b) +# and making the effective environment depend on whatever a user once ran +# saveenv with. Mirroring stock's offset and size is what keeps the wipe +# landing on the blob's head. +# +# ENV_IS_IN_MMC is already y in layer 1 (defconfig:45). Restated because the two +# lines below are meaningless without it and because a bump that dropped it +# would otherwise move the environment silently. +CONFIG_ENV_IS_IN_MMC=y +CONFIG_ENV_OFFSET=0x200 +CONFIG_ENV_SIZE=0x1000 + +################################################################################ +# 4. Autoboot -- mirror stock's own defconfig +# plan section 3.1 delta #5 (the environment); boot-chain section 3.1 row 2 +################################################################################ +# +# The fork's configs/socfpga_de10_nano_defconfig lines 57-59 are, verbatim, +# BOOTDELAY=0 / AUTOBOOT_STOP_STR="\e" / AUTOBOOT_KEYED=y (written here without +# the CONFIG_ prefix per editing rule part 2). Layer 1 sets none of them: +# baseline resolves BOOTDELAY=2 (boot/Kconfig:1567) and AUTOBOOT_KEYED unset. +# +# BOOTDELAY is not merely behavioural, it is part of U2f's byte compare: +# bootdelay=0 is entry #2 of stock's 21-entry default environment blob +# (boot-chain section 3.1, which cites this very defconfig line as its source), +# emitted into default_environment[] by include/env_default.h. A different +# value here changes those bytes and fails U2f's cmp. +# +# AUTOBOOT_KEYED is required, not optional: AUTOBOOT_STOP_STR sits inside +# "if AUTOBOOT_KEYED" in boot/Kconfig (the block opens at :1589, the symbol is +# at :1653), so without it the string symbol is invisible and the value is +# dropped. With the pair set, a delay of 0 does not mean "any keypress stops +# autoboot" (mainline's un-keyed default, boot/Kconfig:1585-1587) -- only the +# escape byte does, which is stock's behaviour and leaves /linux/u-boot.txt +# (boot-chain sections 4 and 5) as the intended override path. +# +# Observed cascade, for the record: turning AUTOBOOT_KEYED on makes the whole +# "if AUTOBOOT_KEYED" block visible, so AUTOBOOT_FLUSH_STDIN, AUTOBOOT_PROMPT, +# AUTOBOOT_ENCRYPTION, AUTOBOOT_DELAY_STR and AUTOBOOT_KEYED_CTRLC appear in the +# resolved .config at their defaults (all off or empty except the prompt +# string), and AUTOBOOT_USE_MENUKEY -- which sits under "if !AUTOBOOT_KEYED" +# (boot/Kconfig:1717-1719) -- drops out. None of those is a behaviour change we +# rely on; they are listed here so the baseline-to-resolved diff has no +# unexplained line. +CONFIG_BOOTDELAY=0 +CONFIG_AUTOBOOT_KEYED=y +CONFIG_AUTOBOOT_STOP_STR="\e" + +################################################################################ +# 5. OS decompression bound -- plan section 9, open question 6 (STILL OPEN) +################################################################################ +# +# Stock compiles 64 MiB: the fork's include/configs/socfpga_de10_nano.h:11 +# defines SYS_BOOTM_LEN as (64 << 20). Mainline has no socfpga-specific value, +# so layer 1 falls to boot/Kconfig:1078's unqualified "default 0x800000" +# (8 MiB) -- observed in the baseline. +# +# WHAT THIS IS NOT. It is not a uImage budget and it is not a limit today's +# payload exceeds. Stock boots a RAW, self-decompressing zImage -- +# "bootz $loadaddr - $fdt_addr" in mmcboot, boot-chain section 4 -- and stock's +# zImage_dtb is 7,380,857 B = 7.04 MiB (boot-chain section 7.3), already under +# 8 MiB. Plan section 3.3 says nothing about this symbol; do not cite it here. +# +# WHAT THIS IS. Plan section 9 open question 6, which reads: "Does bootz on a +# raw self-decompressing zImage consult SYS_BOOTM_LEN? It dropped from 64 MiB +# (fork) to 8 MiB (mainline); zImage_dtb is ~7 MB and growing toward the 16 MB +# budget of boot-chain section 7.3. Set it to 0x4000000 and move on, but +# confirm." This line is the "set it and move on" half. The "confirm" half is +# NOT closed by this file and nothing here should be read as closing it. +# +# Setting it is the conservative direction regardless of how Q6 resolves: the +# symbol's own help text calls it "the maximum size of the buffer that is used +# to decompress the OS image in to", so raising it can only widen a bound, +# never narrow one -- and 64 MiB is what stock has, which is this port's +# standing tie-breaker. +CONFIG_SYS_BOOTM_LEN=0x4000000 + +################################################################################ +# 6. Load address -- restated, not changed (plan section 3.6's wiring layer) +################################################################################ +# +# arch/arm/mach-socfpga/Kconfig:44-46 already defaults TEXT_BASE to 0x01000040 +# for ARCH_SOCFPGA_GEN5, and the baseline resolves to exactly that. It is the +# uImage load/exec address boot-chain section 2 reads back out of the stock +# blob, and the address scripts/check-uboot-parity.sh (U4a) checks. Pinned here +# rather than left to a default so that a bump touching that Kconfig line fails +# U3's resolved-.config assertion instead of silently relocating U-Boot proper. +CONFIG_TEXT_BASE=0x01000040 + +################################################################################ +# 7. Drop the host gnutls pull-in -- plan section 3.6, host deps +################################################################################ +# +# TOOLS_MKEFICAPSULE is "default y if EFI_LOADER" (tools/Kconfig:109-111) and +# EFI_LOADER is y in the baseline, so layer 1 resolves it on without ever +# naming it. Plan section 3.6 measured it as the ONLY reason host-gnutls enters +# this build. The tool is host-side and cannot touch a target image, and this +# board has no UEFI capsule update path, so turning it off removes a host +# dependency for a capability nothing uses. +# CONFIG_TOOLS_MKEFICAPSULE is not set + +################################################################################ +# 8. SPL headroom -- drop the unused SPI/QSPI stack (plan section 3.5) +################################################################################ +# +# None of these four is reachable on the DE10-Nano's boot path: the BootROM +# hands the SPL an SD card, the SPL loads U-Boot proper raw from the 0xA2 +# partition (boot-chain section 2.1), and no SPI boot device exists in this +# build. Layer 1 has them on because upstream's defconfig also targets a +# QSPI-capable use of the same board (defconfig:30 sets SPL_SPI_LOAD outright; +# the other three arrive as defaults). Plan section 3.5 measures the removal as +# taking SPL headroom from 12.6 % to 30.4 %, restoring stock's margin -- which +# matters because mainline enforces SPL_SIZE_CHECK as a hard build failure, +# where the 2017.03 fork's tools/socfpgaimage.c silently truncates and exits 0. +# +# NOTE FOR U3's resolved-.config assertion: only two of these four come back as +# an explicit "is not set" line. SPL_SPI_FLASH_SUPPORT carries +# "depends on SPL_SPI" (common/spl/Kconfig:1539) and SPL_SPI_LOAD lives inside +# "if SPL_SPI_FLASH_SUPPORT" (:1548-1579, the symbol at :1573), so once +# SPL_SPI is off both become invisible and kconfig drops them from the .config +# ENTIRELY rather than commenting them. Their effective state is off either +# way, but an assertion must test "absent OR not set" for those two, not a +# literal string match. SPL_SPI and SPL_DM_SPI do produce the explicit comment +# form. +# +# Observed cascade, for the record: with SPL_SPI off, SPL_DM_SPI_FLASH, +# SPL_SPI_FLASH_TINY, SPL_DESIGNWARE_SPI and SYS_SPI_U_BOOT_OFFS also drop out +# of the resolved .config. The last of those is the one to notice: layer 1 sets +# it explicitly at defconfig:31 (0x40000), but it carries +# "depends on SPL_SPI_LOAD || SPL_SPI_SUNXI" (common/spl/Kconfig:1585) and is +# the offset of a U-Boot payload in SPI FLASH -- unrelated to the +0x200 sector +# offset inside the 0xA2 SD partition that block 1 is about. Losing it removes +# nothing this boot path uses. +# CONFIG_SPL_SPI is not set +# CONFIG_SPL_SPI_FLASH_SUPPORT is not set +# CONFIG_SPL_DM_SPI is not set +# CONFIG_SPL_SPI_LOAD is not set + +################################################################################ +# 9. mt -- keep the command the stock environment calls (plan section 3.4) +################################################################################ +# +# Decided 2026-09-14 (mirror stock; amends ADR 0024 Decision 5): carry the +# fork's "mt" command as a patch rather than rewrite fpgacheck around itest, so +# that the environment text stays byte-identical to stock's 21 entries and +# U2f's check is a plain cmp. Stock's fpgacheck calls mt twice (boot-chain +# section 4). +# +# The ported mt lives in cmd/mem.c behind CMD_MEMORY, which is already y in the +# baseline from cmd/Kconfig:965-967 ("bool ... default y"), so no new Kconfig +# symbol is introduced. Restated here for the same drift-detection reason as +# TEXT_BASE above: with CMD_MEMORY off, the patch would still apply and build +# but mt would not exist, and the only symptom would be fpgacheck failing at +# runtime on a board nobody has booted yet. +CONFIG_CMD_MEMORY=y + +################################################################################ +# 10. The rest of stock's command table -- plan section 6, "forbidden diffs" +################################################################################ +# +# ADDED AT INTEGRATION (task U2g), not by U2e. U2e was told not to strip +# mainline's extra commands to chase stock's 69; nobody expected mainline to be +# SHORT of four of them. The first run of scripts/check-uboot-parity.sh against +# a real build reported: +# +# FAIL 4 of stock's 69 commands are missing: gzwrite loopw meminfo unzip +# +# and plan section 6 lists "a missing command from stock's 69-entry table" +# among the FORBIDDEN diffs (extras are allowed, absences are not), so these +# three lines close it. None is a MiSTer invention: all four commands exist in +# mainline v2026.07 and are simply off in layer 1's defconfig. +# +# CMD_UNZIP (cmd/Kconfig:1095, "bool unzip", selects GZIP which is already y) +# builds cmd/unzip.o, which carries BOTH U_BOOT_CMD records in one file -- +# "unzip" at cmd/unzip.c:51 and "gzwrite" at :96. There is no separate +# gzwrite symbol in this tree (grep GZWRITE over Kconfig finds nothing), so +# this one line restores two of the four names. +# LOOPW (cmd/Kconfig:922, "bool loopw") un-#ifdefs the do_mem_loopw body and +# its U_BOOT_CMD inside cmd/mem.c (:645-740 and :1433-1439) -- the same file +# patch 0004 adds mt to, and it needs CMD_MEMORY, already y above. +# CMD_MEMINFO (cmd/Kconfig:940, "bool meminfo", default y only for SANDBOX +# and X86) builds cmd/meminfo.o. Its sub-option CMD_MEMINFO_MAP is left at +# its default (off for this board), which matches stock's one-line output. +# +# All three live in cmd/, i.e. in U-Boot proper, NOT in the SPL: the measured +# SPL payload is unchanged by them, so this costs nothing against the 64 KiB +# slot of plan section 3.5. Nothing in stock's environment calls any of the +# four -- the reason to carry them is parity with the shipped table, which is +# this port's specification (owner decision: mirror stock). +CONFIG_CMD_UNZIP=y +CONFIG_LOOPW=y +CONFIG_CMD_MEMINFO=y diff --git a/board/mister/de25nano/patches/arm-trusted-firmware/0001-plat-intel-agilex5-route-bl31-console-to-uart1.patch b/board/mister/de25nano/patches/arm-trusted-firmware/0001-plat-intel-agilex5-route-bl31-console-to-uart1.patch new file mode 100644 index 00000000..df65ec37 --- /dev/null +++ b/board/mister/de25nano/patches/arm-trusted-firmware/0001-plat-intel-agilex5-route-bl31-console-to-uart1.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Michael C. Ferguson +Date: Mon, 14 Sep 2026 16:00:00 -0500 +Subject: [PATCH] plat: intel: soc: route the BL31 boot and crash console to UART1 + +On the DE25-Nano the header (console) UART is HPS UART1 at 0x10C02100; UART0 +is not brought out. TF-A's Intel SoCFPGA platform code hard-codes UART0 for +both its boot console and its crash console, so on this board every +NOTICE/ERROR line BL31 prints -- including the panic path -- goes to a port +nothing is listening on, and a BL31 failure at bring-up is indistinguishable +from a dead board. Point both at UART1. + +Provenance +---------- +Origin: Terasic's own TF-A build for this board makes exactly this + change and nothing else against Altera's socfpga_v2.12.0 + branch: github.com/terasic/arm-trusted-firmware, branch + de25-nano-v2.12.0 (head 580dcb5), plat/intel/soc/common/ + include/platform_def.h. The factory u-boot.itb's BL31 reports + "v2.12.0(release):de25_nano_revA_v1.0" and is that build. + Re-expressed here against upstream v2.15.0, whose + platform_def.h carries the same two defines at :155-156. +Author: this repo (the change is two macro values; Terasic's commit + carries no separate authorship). +Upstream: Not upstreamable as-is: mainline TF-A has no DE25-Nano board + and the value is per-board. A per-platform Kconfig for the + console UART would be; not pursued. +Why carried: visibility only. BL31's behaviour is unchanged; what changes is + where its text goes. docs/de25-uboot.md section 11 and + docs/de25-nano-tasks.md ("no NOTICE: BL31 lines on the header + UART is normal") predate this patch; with it, BL31's banner is + expected on the console and its absence IS a finding. +Verified: [V] applies at -F0 to v2.15.0; bl31.bin rebuilds; the resulting + u-boot.itb passes scripts/check-sdcard-de25.sh and the + MISTER_UBOOT_DE25_QSPI_AUDIT hook. [U] output on the header + UART -- needs the board. + +Signed-off-by: Michael C. Ferguson +--- + plat/intel/soc/common/include/platform_def.h | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/plat/intel/soc/common/include/platform_def.h b/plat/intel/soc/common/include/platform_def.h +--- a/plat/intel/soc/common/include/platform_def.h ++++ b/plat/intel/soc/common/include/platform_def.h +@@ -152,8 +152,8 @@ + /******************************************************************************* + * UART related constants + ******************************************************************************/ +-#define CRASH_CONSOLE_BASE PLAT_UART0_BASE +-#define PLAT_INTEL_UART_BASE PLAT_UART0_BASE ++#define CRASH_CONSOLE_BASE PLAT_UART1_BASE ++#define PLAT_INTEL_UART_BASE PLAT_UART1_BASE + + #define PLAT_BAUDRATE (115200) + #define PLAT_UART_CLOCK (100000000) diff --git a/board/mister/de25nano/patches/linux/linux.hash b/board/mister/de25nano/patches/linux/linux.hash index 4438d0e5..60782b88 120000 --- a/board/mister/de25nano/patches/linux/linux.hash +++ b/board/mister/de25nano/patches/linux/linux.hash @@ -1 +1 @@ -../../../de10nano/patches/linux/linux.hash \ No newline at end of file +../../../../../package/linux-rt/linux-rt.hash \ No newline at end of file diff --git a/board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch b/board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch index a726d16f..7cd9b197 100644 --- a/board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch +++ b/board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch @@ -1,4 +1,4 @@ -From: MiSTer Buildroot (DE25-Nano board support) +From: Michael C. Ferguson Subject: [PATCH] configs: socfpga_soc64: only define mtdids/mtdparts env when MTD exists CFG_EXTRA_ENV_SETTINGS in socfpga_soc64_common.h references @@ -29,7 +29,7 @@ defined. No functional change for any board that has MTD or SPI_FLASH. Upstream status: not submitted yet. Local carry for board/mister/de25nano; see docs/de25-uboot.md. -Signed-off-by: MiSTer Buildroot +Signed-off-by: Michael C. Ferguson --- --- a/include/configs/socfpga_soc64_common.h +++ b/include/configs/socfpga_soc64_common.h diff --git a/board/mister/de25nano/socfpga_agilex5_de25nano.dts b/board/mister/de25nano/socfpga_agilex5_de25nano.dts index 9f1af141..aee663b5 100644 --- a/board/mister/de25nano/socfpga_agilex5_de25nano.dts +++ b/board/mister/de25nano/socfpga_agilex5_de25nano.dts @@ -14,7 +14,8 @@ * 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) + * PORT a third-party DE25-Nano Linux port (6.18.38 + vendor backports), + * consulted read-only; not public * 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 * @@ -26,7 +27,7 @@ / { /* - * MAINLINE + TERASIC + ALTERA + FRIEND all use the SoCDK compatible. + * MAINLINE + TERASIC + ALTERA + PORT 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 @@ -37,14 +38,14 @@ compatible = "intel,socfpga-agilex5-socdk", "intel,socfpga-agilex5"; aliases { - /* Console UART: uart1 (serial@10c02100). TERASIC, ALTERA and FRIEND + /* Console UART: uart1 (serial@10c02100). TERASIC, ALTERA and PORT * 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. */ + * (docs/dts-comparison.md §3.3). TERASIC + ALTERA + PORT. */ 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 @@ -54,9 +55,9 @@ }; chosen { - stdout-path = "serial0:115200n8"; /* TERASIC + ALTERA + FRIEND */ + stdout-path = "serial0:115200n8"; /* TERASIC + ALTERA + PORT */ /* - * No bootargs. TERASIC and FRIEND carry a stale string whose + * No bootargs. TERASIC and PORT 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 @@ -68,7 +69,7 @@ compatible = "gpio-leds"; /* - * Node name from ALTERA. TERASIC/FRIEND call it "hps0", which does + * Node name from ALTERA. TERASIC/PORT 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 @@ -76,15 +77,17 @@ */ led-0 { label = "hps_led0"; - gpios = <&portb 17 GPIO_ACTIVE_LOW>; /* TERASIC + ALTERA + FRIEND */ + gpios = <&portb 17 GPIO_ACTIVE_LOW>; /* TERASIC + ALTERA + PORT */ }; }; /* * 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>"). + * U-Boot DTS constant Terasic's public branch declares + * (github.com/terasic/u-boot-socfpga @ de25-nano-v2025.01, + * arch/arm/dts/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 — @@ -95,7 +98,7 @@ * 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 + * It is still the right value to state. TERASIC and PORT 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. @@ -179,7 +182,7 @@ /* * 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 + * PORT, 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. * @@ -207,7 +210,7 @@ * 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 + * §2.6 step 4), and the closest supporting evidence is the PORT's * iommu.passthrough=1 cold boot reaching a login prompt. * * Every iommus property in the tree is KEPT and simply goes inert: @@ -236,7 +239,7 @@ * 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 + * byte-identical in TERASIC (:851-865) and PORT (: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 @@ -254,11 +257,11 @@ */ 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 */ + reg = <0x10808000 0x1000>; /* TERASIC + ALTERA + PORT */ + interrupts = ; /* TERASIC + ALTERA + PORT */ + resets = <&rst SDMMC_RESET>; /* TERASIC + PORT */ + clocks = <&clkmgr AGILEX5_L4_MP_CLK>, /* biu — TERASIC + ALTERA + PORT */ + <&clkmgr AGILEX5_SDMCLK>; /* ciu — TERASIC + ALTERA + PORT */ clock-names = "biu", "ciu"; /* @@ -273,7 +276,7 @@ * 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 */ + iommus = <&smmu 5>; /* TERASIC + ALTERA + PORT */ /* * KEPT. Corroborated inside MAINLINE itself: the sibling nand * controller on this same SoC carries dma-coherent in @@ -295,7 +298,7 @@ /* ---- board wiring below this line ---- */ - bus-width = <4>; /* TERASIC + ALTERA + FRIEND */ + bus-width = <4>; /* TERASIC + ALTERA + PORT */ /* * FIRST-BOOT CLOCK CAP. Mainline's sdhci-cadence programs NONE of the @@ -303,17 +306,17 @@ * 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, + * on a mainline sdhci-cadence driver — PORT, * 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 */ + max-frequency = <25000000>; /* PORT */ /* - * KEPT even though FRIEND drops it, because dropping it would be + * KEPT even though PORT 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 @@ -322,9 +325,9 @@ * 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 */ + disable-wp; /* TERASIC + ALTERA + PORT — no WP switch wired */ + no-1-8-v; /* TERASIC + PORT — 3.3V-only signalling */ + no-sdio; /* TERASIC + PORT — SD card slot only */ /* * sdhci-caps / sdhci-caps-mask are LIVE on mainline: sdhci.c's @@ -348,13 +351,13 @@ }; &gmac0 { - status = "okay"; /* TERASIC + ALTERA + FRIEND */ + status = "okay"; /* TERASIC + ALTERA + PORT */ /* "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-mode = "rgmii"; /* TERASIC + ALTERA + PORT */ phy-handle = <&emac0_phy0>; - max-frame-size = <9000>; /* TERASIC + ALTERA + FRIEND */ + max-frame-size = <9000>; /* TERASIC + ALTERA + PORT */ /* * Node name "mdio0" is kept deliberately. It does not match mdio.yaml's @@ -385,19 +388,19 @@ }; &gpio1 { - status = "okay"; /* TERASIC + ALTERA + FRIEND — portb, used by led-0 */ + status = "okay"; /* TERASIC + ALTERA + PORT — portb, used by led-0 */ }; &i2c1 { - status = "okay"; /* TERASIC + ALTERA + FRIEND */ + status = "okay"; /* TERASIC + ALTERA + PORT */ }; &uart1 { - status = "okay"; /* TERASIC + ALTERA + FRIEND — console */ + status = "okay"; /* TERASIC + ALTERA + PORT — console */ }; &usb0 { - status = "okay"; /* TERASIC + ALTERA + FRIEND */ + status = "okay"; /* TERASIC + ALTERA + PORT */ /* 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. */ @@ -405,7 +408,7 @@ }; /* - * Watchdogs. MAINLINE leaves all five disabled; TERASIC, ALTERA and FRIEND all + * Watchdogs. MAINLINE leaves all five disabled; TERASIC, ALTERA and PORT 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 @@ -415,7 +418,7 @@ * 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. + * unchanged into ALTERA and PORT. */ &watchdog0 { status = "okay"; @@ -442,7 +445,7 @@ * 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). + * + PORT (and the DE25-Nano's 25 MHz HPS oscillator). */ clock-frequency = <25000000>; }; diff --git a/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts b/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts index f9faedb9..ce704d5c 100644 --- a/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts +++ b/board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts @@ -24,10 +24,11 @@ * * Authored against mainline U-Boot v2026.07's arch/arm/dts/socfpga_agilex5.dtsi * (which this #includes out of the U-Boot tree, so this is a small delta, not a - * from-scratch device tree). Cross-checked node by node against the reference - * DE25 U-Boot tree at /mnt/source/de25-uboot-socfpga - * (arch/arm/dts/socfpga_agilex5_de25_nano.dts) -- read for board WIRING only; - * none of its SPL, RSU or exFAT-SPL work is carried. + * from-scratch device tree). Cross-checked node by node against the PORT + * tree's U-Boot fork (Terasic 2025.01 base plus exFAT-in-SPL patches; + * consulted read-only, not public), arch/arm/dts/socfpga_agilex5_de25_nano.dts + * -- read for board WIRING only; none of its SPL, RSU or exFAT-SPL work is + * carried. */ #include "socfpga_agilex5.dtsi" diff --git a/configs/mister_de10nano_defconfig b/configs/mister_de10nano_defconfig index 2604e65f..604a964c 100644 --- a/configs/mister_de10nano_defconfig +++ b/configs/mister_de10nano_defconfig @@ -62,7 +62,6 @@ BR2_TARGET_UBOOT_BOARD_DEFCONFIG="socfpga_de10_nano" BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/uboot.fragment" BR2_TARGET_UBOOT_DEFAULT_ENV_FILE="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de10nano/uboot.env" BR2_TARGET_UBOOT_NEEDS_OPENSSL=y -BR2_TARGET_UBOOT_NEEDS_GNUTLS=y BR2_TARGET_UBOOT_FORMAT_CUSTOM=y BR2_TARGET_UBOOT_FORMAT_CUSTOM_NAME="u-boot-with-spl.sfp" BR2_PACKAGE_HOST_KMOD_XZ=y diff --git a/configs/mister_de25nano_defconfig b/configs/mister_de25nano_defconfig index 3148fc95..dc5b7122 100644 --- a/configs/mister_de25nano_defconfig +++ b/configs/mister_de25nano_defconfig @@ -37,7 +37,7 @@ BR2_TARGET_GENERIC_GETTY_BAUDRATE_115200=y BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/post-image.sh" BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL_CUSTOM_VERSION=y -BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="7.2.3" +BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="7.2.5" BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/linux-patches" BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_MISTER_PATH)/board/mister/de25nano/linux.config" diff --git a/docs/boot-chain.md b/docs/boot-chain.md index e65aa283..ef011071 100644 --- a/docs/boot-chain.md +++ b/docs/boot-chain.md @@ -143,8 +143,11 @@ does not boot; that is the whole recovery story for Phase 5. ### 3.1 Result: zero divergence I re-extracted the built-in environment directly from the shipped binary -(`work/uboot-proper.bin`, env blob at file offset `0x28018`–`0x28495`, 1,149 bytes of -NUL-separated `k=v` strings) and compared it to what the source at `8dcc3484` produces. +(`work/uboot-proper.bin`, env blob at file offset `0x28018`–`0x28495`, 1,150 bytes of +NUL-separated `k=v` strings, 21 entries — `docs/uboot-mainline-port.md` §6 reconciles that +against the "20 entries, 1,149 bytes" this section used to claim, and records the fork's +`default_environment[]` ELF symbol as 1,151, one byte more than the blob) and compared it +to what the source at `8dcc3484` produces. **Every variable, every value, and the ordering match exactly.** The order is dictated by `u-boot:include/env_default.h:31-84` (the fixed prefix) followed by `:107-109` @@ -230,7 +233,7 @@ CI must fetch it and verify sha256 `e2d46cf9fe1ec40ca2c9c7409870249f267e06f70e57 from-source build — same commit, `8dcc3484` — ships opt-in only and is validated by behavioural parity (P5.2), not byte identity.)* -### 3.3 `mt` is a MiSTer-only U-Boot command (a Phase-5 blocker again under ADR 0024; solved with `itest.l`) +### 3.3 `mt` is a MiSTer-only U-Boot command (a Phase-5 blocker again under ADR 0024; solved by carrying the command) `fpgacheck` uses `mt`, which is **not** an upstream U-Boot command. It was added by this fork (`u-boot` commit `c0ed23f52e` "Implement simple memory test against value"): @@ -248,9 +251,15 @@ So `if mt ; then …` means *"if the word at `addr` equals `val`"*. U-Boot port must re-implement `mt` or rewrite `fpgacheck` without it** (e.g. `setexpr` + `test`). *(ADR 0017 briefly dissolved this blocker by building the fork; **[ADR 0024](decisions/0024-mainline-uboot-capability-artifact.md) superseded that** — -Phase 5 now builds **mainline U-Boot 2026.04**, so `mt` does **not** ship. The -replacement is `itest.l * == `, verified by execution in a U-Boot sandbox -across all three warm-reboot dispatch cases — `docs/uboot-mainline-port.md` §3.4.)* +Phase 5 builds **mainline U-Boot 2026.07** — and the owner's 2026-09-14 "mirror stock" +decision took the first of those two routes: **the fork's `mt` is carried as a patch** +(`board/mister/de10nano/patches/uboot/0004-cmd-mem-add-mt-memory-test-against-value.patch`, +36 added lines in `cmd/mem.c`, `cmd_tbl_t` → `struct cmd_tbl`), so `fpgacheck` is kept +verbatim and the built-in environment stays byte-identical to stock's — +`docs/uboot-mainline-port.md` §3.4. The `itest.l * == ` rewrite stays documented +but **unused**: it is the fallback if `mt` ever fails to port, and the free stock-hardware +smoke test of that plan's §8. It was verified by execution in a U-Boot sandbox across all +three warm-reboot dispatch cases.)* --- @@ -934,8 +943,10 @@ Also preserved by construction, and worth stating so P1.10 does not "improve" th * **N4 — `mt` is a MiSTer-only U-Boot command.** Any mainline U-Boot port must re-implement it or rewrite `fpgacheck`. (§3.3) *(Live again under [ADR 0024](decisions/0024-mainline-uboot-capability-artifact.md), which builds mainline - — resolved not by re-implementing `mt` but with `itest.l * == `, verified by - execution: `docs/uboot-mainline-port.md` §3.4.)* + — resolved on 2026-09-14 by re-implementing it: the fork's `mt` is carried as patch + `0004-cmd-mem-add-mt-memory-test-against-value.patch`, which keeps `fpgacheck` verbatim. + The `itest.l * == ` rewrite is the documented, verified-by-execution fallback: + `docs/uboot-mainline-port.md` §3.4.)* * **N5 — `uboot.img` cannot be rebuilt byte-identically** (compiled-in `+0800` timestamp, no `SOURCE_DATE_EPOCH`, pinned 2020 Arm toolchain). It must be *fetched by hash*, never built. (§3.2) *(Under ADR 0017 this holds for the default channel; the Phase-5 from-source diff --git a/docs/buildroot-config.md b/docs/buildroot-config.md index 76088a6d..880da213 100644 --- a/docs/buildroot-config.md +++ b/docs/buildroot-config.md @@ -2581,33 +2581,56 @@ would copy whatever the platform's release directory happens to contain; naming the one file we ship keeps `images/` auditable and makes the Makefile's `de25` assertion and that line describe the same thing. -**U-Boot.** `BR2_TARGET_UBOOT=y`, `_BUILD_SYSTEM_KCONFIG=y`, -`_CUSTOM_VERSION=y`, `_CUSTOM_VERSION_VALUE="2026.07"`, `_USE_DEFCONFIG=y`, +**U-Boot.** The literal lines in `configs/mister_de25nano_defconfig` (lines +55-63 as of 933a2d2) are `BR2_TARGET_UBOOT=y`, `_BOARD_DEFCONFIG="socfpga_agilex5"`, `_CONFIG_FRAGMENT_FILES`, -`_CUSTOM_DTS_PATH`, `_NEEDS_ATF_BL31=y`, `_NEEDS_ATF_BL31_BIN=y`, -`_USE_BINMAN=y`, `_NEEDS_OPENSSL=y`, `_FORMAT_ITB=y`, -`# BR2_TARGET_UBOOT_FORMAT_BIN is not set`. +`_CUSTOM_DTS_PATH`, `_NEEDS_ATF_BL31=y`, `_USE_BINMAN=y`, `_NEEDS_OPENSSL=y`, +`_FORMAT_ITB=y` and `# BR2_TARGET_UBOOT_FORMAT_BIN is not set`. The remaining +U-Boot symbols the build resolves — `_USE_DEFCONFIG`, `_NEEDS_ATF_BL31_BIN`, +`_BUILD_SYSTEM_KCONFIG` and `_LATEST_VERSION` (the one that pins 2026.07) — +are all Kconfig-choice defaults, so none of the four is a literal line here. +The `default` lines are `boot/uboot/Config.in:117`, `:267` and `:11`; +`_LATEST_VERSION` has none, because it is the first entry of the version +choice at `:38-44` and that is what makes it the choice's default. The version +and build-system lines *used* to be spelled out; the next two paragraphs say +why, and why they no longer are. Mainline v2026.07 (released 2026-07-07; v2026.10 was at -rc when this was -written). Buildroot 2026.05.2 shipped 2026.04, so this had to be a custom -version. - -**THE 2026.08 BUMP CLOSED THAT GAP AND THE PIN HAS NOT MOVED YET.** Buildroot -2026.08's `BR2_TARGET_UBOOT_LATEST_VERSION` is `2026.07` -(`boot/uboot/Config.in:88`) — the exact version this fragment pins by hand. So -the custom-version pin is now redundant *in version terms*, and switching to -`LATEST` would also make the build-system line below unnecessary (see why in -the next paragraph). Deliberately NOT changed as part of the 2026.08 bump: the -DE25 stack is not built or booted in this repo's CI, so swapping the U-Boot -source out from under an unbooted board belongs in its own commit, with a -build, rather than riding along on a Buildroot bump. Left as a flagged -simplification. - -NOTE THE BUILD-SYSTEM LINE, it is not optional. +written). Buildroot 2026.05.2 shipped 2026.04, so pinning it originally had to +be a custom version: `_CUSTOM_VERSION=y` / `_CUSTOM_VERSION_VALUE="2026.07"`, +with `_BUILD_SYSTEM_KCONFIG=y` also spelled out explicitly alongside it (see +the next paragraph for why that second line was required). + +**THE 2026.08 BUMP CLOSED THAT GAP AND TASK DU1 (2026-09-14) SWITCHED THE PIN, +DONE.** Buildroot 2026.08's `BR2_TARGET_UBOOT_LATEST_VERSION` *is* `2026.07` +(`boot/uboot/Config.in:44` for the prompt, `:88` for the version string it +resolves to) — the exact version this fragment pinned by hand, and the first +and therefore default entry of the "U-Boot Version" choice (`:38-44`). So the +custom-version pin is gone, replaced by plain +`BR2_TARGET_UBOOT_LATEST_VERSION=y` ([`docs/uboot-tasks.md`](uboot-tasks.md) +task DU1) — which, being the choice's default, needs no line of its own +either: `make O=output-de25 savedefconfig` drops it, and +`configs/mister_de25nano_defconfig` as of 933a2d2 names no U-Boot version at +all (`git grep UBOOT configs/mister_de25nano_defconfig` confirms it). +`board/mister/de25nano/patches/uboot/uboot.hash` went with it: Buildroot's own +`boot/uboot/uboot.hash` carries a line for `u-boot-2026.07.tar.bz2`, so the +board-local override is no longer what keeps the download fail-closed. DU1 +proved nothing moved — `make O=output-de25 uboot-dirclean uboot-rebuild` with +`BR2_REPRODUCIBLE=y`, before and after, produced the same `images/u-boot.itb` +(725,568 B, sha256 +`f4e5c924dc20b51b2347dfd5786f7de23613ebfd445bd80009fcb19be6b1963e`) **[V, +2026-09-14]**; the procedure is that task's own stanza. + +THE BUILD-SYSTEM LINE IS GONE FOR THE SAME REASON. `BR2_TARGET_UBOOT_BUILD_SYSTEM` defaults to KCONFIG *only* if `BR2_TARGET_UBOOT_LATEST_VERSION` is set (`boot/uboot/Config.in:11-12`); on a custom version it falls back to LEGACY, which would try `make _config` -and fail on a tree that has had no such target for a decade. +and fail on a tree that has had no such target for a decade — which is why, +pre-DU1, `_BUILD_SYSTEM_KCONFIG=y` had to be spelled out explicitly to avoid +that fallback. Now that `_LATEST_VERSION` is selected (as the choice default), +`_BUILD_SYSTEM_KCONFIG` resolves to KCONFIG on its own and the explicit line +is gone too: as of 933a2d2 `configs/mister_de25nano_defconfig` names neither +`_CUSTOM_VERSION`/`_CUSTOM_VERSION_VALUE` nor `_BUILD_SYSTEM_KCONFIG`. Mainline has NO DE25-Nano board — `board/terasic/` has `de0-nano-soc`, `de1-soc`, `de10-nano`, `de10-standard` and `sockit`, and there is no diff --git a/docs/de25-boot-chain.md b/docs/de25-boot-chain.md index 31f1a760..917a70c1 100644 --- a/docs/de25-boot-chain.md +++ b/docs/de25-boot-chain.md @@ -303,17 +303,26 @@ the mismatch is QSPI-side. Release discipline that keeps this impossible: handoff is inside the QSPI bitstream **[V RB-boot]**. Any future need to change DDR settings is a posture-2 event (bitstream rebuild + bench flash), not a release. - **No release writes QSPI, by any mechanism, including the U-Boot environment (§7 row 5).** - This is the load-bearing rule of the document, and its exact status as of 2026-08-21 is: - **a policy, enforced by prose only.** The "guards" refutation lens verified, and this pass - re-states without softening, that **ADR 0027 Decision 4's board-identity assertion has no - implementation anywhere in the tree** — no shipped script reads `/proc/device-tree/compatible` - or otherwise checks board identity; `ADR 0027` lines 80-83, `de25-nano-tasks.md` 120/155 and + This is the load-bearing rule of the document. As of 2026-08-21 it was **a policy, enforced by + prose only**; as of 2026-09-14 (docs/uboot-tasks.md DU2) **half of that is now enforced by + code**: `external.mk`'s `MISTER_UBOOT_DE25_QSPI_AUDIT` (`UBOOT_POST_BUILD_HOOKS`) hook fails + `make uboot-rebuild` outright if the resolved U-Boot `.config` carries any of §7's QSPI-write + symbols, if `CONFIG_ENV_IS_IN_FAT` is not `y`, or if `strings u-boot.itb` contains `sf probe` + (outside the one documented, gate-closed `linux_qspi_enable` exception), `ubi part` or + `mtdparts`; `scripts/ci-tests.sh`'s DE25 section separately fails if the rootfs ships an + `fw_env.config` naming an MTD device (row 11). That closes item (b) below -- in fact more + strongly than "CI check", since it is a build failure, not a separate lane that can be skipped + or go unnoticed. **The "guards" refutation lens verified, and this pass re-states without + softening, that ADR 0027 Decision 4's board-identity assertion still has no implementation + anywhere in the tree** — no shipped script reads `/proc/device-tree/compatible` or otherwise + checks board identity; `ADR 0027` lines 80-83, `de25-nano-tasks.md` 120/155 and `downloader-contract.md` 1208-1215 are all design prose. Nothing today would stop DE10 flash semantics being cargo-culted onto a DE25 tree except the accident that no DE25 tree exists yet. **Before the first DE25 release:** (a) implement the identity assertion in the updater *and* in - any `updateboot` analogue; (b) add a release-blocking CI check that the DE25 U-Boot config has - `ENV_IS_IN_UBI` unset and ships no QSPI-write command set; (c) only then may "no release writes - QSPI" be tagged **[V]** rather than "[policy, unenforced]". If a QSPI update is ever shipped + any `updateboot` analogue -- **still open**; (b) a release-blocking build assertion that the + DE25 U-Boot config has `ENV_IS_IN_UBI` unset and ships no QSPI-write command set -- + **DONE, docs/uboot-tasks.md DU2**; (c) only once (a) also lands may "no release writes QSPI" be + tagged **[V]** rather than "[policy, partially enforced]". If a QSPI update is ever shipped (posture 2/3) it is a separate, explicit, documented **bench** procedure — the `updateboot` analogue **must not** be a cargo-culted raw `dd`: the DE10 habit (whole-disk `dd` + env wipe, [`downloader-contract.md`](downloader-contract.md) §8) is release-fatal here even though, per @@ -385,7 +394,7 @@ pass. No row has been deleted. Severity vocabulary: **brick-class** = needs JTAG | 3 | Porting DE10 `updateboot` semantics (raw `dd`, env wipe at fixed sectors) | **Strand-class, and the first synthesis overstated it.** *(Amended — all three lenses.)* The `dd` leg writes garbage at Agilex-meaningless offsets **on the SD card only**: `/dev/mmcblk*` is the HPS SD/MMC controller; QSPI is a physically separate Cadence controller behind the SDM (`linux:…/socfpga_agilex5.dtsi:476-488`, `spi@108d2000`). No sector arithmetic on `mmcblk0` can reach boot flash. The **env-wipe leg** is the one that can — via row 5 or row 11 | board-identity assertion before any flash step (ADR 0027 §Decision 4 — **unimplemented**, §5), plus rows 5 and 11 | | 4 | MSEL switched away from `001` by a user following DE10-era lore | No boot until switched back; no damage | docs: "switches stay at default". **Recorded objection** (guards lens): that sentence appears in **no user-facing document** — `docs/user/` has zero DE25 or MSEL content; today the rule lives only in this developer doc. Writing it into the DE25 user docs is a first-release blocker, not an existing guard | | 5 | **`saveenv` (or any env write) with `ENV_IS_IN_UBI` compiled in and no `uboot.env` on FAT** | **Brick-class.** `env_save()` targets the location the env *loaded* from; on FAT-miss that is the **UBI volume in QSPI** → a routine operation writes boot flash | build our U-Boot with `CONFIG_ENV_IS_IN_UBI=n`; ship a valid `uboot.env` on FAT; assert the boot log says `Saving Environment to FAT` (§8.5) | -| 6 | **Factory SPL built with FIT signature required; we ship an unsigned `u-boot.itb`** | Every release strands at SPL on every board — indistinguishable from a bad card to the user | D2.2 first test before any release. **Recorded objection** (completeness lens, and this pass agrees on the evidence): the DTB carved from Terasic's *published* SPL carries **no `/signature` node and no keys** (*SPL-dtb*, inspected 2026-08-21), so this drops from "posture-1 killer" to a routine first-contact check. Residual is only the published-build-vs-factory-flash gap | +| 6 | **Factory SPL built with FIT signature required; we ship an unsigned `u-boot.itb`** | Every release strands at SPL on every board — indistinguishable from a bad card to the user | D2.2 first test before any release. **Recorded objection** (completeness lens, and this pass agrees on the evidence): the DTB carved from Terasic's *published* SPL carries **no `/signature` node and no keys** (*SPL-dtb*, inspected 2026-08-21), so this drops from "posture-1 killer" to a routine first-contact check. Residual is only the published-build-vs-factory-flash gap. **Closed further 2026-09-14:** the `u-boot.itb` on Terasic's *factory SD image* is itself unsigned, crc32-only, same layout as ours (`de25-uboot.md` §12) — the factory loader demonstrably accepts that shape | | 7 | **Any QSPI write attempted with no archived known-good JIC** | *(Amended — all three lenses: the premise was false.)* The JTAG path is **not** empty-handed: `golden_top_hps.jic` is published (§5) and *GSG* documents the restore. The real exposures are vendor **link-rot**, **wrong-revision** substitution (row 14), and the fact that the published JIC's bootability is untested | archive both revisions' Resource Packages **with hashes** now, locally and durably; verify before erase (§6 ordering rule); never rely on the vendor URL staying live | | 8 | **Posture-2 QSPI flash performed as a field/OTA step** | **Brick-class today.** *(Amended — all three lenses: state the rule, not an impossibility.)* No power-loss-safe layout for 16 MB is **demonstrated or documented** (§8.4 residual; the measured ~2.87 MiB phase-1 payload makes a custom layout plausible but unproven), so a power cut mid-write leaves JTAG-only recovery for an end user | posture 2 is bench-only, PC-attached, documented one-time; never an update-channel artifact. Revisit only if §8.4's residual resolves in RSU's favour | | 9 | Assuming our `u-boot.itb` can set/repair DDR or pinmux | It cannot — the handoff is inside the QSPI bitstream **[V RB-boot]**; a "fix it in U-Boot" reflex produces silent misconfiguration or no boot | treat DDR/pinmux as QSPI-owned; any change is a bitstream rebuild + bench flash (§5) | @@ -397,6 +406,21 @@ pass. No row has been deleted. Severity vocabulary: **brick-class** = needs JTAG | 15 | **Shipping a phase-2 `core.rbf` from our own Quartus compilation against the factory phase-1 resident in QSPI** | Altera doctrine for split (HPS-first) configuration is that periphery and core images come from the **same** Quartus compilation. If that holds here, every boot-time fabric design we ship is pinned to Terasic's exact factory compile — a **project-shaping constraint on the whole core-switching model**, and a strand-at-U-Boot failure when violated. **[U]** — consistently reported across Altera-derived sources but the authoritative UG (813773) is 403-blocked | D0.2/D2.2: (a) test a self-recompiled GHRD `core.rbf` against the untouched factory QSPI; (b) test whether runtime `COMMAND_RECONFIG` full reconfiguration is compilation-independent — that decides whether core switching routes through U-Boot phase-2 at all or must be Linux-runtime-only. Until answered, treat boot-time phase-2 as pinned to the factory compilation | | 16 | **Running `flash_erase.bat` (or `flash_program.bat`, which erases) as the first act of a D2.2 bench session** | The factory content — never dumped, never compared — is destroyed first; if the published JIC then fails to boot this board, no known-good image has ever existed for it | the §6 ordering rule, written into the D2.2 task *before* hardware arrives: verify-only → readback/archive → only then write | +### 7b. Field notes learned from another DE25-Nano port (2026-09-14) + +Reported behaviour on real DE25-Nano hardware, from a separate port of this board that took a +different path (its own flash image, its own bootloader). Recorded here because each one either +confirms a choice above or names a trap; none is verified by us **[V there, U here]**. + +| # | Observation | Bears on | +|---|---|---| +| F1 | Loading a fabric bitstream **from U-Boot** (`fpga load`) wedged the SDM; that launcher was retired in favour of loading from Linux | §2 step 5 (we do not load cores from U-Boot); `de25-fpga-reconfig.md` §3 option (c) | +| F2 | A flash image whose phase-1 leaves the fabric **unconfigured** ("HPS-first") produced a fatal asynchronous bus error on the first fabric access after warm reboots; a flash image that configures a full design at every reset was stable | §4 posture 1 keeps Terasic's fully-configured factory image — the stable shape | +| F3 | Booting after a JTAG load of a full `.sof` needs `sdhci.debug_quirks=0x60` on the kernel command line and must **not** use `iommu.passthrough=1`, or SD host ADMA corrupts early SD init; QSPI cold boots are unaffected | our SMMU-off DTS and default-speed SD posture; bench sessions that use JTAG | +| F4 | The HPS MAC is unfused and **random on every boot** (`NET_RANDOM_ETHADDR` in the factory config); a persistent MAC has to be provisioned by the OS | first-boot MAC provisioning, the DE10's `u-boot.txt` `ethaddr` idea | +| F5 | Unsigned, locally compiled bitstreams are accepted by the SDM (no VAB); full reconfiguration ~3 s SDM time, ~8 s end to end with settle windows; repeated switching works only with those windows | §7 row 15, `de25-fpga-reconfig.md` §5–§6 | +| F6 | An "HPS-first launcher" flash image that holds the fabric in reset gives a dark board with zero serial; the recovery is reflashing the correct image over JTAG | §6 recovery; §7 row 14 | + ## 8. Resolved questions (D0.1 close-out) **Provenance note.** Six research legs were run, one per question. **Q4 and Q6 returned @@ -469,7 +493,7 @@ a **board's** image. Two candidate closures, both in §5, both **inheriting D2.2 `quartus_pgm` verify-only against the published JIC, or an HPS-side MTD read from a QSPI-enabled DTB (weighing §7 row 10 first). -### 8.3 Q3 — Will the factory SPL boot a mainline-built `u-boot.itb`? — **PARKED on hardware; structure resolved [V], and two of three blockers closed at the desk.** +### 8.3 Q3 — Will the factory SPL boot a mainline-built `u-boot.itb`? — **PARKED on hardware; structure resolved [V], and all three desk-closable blockers closed (FIT acceptance closed by reference 2026-09-14 — `de25-uboot.md` §12).** Structure, mainline-defined **[V, `u-boot:` at `master`, re-fetched 2026-08-21]**: `configs/socfpga_agilex5_defconfig` sets `CONFIG_SPL_LOAD_FIT=y`, @@ -651,7 +675,7 @@ against `/mnt/source/Buildroot_MiSTer/output/build/linux-6.18.44` by this pass. | Claim | Lens | Objection, recorded verbatim in substance | |---|---|---| -| §5 no-release-writes-QSPI | Guards | The rule exists; **the guard does not, except as sentences.** No shipped script reads `/proc/device-tree/compatible`; ADR 0027 Decision 4, `de25-nano-tasks.md` and `downloader-contract.md` are all prose; no CI check pins a future DE25 U-Boot to `ENV_IS_IN_UBI=n`. Tag it [V] only after (a) the identity assertion exists as code and (b) a release-blocking CI check exists. **Carried in §5, final bullet.** | +| §5 no-release-writes-QSPI | Guards | **Partially resolved 2026-09-14 (DU2).** The board-identity half of the guard still does not exist: no shipped script reads `/proc/device-tree/compatible`; ADR 0027 Decision 4, `de25-nano-tasks.md` and `downloader-contract.md` are all prose. The CI-check half now exists and is stronger than originally asked: `external.mk`'s `MISTER_UBOOT_DE25_QSPI_AUDIT` hook fails the DE25 U-Boot build itself (not just a separate CI lane) if `ENV_IS_IN_UBI` (or any other §7 symbol) resolves on, and `scripts/ci-tests.sh` fails if the rootfs ships an MTD-naming `fw_env.config`. Tag it [V] only once the identity assertion also exists as code. **Carried in §5, final bullet.** | | §7 row 2 | Guards | Consequence overstated (**strand-class**, card-recoverable, since QSPI is untouched) and the "per-release factory-QSPI test matrix" exists nowhere. **Carried in the row.** | | §7 row 4 | Guards | "Switches stay at default" appears in **no user-facing doc**; `docs/user/` has zero DE25/MSEL content. It is a task, not a guard. **Carried in the row.** | | §7 row 6 | Severity | Antecedent is desk-testable and tests **negative**: the published SPL's DTB has no signature keys, so this drops from posture-1 killer to a first-contact check. **Carried in the row** (this pass independently re-inspected the DTS and agrees). | diff --git a/docs/de25-dts-rationale.md b/docs/de25-dts-rationale.md index 703b6ab2..94697677 100644 --- a/docs/de25-dts-rationale.md +++ b/docs/de25-dts-rationale.md @@ -27,7 +27,7 @@ Read alongside [`de25-implementation-path.md`](de25-implementation-path.md) §2 | **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*. | +| **PORT** | A third-party DE25-Nano Linux port (6.18.38 + vendor backports), consulted read-only; not public — `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}`). @@ -41,9 +41,9 @@ Nothing here needs a cross compiler — DTB generation and schema checking are h ```sh # 0. A private, writable copy of the pinned kernel. Do NOT use output/build/linux-rt-; # 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 +mkdir -p /mnt/source/dts-check-work/t3 +tar -xf dl/linux/linux-7.2.2.tar.xz -C /mnt/source/dts-check-work/t3/ +cd /mnt/source/dts-check-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 \ @@ -63,7 +63,7 @@ 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 +source /mnt/source/dts-check-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 @@ -109,7 +109,7 @@ arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi:583.5-28: Warning (property_name_ ``` 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) +have introduced (`ethernet-phy@0 { reg = <1>; }` — present in TERASIC, ALTERA **and** PORT) was fixed before it was committed; see §5 D3. ### 2.2 `dtbs_check` @@ -297,13 +297,13 @@ file is named. | `#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/serial0 = &uart1` | TERASIC + ALTERA + PORT | **kept** | See "Console UART" below. | +| `aliases/ethernet0 = &gmac0` | TERASIC + ALTERA + PORT | **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. | +| `chosen/bootargs` | TERASIC + PORT | **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. | +| LED child node name | ALTERA (`led-0`) | **changed** from TERASIC/PORT (`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. | @@ -315,28 +315,28 @@ file is named. | `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. | +| `hwmon` / `temp_volt` child of svc | TERASIC + PORT | **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`** — `compatible` | TERASIC + PORT | **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 + PORT (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` `resets` | TERASIC + PORT | **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 + PORT | **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 + PORT | **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` `cap-sd-highspeed` | TERASIC + ALTERA (PORT 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 + PORT | **kept** | 3.3V-only signalling. Also the reason we can safely omit ALTERA's `vqmmc-supply` level-shifter regulator (below). | +| `mmc0` `no-sdio` | TERASIC + PORT | **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` 40 × `cdns,phy-*` / `cdns,hrs*` | TERASIC + PORT | **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 port'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. | +| `mmc0` `max-frequency` | PORT `25000000` (ALTERA `200000000`; TERASIC none) | **taken from PORT** | **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 — PORT, `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. | @@ -346,10 +346,10 @@ file is named. | `&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. | +| `disable-over-current` on `&watchdog4` | TERASIC → ALTERA → PORT | **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. | +| `&qspi` + `flash@0` + partitions | ALTERA only | **dropped** | Not in the §3.1 node set; TERASIC and PORT 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 | PORT 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 port'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 @@ -387,22 +387,23 @@ keeping the SoC-accurate string plus a one-line patch is the better shape. 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 +- TERASIC, ALTERA and PORT *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` +- The one apparent counter-evidence is TERASIC's/PORT'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`, +- UART addresses are identical across MAINLINE, TERASIC and PORT (`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. +Linux DTS: Terasic's public branch (`github.com/terasic/u-boot-socfpga` @ `de25-nano-v2025.01`, +`arch/arm/dts/socfpga_agilex5_de25_nano.dts:11`) has `serial0 = &uart1`, and `...-u-boot.dtsi` +sets `stdout-path = "serial0:115200n8"` — and the PORT 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 @@ -410,12 +411,13 @@ 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 | +| TERASIC, PORT | `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 +1 GiB at `0x8000_0000` matches the *UM* and the DE25 **U-Boot** DTS — Terasic's public branch +(`github.com/terasic/u-boot-socfpga` @ `de25-nano-v2025.01`, +`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 @@ -429,14 +431,14 @@ mismatch. U-Boot then rewrites Linux's node wholesale from `bi_dram` — (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 +TERASIC and PORT'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 +`de25-reference-implementation.md` asked this of the PORT tree ("Does the DE25-Nano HPS +actually have 2 GiB of DRAM?"). Every DE25 bootloader source says 1 GiB, so the port's 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. @@ -498,7 +500,7 @@ Stated honestly: SMMU-off removes the *identified* fault, it does not establish 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 + is MAINLINE 7.2's shipped default; and the closest supporting data point is the PORT'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 @@ -544,7 +546,7 @@ with the SMMU **on**, where the cacheability of an access is determined by the S 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 +it was. The port's setup guide (`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. @@ -556,7 +558,7 @@ 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. +Every difference a node-by-node diff against TERASIC/ALTERA/PORT turns up, accounted for. D1–D6 are changes of *form*; the content drops are in §3. | # | Divergence | Why | @@ -568,7 +570,7 @@ D1–D6 are changes of *form*; the content drops are in §3. | 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. | +| D8 | `mmc0` gains **`max-frequency = <25000000>`** | PORT'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. | --- @@ -598,9 +600,9 @@ D1–D6 are changes of *form*; the content drops are in §3. | # | 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. | +| ~~U1~~ | ~~Is the console really `uart1`?~~ | — | **Closed `[V]`.** The DE25 U-Boot tree sets `serial0 = &uart1` and `stdout-path = "serial0:…"` (Terasic's public branch, `github.com/terasic/u-boot-socfpga` @ `de25-nano-v2025.01`, `arch/arm/dts/socfpga_agilex5_de25_nano.dts:11`, `...-u-boot.dtsi`), and the PORT 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 port's setup guide (`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 port 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. | @@ -616,8 +618,8 @@ D1–D6 are changes of *form*; the content drops are in §3. |---|---|---| | [`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-reference-implementation.md`](de25-reference-implementation.md) (open question, line ~664) | "Does the DE25-Nano HPS actually have 2 GiB of DRAM? The port's memory node hard-codes `reg = <0 0x80000000 0 0x80000000>`…" | **Answered as far as any desk source can: every DE25 bootloader source says 1 GiB.** The port's Linux node over-claims 2×, and its `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* (Terasic's public branch, `github.com/terasic/u-boot-socfpga` @ `de25-nano-v2025.01`, `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]. | diff --git a/docs/de25-implementation-path.md b/docs/de25-implementation-path.md index adb4450f..06f9fe46 100644 --- a/docs/de25-implementation-path.md +++ b/docs/de25-implementation-path.md @@ -59,8 +59,8 @@ All retrieved or read **2026-08-22** unless stated. - Local kernel tree `output/build/linux-6.18.44` — cited as `linux:path:line`. - Mainline Linux at tags `v6.18`, `v6.19`, `v7.0`, `v7.1`, `v7.2` and at `master` (post-7.2, → 7.3), via `raw.githubusercontent.com/torvalds/linux//…` — cited as `mainline@:path`. -- Read-only reference fork `/mnt/source/de25-linux` (Linux 6.18.38, the friend's working board) — - cited as `de25-linux:path`. **Read only; never executed, modified, or checked out.** +- A third-party DE25-Nano Linux port (Linux 6.18.38, a working board), consulted read-only; not + public — cited as `PORT tree, path`. **Read only; never executed, modified, or checked out.** - `github.com/altera-fpga/linux-socfpga` branch `socfpga-6.18.20-lts` (the org formerly `altera-opensource`), and `github.com/terasic/linux-socfpga` branch `de25-nano-6.12.11-lts`. - Mainline U-Boot at tag `v2026.07`; mainline TF-A at tag `v2.15.0`. @@ -121,7 +121,9 @@ untested on hardware** — it is the second item on §2.6's bring-up test list, - **Binding: yes, DTS-only, zero driver patches, on a 6.19-or-newer base [V].** A two-string fallback compatible binds the stock `stratix10-svc`, `stratix10-soc` and `sdhci-cadence` drivers. - The friend's driver commit `d1878a320` was avoidable and must not be copied. + The port's driver commit that hard-coded the `intel,agilex5-*` compatible strings directly + into the stock `stratix10-svc`/`stratix10-soc` match tables (see the table in §2.3) was + avoidable and must not be copied. - **Programming: not established, and one observed on-hardware failure argues against it [V].** See §2.6. This is the single largest risk in the DE25 plan and it is a hardware question. - **The only carried patch a 6.18 base would force is not the fpga-mgr patch at all — it is the @@ -177,60 +179,62 @@ properties, not compatibles, so they are easy to lose when authoring by hand: `method = "smc"` or `"hvc"` **[V `linux:drivers/firmware/stratix10-svc.c:865-885`]**. Mainline's own Agilex 5 shape satisfies all three (`firmware { svc { method = "smc"; … } }`, -`mainline@v7.2:arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi`) **[V]**, as does the friend's -(`de25-linux:…/socfpga_agilex5.dtsi:213-218`) **[V]**. On a 6.18 base, where we would author the +`mainline@v7.2:arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi`) **[V]**, as does the PORT tree's +(`PORT tree, …/socfpga_agilex5.dtsi:213-218`) **[V]**. On a 6.18 base, where we would author the whole subtree, they must be met deliberately. ### 2.2 The independent empirical check — what it proves, and what it does not -The friend's board boots from SD on real DE25 silicon. His `drivers/mmc/host/sdhci-cadence.c` is -**byte-identical to stock 6.18.44** (`diff -q` → identical, re-run this pass) **[V]**, and his +The PORT's board boots from SD on real DE25 silicon. Its `drivers/mmc/host/sdhci-cadence.c` is +**byte-identical to stock 6.18.44** (`diff -q` → identical, re-run this pass) **[V]**, and its `mmc0` declares `compatible = "intel,agilex5-sd4hc", "cdns,sd4hc"` -**[V `de25-linux:arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi:387`]**. That is the fallback idiom, +**[V `PORT tree, arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi:387`]**. That is the fallback idiom, matching on the bare `cdns,sd4hc` entry, working on **real Agilex 5 hardware, with an unpatched mainline driver**. **The necessary qualifier, added under challenge:** that boot runs with `sdhci.debug_quirks=0x60` baked into the production `boot.scr.uimg` bootargs, which forces SDHCI into **PIO** instead of ADMA -**[V `de25-reference-implementation.md` §3, commit `716559020`]**. So the empirical check proves -**binding, probe and PIO operation through a fallback compatible on real silicon**. It does **not** -prove the DMA path. §8 Q2 is where that lives, and §5.1 no longer treats it as a settled non-issue. - -Note in passing that his board `.dts` sets nine `cdns,phy-use-*` / `cdns,phy-io-mask-*` / -`cdns,phy-sync-method` properties **[V `de25-linux:…/socfpga_agilex5_de25_nano.dts:127-135`]** that +**[V `de25-reference-implementation.md` §3 — the port's retraction of an earlier false-negative, +reproduced via a genuine power-cycle through the unmodified production boot chain]**. So the +empirical check proves **binding, probe and PIO operation through a fallback compatible on real +silicon**. It does **not** prove the DMA path. §8 Q2 is where that lives, and §5.1 no longer treats +it as a settled non-issue. + +Note in passing that the PORT's board `.dts` sets nine `cdns,phy-use-*` / `cdns,phy-io-mask-*` / +`cdns,phy-sync-method` properties **[V `PORT tree, …/socfpga_agilex5_de25_nano.dts:127-135`]** that the stock driver's property table **does not parse** — it knows only eleven `cdns,phy-input-delay-*` / `cdns,phy-dll-delay-*` names **[V `linux:drivers/mmc/host/sdhci-cadence.c:108-118`]**. Those are **dead devicetree on a mainline driver**; do not transcribe them. Their inertness also -means his working SD path runs on the stock driver's **default** PHY configuration, which is what we -would inherit. +means the port's working SD path runs on the stock driver's **default** PHY configuration, which is +what we would inherit. **A correction to an earlier draft of this section:** `altr,smmu_enable_quirk` was described as dead -devicetree on the same footing. That is true **only** of the friend's mainline-based tree -(`grep -rn smmu_enable_quirk /mnt/source/de25-linux/drivers/` → no hits) **[V]**. In Terasic's vendor +devicetree on the same footing. That is true **only** of the PORT's mainline-based tree +(`grep -rn smmu_enable_quirk` over the PORT tree's `drivers/` → no hits) **[V]**. In Terasic's vendor tree the property is **live and load-bearing**: `stratix10-svc.c:3508` reads it, and without it the agilex5-svc probe path returns `-ENODEV` **[V, fetched this pass]**. It is mainline-inert, not meaningless — and the fact that a vendor driver gates SDM DMA setup on it is evidence for §2.6, not against it. -### 2.3 What the friend actually carries, counted +### 2.3 What the PORT actually carries, counted -Diffing `/mnt/source/de25-linux` (6.18.38) against `linux-6.18.44`, filtering 6.18.38→.44 stable +Diffing the PORT tree (6.18.38) against `linux-6.18.44`, filtering 6.18.38→.44 stable churn **[V]**: | Carried change | Size | Avoidable? | |---|---|---| -| `+{.compatible = "intel,agilex5-soc-fpga-mgr"},` in `s10_of_match` (`de25-linux:drivers/fpga/stratix10-soc.c:451`) | 1 line | **Yes** — declare the fallback in DT instead | -| `+{.compatible = "intel,agilex5-svc"},` in `stratix10_svc_drv_match` (`de25-linux:drivers/firmware/stratix10-svc.c:1134`) | 1 line | **Yes** — same | -| `drivers/clk/socfpga/clk-agilex5.c` + `Makefile:6` | **847 lines** (his vendor backport; the mainline v6.19 file is 561) | **Yes, by moving to ≥6.19** (§5) | +| `+{.compatible = "intel,agilex5-soc-fpga-mgr"},` in `s10_of_match` (`PORT tree, drivers/fpga/stratix10-soc.c:451`) | 1 line | **Yes** — declare the fallback in DT instead | +| `+{.compatible = "intel,agilex5-svc"},` in `stratix10_svc_drv_match` (`PORT tree, drivers/firmware/stratix10-svc.c:1134`) | 1 line | **Yes** — same | +| `drivers/clk/socfpga/clk-agilex5.c` + `Makefile:6` | **847 lines** (the port's vendor backport; the mainline v6.19 file is 561) | **Yes, by moving to ≥6.19** (§5) | | `drivers/misc/de25_fpga_trigger.c` | 95 lines | **No** — see §7 | **[V** all four, `wc -l` and `grep` this pass.**]** -He needed the two match-table lines only because his DT declares `intel,agilex5-svc` and +The port needed the two match-table lines only because its DT declares `intel,agilex5-svc` and `intel,agilex5-soc-fpga-mgr` **with no fallback string** -**[V `de25-linux:…/socfpga_agilex5.dtsi:215,224`]**. That is a DT authoring choice, not a kernel -constraint. `of-fpga-region.c` and `fpga-mgr.c` are untouched in his tree **[V `diff`]** — he added -no writable attribute; the trigger is the separate module. +**[V `PORT tree, …/socfpga_agilex5.dtsi:215,224`]**. That is a DT authoring choice, not a kernel +constraint. `of-fpga-region.c` and `fpga-mgr.c` are untouched in the port's tree **[V `diff`]** — it +added no writable attribute; the trigger is the separate module. ### 2.4 What would falsify the "binds and works" reading @@ -274,11 +278,11 @@ State it plainly, because it is the difference between a plan and a hope: | Question | Status | Evidence | |---|---|---| -| Does a fallback compatible make `/sys/class/fpga_manager/fpga0` and `/sys/class/fpga_region/region0` appear, with stock drivers? | **Very likely yes** | OF core mechanism [V]; same idiom proven on this silicon for `sdhci-cadence` [V]; friend's board reaches probe with only string additions [V] | +| Does a fallback compatible make `/sys/class/fpga_manager/fpga0` and `/sys/class/fpga_region/region0` appear, with stock drivers? | **Very likely yes** | OF core mechanism [V]; same idiom proven on this silicon for `sdhci-cadence` [V]; the PORT's board reaches probe with only string additions [V] | | Does mainline's `stratix10-svc` actually **program an Agilex 5 fabric**? | **Not established. One observed failure.** | Mainline svc hands the SDM raw `gen_pool` physical addresses with no IOMMU mapping and no offset (`linux:drivers/firmware/stratix10-svc.c:277,458,785-807,1007`) [V]. Terasic's vendor svc adds `+0x80000000`, attaches an IOMMU domain, and *disables the SDM remapper* for `intel,agilex5-svc` [V]. Mainline has none of this at 6.18.44 or v7.2 [V]. The single mainline-path attempt on hardware timed out on `RECONFIG_REQUEST` and wedged the board [V] | **Confidence: high on binding; low on end-to-end reconfiguration through stock mainline drivers.** -The friend's board is **not** evidence that mainline svc can program this fabric — his own working +The PORT's board is **not** evidence that mainline svc can program this fabric — its own working reconfigurations ran on Terasic's vendor 6.12.11 kernel, which has all of the above. **The on-hardware test that settles it, and it is cheap:** @@ -418,7 +422,7 @@ That Altera itself has taken the DE25-Nano board file in-house — on its **curr pushed eleven days before this retrieval — is the most useful thing this survey found. It makes the following unnecessary: authoring the board `.dts` from first principles; guessing pinmux, PHY wiring, regulator GPIOs, QSPI partition layout, or the `temp_volt` hwmon channel map; and treating -the friend's back-port as the only reference. It is a **better primary reference** than the friend's +the PORT's back-port as the only reference. It is a **better primary reference** than the PORT's tree: newer, closer to our base, and traceable to a named vendor org. ### 4.1 But it is not drop-in, and the reason matters @@ -432,7 +436,7 @@ calculation replacing static DT params, by Tanmay Kathpalia, Altera; visible in **`cdns,sd6hc` does not exist anywhere in mainline** — not in 6.18.44, not at v7.2, not at `master` **[V `grep` of `drivers/mmc/` and `Documentation/devicetree/bindings/mmc/` on all three]**. Copying Altera's `mmc0` verbatim therefore **buys a carried driver patch** and violates decision 5. The -older SD4HC form, which Terasic and the friend both use and which the friend's board demonstrably +older SD4HC form, which Terasic and the PORT both use and which the PORT's board demonstrably boots on with an unpatched driver (§2.2), does not. **Rule for harvesting: take Altera's board `.dts` as the wiring reference — pinmux, PHY, regulators, @@ -495,8 +499,8 @@ The 6.18 case rested on three supports. Two collapse and the third has been re-c `intel,agilex5-clkmgr` binding, the clock-ID header, and the DT node at `:144-148` — and no driver at all; the *only* `agilex5` compatible string in the whole of 6.18.44's `drivers/` is stmmac's **[V, `grep -rl` this pass]**. Every consumer of `&clkmgr` — `mmc0` included — would defer - forever. The friend confirms this by construction: he backported an 847-line vendor - `clk-agilex5.c` into his 6.18.38 tree and wired it into `drivers/clk/socfpga/Makefile:6` **[V]**. + forever. The PORT confirms this by construction: it backported an 847-line vendor + `clk-agilex5.c` into its 6.18.38 tree and wired it into `drivers/clk/socfpga/Makefile:6` **[V]**. Support 3 is decisive under decision 5. Basing on 6.18 means carrying a whole SoC clock driver when a mainline route plainly exists one release later. That is exactly the carried patch decision 5 @@ -629,7 +633,7 @@ CONFIG_ENV_FAT_DEVICE_AND_PART="0:1" Mainline U-Boot now has real exFAT: `fs/exfat/`, `CONFIG_FS_EXFAT` ("read/write support", `imply CMD_FS_GENERIC if CMDLINE`), added by commit `b86a651b64` on **2025-03-17** — *after* the -reference board's U-Boot 2025.01 base **[V]**. That is precisely why the friend had to hand-roll +reference board's U-Boot 2025.01 base **[V]**. That is precisely why the PORT had to hand-roll `libexfat` and a custom exFAT-aware SPL; on current mainline the equivalent is one defconfig line. Note the stock defconfig enables `SPL_FS_FAT` (`:22`) but **not** `FS_FAT`/`CMD_FAT`/`FS_EXFAT` for U-Boot proper **[V, re-checked this pass]** — we set what we need either way. @@ -654,7 +658,7 @@ loader. (`linux:drivers/fpga/fpga-region.c:175`). There is no writable attribute anywhere. `OF_CONFIGFS` **does not exist in mainline** — `linux:drivers/of/Kconfig` has only `OF_OVERLAY:105` and `OF_OVERLAY_KUNIT_TEST:116`, and there is no `drivers/of/configfs.c`. Altera's vendor 6.12-lts tree -does carry it — i.e. the friend's original path was the vendor path. +does carry it — i.e. the PORT's original path was the vendor path. **The only mainline trigger is an OF overlay carrying `firmware-name`**, caught by of-fpga-region's overlay notifier, registered at module init (`linux:drivers/fpga/of-fpga-region.c:340,455`). @@ -673,9 +677,9 @@ closes Leg B's open unknown about a zero-out-of-tree-code path: **there isn't on **(a) is the recommendation.** `of_overlay_fdt_apply()` and `of_overlay_remove()` are `EXPORT_SYMBOL_GPL` (`linux:drivers/of/overlay.c:1090,1272`) **[V]**, so a small GPL module can drive -reconfiguration without patching any mainline file. The friend's `de25_fpga_trigger.c` is 95 lines +reconfiguration without patching any mainline file. The PORT's `de25_fpga_trigger.c` is 95 lines doing exactly this — `request_firmware()` → `of_overlay_fdt_apply()` → `DEVICE_ATTR_WO(trigger)` on a -misc device **[V `de25-linux:drivers/misc/de25_fpga_trigger.c`, 95 lines by `wc -l`]**. +misc device **[V `PORT tree, drivers/misc/de25_fpga_trigger.c`, 95 lines by `wc -l`]**. **What that module's hardware history actually shows — corrected.** An earlier draft said "it is running on real hardware." That overstates it. On real silicon the module **reached the fpga-mgr @@ -724,7 +728,7 @@ mainline exposes no writable fpga-manager attribute, has no configfs overlay loa **Resolved disagreements.** 1. **"No driver patch needed" vs "Altera's own tree carries the match entries."** Both observations - are true and they do not conflict *for binding*. Altera and the friend chose *exact* compatible + are true and they do not conflict *for binding*. Altera and the PORT chose *exact* compatible strings in their DT, which forces the match-table additions; the fallback route was simply not taken. The OF core makes a fallback binding-equivalent **[V]**, and we author our own DT, so the choice is ours. The salvaged doc's claim is **upheld in substance for binding**, with two @@ -750,7 +754,7 @@ mainline exposes no writable fpga-manager attribute, has no configfs overlay loa **Corrections applied to this document's own earlier draft**, listed so the change is auditable: -- §2.2 no longer presents the friend's SD boot as unqualified evidence — it runs in PIO. +- §2.2 no longer presents the PORT's SD boot as unqualified evidence — it runs in PIO. - §2.2 no longer calls `altr,smmu_enable_quirk` dead devicetree without qualification; it is mainline-inert but vendor-live. - §7 no longer says the trigger module "is running on real hardware"; its one attempt failed. @@ -759,7 +763,7 @@ mainline exposes no writable fpga-manager attribute, has no configfs overlay loa - §2.4/§8 Q1 confidence on end-to-end reconfiguration lowered from **medium** to **low**, with the vendor-driver evidence attached. - The lkml version label for the DTS companion corrected from v6 to v2 (the binding half is v6). -- Line-number citations corrected: friend's `mmc0` compatible is at `socfpga_agilex5.dtsi:387`; +- Line-number citations corrected: the PORT's `mmc0` compatible is at `socfpga_agilex5.dtsi:387`; 6.18.44 `sdhci-cadence` match table at `:643-658`; `of_iommu`/OF base score function at `base.c:338-356`. - §8 gained Q10 (watch upstream for adopted vendor svc behaviour). @@ -769,7 +773,7 @@ mainline exposes no writable fpga-manager attribute, has no configfs overlay loa | Proposed | Rejected because | |---|---| | "The v7.2 citation `stratix10-svc.c:1911-1915` is a wrong line number; the table sits near `:1133`." | **The citation is correct.** `:1133` is the **6.18.44** location, in a 1334-line file. The **v7.2** file has grown to 2113 lines (FCS command plumbing) and the table genuinely sits at `:1911-1915` **[V, `mainline@v7.2:drivers/firmware/stratix10-svc.c` fetched and grepped this pass]**. Both citations retained, each labelled with its base. | -| "`clk-agilex5.c … 561 lines` is unconfirmed; the commit adds 736 lines across 7 files." | **The file is exactly 561 lines** **[V, `torvalds/linux@v6.19` fetched, `wc -l` = 561]**. The 736-line figure is the whole commit including Kconfig/Makefile/header churn — a different measure of a different thing. Kept 561 for the file, and now name the friend's 847-line vendor backport alongside it so the three numbers cannot be confused again. | +| "`clk-agilex5.c … 561 lines` is unconfirmed; the commit adds 736 lines across 7 files." | **The file is exactly 561 lines** **[V, `torvalds/linux@v6.19` fetched, `wc -l` = 561]**. The 736-line figure is the whole commit including Kconfig/Makefile/header churn — a different measure of a different thing. Kept 561 for the file, and now name the PORT's 847-line vendor backport alongside it so the three numbers cannot be confused again. | **Never challenged by any leg**, and worth flagging as such: that `s10_init()`'s `of_find_node_by_name(NULL, "svc")` makes the parent node **name** load-bearing, that @@ -795,7 +799,7 @@ primary source before it was applied. Nothing was accepted on a verifier's say-s `drivers/mmc/host/sdhci.c` (`sdhci_set_dma_mask`), `arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi`, `drivers/clk/socfpga/` (directory listing), and a tree-wide `grep -rl agilex5 drivers/`. -**Read read-only from `/mnt/source/de25-linux`:** `drivers/clk/socfpga/clk-agilex5.c` (847 lines), +**Read read-only from the PORT tree:** `drivers/clk/socfpga/clk-agilex5.c` (847 lines), `drivers/misc/de25_fpga_trigger.c` (95 lines), both patched match tables, `socfpga_agilex5.dtsi` (svc/fpga-mgr/mmc0 compatibles), `diff -q` of `sdhci-cadence.c` against ours (identical), and a `grep` for `smmu_enable_quirk` consumers (none). diff --git a/docs/de25-nano-overview.md b/docs/de25-nano-overview.md new file mode 100644 index 00000000..b3afc1ef --- /dev/null +++ b/docs/de25-nano-overview.md @@ -0,0 +1,226 @@ +# DE25-Nano — how this is going to work + +**Who this is for:** someone technical who has not followed the DE25 work and wants the shape of +it in ten minutes: how the board boots, what we build for it, how far along it is, and what could +still stop it. Every section links to the document that holds the evidence. Status as of +**2026-09-14**: everything below is built and checked at the desk; **nothing has run on a +DE25-Nano yet** — the board is on order. + +Tags: **[V]** verified (in a file, a build, a vendor artifact, or on silicon by someone else); +**[U]** unverified, with what would settle it. + +--- + +## 1. The short answer + +**Yes: a single SD-card image, written the way the DE10-Nano's is, and the board boots from it.** +The card holds everything we build — bootloader, firmware, kernel, root filesystem. The board's +own flash holds Terasic's factory first-stage loader and is **never written** by anything we ship. +"Burn a card, insert it, power on" is the whole workflow, and the DIP switches stay at their +factory setting **[V, `de25-boot-chain.md` §1]**. + +Two things are different from the DE10, and both are fine: + +- **The boot partition is FAT32 and the loader finds files by name.** There is no `0xA2` raw + partition and no `uboot.img`; the factory loader reads `u-boot.itb` off partition 1 like any + other file **[V]**. The exFAT data partition the DE10 uses is still the target for the *data* + side of the card, exactly as on the DE10 (§3). +- **The kernel is a 64-bit `Image` plus a separate device tree, booted through a text config + file**, not the DE10's concatenated `zImage_dtb` **[V, `de25-sdcard.md`]**. + +What is *not* yet answered, in one sentence: whether our particular bootloader and firmware pair +boots under Terasic's factory loader on real silicon, and how well the SD controller and the +fabric behave once it does. Section 5 ranks those. + +--- + +## 2. The boot chain, link by link + +The DE10-Nano's chain is short: a boot ROM in the SoC scans the card for a raw partition, runs +the small loader it finds there, and that loader configures the FPGA and boots Linux. Everything +lives on the card ([`boot-chain.md`](boot-chain.md)). + +The DE25-Nano's Agilex 5 has a different first mover: a hard microcontroller in the chip called the +**Secure Device Manager (SDM)** boots first, and it can only load from the board's QSPI flash or +from a JTAG cable — **not from the SD card** **[V, board wiring and the vendor's own manual, +`de25-boot-chain.md` §8.1]**. So the first two links are board-resident, and the seam between +"the board's" and "ours" is permanent. + +| # | Link | Lives in | Who builds it | What it does | Status | +|---|---|---|---|---|---| +| 1 | **SDM firmware** | QSPI flash | Terasic (factory) | Boots the SoC, reads the flash | [V] never ours | +| 2 | **Phase-1 bitstream + first-stage loader (SPL)** | QSPI flash | Terasic (factory) | Configures the FPGA fabric with Terasic's reference design, sets up HPS pins and DDR (the *handoff* data is inside this bitstream), then runs U-Boot SPL | [V] never ours; the SPL is U-Boot 2025.01 built from Terasic's public branch (why we keep it: see below) | +| 3 | **SPL reads the card** | — | — | Finds `u-boot.itb` by name on partition 1 (FAT) and verifies its crc32 hashes | [V] by reference: Terasic's own shipped FIT is unsigned crc32 with our exact layout, so the factory loader demonstrably accepts that shape (§5.1) | +| 4 | **`u-boot.itb`** — a container holding **TF-A BL31** (the EL3 secure firmware) + **U-Boot proper** + our board device tree | card, p1 | **us** (mainline U-Boot 2026.07 + TF-A v2.15.0, through Buildroot) | BL31 runs first and stays resident as the SoC's secure monitor; U-Boot proper then takes over | built, structure verified with `dumpimage` [V]; boots under the factory SPL **[U — needs the board]** | +| 5 | **U-Boot boots Linux** | card, p1 | us | Reads `extlinux/extlinux.conf`, loads `Image` + `socfpga_agilex5_de25nano.dtb`, boots | built [V]; SD timing on this board **[U]** | +| 6 | **Linux** | card, p2 | us (kernel 7.2 series, arm64, 92 modules, MiSTer's shared userspace profile) | Mounts the root filesystem, brings up console, network, USB | built; boots under QEMU [V]; on the board **[U]** | +| 7 | **Fabric reconfiguration at runtime** | — | us (kernel + DTS) + later the MiSTer framework | Linux hands a bitstream to BL31 over an SMC call and the SDM rewrites the fabric (§6) | mechanism [V]; not yet exercised by us **[U]** | + +Detail: [`de25-boot-chain.md`](de25-boot-chain.md) §2 is this table with the evidence per step; +[`de25-uboot.md`](de25-uboot.md) is link 4 in depth; [`de25-sdcard.md`](de25-sdcard.md) is the card. + +**Why the first-stage loader stays Terasic's 2025.01 when everything else is current.** It is +not a choice between old and new U-Boot: the SPL lives *inside the phase-1 bitstream in the +board's flash*, and the only ways to put a different one there are a JTAG cable and a PC. Our +build does compile a modern (2026.07) SPL, and nothing of it is shipped, because using it would +mean reflashing every board once at install time — the "posture 2" bench operation, which +another port of this board did take. Until there is a reason to (§5.3 may supply one), the +factory SPL is the part of the chain we deliberately do not own, and the modern U-Boot *proper* +it hands off to is ours. + +**What "never written" costs and buys.** Because links 1–2 are Terasic's, we inherit their DDR +and pin configuration and their loader's policy, and we control exactly one interface: a FIT +named `u-boot.itb` on a FAT partition. That is a thin contract, which is both the good news and +the whole risk **[V, `de25-boot-chain.md` §4]**. It also means there is no field-update path for +the flash and none is needed: recovery from a bad *card* is writing a new card; recovery from a +bad *flash* (which nothing we ship can cause) is a PC with Quartus and the on-board USB-Blaster +**[V, vendor guide]**. The build enforces the rule mechanically: the DE25 U-Boot build fails if +any flash-writing driver, command, or environment location is compiled in +(`external.mk`, `MISTER_UBOOT_DE25_QSPI_AUDIT`) **[V]**. + +--- + +## 3. The card + +Today's card (interim, chosen so the first boot has the fewest moving parts) **[V, `de25-sdcard.md`]**: + +| Partition | Type | Holds | +|---|---|---| +| p1, 256 MiB | FAT32 `DE25BOOT` | `u-boot.itb`, `Image`, the `.dtb`, `extlinux/extlinux.conf` | +| p2 | ext4 | the Buildroot root filesystem, written directly | + +Target card (decided, [ADR 0029](decisions/0029-de25-implementation-path.md) D11): p1 stays FAT +with the boot files; **p2 becomes the exFAT data partition, the `/media/fat` equivalent, holding +`linux/linux.img`**, loop-mounted as root by the same two-stage initramfs the DE10 uses. That +initramfs already builds for aarch64 and passes its QEMU tests (`scripts/test-initramfs.sh +--board de25nano`) **[V]**. The switch is deliberately deferred until a board has answered the +loader, DTS, and SD-controller questions with the simpler card. + +`make de25` builds the whole thing into `output-de25/images/sdcard-de25.img`, and a fail-closed +checker rejects any image that looks like DE10 layout lore transplanted (a `0xA2` partition, a +`uboot.img`) **[V]**. + +--- + +## 4. Where things stand + +| Piece | State | +|---|---| +| Toolchain, kernel, DTS, root filesystem, card image | build green from one defconfig; the DTS is documented node by node against Terasic's, Altera's, and mainline's trees ([`de25-dts-rationale.md`](de25-dts-rationale.md)) | +| U-Boot + TF-A | built from mainline, pinned by Buildroot; FIT layout matches the factory loader's contract; every flash-writing path compiled out and asserted at build time ([`de25-uboot.md`](de25-uboot.md)) | +| BL31 console | moved to the header UART (a one-line carried patch, matching what Terasic ships), so secure-firmware messages and crashes are visible at bring-up instead of going to an unwired port | +| Reproducibility | the FIT and BL31 are byte-identical across three clean builds on this machine [V]; the only difference against the incremental tree was the toolchain's git-describe banner | +| Stage-1 initramfs (two-stage card) | built for aarch64, QEMU-tested [V] | +| Hardware | none yet; first-boot serial checklist written ([`de25-sdcard.md`](de25-sdcard.md) §6, [`de25-uboot.md`](de25-uboot.md) §11) | + +The pre-hardware task list and its history are [`de25-nano-tasks.md`](de25-nano-tasks.md); the +bootloader desk plan is [`uboot-tasks.md`](uboot-tasks.md) (DU-series). + +--- + +## 5. Blockers and unknowns, ranked + +### 5.1 Closed at the desk + +- **"Will the factory loader reject our unsigned FIT?"** — No. Its config verifies hashes only, + its device tree carries no signing key, and Terasic's own shipped `u-boot.itb` (extracted from + the factory SD image) is crc32-only with no signature and the same three-image layout and load + addresses as ours **[V, `de25-uboot.md` §12]**. This was the sharpest brick-adjacent risk on the + list ([`de25-boot-chain.md`](de25-boot-chain.md) §7 row 6) and it is now a structural match. +- **Where the environment lives.** On the FAT partition only; the flash-resident fallback that + mainline compiles in by default is removed and asserted absent **[V]**. +- **Console UART, memory node, SD controller node, SMMU posture** — all resolved by + cross-checking three vendor trees ([`de25-dts-rationale.md`](de25-dts-rationale.md)). + +### 5.2 Open, needs the board (in the order the first boot will hit them) + +1. **The version pairing.** Terasic ships U-Boot 2025.01 + TF-A 2.12; we ship 2026.07 + v2.15.0 + under Terasic's 2025.01 SPL. Nobody has run that combination. The SPL→BL31→U-Boot interfaces + are stable and generic, but this is the one thing only a serial log can confirm **[U]**. +2. **SD controller timing.** We run the controller at default speed with Intel dev-kit PHY + values; Terasic's own board support uses the same PHY values (and enables faster modes). If + `Retrieving file: /Image` stalls, this is the first knob ([`de25-uboot.md`](de25-uboot.md) §5.1) **[U]**. +3. **DRAM size.** Declared as 1 GiB in our device tree rather than read from the loader's + handoff, on purpose; the loader's `DDR:` lines on the console settle it **[U]**. +4. **The MAC address.** The HPS MAC is not fused and is random on every boot; first-boot + provisioning (as the DE10 does through `u-boot.txt`) is needed before networking is + deterministic. Learned from another DE25-Nano port **[V there, U here]**. + +### 5.3 Open, and project-shaping: programming the fabric from Linux + +**Yes, the fabric can be programmed from the HPS — but not the way the DE10 does it, and the +MiSTer framework will need a new back end for it.** On the DE10, Main_MiSTer writes a +memory-mapped FPGA manager directly. On Agilex 5 there is no such device: Linux hands the +bitstream to BL31 with an SMC call, the SDM rewrites the fabric, and the only userspace entry +point is applying a device-tree overlay against an FPGA region **[V, `de25-fpga-reconfig.md`]**. +The mainline kernel has the drivers but no Agilex 5 device-tree nodes for them; ours adds them +**[V]**. + +What is known from another DE25-Nano port that runs this path on real hardware: + +- **Unsigned, locally compiled bitstreams are accepted** by the factory SDM: bitstream + authentication is not enforced on shipped boards **[V there]**. This removes the scenario where + community cores would need a signing infrastructure. +- **A full reconfiguration takes about 3 s of SDM time, roughly 8 s end to end**, and repeated + switching works only with settle windows before and after — discovered the hard way + **[V there, U for us]**. MiSTer switches cores dozens of times a session, so this is the number + to beat and the behaviour to harden ([`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) §6). +- **Do not load cores from U-Boot.** `fpga load` from the bootloader wedged the SDM on that + port and was retired; load from Linux **[V there]**. +- **A fabric-unconfigured ("HPS-first") flash image is fragile**: it produced a fatal bus error + on warm reboots there. A flash image that configures a full design at every reset was stable. + Our posture keeps Terasic's factory (fully configured) image in flash, which is the stable + shape **[V there]**. + +What is still unknown for *our* model specifically: + +- **Whether a core compiled by us can be loaded against Terasic's factory phase-1 bitstream.** + Altera's doctrine for split configuration is that both halves come from the same compilation. + If that holds, MiSTer-style core switching on an untouched factory flash is not possible, and + the realistic model becomes what that other port converged on: a **one-time, bench flash of a + MiSTer "golden" image** (menu core plus HPS handoff) over JTAG at install time — a PC step users + do once, never an update-channel operation — after which cores switch from Linux + ([`de25-boot-chain.md`](de25-boot-chain.md) §7 row 15, §4 posture 2) **[U]**. +- **HPS↔FPGA memory sharing.** The DE10's shared framebuffer rides bridges Linux can see and + control; Agilex 5 exposes no bridge devices to Linux at all, and the aperture semantics for a + MiSTer-style frame buffer are documented weakly ([`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) §7) **[U]**. +- **Everything above the OS.** Main_MiSTer on aarch64 (or 32-bit under compat), its core-loading + back end, the fabric-side `sys/` framework, the HDMI and SDRAM IP — none of that exists for + this board, none of it is this repo's to build, and it gates "MiSTer parity" entirely + ([`de25-nano-plan.md`](de25-nano-plan.md) §2, layers L0–L2). What this repo delivers first is + the **bare developer OS** ([ADR 0027](decisions/0027-de25-nano-multi-board-readiness.md)): + the card, the boot chain, a kernel with the reconfiguration path wired, serial, network — the + platform someone would port the framework onto. + +--- + +## 6. What the first board session looks like + +1. Write today's card, attach the serial cable, watch for the SPL's `DDR:` lines (link 2), then + the `U-Boot 2026.07` banner (link 4 is proven the moment it prints), then extlinux loading + `Image` (link 5), then a login prompt (link 6). Checklist: [`de25-sdcard.md`](de25-sdcard.md) §6. +2. If the kernel load stalls, lift the SD controller from default to high speed + ([`de25-uboot.md`](de25-uboot.md) §5.1). +3. Switch the card to the two-stage exFAT layout (§3). +4. Apply a device-tree overlay carrying a bitstream and time it — the first fabric + reconfiguration from our Linux, and the measurement the core-switching question needs + ([`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) §11). +5. Only then decide the golden-image question (§5.3), with numbers. + +--- + +## 7. Map of the detailed documents + +| Document | Read it for | +|---|---| +| [`de25-boot-chain.md`](de25-boot-chain.md) | the chain link by link, the flash-writing rules, the brick/strand inventory, the resolved questions | +| [`de25-uboot.md`](de25-uboot.md) | the bootloader build, the FIT contract, the flash audit, first-boot serial expectations | +| [`de25-sdcard.md`](de25-sdcard.md) | card layout, building and writing it, first-boot checklist | +| [`de25-dts-rationale.md`](de25-dts-rationale.md) | every device-tree node and why | +| [`de25-kernel-config.md`](de25-kernel-config.md) | the kernel configuration and its diet | +| [`de25-fpga-reconfig.md`](de25-fpga-reconfig.md) | how the fabric is reprogrammed on Agilex 5, latency, the open questions | +| [`de25-implementation-path.md`](de25-implementation-path.md) | the decisions behind the current shape | +| [`de25-reference-implementation.md`](de25-reference-implementation.md) | what was learned from another port's working board | +| [`de25-nano-plan.md`](de25-nano-plan.md) / [`de25-nano-tasks.md`](de25-nano-tasks.md) | the readiness plan and the task history | +| [`de25-readiness-ledger.md`](de25-readiness-ledger.md) | every place the DE10's assumptions are baked into scripts and CI | +| [ADR 0027](decisions/0027-de25-nano-multi-board-readiness.md), [ADR 0029](decisions/0029-de25-implementation-path.md) | the governing decisions | diff --git a/docs/de25-nano-tasks.md b/docs/de25-nano-tasks.md index 83a0d838..b26e06f9 100644 --- a/docs/de25-nano-tasks.md +++ b/docs/de25-nano-tasks.md @@ -302,7 +302,7 @@ 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 +A borrowed 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 @@ -313,7 +313,7 @@ so a boot failure there would tell us nothing about our image. 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 +- **Doing this on a borrowed 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 — @@ -339,8 +339,9 @@ No brick-class or boot-blocking finding. Verified in the built artefacts, not th command or driver in U-Boot proper; BL31 issues no QSPI/RSU command at boot; the kernel has no MTD/spi-nor/RSU driver and its DTB has no flash node; the FIT is unsigned-crc32 at the addresses the factory SPL expects; nothing writes anything a power cycle does not clear. First-boot -expectations worth knowing: BL31 prints on UART0, so **no `NOTICE: BL31` lines on the header -UART** is normal; capture the SPL's `DDR:` lines (the only real DRAM-size measurement); if +expectations worth knowing: ~~BL31 prints on UART0, so **no `NOTICE: BL31` lines on the header +UART** is normal~~ (superseded 2026-09-14: the carried TF-A console patch routes BL31 to the +header UART, so its banner is now expected — `de25-uboot.md` §11); capture the SPL's `DDR:` lines (the only real DRAM-size measurement); if `Retrieving file: /Image` stalls, the SD PHY timing in `uboot-dts/` is the first knob (`de25-uboot.md` §5.1). @@ -354,8 +355,16 @@ UART** is normal; capture the SPL's `DDR:` lines (the only real DRAM-size measur registry, so an rt bump replaces the 7.2.y hash line rather than adding one. **This happened on 2026-09-02** (rt 7.2.2 -> 7.2.3): the DE25 pin was moved to 7.2.3 in the same series of commits and the series/config were re-verified there (34/34 patches at `-F0`, 460 symbols 0 dropped, - `docs/de25-kernel-config.md` §8). Still needs its own Renovate manager, or a hash-sync rule that - keeps every line a fragment still pins, so the next bump is not manual. + `docs/de25-kernel-config.md` §8). ~~Still needs its own Renovate manager, or a hash-sync rule that + keeps every line a fragment still pins, so the next bump is not manual.~~ **Done 2026-09-14:** + the DE25 pin (`BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE`, now **7.2.5**) is matched by the same + Renovate manager as the DE10's RT pin (`renovate.json`, depName `kernel-rt-7.2`), and + `board/mister/de25nano/patches/linux/linux.hash` is a symlink to `package/linux-rt/linux-rt.hash`, + so one PR moves both and the `rt` hash-sync case covers both. Found because `make de25` had been + failing closed since the RT bump to 7.2.5 (no 7.2.3 hash line) and, separately, since ADR 0030 + (`package/linux-rt` registered a rule on the shared `linux.config` even when disabled — fixed in + `linux-rt.mk`). At 7.2.5 all 34 DE25 kernel patches apply with offsets and no fuzz; the full + `make de25` is green and `check-sdcard-de25.sh` passes. - ~~DE25 selects no `linux-firmware`~~ **Decided 2026-09-03 (ADR 0029 D12):** mirror the DE10 set via a shared `image-common` fragment (PR in flight); seccomp stays off as on the DE10. - Patch 0002 (MiSTer audio) still excluded; `openssh` will need `_SANDBOX` off when added. @@ -381,6 +390,126 @@ run. Recorded in ADR 0002 §8b, `docs/rt-beta-kernel.md` §6, the DE25 patch REA |---|---|---| | 0031 re-anchor for 7.x | `linux-patches-beta/0031-exfat-samsung-symlinks.patch` is a real file again (the fifth re-anchor; the shared 6.18 patch untouched); `de25nano/linux-patches/0031` symlink → the beta copy; `series` note updated | `exfat_symlink_write_target()`: clusters via `exfat_map_cluster()` (7.x signature) under `s_lock`, sectors via buffer heads, `sync_blockdev_range()`, then `valid_size`/`zeroed_size`/`i_size`. Applies at `-F0` to pristine 7.2.3 (12/12); arm compile clean with `output-rt`'s `.config`; **aarch64 leg 8/8** from a fresh source tree (4 m 04 s). | +## Wave 4 — 2026-09-14 (pre-hardware) — U-Boot desk work + +The DE25's remaining U-Boot desk work runs as the DU-series of +[`docs/uboot-tasks.md`](uboot-tasks.md) (wave A landed as `933a2d2`). This section logs the DE25 +half of wave A (DU1, DU2, DU6, verbatim returns in the wave-A results file, outside the repo) and +the **DU7 boot-path re-review** — the wave-2 `fable` checklist above, re-run against the +`BR2_TARGET_UBOOT_LATEST_VERSION`-pinned artifact. No board, no rebuild: every claim is +**[V]** (observed, where) or **[U]** (not observed, missing input named). The DU2 hook's +wording of "verified" is a build-time assertion, not a boot. + +### Wave A — DE25 results (DU1, DU2, DU6) + +| Task | Deliverable | Result | +|---|---|---| +| DU1 — drop the redundant U-Boot pin | `configs/mister_de25nano_defconfig` → `BR2_TARGET_UBOOT_LATEST_VERSION=y`; `board/mister/de25nano/patches/uboot/uboot.hash` removed; `de25-uboot.md` §2 row rewritten | `u-boot.itb` byte-identical before and after the switch (`uboot-dirclean uboot` each side): 725,568 B, `sha256 f4e5c924dc20b51b2347dfd5786f7de23613ebfd445bd80009fcb19be6b1963e` **[V, DU1 + its verifier's independent clean-tree rebuild]**; after the board-local hash file went, Buildroot's own `boot/uboot/uboot.hash` line (`78e8bfc3…`) satisfied `BR2_DOWNLOAD_FORCE_CHECK_HASHES` **[V, build log]**; defconfig canonical by `savedefconfig` diff (LATEST_VERSION and `BUILD_SYSTEM_KCONFIG` are now defaults and drop out) **[V]**. `scripts/check-defconfigs.sh` itself was **not** run (shared-dir rule during the fan-out); its five assertions were replicated in scratch by the verifier **[U as the literal Done-when]**. Verifier also found the FIT timestamp moved with the Buildroot bump (`SOURCE_DATE_EPOCH` is `BR2_VERSION_EPOCH`), so `de25-uboot.md` §6.1's listing is stale — confirmed below. Doc drift still owed: `docs/buildroot-config.md` §6.9's "the pin has not moved yet" paragraph, `docs/uboot-mainline-port.md` §3.6's reference to the deleted hash file, `de25-uboot.md` §3's file-table row. | +| DU2 — §7 as a build assertion | `external.mk` `MISTER_UBOOT_DE25_QSPI_AUDIT` (`UBOOT_POST_BUILD_HOOKS`, guarded on `BR2_TARGET_UBOOT_BOARD_DEFCONFIG=socfpga_agilex5`); `scripts/ci-tests.sh` "DE25-Nano — QSPI-write audit, Linux side" section | Three checks per build: the **fragment text**, the **resolved `.config`** (every §7 symbol absent or unset, `ENV_IS_IN_FAT=y`), and `strings u-boot.itb` (eleven erase/write/probe verbs, one pinned exemption). Round 1 failed verification on three counts, all fixed: the literal Done-when (`CONFIG_ENV_IS_IN_UBI=y` in the fragment) was vacuous because kconfig drops the request (`depends on MTD_UBI`/`CMD_UBI`) — hence the fragment-text check, which now fails it; the `linux_qspi_enable` carve-out was widened to "any line starting `linux_qspi_enable=`" and is now pinned to **one line, one verb, exact head**; and the `de25-boot-chain.md` §5 bullet was *not* flipped to [V] but to "partially enforced" (the board-identity assertion in the updater still has no code). Positive and negative builds are in the DU2 logs (`build-restore.log:170` PASS; `negative-test-run.log:623-630` FAILED naming both docs, `Error 1`/`Error 2`) **[V]**. | +| DU6 — TF-A tag signature | `board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash` header (sha256 line byte-identical) | Round 1 concluded "no reachable channel, TOFU" and cited a WKD URL whose hu-part was **wrong** (fabricated); the verifier found the key itself. Fix round: key fetched from `https://github.com/odeprez.gpg`, `git tag -v v2.15.0` → *Good signature from "Olivier Deprez "*, RSA-4096 `5D6F 8960 43AD EFDF 7B76 BFAA 89C0 8CFD B867 3E0C`, in both the Buildroot download cache checkout and a fresh mirror clone **[V]**; GitHub reports the tag `verified: true`. Trust anchor stated honestly: the tagger's self-published GitHub key — **not** a project keyring, keyserver or WKD hit (both keyservers re-confirmed 404; the corrected WKD URLs redirect and return nothing) — so "the `odeprez` account is the real Olivier Deprez" is **[U]**. Bonus: the hashed `-git4` tarball is the signed tag's tree bar four uninitialised `contrib/` submodule gitlinks **[V, `diff -r`]**. Doc drift owed: `de25-uboot.md` §2's TF-A row and §12's "signature is authentic **[U]**" row still say unverifiable. | + +(DU3, wave B, has since landed in `de25-uboot.md` §5b: the FIT is byte-stable across clean trees +and moves only with the toolchain's `git describe` banner. Not part of this pass.) + +### DU7 — the wave-2 boot-path checklist, re-run against `f4e5c924…` + +**Artifact identity.** `output-de25/images/u-boot.itb` 725,568 B +`sha256 f4e5c924dc20b51b2347dfd5786f7de23613ebfd445bd80009fcb19be6b1963e`, byte-identical to +`build/uboot-2026.07/u-boot.itb`; produced by the build in the DU2 restore log (`>>> uboot 2026.07 +Building` → hook PASS at `:170` → `Installing to images directory`; `.stamp_built` 13:05:44.75, +the file 13:05:44.69) **[V]**. `images/bl31.bin` 53,304 B +`sha256 2052e4c9a62c1a2a947ee20886c6419eb5f802a1338bd12e22a99c916cc0250c`, identical to the TF-A +tree's `build/agilex5/release/bl31.bin` **[V]**. Resolved config +`build/uboot-2026.07/.config` `sha256 ce84f68e…`. + +| Wave-2 claim | This pass | | +|---|---|---| +| No QSPI command or driver in U-Boot proper | **Resolved `.config`:** the hook's §7 regex (`^CONFIG_(ENV_IS_IN_UBI\|…\|BLOBLIST)=`) has zero hits; `# CONFIG_CADENCE_QSPI is not set`, `# CONFIG_MTD is not set`, `# CONFIG_CMD_UBI is not set`, `# CONFIG_BLOBLIST is not set`; `ENV_IS_IN_UBI`, `CMD_SF`, `SPI_FLASH*`, `DM_SPI_FLASH`, `MTD_UBI` have no line at all; `CONFIG_ENV_IS_IN_FAT=y`, `mmc` `0:1` `uboot.env`. **Linked binary** (`nm` on the ELF's linker lists): 104 commands, none named `sf`/`ubi`/`ubifs`/`mtd`/`mtdparts`/`nand`/`rsu`; 43 DM drivers, none matching `qspi`/`spi_flash`/`mtd`/`nand`; **the only env driver is `fat`**; the only symbol containing `qspi` is `cm_get_qspi_controller_clk_hz` — a clock-manager register read that `arch_misc_init()` (`misc_soc64.c:106`) folds into the `qspi_clock` env string. `strings` of the FIT has **0** lines equal to any of those command names. `sspi` (`CMD_SPI`) is present, but the only SPI bus driver is `dw_spi` (`spi_generic_drv` on top): the Cadence controller behind the SDM has no driver in the binary to be reached through. | **[V]** | +| FIT is unsigned crc32 at the factory SPL's addresses | `dumpimage -l` (the build's own `tools/dumpimage`, 2026.07 — `host/bin/dumpimage` does not exist because the tree was rebuilt through `uboot` only): `uboot` Standalone Program, AArch64, **load `0x80200000`**, 647,448 B, crc32 `0559fc8a`; `atf` Firmware, OS ARM Trusted Firmware, **load `0x80000000`**, 53,304 B, crc32 `efc1cf2a` (`entry = <0x80000000>` in the decompiled FIT); `fdt-0` Flat Device Tree, description **`socfpga_agilex5_de25nano`**, 23,584 B, crc32 `64e75874`; default configuration **`board-0`**, `firmware = "atf"`, `loadables = "uboot"`, `fdt = "fdt-0"`, **`Sign algo: crc32:dev`, `Sign value: unavailable`**; `dtc -I dtb -O dts` → `grep -icE 'rsa\|required\|sha[0-9]'` = **0**. Every §6.2 contract term holds. **Drift from §6.1's listing:** `Created:` is now `Fri Sep 4 10:16:40 2026` (Buildroot 2026.08's epoch, not 2026.05.2's `Aug 23`) and the `uboot`/`fdt-0` payloads are 647,448/23,584 B (were 654,016/23,176) — a stale listing, not a changed contract. Address map recomputed: BL31 `0x8000_0000`–`0x8000_D038`; U-Boot `0x8020_0000`–`0x8029_E118`; FIT staging `0x8200_0000`+`0xB1240`; no overlap, `BL31_LIMIT` = staging base as before. | **[V]** | +| BL31 issues no QSPI/RSU command at boot | `bl31_platform_setup()` (`plat/intel/soc/agilex5/bl31_plat_setup.c:166-191`) is the delay timer, the GICv3 init, `mailbox_init()` (`SIP_SVC_V3`) and `mailbox_hps_stage_notify(SSBL)`. The one QSPI string in `bl31.bin` (`MBOX: 0x%x: QSPI address not 4K aligned`) is `socfpga_sip_svc.c:1242`, inside an SMC handler. `mailbox_rsu_status` is linked, but its only callers are the SMC RSU-status/DCMF handlers; `mailbox_rsu_update` runs from the PSCI reset path **only** if `intel_rsu_update_address` was set by an `RSU_UPDATE` SMC first; `ros_qspi_get_ssbl_offset` is BL2-only and no `ros_` symbol is in `bl31.elf`. | **[V]** | +| Kernel has no MTD/spi-nor/RSU driver; DTB has no flash node | **Not re-run.** `output-de25/` currently holds no `Image`, no `.dtb` and no `linux-7.x` build directory (DU1/DU2 rebuilt it through `uboot` only; `images/` is `u-boot.itb` + `bl31.bin`). Wave 2's [V] stands for the 2026-09-02 kernel; the next `make de25` should repeat the check (`board/mister/de25nano/linux.config` has not changed since wave 3, but that is a config, not a binary). | **[U]** — no kernel in the tree | +| Nothing writes anything a power cycle does not clear | The only env driver is `fat` on our SD card (`mmc 0:1`, `uboot.env`) — not flash. `objdump`: `env_save` has exactly **one** caller, `do_env_save` (the interactive `saveenv`); `env_fat_save` is reached from nowhere else. The built default env is `bootcmd=run distro_bootcmd`, `boot_targets=mmc0 `, `bootcmd_mmc0=devnum=0; run mmc_boot`; `bootcmd_qspi`/`bootcmd_nand` absent; the string `saveenv` occurs once, as the command name beside its help text. `# CONFIG_EFI_LOADER is not set` (no `ubootefi.var` on the card), `# CONFIG_BOOTCOUNT_LIMIT is not set`, `# CONFIG_ENV_OVERWRITE is not set`. Precisely: nothing writes *unless a person types `saveenv`*, and then to the card's FAT, which a re-`dd` clears. | **[V]** | + +### The three additions DU7 asked for + +**1. Does the DU2 hook fire on the DE25 tree and not the DE10's?** Yes, by `make` evaluation +with an **absolute** `O=` (see the finding below for why that matters), nothing built: + +``` +$ make O=/mnt/source/Buildroot_MiSTer/output-de25 printvars VARS='UBOOT_%_HOOKS MISTER_UBOOT_% UBOOT_KCONFIG_FRAGMENT_FILES BR2_TARGET_UBOOT_BOARD_DEFCONFIG' +BR2_TARGET_UBOOT_BOARD_DEFCONFIG="socfpga_agilex5" +MISTER_UBOOT_DE25_QSPI_AUDIT= @set -eu; cfg='./.config'; itb='./u-boot.itb'; frags='…/board/mister/de25nano/uboot.fragment'; … +UBOOT_KCONFIG_FRAGMENT_FILES=/mnt/source/Buildroot_MiSTer/board/mister/de25nano/uboot.fragment +UBOOT_POST_BUILD_HOOKS=MISTER_UBOOT_DE25_QSPI_AUDIT +$ make O=/mnt/source/Buildroot_MiSTer/output printvars VARS='…same…' +BR2_TARGET_UBOOT_BOARD_DEFCONFIG="socfpga_de10_nano" +MISTER_UBOOT_DE10_CONFIG_AUDIT= @set -eu; … ← U3's hook, working tree, not the DE25 one +UBOOT_KCONFIG_FRAGMENT_FILES=/mnt/source/Buildroot_MiSTer/board/mister/de10nano/uboot.fragment +UBOOT_POST_BUILD_HOOKS=MISTER_UBOOT_DE10_CONFIG_AUDIT +``` + +No `MISTER_UBOOT_DE25_*` variable exists on the DE10 tree and no `MISTER_UBOOT_DE10_*` on the DE25 +tree **[V]**. That the hook *executes* inside the stamp recipe, not merely that it is wired, is +the DU2 restore log (PASS line between `Building` and `Installing`), and the current `external.mk` +body is the one that ran there (`git diff HEAD -- external.mk` is a single appended hunk — the +DE10 block; the DE25 block is as committed) **[V]**. + +**Finding (method, not product) — a relative `O=` evaluates an empty tree.** The wrapper +`Makefile`'s `%:` rule forwards `O=$(O)` verbatim into `make -C work/buildroot`, and Buildroot +canonicalises `O` against *its own* directory, so the documented form `make O=output-de25 …` +(`Makefile:9`, `:82`) resolves to `work/buildroot/output-de25` — which exists, is empty, and has +no `.config` (`make O=output-de25 printvars VARS=O` → `O=/mnt/source/Buildroot_MiSTer/work/buildroot/output-de25`; +`BR2_DEFCONFIG` prints nothing). Every `printvars` claim gathered with a relative `O` — the +"[V, evaluated 2026-09-14]" in `external.mk`'s DE25 comment, the DU2 verifier's DE10-inertness +line, and by the timestamps (14:18) the U3 agent's own check — was therefore **vacuous: it prints +nothing for *any* variable on either tree.** With an absolute `O` the real answer agrees on the +point that matters (no DE25 hook on the DE10), but the comment's literal text ("prints neither +`UBOOT_POST_BUILD_HOOKS` nor any `MISTER_UBOOT_*`") is now false on two counts. `make de25` and +`make all` pass an absolute `O` and are unaffected. Owed: fix the wrapper (`$(abspath $(O))`) or +the docs, and re-word that comment — outside DU7's file scope. The empty +`work/buildroot/output{,-de25}/` directories are the footprint; left in place. + +**2. Is the `linux_qspi_enable` carve-out exactly one line with one verb?** Yes: +`strings images/u-boot.itb | grep -E ''` returns **one** line (4624, +`linux_qspi_enable=if sf probe; then echo Enabling QSPI at Linux DTB...;fdt addr ${fdt_addr}; …`) +and that line contains **one** verb (`sf probe`) **[V]**. The make-expanded hook body (lifted +from `printvars`, run with `sh -c` in the real build directory — a verbatim execution, not a +replica, and not a rebuild) prints `PASS -- fragment, resolved .config and u-boot.itb all clean`; +four mutated scratch copies of the inputs each fail on the intended check: the fragment plus +`CONFIG_ENV_IS_IN_UBI=y` (the literal DU2 Done-when) fails at check 1 naming the fragment and +both docs; `.config` plus `CONFIG_CMD_SF=y` fails at check 2; the FIT plus an appended +`bootcmd_qspi=… ubi part root` fails at check 3; the FIT plus a *second* line with the exempt +head fails at check 3 (`nallowed > 1`) **[V]**. Two honest limits: the strings check is a tripwire, +not the lock — `sf` is not a command in this binary, so any such string is inert, and the lock is +check 2's proof that `CADENCE_QSPI`/`CMD_SF` are absent; and on the SPL side +`CONFIG_SPL_SPI=y`, `CONFIG_SPL_SPI_FLASH_SUPPORT=y`, `CONFIG_SPL_SPI_FLASH_TINY=y` resolve **on** +(outside the hook's `^CONFIG_` anchor by design, and contrary to the fragment's comment at +lines 127-133 that the SPL block removes the stack), but `spl/u-boot-spl` links no +`cadence_qspi`/`spi_flash` symbol and `images/` holds only `u-boot.itb` and `bl31.bin` — our SPL +ships nowhere, as §4.5 already says **[V]**. Cosmetic; noted for DU4's fragment pass. + +**3. Does `scripts/ci-tests.sh`'s DE25 section behave on the real `output-de25/target`?** The +section (helpers `:141-169` + the section `:480-520`, extracted verbatim) against the real target — +a 7.7 MB skeleton from a `uboot`-only build, zero `fw_env.config`, no `fw_setenv`/`libubootenv` — +prints `PASS DE25 rootfs: no fw_env.config shipped -- nothing to audit`. Synthetic trees: a +`/dev/mtd0` line → `FAIL … de25-boot-chain.md section 7 row 11 …` with the offending line +echoed; a FAT-path file whose only `ubi` is in a `#` comment → PASS (the comment filter works); +no tree → SKIP. `shellcheck` and `bash -n` clean **[V]**. Limit: the real target exercised only the +trivial branch; the check has not yet seen a fully populated DE25 rootfs **[U until the next +`make de25`]**. + +### Verdict and drift list + +No brick-class or boot-blocking finding. Every wave-2 U-Boot and BL31 claim re-verifies against +`f4e5c924…` / `2052e4c9…`; the two kernel-side legs are **[U]** for want of a kernel in the tree, +not for any contrary evidence; the DU2 hook fires where it should, executes where it should, and +its carve-out is as narrow as its comment says. One method finding (relative `O=`) invalidates the +*evidence* behind an `external.mk` comment without changing its conclusion. Documentation now +behind the artifacts, for whoever next edits each file: `de25-uboot.md` §6.1 (timestamp, two +payload sizes), §2 TF-A row and §12 TF-A row (DU6 verified the signature), §3 file table +(`uboot.hash` gone); `external.mk` DE25 comment (relative-`O` evidence; the DE10 tree now has a +hook); `docs/buildroot-config.md` §6.9; `docs/uboot-mainline-port.md` §3.6. + ## 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 diff --git a/docs/de25-reference-implementation.md b/docs/de25-reference-implementation.md index b53ac869..d9736232 100644 --- a/docs/de25-reference-implementation.md +++ b/docs/de25-reference-implementation.md @@ -29,18 +29,17 @@ this document was to feed now lives in decision-bearing document; this file remains the **record of what the reference repos contain**. **Nothing here is adopted.** The owner's standing instruction governs: this is reference material, -and every divergence from our plan is his decision, not a recommendation. Where a leg's wording +and every divergence from our plan is the owner's decision, not a recommendation. Where a leg's wording drifts toward advocacy, that is an artifact of the missing neutrality pass. -**Sources.** Two read-only reference repos, supplied by the owner 2026-08-21: -`/mnt/source/de25-linux` (kernel; `881d4404a`, tag `v2026.07.27-v618-kernel-port-f9-fb-fix-5`) -and `/mnt/source/de25-uboot-socfpga` (U-Boot **2025.01**, branch `de25-mister-exfat-boot`, -tag `v2026.07.13-uboot-hf-launcher`). Neither was executed, modified, or checked out. -Comparison baseline is our `output/build/linux-6.18.44`. +**Sources.** Two read-only reference repos, supplied by the owner 2026-08-21, together forming a +third-party DE25-Nano Linux port (PORT tree): a kernel tree (6.18.38 + vendor backports) and its +companion U-Boot fork (**2025.01** base, Terasic's vendor tree plus exFAT-in-SPL patches). Neither +was executed, modified, or checked out. Comparison baseline is our `output/build/linux-6.18.44`. -**What he achieved, stated fairly:** a DE25-Nano booting Linux to the MiSTer MENU, with +**What the port achieved, stated fairly:** a DE25-Nano booting Linux to the MiSTer MENU, with `fpga_manager` and `fpga_region` probing on real hardware — further than any other work we know -of. His own `SETUP.md` §9 records that core switching is **not** wired up on his current card +of. The port's own `SETUP.md` §9 records that core switching is **not** wired up on the port's current card layout, and the intended fast path is explicitly marked not end-to-end tested. --- @@ -54,7 +53,7 @@ Both come from the U-Boot/QSPI core-loading route — the route this project has ### (a) "Only a certain number of cores into firmware" -"Firmware" was literal: **QSPI**. His first working core-switch tier put each core into the SDM's +"Firmware" was literal: **QSPI**. The port's first working core-switch tier put each core into the SDM's configuration flash as an RSU application image and rebooted into it. Three independent caps stacked up: @@ -67,9 +66,9 @@ stacked up: 2. **A ~2 MiB ceiling on the Linux live path.** Not folklore: mainline `stratix10-soc.c:19-20` allocates `NUM_SVC_BUFS 4 × SVC_BUF_SIZE SZ_512K` = exactly **2 MiB in flight**, and `s10_ops_write()` cannot proceed past that without `SVC_STATUS_BUFFER_DONE` callbacks recycling - buffers **[V]**. His note attributes the stall to his own BL31 modification + buffers **[V]**. The port's note attributes the stall to the port's own BL31 modification (`FPGA_CONFIG_BUFFER_SIZE=16`) killing the reply that drives recycling **[U — the ATF source is - in neither repo]**. With 5 of his 8 cores under 2 MB and 3 over, "only some cores load" is + in neither repo]**. With 5 of the port's 8 cores under 2 MB and 3 over, "only some cores load" is literally what a user would have seen **[V]**. 3. **Only eight cores have ever been rebuilt for A5EB013** (`CORES.manifest`) — cores are device-specific and must be recompiled **[V]**. @@ -81,25 +80,25 @@ stacked up: ### (b) "Very slow to load cores" -Every path he actually shipped is dominated by something that is not reconfiguration: +Every path the port actually shipped is dominated by something that is not reconfiguration: | Path | Dominant cost | Evidence | |---|---|---| -| RSU / QSPI rewrite | `de25_fcs_stage_core.py` pushes the `.rpd` through the FCS→SDM mailbox in **4096-byte writes with a 0.05 s settle each**, re-asserting chip-select per write, then reads the slot back for an md5 verify in 4 KiB calls. For a 3.35 MB core that is **≥817 round-trips and ≥41 s of `sleep()` alone** before mailbox cost — then a reboot. His own script calls it "the ~minutes-long FCS rewrite". | `:65-70,95-107`; `de25_stage_core.sh:72` **[V]** | -| U-Boot launcher | No QSPI write, but a **warm reboot** — MiSTer's OSD string is `"Loading core... (board reboots ~30s)"`; his hook comments say ~35 s. On this SoC every reset is a full SDM configuration cycle with no warm/cold distinction. | **[V]** | +| RSU / QSPI rewrite | `de25_fcs_stage_core.py` pushes the `.rpd` through the FCS→SDM mailbox in **4096-byte writes with a 0.05 s settle each**, re-asserting chip-select per write, then reads the slot back for an md5 verify in 4 KiB calls. For a 3.35 MB core that is **≥817 round-trips and ≥41 s of `sleep()` alone** before mailbox cost — then a reboot. The port's own script calls it "the ~minutes-long FCS rewrite". | `:65-70,95-107`; `de25_stage_core.sh:72` **[V]** | +| U-Boot launcher | No QSPI write, but a **warm reboot** — MiSTer's OSD string is `"Loading core... (board reboots ~30s)"`; the port's hook comments say ~35 s. On this SoC every reset is a full SDM configuration cycle with no warm/cold distinction. | **[V]** | | Kernel `fpga_manager` + DT overlay (the intended fast path) | "~8 s total", "NES 3,325,952 B live-switches in ~3 s" | comment **[V]**, but see below | -**The fast path is the one he never proved.** Commit `881d4404a` (2026-08-01) records that the +**The fast path is the one the port never proved.** The port's 2026-08-01 commit records that the script wrote to `/sys/kernel/config/device-tree/overlays/fullcore` — a configfs path that does not -exist in his mainline-based 6.18 tree — and states the replacement (the ~95-line misc driver over -`of_overlay_fdt_apply()`, `30d9c99a8`) **"has NOT yet been end-to-end tested"** **[V]**. MiSTer's +exist in the port's mainline-based 6.18 tree — and states the replacement (the ~95-line misc driver over +`of_overlay_fdt_apply()`, added in an earlier commit) **"has NOT yet been end-to-end tested"** **[V]**. MiSTer's Tier A/B binaries still target the same dead configfs paths **[V strings]**. ### Verdict for our D0.2 conclusion -His experience **neither confirms nor undermines** our low-to-moderate-confidence finding that -core switching is UX-viable. He has never measured Agilex 5 reconfiguration on a working Linux -overlay path. What he measured is QSPI rewriting (minutes) and reboots (~30–35 s). +The port's experience **neither confirms nor undermines** our low-to-moderate-confidence finding that +core switching is UX-viable. The port has never measured Agilex 5 reconfiguration on a working Linux +overlay path. What the port measured is QSPI rewriting (minutes) and reboots (~30–35 s). One genuinely intrinsic floor does appear, from two independent code paths: U-Boot's SDM driver sleeps `udelay(1000000)` before its first `RECONFIG_STATUS` poll and then polls at 1 s intervals @@ -109,192 +108,192 @@ silicon.** --- -## 2. What his work settles for us +## 2. What the port's work settles for us Our five open unknowns, as answered by two independent legs. Where the legs differ in emphasis, both readings are given — there was no synthesis pass to reconcile them. -### From LEG 1 — Device tree (de25-linux @ 881d4404a, tag v2026.07.27-v618-kernel-port-f9-fb-fix-5-g881d4404a) vs mainline linux-6.18.44 +### From LEG 1 — Device tree (PORT tree, kernel) vs mainline linux-6.18.44 **[V] U1 — Do mainline's intel,agilex-svc / intel,agilex-soc-fpga-mgr compatibles bind on Agilex 5?** -No. Mainline 6.18.44's match tables contain only stratix10 and agilex (gen1) strings, and the Agilex 5 DTS declares intel,agilex5-svc / intel,agilex5-soc-fpga-mgr. Two one-line of_device_id additions fix it, and BOTH are required (svc is built-in and matches at __init, so the mgr cannot be created until svc binds). Separately, mainline 6.18.44's socfpga_agilex5.dtsi instantiates no svc/fpga-mgr/fpga-region node at all, so the DTS content must be supplied too. He reports fpga0/region0 binding on real hardware after the patch. +No. Mainline 6.18.44's match tables contain only stratix10 and agilex (gen1) strings, and the Agilex 5 DTS declares intel,agilex5-svc / intel,agilex5-soc-fpga-mgr. Two one-line of_device_id additions fix it, and BOTH are required (svc is built-in and matches at __init, so the mgr cannot be created until svc binds). Separately, mainline 6.18.44's socfpga_agilex5.dtsi instantiates no svc/fpga-mgr/fpga-region node at all, so the DTS content must be supplied too. The port reports fpga0/region0 binding on real hardware after the patch. -*Source:* `linux-6.18.44 drivers/firmware/stratix10-svc.c:1133-1137, drivers/fpga/stratix10-soc.c:448-452; /mnt/source/de25-linux commit d1878a320; /mnt/source/de25-linux/.../socfpga_agilex5.dtsi:213-243` +*Source:* `linux-6.18.44 drivers/firmware/stratix10-svc.c:1133-1137, drivers/fpga/stratix10-soc.c:448-452; the PORT tree commit; the PORT tree, .../socfpga_agilex5.dtsi:213-243` **[V] U2 — Is there a usable DT-overlay path for reconfiguration?** -Yes, but only via of_overlay_fdt_apply() from kernel code. The configfs device-tree path (/sys/kernel/config/device-tree/overlays/) does not exist upstream and never worked on his kernel — his switch script had been failing structurally on it for its whole history. Mainline fpga_manager sysfs is read-only (no writable firmware attribute), so he wrote a 95-line misc driver whose one write-only sysfs attribute re-applies a static .dtbo naming firmware-name=core.rbf under /fpga-region. The mechanism is HW-proven to reach the real reconfigure path; the one test attempt then failed at the SDM mailbox ('timeout waiting for RECONFIG_REQUEST') and wedged the board. This directly answers our fpga-reconfig §11 row 6 (carry OF_CONFIGFS vs write a board driver vs U-Boot-preload): OF_CONFIGFS is not an option, and he chose 'write a board driver'. +Yes, but only via of_overlay_fdt_apply() from kernel code. The configfs device-tree path (/sys/kernel/config/device-tree/overlays/) does not exist upstream and never worked on the port's kernel — the port's switch script had been failing structurally on it for its whole history. Mainline fpga_manager sysfs is read-only (no writable firmware attribute), so the port wrote a 95-line misc driver whose one write-only sysfs attribute re-applies a static .dtbo naming firmware-name=core.rbf under /fpga-region. The mechanism is HW-proven to reach the real reconfigure path; the one test attempt then failed at the SDM mailbox ('timeout waiting for RECONFIG_REQUEST') and wedged the board. This directly answers our fpga-reconfig §11 row 6 (carry OF_CONFIGFS vs write a board driver vs U-Boot-preload): OF_CONFIGFS is not an option, and the port chose 'write a board driver'. -*Source:* `/mnt/source/de25-linux commits 30d9c99a8 and 881d4404a; drivers/misc/de25_fpga_trigger.c; analysis/full_config/de25_full_core.dtso` +*Source:* `the PORT tree commits; drivers/misc/de25_fpga_trigger.c; analysis/full_config/de25_full_core.dtso` **[U] U3 — Does the factory FSBL boot a mainline-built u-boot.itb?** -Not answered, and this leg found no evidence either way. He flashes his own exFAT-aware SPL in QSPI (posture 2), so the factory-FSBL path is never exercised in his tree. Nothing in the kernel repo bears on it. Treat as still open. +Not answered, and this leg found no evidence either way. The port flashes the port's own exFAT-aware SPL in QSPI (posture 2), so the factory-FSBL path is never exercised in the port's tree. Nothing in the kernel repo bears on it. Treat as still open. -*Source:* `absence of evidence in /mnt/source/de25-linux (kernel repo only); his SETUP.md §3 boot chain` +*Source:* `absence of evidence in the PORT tree (kernel repo only); the port's SETUP.md §3 boot chain` **[U] U4 — Is bitstream authentication (VAB) enforced out of the box?** -Not settled by the DTS, but two adjacent facts: his dtsi instantiates the full Intel FCS subtree (fcs-hal 'intel,agilex5-soc-fcs-hal', fcs-crypto, fcs-config) and his overlay ships plain unsigned .rbf/.rpd payloads which the SDM accepts, with the only rejection documented being a wrong-recipe RSU magic (0xa9129446 vs the expected 0x95482962), not a signature failure. That is consistent with VAB not being enforced on this board, but it is inference, not a read of a fuse or a config bit. +Not settled by the DTS, but two adjacent facts: the port's dtsi instantiates the full Intel FCS subtree (fcs-hal 'intel,agilex5-soc-fcs-hal', fcs-crypto, fcs-config) and the port's overlay ships plain unsigned .rbf/.rpd payloads which the SDM accepts, with the only rejection documented being a wrong-recipe RSU magic (0xa9129446 vs the expected 0x95482962), not a signature failure. That is consistent with VAB not being enforced on this board, but it is inference, not a read of a fuse or a config bit. -*Source:* `/mnt/source/de25-linux/.../socfpga_agilex5.dtsi:232-253; board_overlay/usr/local/bin/de25_stage_core.sh:42-58` +*Source:* `the PORT tree, .../socfpga_agilex5.dtsi:232-253; board_overlay/usr/local/bin/de25_stage_core.sh:42-58` **[V] U5 — Real reconfiguration latency** -His numbers, from source comments rather than a log I read: a 3,325,952-byte NES core live-switches in ~3 s of config time, ~8 s end-to-end including a mandatory 3 s pre-quiesce settle and a mandatory 5 s post-config settle; the reboot fallback path is ~35 s. Both settle delays are HW-forced, not padding: trimming the pre-quiesce to 1 s reproducibly wedged the board on the NEXT switch, and any LWH2F access inside the post-config window hangs a CPU on the AXI bus with no timeout (RCU stall, warm reset comes back dark). So ~8 s, of which ~5 s is unavoidable dead time, is the honest UX figure for his design. +The port's numbers, from source comments rather than a log I read: a 3,325,952-byte NES core live-switches in ~3 s of config time, ~8 s end-to-end including a mandatory 3 s pre-quiesce settle and a mandatory 5 s post-config settle; the reboot fallback path is ~35 s. Both settle delays are HW-forced, not padding: trimming the pre-quiesce to 1 s reproducibly wedged the board on the NEXT switch, and any LWH2F access inside the post-config window hangs a CPU on the AXI bus with no timeout (RCU stall, warm reset comes back dark). So ~8 s, of which ~5 s is unavoidable dead time, is the honest UX figure for the port's design. -*Source:* `/mnt/source/de25-linux/board_overlay/usr/local/bin/de25_live_switch_core.sh:7-12, 81-93, 123-132; de25_launch_core.sh:8-18` +*Source:* `the PORT tree, board_overlay/usr/local/bin/de25_live_switch_core.sh:7-12, 81-93, 123-132; de25_launch_core.sh:8-18` **[V] Our fpga-reconfig §11 row 14 — Does the Agilex 5 svc node need iommus = <&smmu 10>?** -His does, and it also carries altr,smmu_enable_quirk on both the svc node and the fpga-mgr child. He supplies the smmu node himself (mainline 6.18.44 has none) and enables it from the board DTS. He did not test the no-iommus form, so 'needs' is not proven — but the working configuration is known. +The port's does, and it also carries altr,smmu_enable_quirk on both the svc node and the fpga-mgr child. The port supplies the smmu node itself (mainline 6.18.44 has none) and enables it from the board DTS. The port did not test the no-iommus form, so 'needs' is not proven — but the working configuration is known. -*Source:* `/mnt/source/de25-linux/.../socfpga_agilex5.dtsi:213-226, 461-471; socfpga_agilex5_de25_nano.dts:182-184` +*Source:* `the PORT tree, .../socfpga_agilex5.dtsi:213-226, 461-471; socfpga_agilex5_de25_nano.dts:182-184` **[V] DP-10's residual question (is the display pipeline HPS-reachable?)** -Corroborated as no. His DTS has no display, DRM, HDMI or I2C-transmitter node whatsoever; Linux's only display device is MiSTer_fb writing a DRAM region the fabric scaler reads. He also enables exactly one HPS I2C bus with no children, which means the DE10's ADV7513-on-/dev/i2c-0..2 discovery contract cannot work on this board. +Corroborated as no. The port's DTS has no display, DRM, HDMI or I2C-transmitter node whatsoever; Linux's only display device is MiSTer_fb writing a DRAM region the fabric scaler reads. The port also enables exactly one HPS I2C bus with no children, which means the DE10's ADV7513-on-/dev/i2c-0..2 discovery contract cannot work on this board. -*Source:* `full read of /mnt/source/de25-linux/.../socfpga_agilex5_de25_nano.dts; /mnt/source/Buildroot_MiSTer/docs/de25-fpga-reconfig.md:696-727` +*Source:* `full read of the PORT tree, .../socfpga_agilex5_de25_nano.dts; /mnt/source/Buildroot_MiSTer/docs/de25-fpga-reconfig.md:696-727` ### From Leg 2 — Kernel Deltas **[V] U1 — Do intel,agilex-svc/soc-fpga-mgr compatibles bind on Agilex 5?** -No — mainline's stratix10-svc.c/stratix10-soc.c need an added 'intel,agilex5-svc'/'intel,agilex5-soc-fpga-mgr' of_device_id entry each (his commit message: this gap exists in current mainline too). His DTS already declares those compatibles and a fpga-region node inherited unmodified from his kernel base — but our own linux-6.18.44 baseline lacks those same DTS nodes entirely, an unresolved discrepancy between two nominally-mainline-descended point releases. +No — mainline's stratix10-svc.c/stratix10-soc.c need an added 'intel,agilex5-svc'/'intel,agilex5-soc-fpga-mgr' of_device_id entry each (the port's commit message: this gap exists in current mainline too). The port's DTS already declares those compatibles and a fpga-region node inherited unmodified from the port's kernel base — but our own linux-6.18.44 baseline lacks those same DTS nodes entirely, an unresolved discrepancy between two nominally-mainline-descended point releases. -*Source:* `de25-linux commit d1878a320; both repos' socfpga_agilex5.dtsi` +*Source:* `the PORT tree commit; both repos' socfpga_agilex5.dtsi` **[V] U2 — Is there a usable DT-overlay reconfig path?** Yes, mechanism confirmed on real hardware: apply a DT overlay with a firmware-name property via of_overlay_fdt_apply(); of-fpga-region.c's own notifier drives the real reconfigure. The RPi-style OF-configfs path some earlier code assumed was never upstream and always failed. Reliability under real SDM mailbox timing is NOT yet demonstrated — one live test hit a 300ms RECONFIG_REQUEST timeout and wedged the board. -*Source:* `de25-linux commits 30d9c99a8, 881d4404a` +*Source:* `the PORT tree commits` **[U] U3 — Does the factory FSBL boot a mainline-built u-boot.itb?** -Not established — he replaced the SPL with his own exFAT-aware build and there is no evidence in SETUP.md or the repo that the stock factory FSBL was ever tested against any other u-boot.itb. +Not established — the port replaced the SPL with the port's own exFAT-aware build and there is no evidence in SETUP.md or the repo that the stock factory FSBL was ever tested against any other u-boot.itb. -*Source:* `/mnt/source/de25-linux/SETUP.md (absence of any such test)` +*Source:* `the PORT tree, SETUP.md (absence of any such test)` **[V] U4 — Is VAB (bitstream authentication) enforced out of the box?** No — CONFIG_SPL_FIT_SIGNATURE=y is set (FIT image signing) but CONFIG_SOCFPGA_SECURE_VAB_AUTH is not set anywhere in the defconfig, board header, or board directory. -*Source:* `/mnt/source/de25-uboot-socfpga/configs/socfpga_agilex5_de25_nano_defconfig and related files (grep, both symbols)` +*Source:* `the PORT tree's U-Boot fork, configs/socfpga_agilex5_de25_nano_defconfig and related files (grep, both symbols)` **[V] U5 — Real reconfiguration latency.** No clean success-path number obtained. Working path (reboot) measured at ~35s. Fast path (live DT-overlay) targets ~8s but is unverified end-to-end; the one real hardware attempt at it hit the hardcoded 300ms SDM RECONFIG_REQUEST timeout and wedged the board on resume — empirical (if limited, n=1, unhardened-driver) evidence that the timeout-tightness risk our own D0.2 dossier already flagged on paper is real on Agilex 5 silicon. -*Source:* `de25-linux commit 30d9c99a8; board_overlay/usr/local/bin/de25_live_switch_core.sh, de25_launch_core.sh` +*Source:* `the PORT tree commit; board_overlay/usr/local/bin/de25_live_switch_core.sh, de25_launch_core.sh` ### From Leg 3 — the two symptoms ("only a certain number of cores into firmware", "very slow to load cores"), mechanism-level explanation from the reference repos **[V] U1 — Do mainline's 'intel,agilex-svc' / 'intel,agilex-soc-fpga-mgr' compatibles bind on Agilex 5?** -Not as shipped, for two independent reasons, and he fixed both. (i) DT: mainline 6.18.44's socfpga_agilex5.dtsi instantiates no svc, fpga-mgr or fpga-region node at all — only the svcbuffer reserved-memory. He authored all three: a firmware/svc node with compatible 'intel,agilex5-svc' (method smc, memory-region svcbuffer, iommus <&smmu 10>, altr,smmu_enable_quirk, GIC_SPI 0), a child fpga-mgr 'intel,agilex5-soc-fpga-mgr', and a top-level fpga-region with fpga-mgr = <&fpga_mgr>. (ii) Drivers: neither stratix10-svc.c nor stratix10-soc.c matches an agilex5 string in 6.18.44 — his two-line patch adds one entry to each match table. His commit message records that both were required together: fixing only the fpga-mgr left the mailbox failing with "couldn't get service channel (fpga)", because the SVC node binds at __init before its children can be created. HW-confirmed on the board: /sys/class/fpga_manager/fpga0 and /sys/class/fpga_region/region0 exist and 'of-fpga-region fpga-region: FPGA Region probed' appears in dmesg — his words, 'the first time this has worked on this board'. Both match tables have no per-compatible .data, so the third entry is functionally free. NOTE: this is the fix an already-posted Altera series does upstream with 'intel,agilex-soc-fpga-mgr' as the declared DT fallback (our §3.1) — he did not use the fallback route, he added the strings. +Not as shipped, for two independent reasons, and the port fixed both. (i) DT: mainline 6.18.44's socfpga_agilex5.dtsi instantiates no svc, fpga-mgr or fpga-region node at all — only the svcbuffer reserved-memory. The port authored all three: a firmware/svc node with compatible 'intel,agilex5-svc' (method smc, memory-region svcbuffer, iommus <&smmu 10>, altr,smmu_enable_quirk, GIC_SPI 0), a child fpga-mgr 'intel,agilex5-soc-fpga-mgr', and a top-level fpga-region with fpga-mgr = <&fpga_mgr>. (ii) Drivers: neither stratix10-svc.c nor stratix10-soc.c matches an agilex5 string in 6.18.44 — the port's two-line patch adds one entry to each match table. The port's commit message records that both were required together: fixing only the fpga-mgr left the mailbox failing with "couldn't get service channel (fpga)", because the SVC node binds at __init before its children can be created. HW-confirmed on the board: /sys/class/fpga_manager/fpga0 and /sys/class/fpga_region/region0 exist and 'of-fpga-region fpga-region: FPGA Region probed' appears in dmesg — the port's words, 'the first time this has worked on this board'. Both match tables have no per-compatible .data, so the third entry is functionally free. NOTE: this is the fix an already-posted Altera series does upstream with 'intel,agilex-soc-fpga-mgr' as the declared DT fallback (our §3.1) — the port did not use the fallback route, the port added the strings. -*Source:* `/mnt/source/de25-linux commit d1878a320; arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi:213-244 (added in bf8e5c9c6); diff vs /mnt/source/Buildroot_MiSTer/output/build/linux-6.18.44/drivers/{firmware/stratix10-svc.c,fpga/stratix10-soc.c} and .../dts/intel/socfpga_agilex5.dtsi` +*Source:* `the PORT tree's commit; arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi:213-244 (added by an earlier PORT tree commit); diff vs /mnt/source/Buildroot_MiSTer/output/build/linux-6.18.44/drivers/{firmware/stratix10-svc.c,fpga/stratix10-soc.c} and .../dts/intel/socfpga_agilex5.dtsi` **[V] U2 — Is there a usable DT-overlay path for reconfiguration?** -Yes, and our §3.2 option table is now settled empirically rather than by inference. Option (a) is real and is what the vendor ships: Terasic's stock DE25 kernel config (6.12.11) has CONFIG_OF_CONFIGFS=y — so the /sys/kernel/config/device-tree/overlays/ workflow every Altera document describes works verbatim on the vendor kernel, and its provenance as a vendor carry is no longer [U]. Option (b) is real and now has a working reference implementation: his drivers/misc/de25_fpga_trigger.c, ~95 lines, request_firmware()s a static .dtbo and calls the mainline exported of_overlay_fdt_apply(); of-fpga-region's overlay notifier then calls fpga_region_program_fpga(). HW-tested to the point of 'fpga_manager fpga0: writing de25_live_switch_current.rbf to Stratix10 SOC FPGA Manager' in dmesg. Two caveats: his design re-applies a STATIC overlay naming a FIXED firmware path and stages the chosen core to that path first (avoiding rebuilding a DTB per switch), and his end-to-end integration is still untested. His confirmation that mainline fpga_manager class devices expose no writable 'firmware' attribute — only RO name/state/status — matches our §2.6 'there is no second door'. +Yes, and our §3.2 option table is now settled empirically rather than by inference. Option (a) is real and is what the vendor ships: Terasic's stock DE25 kernel config (6.12.11) has CONFIG_OF_CONFIGFS=y — so the /sys/kernel/config/device-tree/overlays/ workflow every Altera document describes works verbatim on the vendor kernel, and its provenance as a vendor carry is no longer [U]. Option (b) is real and now has a working reference implementation: the port's drivers/misc/de25_fpga_trigger.c, ~95 lines, request_firmware()s a static .dtbo and calls the mainline exported of_overlay_fdt_apply(); of-fpga-region's overlay notifier then calls fpga_region_program_fpga(). HW-tested to the point of 'fpga_manager fpga0: writing de25_live_switch_current.rbf to Stratix10 SOC FPGA Manager' in dmesg. Two caveats: the port's design re-applies a STATIC overlay naming a FIXED firmware path and stages the chosen core to that path first (avoiding rebuilding a DTB per switch), and the port's end-to-end integration is still untested. The port's confirmation that mainline fpga_manager class devices expose no writable 'firmware' attribute — only RO name/state/status — matches our §2.6 'there is no second door'. -*Source:* `/mnt/source/de25-linux/analysis/smc_bridge/board_kernel.config:2246; commits 30d9c99a8 and 881d4404a` +*Source:* `the PORT tree, analysis/smc_bridge/board_kernel.config:2246; commits` **[U] U3 — Does the factory FSBL boot a mainline-built u-boot.itb?** -NOT ANSWERED, and he is structurally unable to answer it: he replaced the SPL (his exFAT-aware SPL is inside his own .jic) precisely because the factory one cannot read exFAT. What his repo does settle is the baseline: the u-boot.itb he pulled off the board identifies as '2025.01-gd0f8813fd6bf' with vendor=terasic — Terasic shipped a build of this very tree at the 'add support for de25-nano' commit, confirming U-Boot 2025.01 + a board commit that is public. His own build carries the identical version string, so he never moved off 2025.01 either. Two facts sharpen our Q3: his defconfig sets CONFIG_SPL_FIT_SIGNATURE=y (as mainline's agilex5 defconfig does) yet he loads locally built, unsigned FITs — consistent with our finding that the factory SPL's control DTB requires no keys; and the SPL FIT load address contract is visible (CONFIG_SPL_LOAD_FIT_ADDRESS=0x82000000, CONFIG_SYS_SPI_U_BOOT_OFFS=0x04000000). +NOT ANSWERED, and the port is structurally unable to answer it: the port replaced the SPL (the port's exFAT-aware SPL is inside the port's own .jic) precisely because the factory one cannot read exFAT. What the port's repo does settle is the baseline: the u-boot.itb the port pulled off the board identifies as '2025.01-gd0f8813fd6bf' with vendor=terasic — Terasic shipped a build of this very tree at the 'add support for de25-nano' commit, confirming U-Boot 2025.01 + a board commit that is public. The port's own build carries the identical version string, so the port never moved off 2025.01 either. Two facts sharpen our Q3: the port's defconfig sets CONFIG_SPL_FIT_SIGNATURE=y (as mainline's agilex5 defconfig does) yet the port loads locally built, unsigned FITs — consistent with our finding that the factory SPL's control DTB requires no keys; and the SPL FIT load address contract is visible (CONFIG_SPL_LOAD_FIT_ADDRESS=0x82000000, CONFIG_SYS_SPI_U_BOOT_OFFS=0x04000000). -*Source:* `/mnt/source/de25-uboot-socfpga/configs/socfpga_agilex5_de25_nano_defconfig; strings of /mnt/source/de25-linux/analysis/uboot_board_orig_pulled.itb; SETUP.md §3` +*Source:* `the PORT tree's U-Boot fork, configs/socfpga_agilex5_de25_nano_defconfig; strings of the PORT tree, analysis/uboot_board_orig_pulled.itb; SETUP.md §3` **[V] U4 — Is bitstream authentication (VAB) enforced on this board out of the box?** -Strong practical evidence that it is NOT — but not from a stock board. He loads locally built, unsigned .uboot.rbf files into the fabric both from U-Boot ('fpga load' + 'FPGA reconfiguration OK!') and from Linux via the fpga_manager, across at least eight cores, with no signing step anywhere in the repos; grep for VAB/QKY/efuse/authentication over his scripts, docs and configs returns nothing relevant. His defconfig does not set CONFIG_SOCFPGA_SECURE_VAB_AUTH (upstream keeps that in a separate socfpga_agilex5_vab_defconfig). The caveat is real and unremovable: he flashed his own .jic over the factory QSPI, so this is evidence about HIS device state, not proof about a factory-fresh DE25. It does establish that the A5EB013 silicon in front of him is not efuse-locked, which is the half of the question that would have been fatal. +Strong practical evidence that it is NOT — but not from a stock board. The port loads locally built, unsigned .uboot.rbf files into the fabric both from U-Boot ('fpga load' + 'FPGA reconfiguration OK!') and from Linux via the fpga_manager, across at least eight cores, with no signing step anywhere in the repos; grep for VAB/QKY/efuse/authentication over the port's scripts, docs and configs returns nothing relevant. The port's defconfig does not set CONFIG_SOCFPGA_SECURE_VAB_AUTH (upstream keeps that in a separate socfpga_agilex5_vab_defconfig). The caveat is real and unremovable: the port flashed its own .jic over the factory QSPI, so this is evidence about THE PORT'S device state, not proof about a factory-fresh DE25. It does establish that the A5EB013 silicon on that board is not efuse-locked, which is the half of the question that would have been fatal. -*Source:* `/mnt/source/de25-uboot-socfpga/configs/socfpga_agilex5_{de25_nano,vab}_defconfig; grep over /mnt/source/de25-linux; SETUP.md §3` +*Source:* `the PORT tree's U-Boot fork, configs/socfpga_agilex5_{de25_nano,vab}_defconfig; grep over the PORT tree; SETUP.md §3` **[U] U5 — Real reconfiguration latency.** -STILL UNMEASURED on a working Linux overlay path, and this leg cannot close it. The only in-repo numbers are comment/commit prose from the 6.12.11-era configfs path — '~3 s config' for a 3,325,952 B NES core, '16-18 s' for an a2600<->NES round trip, '~8 s' claimed live-switch total — and the referenced backing artifacts (analysis/SUB10S_ATF_KERNEL_DIVERGENCE.md, the memory/*.md notes, com9_*_throttle_validate.log) are NOT in the shared repo, so none is verifiable here. What IS new and verifiable are three real inputs to our §6.2 arithmetic: (1) actual MiSTer-scale Agilex 5 bitstream sizes for the A5EB013 die — 1.92 MB (menu) to 3.65 MB — which lands at the top of our 1-4 MB guess; (2) a second, independent ~1 s poll quantization, in U-Boot's intel_sdm_mb.c, matching the kernel service layer's msleep(1000) (our §6.1 row 7); (3) hard evidence that reconfiguration cost is NOT the dominant term in a perceived switch — his own design spends >=3 s quiescing and 5 s settling around it, both for HW-proven safety reasons. +STILL UNMEASURED on a working Linux overlay path, and this leg cannot close it. The only in-repo numbers are comment/commit prose from the 6.12.11-era configfs path — '~3 s config' for a 3,325,952 B NES core, '16-18 s' for an a2600<->NES round trip, '~8 s' claimed live-switch total — and the referenced backing artifacts (analysis/SUB10S_ATF_KERNEL_DIVERGENCE.md, the memory/*.md notes, com9_*_throttle_validate.log) are NOT in the shared repo, so none is verifiable here. What IS new and verifiable are three real inputs to our §6.2 arithmetic: (1) actual MiSTer-scale Agilex 5 bitstream sizes for the A5EB013 die — 1.92 MB (menu) to 3.65 MB — which lands at the top of our 1-4 MB guess; (2) a second, independent ~1 s poll quantization, in U-Boot's intel_sdm_mb.c, matching the kernel service layer's msleep(1000) (our §6.1 row 7); (3) hard evidence that reconfiguration cost is NOT the dominant term in a perceived switch — the port's own design spends >=3 s quiescing and 5 s settling around it, both for HW-proven safety reasons. -*Source:* `/mnt/source/de25-linux/board_overlay/media/fat/cores/CORES.manifest; de25_live_switch_core.sh:7-12,74-92,120-131; /mnt/source/de25-uboot-socfpga/drivers/fpga/intel_sdm_mb.c:20-21,1060-1070` +*Source:* `the PORT tree, board_overlay/media/fat/cores/CORES.manifest; de25_live_switch_core.sh:7-12,74-92,120-131; the PORT tree's U-Boot fork, drivers/fpga/intel_sdm_mb.c:20-21,1060-1070` ### From Leg 5 — platform gotchas, secure-boot, and the two symptoms (plus U1–U5 resolution) **[V] U1 -- Do mainline's intel,agilex-svc / intel,agilex-soc-fpga-mgr compatibles bind on Agilex 5?** -No, not as-is on this kernel generation: neither string matched an agilex5-specific DTS compatible in his tree, and mainline 6.18.44 has no svc/fpga-mgr/fpga-region node in socfpga_agilex5.dtsi at all (confirms D0.2 §3.1's absence finding). His fix -- adding explicit intel,agilex5-svc / intel,agilex5-soc-fpga-mgr match-table entries -- is a small, zero-risk kernel patch, and is HW-confirmed to bind and probe. This independently corroborates Altera's own upstream A5-series patch, which adds the identical fpga-mgr string. +No, not as-is on this kernel generation: neither string matched an agilex5-specific DTS compatible in the port's tree, and mainline 6.18.44 has no svc/fpga-mgr/fpga-region node in socfpga_agilex5.dtsi at all (confirms D0.2 §3.1's absence finding). The port's fix -- adding explicit intel,agilex5-svc / intel,agilex5-soc-fpga-mgr match-table entries -- is a small, zero-risk kernel patch, and is HW-confirmed to bind and probe. This independently corroborates Altera's own upstream A5-series patch, which adds the identical fpga-mgr string. -*Source:* `de25-linux commit d1878a320` +*Source:* `the PORT tree commit` **[V] U2 -- Is there a usable DT-overlay path for reconfiguration?** -Yes, and it is the only usable path -- but the /sys/kernel/config/device-tree/overlays/ configfs workflow every vendor doc describes never existed in his kernel (confirmed against kernel.org: no CONFIG_OF_CONFIGFS subsystem upstream), matching D0.2 §3.2 exactly. He built a small (~90-line) misc-device driver calling the exported of_overlay_fdt_apply() directly, HW-confirmed to reach the real fpga_manager write path via of-fpga-region.c's own notifier. +Yes, and it is the only usable path -- but the /sys/kernel/config/device-tree/overlays/ configfs workflow every vendor doc describes never existed in the port's kernel (confirmed against kernel.org: no CONFIG_OF_CONFIGFS subsystem upstream), matching D0.2 §3.2 exactly. The port built a small (~90-line) misc-device driver calling the exported of_overlay_fdt_apply() directly, HW-confirmed to reach the real fpga_manager write path via of-fpga-region.c's own notifier. -*Source:* `de25-linux commits 30d9c99a8, 881d4404a` +*Source:* `the PORT tree commits` **[U] U3 -- Does the factory FSBL boot a mainline-built u-boot.itb?** -Untested. He replaced Terasic's factory SPL with his own exFAT-aware SPL before the board was ever brought up in this repo's recorded history; no commit or doc anywhere in his tree exercises the unmodified factory FSBL against any FIT image, mainline-built or otherwise. This remains a genuinely open question for our posture-1 plan. +Untested. The port replaced Terasic's factory SPL with the port's own exFAT-aware SPL before the board was ever brought up in this repo's recorded history; no commit or doc anywhere in the port's tree exercises the unmodified factory FSBL against any FIT image, mainline-built or otherwise. This remains a genuinely open question for our posture-1 plan. -*Source:* `de25-uboot-socfpga commit history d0f8813fd6->096da9d4bb->38eff87fc0; absence confirmed by full log review` +*Source:* `the PORT tree's U-Boot fork commit history, starting from Terasic's public d0f8813fd6 and followed by two port-only commits; absence confirmed by full log review` **[V] U4 -- Is bitstream authentication (VAB) enforced on this board out of the box?** -No, on his specific unit: his defconfig never sets CONFIG_SOCFPGA_SECURE_VAB_AUTH (confirmed against the Kconfig, which shows that symbol lives only in separate *_vab_defconfig files), and his board repeatedly boots a self-built, unsigned .jic via JTAG all the way to a running MiSTer MENU -- the strongest practical evidence available that VAB is unprovisioned on this board class. This is single-unit evidence, not a guarantee about every DE25-Nano's eFuse state. +No, on the port's specific unit: the port's defconfig never sets CONFIG_SOCFPGA_SECURE_VAB_AUTH (confirmed against the Kconfig, which shows that symbol lives only in separate *_vab_defconfig files), and the port's board repeatedly boots a self-built, unsigned .jic via JTAG all the way to a running MiSTer MENU -- the strongest practical evidence available that VAB is unprovisioned on this board class. This is single-unit evidence, not a guarantee about every DE25-Nano's eFuse state. -*Source:* `de25-uboot-socfpga configs/ + arch/arm/mach-socfpga/Kconfig:27; de25-linux SETUP.md §3 and full working-boot history` +*Source:* `the PORT tree's U-Boot fork configs/ + arch/arm/mach-socfpga/Kconfig:27; the PORT tree SETUP.md §3 and full working-boot history` **[V] U5 -- Real reconfiguration latency** One real number now exists, single-source and self-reported: a 3.3MB core's SDM reconfiguration window is ~3s (post-BUF4-fix), and the full quiesce->overlay->settle->service-restart worker completes in ~8s regardless of core size, with a documented a2600<->NES round trip of 16-18s. This lands inside D0.2 §6.2's estimated range and is the first hardware corroboration of it, but it is not independently reproduced and the pre-fix pathology (an SDM/ATF buffer-reclaim bug, not a fundamental SoC limit) shows how easily a latency measurement can be dominated by a project-specific bug rather than the platform. -*Source:* `de25-linux board_overlay/usr/local/bin/de25_live_switch_core.sh header + commit 7fb52ac3f` +*Source:* `the PORT tree board_overlay/usr/local/bin/de25_live_switch_core.sh header + commit` --- ## 3. Platform gotchas we inherit regardless of design choices -These are properties of the SoC and the board, not of his design decisions, so our port meets them +These are properties of the SoC and the board, not of the port's design decisions, so our port meets them whichever posture we choose. ### Mainline 6.18's Agilex 5 DTSI has no SD/MMC controller node and no SMMU node. A board DTS that references &mmc or &smmu is a dtc compile error; without an mmc node the board cannot boot from SD at all. -- **Evidence:** grep -i 'mmc|sd4hc|smmu|iommu' over /mnt/source/Buildroot_MiSTer/output/build/linux-6.18.44/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi returns nothing; the same nodes appear as + hunks in his fork at :384 and :461 -- **His fix:** Back-ported mmc0@10808000 ('intel,agilex5-sd4hc','cdns,sd4hc', iommus=<&smmu 5>, dma-coherent) and smmu: iommu@16000000 ('arm,smmu-v3') from Terasic's 6.12.11-LTS tree into the dtsi (bf8e5c9c6). +- **Evidence:** grep -i 'mmc|sd4hc|smmu|iommu' over /mnt/source/Buildroot_MiSTer/output/build/linux-6.18.44/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi returns nothing; the same nodes appear as + hunks in the port's fork at :384 and :461 +- **The port's fix:** Back-ported mmc0@10808000 ('intel,agilex5-sd4hc','cdns,sd4hc', iommus=<&smmu 5>, dma-coherent) and smmu: iommu@16000000 ('arm,smmu-v3') from Terasic's 6.12.11-LTS tree into the dtsi. - **Applies to us:** Unavoidably. Any DE25 port we do needs these nodes from somewhere. The only open choice is which file they live in and whether we try to upstream them. ### Mainline 6.18's own gmac0 node for Agilex 5 does not work on real DE25 hardware — ethernet never probes. -- **Evidence:** /mnt/source/de25-linux commit e57c54c74: -EIO 'Cannot register the MDIO bus', preceded by 'Version ID not available' / 'No HW DMA feature register supported' on every boot -- **His fix:** Replaced the mainline node (compatible altr,socfpga-stmmac-agilex5, one macirq, reset-names stmmaceth/ahb) with Terasic's full node: altr,socfpga-stmmac-a10-s10 + 17 per-queue IRQ vectors, snps,multi-irq-en, snps,pblx8, altr,smtg-hub, iommus=<&smmu 1>, dma-coherent, and reset-names stmmaceth/stmmaceth-ocp. He identifies the concrete mechanism: dwmac-socfpga.c acquires the OCP reset exclusively by the name 'stmmaceth-ocp', which mainline's 'ahb' naming never satisfies. +- **Evidence:** the PORT tree's commit: -EIO 'Cannot register the MDIO bus', preceded by 'Version ID not available' / 'No HW DMA feature register supported' on every boot +- **The port's fix:** Replaced the mainline node (compatible altr,socfpga-stmmac-agilex5, one macirq, reset-names stmmaceth/ahb) with Terasic's full node: altr,socfpga-stmmac-a10-s10 + 17 per-queue IRQ vectors, snps,multi-irq-en, snps,pblx8, altr,smtg-hub, iommus=<&smmu 1>, dma-coherent, and reset-names stmmaceth/stmmaceth-ocp. The port identifies the concrete mechanism: dwmac-socfpga.c acquires the OCP reset exclusively by the name 'stmmaceth-ocp', which mainline's 'ahb' naming never satisfies. - **Applies to us:** Yes, verbatim — this is a property of mainline's DTS vs this silicon, independent of any design choice of ours. It also means mainline's Agilex 5 ethernet support is untested on hardware and we should not trust other mainline Agilex 5 nodes by default. ### With the SMMU enabled, any DMA-capable peripheral missing an iommus stream ID faults on every transfer. -- **Evidence:** /mnt/source/de25-linux commit 36f39d30d: usb0 without iommus=<&smmu 6> produced AHB ERROR on every dwc2 channel; lsusb showed only root hubs -- **His fix:** Added iommus = <&smmu 6> to usb0. Stream IDs in use in his tree: gmac0=1, mmc0=5, usb0=6, svc=10. +- **Evidence:** the PORT tree's commit: usb0 without iommus=<&smmu 6> produced AHB ERROR on every dwc2 channel; lsusb showed only root hubs +- **The port's fix:** Added iommus = <&smmu 6> to usb0. Stream IDs in use in the port's tree: gmac0=1, mmc0=5, usb0=6, svc=10. - **Applies to us:** Yes, if we enable the SMMU. The failure mode is a device that enumerates nothing rather than a probe error, so it is easy to misdiagnose as a USB or PHY problem. ### mmc0 DMA through the SMMU F_TRANSLATION-faults on 6.18 but not on 6.12, with a byte-identical devicetree. ~~It is a kernel regression, not a DT gap.~~ — **the FAULT is confirmed; the "kernel regression" ATTRIBUTION is CORRECTED, 2026-08-22** -> **Correction (2026-08-22).** The **fault is real and well-evidenced** — commit `716559020`'s -> retract-of-a-retraction stands, verified [V]. What does **not** hold is calling it a *kernel-version +> **Correction (2026-08-22).** The **fault is real and well-evidenced** — the port's +> retraction-of-a-retraction commit stands, verified [V]. What does **not** hold is calling it a *kernel-version > regression*. The "working 6.12" baseline is **Terasic's vendor tree**, not mainline, and its > `sdhci-cadence.c` gives `intel,agilex5-sd4hc` a dedicated match entry carrying > **`SDHCI_QUIRK2_40_BIT_DMA_MASK`** (`terasic/linux-socfpga@de25-nano-6.12.11-lts: > drivers/mmc/host/sdhci-cadence.c:783-786,952-953`) **[V, fetched 2026-08-22]**. That quirk **exists > nowhere in mainline** — zero hits in `drivers/mmc/` and `include/` at 6.18.44, and zero in -> `sdhci.h` at v7.2 and `master` **[V]**. His 6.18 tree binds the bare `cdns,sd4hc` entry with a +> `sdhci.h` at v7.2 and `master` **[V]**. The port's 6.18 tree binds the bare `cdns,sd4hc` entry with a > byte-identical-to-mainline driver **[V `diff -q`]**, so mainline's > `sdhci_set_dma_mask()` takes the 64-bit branch (`DMA_BIT_MASK(64)`, `sdhci.c:4117-4123`) **[V]** on > a controller the vendor deliberately caps at 40 bits. Under SMMU translation, top-down IOVA @@ -309,32 +308,32 @@ whichever posture we choose. > `intel,agilex5-sd4hc` with a 40-bit mask. See > [`de25-implementation-path.md`](de25-implementation-path.md) §5.1 and §8 Q2. -- **Evidence:** /mnt/source/de25-linux commit 716559020: the earlier 'false alarm / warm-reboot artifact' conclusion is retracted; the 'clean' retest had sdhci.debug_quirks=0x60 silently inherited from a JTAG-recovery script, which forces PIO and never exercised the faulting path. A genuine power-cycle through the unmodified production boot script reproduced it decisively. -- **His fix:** sdhci.debug_quirks=0x60 baked into the production boot.scr.uimg — forces SDHCI into PIO instead of ADMA. Root cause deliberately not bisected. +- **Evidence:** the PORT tree's commit: the earlier 'false alarm / warm-reboot artifact' conclusion is retracted; the 'clean' retest had sdhci.debug_quirks=0x60 silently inherited from a JTAG-recovery script, which forces PIO and never exercised the faulting path. A genuine power-cycle through the unmodified production boot script reproduced it decisively. +- **The port's fix:** sdhci.debug_quirks=0x60 baked into the production boot.scr.uimg — forces SDHCI into PIO instead of ADMA. Root cause deliberately not bisected. - **Applies to us:** Yes, and on **every** mainline kernel version, not just 6.18 (see the correction box). The mitigation costs the entire SD DMA path. This is also a methodology warning we should adopt: do not test SMMU-adjacent behaviour on this board via a warm reboot, and audit your own test harness's default bootargs before declaring a fault gone. A third methodology lesson, added by the 2026-08-22 pass: **a "byte-identical devicetree" isolates nothing when the two kernels are a vendor tree and a mainline tree** — diff the drivers before attributing a fault to a version. ### Touching the LWH2F window too soon after config-complete hangs a CPU on the AXI bus with no bus timeout — RCU stall, and a warm reset from that state brings the board back DARK (power-cycle only). -- **Evidence:** /mnt/source/de25-linux/board_overlay/usr/local/bin/de25_live_switch_core.sh:123-132, described as 'HW-proven 2026-07-13, the hard way' -- **His fix:** Mandatory 5 s settle after config-complete before any fabric access, and a mandatory 3 s settle before starting a new reconfigure (trimming that to 1 s reproducibly wedged the next switch). Both are dead time in every core switch. +- **Evidence:** the PORT tree, board_overlay/usr/local/bin/de25_live_switch_core.sh:123-132, described as 'HW-proven 2026-07-13, the hard way' +- **The port's fix:** Mandatory 5 s settle after config-complete before any fabric access, and a mandatory 3 s settle before starting a new reconfigure (trimming that to 1 s reproducibly wedged the next switch). Both are dead time in every core switch. - **Applies to us:** Yes if we do live reconfiguration on this SoC. It sets a hard floor of several seconds on switch latency that no software optimisation removes, and it means any 'is the fabric up?' probe must be gated, not speculative. ### On an unconfigured fabric, a raw MMIO read of the fabric sysid is a FATAL async SError — a kernel panic, not a recoverable probe failure. -- **Evidence:** /mnt/source/de25-linux/board_overlay/usr/local/bin/de25_fabric_ready.py (HPS-First guard comment, 'HW-proven on the first Stage-1 HPS-First boot') -- **His fix:** A guard script that refuses the MMIO probe unless a U-Boot-supplied de25_core= breadcrumb names a configured core, or fpga0/state reads 'operating' after a kernel-driven config. Note his finding that fpga0/state reads 'unknown' for a U-Boot-configured fabric, so it cannot be used as a universal signal. +- **Evidence:** the PORT tree, board_overlay/usr/local/bin/de25_fabric_ready.py (HPS-First guard comment, 'HW-proven on the first Stage-1 HPS-First boot') +- **The port's fix:** A guard script that refuses the MMIO probe unless a U-Boot-supplied de25_core= breadcrumb names a configured core, or fpga0/state reads 'operating' after a kernel-driven config. Note the port's finding that fpga0/state reads 'unknown' for a U-Boot-configured fabric, so it cannot be used as a universal signal. - **Applies to us:** Yes. Any Main_MiSTer aarch64 HAL that probes the fabric the way DE10's does will panic the kernel on an unconfigured board. This is a hard constraint on how L0's HAL discovers the fabric. ### The configfs device-tree overlay path does not exist in upstream Linux and never will as things stand. -- **Evidence:** /mnt/source/de25-linux commits 881d4404a and 30d9c99a8: 'confirmed against kernel.org: the RPi-style drivers/of/configfs.c subsystem this assumed was never upstream'; his defconfig even carries an inert CONFIG_OF_CONFIGFS=y line for a symbol that does not exist -- **His fix:** Wrote a misc driver that calls of_overlay_fdt_apply() from kernel context. +- **Evidence:** the PORT tree's commits: 'confirmed against kernel.org: the RPi-style drivers/of/configfs.c subsystem this assumed was never upstream'; the port's defconfig even carries an inert CONFIG_OF_CONFIGFS=y line for a symbol that does not exist +- **The port's fix:** Wrote a misc driver that calls of_overlay_fdt_apply() from kernel context. - **Applies to us:** Yes — it removes one of the three options our fpga-reconfig §11 row 6 was going to cost out. Also a caution: an inert CONFIG_ symbol in a defconfig produces no warning and no behaviour. ### With CONFIG_STRICT_DEVMEM=y, ordinary System RAM cannot be mmap'd via /dev/mem; only no-map reserved regions can. -- **Evidence:** /mnt/source/de25-linux commit 9454a984d; arch/arm64/configs/de25_defconfig:12312 -- **His fix:** A no-map reserved-memory node over the AO486 0xB0000000 window. He notes the failure was silent and cost several investigation sessions — Main released the x86 CPU over a never-staged BIOS and the core triple-faulted. +- **Evidence:** the PORT tree's commit; arch/arm64/configs/de25_defconfig:12312 +- **The port's fix:** A no-map reserved-memory node over the AO486 0xB0000000 window. The port notes the failure was silent and cost several investigation sessions — Main released the x86 CPU over a never-staged BIOS and the core triple-faulted. - **Applies to us:** Yes, for any Main_MiSTer shared-memory window, if we keep STRICT_DEVMEM. Note our DE10 config sets '# CONFIG_STRICT_DEVMEM is not set' (board/mister MiSTer_defconfig), so this is a divergence we would inherit only if the DE25 config turns it on. ### ~~drivers/input/mousedev.c is missing #include ~~ — **REFUTED by the main session, 2026-08-21** @@ -353,307 +352,307 @@ whichever posture we choose. > aarch64 with `CONFIG_COMPAT=y` it becomes live for the first time. The patch author already > reasoned about the safety (truth value, not pointer), so this is a review item, not a defect. -- **Evidence:** de25-linux commit 9a3e73919; CONFIG_COMPAT=y confirmed in his defconfig -- **His fix:** One-line #include add. +- **Evidence:** the PORT tree's commit; CONFIG_COMPAT=y confirmed in the port's defconfig +- **The port's fix:** One-line #include add. - **Applies to us:** Any aarch64 DE25 defconfig of ours that sets CONFIG_COMPAT=y (likely, for any 32-bit compat needs) will hit the identical implicit-declaration build error and need the identical one-line fix — trivial but must not be missed. ### stratix10-svc.c / stratix10-soc.c of_device_id tables lack 'intel,agilex5-svc' / 'intel,agilex5-soc-fpga-mgr' entries — a genuine mainline gap for this SoC family, not board-specific. -- **Evidence:** de25-linux commit d1878a320, explicitly stated to reproduce on current mainline too -- **His fix:** Add one compatible-string line to each driver's match table (zero functional risk, no .data/branching on the tables). +- **Evidence:** the PORT tree commit, explicitly stated to reproduce on current mainline too +- **The port's fix:** Add one compatible-string line to each driver's match table (zero functional risk, no .data/branching on the tables). - **Applies to us:** Any DE25 board work using mainline-flavored kernel sources needs this same two-line fix before /sys/class/fpga_manager or /sys/class/fpga_region will bind at all. ### There is no writable userspace attribute on fpga_manager class devices in current mainline, and the RPi-style OF-configfs overlay subsystem was never upstreamed — any design assuming a configfs-triggered reconfigure path will fail structurally, not intermittently. -- **Evidence:** de25-linux commits 30d9c99a8, 881d4404a, explicit statement 'confirmed against kernel.org' -- **His fix:** A custom misc-device kernel module applying a static prebuilt DT overlay via of_overlay_fdt_apply(), relying on of-fpga-region.c's own notifier. -- **Applies to us:** Any of our own reconfigure design must go through the DT-overlay + fpga-region-notifier path (or write our own trigger like his), not a configfs write. +- **Evidence:** the PORT tree commits, explicit statement 'confirmed against kernel.org' +- **The port's fix:** A custom misc-device kernel module applying a static prebuilt DT overlay via of_overlay_fdt_apply(), relying on of-fpga-region.c's own notifier. +- **Applies to us:** Any of our own reconfigure design must go through the DT-overlay + fpga-region-notifier path (or write our own trigger like the port's), not a configfs write. ### Agilex 5 Rev A silicon requires a runtime-detected, non-optional Intel-upstream FPGA-reconfig workaround (register-gated, not a defconfig symbol), dated 2023 and explicitly scoped to Rev A only. -- **Evidence:** de25-uboot-socfpga commit 2030244a1e, confirmed ancestor of his branch -- **His fix:** Already inherited automatically via mainline U-Boot; no action needed beyond using a U-Boot new enough to carry it. +- **Evidence:** Altera's vendor U-Boot commit 2030244a1e (2023-09-05, "HSD #15013985953: arm: socfpga: agilex5: Add workaround for FPGA reconfig"), a confirmed ancestor of the PORT tree's U-Boot branch +- **The port's fix:** Already inherited automatically via mainline U-Boot; no action needed beyond using a U-Boot new enough to carry it. - **Applies to us:** If our target boards are Rev A Agilex 5 silicon (needs confirming against our own board's stepping), we inherit this same requirement for free from any modern U-Boot — but must confirm our chosen U-Boot baseline actually includes it. -### The SDM reconfigure mailbox protocol has a hardcoded, tight 300ms RECONFIG_REQUEST timeout and a 4×512KiB buffer pool with a 720ms per-buffer reclaim timeout — both drivers our and his kernels share verbatim. +### The SDM reconfigure mailbox protocol has a hardcoded, tight 300ms RECONFIG_REQUEST timeout and a 4×512KiB buffer pool with a 720ms per-buffer reclaim timeout — both drivers our and the port's kernels share verbatim. - **Evidence:** stratix10-svc-client.h:68-69 in both repos; matches docs/de25-fpga-reconfig.md's own independent desk-research figures -- **His fix:** None yet — his one live test hit this timeout and wedged the board; the fix path (retry/backoff/hardened quiesce) is explicitly not yet built. +- **The port's fix:** None yet — the port's one live test hit this timeout and wedged the board; the fix path (retry/backoff/hardened quiesce) is explicitly not yet built. - **Applies to us:** Confirms our D0.2 dossier's latency-risk flag was well-founded rather than overcautious; any of our own live-reconfigure implementation needs to budget for this timeout being real and tight on actual hardware, not just in the datasheet. ### Mainline 6.18.x cannot reconfigure an Agilex 5 fabric at all until BOTH a DT node set and a driver match-table entry are added — and adding only one of them fails in a way that looks like a driver bug. -- **Evidence:** mainline socfpga_agilex5.dtsi has no svc/fpga-mgr/fpga-region node (only svcbuffer@0); stratix10-svc.c and stratix10-soc.c match only stratix10-*/agilex-*. His commit records that patching only the fpga-mgr left modprobe succeeding while the mailbox still failed with "couldn't get service channel (fpga)", because the SVC node binds at __init before its children can be created. -- **His fix:** Two one-line match-table additions (intel,agilex5-svc, intel,agilex5-soc-fpga-mgr) plus authoring firmware/svc + fpga-mgr + fpga-region nodes in the SoC dtsi. Note his svc node needs memory-region=<&service_reserved>, iommus=<&smmu 10> and altr,smmu_enable_quirk on this SMMU-enabled SoC. -- **Applies to us:** Directly and unavoidably — this is our D0.2 §3.1/§4.2 work item, now with a working reference. The upstream Altera series uses 'intel,agilex-soc-fpga-mgr' as a DT fallback instead; either route works, but the fallback route avoids carrying driver patches. Our decision, not his. +- **Evidence:** mainline socfpga_agilex5.dtsi has no svc/fpga-mgr/fpga-region node (only svcbuffer@0); stratix10-svc.c and stratix10-soc.c match only stratix10-*/agilex-*. The port's commit records that patching only the fpga-mgr left modprobe succeeding while the mailbox still failed with "couldn't get service channel (fpga)", because the SVC node binds at __init before its children can be created. +- **The port's fix:** Two one-line match-table additions (intel,agilex5-svc, intel,agilex5-soc-fpga-mgr) plus authoring firmware/svc + fpga-mgr + fpga-region nodes in the SoC dtsi. Note the port's svc node needs memory-region=<&service_reserved>, iommus=<&smmu 10> and altr,smmu_enable_quirk on this SMMU-enabled SoC. +- **Applies to us:** Directly and unavoidably — this is our D0.2 §3.1/§4.2 work item, now with a working reference. The upstream Altera series uses 'intel,agilex-soc-fpga-mgr' as a DT fallback instead; either route works, but the fallback route avoids carrying driver patches. Our decision, not the port's. ### Reconfiguration on this SoC quantizes to ~1 s regardless of bitstream size, from two independent code paths. - **Evidence:** U-Boot's intel_sdm_mb.c does udelay(RECONFIG_STATUS_INTERVAL_DELAY_US = 1000000) after sending the bitstream, before its FIRST RECONFIG_STATUS poll, then polls at 1 s intervals (60 s ceiling, 100-retry inner loop at 20 ms). The kernel service layer independently polls with msleep(1000) (our §6.1 row 7). -- **His fix:** None — accepted. It is why even a ~3 s claimed 'live' config is nowhere near the raw fabric-write time. +- **The port's fix:** None — accepted. It is why even a ~3 s claimed 'live' config is nowhere near the raw fabric-write time. - **Applies to us:** Yes. Our §6.2 estimate of '~10 ms to ~1 s' should treat ~1 s as the practical floor, not the optimistic tail. It is still small next to a reboot. ### An MMIO read of an unconfigured or half-configured fabric is a FATAL async SError on Agilex 5 — a kernel panic, not a failed probe — and there is no AXI bus timeout, so a poke during the post-config settle window hangs a CPU (RCU stall) and a warm reset from that state brings the board back DARK (power-cycle or JTAG only). - **Evidence:** de25_fabric_ready.py's header ('on Agilex 5 the sysid read below is then a fatal async SError (kernel panic, not a recoverable failed probe; HW-proven)'); de25_live_switch_core.sh's settle comment ('the first LWH2F poke during that window ... hangs the CPU on the AXI bus — no timeout on Agilex → RCU stall, and a warm reset from that state leaves the board DARK'); MiSTer's own 'FULL-config WEDGE ... JTAG recovery needed' strings. -- **His fix:** A hard guard before Main is ever started (a de25_core= bootargs breadcrumb from U-Boot, an HPS_FIRST_MODE marker, and a fpga_manager state check), a 5 s settle after config-complete before any fabric access, and a policy of NEVER warm-rebooting out of a suspected wedge. -- **Applies to us:** Yes, and it is a design constraint on Main_MiSTer's DE25 port, not just on scripts: anything that probes the fabric must be gated on a positive fabric-configured signal. Note also his HW-proven finding that fpga0/state reads 'unknown' (a false negative) for a fabric configured by U-Boot rather than by the kernel driver — so fpga0/state alone is not a usable guard. +- **The port's fix:** A hard guard before Main is ever started (a de25_core= bootargs breadcrumb from U-Boot, an HPS_FIRST_MODE marker, and a fpga_manager state check), a 5 s settle after config-complete before any fabric access, and a policy of NEVER warm-rebooting out of a suspected wedge. +- **Applies to us:** Yes, and it is a design constraint on Main_MiSTer's DE25 port, not just on scripts: anything that probes the fabric must be gated on a positive fabric-configured signal. Note also the port's HW-proven finding that fpga0/state reads 'unknown' (a false negative) for a fabric configured by U-Boot rather than by the kernel driver — so fpga0/state alone is not a usable guard. ### A hard-kill of the fabric client followed immediately by a reconfigure poisons the SDM service layer; the quiesce delay before a reconfigure is load-bearing and cannot be tuned down. -- **Evidence:** de25_live_switch_core.sh's F8 note: HW-tested 2026-07-16, trimming the 3 s quiesce to 1 s made the very NEXT switch fail with 'timeout waiting for svc layer buffers', and the following service restart hung a CPU on the AXI bus (RCU stall), wedging the board silently — JTAG-recoverable only. His reading is that the sleep also lets the prior reconfigure's svc buffer pool fully reclaim. -- **His fix:** Left at 3 s quiesce + 5 s settle, with an explicit DO-NOT-REDUCE comment. +- **Evidence:** de25_live_switch_core.sh's F8 note: HW-tested 2026-07-16, trimming the 3 s quiesce to 1 s made the very NEXT switch fail with 'timeout waiting for svc layer buffers', and the following service restart hung a CPU on the AXI bus (RCU stall), wedging the board silently — JTAG-recoverable only. The port's reading is that the sleep also lets the prior reconfigure's svc buffer pool fully reclaim. +- **The port's fix:** Left at 3 s quiesce + 5 s settle, with an explicit DO-NOT-REDUCE comment. - **Applies to us:** Yes, if we do runtime switching. This is ~8 s of pure sequencing per switch on top of any reconfiguration cost, and it is the real answer to 'how fast can a core switch be' — far more than the SDM write. It should be measured in D2.5 as its own term. ### MiSTer-scale Agilex 5 bitstreams for the A5EB013 die are 1.9-3.6 MB, i.e. they straddle the kernel's 2 MiB in-flight buffer pool (4 x 512 KiB). - **Evidence:** CORES.manifest sizes (menu 1,929,216 ... ao486 3,645,440); mainline stratix10-soc.c NUM_SVC_BUFS=4, SVC_BUF_SIZE=SZ_512K. -- **His fix:** None needed once the buffer-recycling replies work; his wall was self-inflicted via an ATF mod. +- **The port's fix:** None needed once the buffer-recycling replies work; the port's wall was self-inflicted via an ATF mod. - **Applies to us:** As a sizing input, yes — it fills in the missing input to our §6.2 arithmetic and confirms our 1-4 MB guess. As a wall, only if we ever modify ATF's FPGA config buffer handling. It also means any bitstream transfer necessarily makes at least 4-8 buffer round trips, so BUFFER_DONE callback health is on the critical path for every switch. ### The SDM owns the QSPI; the HPS cannot drive the Cadence QSPI controller directly (that path hangs). All HPS-side flash access goes through the FCS mailbox, in tiny chunks, with a chip-select re-assert required before EVERY write. - **Evidence:** de25_fcs_qspi.py header ('The HPS cannot touch it through the cadence controller (that path hangs)'); de25_fcs_stage_core.py:56-70 ('the SDM deselects the QSPI chip after every write cycle, so each consecutive write MUST re-assert cs(0) first -- else the 2nd write returns SDM status 4 and WEDGES the async channel (needs JTAG/power-cycle recovery)'); qspi_read max 4096 bytes per call. -- **His fix:** 4 KiB write chunks with cs-per-write and a 50 ms settle; cancel checks only BETWEEN operations, never mid-op. +- **The port's fix:** 4 KiB write chunks with cs-per-write and a 50 ms settle; cancel checks only BETWEEN operations, never mid-op. - **Applies to us:** Only if we ever write QSPI from Linux — which our posture 1 says we never do. It is decisive evidence FOR posture 1: any QSPI-write-based update mechanism on this board is minutes-long, wedge-prone, and JTAG-recoverable at best. It also means our Q4 RSU sizing question has a companion: even if RSU fits, writing it is this. ### The SD host needed capping at 25 MHz (default speed, no UHS/SDR50) for stability; SCR-corruption and ADMA issues appear around JTAG-adjacent boots. -- **Evidence:** His board DTS caps &mmc max-frequency at 25000000 with an explicit stability rationale; SETUP.md §9 additionally requires sdhci.debug_quirks=0x60 and forbids iommu.passthrough=1 for post-JTAG full-SOF boots or SDHCI ADMA corrupts early SD init. -- **His fix:** DTS clock cap plus documented cmdline requirements for the JTAG development flow. -- **Applies to us:** Probably — it is a board/PHY-level finding, and the cdns,phy-* tuning block in his DTS is inherited from Terasic. If we hit SD instability, this is the first knob, and it costs bitstream read bandwidth (a 3.5 MB core at 4-bit/25 MHz is bounded near 12.5 MB/s theoretical). +- **Evidence:** The port's board DTS caps &mmc max-frequency at 25000000 with an explicit stability rationale; SETUP.md §9 additionally requires sdhci.debug_quirks=0x60 and forbids iommu.passthrough=1 for post-JTAG full-SOF boots or SDHCI ADMA corrupts early SD init. +- **The port's fix:** DTS clock cap plus documented cmdline requirements for the JTAG development flow. +- **Applies to us:** Probably — it is a board/PHY-level finding, and the cdns,phy-* tuning block in the port's DTS is inherited from Terasic. If we hit SD instability, this is the first knob, and it costs bitstream read bandwidth (a 3.5 MB core at 4-bit/25 MHz is bounded near 12.5 MB/s theoretical). ### Every reset on this SoC is a full SDM reconfiguration cycle, and software cannot distinguish a deliberate reboot from a power-cycle: RSTMGR_SOC64_STATUS reads 'Reset state: Cold' for both, and ATF's handler for software reboot is literally mailbox_reset_cold(). -- **Evidence:** boot.scr.uimg's 2026-07-20 note, stated as validated against his own com9_coldboot_throttle_validate.log vs com9_warmreboot_throttle_validate.log (both logs absent from the shared repo, so the validation itself is [U]). -- **His fix:** A one-shot marker file written by the single legitimate reboot path and consumed by boot.scr on read, so any unplanned reset defaults to MENU. +- **Evidence:** boot.scr.uimg's 2026-07-20 note, stated as validated against the port's own com9_coldboot_throttle_validate.log vs com9_warmreboot_throttle_validate.log (both logs absent from the shared repo, so the validation itself is [U]). +- **The port's fix:** A one-shot marker file written by the single legitimate reboot path and consumed by boot.scr on read, so any unplanned reset defaults to MENU. - **Applies to us:** Yes for our Q5 (warm reboot, parked) and for any boot-time state machine we build. It also means a reboot can never be cheap on this board: it is a full SDM configuration cycle every time. ### Partial reconfiguration is not a free speed win: PR personas must come from one base compile, and loading a persona that does not match wedges the SDM. - **Evidence:** MiSTer binary strings 'fpga_load_rbf: SDM WEDGE on %s (persona not from base compile?) - JTAG recovery needed' and 'fpga_load_rbf: restoring PR base before partial'; analysis/pr_partial/de25_pr_partial.dtso documents that the earlier FULL-config overlay HUNG the HPS in 'Experiment 2' and that partial-fpga-config is 'the make-or-break difference'; the PR partial bitstream in that experiment was 344 KB vs multi-MB full cores. -- **His fix:** He kept a de25base.core.rbf and restored it before a partial swap, then abandoned the tier in favour of full config once HPS-first compilation made full reconfiguration survivable. +- **The port's fix:** The port kept a de25base.core.rbf and restored it before a partial swap, then abandoned the tier in favour of full config once HPS-first compilation made full reconfiguration survivable. - **Applies to us:** Relevant if anyone proposes PR as the MiSTer core-switch mechanism: it would make every core a persona of one frozen base compile — an FPGA-project-level constraint, not a software one — in exchange for a ~10x smaller bitstream. -### Every DMA-capable peripheral needs an explicit iommus= stream-ID property once an smmu node is enabled on Agilex 5 -- omitting it on even one master (he missed usb0) silently AHB-faults every DMA transfer on that device with no obvious error pointing at the DT. +### Every DMA-capable peripheral needs an explicit iommus= stream-ID property once an smmu node is enabled on Agilex 5 -- omitting it on even one master (the port missed usb0) silently AHB-faults every DMA transfer on that device with no obvious error pointing at the DT. -- **Evidence:** de25-linux commits 36f39d30d/c917db658: usb0 was missing iommus=<&smmu 6> after being ported from a different upstream base than the vendor tree it was checked against; fixed with one line, HW-validated. -- **His fix:** Add the correct iommus=<&smmu N> property with the vendor tree's stream ID for every boot-relevant DMA master. +- **Evidence:** the PORT tree's commits: usb0 was missing iommus=<&smmu 6> after being ported from a different upstream base than the vendor tree it was checked against; fixed with one line, HW-validated. +- **The port's fix:** Add the correct iommus=<&smmu N> property with the vendor tree's stream ID for every boot-relevant DMA master. - **Applies to us:** Mainline 6.18.44's socfpga_agilex5.dtsi ships neither an smmu node nor any iommus= properties at all (verified directly). Whoever authors our DE25 board DTS (D0.3/D2) must add the smmu node and correctly stream-ID every master ourselves, sourced from a vendor tree or Altera's newer DTS series, and audit every DMA-capable node individually -- this is not a one-time fix, it's a class of omission to check for on every node we add. ### mmc0/SDHCI on Agilex 5 faults arm-smmu-v3 F_TRANSLATION on a genuine cold boot with an SMMU node enabled and a v6.18-generation kernel, even with a devicetree byte-identical to a working 6.12.11 baseline -- ~~a real kernel-version regression~~ **[CORRECTED 2026-08-22: a vendor-vs-mainline DRIVER delta, not a version regression — see the correction box in §3 above; the leading root cause is a 64-bit vs 40-bit DMA mask under SMMU translation, and it travels forward to 6.19/7.2]**, not a DT authoring mistake, root cause unresolved. -- **Evidence:** de25-linux commits 314a0be6c (initial false-negative) and 716559020 (retraction + reproduction via a genuine physical power-cycle through the unmodified production boot chain). -- **His fix:** sdhci.debug_quirks=0x60 on the kernel command line, forcing SDHCI/mmc0 into PIO instead of ADMA, sidestepping the DMA-through-IOMMU path entirely. Baked into the default production boot.scr.uimg bootargs, not a JTAG-only special case. +- **Evidence:** the PORT tree's commits: an initial false-negative, then a retraction + reproduction via a genuine physical power-cycle through the unmodified production boot chain. +- **The port's fix:** sdhci.debug_quirks=0x60 on the kernel command line, forcing SDHCI/mmc0 into PIO instead of ADMA, sidestepping the DMA-through-IOMMU path entirely. Baked into the default production boot.scr.uimg bootargs, not a JTAG-only special case. - **Applies to us:** Our kernel baseline is also 6.18.x mainline. If we enable an SMMU node for Agilex 5 (which D0.2 flagged as an open question -- §3.1 note 2, whether the svc node even needs iommus=<&smmu 10>), we should expect to hit the identical mmc0/ADMA fault the moment SD/MMC is DT-enabled alongside SMMU, and plan for the PIO-quirk mitigation (with its throughput cost) rather than treating it as a DT-authoring bug to debug from scratch. **[CORRECTED 2026-08-22:** there is no "upstream fix" coming, because there is no upstream regression — the delta is vendor-driver-vs-mainline-driver (a 40-bit vs 64-bit DMA mask) and it is present on 6.19 and 7.2 too. The mainline-first remedy to test is a one-entry `sdhci-cadence` patch adding `intel,agilex5-sd4hc` with a 40-bit mask.**]** ### Reconfiguring the fabric and then touching it (an LWH2F/fabric-side register poke) too soon after config-complete hangs the CPU on the AXI bus with no timeout -- an RCU stall that leaves the board completely dark, JTAG-recoverable only, not warm-reset-recoverable. -- **Evidence:** de25-linux board_overlay/usr/local/bin/de25_live_switch_core.sh: HW-tested trimming the post-quiesce sleep from 3s to 1s reproduced this exact failure mode; a hard-kill of MiSTer followed by immediate reconfig was separately found to poison the SDM service layer. -- **His fix:** A generous, empirically-tuned settle window: 3s before starting reconfiguration (to let the prior fabric session's buffer pool fully reclaim) and 5s after config-complete before any fabric-side access (to let the new core's internal resets/PLLs lock). -- **Applies to us:** This directly bears on D0.2 §11 row 4 (does the region accept repeated overlay apply/remove cycles cleanly). His answer is 'yes, but only with settle windows this project had to discover by bricking the board twice' -- our own D2.5 hardware-measurement plan should budget for exactly this discovery rather than assuming a bare of_overlay_fdt_apply()/remove() pair is safe back-to-back, and any MiSTer-style core-switch UI on this board needs a mandatory settle floor baked into the framework, not left to a user-triggerable spam-switch. +- **Evidence:** the PORT tree board_overlay/usr/local/bin/de25_live_switch_core.sh: HW-tested trimming the post-quiesce sleep from 3s to 1s reproduced this exact failure mode; a hard-kill of MiSTer followed by immediate reconfig was separately found to poison the SDM service layer. +- **The port's fix:** A generous, empirically-tuned settle window: 3s before starting reconfiguration (to let the prior fabric session's buffer pool fully reclaim) and 5s after config-complete before any fabric-side access (to let the new core's internal resets/PLLs lock). +- **Applies to us:** This directly bears on D0.2 §11 row 4 (does the region accept repeated overlay apply/remove cycles cleanly). The port's answer is 'yes, but only with settle windows this project had to discover by bricking the board twice' -- our own D2.5 hardware-measurement plan should budget for exactly this discovery rather than assuming a bare of_overlay_fdt_apply()/remove() pair is safe back-to-back, and any MiSTer-style core-switch UI on this board needs a mandatory settle floor baked into the framework, not left to a user-triggerable spam-switch. ### The SDM mailbox layer exhibits an apparent request-budget/throttling behavior across reconfiguration attempts that is broader than any single driver bug -- a raw overlay-trigger write timed out waiting for RECONFIG_REQUEST even after the buffer-size (BUF4) bug that had caused the '>2MB wall' was already fixed. -- **Evidence:** de25-linux commit 30d9c99a8 message, explicitly describing this as 'the same class of pre-existing mailbox/budget issue already documented elsewhere in this project.' The referenced memory files documenting that broader pattern are not present in this repo snapshot -- root cause is [U] to us. -- **His fix:** Not fixed at the point this repo snapshot ends; the live-switch worker treats an unresponsive trigger write as fatal (30s deadline, then park rather than reboot) rather than retrying. -- **Applies to us:** We should not assume the mainline stratix10-soc.c/stratix10-svc.c stack is bug-free on Agilex 5 just because the compatible-string gap (U1) is a known, fixable issue. There appears to be at least one more class of SDM-mailbox fragility on this platform that neither his fixes nor our D0.2 desk research have fully characterized; D2.5's hardware-measurement plan should treat 'the mailbox occasionally times out for reasons unrelated to buffer size' as an open risk to budget time against, not a solved problem. +- **Evidence:** the PORT tree commit message, explicitly describing this as 'the same class of pre-existing mailbox/budget issue already documented elsewhere in this project.' The referenced memory files documenting that broader pattern are not present in this repo snapshot -- root cause is [U] to us. +- **The port's fix:** Not fixed at the point this repo snapshot ends; the live-switch worker treats an unresponsive trigger write as fatal (30s deadline, then park rather than reboot) rather than retrying. +- **Applies to us:** We should not assume the mainline stratix10-soc.c/stratix10-svc.c stack is bug-free on Agilex 5 just because the compatible-string gap (U1) is a known, fixable issue. There appears to be at least one more class of SDM-mailbox fragility on this platform that neither the port's fixes nor our D0.2 desk research have fully characterized; D2.5's hardware-measurement plan should treat 'the mailbox occasionally times out for reasons unrelated to buffer size' as an open risk to budget time against, not a solved problem. --- ## 4. Decisions for the owner -Every point below is a place his approach diverges from our documented plan. **These are not +Every point below is a place the port's approach diverges from our documented plan. **These are not recommendations.** The neutrality audit that was to strip advocacy from this section never ran, so read any evaluative wording with that in mind. ### DTSI provenance — fork mainline's socfpga_agilex5.dtsi vs override from the board DTS -- **His approach:** Forked mainline's socfpga_agilex5.dtsi and back-ported vendor nodes into it, including a wholesale replacement of the mainline gmac0 node. Board DTS is Terasic's file verbatim. +- **The port's approach:** Forked mainline's socfpga_agilex5.dtsi and back-ported vendor nodes into it, including a wholesale replacement of the mainline gmac0 node. Board DTS is Terasic's file verbatim. - **Our plan:** Parity-with-stock methodology: author our own board DTS on top of mainline's, using &label overrides, and keep patches off shared .dtsi files (the DE10 patch explicitly refuses to carry a socfpga.dtsi hunk for exactly this reason). -- **What forced his choice:** Forced for three of the nodes: mmc0, smmu and the firmware/svc subtree do not exist in mainline 6.18 at all, and the board DTS references &mmc, &smmu and &temp_volt by label, so their absence is a dtc compile error, not a silent gap (bf8e5c9c6 says so explicitly). The gmac0 replacement was forced by a real probe failure (e57c54c74) and he states it is a whole-node structural difference — split IRQs, reset-names, iommus — not expressible as a board-level override. -- **Cost of his way:** A forked shared .dtsi that every socfpga board in the tree inherits; every kernel bump is a merge against upstream's own evolving Agilex 5 DTSI, and upstream has already been seen to independently re-add a conflicting gmac0 revision. +- **What forced the port's choice:** Forced for three of the nodes: mmc0, smmu and the firmware/svc subtree do not exist in mainline 6.18 at all, and the board DTS references &mmc, &smmu and &temp_volt by label, so their absence is a dtc compile error, not a silent gap (the port's commit says so explicitly). The gmac0 replacement was forced by a real probe failure (documented in the port's own commit message) and the port states it is a whole-node structural difference — split IRQs, reset-names, iommus — not expressible as a board-level override. +- **Cost of the port's way:** A forked shared .dtsi that every socfpga board in the tree inherits; every kernel bump is a merge against upstream's own evolving Agilex 5 DTSI, and upstream has already been seen to independently re-add a conflicting gmac0 revision. - **Cost of our way:** Not available for mmc0/smmu/svc — there is nothing to override. A mainline-only DTS for this board does not boot from SD. We would have to add those nodes somewhere; the only parity-preserving choice is which file, and whether we upstream them. - **Our doc:** docs/de25-nano-plan.md §4.1; docs/dts-comparison.md; board/mister/de10nano/linux-patches/0004-dts-de10nano-MiSTer.patch (Forward-port note refusing the shared-dtsi hunk) ### Reconfiguration trigger — custom in-tree misc driver -- **His approach:** drivers/misc/de25_fpga_trigger.c: a new 95-line misc driver with one write-only sysfs attribute that re-applies a static .dtbo via of_overlay_fdt_apply(), plus a new Kconfig symbol CONFIG_DE25_FPGA_TRIGGER. +- **The port's approach:** drivers/misc/de25_fpga_trigger.c: a new 95-line misc driver with one write-only sysfs attribute that re-applies a static .dtbo via of_overlay_fdt_apply(), plus a new Kconfig symbol CONFIG_DE25_FPGA_TRIGGER. - **Our plan:** Not decided — our fpga-reconfig §11 row 6 lists three costed options (carry OF_CONFIGFS, write a board driver, U-Boot-preload only) and defers the choice to D2. -- **What forced his choice:** Forced: mainline exposes no userspace path to fpga_mgr_load(); of-fpga-region only programs on OF_OVERLAY_PRE_APPLY; and the configfs overlay path he had assumed (RPi-style drivers/of/configfs.c) was never upstream, so his script's core step had never once worked. -- **Cost of his way:** A permanently carried out-of-tree kernel patch adding a board-specific driver + Kconfig symbol, with no upstreaming story. The trigger has no quiesce sequencing of its own, so driving it directly is documented by its own author as unsafe. -- **Cost of our way:** U-Boot-preload-only means every core switch is a reboot (~35 s on his measurements), which is the UX premise our D0.2 called into question. OF_CONFIGFS is now known not to be a real option. +- **What forced the port's choice:** Forced: mainline exposes no userspace path to fpga_mgr_load(); of-fpga-region only programs on OF_OVERLAY_PRE_APPLY; and the configfs overlay path the port had assumed (RPi-style drivers/of/configfs.c) was never upstream, so the port's script's core step had never once worked. +- **Cost of the port's way:** A permanently carried out-of-tree kernel patch adding a board-specific driver + Kconfig symbol, with no upstreaming story. The trigger has no quiesce sequencing of its own, so driving it directly is documented by its own author as unsafe. +- **Cost of our way:** U-Boot-preload-only means every core switch is a reboot (~35 s on the port's measurements), which is the UX premise our D0.2 called into question. OF_CONFIGFS is now known not to be a real option. - **Our doc:** docs/de25-fpga-reconfig.md §11 row 6; §8 Claim A ### Runtime HPS↔fabric access — raw /dev/mem instead of UIO -- **His approach:** CONFIG_UIO is not set. Every fabric register access (sysid probe, ascal fbcfg base) is an mmap of /dev/mem at fixed physical addresses from Python scripts run by systemd units. +- **The port's approach:** CONFIG_UIO is not set. Every fabric register access (sysid probe, ascal fbcfg base) is an mmap of /dev/mem at fixed physical addresses from Python scripts run by systemd units. - **Our plan:** DP-9 (as narrowed by D0.2 §8) leaves the runtime signaling/aperture contract explicitly undecided and flags the /dev/mem practice as the thing patches 0044/0045 exist to replace. -- **What forced his choice:** No forcing reason found in the commits; it appears to be the path of least resistance carried forward from bring-up scripting. He has not needed an aperture binding because Main's fabric access is not yet performance-bound on his setup. -- **Cost of his way:** No named/size-bounded apertures, no write-combining, no page-attribute control, and STRICT_DEVMEM had to be worked around with a no-map reserved region for AO486's 256 MB window. Also no protection against Main touching a half-configured region — a documented board-wedge hazard. +- **What forced the port's choice:** No forcing reason found in the commits; it appears to be the path of least resistance carried forward from bring-up scripting. The port has not needed an aperture binding because Main's fabric access is not yet performance-bound on the port's setup. +- **Cost of the port's way:** No named/size-bounded apertures, no write-combining, no page-attribute control, and STRICT_DEVMEM had to be worked around with a no-map reserved region for AO486's 256 MB window. Also no protection against Main touching a half-configured region — a documented board-wedge hazard. - **Cost of our way:** Re-deriving 0043/0044/0045 for Agilex 5 is real work gated on a GHRD that defines the topology, which does not exist yet. - **Our doc:** docs/de25-fpga-reconfig.md §8 (Claim B refuted), §11 rows 10 and 11 ### Vsync interrupt / FBIO_WAITFORVSYNC -- **His approach:** No interrupts property on the fb node; MiSTer_fb.c patched so an absent IRQ is legal and the ioctl simply becomes unavailable. +- **The port's approach:** No interrupts property on the fb node; MiSTer_fb.c patched so an absent IRQ is legal and the ioctl simply becomes unavailable. - **Our plan:** Our D0.2 §7.3 names 'a per-frame interrupt from fabric to HPS' as one of four requirements for a MiSTer-style framebuffer and calls it 'the doorbell problem again'. -- **What forced his choice:** No f2h IRQ is wired in his golden_top/GHRD, and the mainline Agilex 5 DTSI has no bridge or f2h interrupt-cell scheme to name one. He worked around the driver's arm32 NO_IRQ check rather than wire an interrupt. -- **Cost of his way:** FBIO_WAITFORVSYNC is silently unavailable — an ABI Main_MiSTer uses on DE10. Any tearing/pacing behavior that depends on it is gone with no error. +- **What forced the port's choice:** No f2h IRQ is wired in the port's golden_top/GHRD, and the mainline Agilex 5 DTSI has no bridge or f2h interrupt-cell scheme to name one. The port worked around the driver's arm32 NO_IRQ check rather than wire an interrupt. +- **Cost of the port's way:** FBIO_WAITFORVSYNC is silently unavailable — an ABI Main_MiSTer uses on DE10. Any tearing/pacing behavior that depends on it is gone with no error. - **Cost of our way:** Requires the GHRD to expose an f2h interrupt and a documented Agilex 5 SPI number for it. Not resolvable from Linux alone. - **Our doc:** docs/de25-fpga-reconfig.md §7.3 (table row 3); board/mister/de10nano/linux-patches/0004-dts-de10nano-MiSTer.patch:195-201 ### Audio -- **His approach:** MiSTer-audio-spi driver ported and built as a module, but no DT node, so it never probes. No spi0/spidev nodes either. +- **The port's approach:** MiSTer-audio-spi driver ported and built as a module, but no DT node, so it never probes. No spi0/spidev nodes either. - **Our plan:** Our DE10 DTS wires spi0 → 'MiSTer,spi-audio' @10 MHz and spi1 → spidev for brightness/add-on control, as stock-parity ABI. -- **What forced his choice:** Not stated in any commit. Most likely simply not reached yet — his SETUP.md scope is reaching the MENU. I could not find a forcing reason. -- **Cost of his way:** No audio path through the documented MiSTer mechanism; whatever audio exists is fabric/HDMI-side only. /dev/spidev1.0 (Main's brightness.cpp) does not exist. +- **What forced the port's choice:** Not stated in any commit. Most likely simply not reached yet — the port's SETUP.md scope is reaching the MENU. I could not find a forcing reason. +- **Cost of the port's way:** No audio path through the documented MiSTer mechanism; whatever audio exists is fabric/HDMI-side only. /dev/spidev1.0 (Main's brightness.cpp) does not exist. - **Cost of our way:** Requires knowing which Agilex 5 SPI controller the GHRD wires to the audio link, which is a GHRD question. - **Our doc:** board/mister/de10nano/linux-patches/0004-dts-de10nano-MiSTer.patch (spi0/spi1 sections); docs/dts-comparison.md ### SD performance posture -- **His approach:** max-frequency = <25000000>, no-1-8-v, sd-uhs-sdr50 and the sdhci-caps/sdhci-caps-mask overrides removed; plus sdhci.debug_quirks=0x60 (PIO, no ADMA) baked into the production boot script. +- **The port's approach:** max-frequency = <25000000>, no-1-8-v, sd-uhs-sdr50 and the sdhci-caps/sdhci-caps-mask overrides removed; plus sdhci.debug_quirks=0x60 (PIO, no ADMA) baked into the production boot script. - **Our plan:** Parity with stock DE10 means SD performance is not deliberately crippled; nothing in our docs contemplates a PIO-mode root filesystem. -- **What forced his choice:** Both forced by real faults: corrupted SCR reads on post-JTAG boots (b3cc7d53f) and an SMMU F_TRANSLATION fault on mmc0 DMA that he established is a 6.12→6.18 kernel regression with a byte-identical devicetree (716559020). Root cause not bisected, deferred by explicit choice. -- **Cost of his way:** Default-speed 25 MHz 4-bit AND PIO for every block read on a Cortex-A55. This is a large, permanent, whole-system I/O penalty that touches every core load, every ROM load and the loop-mounted rootfs. It is the strongest DTS-level candidate for symptom (b) that is independent of reconfiguration cost. -- **Cost of our way:** Bisecting the 6.12→6.18 mmc0/SMMU regression, or shipping with the SMMU off / iommu.passthrough, each of which has its own consequences (his SETUP.md §9 warns iommu.passthrough=1 corrupts early SD init after a JTAG load). +- **What forced the port's choice:** Both forced by real faults: corrupted SCR reads on post-JTAG boots (the port's own commit) and an SMMU F_TRANSLATION fault on mmc0 DMA that the port established is a 6.12→6.18 kernel regression with a byte-identical devicetree (the port's retraction/reproduction commit). Root cause not bisected, deferred by explicit choice. +- **Cost of the port's way:** Default-speed 25 MHz 4-bit AND PIO for every block read on a Cortex-A55. This is a large, permanent, whole-system I/O penalty that touches every core load, every ROM load and the loop-mounted rootfs. It is the strongest DTS-level candidate for symptom (b) that is independent of reconfiguration cost. +- **Cost of our way:** Bisecting the 6.12→6.18 mmc0/SMMU regression, or shipping with the SMMU off / iommu.passthrough, each of which has its own consequences (the port's SETUP.md §9 warns iommu.passthrough=1 corrupts early SD init after a JTAG load). - **Our doc:** docs/de25-boot-chain.md §4 (posture); docs/de25-nano-plan.md §4.1 ### Core payload location — QSPI RSU slots vs SD files -- **His approach:** Original design staged core bitstreams into QSPI RSU application slots: P1 3 MB fixed MENU hub, plus (originally) a 4 MB P2 and a 3 MB P3, merged 2026-07-07 into one 7 MB staging slot so >4 MB cores would fit. The current design reads .uboot.rbf from the exFAT card instead. +- **The port's approach:** Original design staged core bitstreams into QSPI RSU application slots: P1 3 MB fixed MENU hub, plus (originally) a 4 MB P2 and a 3 MB P3, merged 2026-07-07 into one 7 MB staging slot so >4 MB cores would fit. The current design reads .uboot.rbf from the exFAT card instead. - **Our plan:** Our posture-1 v1 recommendation never writes QSPI at all; cores live on the FAT partition, as on DE10. -- **What forced his choice:** He flashes his own QSPI image anyway (his SPL is exFAT-aware), so the RSU slot mechanism was available and gave a reboot-based switch that worked before the kernel fpga_manager path did. -- **Cost of his way:** A hard, small ceiling on how many core images the firmware can hold — one MENU plus effectively one staged core — and a per-core QSPI write cycle. This is the most literal match for the owner's second-hand symptom (a), 'only being able to load a certain number of cores into firmware'. +- **What forced the port's choice:** The port flashes the port's own QSPI image anyway (the port's SPL is exFAT-aware), so the RSU slot mechanism was available and gave a reboot-based switch that worked before the kernel fpga_manager path did. +- **Cost of the port's way:** A hard, small ceiling on how many core images the firmware can hold — one MENU plus effectively one staged core — and a per-core QSPI write cycle. This is the most literal match for the owner's second-hand symptom (a), 'only being able to load a certain number of cores into firmware'. - **Cost of our way:** None on this axis; the SD-file model has no such ceiling. But it depends on a working live-switch or a reboot-with-fpga-load path. - **Our doc:** docs/de25-boot-chain.md §4 (posture 1 vs 2) ### Core-switch mechanism actually in production use -- **His approach:** A full warm reboot (~35s) per core switch, with U-Boot re-running `fpga load` at boot per a persisted core_select.txt. His in-kernel live DT-overlay reconfigure path (~8s target) exists as of Aug 1, 2026 but is not yet reliable — it can time out at the SDM mailbox and wedge the board, and its safety net (park the board rather than reboot on a stuck-mid-config SDM) is deliberate but user-visible. +- **The port's approach:** A full warm reboot (~35s) per core switch, with U-Boot re-running `fpga load` at boot per a persisted core_select.txt. The port's in-kernel live DT-overlay reconfigure path (~8s target) exists as of Aug 1, 2026 but is not yet reliable — it can time out at the SDM mailbox and wedge the board, and its safety net (park the board rather than reboot on a stuck-mid-config SDM) is deliberate but user-visible. - **Our plan:** Our D0.2 dossier's DP-9 concluded core-switching is 'UX-viable at low-to-moderate confidence', implicitly assuming a fast (non-reboot) in-kernel reconfigure path is achievable, with latency still an open unknown (U5) pending measurement. -- **What forced his choice:** The reboot fallback is what actually works reliably today; the live path is new (committed same day as the failed HW test) and explicitly documented by him as not yet safe for routine use ('the raw trigger has no quiesce/settle sequencing of its own... unsafe for routine use'). -- **Cost of his way:** 35s per core switch if the live path is disabled/unreliable — materially worse UX than instant Cyclone-V-style core swap, and a documented failure mode (board wedge, JTAG-only recovery) on the fast path as currently implemented. -- **Cost of our way:** Building and hardening the same live DT-overlay + SDM-mailbox mechanism ourselves, with no guarantee it clears the same 300ms RECONFIG_REQUEST timeout more reliably than his first attempt did — this is real engineering work, not a known-solved problem, and his result is the first empirical Agilex-5-hardware data point either project has. +- **What forced the port's choice:** The reboot fallback is what actually works reliably today; the live path is new (committed same day as the failed HW test) and explicitly self-documented as not yet safe for routine use ('the raw trigger has no quiesce/settle sequencing of its own... unsafe for routine use'). +- **Cost of the port's way:** 35s per core switch if the live path is disabled/unreliable — materially worse UX than instant Cyclone-V-style core swap, and a documented failure mode (board wedge, JTAG-only recovery) on the fast path as currently implemented. +- **Cost of our way:** Building and hardening the same live DT-overlay + SDM-mailbox mechanism ourselves, with no guarantee it clears the same 300ms RECONFIG_REQUEST timeout more reliably than the port's first attempt did — this is real engineering work, not a known-solved problem, and the port's result is the first empirical Agilex-5-hardware data point either project has. - **Our doc:** docs/de25-fpga-reconfig.md §8 (DP-9 verdict), §6 (latency, U5 marked open) ### Bitstream authentication (VAB) -- **His approach:** VAB is not enabled — only FIT image signing (CONFIG_SPL_FIT_SIGNATURE=y). Anyone who can write the SD card/QSPI can boot arbitrary code. +- **The port's approach:** VAB is not enabled — only FIT image signing (CONFIG_SPL_FIT_SIGNATURE=y). Anyone who can write the SD card/QSPI can boot arbitrary code. - **Our plan:** Not yet decided by our project (U4 was an open unknown, not yet dispositioned in policy). -- **What forced his choice:** No evidence of an explicit security requirement in his project; consistent with a developer-focused, JTAG-recoverable bring-up posture rather than a hardened consumer deployment. -- **Cost of his way:** No bitstream-tampering protection; acceptable for a dev board, not necessarily for a shipped image. -- **Cost of our way:** Enabling VAB would require CONFIG_SOCFPGA_SECURE_VAB_AUTH plus a real key/fusing workflow, a project we have not scoped and he has not attempted either. +- **What forced the port's choice:** No evidence of an explicit security requirement in the port's project; consistent with a developer-focused, JTAG-recoverable bring-up posture rather than a hardened consumer deployment. +- **Cost of the port's way:** No bitstream-tampering protection; acceptable for a dev board, not necessarily for a shipped image. +- **Cost of our way:** Enabling VAB would require CONFIG_SOCFPGA_SECURE_VAB_AUTH plus a real key/fusing workflow, a project we have not scoped and the port has not attempted either. - **Our doc:** docs/de25-boot-chain.md (U4 listed as open unknown) ### Non-stock ATF/BL31 buffer-size modification -- **His approach:** His comments describe carrying a custom BL31 change ('FPGA_CONFIG_BUFFER_SIZE=16' mod, later a 'BUF4' fix) affecting the SDM reconfigure buffer-reclaim BUSY-reply path — a modification to Arm Trusted Firmware itself, not to U-Boot or the kernel. +- **The port's approach:** The port's comments describe carrying a custom BL31 change ('FPGA_CONFIG_BUFFER_SIZE=16' mod, later a 'BUF4' fix) affecting the SDM reconfigure buffer-reclaim BUSY-reply path — a modification to Arm Trusted Firmware itself, not to U-Boot or the kernel. - **Our plan:** Our plan is to use a modern/current U-Boot as a reference and has not scoped any ATF/BL31-level customization at all. -- **What forced his choice:** Presumably to work around an observed >2MB live-reconfigure failure — but I could not verify this in code (the ATF source is not among the repos we have access to), so I cannot confirm whether it is a real fix, a misdiagnosis, or unnecessary against a stock/current ATF. -- **Cost of his way:** An unverifiable, out-of-band firmware customization layered under U-Boot that neither of us can currently audit. -- **Cost of our way:** If we adopt current/stock ATF without this change, we do not know whether we would hit the same >2MB wall he describes — unresolved, not established either way. +- **What forced the port's choice:** Presumably to work around an observed >2MB live-reconfigure failure — but I could not verify this in code (the ATF source is not among the repos we have access to), so I cannot confirm whether it is a real fix, a misdiagnosis, or unnecessary against a stock/current ATF. +- **Cost of the port's way:** An unverifiable, out-of-band firmware customization layered under U-Boot that neither of us can currently audit. +- **Cost of our way:** If we adopt current/stock ATF without this change, we do not know whether we would hit the same >2MB wall the port describes — unresolved, not established either way. - **Our doc:** docs/de25-nano-plan.md §4.1 (modern U-Boot direction) -### QSPI posture — he writes the SDM configuration flash; our v1 recommendation is never to write it +### QSPI posture — the port writes the SDM configuration flash; our v1 recommendation is never to write it -- **His approach:** Flashes a custom .jic over the factory QSPI via JTAG (golden_top_menu_exfat_spl_ro_bootcmd_20260704.jic), carrying his own exFAT-aware SPL, his own bootcmd, and his own fabric image. This is our posture 2. An earlier variant was HPS-First (Phase-1 .hps.jic, fabric left unconfigured for U-Boot to fill), and an even earlier one carried a full RSU application-slot layout (rsu_initial_v2). +- **The port's approach:** Flashes a custom .jic over the factory QSPI via JTAG (golden_top_menu_exfat_spl_ro_bootcmd_20260704.jic), carrying the port's own exFAT-aware SPL, the port's own bootcmd, and the port's own fabric image. This is our posture 2. An earlier variant was HPS-First (Phase-1 .hps.jic, fabric left unconfigured for U-Boot to fill), and an even earlier one carried a full RSU application-slot layout (rsu_initial_v2). - **Our plan:** Posture 1: pin factory QSPI byte-untouched, boot our own u-boot.itb from Terasic's factory SPL, treat the FSBL->u-boot.itb name+location contract as the only interface we own. -- **What forced his choice:** Forced, and the forcing reason is legible: he chose a single-partition exFAT card with the rootfs as a loop-mounted /linux/rootfs.ext4, and the factory SPL cannot read exFAT. His two DE25 U-Boot commits are exactly 'Add SPL exFAT boot support for DE25' (a new common/spl/spl_exfat.c plus an imported libexfat) and 'Make DE25 exFAT boot read-only and script-first'. An exFAT SPL cannot live on the SD card it must read, so it has to go into QSPI. -- **Cost of his way:** Every board needs a bench JTAG flash before it boots, with Quartus Pro on a PC. The QSPI becomes a per-board version-skew surface (his SETUP.md §10 troubleshooting is 'you flashed the wrong .jic'). It cost him at least one hard brick (2026-07-12: a stale RSU fallback armed reboot_image over a Phase-1 image, SDM booted garbage, JTAG reflash). And it makes the SPL/bootcmd our-code, not vendor-code, forever. +- **What forced the port's choice:** Forced, and the forcing reason is legible: the port chose a single-partition exFAT card with the rootfs as a loop-mounted /linux/rootfs.ext4, and the factory SPL cannot read exFAT. The port's two DE25 U-Boot commits are exactly 'Add SPL exFAT boot support for DE25' (a new common/spl/spl_exfat.c plus an imported libexfat) and 'Make DE25 exFAT boot read-only and script-first'. An exFAT SPL cannot live on the SD card it must read, so it has to go into QSPI. +- **Cost of the port's way:** Every board needs a bench JTAG flash before it boots, with Quartus Pro on a PC. The QSPI becomes a per-board version-skew surface (the port's SETUP.md §10 troubleshooting is 'you flashed the wrong .jic'). It cost the port at least one hard brick (2026-07-12: a stale RSU fallback armed reboot_image over a Phase-1 image, SDM booted garbage, JTAG reflash). And it makes the SPL/bootcmd our-code, not vendor-code, forever. - **Cost of our way:** We inherit whatever the factory SPL can read — realistically FAT — so the single-exFAT-card layout and the loop-root design are off the table for us, and the version-skew seam our de25-boot-chain.md §5 identifies stays. We also cannot fix an SPL bug without moving to posture 2/3. - **Our doc:** docs/de25-boot-chain.md §4 (postures 1/2/3) and §5 (version-skew seam); docs/de25-nano-plan.md §4.1; DP-1 ### Card layout — one exFAT partition with a loop-mounted ext4 rootfs vs our FAT + rootfs model -- **His approach:** Single active MBR partition, exFAT, spanning the card; boot files at the root; rootfs is /linux/rootfs.ext4 (3.5 GiB) loop-mounted as /dev/loop8 with the outer exFAT bind-mounted at /media/fat. Kernel-side init/do_mounts carries a custom mount_mister_loop_root path. +- **The port's approach:** Single active MBR partition, exFAT, spanning the card; boot files at the root; rootfs is /linux/rootfs.ext4 (3.5 GiB) loop-mounted as /dev/loop8 with the outer exFAT bind-mounted at /media/fat. Kernel-side init/do_mounts carries a custom mount_mister_loop_root path. - **Our plan:** DE10-style separation: a FAT boot partition read by the bootloader plus a rootfs the kernel mounts directly; persistent state on the FAT partition. -- **What forced his choice:** Stated in SETUP.md §1: it supersedes an earlier FAT32-p1 + ext4-p2 layout so the whole card is one user-visible exFAT volume (the MiSTer /media/fat experience) with no small boot partition to manage. It is what forced the exFAT SPL, hence the QSPI flash. -- **Cost of his way:** An exFAT SPL, a QSPI flash per board, a kernel patch to init/do_mounts, an ext4-in-a-file rootfs that cannot be grown or fsck'd normally, and a documented Windows failure mode copying the 3.5 GiB image (ERROR 1006 on some readers). He also hit live ext4-in-loop rootfs corruption during a failed boot incident. +- **What forced the port's choice:** Stated in SETUP.md §1: it supersedes an earlier FAT32-p1 + ext4-p2 layout so the whole card is one user-visible exFAT volume (the MiSTer /media/fat experience) with no small boot partition to manage. It is what forced the exFAT SPL, hence the QSPI flash. +- **Cost of the port's way:** An exFAT SPL, a QSPI flash per board, a kernel patch to init/do_mounts, an ext4-in-a-file rootfs that cannot be grown or fsck'd normally, and a documented Windows failure mode copying the 3.5 GiB image (ERROR 1006 on some readers). The port also hit live ext4-in-loop rootfs corruption during a failed boot incident. - **Cost of our way:** Two partitions the user can see, and a FAT-side/rootfs-side split for persistent state — exactly the DE10 model our persistent-state rule already assumes. - **Our doc:** docs/de25-boot-chain.md §3 ('What lives where'); docs/de25-nano-plan.md §4.1 ### U-Boot base version -- **His approach:** U-Boot 2025.01, the Terasic/Altera socfpga vendor fork (Makefile VERSION=2025 PATCHLEVEL=01), with his exFAT work applied on top. He never rebased. +- **The port's approach:** U-Boot 2025.01, the Terasic/Altera socfpga vendor fork (Makefile VERSION=2025 PATCHLEVEL=01), with the port's exFAT work applied on top. The port never rebased. - **Our plan:** Modern/current upstream U-Boot, per the owner's standing instruction. -- **What forced his choice:** He started from the board's own factory u-boot.itb (which identifies as 2025.01-gd0f8813fd6bf, i.e. a build of this tree at Terasic's board commit) and needed the vendor DDR/SDM/handoff code that is only in that fork. Rebasing was never the goal; booting was. -- **Cost of his way:** His SPL exFAT series is against a 2025.01 vendor fork; nothing of it is upstreamable as-is and it does not transplant to current U-Boot without work. He also inherits the fork's Agilex 5 quirks with no upstream fix path. -- **Cost of our way:** We must establish that current upstream U-Boot has enough Agilex 5 support for this board, and carry the Terasic board DTS/defconfig ourselves. His repo shows the board support commit is small and self-contained (8 files) — that is encouraging, not proof. +- **What forced the port's choice:** The port started from the board's own factory u-boot.itb (which identifies as 2025.01-gd0f8813fd6bf, i.e. a build of this tree at Terasic's board commit) and needed the vendor DDR/SDM/handoff code that is only in that fork. Rebasing was never the goal; booting was. +- **Cost of the port's way:** The port's SPL exFAT series is against a 2025.01 vendor fork; nothing of it is upstreamable as-is and it does not transplant to current U-Boot without work. The port also inherits the fork's Agilex 5 quirks with no upstream fix path. +- **Cost of our way:** We must establish that current upstream U-Boot has enough Agilex 5 support for this board, and carry the Terasic board DTS/defconfig ourselves. The port's repo shows the board support commit is small and self-contained (8 files) — that is encouraging, not proof. - **Our doc:** docs/uboot-mainline-port.md / docs/uboot-tasks.md; owner's standing instruction ### Core storage model — cores in QSPI 'firmware' slots vs cores as files on the card -- **His approach:** Three successive models: (Tier C/RSU) stage a core's .rpd into a QSPI RSU application slot via the FCS mailbox and reboot the SDM into it; (Tier C/launcher) leave cores on the card as .uboot.rbf and warm-reboot so U-Boot fpga-loads the selection named in /core_select.txt; (live) kernel fpga_manager overlay, no reboot. He moved forward through all three; the RSU tier is now explicitly defused. +- **The port's approach:** Three successive models: (Tier C/RSU) stage a core's .rpd into a QSPI RSU application slot via the FCS mailbox and reboot the SDM into it; (Tier C/launcher) leave cores on the card as .uboot.rbf and warm-reboot so U-Boot fpga-loads the selection named in /core_select.txt; (live) kernel fpga_manager overlay, no reboot. The port moved forward through all three; the RSU tier is now explicitly defused. - **Our plan:** Cores are ordinary files on the card, loaded at runtime through the kernel fpga_manager + DT overlay; QSPI is never written. -- **What forced his choice:** The forcing reason is legible from the tier ordering: the RSU path was the first thing that worked, because it needs no working overlay loader and no kernel fpga_manager binding — the SDM does the configuration at reset. He only reached the fpga_manager path after fixing the agilex5 compatibles on 2026-08-01. -- **Cost of his way:** This IS symptom (a) and half of symptom (b): a hard cap of MENU + two (then one) cores resident in firmware, minutes to re-stage, a reboot per switch, and a brick class where a mis-armed reboot_image points the SDM at a slot that no longer holds a valid image. +- **What forced the port's choice:** The forcing reason is legible from the tier ordering: the RSU path was the first thing that worked, because it needs no working overlay loader and no kernel fpga_manager binding — the SDM does the configuration at reset. The port only reached the fpga_manager path after fixing the agilex5 compatibles on 2026-08-01. +- **Cost of the port's way:** This IS symptom (a) and half of symptom (b): a hard cap of MENU + two (then one) cores resident in firmware, minutes to re-stage, a reboot per switch, and a brick class where a mis-armed reboot_image points the SDM at a slot that no longer holds a valid image. - **Cost of our way:** We depend entirely on the overlay path working, repeatedly, which our §6.1 row 4a and §3.2 flag as the unexercised risk — with no fallback tier if it does not. - **Our doc:** docs/de25-fpga-reconfig.md §2, §3.2 option (c); docs/de25-nano-plan.md DP-9 ### Overlay loader choice -- **His approach:** Currently option (b): a custom ~95-line misc driver (de25_fpga_trigger) over of_overlay_fdt_apply(), applying a STATIC pre-built .dtbo that names a FIXED firmware path, with the chosen core copied to that path first. Previously he relied on option (a), the vendor kernel's CONFIG_OF_CONFIGFS, without knowing it was vendor-only. +- **The port's approach:** Currently option (b): a custom ~95-line misc driver (de25_fpga_trigger) over of_overlay_fdt_apply(), applying a STATIC pre-built .dtbo that names a FIXED firmware path, with the chosen core copied to that path first. Previously the port relied on option (a), the vendor kernel's CONFIG_OF_CONFIGFS, without knowing it was vendor-only. - **Our plan:** Undecided between (a) carry OF_CONFIGFS, (b) our own small driver, (c) U-Boot preload only; (c) is sufficient for the L1 developer OS. -- **What forced his choice:** Not a design preference — a forced discovery. His scripts assumed configfs because every Altera document describes it; when he moved from the stock 6.12.11 kernel (CONFIG_OF_CONFIGFS=y) to his mainline-based 6.18 port, the path silently vanished and his core-write step failed structurally. -- **Cost of his way:** ~95 lines and a UAPI he owns forever, plus a static-overlay design that cannot vary anything per switch except the file contents at a fixed path. In exchange: no out-of-tree Kconfig carry and no dependence on an unmerged 2014 series. +- **What forced the port's choice:** Not a design preference — a forced discovery. The port's scripts assumed configfs because every Altera document describes it; when the port moved from the stock 6.12.11 kernel (CONFIG_OF_CONFIGFS=y) to the port's mainline-based 6.18 port, the path silently vanished and the port's core-write step failed structurally. +- **Cost of the port's way:** ~95 lines and a UAPI the port owns forever, plus a static-overlay design that cannot vary anything per switch except the file contents at a fixed path. In exchange: no out-of-tree Kconfig carry and no dependence on an unmerged 2014 series. - **Cost of our way:** Option (a) matches every vendor recipe verbatim but carries a patch unmerged since 2014; option (c) means no runtime core switching at all. - **Our doc:** docs/de25-fpga-reconfig.md §3.2 (option table a/b/c) ### Fabric-at-boot posture — HPS-First (fabric unconfigured until something loads a core) vs golden/MENU-in-QSPI -- **His approach:** Tried HPS-First (Phase-1 .hps.jic; U-Boot fpga-loads the selected core before booting Linux) and RETIRED it in favour of a golden .jic that configures a MENU fabric from QSPI at every reset. Guards were needed either way: a de25_core= bootargs breadcrumb, an /media/fat/HPS_FIRST_MODE marker, and a fabric-ready probe, because on Agilex 5 an MMIO read of an unconfigured fabric is a fatal async SError (kernel panic), not a failed probe. +- **The port's approach:** Tried HPS-First (Phase-1 .hps.jic; U-Boot fpga-loads the selected core before booting Linux) and RETIRED it in favour of a golden .jic that configures a MENU fabric from QSPI at every reset. Guards were needed either way: a de25_core= bootargs breadcrumb, an /media/fat/HPS_FIRST_MODE marker, and a fabric-ready probe, because on Agilex 5 an MMIO read of an unconfigured fabric is a fatal async SError (kernel panic), not a failed probe. - **Our plan:** Not yet decided; our de25-boot-chain.md assumes the factory QSPI configures the fabric and our fpga-reconfig doc assumes Linux reconfigures it afterwards. -- **What forced his choice:** HPS-First produced a reproducible fatal async SError on the first fabric read ~26 s into every warm-reboot boot, board unreachable, JTAG recovery required — he explicitly tested and REFUTED the leading hypothesis (a redundant double `bridge enable`) and still could not fix it. Retiring the posture was the fix. -- **Cost of his way:** A cold boot always lands on the MENU fabric, matching DE10's menu.rbf-at-every-boot guarantee, at the cost of putting a fabric image in QSPI (posture 2 again). +- **What forced the port's choice:** HPS-First produced a reproducible fatal async SError on the first fabric read ~26 s into every warm-reboot boot, board unreachable, JTAG recovery required — the port explicitly tested and REFUTED the leading hypothesis (a redundant double `bridge enable`) and still could not fix it. Retiring the posture was the fix. +- **Cost of the port's way:** A cold boot always lands on the MENU fabric, matching DE10's menu.rbf-at-every-boot guarantee, at the cost of putting a fabric image in QSPI (posture 2 again). - **Cost of our way:** If we stay on the factory QSPI we inherit whatever fabric Terasic's golden image configures, and our first fabric access from Linux must be guarded the same way — an unguarded probe is a panic, not an error. - **Our doc:** docs/de25-boot-chain.md §2, §7; docs/de25-fpga-reconfig.md §7 ### Warm reboot as a core-switch mechanism, and the cold/warm indistinguishability it forces -- **His approach:** The reboot tier writes /core_select.txt and reboots; because U-Boot cannot tell a deliberate reboot from a power-cycle (RSTMGR_SOC64_STATUS reads 'Reset state: Cold' for both, and ATF's own handler for every software reboot is mailbox_reset_cold()), he added a one-shot /core_switch_pending.txt marker that boot.scr consumes on read, so any unplanned reset falls back to MENU. +- **The port's approach:** The reboot tier writes /core_select.txt and reboots; because U-Boot cannot tell a deliberate reboot from a power-cycle (RSTMGR_SOC64_STATUS reads 'Reset state: Cold' for both, and ATF's own handler for every software reboot is mailbox_reset_cold()), the port added a one-shot /core_switch_pending.txt marker that boot.scr consumes on read, so any unplanned reset falls back to MENU. - **Our plan:** No reboot-based switching contemplated; our Q5 parks warm-reboot behaviour as [U]. -- **What forced his choice:** Forced by the SoC: he validated the indistinguishability against his own cold-boot and warm-reboot serial logs. Without the marker, a core that hangs at boot is retried forever. -- **Cost of his way:** An extra file-based state machine spanning Linux and U-Boot, and a boot that must be orchestrated by exactly one code path to be honoured. +- **What forced the port's choice:** Forced by the SoC: the port validated the indistinguishability against the port's own cold-boot and warm-reboot serial logs. Without the marker, a core that hangs at boot is retried forever. +- **Cost of the port's way:** An extra file-based state machine spanning Linux and U-Boot, and a boot that must be orchestrated by exactly one code path to be honoured. - **Cost of our way:** None directly — but Q5 should record that a warm reboot on this SoC is a full SDM reconfiguration cycle and is not distinguishable in software from a cold boot. - **Our doc:** docs/de25-boot-chain.md §8.5 (Q5, warm reboot parked) ### Kernel base -- **His approach:** A mainline-derived 6.18.38 tree with the DE25 board port and the MiSTer kernel series merged in, plus a de25_defconfig. The board's shipped rootfs still carries modules for the Terasic 6.12.11 vendor kernel, and his autostart script insmods loose .ko files by absolute path because they are not depmod-wired. +- **The port's approach:** A mainline-derived 6.18.38 tree with the DE25 board port and the MiSTer kernel series merged in, plus a de25_defconfig. The board's shipped rootfs still carries modules for the Terasic 6.12.11 vendor kernel, and the port's autostart script insmods loose .ko files by absolute path because they are not depmod-wired. - **Our plan:** Buildroot-built mainline 6.18.44 with our patch series, one coherent module tree. -- **What forced his choice:** He is running Terasic's STOCK rootfs (extracted from de25_nano_revA_sdcard_console_v1.1.img p2) with an overlay dropped on top, so the userspace and the kernel come from different worlds and the seams are patched by hand. -- **Cost of his way:** Hand-installed modules, an insmod-by-path loop in a service, and a documented class of silent failure when the module tree and the running kernel disagree — the same trap our own memory notes call out for kernel bumps. +- **What forced the port's choice:** The port is running Terasic's STOCK rootfs (extracted from de25_nano_revA_sdcard_console_v1.1.img p2) with an overlay dropped on top, so the userspace and the kernel come from different worlds and the seams are patched by hand. +- **Cost of the port's way:** Hand-installed modules, an insmod-by-path loop in a service, and a documented class of silent failure when the module tree and the running kernel disagree — the same trap our own memory notes call out for kernel bumps. - **Cost of our way:** We build the rootfs, so this class does not arise; but we also do not get Terasic's vendor userspace for free. - **Our doc:** docs/de25-nano-plan.md; ADR 0027 (bare developer OS scope) ### QSPI-resident boot posture -- **His approach:** He flashes his own custom .jic (a Terasic golden_top GHRD rebuild with a custom exFAT-aware, read-only, script-first SPL) over JTAG, rather than the factory-shipped image. His SPL sources boot.scr.uimg from an exFAT partition instead of the DE10-style FAT32/0xA2 split. +- **The port's approach:** The port flashes the port's own custom .jic (a Terasic golden_top GHRD rebuild with a custom exFAT-aware, read-only, script-first SPL) over JTAG, rather than the factory-shipped image. The port's SPL sources boot.scr.uimg from an exFAT partition instead of the DE10-style FAT32/0xA2 split. - **Our plan:** Posture 1 (v1 recommendation, not yet decided): pin factory QSPI byte-untouched, never write it, and prove our own u-boot.itb loads under Terasic's unmodified factory SPL. -- **What forced his choice:** His stated reason (SETUP.md §1/§3) is that the persistent exFAT + single-partition loop-root boot flow needed an exFAT-aware SPL, which the factory SPL is not; an earlier all-core U-Boot fpga-load launcher approach wedged the SDM and was retired, motivating a rebuilt, more conservative SPL. -- **Cost of his way:** He owns SDM firmware + FSBL + DDR/pinmux handoff for every board onboarded (a bench JTAG operation per unit); factory-FSBL compatibility with any mainline artifact (U3) is now untested and unknown for his design. +- **What forced the port's choice:** The port's stated reason (SETUP.md §1/§3) is that the persistent exFAT + single-partition loop-root boot flow needed an exFAT-aware SPL, which the factory SPL is not; an earlier all-core U-Boot fpga-load launcher approach wedged the SDM and was retired, motivating a rebuilt, more conservative SPL. +- **Cost of the port's way:** The port owns SDM firmware + FSBL + DDR/pinmux handoff for every board onboarded (a bench JTAG operation per unit); factory-FSBL compatibility with any mainline artifact (U3) is now untested and unknown for the port's design. - **Cost of our way:** Posture 1 constrains the SD-side boot flow to whatever partition scheme the factory SPL can read (its SPL_FS_LOAD_PAYLOAD_NAME/FS_BOOT_PARTITION contract), and is untested against a mainline-built u-boot.itb until D2.2's first hardware pass. - **Our doc:** docs/de25-boot-chain.md §4 (three postures) and §8.3 (Q3, parked) ### Core-switch mechanism and QSPI role -- **His approach:** Runtime core switching is a two-tier system: a fast, no-reboot Linux-kernel overlay path (de25_fpga_trigger -> of_overlay_fdt_apply -> stratix10-soc.c) as the default, falling back to a warm-reboot U-Boot fpga-load path; an earlier QSPI-resident RSU multi-slot staging scheme for baking cores into flash was tried and partly retired after a board-wedging failure. +- **The port's approach:** Runtime core switching is a two-tier system: a fast, no-reboot Linux-kernel overlay path (de25_fpga_trigger -> of_overlay_fdt_apply -> stratix10-soc.c) as the default, falling back to a warm-reboot U-Boot fpga-load path; an earlier QSPI-resident RSU multi-slot staging scheme for baking cores into flash was tried and partly retired after a board-wedging failure. - **Our plan:** DP-9 (decided in direction): DTS/fpga-region overlay configuration is the sole native reconfiguration architecture on Agilex 5, adopted in place of the DE10's carried UIO doorbell patches. Our plan does not touch QSPI for core switching at all (per de25-boot-chain.md §5's 'no release writes QSPI' rule). -- **What forced his choice:** QSPI staging was his path to give any-size cores a boot-time selection mechanism before the Linux overlay driver existed and worked; it was abandoned once the overlay-based live-switch matured, not because QSPI residency was ever the intended long-term design. -- **Cost of his way:** He hit two board-wedging failures along the way (the retired all-core fpga-load launcher, and the 2026-07-12 stale-.rpd/reboot_image wedge) before landing on the Linux-overlay-based approach our DP-9 already independently arrived at from source-code reading alone. -- **Cost of our way:** None yet paid -- D0.2's overlay-only conclusion is confirmed as correct by his hardware trail, at the cost of two wedges he already absorbed and we have not. +- **What forced the port's choice:** QSPI staging was the port's path to give any-size cores a boot-time selection mechanism before the Linux overlay driver existed and worked; it was abandoned once the overlay-based live-switch matured, not because QSPI residency was ever the intended long-term design. +- **Cost of the port's way:** The port hit two board-wedging failures along the way (the retired all-core fpga-load launcher, and the 2026-07-12 stale-.rpd/reboot_image wedge) before landing on the Linux-overlay-based approach our DP-9 already independently arrived at from source-code reading alone. +- **Cost of our way:** None yet paid -- D0.2's overlay-only conclusion is confirmed as correct by the port's hardware trail, at the cost of two wedges the port already absorbed and we have not. - **Our doc:** docs/de25-fpga-reconfig.md §8 (DP-9 verdict), docs/de25-nano-plan.md §6 DP-9 ### HPS Ethernet MAC address -- **His approach:** A single fixed MAC (CE:24:6F:BD:E3:59) baked into a NetworkManager cloned-mac-address profile shipped inside the released rootfs image -- identical on every board flashed from that image. +- **The port's approach:** A single fixed MAC (CE:24:6F:BD:E3:59) baked into a NetworkManager cloned-mac-address profile shipped inside the released rootfs image -- identical on every board flashed from that image. - **Our plan:** No DE25-specific mechanism designed yet; the DE10 precedent (ADR-adjacent, installer-overlay/init) generates one random locally-administered MAC per physical card at flash time and injects it via U-Boot's fdt_fixup_ethernet()/aliases mechanism, giving each card a distinct address; the project's stated philosophy (ADR 0015, per-device SSH host keys) explicitly rejects shipping identical per-device identifiers. -- **What forced his choice:** His stated reason (comment in the .nmconnection file) is purely pragmatic: get a deterministic DHCP lease for a single development board without touching U-Boot env/QSPI at all, given the HPS has no fused MAC and CONFIG_NET_RANDOM_ETHADDR randomizes it every cold boot. -- **Cost of his way:** Every board flashed from his released rootfs image collides on the same MAC and the same DHCP lease if two are ever on the same network -- fine for one dev unit, not viable as a multi-user distribution default. +- **What forced the port's choice:** The port's stated reason (comment in the .nmconnection file) is purely pragmatic: get a deterministic DHCP lease for a single development board without touching U-Boot env/QSPI at all, given the HPS has no fused MAC and CONFIG_NET_RANDOM_ETHADDR randomizes it every cold boot. +- **Cost of the port's way:** Every board flashed from the port's released rootfs image collides on the same MAC and the same DHCP lease if two are ever on the same network -- fine for one dev unit, not viable as a multi-user distribution default. - **Cost of our way:** Requires either reusing the DE10's flash-time random-MAC-into-u-boot.txt mechanism (needs a persisted env location on the exFAT/loop-root layout, which is a different boot flow than DE10's) or a first-boot systemd/udev generator writing a per-device NetworkManager profile -- undesigned, not yet costed. - **Our doc:** board/mister/de10nano/installer-overlay/init:856-885; memory: ssh-host-keys-shared-across-all-misters (ADR 0015 philosophy) @@ -661,35 +660,35 @@ read any evaluative wording with that in mind. ## 5. Open questions this pass did not close -- Does the DE25-Nano HPS actually have 2 GiB of DRAM? His memory node hard-codes reg = <0 0x80000000 0 0x80000000> despite a comment saying the bootloader fills it in, while our plan §4.1 records 1 GB LPDDR4 for the HPS from the User Manual. If it is 1 GiB, x86ram@b0000000+0x10000000 sits exactly at the top of RAM and the declared size is wrong — worth checking against a booted /proc/meminfo or the U-Boot log before we copy any of this map. -- Is his claim that the FDT reserved-memory scanner silently drops the LAST child node true on 6.18? He designs around it (x86ram deliberately not last) but cites only a document in a parent repo I do not have. If false, it is harmless superstition; if true, it is a mainline bug we should know about. -- Which of the four SMMU stream IDs (gmac0=1, mmc0=5, usb0=6, svc=10) are architectural for Agilex 5 and which are Terasic-GHRD-specific? He inherited all four from the vendor BSP without a citation to an Intel document. -- Does the svc node actually require iommus=<&smmu 10> and altr,smmu_enable_quirk, or are they cargo from the vendor tree? He never tested the form without them, and mainline gen1 socfpga_agilex.dtsi has no such properties on its svc node. +- Does the DE25-Nano HPS actually have 2 GiB of DRAM? The port's memory node hard-codes reg = <0 0x80000000 0 0x80000000> despite a comment saying the bootloader fills it in, while our plan §4.1 records 1 GB LPDDR4 for the HPS from the User Manual. If it is 1 GiB, x86ram@b0000000+0x10000000 sits exactly at the top of RAM and the declared size is wrong — worth checking against a booted /proc/meminfo or the U-Boot log before we copy any of this map. +- Is the port's claim that the FDT reserved-memory scanner silently drops the LAST child node true on 6.18? The port designs around it (x86ram deliberately not last) but cites only a document in a parent repo I do not have. If false, it is harmless superstition; if true, it is a mainline bug we should know about. +- Which of the four SMMU stream IDs (gmac0=1, mmc0=5, usb0=6, svc=10) are architectural for Agilex 5 and which are Terasic-GHRD-specific? The port inherited all four from the vendor BSP without a citation to an Intel document. +- Does the svc node actually require iommus=<&smmu 10> and altr,smmu_enable_quirk, or are they cargo from the vendor tree? The port never tested the form without them, and mainline gen1 socfpga_agilex.dtsi has no such properties on its svc node. - Is the ~3 s / 3.3 MB reconfiguration figure real? It appears only as a source comment citing analysis/SUB10S_ATF_KERNEL_DIVERGENCE.md and a COM9 serial log, neither of which is present in this repo. The single documented HW attempt at the kernel fpga_manager path FAILED with 'timeout waiting for RECONFIG_REQUEST', so the ~3 s number most plausibly comes from the U-Boot fpga-load path, not the Linux path. Someone should establish which path it measures before D0.2's UX conclusion is revisited on it. -- Does the >2 MB live-load wall really disappear with a stock (unmodified) BL31, or only with his BUF4 build? He attributes the wall to his own FPGA_CONFIG_BUFFER_SIZE=16 modification, which would mean a stock ATF never had the problem — but that is his diagnosis of his own bug, and it is the load-bearing claim for symptom (a) being self-inflicted rather than intrinsic. -- How does Main_MiSTer find the HDMI transmitter on DE25? On DE10 it probes 0x39 across /dev/i2c-0..2 (video.cpp:1448) and refuses to scan past bus 2. His DTS enables exactly one HPS I2C adapter with no children, and the ADV7513 control I2C is on FPGA pins. Either his aarch64 Main fork removed that probe or the transmitter is configured entirely from the fabric — neither is established here. +- Does the >2 MB live-load wall really disappear with a stock (unmodified) BL31, or only with the port's BUF4 build? The port attributes the wall to the port's own FPGA_CONFIG_BUFFER_SIZE=16 modification, which would mean a stock ATF never had the problem — but that is the port's diagnosis of the port's own bug, and it is the load-bearing claim for symptom (a) being self-inflicted rather than intrinsic. +- How does Main_MiSTer find the HDMI transmitter on DE25? On DE10 it probes 0x39 across /dev/i2c-0..2 (video.cpp:1448) and refuses to scan past bus 2. The port's DTS enables exactly one HPS I2C adapter with no children, and the ADV7513 control I2C is on FPGA pins. Either the port's aarch64 Main fork removed that probe or the transmitter is configured entirely from the fabric — neither is established here. - Where did the audio path go? MiSTer-audio-spi is ported and built as a module but has no DT node. Is HDMI audio driven entirely from the fabric on this board, making the SPI audio link unnecessary, or is audio simply not implemented yet? -- Whether the ascal-scratch/framebuffer split at 0x9e000000 / 0xa0000000 is forced by hardware or is a consequence of his particular golden_top's fbcfg wiring. If it is a design choice, our port need not inherit the 64 MiB of reservations. +- Whether the ascal-scratch/framebuffer split at 0x9e000000 / 0xa0000000 is forced by hardware or is a consequence of the port's particular golden_top's fbcfg wiring. If it is a design choice, our port need not inherit the 64 MiB of reservations. - Does 'a certain number of cores' refer to the 8-core roster limit (CORES.manifest) or the (claimed-fixed, unverifiable) >2MB BL31 buffer wall, or both? Ambiguous, not disambiguated by any source available to us. -- Is the claimed 'NES live-switches in ~3s' result (script header, referencing an analysis file not present in either repo we have access to) still valid, given the same-day 30d9c99a8 commit shows a fresh standalone HW test of the reconfigure mechanism failing? Could not resolve. -- Does the 'FPGA_CONFIG_BUFFER_SIZE'/'BUF4 BL31' fix he describes actually exist and do what the comments claim? The ATF/arm-trusted-firmware source is not among the repos we were given — unverifiable from here. -- Why does our own linux-6.18.44 baseline lack the agilex5-svc/soc-fpga-mgr/fpga-region DT nodes that his 6.18.38-based tree (a numerically earlier stable point release) already has? Worth checking directly against a plain kernel.org 6.18.44 tarball rather than assuming either tree's provenance. +- Is the claimed 'NES live-switches in ~3s' result (script header, referencing an analysis file not present in either repo we have access to) still valid, given a same-day commit shows a fresh standalone HW test of the reconfigure mechanism failing? Could not resolve. +- Does the 'FPGA_CONFIG_BUFFER_SIZE'/'BUF4 BL31' fix the port describes actually exist and do what the comments claim? The ATF/arm-trusted-firmware source is not among the repos we were given — unverifiable from here. +- Why does our own linux-6.18.44 baseline lack the agilex5-svc/soc-fpga-mgr/fpga-region DT nodes that the port's 6.18.38-based tree (a numerically earlier stable point release) already has? Worth checking directly against a plain kernel.org 6.18.44 tarball rather than assuming either tree's provenance. - Was the factory Terasic FSBL ever tested against a non-custom (mainline-built) u-boot.itb? No evidence either way in the materials available. -- What is the real Rev A vs Rev B silicon stepping of the DE25-Nano boards our project would target, given the Intel Rev-A-only U-Boot workaround (2030244a1e) is conditional on that? -- His record is internally contradictory about whether a no-reboot core switch was EVER achieved. 7fb52ac3f (2026-07-13) claims 'HW-proven a2600<->NES round trip in 16-18s' via the configfs overlay; 881d4404a (2026-08-01) says that same write 'had ALWAYS failed here, structurally, since before this file's own history begins'. The reconciliation the dates support is that the July tests ran on Terasic's stock 6.12.11 kernel (CONFIG_OF_CONFIGFS=y [V]) and the August finding is about his mainline 6.18 port, which first booted on hardware 2026-07-24 [V]. Worth asking him directly — because if the 16-18 s round trip is real, it is the single most valuable data point in existence for our D0.2 §6, and if it is not, nobody has yet switched a core on Agilex 5 without a reboot. -- Which symptom did the owner's friend actually report, and from which era? '~35 s reboot per switch' (launcher tier, 2026-07-13 onward), 'minutes per switch' (RSU/FCS staging tier, before that), and 'only small cores load' (the 2 MiB svc-buffer wall) are three different experiences of three different designs. The second-hand phrasing 'a certain number of cores into firmware' matches the RSU tier specifically. -- The backing artifacts for every timing claim are absent from the shared repos: analysis/SUB10S_ATF_KERNEL_DIVERGENCE.md, the whole memory/*.md set referenced by name in a dozen script comments, com9_coldboot_throttle_validate.log / com9_warmreboot_throttle_validate.log, and analysis/boot-hf-launcher-production.cmd. If he can share the COM9 serial logs and the SUB10S analysis, that is the only path to closing U5 without our own hardware. -- Exactly what his BL31 'FPGA_CONFIG_BUFFER_SIZE=16' modification changed, and whether stock ATF (2.12.0 per Terasic's build record, or 2.14) has the same behaviour. The kernel-side 2 MiB geometry is [V] mainline; the ATF side is his prose only, and neither reference repo contains ATF source. If the BUSY/BUFFER_DONE contract is fragile in stock ATF too, the 2 MiB wall becomes OUR problem and not just his. -- Is the DE25's QSPI actually 16 MiB or 256 MiB? The Terasic U-Boot DTS says mt25qu02g (256 MiB) but the node is verbatim SoCDK boilerplate; his RSU layout terminates exactly at 16 MiB. This bears directly on our Q4 ('Does RSU fit in 16 MB?') — if the part is really 2 Gbit, that question changes shape entirely. Settle it against the DE25 UM/schematic or an on-board `sf probe`, not against a copied DTS node. -- Whether Terasic's FACTORY QSPI (as opposed to his replacement .jic) enforces bitstream authentication. His unsigned-RBF success is on a board whose QSPI he overwrote; U4 is answered for the silicon but not strictly for the factory configuration. -- Whether the fpga-region overlay path survives DOZENS of apply/remove cycles — our §3.2's 'no mainline user at all' concern. His design deliberately re-applies a STATIC overlay via of_overlay_remove() + of_overlay_fdt_apply() each time, which is exactly that pattern, but he has not run it end-to-end even once. -- Whether the golden/MENU-in-QSPI posture he settled on is compatible with our posture 1 at all: does Terasic's FACTORY .jic configure a fabric that a MiSTer MENU can run on, or does reaching the MENU inherently require replacing the QSPI fabric image? If the latter, posture 1 is only viable for the L1 developer OS and DP-1 must be re-opened for the MiSTer image. Nothing in either repo answers this — he never ran on the factory .jic. -- What exactly is the 'throttle'/'budget depleted' mailbox issue referenced in commit 30d9c99a8? The memory files it cites are not in this repo snapshot (they live in an external monorepo we were not given), so its root cause, frequency, and whether it is deterministic or transient remain unknown to us. -- Does Terasic's factory-shipped (unmodified) SPL/FSBL accept a mainline-built u-boot.itb at all (U3)? Neither this task nor his repo has ever tested it -- he replaced the SPL before first boot. This is the single biggest open item for our posture-1 plan and is not answerable from either reference repo; it needs a real board running the untouched factory .jic. -- Is his board's permissive VAB state (U4) representative of the DE25-Nano product line generally, or could Terasic ship (or a customer provision) units with VAB fused? His evidence is single-unit and structural (an unsigned .jic boots), not a statement about eFuse policy across the SKU. -- Does the mmc0/SMMU F_TRANSLATION regression (6.12->6.18 window) have a known upstream fix, a tracked kernel bug, or a narrower root cause than 'somewhere in arm-smmu-v3 or DMA-mapping'? He explicitly deferred bisection; this is worth a targeted search before our own D0.3/D2 work reaches the same wall. -- What is the actual current state of end-to-end live core switching as of his latest commits (881d4404a, 2026-08-01)? SETUP.md (dated 2026-07-04) says core switching is not wired up; later commits show it working in principle (mechanism HW-confirmed) but the most recent standalone test of the new trigger driver hit a mailbox timeout. Whether a full menu-driven live switch has been demonstrated end-to-end with all fixes (agilex5 compatibles + de25_fpga_trigger + BUF4 BL31) applied together is not established from what's in this repo. -- Does his DP-1-relevant Terasic stock image (de25_nano_revA_sdcard_console_v1.1.img) represent a genuine board-support reference analogous to what DE10's stock-shadowing model needs, or is it materially different in kind (a generic Linux console BSP vs. a running MiSTer install)? This bears on whether DP-1's premise needs revisiting and by how much -- flagged, not resolved, per the task's standing instruction not to decide DP-1 here. +- What is the real Rev A vs Rev B silicon stepping of the DE25-Nano boards our project would target, given the Intel Rev-A-only U-Boot workaround (Altera vendor commit 2030244a1e) is conditional on that? +- The port's record is internally contradictory about whether a no-reboot core switch was EVER achieved. A 2026-07-13 commit claims 'HW-proven a2600<->NES round trip in 16-18s' via the configfs overlay; a 2026-08-01 commit says that same write 'had ALWAYS failed here, structurally, since before this file's own history begins'. The reconciliation the dates support is that the July tests ran on Terasic's stock 6.12.11 kernel (CONFIG_OF_CONFIGFS=y [V]) and the August finding is about the port's mainline 6.18 port, which first booted on hardware 2026-07-24 [V]. Worth asking the port's author directly — because if the 16-18 s round trip is real, it is the single most valuable data point in existence for our D0.2 §6, and if it is not, nobody has yet switched a core on Agilex 5 without a reboot. +- Which symptom did the PORT's author actually report, and from which era? '~35 s reboot per switch' (launcher tier, 2026-07-13 onward), 'minutes per switch' (RSU/FCS staging tier, before that), and 'only small cores load' (the 2 MiB svc-buffer wall) are three different experiences of three different designs. The second-hand phrasing 'a certain number of cores into firmware' matches the RSU tier specifically. +- The backing artifacts for every timing claim are absent from the shared repos: analysis/SUB10S_ATF_KERNEL_DIVERGENCE.md, the whole memory/*.md set referenced by name in a dozen script comments, com9_coldboot_throttle_validate.log / com9_warmreboot_throttle_validate.log, and analysis/boot-hf-launcher-production.cmd. If the port can share the COM9 serial logs and the SUB10S analysis, that is the only path to closing U5 without our own hardware. +- Exactly what the port's BL31 'FPGA_CONFIG_BUFFER_SIZE=16' modification changed, and whether stock ATF (2.12.0 per Terasic's build record, or 2.14) has the same behaviour. The kernel-side 2 MiB geometry is [V] mainline; the ATF side is the port's prose only, and neither reference repo contains ATF source. If the BUSY/BUFFER_DONE contract is fragile in stock ATF too, the 2 MiB wall becomes OUR problem and not just the port's. +- Is the DE25's QSPI actually 16 MiB or 256 MiB? The Terasic U-Boot DTS says mt25qu02g (256 MiB) but the node is verbatim SoCDK boilerplate; the port's RSU layout terminates exactly at 16 MiB. This bears directly on our Q4 ('Does RSU fit in 16 MB?') — if the part is really 2 Gbit, that question changes shape entirely. Settle it against the DE25 UM/schematic or an on-board `sf probe`, not against a copied DTS node. +- Whether Terasic's FACTORY QSPI (as opposed to the port's replacement .jic) enforces bitstream authentication. The port's unsigned-RBF success is on a board whose QSPI the port overwrote; U4 is answered for the silicon but not strictly for the factory configuration. +- Whether the fpga-region overlay path survives DOZENS of apply/remove cycles — our §3.2's 'no mainline user at all' concern. The port's design deliberately re-applies a STATIC overlay via of_overlay_remove() + of_overlay_fdt_apply() each time, which is exactly that pattern, but the port has not run it end-to-end even once. +- Whether the golden/MENU-in-QSPI posture the port settled on is compatible with our posture 1 at all: does Terasic's FACTORY .jic configure a fabric that a MiSTer MENU can run on, or does reaching the MENU inherently require replacing the QSPI fabric image? If the latter, posture 1 is only viable for the L1 developer OS and DP-1 must be re-opened for the MiSTer image. Nothing in either repo answers this — the port never ran on the factory .jic. +- What exactly is the 'throttle'/'budget depleted' mailbox issue referenced in the port's live-switch commit? The memory files it cites are not in the tree we were shown (they live in a separate private repository we were not given), so its root cause, frequency, and whether it is deterministic or transient remain unknown to us. +- Does Terasic's factory-shipped (unmodified) SPL/FSBL accept a mainline-built u-boot.itb at all (U3)? Neither this task nor the port's repo has ever tested it -- the port replaced the SPL before first boot. This is the single biggest open item for our posture-1 plan and is not answerable from either reference repo; it needs a real board running the untouched factory .jic. +- Is the port's board's permissive VAB state (U4) representative of the DE25-Nano product line generally, or could Terasic ship (or a customer provision) units with VAB fused? The port's evidence is single-unit and structural (an unsigned .jic boots), not a statement about eFuse policy across the SKU. +- Does the mmc0/SMMU F_TRANSLATION regression (6.12->6.18 window) have a known upstream fix, a tracked kernel bug, or a narrower root cause than 'somewhere in arm-smmu-v3 or DMA-mapping'? The port explicitly deferred bisection; this is worth a targeted search before our own D0.3/D2 work reaches the same wall. +- What is the actual current state of end-to-end live core switching as of the port's latest commits (2026-08-01)? SETUP.md (dated 2026-07-04) says core switching is not wired up; later commits show it working in principle (mechanism HW-confirmed) but the most recent standalone test of the new trigger driver hit a mailbox timeout. Whether a full menu-driven live switch has been demonstrated end-to-end with all fixes (agilex5 compatibles + de25_fpga_trigger + BUF4 BL31) applied together is not established from what's in this repo. +- Does the port's DP-1-relevant Terasic stock image (de25_nano_revA_sdcard_console_v1.1.img) represent a genuine board-support reference analogous to what DE10's stock-shadowing model needs, or is it materially different in kind (a generic Linux console BSP vs. a running MiSTer install)? This bears on whether DP-1's premise needs revisiting and by how much -- flagged, not resolved, per the task's standing instruction not to decide DP-1 here. --- @@ -705,8 +704,8 @@ read any evaluative wording with that in mind. brick-adjacent and flash-path claims (FCS/QSPI staging, the SError/LWH2F wedge, the RSU slot map) were NOT re-checked** and still do not meet the bar. 3. **The posture-1 gate remains open (D0.1 Q3).** Nobody has booted a mainline-built `u-boot.itb` - from Terasic's *factory* SPL — his repo is structurally unable to answer it, since he replaced - the SPL. The best indirect evidence: his own defconfig sets `CONFIG_SPL_FIT_SIGNATURE=y` and he + from Terasic's *factory* SPL — the port's repo is structurally unable to answer it, since the port replaced + the SPL. The best indirect evidence: the port's own defconfig sets `CONFIG_SPL_FIT_SIGNATURE=y` and the port loads locally built **unsigned** FITs successfully, which is the same U-Boot code path and is consistent with a control DTB that requires no keys **[V]**. @@ -727,11 +726,11 @@ build fragment, lives in [`de25-implementation-path.md`](de25-implementation-pat "re-verified" below, which were fetched from `u-boot/u-boot@v2026.07` and read during the verification pass. Everything else here carries this document's usual single-agent standard. -**What his tree is, for comparison [V].** U-Boot **2025.01**, branch `de25-mister-exfat-boot`. The -`u-boot.itb` he pulled off the factory board identifies as `2025.01-gd0f8813fd6bf` with +**What the port's tree is, for comparison [V].** U-Boot **2025.01**, the port's own branch. The +`u-boot.itb` the port pulled off the factory board identifies as `2025.01-gd0f8813fd6bf` with `vendor=terasic` — Terasic shipped a build of this very tree at its "add support for de25-nano" -commit, and his own build carries the identical version string, so he never moved off 2025.01. His -defconfig sets `CONFIG_SPL_FIT_SIGNATURE=y` (as mainline's agilex5 defconfig does) yet he loads +commit, and the port's own build carries the identical version string, so the port never moved off 2025.01. The port's +defconfig sets `CONFIG_SPL_FIT_SIGNATURE=y` (as mainline's agilex5 defconfig does) yet the port loads locally built **unsigned** FITs successfully; the SPL FIT load contract is visible as `CONFIG_SPL_LOAD_FIT_ADDRESS=0x82000000`, `CONFIG_SYS_SPI_U_BOOT_OFFS=0x04000000`. @@ -782,7 +781,7 @@ Kconfig graph, not build-tested** — first thing to check at first build. **6. The exFAT blocker has expired [V].** Mainline U-Boot has real exFAT — `fs/exfat/`, `CONFIG_FS_EXFAT` ("read/write support") — added by commit `b86a651b64` on **2025-03-17**, i.e. -*after* his 2025.01 base. That is exactly why he had to hand-roll `libexfat` and a custom +*after* the port's 2025.01 base. That is exactly why the port had to hand-roll `libexfat` and a custom exFAT-aware SPL, and it is why that whole workstream is **not** something we inherit: on current mainline the equivalent is one defconfig line. Note the stock defconfig enables `SPL_FS_FAT` (`:22`) but not `FS_FAT`/`CMD_FAT`/`FS_EXFAT` for U-Boot proper *(re-verified)*. This removes a capability @@ -792,8 +791,8 @@ gap; it does not by itself decide p2's filesystem. **v2026.07** is current stable (v2026.10 is at -rc2, due 2026-10-05), and mainline **TF-A v2.15.0** does carry `plat/intel/soc/agilex5/`. **That pairing is unblessed and untested by anyone we know of** — Terasic and Altera document only vendor forks (`u-boot-socfpga socfpga_v2023.10` + -`arm-trusted-firmware socfpga_v2.10.0`), and he stayed on 2025.01 throughout. Whether a stock -v2.15.0 BL31 boots this board, and whether it handles >2 MB reconfiguration without his +`arm-trusted-firmware socfpga_v2.10.0`), and the port stayed on 2025.01 throughout. Whether a stock +v2.15.0 BL31 boots this board, and whether it handles >2 MB reconfiguration without the port's `FPGA_CONFIG_BUFFER_SIZE` modification, is open. **What Leg 4 does *not* answer.** It does not close U3 — nobody has booted a mainline-built @@ -815,21 +814,21 @@ not re-checked and still carry the original single-agent standard. |---|---|---| | Mainline 6.18.44's `socfpga_agilex5.dtsi` has **no** `mmc0`, `smmu`, `svc`, `fpga-mgr` or `fpga-region` node (§2 U1, §3) | **VERIFIED** | Case-insensitive `grep` for `mmc\|sd4hc\|sdhci\|smmu\|iommu` over the 826-line file → **zero hits**; only `service_reserved` svcbuffer at `:23`, `clkmgr` at `:144-148`, QSPI at `:477`, three stmmac compatibles at `:491,:603,:715` | | Neither `stratix10-svc.c` nor `stratix10-soc.c` matches an agilex5 string in 6.18.44, and both tables carry **no `.data`** (§2 U1, §3) | **VERIFIED** | Tables read at `stratix10-svc.c:1133-1137` and `stratix10-soc.c:448-452`; `grep` for `of_device_get_match_data`/`of_device_is_compatible`/`match->data` in both files → zero hits. Also re-verified at **v7.2** (`:1911-1915` and `:448-452`) and at `master` — still no agilex5 string | -| His fix is exactly two one-line match-table additions (§2 U1, §3) | **VERIFIED** | `de25-linux:drivers/fpga/stratix10-soc.c:451`, `drivers/firmware/stratix10-svc.c:1134` | -| The fault that stopped his one Linux-path reconfiguration attempt: mailbox timeout, board wedged (§2 U2/U5, §3) | **VERIFIED** | Re-read here at `:115`; consistent across Legs 1/2/3/5 | +| The port's fix is exactly two one-line match-table additions (§2 U1, §3) | **VERIFIED** | `PORT tree, drivers/fpga/stratix10-soc.c:451`, `drivers/firmware/stratix10-svc.c:1134` | +| The fault that stopped the port's one Linux-path reconfiguration attempt: mailbox timeout, board wedged (§2 U2/U5, §3) | **VERIFIED** | Re-read here at `:115`; consistent across Legs 1/2/3/5 | | `stratix10-soc.c` allocates `NUM_SVC_BUFS 4 × SVC_BUF_SIZE SZ_512K` = 2 MiB in flight (§1a cap 2, §3) | **VERIFIED** | `stratix10-soc.c:19-20`, allocation loop at `:215-216` | | `fpga_manager` class devices expose **no** writable attribute; `fpga_region` exposes only RO `compat_id`; `OF_CONFIGFS` is not in mainline (§2 U2, §3) | **VERIFIED** | `fpga-mgr.c:655-664` (three `DEVICE_ATTR_RO`, and `fpga_mgr_attrs[]` holds exactly those); `fpga-region.c:175`; `drivers/of/Kconfig` has only `OF_OVERLAY:105` and `OF_OVERLAY_KUNIT_TEST:116`; no `drivers/of/configfs.c` | -| Reconfiguration quantizes to ~1 s from two independent code paths (§1b, §3) | **VERIFIED** | `de25-uboot-socfpga:drivers/fpga/intel_sdm_mb.c:20-21` (60 s timeout, 1 s interval, `udelay(1 s)` before the first poll); kernel `stratix10-svc.c:295` `msleep(1000)`, 30 s ceiling. Caveat kept: the kernel sleep fires only if the first poll is not already complete, so "~1 s practical floor" is the right framing — which is what this document already said | -| The mmc0/SMMU `F_TRANSLATION` fault itself is real, not a warm-reboot artifact (§3) | **VERIFIED** | Commit `716559020`'s retract-of-a-retraction; the "clean" disproof boot had `sdhci.debug_quirks=0x60` silently baked in by a JTAG-recovery script default | +| Reconfiguration quantizes to ~1 s from two independent code paths (§1b, §3) | **VERIFIED** | `PORT tree, drivers/fpga/intel_sdm_mb.c:20-21` (60 s timeout, 1 s interval, `udelay(1 s)` before the first poll); kernel `stratix10-svc.c:295` `msleep(1000)`, 30 s ceiling. Caveat kept: the kernel sleep fires only if the first poll is not already complete, so "~1 s practical floor" is the right framing — which is what this document already said | +| The mmc0/SMMU `F_TRANSLATION` fault itself is real, not a warm-reboot artifact (§3) | **VERIFIED** | The port's retraction-of-a-retraction commit; the "clean" disproof boot had `sdhci.debug_quirks=0x60` silently baked in by a JTAG-recovery script default | | MiSTer-scale A5EB013 bitstreams are 1.9–3.6 MB (§1a cap 3, §3) | **VERIFIED** | `board_overlay/media/fat/cores/CORES.manifest`: menu 1,929,216 … ao486 3,645,440. Minor discrepancy noted: the NES figure quoted in prose (3,325,952) vs the manifest's 3,346,432 — different build cutover, immaterial to the >2 MiB arithmetic | -| The friend's SD path runs an **unmodified** mainline `sdhci-cadence` (§3) | **VERIFIED** | `diff -q` against `linux-6.18.44` → identical. Qualifier added by the pass: it runs with `sdhci.debug_quirks=0x60` (PIO), so it proves binding and PIO, **not** the DMA path | +| The PORT's SD path runs an **unmodified** mainline `sdhci-cadence` (§3) | **VERIFIED** | `diff -q` against `linux-6.18.44` → identical. Qualifier added by the pass: it runs with `sdhci.debug_quirks=0x60` (PIO), so it proves binding and PIO, **not** the DMA path | ### Independently verified — claims that FAILED and are CORRECTED | Claim | Outcome | Correction | |---|---|---| -| "mmc0 DMA F_TRANSLATION-faults on 6.18 but not 6.12 with a byte-identical DT — **a kernel-version regression**" (§3, twice) | **FAILED — attribution wrong; fault real** | Corrected in place at both sites. The 6.12 baseline is Terasic's **vendor** tree, whose `sdhci-cadence` gives `intel,agilex5-sd4hc` a `SDHCI_QUIRK2_40_BIT_DMA_MASK` that exists nowhere in mainline; his 6.18 tree binds bare `cdns,sd4hc` and gets a 64-bit mask. Vendor-vs-mainline **driver** delta, not a version regression — and it therefore **travels forward to 6.19 and 7.2** | -| "Both match tables have no per-compatible `.data`, so the third entry is **functionally free**" (§2 Leg 3 U1, §3) | **HOLDS for mainline; MISLEADING as a general claim** | True of mainline at 6.18.44, v7.2 and `master` [V]. But Terasic's **vendor** `stratix10-svc.c` keys real behaviour off `of_device_is_compatible(node, "intel,agilex5-svc")` at `:3507`: IOMMU attach + IOVA carveout, `AGILEX5_SDM_DMA_ADDR_OFFSET 0x80000000` added to every buffer address sent to the SDM (`:59,:3244`), an `INTEL_SIP_SMC_SDM_REMAPPER_CONFIG` remapper bypass (`:3550-3551`), and a hard `-ENODEV` if the SMMU is absent. So the "per-compatible behaviour appears" falsifier is not hypothetical — it already exists downstream, and it is the concrete mechanism behind the mailbox timeout his one Linux-path attempt hit | +| "mmc0 DMA F_TRANSLATION-faults on 6.18 but not 6.12 with a byte-identical DT — **a kernel-version regression**" (§3, twice) | **FAILED — attribution wrong; fault real** | Corrected in place at both sites. The 6.12 baseline is Terasic's **vendor** tree, whose `sdhci-cadence` gives `intel,agilex5-sd4hc` a `SDHCI_QUIRK2_40_BIT_DMA_MASK` that exists nowhere in mainline; the port's 6.18 tree binds bare `cdns,sd4hc` and gets a 64-bit mask. Vendor-vs-mainline **driver** delta, not a version regression — and it therefore **travels forward to 6.19 and 7.2** | +| "Both match tables have no per-compatible `.data`, so the third entry is **functionally free**" (§2 Leg 3 U1, §3) | **HOLDS for mainline; MISLEADING as a general claim** | True of mainline at 6.18.44, v7.2 and `master` [V]. But Terasic's **vendor** `stratix10-svc.c` keys real behaviour off `of_device_is_compatible(node, "intel,agilex5-svc")` at `:3507`: IOMMU attach + IOVA carveout, `AGILEX5_SDM_DMA_ADDR_OFFSET 0x80000000` added to every buffer address sent to the SDM (`:59,:3244`), an `INTEL_SIP_SMC_SDM_REMAPPER_CONFIG` remapper bypass (`:3550-3551`), and a hard `-ENODEV` if the SMMU is absent. So the "per-compatible behaviour appears" falsifier is not hypothetical — it already exists downstream, and it is the concrete mechanism behind the mailbox timeout the port's one Linux-path attempt hit | | ~~`drivers/input/mousedev.c` is missing `#include `~~ | **FAILED, refuted 2026-08-21** | Already annotated in place in §3. Left exactly as it was, as the worked example of why this pass mattered | ### What the pass concluded about this document as a whole diff --git a/docs/de25-uboot.md b/docs/de25-uboot.md index a5643617..13c737ce 100644 --- a/docs/de25-uboot.md +++ b/docs/de25-uboot.md @@ -35,7 +35,7 @@ danger is not `saveenv`, it is the environment **load** path. | Component | Pin | Why not Buildroot's own | Hash provenance | |---|---|---|---| -| U-Boot | **v2026.07** (released 2026-07-07) | Buildroot 2026.05.2 ships 2026.04 | **Signed.** `ftp.denx.de/pub/u-boot/u-boot-2026.07.tar.bz2` + its `.sig`; `gpg --verify` → *Good signature from "Thomas Rini "*, EDDSA key `F3CEA8743D60E0192F9B4C7A2BE2A0F50ABFE40A`, fetched by full fingerprint from keys.openpgp.org **[V, done 2026-09-02]** | +| U-Boot | **v2026.07** (released 2026-07-07) | N/A as of **DU1 (2026-09-14)** -- switched to `BR2_TARGET_UBOOT_LATEST_VERSION=y`. Buildroot 2026.08 bundles 2026.07 as its own `LATEST_VERSION` default (`boot/uboot/Config.in:88`), the same version this file already pinned by hand, so the custom pin and `board/mister/de25nano/patches/uboot/uboot.hash` no longer buy anything -- removed in the same commit. (Historical reason, now stale: Buildroot 2026.05.2 shipped 2026.04.) | Buildroot's own `boot/uboot/uboot.hash` (header says "Locally computed") now carries the line `sha256 78e8bfc382fe388f9b55aa1daf8c563522a037779b5d4c349d1415e381f1243e u-boot-2026.07.tar.bz2` -- byte-identical to the value this repo independently GPG-verified against Thomas Rini's release signature on 2026-09-02 (EDDSA key `F3CEA8743D60E0192F9B4C7A2BE2A0F50ABFE40A`; the DE10 leg of the same fact is `docs/uboot-mainline-port.md` §3.6). `BR2_DOWNLOAD_FORCE_CHECK_HASHES=y` still fails closed on any future drift; it just checks against Buildroot's line instead of ours now. **[V, DU1 2026-09-14]** | | TF-A | **v2.15.0** | Buildroot 2026.05.2 tops out at v2.12, which has **no Agilex 5 platform** — a custom version is the only route, not a preference | **TOFU, honestly labelled.** trustedfirmware.org publishes no release tarballs and no signed manifest. Anchored on annotated tag `v2.15.0` (object `9ad327a8…`) → commit `da738d5eae93af342fdc4995dd3c05acb4c9d757`, confirmed from a **second, independent clone**. The tag *is* PGP-signed (RSA `5D6F8960…`, Olivier Deprez/Arm) but that key is on **neither** keys.openpgp.org nor keyserver.ubuntu.com (both 404, 2026-09-02), so the signature could **not** be verified **[V that it is unverifiable today]** | Both hash files live under the existing `BR2_GLOBAL_PATCH_DIR` @@ -47,8 +47,9 @@ changing a value. `BR2_DOWNLOAD_FORCE_CHECK_HASHES=y` makes both **fail closed**, and the two failures have different shapes worth knowing: -- **U-Boot**: `boot/uboot/uboot.hash` exists but only lists 2026.04, so `check-hash` finds a hash - *file* and no matching *line* → exit 3, `ERROR: No hash found for u-boot-2026.07.tar.bz2`. +- **U-Boot**: ~~historically~~ `boot/uboot/uboot.hash` existed only for 2026.04, causing exit 3 on + 2026.07. As of **DU1 (2026-09-14)**, the repo uses `BR2_TARGET_UBOOT_LATEST_VERSION=y` and + Buildroot's own hash line validates; the board-local hash file was removed. - **TF-A**: Buildroot ships no ATF hash file at all and explicitly excuses git-generated tarballs via `BR_NO_CHECK_HASH_FOR`. `BR2_DOWNLOAD_FORCE_CHECK_HASHES` **empties** that variable (`package/pkg-download.mk:119`), so the excuse does not apply and the same exit 3 results **[V]**. @@ -62,6 +63,21 @@ build fails closed until both are re-derived **[V]**. item, and building green does not close it. Terasic and Altera document only vendor forks (`u-boot-socfpga socfpga_v2023.10` + `arm-trusted-firmware socfpga_v2.10.0`). +**What the factory actually pairs (2026-09-14, read from the FIT on Terasic's factory SD image):** +U-Boot `2025.01-gd0f8813fd6bf` — the head of Terasic's public `github.com/terasic/u-boot-socfpga` +branch `de25-nano-v2025.01` — with TF-A `v2.12.0(release):de25_nano_revA_v1.0`, Terasic's public +`arm-trusted-firmware` branch `de25-nano-v2.12.0`. That TF-A is Altera's downstream +`socfpga_v2.12.0` plus **exactly one change**: the boot and crash console moved from UART0 to +UART1 (`plat/intel/soc/common/include/platform_def.h`). We carry the same two-line change +against v2.15.0 (`board/mister/de25nano/patches/arm-trusted-firmware/0001-…uart1.patch`) so +BL31's banner and any panic are visible on the header UART at bring-up (§11). Upstream v2.15.0 +already contains the rest of Altera's Agilex 5 downstream work (the `mailbox_init()` re-init in +`bl31_plat_setup.c`, the handoff offsets), which is why nothing else is carried. The factory +SPL's own config, from the same public branch: `SPL_FIT_SIGNATURE=y` with no keys (hash-only), +`SPL_LOAD_FIT_ADDRESS=0x82000000`, `SPL_ATF_NO_PLATFORM_PARAM=y`, `HANDOFF=y` + +`BLOBLIST_ADDR=0x72000` (the value §4.4 guessed, confirmed), boot order mmc→flash0→nand, and +`plat->dualport = 0` forced in `sdram_agilex5.c`. + --- ## 3. Files @@ -73,7 +89,6 @@ item, and building green does not close it. Terasic and Altera document only ven | `board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano.dts` | U-Boot board device tree (§5) | | `board/mister/de25nano/uboot-dts/socfpga_agilex5_de25nano-u-boot.dtsi` | U-Boot additions: `stdout-path`, mmc caps, FIT tweaks (§5, §6) | | `board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch` | the one carried U-Boot patch (§8) | -| `board/mister/de25nano/patches/uboot/uboot.hash` | signed-provenance hash for the 2026.07 tarball | | `board/mister/de25nano/patches/arm-trusted-firmware/arm-trusted-firmware.hash` | TOFU hash for the v2.15.0 git tarball | | `Makefile` (`de25` recipe) | post-build assertions for `images/bl31.bin` and `images/u-boot.itb` | @@ -274,6 +289,15 @@ the whole justification; everything else in our board file follows from keeping Those are two different categories and the block splits them deliberately. +**What Terasic's own board support does (2026-09-14, public branch `de25-nano-v2025.01`, +`socfpga_agilex5_de25_nano-u-boot.dtsi`):** `bus-width = <4>; sd-uhs-sdr50; cap-mmc-highspeed;` +with the 2025.01-era `&combophy0 cdns,phy-*` PHY knobs **identical to that tree's SoCDK file** — i.e. +Terasic ran the dev kit's PHY values unchanged on this board, with no `vqmmc` regulator. That is +the same decision this block makes in the 2026.07 driver's per-mode form (the two driver +generations' properties are not directly comparable), and it is silicon evidence that the SoCDK +values suit this board. The `sd-uhs-sdr50` line is *not* copied: without a voltage regulator +U-Boot cannot switch to 1.8 V signalling, so it is inert at best. + **BOARD facts we do not take from socdk.** socdk declares `sd-uhs-sdr50`/`sd-uhs-sdr104` with `vqmmc-supply = <&sd_io_1v8_reg>`, whose GPIO is `<&portb 3>` — a **SoC Development Kit wiring fact**. Driving the wrong GPIO to switch SD bus voltage is a way to break a card, not a way to go @@ -363,15 +387,47 @@ produced byte-identical artifacts — `bl31.bin` is `sha256 863073b2c0a9489ae04cbf077b5496975f7aa7f925a8397fad705bcb3c390bf1` across every run, and `u-boot.itb` is byte-stable **within a build tree** — verified twice: two runs in the wave-2 tree (728,176 B) and, after a full `distclean`, two runs in the clean tree including a -`uboot-dirclean` rebuild (728,168 B, `sha256 49f1c7dd…`) **[V]**. It is **not yet shown to be -byte-stable across clean trees [U]**: the wave-2 tree's FIT and the clean tree's FIT differ by -8 bytes, all of it inside the `uboot` payload (650,056 → 650,048 B; `atf` and `fdt-0` identical -in size and BL31 identical in hash), with the same resolved Buildroot config. The version string -is not the cause (it carries only the pinned `SOURCE_DATE_EPOCH` date). The boot contract is -unaffected — load addresses, config node, crc32-only signature and DTB are what the checker -asserts, not the hash — but the D2.8 release lane should pin this down with two clean CI builds -before it publishes attested hashes. (History: 731,728 B before the review fixes, 728,176 B -after `CONFIG_BLOBLIST` came out and the mmc node grew.) +`uboot-dirclean` rebuild (728,168 B, `sha256 49f1c7dd…`) **[V]**. It is now also +**byte-stable across clean trees [V, DU3, 2026-09-14]**. At one commit (`933a2d2`), three +from-scratch `make O= mister_de25nano_defconfig && make O= BR2_JLEVEL=12 uboot` runs — +two into same-length paths (`…/DU3/a`, `…/DU3/b`, 30 characters) and a third into a path 25 +characters deeper (`…/DU3/c-path-length-control-tree`, 55 characters), each rebuilding the aarch64 +toolchain, TF-A and U-Boot from scratch — produced one `u-boot.itb`, 725,568 B, +`sha256 d5f487046ac9549349d918756c7bfc7611136d8966f8d047cfc484ca3a52ee97`, and one `bl31.bin`, +53,304 B, `sha256 2052e4c9a62c1a2a947ee20886c6419eb5f802a1338bd12e22a99c916cc0250c`. The build +path does not reach either artifact: `strings` finds no build path in any of the three FITs, and +the deep-path tree is byte-equal to the two shallow ones **[V]**. + +The one input that does move the FIT is the **Buildroot version string baked into the cross +compiler**, and it tracks *this repository's* `git describe` — not the tree, the clock or the path. +`output-de25`, whose toolchain was built one commit earlier (`1b24868`), gives the same 725,568 B +at `sha256 f4e5c924dc20b51b2347dfd5786f7de23613ebfd445bd80009fcb19be6b1963e`, differing in exactly +**12 bytes [V, `cmp -l`]**: 8 ASCII bytes of the abbreviated commit hash inside the compiler banner +(`…gcc.br_real (Buildroot v2026.09.11-beta-31-g933a2d2-dirty) 15.3.0` against +`…-beta-30-g1b24868-dirty`), which U-Boot records verbatim as `CC_VERSION_STRING` for its `version` +command, plus the 4-byte crc32 of the `uboot` FIT node that covers them. Nothing else moves: +`dumpimage -l` reports the same `Created:` (`SOURCE_DATE_EPOCH`), the same three data sizes, the +same `atf` (`efc1cf2a`) and `fdt-0` (`64e75874`) crc32s, the same load addresses and config node, +and `U-Boot 2026.07 (Sep 04 2026 - 15:16:40 +0000)` is character-identical in all four builds. +`bl31.bin` is `sha256 2052e4c9…` in all four — TF-A does not record the compiler banner. The chain +is `work/buildroot/support/scripts/setlocalversion`, which — because `work/buildroot` carries no +`.git` of its own — reports *this* repo's describe → `BR2_VERSION_FULL` → gcc's +`--with-pkgversion` (`package/gcc/gcc.mk:78`) → U-Boot's `filechk_version.h` → the payload **[V]**. + +(The table above is the 2026-09-02 pass. Both artifacts have moved since, across DU1, DU6 and the +intervening fragment and defconfig edits; the hashes in the two paragraphs here are the current +ones **[V, measured 2026-09-14]**, and what changed between the two passes was not re-derived +**[U]**.) + +So the FIT is reproducible **per commit**, and a published `u-boot.itb` hash is only meaningful +beside the commit that produced it. **Method for the D2.8 release lane**, kept local — no DE25 leg +was added to `reproducibility.yml`, the Actions budget says so: two `O=` trees off one commit, then +`sha256sum images/u-boot.itb images/bl31.bin`; on a mismatch, `cmp -l` the two FITs, `dumpimage -l` +both, and `dumpimage -T flat_dt -p 0 -o ` the `uboot` payload out of each. If the delta is +confined to the compiler banner and the one crc32 that covers it, the cause is the toolchain's +version stamp and nothing else. The boot contract is unaffected either way — load addresses, config +node, crc32-only signature and DTB are what the checker asserts, not the hash. (History: 731,728 B +before the review fixes, 728,176 B after `CONFIG_BLOBLIST` came out and the mmc node grew.) **Housekeeping done in the same pass:** `images/socfpga_agilex5_socdk.dtb` was a stale leftover from before D2.3 (mtime predating this build by hours, from when the defconfig still pointed at @@ -390,6 +446,14 @@ have copied it. Deleted. **Worth a guard**: nothing in the build detects this cl always passes and never checks anything. That is a worse failure than a crash. Found on the first build; the symbol is now in the defconfig with a comment saying why **[V]**. + +**Artifacts after the TF-A console patch (2026-09-14, this tree):** `bl31.bin` 53,304 B +`sha256 f270b16f967998020fda69fe2caf3aac84d68d107a9446d19286aa9b827c6d42`, `u-boot.itb` 725,568 B +`sha256 0bbe02da1aec5a3833cee880f54ff2a2d9b9f1268f2d0b695459bcde50f147f0` — same sizes as the +unpatched build; the FIT's `atf` member changed, nothing else. The cross-tree identity measured +above (DU3) predates the patch and holds for the method; it has not been re-measured on three +clean trees since **[U, cheap: rerun DU3's recipe]**. + ## 6. The FIT — checked against the factory SPL contract ### 6.1 What `dumpimage` says @@ -565,7 +629,14 @@ assert. --- -## 8. The one carried U-Boot patch +## 8. The carried patches — one U-Boot, one TF-A + +TF-A carries `patches/arm-trusted-firmware/0001-plat-intel-agilex5-route-bl31-console-to-uart1.patch` +(2026-09-14): two macro values, boot and crash console to UART1, the header UART. Terasic's own +BL31 makes exactly this change (§2). Visibility only; `bl31.bin` and `u-boot.itb` hashes in §5b +are from the patched build. + +The U-Boot one: `board/mister/de25nano/patches/uboot/0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch`. @@ -710,7 +781,7 @@ Then, in order: | Expect | Means | |---|---| | any output at all on uart1 @115200 8N1 | the alias/`stdout-path` pair is right. **Silence here is the failure this board file exists to prevent** | -| **NO `NOTICE: BL31: v2.15.0…` lines** | **expected — absence is not failure.** TF-A's Agilex 5 platform registers its console at `PLAT_INTEL_UART_BASE`, which is `PLAT_UART0_BASE = 0x10C02000` **[V `plat/intel/soc/common/include/platform_def.h:156`, `plat/intel/soc/agilex5/include/socfpga_plat_def.h:154`, `bl31_plat_setup.c:61`]** — that is **uart0**, not the DE25's header UART at `0x10C02100`. So BL31 runs and says nothing on the cable you are watching. Do not read a missing BL31 banner as "BL31 did not run"; the thing that proves BL31 ran is the U-Boot banner on the next line, because U-Boot is BL33 and only BL31 gets there. (If you need BL31's own output, uart0 is exposed on the HPS header pins, or `PLAT_INTEL_UART_BASE` can be re-pointed in a TF-A rebuild — neither is needed for a normal bring-up.) | +| **`NOTICE: BL31: v2.15.0…` lines on the header UART** | **expected as of 2026-09-14 — their absence is now a finding.** The carried patch `patches/arm-trusted-firmware/0001-plat-intel-agilex5-route-bl31-console-to-uart1.patch` points TF-A's boot and crash consoles at UART1 (`0x10C02100`, the header), which is exactly the one change Terasic's own BL31 makes against Altera's downstream branch. *Historical text follows — it described the unpatched build:* absence was not failure because TF-A's Agilex 5 platform registers its console at `PLAT_INTEL_UART_BASE`, which is `PLAT_UART0_BASE = 0x10C02000` **[V `plat/intel/soc/common/include/platform_def.h:156`, `plat/intel/soc/agilex5/include/socfpga_plat_def.h:154`, `bl31_plat_setup.c:61`]** — that is **uart0**, not the DE25's header UART at `0x10C02100`. So BL31 runs and says nothing on the cable you are watching. Do not read a missing BL31 banner as "BL31 did not run"; the thing that proves BL31 ran is the U-Boot banner on the next line, because U-Boot is BL33 and only BL31 gets there. (If you need BL31's own output, uart0 is exposed on the HPS header pins, or `PLAT_INTEL_UART_BASE` can be re-pointed in a TF-A rebuild — neither is needed for a normal bring-up.) | | `U-Boot 2026.07 …` banner | the FIT parsed, BL31 ran, BL33 entered — i.e. the whole §2 pairing works. This is the [U] that only hardware closes | | `DRAM: 1 GiB` | `dram_init()` took the `fdtdec` branch (§4.4). If instead U-Boot dies before the banner with `Missing SPL hand-off info`, `CONFIG_HANDOFF` came back on | | `Loading Environment from FAT... ` then either `OK` or `Unable to read "uboot.env" from mmc0:1...` | §10. **`Loading Environment from UBI` must NEVER appear.** If it does, stop and do not boot again until the config is fixed — that message means the QSPI is being attached | @@ -736,12 +807,12 @@ this build; typing them should produce `Unknown command` — which is itself a u | U-Boot's SD access uses socdk's silicon-validated Agilex 5 PHY timings, at default speed only | **[V, config-traced]** — §5.1. Whether those timings suit *this* board is **[U]** until hardware | | `# CONFIG_SPL is not set` does not work; SPL is compiled and nothing of it is shipped | **[V]** — §4.5, closes §8 Q6 | | `boot_targets` contains only `mmc0`, and `bootcmd_qspi`'s embedded `saveenv` is gone | **[V]** — built default env | -| U-Boot 2026.07 + TF-A v2.15.0 boot this board under the factory SPL | **[U]** — needs hardware. ADR 0029 D4 | -| The factory SPL accepts our unsigned crc32 FIT | **[U]** — the *published* SPL DTB has no keys **[V]**; the *programmed* flash is unread | +| U-Boot 2026.07 + TF-A v2.15.0 boot this board under the factory SPL | **[U]** — needs hardware. ADR 0029 D4. Narrowed 2026-09-14: the factory pairing is U-Boot `2025.01-gd0f8813fd6bf` + TF-A `v2.12.0` (Terasic's build of Altera's downstream branch), read from the FIT in Terasic's factory SD image; the SPL→BL31→BL33 interfaces between those and ours are the generic FIT/`bl_params` ones, so this is a compatibility question, not a contract question | +| The factory SPL accepts our unsigned crc32 FIT | **[V by reference, 2026-09-14]** — the `u-boot.itb` on Terasic's factory SD image (`de25_nano_revA_sdcard_console_v1.1.img`, partition 1) is itself crc32-only with **no signature value**: images `uboot`@`0x80200000` / `atf`@`0x80000000` / `fdt-0`, configuration `board-0`, "Sign algo crc32:dev" — byte-for-byte the shape `dumpimage -l` prints for ours (§6.1). Its U-Boot banner names the build (`2025.01-gd0f8813fd6bf`, the head of Terasic's public `de25-nano-v2025.01` branch), whose defconfig sets `SPL_FIT_SIGNATURE=y` with no keys in the SPL DTB, i.e. hash-only verification. Terasic's card boots on factory flash, so the factory SPL demonstrably accepts this shape. Residual [U]: nobody has booted a *third-party* FIT under an *untouched* factory flash — reports from another DE25-Nano port all involve a reflashed QSPI | | The SD controller works with our conservative `&mmc` block | **[U]** — needs hardware | | DRAM is 1 GiB at `0x8000_0000` | **[U, vendor declaration]** — the `DDR:` lines settle it | -| The TF-A v2.15.0 tag signature is authentic | **[U]** — signing key not published on any reachable keyserver | -| Nothing else in the release writes QSPI (Linux side, `fw_setenv`, updater) | **[policy, unenforced]** — `de25-boot-chain.md` §5 | +| The TF-A v2.15.0 tag signature is authentic | **[V, DU6 2026-09-14]** — verified against the tagger's self-published key; the hash file header records the fingerprint and the trust anchor | +| Nothing else in the release writes QSPI (Linux side, `fw_setenv`, updater) | **[policy, partially enforced]** — `de25-boot-chain.md` §5; `external.mk` hook + `ci-tests.sh` check (DU2, 2026-09-14) | --- @@ -771,17 +842,30 @@ in the same commit. 1. **The carried U-Boot patch (§8)** vs putting `CONFIG_MTD=y` back and arguing inertness. One line either way. + Disposition (2026-09-14): keep the carried mtdids/mtdparts guard patch — one line, inert argument not needed. + 2. **`CONFIG_HANDOFF` off + a declared 1 GiB (§4.4)** vs `CONFIG_BLOBLIST_ADDR=0x72000` and taking the measured size from the factory SPL. Recommendation: as shipped; revisit only if the board reports the wrong size. + Disposition (2026-09-14): HANDOFF off + declared 1 GiB as shipped. + 3. **The `&mmc` block (§5.1)** — socdk's silicon-validated PHY timings, but default speed only at 25 MHz. It is the first knob to turn if the card misbehaves, and §5.1 has both the diagnosis order and the one-line lift back to 50 MHz high speed. Faster than that needs the DE25's real vqmmc GPIO, which is a hardware-session observation. + Disposition (2026-09-14): &mmc at 25 MHz as shipped. + 4. **No seeded `uboot.env` (§10).** + Disposition (2026-09-14): no seeded uboot.env. + 5. **`CONFIG_FS_EXFAT=y`** anticipates §8 Q7 resolving toward exFAT on p2. If p2 stays ext4 forever, this line can go. + Disposition (2026-09-14): keep FS_EXFAT=y (ADR 0029 D11 makes the two-stage exFAT layout the target). + 6. **`DISTRO_DEFAULTS` is deprecated upstream.** Migrating to `BOOTSTD_DEFAULTS` is a separate, testable change; doing it now would mean bring-up debugs two new things at once. + Disposition (2026-09-14): stay on DISTRO_DEFAULTS until first boot, naming BOOTSTD_DEFAULTS as the migration target. + 7. **The §5 CI check** ("`ENV_IS_IN_UBI` unset and no QSPI-write command set") is still unimplemented. §7 is the assertion list it should encode. + Disposition (2026-09-14): done — external.mk MISTER_UBOOT_DE25_QSPI_AUDIT (DU2, 2026-09-14). diff --git a/docs/uboot-tasks.md b/docs/uboot-tasks.md index 58dff965..ae3b0ec2 100644 --- a/docs/uboot-tasks.md +++ b/docs/uboot-tasks.md @@ -96,7 +96,7 @@ Answer in one line each; silence takes the recommendation. - [x] **U0 — ADR + redirects** — DONE (ADR 0024 landed 2026-07-28; amended 2026-09-14 with the version drift, the mirror-stock decisions and the ADR 0023 fold-in). -- [ ] **U1 — Buildroot wiring** — [SONNET] — Size M — Depends: owner decision 1 +- [x] **U1 — Buildroot wiring** — [SONNET] — Size M — Depends: owner decision 1 Assuming decision 1's recommendation: add to `configs/mister_de10nano_defconfig` `BR2_TARGET_UBOOT=y`, `BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y`, `BR2_TARGET_UBOOT_LATEST_VERSION=y` (2026.07 under Buildroot 2026.08 — the only source choice @@ -125,7 +125,7 @@ Answer in one line each; silence takes the recommendation. **Done when:** `make` from a configured tree emits `images/u-boot-with-spl.sfp`; `ci-tests.sh` passes; `check-defconfigs.sh` green; CI build time delta recorded in the PR. -- [ ] **U2a — Patches 0001/0002, the two upstream fixes** — [SONNET] — Size S — Depends: — +- [x] **U2a — Patches 0001/0002, the two upstream fixes** — [SONNET] — Size S — Depends: — `0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch`: `arch/arm/mach-socfpga/board.c:214-215` `CONFIG_TARGET_SOCFPGA_{ARRIA10,GEN5}` → `CONFIG_ARCH_SOCFPGA_{ARRIA10,GEN5}` (fallout of `62f7a94602`). `0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch`: `fs/exfat/time.c:129,147-148` @@ -137,7 +137,7 @@ Answer in one line each; silence takes the recommendation. **Done when:** both apply at `-F0`; `nm spl/u-boot-spl` shows `board_spl_mmc_get_uboot_raw_sector` **[V]** (absent without 0001); the `FS_EXFAT` build links **[V]**. -- [ ] **U2b — Patch 0003, the fork's QTS handoff** — [SONNET] — Size S — Depends: — +- [x] **U2b — Patch 0003, the fork's QTS handoff** — [SONNET] — Size S — Depends: — `0003-board-terasic-de10-nano-mister-qts-handoff.patch`: the four `board/terasic/de10-nano/qts/*.h` from `work/U-Boot_MiSTer@8dcc3484`, `s/CONFIG_HPS_/CFG_HPS_/`, **values unmodified** (decided: `FPGAPORTRST=0x3FFF`, both s2f clock counts, the three pinmux bits, the 32 IOCSR words). @@ -146,7 +146,7 @@ Answer in one line each; silence takes the recommendation. Provenance: fork `dadd1c8978` ("Use SPL config from DE10 FB project") and plan §3.2/§3.2a. **Done when:** the two `qtsdiff.py` runs say exactly that **[V]**, recorded in the patch header. -- [ ] **U2c — Patch 0004, the fork's `mt`** — [SONNET] — Size S — Depends: — +- [x] **U2c — Patch 0004, the fork's `mt`** — [SONNET] — Size S — Depends: — `0004-cmd-mem-add-mt-memory-test-against-value.patch`: port `do_mem_mt` + its `U_BOOT_CMD` from `work/U-Boot_MiSTer/cmd/mem.c:158-180,1259-1263` (`cmd_tbl_t` → `struct cmd_tbl`, `simple_strtoul` → `hextoul`); find the introducing fork commit with `git log -S do_mem_mt` @@ -157,7 +157,7 @@ Answer in one line each; silence takes the recommendation. **Done when:** the sandbox transcript is in the patch header **[V]**; the command table gains `mt` with stock's help string. -- [ ] **U2d — Patch 0005, the warm-reboot bridge behaviour** — [OPUS] — Size M — Depends: — +- [x] **U2d — Patch 0005, the warm-reboot bridge behaviour** — [OPUS] — Size M — Depends: — The fork's `d6010efe50` (Sorgelig, 2017-03-27) adds one line, `socfpga_bridges_reset(0);`, at the end of `arch_early_init_r` in the 2017.03 `misc.c`. Mainline's `misc_gen5.c:185-210` `arch_early_init_r` ends in `socfpga_bridges_reset(1)`. The line does not port; the @@ -171,7 +171,7 @@ Answer in one line each; silence takes the recommendation. **Done when:** the patch builds; `objdump -d u-boot` shows `arch_early_init_r` reaching the release path **[V]**; the argument survives U9. -- [ ] **U2e — The fragment** — [SONNET] — Size M — Depends: — +- [x] **U2e — The fragment** — [SONNET] — Size M — Depends: — `board/mister/de10nano/uboot.fragment`, headed like the DE25's (which layer is which; the merge_config comment-line rule). Every line a delta on `socfpga_de10_nano_defconfig`, each with its plan §3.1/§3.3 citation: `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y`; @@ -189,7 +189,7 @@ Answer in one line each; silence takes the recommendation. **Done when:** the resolved `.config` carries every symbol U3 will assert **[V]**; the merge report shows zero dropped symbols. -- [ ] **U2f — The environment file** — [SONNET] — Size S — Depends: — +- [x] **U2f — The environment file** — [SONNET] — Size S — Depends: — `board/mister/de10nano/uboot.env`: stock's 21 entries, verbatim, in U-Boot's `.env` text format, with `mt` kept (U2c). Start from `/mnt/source/uboot-mainline/env-layout-parity/mister.env.txt` (it already built @@ -202,7 +202,7 @@ Answer in one line each; silence takes the recommendation. file header that `CONFIG_ENV_SIZE=0x1000` (U2e) bounds it. **Done when:** `cmp` is silent **[V]**. -- [ ] **U4a — `scripts/check-uboot-parity.sh`** — [OPUS] — Size M — Depends: — +- [x] **U4a — `scripts/check-uboot-parity.sh`** — [OPUS] — Size M — Depends: — House style of `scripts/check-zimage-dtb.sh`: POSIX `sh`, `set -eu`, header naming the contract and citing boot-chain sections, `Usage: check-uboot-parity.sh `, `Exit: 0 = pass, 1 = contract violation, 2 = usage/IO error`, `note()`/`ok()`/`bad()`. Structural @@ -221,7 +221,7 @@ Answer in one line each; silence takes the recommendation. with the right message. Runs today against the stock blob alone; U2g points it at the build. **Done when:** fixtures pass/fail as listed **[V]**; shellcheck clean. -- [ ] **U4b — `scripts/check-uboot-handoff.sh`** — [SONNET] — Size M — Depends: — +- [x] **U4b — `scripts/check-uboot-handoff.sh`** — [SONNET] — Size M — Depends: — The plan §3.2a/§6 handoff-equality gate. Inputs: the carried `qts/*.h` (from the patch or the patched tree), the built `.sfp`, the stock `uboot.img`. Pack the seven tables (u32 LE for the `iocsr_scan_chain*`, `ac_rom_init`, `inst_rom_init` arrays; **raw bytes** for @@ -234,23 +234,28 @@ Answer in one line each; silence takes the recommendation. run is the whole point, keep its transcript in `docs/verification/uboot-mainline.md`. **Done when:** both fixture runs behave **[V]**; shellcheck clean. -- [ ] **U8 — Docs debt** — [HAIKU] — Size S — Depends: — +- [x] **U8 — Docs debt** — [HAIKU] — Size S — Depends: — boot-chain §3.1's "20 entries, 1,149 bytes" → **21 entries, 1,150 bytes** (plan §6; the ELF symbol is 1,151); README phase-5 row (`README.md:167`) and the documentation-map rows (`README.md:1022-1023`) say "2026.07, in progress, ships nowhere"; `PLAN.md` §8 / `TASKS.md` P5.1-P5.2 pointers checked. No narrative copied anywhere. **Done when:** `git grep "2026.04"` finds only historical measurements in the plan. -- [ ] **U7-prep — Upstream submissions, drafted** — [SONNET] — Size S — Depends: U2a +- [x] **U7-prep — Upstream submissions, drafted** — [SONNET] — Size S — Depends: U2a Two `git format-patch` mails with cover text in U-Boot list style (`scripts/get_maintainer.pl` for the socfpga custodian Cc), one per fix, ready in `docs/verification/uboot-upstream/` or a PR comment. **Not sent** — owner decision 4. After a send, the carried patches' provenance headers gain the list URL. **Done when:** the two mails exist and `checkpatch.pl` is clean. + **Status 2026-09-14:** both mails are in `/mnt/source/uboot-wave-a/staging/upstream/` + (outside the repo, not sent). 0002 is checkpatch-clean; 0001 carries exactly one warning, + `PREFER_IF` on the pre-existing `#if defined(...)` line the fix renames. Accepted as + intrinsic: the submission is a symbol rename and rewriting the guard as `IS_ENABLED()` + would widen a two-symbol fix. Send as-is if ever sent. ### Wave B — integrate (one agent), then verify (three in parallel) -- [ ] **U2g — Integration build** — [SONNET] — Size M — Depends: U1, U2a–U2f +- [x] **U2g — Integration build** — [SONNET] — Size M — Depends: U1, U2a–U2f Drop the five patches into `board/mister/de10nano/patches/uboot/` (with a `series` file if ordering matters), replace the stubs with the real fragment and env, `make uboot-dirclean uboot-rebuild`. Record: applies at `-F0`; `nm` shows the hook; `spl/u-boot-spl.bin` size @@ -261,7 +266,7 @@ Answer in one line each; silence takes the recommendation. **Done when:** `check-uboot-parity.sh` and `check-uboot-handoff.sh` pass against the build and the stock blob **[V]**; the verification doc exists. -- [ ] **U3 — Resolved-`.config` assertion inside the build** — [SONNET] — Size S — Depends: U2g +- [x] **U3 — Resolved-`.config` assertion inside the build** — [SONNET] — Size S — Depends: U2g Not a checklist: an `UBOOT_POST_BUILD_HOOKS` append from `external.mk` (the same trick that fixed dhcpcd's `CONF_OPTS`) that greps `$(@D)/.config` and fails the build naming the delta and its plan §3.1 row. Assert at minimum @@ -276,7 +281,7 @@ Answer in one line each; silence takes the recommendation. **Done when:** deleting any one fragment line makes `make uboot-rebuild` fail with a message naming it **[V]** (do it for three of them and record the output). -- [ ] **U5 — CI** — [SONNET] — Size S — Depends: U2g +- [x] **U5 — CI** — [SONNET] — Size S — Depends: U2g With decision 1's recommendation there is **no new workflow**: the build already runs U-Boot, `verify-image` already runs `ci-tests.sh`. Add to `ci-tests.sh` a "U-Boot" section that runs both U4 scripts against `images/u-boot-with-spl.sfp` and the stock blob fetched by hash @@ -289,7 +294,7 @@ Answer in one line each; silence takes the recommendation. **Done when:** a PR touching only U-Boot inputs runs the checks; the Actions-minutes delta is in the PR description. -- [ ] **U9 — Adversarial review** — [FABLE] — Size M — Depends: U2g (runs alongside U3/U5) +- [x] **U9 — Adversarial review** — [FABLE] — Size M — Depends: U2g (runs alongside U3/U5) Reads, does not edit. Against plan §8's failure taxonomy, patch by patch and symbol by symbol: can the built `.sfp` present as a boot on a board while being wrong? Specifically: the U2d bridge argument at cold boot and at warm reboot; whether the env `cmp` could pass while the @@ -320,7 +325,7 @@ the rest. ### Wave A — fan out (runs with the DE10's wave A) -- [ ] **DU1 — One U-Boot pin for both boards** — [HAIKU] — Size S — Depends: owner decision 2 +- [x] **DU1 — One U-Boot pin for both boards** — [HAIKU] — Size S — Depends: owner decision 2 If recommended: in `configs/mister_de25nano_defconfig` replace `BR2_TARGET_UBOOT_CUSTOM_VERSION=y` + `_VALUE="2026.07"` with `BR2_TARGET_UBOOT_LATEST_VERSION=y`; `git rm board/mister/de25nano/patches/uboot/uboot.hash` (its header's reason — "Buildroot's @@ -331,7 +336,7 @@ the rest. **Done when:** the two hashes match **[V]**; `check-defconfigs.sh` green; `de25-uboot.md` §2's version table updated. -- [ ] **DU2 — The QSPI-write audit as a build assertion** — [SONNET] — Size S — Depends: — +- [x] **DU2 — The QSPI-write audit as a build assertion** — [SONNET] — Size S — Depends: — `de25-uboot.md` §13 item 7 and `de25-boot-chain.md` §5's final bullet: the rule exists only as sentences. Encode §7's table as an `UBOOT_POST_BUILD_HOOKS` append in `external.mk` guarded on the DE25 defconfig: every §7 symbol **absent or unset** in the resolved `.config` @@ -342,10 +347,21 @@ the rest. row 11 in `ci-tests.sh`'s DE25 section: no `fw_env.config` in the DE25 rootfs names an MTD device. Cite the two docs in the hook's error text. **Done when:** re-adding `CONFIG_ENV_IS_IN_UBI=y` to the fragment fails the build naming - `de25-uboot.md` §7 **[V]**; `de25-boot-chain.md` §5's final bullet and its §9.3 open-concern row flip to - [V] with the commit cited. - -- [ ] **DU3 — FIT reproducibility across clean trees** — [SONNET] — Size S — Depends: DU1 + `de25-uboot.md` §7 **[V]**; `de25-boot-chain.md` §5's final bullet and its §9.3 open-concern row + record what is now enforced, with the commit cited. + **Status 2026-09-14 (933a2d2), two amendments learned by doing it:** (a) a bare + `CONFIG_ENV_IS_IN_UBI=y` never reaches the resolved `.config` (kconfig drops it — `MTD_UBI` and + `CMD_UBI` are off), so the hook audits the **fragment text first**, then the resolved config, + then `strings u-boot.itb`; the literal negative test fails on the first check. (b) The built + FIT unconditionally carries `linux_qspi_enable=if sf probe; …` from + `include/configs/socfpga_soc64_common.h` (§7 already dispositions it inert; its caller is + gated on `CADENCE_QSPI`, which the same hook proves absent), so the strings check pins exactly + one such line and fails on any other. The §5 bullet says "partially enforced": the build + assertion is (b) of that bullet's precondition; its (a), a runtime board-identity assertion in + the updater, is still unimplemented and out of this task's scope. Guarding + `linux_qspi_enable` under `CADENCE_QSPI` in the carried §8 patch is a follow-up for DU5. + +- [x] **DU3 — FIT reproducibility across clean trees** — [SONNET] — Size S — Depends: DU1 `de25-uboot.md` §5b tags cross-tree identity [U]. Two clean `O=` trees on one commit, same toolchain, `sha256sum` of `u-boot.itb` and `bl31.bin`; if they differ, `dumpimage -l` and `diffoscope`-style section comparison to name the source (build path, timestamp, @@ -353,7 +369,7 @@ the rest. budget); record the method so the D2.8 release lane can repeat it. **Done when:** §5b's [U] becomes [V] or a named cause. -- [ ] **DU4 — §13 decisions dispositioned** — [HAIKU] — Size S — Depends: owner decision 5 +- [x] **DU4 — §13 decisions dispositioned** — [HAIKU] — Size S — Depends: owner decision 5 Recommendations, one per `de25-uboot.md` §13 item: (1) keep the carried mtdids/mtdparts guard patch — one line, inert argument not needed; (2) `HANDOFF` off + declared 1 GiB as shipped; (3) `&mmc` at 25 MHz as shipped; (4) no seeded `uboot.env`; (5) keep `FS_EXFAT=y` @@ -363,13 +379,13 @@ the rest. into §13 with the date; nothing else changes. **Done when:** §13 has a disposition line per item. -- [ ] **DU5-prep — Upstream the mtdids guard** — [SONNET] — Size S — Depends: — +- [x] **DU5-prep — Upstream the mtdids guard** — [SONNET] — Size S — Depends: — `0001-configs-socfpga_soc64-guard-mtdids-mtdparts-env.patch` (`de25-uboot.md` §8 calls it upstreamable, not submitted). Same shape as U7-prep: a list-ready mail, `checkpatch.pl` clean, **not sent** without the owner's go. **Done when:** the mail exists. -- [ ] **DU6 — TF-A tag signature** — [SONNET] [NET] — Size S — Depends: — +- [x] **DU6 — TF-A tag signature** — [SONNET] [NET] — Size S — Depends: — §12 says the v2.15.0 tag's signing key was on no reachable keyserver. Try the trustedfirmware.org release announcement / the project's published key file / a WKD lookup; if found, verify the tag and record the fingerprint in @@ -379,7 +395,7 @@ the rest. ### Wave B -- [ ] **DU7 — Boot-path re-review** — [FABLE] — Size S — Depends: DU1, DU2 (may run as part of U9) +- [x] **DU7 — Boot-path re-review** — [FABLE] — Size S — Depends: DU1, DU2 (may run as part of U9) The wave-2 `fable` boot-path pass was against the custom-pin build. Re-run its checklist (`de25-nano-tasks.md` "What the boot-path fable pass established") against the DU1 artifact: no QSPI command or driver in U-Boot proper, unsigned-crc32 FIT at the factory SPL's addresses, @@ -415,5 +431,20 @@ a serial cable, or network beyond the pinned tarball and the hash-fetched stock ## Status -**2026-09-14:** plan written; nothing below U0 started. Both boards' U-Boot work is desk-only by -the owner's direction, and "it boots" remains a per-build claim that only hardware can make. +**2026-09-14, waves B/C landed:** U2g (five patches + a sixth from U9 bounding the Linux boot map, +the real fragment and env, gnutls line gone), U3 (DE10 resolved-`.config` hook in `external.mk`), +U5 (offline checks in `ci-tests.sh`, the full stock comparison in `release.yml`, patch lint over +both boards' U-Boot series — the DE25 patch's placeholder author line fixed by the orchestrator), +U8, U9 (`docs/verification/uboot-mainline.md` §11), DU3 (three clean trees byte-identical), DU4, +DU7 (wave-4 section in `de25-nano-tasks.md`). Added outside the plan the same day: the TF-A +console patch (`patches/arm-trusted-firmware/0001-…uart1.patch`, `de25-uboot.md` §8) and the +FIT-acceptance closure by reference (`de25-uboot.md` §12). Also landed the same day while gating the DE25 with a full `make de25`: the DE25 kernel pin +moved to 7.2.5 and joined the 7.2 Renovate manager, and `package/linux-rt/linux-rt.mk` stopped +registering its kconfig-file rule on trees where the package is off (`make de25` had been broken +by it since ADR 0030). Remaining for either board: hardware. + +**2026-09-14, wave A landed (933a2d2):** U1, U2a–U2f, U4a, U4b, U7-prep, DU1, DU2, DU5-prep, DU6 +done and independently verified (13 build agents, 26 verify/fix agents; three tasks needed a +second or third round). Owner decisions 1–5 resolved as recommended. Waves B/C (U2g → U3, U5, U8, +U9; DU3 → DU4 → DU7) in progress. Both boards' U-Boot work is desk-only by the owner's direction, +and "it boots" remains a per-build claim that only hardware can make. diff --git a/docs/verification/uboot-mainline.md b/docs/verification/uboot-mainline.md new file mode 100644 index 00000000..705b33dc --- /dev/null +++ b/docs/verification/uboot-mainline.md @@ -0,0 +1,1273 @@ +# Mainline U-Boot 2026.07 for the DE10-Nano — integration build, measured + +This is the evidence file for the from-source DE10-Nano bootloader: **what was built, what +differs from the stock 2017.03 fork blob, why each difference is allowed, and what no desk +check can settle.** The design lives in +[`docs/uboot-mainline-port.md`](../uboot-mainline-port.md), the decision in +[ADR 0024](../decisions/0024-mainline-uboot-capability-artifact.md), the boot contract it +has to honour in [`docs/boot-chain.md`](../boot-chain.md). This file records the run. + +**This artifact ships nowhere.** It is built by the ordinary `make` of +`configs/mister_de10nano_defconfig` into `output/images/u-boot-with-spl.sfp`, it is never +named `uboot.img`, and `scripts/mk-release.sh` stages named files only — the release keeps +embedding the stock blob (`docs/verification/sdcard-payload.md` §3 item 2). The point of +building it is that the whole image exists from source and cannot rot, and the point of +this file is that the claim "it behaves like stock" is checkable without a board. + +Every statement below is tagged **[V]** (observed in the run recorded here, with the +command) or **[U]** (unverified, with the missing input named). Nothing here was observed +on hardware: **there has been no hardware session, and none is planned** — the DE10 gate +(task U6) is deferred, not waived. See §8. + +--- + +## 1. What was built + +| Input | Value | +|---|---| +| U-Boot | **2026.07**, via `BR2_TARGET_UBOOT_LATEST_VERSION` under Buildroot 2026.08 — the only source choice Buildroot hash-verifies (plan §3.6) | +| tarball | `dl/uboot/u-boot-2026.07.tar.bz2`, sha256 `78e8bfc382fe388f9b55aa1daf8c563522a037779b5d4c349d1415e381f1243e` (verified by Buildroot at extract time, build log) | +| board defconfig | `configs/socfpga_de10_nano_defconfig` (upstream's, unmodified) | +| fragment | `board/mister/de10nano/uboot.fragment`, sha256 `8ae7b43d549d856fabf398fbf981ac96e5cb8ac521e18222a41e1274557accd0` | +| environment text | `board/mister/de10nano/uboot.env`, sha256 `65da6ec5309c5a104aa108ed26232ed12f34fb60e62a0e8e56c6d6ba095ae6a8` | +| patches | `board/mister/de10nano/patches/uboot/000{1..5}-*.patch` (§2) | +| Buildroot | 2026.08 (`Makefile:BUILDROOT_VERSION`) | +| toolchain | `arm-buildroot-linux-gnueabihf-gcc` 15.3.0 (Buildroot-built, `output/host/bin/`) | +| reference blob | stock `uboot.img`, 515,141 B, sha256 `e2d46cf9fe1ec40ca2c9c7409870249f267e06f70e5736dc6d30b4e21fe62a64` | + +Build command, from a tree configured with `make mister_de10nano_defconfig`: + +``` +make BR2_JLEVEL=12 uboot-dirclean uboot # exit 0 +``` + +Artifacts **[V]**: + +| File | Size | sha256 | +|---|---|---| +| `output/images/u-boot-with-spl.sfp` | 816,000 B | `287201ee714d351abd19f7c827a405f8c7e0f796fdaf33ccb71d518039455e46` | +| `output/build/uboot-2026.07/spl/u-boot-spl.bin` | 46,386 B | `ae00baa6fba3822e8035a7e510a6e38ba24babc9b7a6f8fa16b164136d2cbcd4` | +| `output/images/u-boot.bin` (U-Boot proper) | 553,792 B | `c9052ca30afa5942569e719689120a796b0f90a725e8b2a1adb431d15ea5abeb` | + +**Those hashes are stable.** `BR2_REPRODUCIBLE=y` is set for this board, so the build +carries a fixed `SOURCE_DATE_EPOCH`: the uImage `ih_time` is `1788535000` = 2026-09-04 +15:16:40 UTC, not this run's wall clock. That is Buildroot's own `BR2_VERSION_EPOCH` +(`work/buildroot/Makefile:97`, used at `:540` because the pinned tree is an unpacked +release tarball and not a git checkout — in a git checkout the commit date would win +instead, which is still deterministic but a different number). Two independent +`uboot-dirclean uboot` runs produced byte-identical `.sfp` files **[V]** (`cmp` silent). +That is worth stating because an earlier out-of-Buildroot scratch build of the same source +did *not* fix the epoch and its `.sfp` hash was therefore not an identity; inside Buildroot +it is. + +### The temporary `gnutls` line is gone + +Wave A's wiring had to add `BR2_TARGET_UBOOT_NEEDS_GNUTLS=y` to +`configs/mister_de10nano_defconfig`, because the pristine board defconfig resolves +`CONFIG_TOOLS_MKEFICAPSULE=y` (it is `default y if EFI_LOADER`) and `tools/mkeficapsule.c` +hard-requires `gnutls/gnutls.h`. The fragment's `# CONFIG_TOOLS_MKEFICAPSULE is not set` +(§7 of the fragment) removes the pull-in, so the line was deleted here and the claim was +proved the only way that settles it — **a `uboot-dirclean` rebuild with the line removed, +which exits 0 and mentions gnutls nowhere** (`grep -ci gnutls build-final.log` → `0`) **[V]**. +CI therefore does not pay for the `host-gnutls`/nettle/p11-kit/libtasn1/libunistring chain. + +Be precise about what that rebuild proves. `output/` is not a clean tree: wave A's build +had already installed `host-gnutls` (`output/build/host-gnutls-3.8.13/`, +`output/host/lib/libgnutls.so`), and `uboot-dirclean` does not remove them, so a rebuild +here cannot *observe* that nothing latent would still need them. The claim rests instead on +gates that are deterministic in the tree: `output/.config` has +`# BR2_TARGET_UBOOT_NEEDS_GNUTLS is not set` (the only thing that adds `host-gnutls` to +`UBOOT_DEPENDENCIES`, `boot/uboot/uboot.mk`); the resolved U-Boot `.config` has +`# CONFIG_TOOLS_MKEFICAPSULE is not set`, and `tools/Makefile`'s +`hostprogs-always-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule` is the only consumer of +`gnutls`, so `tools/mkeficapsule` is absent from the build tree; and the build log has zero +`gnutls`/`lgnutls` hits. The first from-scratch observation is CI's (task U5), which +starts from no `output/` at all. + +`configs/mister_de10nano_defconfig` is canonical after the change: `make savedefconfig` +reproduces its `BR2_` lines exactly, and `scripts/check-defconfigs.sh` is green for all +three defconfigs **[V]** (§6). + +--- + +## 2. The five carried patches + +They live in `board/mister/de10nano/patches/uboot/` and are picked up by +`BR2_GLOBAL_PATCH_DIR` exactly like `patches/linux/`. **No `series` file is needed:** +Buildroot applies `*.patch` in sorted order and no two of these touch the same file, so the +numeric order *is* the applied order. + +| # | Patch | Touches | Class | +|---|---|---|---| +| 0001 | `arm-socfpga-fix-dead-raw-sector-hook-guard` | `arch/arm/mach-socfpga/board.c` | upstream bug fix (plan §3.1 delta 2) | +| 0002 | `fs-exfat-fix-64-bit-division-on-32-bit-arm` | `fs/exfat/time.c` | upstream bug fix (delta 3) | +| 0003 | `board-terasic-de10-nano-mister-qts-handoff` | `board/terasic/de10-nano/qts/{iocsr,pinmux,pll,sdram}_config.h` | carried fork data (delta 4) | +| 0004 | `cmd-mem-add-mt-memory-test-against-value` | `cmd/mem.c` | carried fork command (plan §3.4) | +| 0005 | `arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode` | `arch/arm/mach-socfpga/misc_gen5.c` | carried fork behaviour (plan §3.3) | + +sha256, as committed: + +``` +3ef5b26b6b916e459318d709998f99c059544317019a2f840f4e8ef0f7a673d1 0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch +5f9be8eabb0f77aa3cf3ccc8624f798ac8ba69beebee7c8f475f59c36bd58bb8 0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch +96a54b3d6223a3f883101eba1f9202b9ca19cbb395e0ecd38942fa1bd5aae535 0003-board-terasic-de10-nano-mister-qts-handoff.patch +01e0de68875c02180bb4af13f4703ce150432267b86d7738b379b2e9104497a8 0004-cmd-mem-add-mt-memory-test-against-value.patch +7118f4c0bc69572a6ebb13d13785c0467aff9e155c80754ed3ec9b2f4dc756c2 0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch +``` + +### All five apply, at zero fuzz — the build's own transcript **[V]** + +Buildroot's `support/scripts/apply-patches.sh` runs `patch -p1 -F0 -g0 --no-backup-if-mismatch -t -N`, +so a hunk that needed fuzz or an offset would be *rejected*, not accepted quietly, and any +`Hunk #n succeeded at … (offset …)` line would appear here. None does: + +``` +>>> uboot 2026.07 Patching + +Applying 0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch using patch: +patching file arch/arm/mach-socfpga/board.c + +Applying 0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch using patch: +patching file fs/exfat/time.c + +Applying 0003-board-terasic-de10-nano-mister-qts-handoff.patch using patch: +patching file board/terasic/de10-nano/qts/iocsr_config.h +patching file board/terasic/de10-nano/qts/pinmux_config.h +patching file board/terasic/de10-nano/qts/pll_config.h +patching file board/terasic/de10-nano/qts/sdram_config.h + +Applying 0004-cmd-mem-add-mt-memory-test-against-value.patch using patch: +patching file cmd/mem.c + +Applying 0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch using patch: +patching file arch/arm/mach-socfpga/misc_gen5.c +``` + +`scripts/lint-kernel-patches.sh board/mister/de10nano/patches/uboot` is also green — all +five are `git am`-able, i.e. their mail headers parse, which the `patch -p1` path never +exercises **[V]**: + +``` +=== board/mister/de10nano/patches/uboot +ok 0001-arm-socfpga-fix-dead-raw-sector-hook-guard.patch Michael C. Ferguson +ok 0002-fs-exfat-fix-64-bit-division-on-32-bit-arm.patch Michael C. Ferguson +ok 0003-board-terasic-de10-nano-mister-qts-handoff.patch Sorgelig +ok 0004-cmd-mem-add-mt-memory-test-against-value.patch Sorgelig +ok 0005-arm-socfpga-gen5-release-bridges-after-reset-if-fpga-in-user-mode.patch Sorgelig + +RESULT: PASS — all 5 patches in 1 series are `git am`-able. +``` + +### Each patch's effect, observed in the built binaries **[V]** + +* **0001 — the `+0x200` hook is linked and strong.** + + ``` + $ arm-buildroot-linux-gnueabihf-nm output/build/uboot-2026.07/spl/u-boot-spl | grep raw_sector + ffff0154 T board_spl_mmc_get_uboot_raw_sector + ffff1682 W spl_mmc_get_uboot_raw_sector + ``` + + Note the second line. Mainline's fallback is a **three-hop** weak chain + (`spl_mmc_get_uboot_raw_sector` → `board_spl_mmc_get_uboot_raw_sector` → + `arch_spl_mmc_get_uboot_raw_sector`, `common/spl/spl_mmc.c:322/:316/:310`), so a `W` + symbol whose name contains `raw_sector` is present with *or without* this patch. **An + assertion must match `T board_spl_mmc_get_uboot_raw_sector` exactly** — a bare grep for + `raw_sector` passes on a broken build. This is the note U3's hook has to honour. + +* **0002 — exFAT links and is in the image.** `CONFIG_FS_EXFAT=y` builds and links (the + whole build is exit 0, which it could not be with the unresolved `__aeabi_ldivmod`), and + `strings output/images/u-boot.bin` carries `EXFAT`, `exfat` and + `unsupported exFAT version: %hhu.%hhu`. + +* **0003 — the fork's handoff tables are in our SPL.** §5, the handoff-equality gate. + +* **0004 — `mt` exists with stock's help string.** `nm` shows `0100eb34 t do_mem_mt`, the + parity check finds `mt` in the built command table (§4 block 5), and the built binary + carries the string `memory test against value`, which is byte-for-byte what the stock + blob carries. + +* **0005 — `arch_early_init_r()` reaches the release path.** Disassembly of the built + U-Boot proper shows both calls, the pristine one and the added one: + + ``` + 100278a: 2001 movs r0, #1 + 100278c: f000 f8d8 bl 1002940 + … + 100279a: 2000 movs r0, #0 + 100279c: f000 f8d0 bl 1002940 + ``` + + The second is the patch: `socfpga_bridges_reset(0)` as the last statement of + `arch_early_init_r`, which is the fork's warm-reboot behaviour. The equivalence argument + is in the patch header and in plan §3.3; **what it does on a board is [U]** (§8). + +--- + +## 3. The resolved `.config` — every symbol U3 will assert + +Read out of `output/build/uboot-2026.07/.config` *after* the fragment merge and +`olddefconfig`, which is the only file that tells the truth (the fragment is a request; the +board defconfig, the Kconfig defaults and Buildroot's own fixups all get a say). Plan §5 +step 2's list, plus the four the task adds **[V]**: + +``` +CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE=y +CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION_TYPE=0xa2 +# CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR is not set +CONFIG_FS_EXFAT=y +CONFIG_ENV_IS_IN_MMC=y +CONFIG_ENV_OFFSET=0x200 +CONFIG_ENV_SIZE=0x1000 +CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE=y +CONFIG_ENV_DEFAULT_ENV_TEXT_FILE="…/board/mister/de10nano/uboot.env" +CONFIG_TEXT_BASE=0x01000040 +CONFIG_SPL_PAD_TO=0x10000 +CONFIG_ARCH_SOCFPGA_GEN5=y +CONFIG_CMD_MEMORY=y +``` + +and the rest of the fragment's deltas, for completeness: + +``` +CONFIG_BOOTDELAY=0 +CONFIG_AUTOBOOT_KEYED=y +CONFIG_AUTOBOOT_STOP_STR="\e" +CONFIG_SYS_BOOTM_LEN=0x4000000 +# CONFIG_TOOLS_MKEFICAPSULE is not set +# CONFIG_SPL_SPI is not set +# CONFIG_SPL_DM_SPI is not set +CONFIG_CMD_UNZIP=y +CONFIG_LOOPW=y +CONFIG_CMD_MEMINFO=y +``` + +**Four symbols are ABSENT from the resolved `.config` rather than set to `n`**, and an +assertion that string-matches `# CONFIG_X is not set` on any of them fails on a correct +build: + +| Symbol | Why it vanishes | +|---|---| +| `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR` | `depends on …USE_SECTOR` (`common/spl/Kconfig:581`) — this is what plan §3.1 means by "the SECTOR symbol disappears outright", and it is what makes a double-count to `+0x400` unreachable | +| `CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_DATA_PART_OFFSET` | same dependency (`:598`) | +| `CONFIG_SPL_SPI_FLASH_SUPPORT` | `depends on SPL_SPI` (`:1539`) | +| `CONFIG_SPL_SPI_LOAD` | inside `if SPL_SPI_FLASH_SUPPORT` (`:1548-1579`, symbol at `:1573`) | + +`CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE` deserves a note of its own: it is **not** in the +fragment and cannot be. Buildroot writes it, and the absolute path beside it, from +`BR2_TARGET_UBOOT_DEFAULT_ENV_FILE` in `UBOOT_KCONFIG_FIXUP_CMDS` — *after* the fragments +are merged and *before* `olddefconfig`. Resolving the fragment on its own leaves it `is not +set`; only the real Buildroot build produces the `=y` above. Any check of that symbol must +therefore run inside the build, not against the fragment. + +### The merge dropped nothing + +`merge_config.sh` reports a "Value of … is redefined" block per changed symbol and would +report a dropped one; fifteen blocks appeared, one per directive that actually changes +something — twelve for the fragment as staged by U2e plus three for §4b's block 10 — with +no other diagnostics (`grep -c 'is redefined by fragment'` on the final build's log → `15`; +the first build, before block 10, showed `12`). The three deliberate restatements +(`ENV_IS_IN_MMC`, `TEXT_BASE`, `CMD_MEMORY`) produce no block precisely because they already +resolve to the written value — which is the drift alarm they exist to be. `olddefconfig` +emitted exactly one warning, the intended one: + +``` +.config:2141:warning: override: SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE changes choice state +``` + +--- + +## 4. `scripts/check-uboot-parity.sh` — the structural, environment and command-table gate + +``` +$ SPL_SIZE_LIMIT=$(output/build/uboot-2026.07/tools/spl_size_limit) \ + scripts/check-uboot-parity.sh \ + output/images/u-boot-with-spl.sfp \ + /uboot.img \ + output/build/uboot-2026.07/u-boot +``` + +Exit **0** **[V]**. Full transcript: + +``` +check-uboot-parity.sh: built output/images/u-boot-with-spl.sfp (816000 bytes) +check-uboot-parity.sh: stock .../uboot.img (515141 bytes) +check-uboot-parity.sh: elf output/build/uboot-2026.07/u-boot + +[1] legacy uImage header at 0x00040000 + stock: ih_size=252933 ih_load=0x01000040 ih_ep=0x00000000 +ok uImage magic 0x27051956 present at 0x00040000 + ih_name = "U-Boot 2026.07 for de10-nano boa" + ih_time = 1788535000 (allowed diff: build timestamp) + ih_size = 553792 ih_load = 0x01000040 ih_ep = 0x01000040 + os/arch/type/comp = 17/2/5/0 (want 17/2/5/0 = U-Boot/ARM/firmware/none) +ok uImage os/arch/type/comp match stock's IH_OS_U_BOOT/IH_ARCH_ARM/IH_TYPE_FIRMWARE/IH_COMP_NONE +ok ih_load = 0x01000040 — the SPL of the fork jumps to ih_load (common/spl/spl.c:110) +ok ih_ep = 0x01000040 (allowed diff: mainline sets CONFIG_TEXT_BASE, the fork leaves 0 — plan §3.3) + mixing warning: a stock SPL reads ih_load, a mainline SPL reads ih_ep (common/spl/spl_legacy.c:57) — do not pair an SPL and a uImage from different builds +ok total size closes the file: 0x00040000 + 64 + 553792 = 816000 +ok uImage header CRC recomputes: 0xbfe8e684 +ok uImage payload CRC recomputes over all 553792 bytes: 0x20beac5d + +[2] SPL region: 4 copies of 65536 bytes +ok SPL copy 1 at 0x00010000 is byte-identical to copy 0 +ok SPL copy 2 at 0x00020000 is byte-identical to copy 0 +ok SPL copy 3 at 0x00030000 is byte-identical to copy 0 + +[3] socfpga SPL header at +0x00000040 of copy 0 + validation=0x31305341 version=0 flags=0 length_u32=11600 (46400 bytes) zero=0 checksum=0x0172 +ok validation word 0x31305341 present — the BootROM will accept this SPL +ok SPL header version/flags/zero are stock's 0/0/0 +ok header checksum recomputes: 0x0172 (socfpgaimage.c off-by-one reproduced) +ok SPL payload length 46400 bytes is inside the 65536-byte slot (headroom 19136 bytes, 70% used) +ok SPL payload CRC32 over [0,46396) recomputes: 0x54fe73cf (stored at +0x0000b53c) +ok the rest of copy 0 (19136 bytes) is zero padding +ok SPL payload 46400 <= SPL_SIZE_LIMIT 62752 (headroom 16352 bytes) + +[4] default_environment[] parity + stock env: payload offset 0x00028018, 1150 bytes, 21 entries (file offset 0x00068058) +ok stock env is where plan §6 says it is: 0x00028018, 1150 bytes + nm -S: default_environment at 0x0106cb70 size 1150 -> payload offset 0x0006cb30 + built env: payload offset 0x0006cb30, 1150 bytes, 21 entries (file offset 0x000acb70) + located from the ELF symbol; the scan is only a cross-check + scan: anchored on "bootcmd=" (1 match(es) in the payload), blob at 0x0006cac9, 1253 bytes, 31 entries + the scan and the ELF disagree (0x0006cac9 vs 0x0006cb30) — the ELF wins; a scan-only run of this image would compare the wrong bytes +ok environment is byte-identical to stock: 1150 bytes, 21 entries (`mt` carried, plan §3.4 — no allowed delta) + +[5] command table + stock: 69 entries at payload offset 0x00033410, stride 28 (allowed diff: offset and stride) + built: 112 entries at payload offset 0x00070f6c, stride 28 +ok all 69 stock command names are present (112 total in the build) +ok `mt` is present — stock's fpgacheck runs unmodified (plan §3.4) + extra commands, allowed: [ bind blkcache bootefi bootelf bootflow bootp bootvx chpart dfu dhcp eficonfig ext2load ext2ls fatmkdir fatrm fatwrite fstypes gpio i2c iminfo imxtract ln mdio mii mkdir mtdparts mv net panic part ping pxe random rm sf sspi sysboot tftpboot ums unbind usb usbboot + +[6] allowed diffs (plan §6): version string and build timestamp; uImage + ih_ep 0x01000040 vs 0x00000000; total image size; code layout and + table offsets inside the SPL; extra commands. Everything else above is + a hard contract. +check-uboot-parity.sh: parity holds +``` + +### 4a. The environment, proved a second way + +The script locates the built blob from the ELF symbol; here is the same claim made +independently, straight out of the two files with `dd` **[V]**: + +``` +$ dd if=/uboot.img bs=1 skip=$((0x68058)) count=1150 of=stock.env.bin +$ dd if=output/images/u-boot-with-spl.sfp bs=1 skip=$((0xACB70)) count=1150 of=built.env.bin +$ cmp stock.env.bin built.env.bin # silent +$ sha256sum stock.env.bin built.env.bin +f8c20f3e07669b258adc20d47f68a3ec6b5c21c81cabb2971f999364cd0fef54 stock.env.bin +f8c20f3e07669b258adc20d47f68a3ec6b5c21c81cabb2971f999364cd0fef54 built.env.bin +$ tr '\0' '\n' < built.env.bin | grep -c . +21 +``` + +**1,150 bytes, 21 entries, byte-identical** — including the malformed entry 15 +(`bootm $loadaddr - $fdt_addr`, no `=`), which is the fingerprint boot-chain §3.1 traces +into `himport_r()`'s "no `=` before the NUL" branch. Because `mt` is carried (plan §3.4) +there is no allowed environment delta at all: the comparison is a plain `cmp`, not a +delta list. + +Two offsets worth pinning down, because both plan §6 and `docs/uboot-tasks.md` write them +loosely as "`0x28018` of the stock `uboot.img`": `0x28018` is the offset **into U-Boot +proper**. In `uboot.img` the same bytes are at **`0x68058`** — `0x28018` plus the image's +prefix of four 64 KiB SPL copies (`0x40000`) and the 64-byte legacy uImage header. The +check script has this right (its header says so explicitly); the plan and task text are the +ones that need the wording fixed. **[U] for task U8** — it is not in this task's file scope. + +### 4b. Four stock commands mainline did not have + +The first parity run against a real build failed: + +``` +FAIL 4 of stock's 69 commands are missing: gzwrite loopw meminfo unzip +``` + +Plan §6 lists "a missing command from stock's 69-entry table" among the **forbidden** +diffs (extras are allowed, absences are not), so this was closed rather than excused. +All four commands exist in mainline v2026.07 and were simply off in the board defconfig; +`CONFIG_CMD_UNZIP=y` (which builds `cmd/unzip.o`, carrying **both** `unzip` and `gzwrite` +— there is no separate `GZWRITE` symbol in this tree), `CONFIG_LOOPW=y` and +`CONFIG_CMD_MEMINFO=y` were added to the fragment as block 10, with the reasoning in the +fragment itself. All three live in `cmd/`, i.e. in U-Boot proper, so the SPL payload is +unchanged by them (46,400 B before and after) and the `.sfp` grew by 2,576 B. + +This is a genuine change of scope relative to task U2e, which was told not to *strip* +mainline's extras and quite reasonably did not anticipate mainline being *short*. Nothing +in stock's environment calls any of the four; the reason to carry them is parity with the +shipped table, which is this port's specification. + +--- + +## 5. `scripts/check-uboot-handoff.sh` — the QTS handoff-equality gate + +This is the check for the one silent-brick delta a cold-boot smoke test could not catch: +the build picking up mainline's `qts/*.h` instead of the carried fork ones (plan §3.2a). +It packs the seven handoff tables out of the headers **in the patched build tree** and +asserts each byte string is present in SPL copy 0 of both images. Offsets are reported, +never compared — they are code layout. + +``` +$ scripts/check-uboot-handoff.sh \ + output/build/uboot-2026.07/board/terasic/de10-nano/qts \ + output/images/u-boot-with-spl.sfp \ + /uboot.img +``` + +Exit **0** **[V]**: + +``` +check-uboot-handoff.sh: qts-dir=output/build/uboot-2026.07/board/terasic/de10-nano/qts +check-uboot-handoff.sh: image-a=output/images/u-boot-with-spl.sfp +check-uboot-handoff.sh: image-b=.../uboot.img +ok sys_mgr_init_table @ 0x0a63c in output/images/u-boot-with-spl.sfp (207 bytes, 207 elements) +ok sys_mgr_init_table @ 0x0aac8 in .../uboot.img (207 bytes, 207 elements) +ok iocsr_scan_chain0_table @ 0x091a4 in output/images/u-boot-with-spl.sfp (96 bytes, 24 elements) +ok iocsr_scan_chain0_table @ 0x096b8 in .../uboot.img (96 bytes, 24 elements) +ok iocsr_scan_chain1_table @ 0x09204 in output/images/u-boot-with-spl.sfp (216 bytes, 54 elements) +ok iocsr_scan_chain1_table @ 0x09718 in .../uboot.img (216 bytes, 54 elements) +ok iocsr_scan_chain2_table @ 0x092dc in output/images/u-boot-with-spl.sfp (120 bytes, 30 elements) +ok iocsr_scan_chain2_table @ 0x097f0 in .../uboot.img (120 bytes, 30 elements) +ok iocsr_scan_chain3_table @ 0x09354 in output/images/u-boot-with-spl.sfp (2096 bytes, 524 elements) +ok iocsr_scan_chain3_table @ 0x09868 in .../uboot.img (2096 bytes, 524 elements) +ok ac_rom_init @ 0x08d70 in output/images/u-boot-with-spl.sfp (144 bytes, 36 elements) +ok ac_rom_init @ 0x090f8 in .../uboot.img (144 bytes, 36 elements) +ok inst_rom_init @ 0x08fa8 in output/images/u-boot-with-spl.sfp (508 bytes, 127 elements) +ok inst_rom_init @ 0x094bc in .../uboot.img (508 bytes, 127 elements) + 14 found, 0 missing (7 tables x 2 images = 14 checks expected) +check-uboot-handoff.sh: all seven handoff tables present in SPL copy 0 of both images +``` + +Every stock-side offset reproduces plan §3.2a's table exactly. The built-side offsets +differ, as they must — nine years of code layout. + +### 5a. The negative run — why this gate is not vacuous + +A check that passes on everything proves nothing, so `scripts/test-uboot-handoff.sh` runs +the same gate with **pristine mainline's** `qts/*.h` (extracted fresh from the pinned +tarball at run time) against the same stock blob, and requires it to **fail**. It does, +naming exactly the four tables plan §3.2 says diverged and still finding the three that +never did **[V]** (the `qts-dir=` line names a `mktemp` directory and therefore varies +between runs; everything else is deterministic): + +``` +check-uboot-handoff.sh: qts-dir=/u-boot-2026.07/board/terasic/de10-nano/qts +check-uboot-handoff.sh: image-a=.../uboot.img +check-uboot-handoff.sh: image-b=.../uboot.img +FAIL sys_mgr_init_table NOT FOUND in .../uboot.img (207 bytes, 207 elements packed) +FAIL sys_mgr_init_table NOT FOUND in .../uboot.img (207 bytes, 207 elements packed) +FAIL iocsr_scan_chain0_table NOT FOUND in .../uboot.img (96 bytes, 24 elements packed) +FAIL iocsr_scan_chain0_table NOT FOUND in .../uboot.img (96 bytes, 24 elements packed) +FAIL iocsr_scan_chain1_table NOT FOUND in .../uboot.img (216 bytes, 54 elements packed) +FAIL iocsr_scan_chain1_table NOT FOUND in .../uboot.img (216 bytes, 54 elements packed) +FAIL iocsr_scan_chain2_table NOT FOUND in .../uboot.img (120 bytes, 30 elements packed) +FAIL iocsr_scan_chain2_table NOT FOUND in .../uboot.img (120 bytes, 30 elements packed) +ok iocsr_scan_chain3_table @ 0x09868 in .../uboot.img (2096 bytes, 524 elements) +ok iocsr_scan_chain3_table @ 0x09868 in .../uboot.img (2096 bytes, 524 elements) +ok ac_rom_init @ 0x090f8 in .../uboot.img (144 bytes, 36 elements) +ok ac_rom_init @ 0x090f8 in .../uboot.img (144 bytes, 36 elements) +ok inst_rom_init @ 0x094bc in .../uboot.img (508 bytes, 127 elements) +ok inst_rom_init @ 0x094bc in .../uboot.img (508 bytes, 127 elements) + 6 found, 8 missing (7 tables x 2 images = 14 checks expected) +check-uboot-handoff.sh: HANDOFF MISMATCH — 8 table/image pair(s) missing, named above +``` + +`test-uboot-handoff.sh` itself exits 0 — both fixtures behaved as documented, the positive +one finding all seven at plan §3.2a's offsets **[V]**. + +**What this gate cannot see**, restated from the script's own header so the gap is on the +record: the QTS **scalar** `#define`s — `CFG_HPS_SDR_CTRLCFG_FPGAPORTRST`, the two s2f PLL +counts, `REG_FILE_INIT_SEQ_SIGNATURE` — compile to instruction immediates and are not +greppable. They rest on the source diff of plan §3.2 plus `qtsdiff.py`'s two runs recorded +in patch 0003's header **[V by source, U by binary]**. + +--- + +## 6. SPL size and headroom + +| | Bytes | +|---|---| +| `spl/u-boot-spl.bin` | **46,386** | +| SPL payload as the socfpga header counts it (`length_u32` × 4) | 46,400 | +| 64 KiB SPL slot | 65,536 | +| `tools/spl_size_limit` (the link-time limit `SPL_SIZE_CHECK` enforces) | 62,752 | +| free in the slot | **19,136 (29.2 %)** | +| free against the link limit | **16,352 (26.1 %)** | + +Plan §3.5's target was "12.6 % → 30.4 % of the 64 KiB slot" after dropping the unused SPL +SPI/QSPI stack, measured on 2026.04. This build lands at **29.2 %** on the same metric: the +direction and the magnitude both reproduce, the last point is a 2026.07-vs-2026.04 +difference, not a regression. Stock's own SPL is 45,820 B, i.e. 30.1 % slot-free, so the +margin is now within a point of stock's **[V]**. + +Two things this buys, both of them plan §3.5's argument: + +* Mainline enforces `SPL_SIZE_CHECK` at **build** time. The 2017.03 fork does not: + `tools/socfpgaimage.c:218-225` guards with no `else`, so an oversized SPL prints + "Not a sane SOCFPGA preloader", **exits 0**, and writes a header with a `length_u32` + truncated through a `uint16_t`. Choosing mainline closes that silent-corruption window. +* The `0xA2` partition is 4 MiB in our own `board/mister/de10nano/genimage-sdcard.cfg`, so + the 816,000 B `.sfp` fits with room. What size **mr-fusion and the Windows SD installer** + create is still open (plan §9), and `updateboot` `dd`s with no size check **[U]**. + +--- + +## 7. Every allowed diff, enumerated and explained + +Plan §6 names these; each is present in this build and each is here on purpose. + +| # | Diff | Stock | Built | Why it is allowed | +|---|---|---|---|---| +| 1 | Version string | `U-Boot 2017.03+ (Apr 02 2025 - 20:16:03 +0800)` | `U-Boot 2026.07 (Sep 04 2026 - 15:16:40 +0000)` | The whole point of the port. Nothing in the boot path parses it. | +| 2 | uImage `ih_time` | 2025-04-02 | `1788535000` | Build timestamp, fixed by `BR2_REPRODUCIBLE`'s `SOURCE_DATE_EPOCH` rather than the wall clock — so it is stable, not merely tolerated. | +| 3 | uImage `ih_ep` | `0x00000000` | `0x01000040` | Mainline sets it from `CONFIG_SYS_UBOOT_START`; the fork leaves it 0. **Each build is self-consistent, but the two SPLs read different fields** — the fork's uses `ih_load` (`common/spl/spl.c:110`), mainline's uses `ih_ep` (`common/spl/spl_legacy.c:57`). See the hazard below. | +| 4 | Total size | 515,141 B | 816,000 B | Nine years of U-Boot, and mainline's `EFI_LOADER`/`NET`/`USB` still on. Fits the 4 MiB `0xA2` partition with room (§6). | +| 5 | `ih_size` / U-Boot proper | 252,933 B | 553,792 B | Same reason. | +| 6 | Code layout, table offsets in the SPL | — | — | Presence and byte-equality are the contract (§5); position is not. | +| 7 | Command table offset and stride | `0x33410` | `0x70f6c` | Same. Stride is 28 in both, incidentally. | +| 8 | 43 extra commands | — | listed in §4 block 5 | Plan §6 allows extras explicitly; **removing** them is the risk, since stock's environment might call one. | + +**Forbidden, and none of them occurred [V]:** any environment byte; a missing command from +stock's 69; any handoff table absent from or differing in the built SPL; any layout change +of the four SPL copies or the uImage; a load-address change; any SPL header field change; +`USE_SECTOR` reappearing. + +### The `ih_ep`/`ih_load` mixing hazard — read this before flashing anything partially + +Diff 3 is not cosmetic and it is the one that can brick a card with two individually +correct files. **A stock SPL paired with our `uboot.img`-shaped payload, or our SPL paired +with stock's, jumps to the wrong address** — stock's SPL reads `ih_load` (which we set +correctly, so that direction survives), but *our* SPL reads `ih_ep`, which stock leaves +`0x00000000`. Our SPL + stock's U-Boot proper therefore branches to `0x0`. + +The rule is: **the `.sfp` is atomic.** Never flash part of one build over part of another. +`updateboot` already writes the whole blob, and `scripts/mk-sdcard.sh` writes the whole +stock `uboot.img`, so nothing in this repo does the dangerous thing today — but a recovery +procedure invented under pressure with a card reader might, and this is where it is +written down. The parity script prints the same warning on every run. + +--- + +## 8. What only hardware can show + +Nothing in this file was observed on a board, and nothing in it can be. These are the +claims the desk cannot settle **[U]** — the DE10 hardware gate (task U6) is deferred +indefinitely, not waived, and the list below is its agenda. + +1. **That it boots at all.** The SPL is structurally valid and the BootROM's acceptance + criteria (validation word, checksum, length) are checked byte-for-byte — but "the + BootROM accepts this header" is not "DDR calibrates". Which leads to: +2. **That a 2026 gen5 SPL consuming the fork's 2017 handoff data calibrates this board's + DDR3.** §5 proves the *data* our SPL carries is the data stock's SPL carries; the + sequencer *code* consuming it has nine years of upstream change. Plan §3.2a is explicit + that this narrows the risk rather than removing it. +3. **The scalar handoff values.** `FPGAPORTRST=0x3FFF` releases 14 FPGA→SDRAM port resets + where mainline's `0x1FF` releases 9; the two s2f user-clock counts differ. These are not + greppable in a binary (§5), so only a running core that uses SDRAM through the fabric + exercises them. +4. **Patch 0005's three boot claims** (plan §3.3): that a fabric-preserving warm reset + actually reaches `arch_early_init_r` with the FPGA reporting user mode; that a core + stays reachable from Linux across it; and that cold boot still loads `menu.rbf` + unchanged. The equivalence argument is source-level and is the deliverable; the + behaviour is not observable here. `objdump` proving the call is emitted (§2) is a much + weaker claim than "the warm-reboot path works". +5. **`mt`'s runtime exit-status sense inside `fpgacheck`.** The command is present with + stock's help string and stock's environment calls it unmodified, and the sandbox + transcript in patch 0004's header exercises the hush `if` both ways — but not on this + silicon against the real `0x1FFFF000` mailbox (boot-chain §6.1/§6.3). +6. **exFAT reads.** `CONFIG_FS_EXFAT` links and the driver is in the image; that + `load mmc 0:1 … /linux/zImage_dtb` succeeds off a real exFAT partition formatted by our + installer is untested. +7. **`CONFIG_SYS_BOOTM_LEN` and `bootz`.** Plan §9 Q6 — whether `bootz` on a raw + self-decompressing zImage consults the symbol at all — is **still open**. The fragment + sets stock's 64 MiB, which can only widen a bound, and says in its own text that it does + not close the question. +8. **Whether anything in the SPL touches `0x1FFFF000`–`0x1FFFFFFF`** (the warm-reboot + mailbox, boot-chain §6.3) before U-Boot proper reads it. Not checked here; on task U9's + list. + +Until those are answered, the honest status of this artifact is exactly what ADR 0024 says: +**a buildable, checkable capability that ships nowhere.** + +--- + +## 9. Integration notes and open advisory items + +Findings the wave-A verifiers raised against files integrated here. The factual ones were +fixed in place during integration; the rest are recorded so they are not lost. + +**Fixed here [V]:** + +* `uboot.fragment` — an 81-column line rewrapped; the two `merge_config.sh` copies' + differences stated completely (they also differ in a rewrap of the usage text); + `common/spl/Kconfig:587` quoted in full (`default 0x200 if ARCH_SOCFPGA_GEN5 || ARCH_AT91`). +* `uboot.env` — the `filechk_defaultenv.h` sed described exactly (`s/\\\x0\s*//g` also + swallows whitespace after the backslash-NUL). +* Patch 0001 — the weak fallback is a **three**-hop chain, not two, and the header now says + so and spells out the consequence for U3's assertion (§2). +* Patch 0002 — U-Boot does not "link no libgcc division routine": it carries + `__aeabi_uldivmod` in `arch/arm/lib` and lacks the **signed** `__aeabi_ldivmod`, which is + what a signed `time_t` pulls in. Corrected in both the commit message and the in-code + comment (line count preserved, so the hunk header is unchanged). +* Patch 0003 — the checkpatch totals in the `[V]` bracket were not reproducible from the + deliverable; restated as the reproducible 3E/7W/0C → 3E/3W/0C, with the substantive claim + (four "Misplaced SPDX" warnings before, zero after) unchanged. +* Patch 0004 — the fork's remote is `MiSTer-devel/U-Boot_MiSTer`, not + `MiSTer-devel/Buildroot-MiSTer`. +* Patch 0005 — `[PATCH 5/5]` → `[PATCH]`, matching its four siblings and surviving a + reorder; `fpgamgr_test_fpga_ready()`'s **function body** is character-identical, the + files are not; the `--follow` commit count corrected to 33/71. +* Patches 0003 and 0005 — the diff bodies used bare `--- a/`/`+++ b/` framing with no + `diff --git`/`index` lines, unlike 0001/0002/0004. Framed uniformly now; the `index` + blob ids were produced by `git apply --index` + `git diff --cached` of each patch on the + pristine 2026.07 files, not typed in. Applies identically (the rebuild below is the proof). +* Patch 0004 — line 1 read `From 0000000000000000000000000000000000000000`; now carries the + fork's origin commit `c0ed23f52e203d704c0b149408f2cc0b1bb48939` like the 0003/0005 + siblings (`git -C work/U-Boot_MiSTer cat-file -t` → `commit`). +* Patches 0001 and 0002 — `docs/uboot-tasks.md` (U2a) wants the header to say the literal + "to be submitted upstream (U7)"; the phrase now appears in both `Upstream status:` + fields, still followed by the "drafted by U7-prep, NOT sent, owner decision 4" sentence + and the note that U7 is "send, on the owner's go only". +* `uboot.env` — its `Disposition:` field said it was paired with the *fragment's* + `CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE=y`; the fragment deliberately does not contain that + symbol (§3). Reworded to say Buildroot's `UBOOT_KCONFIG_FIXUP_CMDS` writes it from + `BR2_TARGET_UBOOT_DEFAULT_ENV_FILE` and the fragment owns only the storage side. Comment + lines only — the built blob is unchanged (§4a). + +**Open [U]:** + +* **`Signed-off-by: Sorgelig` on patches 0003/0004/0005.** The fork commits they carry have + no sign-off (`git log -1 --format=%B dadd1c8978` is a single line), so the trailer attests + a DCO the author never gave. The repo's existing fork-origin kernel patches are + inconsistent about this (`linux-patches/0001` and `0018` add it, `0002` does not). This is + a repo-wide template question, not a defect of any one patch — **owner's call**; left as + staged rather than decided unilaterally here. +* **`0x28018` vs `0x68058`.** Plan §6 and `docs/uboot-tasks.md` describe the stock + environment blob's offset as "of the stock `uboot.img`"; it is the offset into U-Boot + proper (§4a). For task U8; both files are outside this task's scope. +* **Plan §3.1 row 1 says the unselected raw-mode default lands at absolute LBA `0x400`.** + In the pinned v2026.07 tree it is `0x200` (`common/spl/Kconfig:587`, and the baseline + `.config`). The nature of the delta is unchanged; the number is wrong. For task U9 or + whoever owns the plan. +* **Plan §3.1 row 3 cites `fs/fat/fat.c:68-95`** for the FAT-type rejection; in v2026.07 it + is `:223-226`. Same class. +* **Plan §3.1 row 2 cites `origin/main` at `134ad3c3c0`**; that object does not exist in the + local mirror, whose head is `5c215cb75c` ("Prepare v2026.10-rc1", 2026-07-27) and still + carries the dead guard. The patch header cites `5c215cb75c` correctly. +* **Patch 0004's checkpatch findings** (4 errors / 3 warnings / 1 check under `--strict`) + are carried verbatim from the fork's 2017 C and are documented as deliberately kept. The + patch is never mailed, so this is a note rather than a debt. +* **`scripts/test-uboot-parity.sh`'s `repair_dcrc` fixture helper** re-stamps the uImage + payload CRC but not the header CRC, so two of its fixtures also emit a header-CRC failure + they do not assert on. Exit codes and asserted messages are unaffected. Owner of the + fixture script (task U4a/U5). +* **`uboot.env` carries no `Signed-off-by` footer.** It has CONTRIBUTING §2's four content + fields (Origin / Author-copyright / Upstream status / Disposition), but §2 is written for + kernel patches and this is an environment text file, so whether such files need a + sign-off line is the owner's call, not decided here. +* **"the ELF symbol is 1,151 bytes"** (`docs/uboot-tasks.md`, plan §6) does not hold on the + `CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE` path: `nm -S` gives `0x47e` = 1,150, because + `xxd -i` emits a byte-list initialiser with no implicit string terminator. 1,151 is what + the `CONFIG_EXTRA_ENV_SETTINGS` string-literal path (stock, and the 2026.04-era scratch + builds) yields. The 1,150-byte comparison in §4/§4a is the right one; the two docs are + outside this task's scope → U8. +* **`docs/init-parity.md:134`** cites `patches/uboot/.gitkeep` as an example of the marker + convention. The directory now exists and holds real patches, so the example should cite + `linux-patches/.gitkeep` instead. For task U8. +* **`scripts/ci-tests.sh` has no DE10 U-Boot section yet** — it asserts nothing about + `images/u-boot-with-spl.sfp` existing, about `images/uboot.img` **not** existing, or about + the release stage carrying no `.sfp`. That is task U5's deliverable; recorded here so the + gap is visible in the meantime. + +--- + +## 10. Reproducing this + +From the repository root, as in §1 — the repo `Makefile`'s catch-all rule +(`%: $(BR_STAMP) hostshim`) forwards every Buildroot goal with `work/.hostshim` on `PATH`, +which is what keeps Buildroot 2026.x's `install` calls away from the uutils `install` +(`make -C output …` directly skips that shim): + +``` +make mister_de10nano_defconfig +make BR2_JLEVEL=12 uboot-dirclean uboot + +# plan §6, both halves +SPL_SIZE_LIMIT=$(output/build/uboot-2026.07/tools/spl_size_limit) \ + scripts/check-uboot-parity.sh \ + output/images/u-boot-with-spl.sfp /uboot.img output/build/uboot-2026.07/u-boot +scripts/check-uboot-handoff.sh \ + output/build/uboot-2026.07/board/terasic/de10-nano/qts \ + output/images/u-boot-with-spl.sfp /uboot.img + +# the gates' own fixtures, including §5a's negative run +STOCK_UBOOT_IMG=/uboot.img scripts/test-uboot-handoff.sh +scripts/test-uboot-parity.sh /uboot.img +``` + +`/uboot.img` is the hash-pinned blob `scripts/fetch-sdcard-payload.sh` fetches +(`STOCK_UBOOT_SHA256`), sha256 `e2d46cf9…62a64`. + +If a Buildroot bump moves `BR2_TARGET_UBOOT_LATEST_VERSION` off 2026.07, expect the patches +to need refreshing and **expect this file's hashes to change**; the two check scripts, not +the hashes, are what has to stay green. + +--- + +## 11. U9 — adversarial review of the integrated artifact (2026-09-14) + +Task U9 (`docs/uboot-tasks.md`): read and test the integrated build against plan §8's +failure taxonomy — *can the built `.sfp` present as a boot on a board while being wrong?* — +and leave no finding unlabelled. Inputs: the carried patches, the fragment and environment, +`output/images/u-boot-with-spl.sfp` and `output/build/uboot-2026.07/` (ELF, `.config`, +`spl/`), the stock blob (`e2d46cf9…62a64`), the fork at `work/U-Boot_MiSTer` (`8dcc3484`, +read only), the mainline mirror at `/mnt/source/uboot-mainline/mainline-board-support/u-boot.git`, +and task U2c's sandbox binary (`/mnt/source/uboot-wave-a/U2c/u-boot-2026.07/u-boot`, v2026.07 ++ patch 0004, `CONFIG_HUSH_OLD_PARSER=y` — the same parser the DE10 `.config` resolves). +Scratch: `/mnt/source/uboot-wave-a/U9/`. Every claim is **[V]** (observed, where stated) or +**[U]** (unverified, missing input named); nothing here was observed on a board. + +**Two findings changed the artifact or the record; everything else held.** One fix was +applied (patch 0006, §11.2) and the tree rebuilt, so **§1's hashes and §2's five-patch table +predate this section** — the current values are in §11.2. Summary: + +| # | Attack | Result | +|---|---|---| +| 11.1 | (a) the bridge argument, cold and warm | holds; one caller the census missed, inert; one ordering delta in `bridge enable`, inert **[V by source]** | +| 11.2 | (b) effective env ≠ compiled env | **FOUND and FIXED**: no `CFG_SYS_BOOTMAPSZ`, DTB relocated above `mem=511M` → patch 0006 | +| 11.3 | (b) hush / `env import` / storage | hold, **by execution** in the 2026.07 sandbox | +| 11.4 | (b) watchdog | **FOUND, [U]**: stock hands Linux a running 30 s watchdog, this build hands it a stopped one — not trivial, recorded for the owner | +| 11.5 | (c) SPL and the mailbox | nothing CPU-side touches `0x1FFFF000–0x1FFFFFFF` **[V by source + link map]**; DDR retention across the reset stays hardware-only, as on stock | +| 11.6 | (d) the `ih_ep`/`ih_load` hazard | documented in the plan, this file and the parity script; **absent from `docs/boot-chain.md`, `README.md`, `mk-sdcard.sh`, `check-sdcard.sh`** → [U] for U8/owner | +| 11.7 | (e) the QTS handoff | gate rerun, exit 0; **the scalars are now proven by binary** — the two structs that carry them (`sdram_config`, `cm_default_cfg`) and `io_config` are byte-identical in stock's SPL; `misc_config` and `rw_mgr_config` hold the same values in a different layout (corrected 2026-09-14, see 11.7) | +| 11.8 | (f) `mt` exit-status sense | identical instruction sequence in stock's binary and ours | +| 11.9 | (g) SPL size | 46,386 B against 62,752 B, check wiring confirmed; the BootROM's own limit is [U] | + +### 11.1 The bridge argument (patch 0005), cold boot and warm reboot + +**Disassembly of the built `arch_early_init_r` [V]** (`objdump -d --disassemble=arch_early_init_r +output/build/uboot-2026.07/u-boot`, unchanged by patch 0006): + +``` +01002760 : + 1002760: b538 push {r3, r4, r5, lr} + 1002766: f7ff fda9 bl 10022bc + 100276a: 2400 movs r4, #0 + 100276c: 4b0d ldr r3, [pc, #52] @ 0xae9efebc + 100276e: 4d0e ldr r5, [pc, #56] @ 0x01073060 = iswgrp_handoff[] + 1002770: f8c0 30e0 str.w r3, [r0, #224] @ warmramgrp_enable + 1002774: f7ff fda2 bl 10022bc + 1002778: 3080 adds r0, #128 @ +0x80 = iswgrp_handoff regs + 100277a: 5903 ldr r3, [r0, r4] + 1002780: 3404 adds r4, #4 + 1002782: 2c20 cmp r4, #32 @ 8 words cached + 1002784: f845 3b04 str.w r3, [r5], #4 + 1002788: d1f4 bne.n 1002774 + 100278a: 2001 movs r0, #1 + 100278c: f000 f8d8 bl 1002940 @ (1) + 1002790: f7ff ffae bl 10026f0 + 1002796: f7ff fd4f bl 1002238 + 100279a: 2000 movs r0, #0 + 100279c: f000 f8d0 bl 1002940 @ (0) -- patch 0005 + 10027a0: 2000 movs r0, #0 + 10027a2: bd38 pop {r3, r4, r5, pc} +``` + +The order is the fork's (`misc.c:356-404`): cache the eight handoff words, `reset(1)`, +remap, register the FPGA, `reset(0)`, return. + +**Register end states, both trees, walked from source [V].** Read from the fork's +`spl.c`/`misc.c`/`reset_manager.c` and mainline's `spl_gen5.c`/`misc_gen5.c`/ +`reset_manager_gen5.c` in the build tree. `brg` = rstmgr `brgmodrst` (+0x1c), `remap` = L3 +`0xff800000`, `h[n]` = sysmgr `iswgrp_handoff[n]` (+0x80+4n). + +| Point | Stock (fork) | This build (mainline + 0005) | +|---|---|---| +| SPL, early | `reset(1)`: `brg=0xffffffff` | `remap_zero`; `reset(1)`: `brg=7, remap=1` | +| SPL, after pinmux | **`reset(0)`** (`spl.c` "De-assert reset for peripherals and bridges based on handoff"): `h[0]=0, h[1]=0x19`; then the ready test — cold: return; warm: `brg=0, remap=0x19` | `socfpga_bridges_set_handoff_regs(true,true,true)`: `h[0]=7, h[1]=1` | +| SPL, SDRAM init | `h[3]=fpgaport_rst=0x3FFF`, `h[4]=rows` (`sdram.c:440-450`) | same (`sdram_gen5.c:461-472`) | +| SPL, end | `reset(1)`: `brg=0xffffffff` (remap untouched) | — | +| proper, `arch_early_init_r` | cache `h[0..7]` = `0, 0x19, …`; `reset(1)`; `remap=1`; `reset(0)`: cold → return, warm → `brg=0, remap=0x19` | cache `h[0..7]` = `7, 1, …`; `reset(1)`; `remap=1`; `reset(0)`: rewrites `h[0]=0, h[1]=0x19`, then cold → return, warm → `brg=0, remap=0x19` | +| `bridge enable` (cold path, `fpgaload`) | `fpgaintf_module=h[2]`; `apply_static_cfg`; `fpgaport_rst=h[3]`; `brg=cache[0]=0`; `remap=cache[1]=0x19` | `do_bridge_reset(1, ~0)`: `set_handoff_regs(false,false,false)` → `h[0]=0, h[1]=0x19`, **re-reads both into the cache**; `fpgaintf_module=h[2]`; `if (h[3]) { fpgaport_rst=h[3]; apply_static_cfg }`; `brg=0`; `remap=0x19` | + +Both paths end with `brg=0`, `remap=0x19`, `fpgaport_rst=0x3FFF`, `fpgaintf_module` from the +pinmux — at cold boot after `fpgaload`, and at a warm reboot in the middle `fpgacheck` branch +where `fpgaload` never runs. Three things the U2d argument did not say, all checked here: + +* **The fork's `socfpga_bridges_reset(0)` has a second caller, in the SPL** (`spl.c`, the + "De-assert reset for peripherals and bridges" block, before `preloader_console_init`). The + patch header's census ("no caller at all in a pristine v2026.07") is about mainline and is + correct; the fork calls it twice. Consequences: on stock the SPL leaves `h[0]=0, h[1]=0x19`, + on this build the SPL leaves `h[0]=7, h[1]=1`, so U-Boot proper's **cached** copies differ + between the trees. Inert: the only consumer of the cached `[0]`/`[1]` on stock's env path is + `do_bridge_reset(1, …)`, which reloads them (`misc_gen5.c:265-269`) after rewriting the + registers; patch 0005's own `reset(0)` rewrites the registers too. The fork's SPL-time + "FPGA not ready" `printf` at cold boot runs before `preloader_console_init` and is dropped; + U-Boot proper's message is printed at the same point in both trees. +* **`bridge enable` orders `apply_static_cfg` differently**: the fork applies, then writes + `fpgaport_rst`; mainline writes, then applies (and only `if (h[3])`). Inert here because + `fpgaport_rst` already holds `0x3FFF` from the SPL's `sdram_write_verify` in both trees and + `h[3] = 0x3FFF ≠ 0`; `bridge enable` rewrites the value the register already has, so the + apply order cannot change the outcome. This closes the "`bridge enable` semantics changed" + half of plan §3.3 that the 2026-09-14 revision left open — **for stock's environment** (mask + `~0`); a `bridge enable ` with a partial mask is mainline-only behaviour nothing here + calls. +* **`h[2]` (`fpgaintf_module`) is populated identically**: `populate_sysmgr_fpgaintf_module()` + is the same function line for line modulo accessor style (fork `system_manager.c:22-53`, + mainline `system_manager_gen5.c:16-57`). +* **Linux reads exactly one handoff word**: `drivers/fpga/altera-fpga2sdram.c:116` + (`SYSMGR_ISWGRP_HANDOFF3`, `0x8C`) in `output/build/linux-6.18.51`. That is `h[3]`, which + both SPLs write from `cfg->fpgaport_rst` and which patch 0005 never touches. Nothing in + `arch/arm/mach-socfpga` or the bridge drivers reads `h[0]`/`h[1]`. + +**Still [U] (hardware):** that a Main_MiSTer cold reset (`rstmgr.ctrl = 1`, boot-chain §6.3) +reaches `arch_early_init_r` with `fpgamgr_test_fpga_ready()` true; that a core stays +reachable across it; that cold boot loads `menu.rbf`. Unchanged from §8 item 4. + +### 11.2 Finding, FIXED — the DTB was relocated above `mem=511M` (patch 0006) + +**The attack.** The environment `cmp` proves 1,150 bytes; it cannot see a `CONFIG_`/`CFG_` +symbol that changes what those bytes *do*. Walking stock's board headers +(`work/U-Boot_MiSTer/include/configs/socfpga_de10_nano.h`, `socfpga_common.h`) define by define +against the resolved `.config` and mainline's two headers found one boot-path define with no +mainline counterpart: **`CONFIG_SYS_BOOTMAPSZ (64 * 1024 * 1024)`** (`socfpga_common.h:18` +in the fork; `:22` at upstream `v2017.03`). + +**What it does.** `bootz` relocates the flattened device tree before the jump +(`arch/arm/lib/bootm.c:158-164 boot_prep_linux` → `boot/image-board.c:929 image_setup_linux` +→ `boot/image-fdt.c boot_relocate_fdt`). With no `fdt_high` in the environment (stock has +none) the target is the highest free 4 KiB-aligned block below +`env_get_bootm_mapsize() + env_get_bootm_low()` (`image-fdt.c:221-247`, `LMB_MEM_ALLOC_MAX`). +`env_get_bootm_mapsize()` (`image-board.c:151-163`) returns `bootm_mapsize` if set, else +`CFG_SYS_BOOTMAPSZ` if defined, else `env_get_bootm_size()` = `bootm_size` if set, else +`gd->ram_size` capped at `gd->ram_top` — **1 GiB on this board** (no `board_get_usable_ram_top` +in `mach-socfpga` or `board/terasic/de10-nano`). The LMB's only reservation up there is +U-Boot's own (`lib/lmb.c:535-543`, `[start_addr_sp - CONFIG_STACK_SIZE, ram_top)`), so the DTB +would land immediately below U-Boot's stack, at roughly `0x3Bxxxxxx`: above the `mem=511M` +line, inside the half of DDR MiSTer cores own (boot-chain §6.4), and ~60 MiB from where every +stock boot has placed it (just under 64 MiB, `0x03FFxxxx`, via the fork's identical +`boot_relocate_fdt` + `getenv_bootm_mapsize()` at `common/image.c:512-526`). + +**Why mainline lost it, and why the env cannot carry the replacement [V]**: mirror commit +`faea9e7a78` (Simon Goldschmidt, 2019-01-09, "arm: socfpga: remove CONFIG_SYS_BOOTMAPSZ") +deleted the define *and relied on* a new `bootm_size=0xa000000` entry in `socfpga_common.h`'s +default environment (`include/configs/socfpga_common.h:150` in v2026.07). This build replaces +that default environment wholesale with stock's 21 entries — byte-identical by owner decision — +so the entry is gone and the bound silently falls to the whole DRAM. **This is a behaviour the +environment-parity check is structurally unable to see.** + +**Would it boot anyway?** Probably: the 6.18 kernel maps the DTB through the fixed FDT fixmap +(`arch/arm/kernel/head.S:304`, `arch/arm/mm/mmu.c:1385-1388`) and reserves it by physical +address (`arch/arm/kernel/setup.c:1108`), so a blob outside `mem=` is still parsed. But the +blob would then sit in fabric-owned DDR for the life of the system, and "mirror stock wherever +there is a choice" decides it. **[U]:** kernel behaviour with the DTB at either address was +not observed (no board; `test-initramfs.sh` boots the kernel without U-Boot). + +**The fix.** `board/mister/de10nano/patches/uboot/0006-configs-socfpga-de10-nano-bound-the-linux-boot-map-at-64-mib.patch` +— ten lines added to `include/configs/socfpga_de10_nano.h`, one of them +`#define CFG_SYS_BOOTMAPSZ (64 * 1024 * 1024)`. The board header is the layer upstream itself +uses for the symbol (some 65 board headers under `include/configs/` in v2026.07 still define +it — 67 files counting `km/`); the value is stock's; +no environment byte changes. Full provenance header in the patch (origin `5095ee088d`, 2014; +removal `faea9e7a78`, 2019; disposition: carry, mirror stock; never upstream). + +Applied and rebuilt here **[V]**: + +``` +$ patch -p1 -F0 -g0 --no-backup-if-mismatch -t -N --dry-run < 0006-*.patch # pristine tarball header +checking file include/configs/socfpga_de10_nano.h +$ patch -p1 -F0 -g0 --no-backup-if-mismatch -t -N < 0006-*.patch # output/build/uboot-2026.07 +patching file include/configs/socfpga_de10_nano.h +$ scripts/lint-kernel-patches.sh board/mister/de10nano/patches/uboot +ok 0006-configs-socfpga-de10-nano-bound-the-linux-boot-map-at-64-mib.patch Michael C. Ferguson +RESULT: PASS — all 6 patches in 1 series are `git am`-able. +$ timeout 590 make BR2_JLEVEL=12 uboot-rebuild # exit 0 +``` + +The flags are the ones Buildroot's `support/scripts/apply-patches.sh` uses, and the dry run +is against the header extracted from the pinned tarball, so the patch applies at fuzz zero on +the path Buildroot will take. **[U]:** a `make uboot-dirclean uboot` through Buildroot's own +extract/patch step was *not* run here (the tree was shared with U3/U5 at the time); the next +clean build is the observation. + +`env_get_bootm_mapsize()` in the built ELF, before and after **[V]**: + +``` +before (u-boot sha256 8004117d…, .sfp 287201ee…): + 100494c: b508 push {r3, lr} + 100494e: 4806 ldr r0, [pc, #24] @ "bootm_mapsize" + 1004950: f02c f848 bl 10309e4 + 1004954: b918 cbnz r0, 100495e + 1004956: e8bd 4008 ldmia.w sp!, {r3, lr} + 100495a: f7ff bfd1 b.w 1004900 <- whole-DRAM fallback +after: + 1004900: b508 push {r3, lr} + 1004902: 4806 ldr r0, [pc, #24] @ "bootm_mapsize" + 1004904: f02c f848 bl 1030998 + 1004908: b120 cbz r0, 1004914 + … + 1004914: f04f 6080 mov.w r0, #67108864 @ 0x4000000 <- 64 MiB +``` + +A normalised whole-binary disassembly diff (addresses, PC-relative literals and raw opcode +bytes stripped; `/mnt/source/uboot-wave-a/U9/dis.diff`, 73 lines) shows exactly: that +function's body, the now-unreferenced `env_get_bootm_size()` dropped by the linker, three +size/offset literals that shifted by its 88 bytes, and one 8-byte alignment change. Nothing +else in U-Boot proper moved. `spl/u-boot-spl.bin` is **byte-identical** to the pre-patch +build (`cmp` silent; sha256 `ae00baa6…`). + +Artifacts after patch 0006 **[V]** — these supersede §1's table: + +| File | Size | sha256 | +|---|---|---| +| `output/images/u-boot-with-spl.sfp` | 815,896 B | `4bf59acf9fea2114b6c5186ef82e8386d7a20e62c3e0fd904b5fb573a9a15f97` | +| `output/images/u-boot.bin` | 553,688 B | `ce860693fa254b6e782d67d7ec803e674d8bc0342b5a6d458c14146f960ddc4b` | +| `output/build/uboot-2026.07/spl/u-boot-spl.bin` | 46,386 B | `ae00baa6fba3822e8035a7e510a6e38ba24babc9b7a6f8fa16b164136d2cbcd4` (unchanged) | + +Both gates rerun against the rebuilt artifact, exit **0** and **0** **[V]** +(`/mnt/source/uboot-wave-a/U9/parity-after.txt`, `handoff-after.txt`): + +``` + ih_size = 553688 ih_load = 0x01000040 ih_ep = 0x01000040 +ok total size closes the file: 0x00040000 + 64 + 553688 = 815896 +ok uImage header CRC recomputes: 0x4e7469eb +ok uImage payload CRC recomputes over all 553688 bytes: 0x7ed6e687 +ok SPL payload 46400 <= SPL_SIZE_LIMIT 62752 (headroom 16352 bytes) +ok environment is byte-identical to stock: 1150 bytes, 21 entries (`mt` carried, plan §3.4 — no allowed delta) +ok all 69 stock command names are present (112 total in the build) +check-uboot-parity.sh: parity holds + 14 found, 0 missing (7 tables x 2 images = 14 checks expected) +check-uboot-handoff.sh: all seven handoff tables present in SPL copy 0 of both images +``` + +The rebuild log also shows task U3's `UBOOT_POST_BUILD_HOOKS` audit (landed in `external.mk` +while this review ran) passing on the patched tree — informational, not relied on: +`MiSTer DE10 U-Boot resolved-.config audit (docs/uboot-mainline-port.md): PASS`. + +### 11.3 The effective environment — everything else held, by execution + +* **The compiled blob is the file and nothing else [V].** `include/env_default.h` wraps its + entire compiled-in list — `bootargs`/`bootcmd`/…, `CONFIG_ENV_VARS_UBOOT_CONFIG`'s + `arch=`/`cpu=`/`board=`/`vendor=`/`soc=`, `CFG_EXTRA_ENV_SETTINGS` (mainline's `bootm_size` + lives there) — in `#ifndef CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE`; the `#else` is only + `#include "generated/defaultenv_autogenerated.h"`. `CONFIG_ENV_VARS_UBOOT_CONFIG=y` in the + resolved `.config` therefore contributes nothing, which `nm -S` = 1,150 confirms. The one + runtime-only extra is `fdtcontroladdr` (`common/board_r.c:458`); no stock variable has that + name. +* **Storage mirrors stock exactly [V]** (`.config`): `ENV_IS_IN_MMC=y`, `ENV_OFFSET=0x200`, + `ENV_SIZE=0x1000`, `ENV_MMC_DEVICE_INDEX=0`, `ENV_MMC_EMMC_HW_PARTITION=0`, + `# ENV_MMC_USE_SW_PARTITION / ENV_MMC_USE_DT / ENV_REDUNDANT / ENV_OFFSET_RELATIVE_END / + ENV_IS_NOWHERE … is not set`. No device-tree, partition-name or redundant-copy lookup can + move it: the CRC is at card bytes 512–515 and `updateboot`'s `dd … seek=1 count=1` still + destroys it (boot-chain §5 Consequence (b)). +* **hush: the same parser, the same `$` rule [V by source and execution].** Both trees run + the old hush (`CONFIG_HUSH_OLD_PARSER=y`; `# CONFIG_HUSH_MODERN_PARSER is not set`), and in + both the `isdigit` arm of `handle_dollar` is `#ifndef __U_BOOT__` (mainline + `common/cli_hush.c:2848-2849`, fork `:2858-2859`), so `$5` is a literal `$` — which is what + keeps `memmap=513M$511M` intact. Executed in the sandbox: + + ``` + => setenv mmcboot "setenv bootargs console=ttyS0,115200 \$v loop.max_part=8 mem=511M memmap=513M\$511M root=\$mmcroot loop=linux/linux.img ro rootwait;echo rc=would-bootz"; run mmcboot; printenv bootargs + rc=would-bootz + bootargs=console=ttyS0,115200 $v loop.max_part=8 mem=511M memmap=513M$511M root=$mmcroot loop=linux/linux.img ro rootwait + ``` + + (the sandbox has no `v`/`mmcroot` set in that run, hence the literal `$v`; on the board + they expand as on stock.) +* **The 1,150 bytes import cleanly, malformed entry included [V by execution].** The exact + stock blob (`work/uboot-proper.bin` @ `0x28018`) was written into sandbox memory word by + word and imported in binary (NUL-separated) form, which is how `env_set_default()` consumes + `default_environment[]`: + + ``` + => <288 x mw.l>; env import -d -b 0x200000 0x47e; echo rc=$?; printenv + rc=0 + baudrate=115200 + bootargs=console=ttyS0,115200 $v loop.max_part=8 mem=511M memmap=513M$511M + bootcmd=mw 0xff709004 0x800; run mmcload; run mmcboot + … (19 variables, the two loadaddr= entries collapsed to one) … + => printenv 'bootm $loadaddr - $fdt_addr' + ## Error: "bootm $loadaddr - $fdt_addr" not defined + ``` + + Entry 15 is silently dropped by `himport_r`'s no-`=` branch, exactly as boot-chain §3.1 + traces for 2017.03. +* **`fpgacheck`'s three-way dispatch, by execution [V]** (mailbox mocked at `0x100000`/ + `0x100f08`, `fpgaload` stubbed to an echo; transcript `/mnt/source/uboot-wave-a/U9/sandbox.txt`): + + ``` + [warm flag + staged env] ## Info: input data size = 23 = 0x17 / DISPATCH-fpgaload / core=menu.rbf foo=bar / both magics cleared (mt … 0 → rc=0) + [warm flag, no staged env] core=zzz (untouched), no fpgaload, flag cleared + [no warm flag] DISPATCH-fpgaload, core=zzz, staged magic left in place + ``` + + `env import -t ` with no size scans for `'\n'` then `'\0'` bounded by + `MAX_ENV_SIZE` in both trees (mainline `cmd/nvedit.c:836-846`, fork `:1049-1066`, + character-identical loop), so Main_MiSTer's `memset(…, 0, 0xF00)` terminator (boot-chain + §6.3) works the same. +* **Autoboot [V by source]:** `bootcmd` is read from the environment (`common/autoboot.c:484`); + `CONFIG_BOOTSTD=y` without `BOOTSTD_BOOTCOMMAND` never substitutes its own. Keyed stop: + the empty `AUTOBOOT_DELAY_STR` cannot match (`:297`, `len > 0` guard). Cosmetic delta: the + Kconfig default `AUTOBOOT_PROMPT="Autoboot in %d seconds\n"` is printed once per boot. +* **`mt` argument parsing [V]:** `cmd_get_data_size("mt", 4)` returns 4 with no suffix + (`common/command.c:469-491`), `hextoul` accepts `0x…`; the command-table entry is + `maxargs=3, repeatable=1` in both binaries. +* **Malloc arena:** `SYS_MALLOC_LEN=0x4000000` and `SYS_MALLOC_CLEAR_ON_INIT=y` in both + (fork `Kconfig:108-110` defaults it on), so both zero the same 64 MiB at the top of DDR + every boot — identical footprint, no new trample of fabric memory. + +### 11.4 Finding, [U] — the watchdog stock hands to Linux + +Stock defines `CONFIG_HW_WATCHDOG` (`socfpga_de10_nano.h:15` in the fork), which makes +`arch_cpu_init()` call `hw_watchdog_init()` (`misc.c:317-326`) — the Designware L4WD0 with +`CONFIG_HW_WATCHDOG_TIMEOUT_MS 30000` — and pet it through `WATCHDOG_RESET()`. Linux then +inherits a *running* 30 s watchdog; the MiSTer kernel copes (`CONFIG_DW_WATCHDOG=y`, +`CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y`, `CONFIG_WATCHDOG_OPEN_TIMEOUT=0` in +`output/build/linux-6.18.51/.config`), and a hang before the driver binds reboots the board. +This build resolves `# CONFIG_WDT is not set` / `# CONFIG_WATCHDOG is not set` +(`.config:1846-1847`), `CONFIG_HW_WATCHDOG` is absent (in 2026.07 it is a hidden `bool`, +`drivers/watchdog/Kconfig:43-44`, selected only by legacy i.MX-class drivers — no fragment +line can turn it on for socfpga), and the DE10 device tree has +`&watchdog0 { status = "disabled"; }` (`arch/arm/dts/socfpga_cyclone5_de10_nano.dts:84`). +The fork's `#else` arm — "make sure it is not running … enabled in the preloader" — **is +carried by mainline verbatim**: the shared `arch/arm/mach-socfpga/misc.c:174-190` has the +identical `#ifdef CONFIG_HW_WATCHDOG hw_watchdog_init(); #else socfpga_per_reset(SOCFPGA_RESET(L4WD0), 1); +socfpga_per_reset(SOCFPGA_RESET(L4WD0), 0); #endif` block in `arch_cpu_init()` (the fork's is +`misc.c:319-336`), `misc.o` is `obj-y` for every socfpga (`arch/arm/mach-socfpga/Makefile:11`), +and the built ELF executes the `#else` arm — `objdump -d --disassemble=arch_cpu_init u-boot` +is exactly `bl socfpga_get_managers_addr`, then `bl socfpga_per_reset` with `r0=#0x106`, +`r1=#1`, then again with `r1=#0`, then `movs r0,#0` (`0x106` = `RSTMGR_DEFINE(1,6)` = +`RSTMGR_L4WD0`, `include/mach/reset_manager_gen5.h:36`) **[V]**. The SPL side is inert: +`CONFIG_SPL_WATCHDOG=y` resolves but `nm spl/u-boot-spl` has no `wdt`/`watchdog` symbol at +all (no driver behind it) **[V]**. So U-Boot proper *explicitly holds L4WD0 in reset and +releases it* — the watchdog Linux inherits is not merely unconfigured but deliberately +stopped — and **the effective boot differs: stock's kernel starts under a live watchdog, +ours does not.** *(Erratum: the first revision of this paragraph claimed the `#else` arm +"has no mainline gen5 counterpart" on the strength of a `grep -i watchdog +arch/arm/mach-socfpga/*.c` said to hit Arria10 only; the grep in fact hits 15 files including +`misc.c`, `misc_gen5.c`, `spl_gen5.c` and `reset_manager_gen5.c`, and the sentence was wrong. +The conclusion stands, more strongly.)* +Not a brick in either direction — a stopped watchdog cannot fire — but a "mirror stock" gap +and a lost safety net. **Not fixed here:** it needs `CONFIG_WDT`/`CONFIG_WDT_DESIGNWARE`/ +`CONFIG_WATCHDOG`/`CONFIG_WATCHDOG_AUTOSTART`/`CONFIG_WATCHDOG_TIMEOUT_MSECS=30000` in the +fragment *and* a DT change (`status = "okay"`), and a wrong timeout is precisely a +"looks like a boot, then resets 30 s later" failure. Ordering note for whoever applies that +recipe: `CONFIG_HW_WATCHDOG` stays off, so the `#else` toggle above still runs — at +`arch_cpu_init` (`common/board_f.c:903`, pre-relocation) — and the `CONFIG_WDT` autostart +runs later, at `initr_watchdog` (`common/board_r.c:663`, post-relocation, gated on +`CONFIG_IS_ENABLED(WDT)`): reset-then-start, which is the order one wants **[V]** (source +order; the recipe itself is unbuilt). **[U]: owner's call**; missing inputs are the decision +and a board to observe the driver takeover. + +### 11.5 The SPL and the mailbox (`0x1FFFF000–0x1FFFFFFF`) + +Every CPU-side store the SPL makes to DDR, from the link map and `.config` **[V]**: + +| What | Where | Source | +|---|---|---| +| SPL image + DTB, `.data`, `__u_boot_list`, `.bss` (16 B) | OCRAM `0xFFFF0000`–`0xFFFFACF0` | `readelf -S spl/u-boot-spl` | +| initial stack, `gd`, early malloc (`0x800`) | top of OCRAM: `_main` does `mov r0,#0; bic r0,#7; mov sp,r0` (`CONFIG_SPL_STACK=0x0` = wrap to the 4 GiB top = end of OCRAM), `board_init_f_alloc_reserve` puts `gd` at `0xFFFFF720` and malloc at `0xFFFFF800` | `objdump --disassemble=_main` | +| relocated stack + simple malloc after DDR is up | malloc `0x00700000`–`0x00800000`, `gd` and stack below `0x00700000` | `CONFIG_SPL_STACK_R_ADDR=0x00800000`, `SPL_STACK_R_MALLOC_SIMPLE_LEN=0x100000`, `common/spl/spl.c:924-941` | +| the loaded U-Boot proper | `0x01000000`–`0x010872xx` (`ih_load - 64`, 553,688 B) | `spl_legacy.c:57-62` | +| `get_ram_size(0, 1 GiB)` probes | `base + 2^k` words: `0x20000000, 0x10000000, … , 0x4, 0x0` — saved and restored | `sdram_gen5.c:608`, `common/memsize.c:60-97` | +| ECC scrub | none: `CFG_HPS_SDR_CTRLCFG_CTRLCFG_ECCEN 0` (patch 0003, `qts/sdram_config.h:17`) | no ECC init/scrub path exists in `sdram_gen5.c` — `grep -in ecc drivers/ddr/altera/sdram_gen5.c` hits only lines 550-553, which are the size computation's `_WITH_ECC` width adjust, not a scrub | + +No power of two lies in `0x1FFFF000–0x1FFFFFFF` and every probe is restored; nothing else +lands within 496 MiB of the mailbox. U-Boot proper's pre-relocation stack is +`CUSTOM_SYS_INIT_SP_ADDR=0x800000` (the fork's was in OCRAM); its `dram_init()` reads the DT +(`misc.c:66 fdtdec_setup_mem_size_base`) where the fork probed with `get_ram_size` (same +power-of-two set, restored). Relocation and the 64 MiB malloc arena sit at the top of the +1 GiB in both trees. **The mailbox is read by `fpgacheck` before any load touches DDR below +it** (`mmcload` runs `fpgacheck` first). What stays **[U]**, exactly as it is for stock: that +DDR *contents* survive the `swcoldrstreq` reset window and the PHY-driven calibration — +the calibration's DRAM traffic is generated by the RW manager from `ac_rom_init`/ +`inst_rom_init`, both proven byte-identical (§5), but the sequencer C that drives them has +nine years of change (plan §3.2a). Hardware only, and the same claim stock rests on. + +### 11.6 The `ih_ep`/`ih_load` mixing hazard — where it is and is not written + +Verified the mechanism it warns about **[V]**: mainline's SPL takes `entry_point = +image_get_ep(header)` (`common/spl/spl_legacy.c:57`), so our SPL over stock's payload (`ih_ep += 0`) jumps to `0x0`; stock's SPL over ours reads `ih_load` and survives (parity §4 block 1). +Where a future flasher would read it (`git grep -n -i "ih_ep\|mixing"`): + +| Place | Present? | +|---|---| +| `docs/uboot-mainline-port.md` §3.3, §6, §8 taxonomy | yes | +| this file, §7 | yes | +| `scripts/check-uboot-parity.sh` (prints the warning on every run; `test-uboot-parity.sh` fixture 6) | yes | +| `docs/boot-chain.md` §2 (the blob's layout spec) and §5 (`updateboot`) | **no** | +| `README.md`, `PLAN.md`, `TASKS.md` | **no** | +| `scripts/mk-sdcard.sh`, `scripts/check-sdcard.sh` headers (the tools that write and check the boot partition) | **no** | +| a written recovery procedure (plan §8 gate item 4) | does not exist yet | + +**[U] → task U8 / owner:** one sentence in boot-chain §2 ("the `.sfp` is atomic; SPL and +uImage must come from the same build — mainline's SPL jumps to `ih_ep`, the fork's to +`ih_load`") and a line in `mk-sdcard.sh`'s header are the two places a person with a card +reader would actually look. Both files are outside this task's scope (and `boot-chain.md` was +being edited by a concurrent task at the time of this review — re-check after merge). + +### 11.7 The QTS handoff — gate rerun, and the scalars proven by binary + +`scripts/check-uboot-handoff.sh` rerun before and after patch 0006: exit **0** both times, +same fourteen offsets as §5 **[V]**. + +Plan §3.2a and §5 above say the scalar defines "compile to instruction immediates and are +not greppable". **That is wrong, and usefully so.** In both trees the handoff scalars are +*data*: `arch/arm/mach-socfpga/wrap_sdram_config.c` and `wrap_pll_config.c` build `static const` +structs from the `qts/*.h` defines, and the SPL reads them at run time (`socfpga_get_sdram_config()` +etc.). Reading each struct's bytes out of the built SPL ELF's `.rodata` (`objcopy +--only-section=.rodata`, symbol addresses from `nm -S`) and searching SPL copy 0 of both images +**[V]** (`/mnt/source/uboot-wave-a/U9/`, python; all offsets are into SPL copy 0): + +| Struct (`nm -S` size) | Built SPL | Stock SPL | Scalar inside it / result | +|---|---|---|---| +| `sdram_config` (148 B) | `0x9c68` | **`0xa384`** | byte-identical; word 22 `fpgaport_rst` = **`0x3fff`** (stock word at `0xa3dc`) | +| `cm_default_cfg` (108 B) | `0x8e28` | **`0x9240`** | byte-identical; word 17 `s2fuser1clk` = **`0x1ff`** (511), word 25 `s2fuser2clk` = **`0x4`** | +| `io_config` (16 B) | `0x9d34` | **`0xabc0`** | byte-identical | +| `misc_config` (20 B) | `0x9b84` | *not byte-searchable* | same twelve `u8` values; mainline's `struct socfpga_sdram_misc_config` inserts `u16 afi_clk_freq` (= 0) after the signature (`sdram_gen5.h`), the fork's has no such field — built `a0045555 0000 0108 0600 …`, stock `a0045555 0108 0600 …` at `0xabd0` | +| `rw_mgr_config` (59 B) | `0x9d74` | *not byte-searchable* | same 59 values; the first 34 bytes match at stock `0xa2d0`, bytes 40–58 match, bytes 34–39 are the same six values (`mrs1..3`, `mrs1..3_mirr`) in the two headers' different field order (mainline `mrs1, mrs2, mrs3, mrs1_mirr, mrs2_mirr, mrs3_mirr`; fork `mrs1, mrs1_mirr, mrs2, mrs2_mirr, mrs3, mrs3_mirr`) — field-by-field equal | +| `REG_FILE_INIT_SEQ_SIGNATURE` as LE u32 | `0x555504a0` @ `0x9b84`, once (= `misc_config`'s first word) | `0x555504a0` @ `0xabd0`, once | mainline's `0x555504a1` absent from **both** | + +**Erratum (2026-09-14, after independent verification).** The first revision of this table +placed `misc_config`, `io_config` and `rw_mgr_config` at built offsets `0x9630`/`0x96b8`/`0x9a70` +and claimed matches in stock at `0x9b44`/`0x9bcc`/`0x9f84`. Those built offsets all fall inside +`iocsr_scan_chain3_table` (`0xffff9354`, 0x830 bytes, per `nm -S`), so the "matches" were +scan-chain fragments the handoff gate already checks, and the sentence "every struct is +byte-identical" was false for two of the five. The rows above are the corrected result +(`/mnt/source/uboot-wave-a/U9/fix2/structs.txt`, `rwmgr.txt`). + +The three structs that are byte-identical between the two SPLs (`struct socfpga_sdram_config`, +`struct cm_config` and `struct socfpga_sdram_io_config` have the same layout, field for field, +in `mach/sdram.h` / `mach/clock_manager.h` (fork) and `mach/sdram_gen5.h` / +`clock_manager_gen5.h` (mainline)) include the two that carry the scalars, so +**`FPGAPORTRST=0x3FFF` and both s2f clock counts are pinned to the stock binary the same way +the seven tables are.** `misc_config` and `rw_mgr_config` carry the same values in a +layout mainline changed (an inserted `u16`; a reordered `mrs*` block), so they are equal +field for field **[V by binary + source]** but cannot be asserted by a byte search. This +upgrades §5's "[V by source, U by binary]" and §8 item 3's premise ("not greppable") to +**[V by binary]** for the scalars; what remains [U] there is only whether the *values* are +required, which the owner has already ruled out of scope (plan §9 item 3). Suggested +follow-up, not done here (script owned by U4b/U5): extend `scripts/lib/qts-tables.py` to pack +the **three** byte-identical structs (`sdram_config`, `cm_default_cfg`, `io_config`) and add them +to the gate — three more "found in both" assertions; `misc_config` and `rw_mgr_config` would +need a field-aware comparison and are not worth it. + +### 11.8 `mt` — the same code in both binaries + +Stock's command table (69 entries at `0x33410`, stride 28) gives `mt: maxargs=3 rep=1 cmd=0x10040a9`; +disassembled from `work/uboot-proper.bin` at that address (Thumb, `--adjust-vma=0x01000040`) +against our `do_mem_mt` **[V]** (addresses from the **current, post-0006** ELF; the first +revision of this section quoted the pre-0006 ELF, `do_mem_mt` at `0100eb34`, `memcmp` call at +`100eb66` — patch 0006 shifted the function by −0x4c, the instruction sequence is identical): + +``` +stock 10040ee: bl memcmp ours 100eb1a: bl 1050c00 + 10040f2: subs r0, #0 100eb1e: subs r0, #0 + 10040f4: it ne 100eb20: it ne + 10040f6: movne r0, #1 100eb22: movne r0, #1 + 10040f8: add sp, #12 100eb24: add sp, #8 + 10040fa: pop {r4, r5, pc} 100eb26: pop {r4, r5, r6, pc} +``` + +Same `memcmp` → `0`/`1` sense; same `argc < 3 → -1` (`CMD_RET_USAGE`) and +`cmd_get_data_size < 0 → 1` early exits (`10040ac`/`10040c4` in stock; ours `100eb2c` `mov.w r0, #0xffffffff` +/ `100eb28` `movs r0, #1`; `do_mem_mt` is at `0100eae8` now, `0100eb34` before 0006). Hush +takes `then` on `0` in both parsers; the sandbox transcript in §11.3 is the execution proof. + +### 11.9 SPL size, the check, and the one limit nobody in the tree enforces + +* `spl/u-boot-spl.bin` = **46,386 B**; packed with the socfpga header and CRC = 46,400 B; + link-time limit `tools/spl_size_limit` = **62,752** = `0x10000 − gd(224) − SPL_SYS_MALLOC_F_LEN(0x800) + − SPL_SIZE_LIMIT_PROVIDE_STACK(0x200)`; headroom 16,352 B (26 %) — §6's numbers reproduce + after the rebuild (SPL unchanged) **[V]**. +* **The check is wired and fatal [V by source]:** `CONFIG_SPL_SIZE_LIMIT=0x10000` ≠ `0x0` + enables `SPL_SIZE_CHECK` (`Makefile:1167-1168`, the `ifneq` guard and its definition), which runs on `spl/u-boot-spl.bin` + (`:2432-2434`); the `size_check` macro (`:449-460`) prints `exceeds file size limit` and + `exit 1`. The 2017.03 tool's silent-truncation window (plan §3.5) is closed. +* **OCRAM layout at run time** (§11.5): image to `0xFFFFACF0`, initial stack from + `0xFFFFF720` down, `gd`/malloc above it — 19,000 B of stack until DDR is up. +* **[U] — the BootROM's own maximum.** Neither `tools/socfpgaimage.c` (mainline caps at the + 64 KiB padded buffer, `sfp_max_size`) nor the fork's (`PADDED_SIZE 0x10000`) enforces the + smaller preloader-image limit the Cyclone V HPS Boot ROM is documented to apply (the top of + OCRAM is in use by the ROM while it copies the image in; the figure recalled is 60 KiB = + `0xF000`, which is 15,040 B above today's 46,400). If that figure is right, the effective + headroom is 15,040 B, not 16,352, and `SPL_SIZE_CHECK` alone would not catch an SPL between + 60 KiB and 61.3 KiB. Missing input: the Cyclone V HPS Technical Reference Manual, "Booting + and Configuration" chapter. Worth a `SPL_SIZE_LIMIT`-style assertion in the parity script + once the number is confirmed. + +### 11.10 Reproducing the review's decisive steps + +``` +# (a) the call is emitted, and where +arm-buildroot-linux-gnueabihf-objdump -d --disassemble=arch_early_init_r output/build/uboot-2026.07/u-boot +# (b) env vs hush, in the 2026.07 sandbox (old parser, as the DE10 build) +cd /mnt/source/uboot-wave-a/U2c/u-boot-2026.07 && ./u-boot -c '' +# (b) the fix +arm-buildroot-linux-gnueabihf-objdump -d --disassemble=env_get_bootm_mapsize output/build/uboot-2026.07/u-boot +# (e) the scalars, by binary +arm-buildroot-linux-gnueabihf-nm -S output/build/uboot-2026.07/spl/u-boot-spl | grep -E ' (sdram_config|cm_default_cfg|misc_config|io_config|rw_mgr_config)$' +# then search SPL copy 0 of both images for those bytes: python3 /mnt/source/uboot-wave-a/U9/fix2/structs.py +# (f) stock's mt +arm-buildroot-linux-gnueabihf-objdump -D -b binary -marm -Mforce-thumb --adjust-vma=0x01000040 \ + --start-address=0x10040a8 --stop-address=0x1004100 work/uboot-proper.bin +``` + +### 11.11 Observation — the parity script's ELF-less form on this artifact + +Not a U9 defect, recorded for the U4a/U5 owners **[V]**: `scripts/check-uboot-parity.sh +output/images/u-boot-with-spl.sfp ` *without* the third (ELF) argument exits 1 with +`CONTRACT VIOLATED` on the current artifact. The `bootcmd=` scan walks back through the +NUL-separated strings that precede `default_environment[]` in `.rodata` and anchors the blob +at payload offset `0x6ca6e` instead of the true `0x6cad5` (from `nm -S`, 1,150 B) — 0x67 bytes +early, so it "sees" 1,253 B / 31 entries, the extra ten being net-driver format strings +(`ethrotate`, `eth%d: %s`, …). With the ELF (release.yml's form and §4's) the same artifact +passes: `environment is byte-identical to stock: 1150 bytes, 21 entries`. The script already +prints its "a scan-only run would compare the wrong bytes" caveat; the owners may prefer the +ELF-less form to fail loudly on an ambiguous scan rather than compare. Transcript: +`/mnt/source/uboot-wave-a/U9/fix2/parity-noelf.txt`. + +### 11.12 Observations from the third verification round — scope, and the carried patches' headers + +**Scope attestation [V].** This review's only repo writes are two untracked paths: +`docs/verification/uboot-mainline.md` (this file) and +`board/mister/de10nano/patches/uboot/0006-*.patch` (a commit-message line in round 2). The +branch's `git status --short` also shows 22 *modified tracked* files (`uboot.env`, +`uboot.fragment`, `external.mk`, `scripts/ci-tests.sh`, README/PLAN/docs …) — those belong to +the concurrent U2/U3/U5/U8 tasks, and a shared working tree cannot prove by itself who wrote +what. What this review can offer instead: patches 0001–0005 carry the mtime at which U2g +placed them (`14:02:28`, all five identical; 0006 is `15:04:49`, round 2), the built artifact +is unchanged across all three rounds (`u-boot-with-spl.sfp` `4bf59acf…`, `u-boot.bin` +`ce860693…`, `u-boot-spl.bin` `ae00baa6…`), and no `make` was run in `output/` by this round. +Integrators who want a hard answer should diff the branch's tracked files against each +task's `files_written` list at merge time. + +**The carried patches' provenance labels [V], no change made.** Patch 0004 has no line +beginning `Origin:`; 0003 and 0005 spell the field `Upstream:` rather than `Upstream status:`. +Checked against `CONTRIBUTING.md:50-56`, which mandates *content* — origin SHA, author, +upstream status, disposition, `Signed-off-by` — and whose own example header says +"Reason for carrying" where every patch in this repo says "Disposition". All five items are +present in all six patches: 0004's origin SHA `c0ed23f52e203d704c0b149408f2cc0b1bb48939` +("Implement simple memory test against value.", Sorgelig, 2017-04-07) sits under an +underlined `Origin` heading at lines 26-31, directly above `Author:`; 0003/0005's `Upstream:` +lines answer the "is it in mainline" question in full. The `Upstream:` spelling is also the +repo's *dominant* convention — `git grep -l '^Upstream:' board/mister/de10nano/linux-patches/` +→ 36 files, `'^Upstream status:'` → 2, `'^Origin:'` → 38 — and nothing in `scripts/`, +`.github/` or `docs/patch-provenance.md` greps for either literal label; +`scripts/lint-kernel-patches.sh` (which checks `git am`-ability and a non-empty mailinfo +author/subject) passes all six: `RESULT: PASS — all 6 patches in 1 series are git am-able`. +Not edited: the three files are other tasks' deliverables and the change would be cosmetic. +If the owner wants one spelling, it is a five-minute label edit that changes no diff body +and needs no rebuild. + +**No finding is unlabelled.** Fixed: 11.2. Corrected after independent verification: 11.7 +(three struct rows and its summary), 11.2/patch 0006 header (the header count), 11.8 (the +pre-0006 addresses), 11.4 (the false "no mainline counterpart" sentence — the L4WD0 reset +toggle *is* compiled and executed, with the erratum kept in place), 11.5 (the ECC row's +source cell pointed at the size computation, not a scrub). Open [U]: 11.1 (hardware, +unchanged), 11.2's clean-build and kernel observations, 11.4 (owner), 11.6 (U8/owner), +11.9's BootROM limit (TRM). 11.11 and 11.12 are observations for the script owners and the +integrator. Everything else **[V]** as marked. diff --git a/external.mk b/external.mk index e2b7268e..226e1524 100644 --- a/external.mk +++ b/external.mk @@ -220,3 +220,101 @@ endef UBOOT_POST_BUILD_HOOKS += MISTER_UBOOT_DE25_QSPI_AUDIT endif # BR2_TARGET_UBOOT_BOARD_DEFCONFIG = socfpga_agilex5 (DE25-Nano) + +################################################################################ +# +# U-Boot / DE10-Nano: the resolved-.config assertion inside the build. +# +# docs/uboot-mainline-port.md section 5 step 2 names this exact symbol list as +# the acceptance test for "the five deltas" (section 3.1): a fragment line +# ASKING for a symbol is not proof the symbol survived into the RESOLVED +# .config. A future Buildroot bump, a U-Boot bump that moves a Kconfig +# default out from under board/mister/de10nano/uboot.fragment, or a hand edit +# of the fragment itself, could drop one of them silently -- nothing short of +# reading $(@D)/.config after `olddefconfig` runs would notice. This hook is +# that read, moved from a one-time manual check (plan section 6's "Structural +# assertions") into every build, per docs/uboot-tasks.md task U3: "This is +# what makes the Buildroot-bump-moves-U-Boot case fail loudly." +# +# Same mechanism as the DE25 QSPI-audit block above: BR2_EXTERNAL_MKS is +# included by Buildroot's own Makefile AFTER every package/*.mk +# (work/buildroot/Makefile: package includes first, then +# $(BR2_EXTERNAL_MKS)), so UBOOT_POST_BUILD_HOOKS already carries its +# Buildroot-default value here, and appending to it is legal. The hook body +# is $(call)ed from $(BUILD_DIR)/%/.stamp_built's own recipe +# (package/pkg-generic.mk), where $(@D) is the package's build directory +# ($(BUILD_DIR)/uboot-) -- no Buildroot patch, no U-Boot patch. +# +# Guarded on the DE10 U-Boot board defconfig specifically, the sibling of the +# DE25 block's own guard above: `make O=output-de25 printvars +# VARS='UBOOT_%_HOOKS MISTER_UBOOT_%'` on the DE25 tree must not print this +# hook's name, the same way the DE25 block is inert on the DE10 tree. +# +# Each symbol below is cited to the plan section that actually derives it, +# not forced onto section 3.1's five-row table where a symbol did not come +# from there: ENV_IS_IN_MMC/ENV_OFFSET/ENV_SIZE are section 3.3's +# environment-LOCATION divergence, not section 3.1 row 5 (which is the +# environment TEXT content, i.e. ENV_USE_DEFAULT_ENV_TEXT_FILE); +# TEXT_BASE is section 3.3's uImage-entry-point divergence and section 3.6's +# wiring; SPL_PAD_TO is section 3.5's size/headroom (the four 64 KiB SPL +# copies); CMD_MEMORY is section 3.4 (`mt`); ARCH_SOCFPGA_GEN5 is the Kconfig +# precondition section 3.1 row 1's own citation (common/spl/Kconfig:587) +# names by name. +# +################################################################################ +ifeq ($(call qstrip,$(BR2_TARGET_UBOOT_BOARD_DEFCONFIG)),socfpga_de10_nano) + +define MISTER_UBOOT_DE10_CONFIG_AUDIT + @set -eu; \ + cfg='$(@D)/.config'; \ + spl='$(@D)/spl/u-boot-spl'; \ + nm='$(TARGET_NM)'; \ + doc='docs/uboot-mainline-port.md'; \ + if [ ! -f "$$cfg" ]; then \ + echo "MiSTer DE10 U-Boot resolved-.config audit: $$cfg not found -- cannot check against $$doc section 5 step 2" >&2; \ + exit 1; \ + fi; \ + fail=0; \ + assert_eq() { \ + sym=$$1; val=$$2; cite=$$3; \ + got=$$(grep -E "^CONFIG_$${sym}=" "$$cfg" || true); \ + want="CONFIG_$${sym}=$${val}"; \ + if [ "$$got" != "$$want" ]; then \ + echo "MiSTer DE10 U-Boot resolved-.config audit FAILED ($$doc): expected $$want in $$cfg, found '$${got:-}' -- $$cite" >&2; \ + fail=1; \ + fi; \ + }; \ + assert_unset() { \ + sym=$$1; cite=$$2; \ + got=$$(grep -E "^CONFIG_$${sym}=" "$$cfg" || true); \ + if [ -n "$$got" ]; then \ + echo "MiSTer DE10 U-Boot resolved-.config audit FAILED ($$doc): CONFIG_$${sym} must be unset in $$cfg, found '$$got' -- $$cite" >&2; \ + fail=1; \ + fi; \ + }; \ + assert_eq SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_TYPE y 'section 3.1 row 1, SPL raw-mode selector'; \ + assert_eq SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION_TYPE 0xa2 'section 3.1 row 1, SPL raw-mode selector (the type-0xA2 contract, boot-chain section 2.1)'; \ + assert_unset SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR 'section 3.1 row 1 and section 6 forbidden diffs, USE_SECTOR reappearing'; \ + assert_eq FS_EXFAT y 'section 3.1 row 3, exFAT'; \ + assert_eq ENV_IS_IN_MMC y 'section 3.3 bullet 2, environment location; boot-chain section 5 Consequence (b)'; \ + assert_eq ENV_OFFSET 0x200 'section 3.3 bullet 2, environment location; boot-chain section 5 Consequence (b)'; \ + assert_eq ENV_SIZE 0x1000 'section 3.3 bullet 2, environment location; boot-chain section 5 Consequence (b)'; \ + assert_eq ENV_USE_DEFAULT_ENV_TEXT_FILE y 'section 3.1 row 5, the entire environment'; \ + assert_eq TEXT_BASE 0x01000040 'section 3.3 bullet 1, uImage entry point; section 3.6, Buildroot wiring'; \ + assert_eq SPL_PAD_TO 0x10000 'section 3.5, size and headroom, the four SPL copies'; \ + assert_eq ARCH_SOCFPGA_GEN5 y 'section 3.1 row 1, the Kconfig precondition common/spl/Kconfig:587 names'; \ + assert_eq CMD_MEMORY y 'section 3.4, mt'; \ + if [ ! -f "$$spl" ]; then \ + echo "MiSTer DE10 U-Boot resolved-.config audit: $$spl not found -- cannot check for board_spl_mmc_get_uboot_raw_sector ($$doc section 3.1 row 2)" >&2; \ + fail=1; \ + elif ! "$$nm" "$$spl" 2>/dev/null | grep -q ' board_spl_mmc_get_uboot_raw_sector$$'; then \ + echo "MiSTer DE10 U-Boot resolved-.config audit FAILED ($$doc): board_spl_mmc_get_uboot_raw_sector not linked into $$spl -- section 3.1 row 2, the dead +0x200 hook fix" >&2; \ + fail=1; \ + fi; \ + if [ "$$fail" -ne 0 ]; then exit 1; fi; \ + echo "MiSTer DE10 U-Boot resolved-.config audit ($$doc): PASS -- section 3.1/3.3/3.4/3.5/3.6 deltas all present in $$cfg, board_spl_mmc_get_uboot_raw_sector linked into $$spl" +endef + +UBOOT_POST_BUILD_HOOKS += MISTER_UBOOT_DE10_CONFIG_AUDIT + +endif # BR2_TARGET_UBOOT_BOARD_DEFCONFIG = socfpga_de10_nano (DE10-Nano) diff --git a/package/linux-rt/linux-rt.mk b/package/linux-rt/linux-rt.mk index 11097fa5..bba7e134 100644 --- a/package/linux-rt/linux-rt.mk +++ b/package/linux-rt/linux-rt.mk @@ -49,7 +49,15 @@ LINUX_RT_MAKE_FLAGS = $(LINUX_MAKE_FLAGS) LINUX_RT_ARCH_PATH = $(@D)/arch/$(KERNEL_ARCH) # Base config = the main kernel's; the RT delta rides on top as a fragment. +# Only name the kconfig file when the package is enabled. pkg-kconfig.mk +# registers `$(KCONFIG_FILE): | linux-rt-patch` for EVERY package that names +# one, enabled or not -- and this file is the board's shared linux.config, so +# on a tree where linux-rt is off (the DE25-Nano) the main kernel's config +# would still drag in linux-rt's download with an empty version and fail the +# build (found 2026-09-14; `make de25` had been broken since ADR 0030). +ifeq ($(BR2_PACKAGE_LINUX_RT),y) LINUX_RT_KCONFIG_FILE = $(call qstrip,$(BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE)) +endif LINUX_RT_KCONFIG_FRAGMENT_FILES = $(call qstrip,$(BR2_PACKAGE_LINUX_RT_CONFIG_FRAGMENT_FILES)) LINUX_RT_KCONFIG_EDITORS = menuconfig nconfig LINUX_RT_KCONFIG_OPTS = $(LINUX_KCONFIG_OPTS) diff --git a/renovate.json b/renovate.json index 20e95abc..cc53e4bd 100644 --- a/renovate.json +++ b/renovate.json @@ -154,11 +154,13 @@ }, { "customType": "regex", - "description": "Bump the RT/beta kernel pin (BR2_PACKAGE_LINUX_RT_VERSION in configs/mister_de10nano_defconfig since ADR 0030 -- the variant is package/linux-rt, a second kernel package of the image configuration; its hash is package/linux-rt/linux-rt.hash), which tracks the 7.2 line rather than 6.18.y -- see docs/rt-beta-kernel.md and ADR 0021. Kept as a SEPARATE manager from the 6.18 one above, with its own datasource and its own depName, because the two must never be able to propose each other's version: the fragment carries the identical Kconfig symbol, so a single shared manager would hand one datasource the other's currentValue. UPDATED 2026-08-17, when 7.2 released and this pin crossed the -rc boundary (7.2-rc7 -> 7.2); the depName went `kernel-mainline-rt` -> `kernel-rt-7.2` because the old one now describes a line this pin deliberately no longer follows. Renaming a depName orphans any open branch under the old name and starts a fresh dependency-dashboard entry -- that was accepted as a one-off cost, not overlooked. THE matchString MUST ACCEPT THREE FORMS and the reason is a silent-failure trap: `7.2-rc7` (history, and the shape it would return to if a human ever repoints this at an -rc), `7.2` (a two-component release -- what kernel.org actually publishes for a .0), and `7.2.1` (every later point release on this line). The three-component alternative is not hypothetical padding: the moment 7.2.1 lands and Renovate writes it here, a matchString that only accepted two components would stop matching its own output, extract zero deps, and silently leave the pin unmanaged forever -- validator-clean and invisible, exactly the failure mode this file's header describes. THE COMPANION HASH IS NOW AUTO-REFRESHED, which reverses what this description said until 2026-08-17: linux-7.2.tar.xz is an ordinary release tarball covered by kernel.org's PGP-signed sha256sums.asc, so scripts/hash-sync-kernel.sh handles this pin as its `rt` case and a routine 7.2.y bump PR can go green on its own. That script REFUSES any value containing `-rc` (kernel.org signs no manifest for a cgit snapshot, so such a hash can only be hand-written TOFU) and leaves the line untouched, so the build fails closed at the kernel download rather than blessing an unexamined snapshot -- see linux.hash's own header and docs/ci.md#renovate-hash-sync-not-automated. allowedVersions below keeps -rc out of this manager's reach in the first place; the script's refusal is the second line of defense for a hand edit.", + "description": "Bump the RT/beta kernel pin (BR2_PACKAGE_LINUX_RT_VERSION in configs/mister_de10nano_defconfig since ADR 0030 -- the variant is package/linux-rt, a second kernel package of the image configuration; its hash is package/linux-rt/linux-rt.hash), which tracks the 7.2 line rather than 6.18.y -- see docs/rt-beta-kernel.md and ADR 0021. Kept as a SEPARATE manager from the 6.18 one above, with its own datasource and its own depName, because the two must never be able to propose each other's version: the fragment carries the identical Kconfig symbol, so a single shared manager would hand one datasource the other's currentValue. UPDATED 2026-08-17, when 7.2 released and this pin crossed the -rc boundary (7.2-rc7 -> 7.2); the depName went `kernel-mainline-rt` -> `kernel-rt-7.2` because the old one now describes a line this pin deliberately no longer follows. Renaming a depName orphans any open branch under the old name and starts a fresh dependency-dashboard entry -- that was accepted as a one-off cost, not overlooked. THE matchString MUST ACCEPT THREE FORMS and the reason is a silent-failure trap: `7.2-rc7` (history, and the shape it would return to if a human ever repoints this at an -rc), `7.2` (a two-component release -- what kernel.org actually publishes for a .0), and `7.2.1` (every later point release on this line). The three-component alternative is not hypothetical padding: the moment 7.2.1 lands and Renovate writes it here, a matchString that only accepted two components would stop matching its own output, extract zero deps, and silently leave the pin unmanaged forever -- validator-clean and invisible, exactly the failure mode this file's header describes. THE COMPANION HASH IS NOW AUTO-REFRESHED, which reverses what this description said until 2026-08-17: linux-7.2.tar.xz is an ordinary release tarball covered by kernel.org's PGP-signed sha256sums.asc, so scripts/hash-sync-kernel.sh handles this pin as its `rt` case and a routine 7.2.y bump PR can go green on its own. That script REFUSES any value containing `-rc` (kernel.org signs no manifest for a cgit snapshot, so such a hash can only be hand-written TOFU) and leaves the line untouched, so the build fails closed at the kernel download rather than blessing an unexamined snapshot -- see linux.hash's own header and docs/ci.md#renovate-hash-sync-not-automated. allowedVersions below keeps -rc out of this manager's reach in the first place; the script's refusal is the second line of defense for a hand edit. ALSO (2026-09-14): the DE25-Nano's MAIN kernel is the same 7.2.y line (BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE in configs/mister_de25nano_defconfig, matched only as 7.x so it can never catch the DE10's 6.18 line) and its board/mister/de25nano/patches/linux/linux.hash is a symlink to package/linux-rt/linux-rt.hash, so both pins move in ONE PR under this depName and the same hash-sync case covers both. Added after the two drifted (DE25 stuck on 7.2.3 with no hash line once the RT pin moved to 7.2.5) and `make de25` failed closed on the download.", "managerFilePatterns": [ - "/^configs/mister_de10nano_defconfig$/" + "/^configs/mister_de10nano_defconfig$/", + "/^configs/mister_de25nano_defconfig$/" ], "matchStrings": [ + "BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE=\"(?7\\.\\d+(?:\\.\\d+)?)\"", "BR2_PACKAGE_LINUX_RT_VERSION=\"(?\\d+\\.\\d+(?:\\.\\d+)?(?:-rc\\d+)?)\"" ], "depNameTemplate": "kernel-rt-7.2", diff --git a/scripts/check-uboot-parity.sh b/scripts/check-uboot-parity.sh index 65d38c6a..171c7e40 100755 --- a/scripts/check-uboot-parity.sh +++ b/scripts/check-uboot-parity.sh @@ -66,18 +66,32 @@ # for the third argument. Verified to give identical results under gawk, mawk, # the one-true awk (nawk) and this image's own BusyBox awk (via qemu-arm). # -# Usage: scripts/check-uboot-parity.sh [u-boot-elf] +# Usage: scripts/check-uboot-parity.sh [stock-uboot.img] [u-boot-elf] # the image under test (u-boot-with-spl.sfp, or a uboot.img) -# the reference blob (scripts/fetch-sdcard-payload.sh fetches -# it by hash as STOCK_UBOOT_SHA256) +# [stock-uboot.img] the reference blob (scripts/fetch-sdcard-payload.sh fetches +# it by hash as STOCK_UBOOT_SHA256). OPTIONAL: when omitted, +# the script runs STRUCTURAL-ONLY MODE -- sections [1]-[3] +# only (uImage header/CRCs, the four SPL copies, the +# socfpga SPL header/CRC/headroom), all of which are +# properties of alone. Sections [4] and [5] +# (environment parity, command table), which are +# inherently a comparison against stock, are skipped and +# say so rather than compare against itself +# (docs/uboot-tasks.md U5: CI stays offline; the full +# comparison against the stock blob is recorded once in +# docs/verification/uboot-mainline.md and re-run by +# release.yml, which already has the blob). # [u-boot-elf] optional: the built u-boot ELF, used to locate # default_environment[] exactly instead of by scan +# (ignored in structural-only mode: no stock to compare it +# against) # Env: # NM nm to use for the ELF (default: nm) # SPL_SIZE_LIMIT byte limit for the SPL payload, e.g. the output of the # U-Boot tree's tools/spl_size_limit (default: unset, only # the 64 KiB slot is enforced) -# Exit: 0 = parity holds, 1 = a contract violation, 2 = usage/IO error. +# Exit: 0 = parity holds (or, in structural-only mode, structural checks +# hold), 1 = a contract violation, 2 = usage/IO error. set -eu @@ -100,7 +114,7 @@ prog=${0##*/} fail=0 usage() { - echo "usage: $prog [u-boot-elf]" >&2 + echo "usage: $prog [stock-uboot.img] [u-boot-elf]" >&2 exit 2 } @@ -361,12 +375,12 @@ payscan() { } # --------------------------------------------------------------------------- -[ $# -eq 2 ] || [ $# -eq 3 ] || usage +[ $# -ge 1 ] && [ $# -le 3 ] || usage built=$1 -stock=$2 +stock=${2:-} elf=${3:-} -for f in "$built" "$stock" ${elf:+"$elf"}; do +for f in "$built" ${stock:+"$stock"} ${elf:+"$elf"}; do [ -f "$f" ] || die "no such file: $f" [ -r "$f" ] || die "not readable: $f" done @@ -375,25 +389,35 @@ tmp=$(mktemp -d "${TMPDIR:-/tmp}/check-uboot-parity.XXXXXX") || die "mktemp fail trap 'rm -rf "$tmp"' EXIT HUP INT TERM bsize=$(wc -c < "$built" | tr -d ' ') -ssize=$(wc -c < "$stock" | tr -d ' ') printf '%s: built %s (%s bytes)\n' "$prog" "$built" "$bsize" -printf '%s: stock %s (%s bytes)\n' "$prog" "$stock" "$ssize" +if [ -n "$stock" ]; then + ssize=$(wc -c < "$stock" | tr -d ' ') + printf '%s: stock %s (%s bytes)\n' "$prog" "$stock" "$ssize" +else + printf '%s: stock (none -- structural-only mode: [4] and [5] are skipped)\n' "$prog" +fi if [ -n "$elf" ]; then printf '%s: elf %s\n' "$prog" "$elf"; fi min=$((UIMG_OFF + UIMG_HDR)) [ "$bsize" -gt "$min" ] || die "$built is $bsize bytes, too small to hold an SPL region and a uImage" -[ "$ssize" -gt "$min" ] || die "$stock is $ssize bytes, too small to be the stock uboot.img" +if [ -n "$stock" ]; then + [ "$ssize" -gt "$min" ] || die "$stock is $ssize bytes, too small to be the stock uboot.img" +fi # --- 1. The legacy uImage at 0x40000 ---------------------------------------- echo echo "[1] legacy uImage header at $(hx $UIMG_OFF)" -smagic=$(u32be "$stock" "$UIMG_OFF") -[ "$(printf '%08x' "$smagic")" = "$UIMG_MAGIC" ] || - die "reference $stock has no uImage magic at $(hx $UIMG_OFF) — wrong file?" -sload=$(u32be "$stock" $((UIMG_OFF + 16))) -ssz=$(u32be "$stock" $((UIMG_OFF + 12))) -note "stock: ih_size=$ssz ih_load=$(hx "$sload") ih_ep=$(hx "$(u32be "$stock" $((UIMG_OFF + 20)))")" +if [ -n "$stock" ]; then + smagic=$(u32be "$stock" "$UIMG_OFF") + [ "$(printf '%08x' "$smagic")" = "$UIMG_MAGIC" ] || + die "reference $stock has no uImage magic at $(hx $UIMG_OFF) — wrong file?" + sload=$(u32be "$stock" $((UIMG_OFF + 16))) + ssz=$(u32be "$stock" $((UIMG_OFF + 12))) + note "stock: ih_size=$ssz ih_load=$(hx "$sload") ih_ep=$(hx "$(u32be "$stock" $((UIMG_OFF + 20)))")" +else + note "no stock reference given -- structural-only mode (sections [4]/[5] skipped below)" +fi bmagic=$(u32be "$built" "$UIMG_OFF") if [ "$(printf '%08x' "$bmagic")" = "$UIMG_MAGIC" ]; then @@ -552,126 +576,137 @@ else note "SPL_SIZE_LIMIT unset: only the $SPL_SLOT-byte slot was enforced. Pass the U-Boot tree's \`tools/spl_size_limit\` output to check the link-time limit too (plan §3.5: stock's 45,820 B, mainline 57,006 B of 62,752 B)." fi -# --- 4. Environment parity --------------------------------------------------- -echo -echo "[4] default_environment[] parity" +if [ -n "$stock" ]; then + # --- 4. Environment parity --------------------------------------------------- + echo + echo "[4] default_environment[] parity" -spay=$((UIMG_OFF + UIMG_HDR)) -bpay=$((UIMG_OFF + UIMG_HDR)) + spay=$((UIMG_OFF + UIMG_HDR)) + bpay=$((UIMG_OFF + UIMG_HDR)) -payscan "$stock" "$spay" "$ssz" "$sload" env > "$tmp/stock.env.txt" || die "env scan of $stock failed" -if grep -q '^ENVERR' "$tmp/stock.env.txt"; then - die "$(sed -n 's/^ENVERR //p' "$tmp/stock.env.txt") in the reference $stock" -fi -sed -n 's/^ENVWARN / warning: /p' "$tmp/stock.env.txt" -sread=$(awk '$1=="ENV"{print $2}' "$tmp/stock.env.txt") -slen=$(awk '$1=="ENV"{print $3}' "$tmp/stock.env.txt") -scnt=$(awk '$1=="ENV"{print $4}' "$tmp/stock.env.txt") -note "stock env: payload offset $(hx "$sread"), $slen bytes, $scnt entries (file offset $(hx $((spay + sread))))" -if [ "$sread" -eq "$STOCK_ENV_OFF" ] && [ "$slen" -eq "$STOCK_ENV_LEN" ]; then - ok "stock env is where plan §6 says it is: $(hx $STOCK_ENV_OFF), $STOCK_ENV_LEN bytes" -else - note "reference note: stock env is at $(hx "$sread")/$slen B, plan §6 records $(hx $STOCK_ENV_OFF)/$STOCK_ENV_LEN B — the reference blob is not the one the plan measured" -fi - -bread="" -esize="" -if [ -n "$elf" ]; then - nmbin=${NM:-nm} - if nmline=$("$nmbin" -S "$elf" 2>/dev/null | awk '$4=="default_environment"{print $1" "$2; found=1} END{exit !found}'); then - eaddr=$((0x$(echo "$nmline" | cut -d' ' -f1))) - esize=$((0x$(echo "$nmline" | cut -d' ' -f2))) - bread=$((eaddr - bload)) - if [ "$bread" -lt 0 ] || [ $((bread + esize)) -gt "$bsz" ]; then - bad "the ELF puts default_environment outside the uImage payload — $(hx "$eaddr") size $esize is not inside [$(hx "$bload"), +$bsz) — wrong ELF for this image?" - bread="" - esize="" - else - note "$nmbin -S: default_environment at $(hx "$eaddr") size $esize -> payload offset $(hx "$bread")" - fi + payscan "$stock" "$spay" "$ssz" "$sload" env > "$tmp/stock.env.txt" || die "env scan of $stock failed" + if grep -q '^ENVERR' "$tmp/stock.env.txt"; then + die "$(sed -n 's/^ENVERR //p' "$tmp/stock.env.txt") in the reference $stock" + fi + sed -n 's/^ENVWARN / warning: /p' "$tmp/stock.env.txt" + sread=$(awk '$1=="ENV"{print $2}' "$tmp/stock.env.txt") + slen=$(awk '$1=="ENV"{print $3}' "$tmp/stock.env.txt") + scnt=$(awk '$1=="ENV"{print $4}' "$tmp/stock.env.txt") + note "stock env: payload offset $(hx "$sread"), $slen bytes, $scnt entries (file offset $(hx $((spay + sread))))" + if [ "$sread" -eq "$STOCK_ENV_OFF" ] && [ "$slen" -eq "$STOCK_ENV_LEN" ]; then + ok "stock env is where plan §6 says it is: $(hx $STOCK_ENV_OFF), $STOCK_ENV_LEN bytes" else - note "$nmbin -S found no default_environment in $elf (stripped, or a different link) — falling back to the scan" + note "reference note: stock env is at $(hx "$sread")/$slen B, plan §6 records $(hx $STOCK_ENV_OFF)/$STOCK_ENV_LEN B — the reference blob is not the one the plan measured" fi -fi -payscan "$built" "$bpay" "$bsz" "$bload" env "${bread:--1}" > "$tmp/built.env.txt" || die "env scan of $built failed" -if grep -q '^ENVERR' "$tmp/built.env.txt"; then - bad "$(sed -n 's/^ENVERR //p' "$tmp/built.env.txt") in $built — no environment blob to compare" -else - boff=$(awk '$1=="ENV"{print $2}' "$tmp/built.env.txt") - blen=$(awk '$1=="ENV"{print $3}' "$tmp/built.env.txt") - bcnt=$(awk '$1=="ENV"{print $4}' "$tmp/built.env.txt") - note "built env: payload offset $(hx "$boff"), $blen bytes, $bcnt entries (file offset $(hx $((bpay + boff))))" - if [ -n "$bread" ]; then - note "located from the ELF symbol; the scan is only a cross-check" - fi - aoff=$(awk '$1=="ENVA"{print $2}' "$tmp/built.env.txt") - if [ -n "$aoff" ]; then - note "scan: $(awk '$1=="ENVA"{printf "anchored on \"%s\" (%d match(es) in the payload), blob at 0x%08x, %d bytes, %d entries", $5, $6, $2, $3, $4}' "$tmp/built.env.txt")" - if [ "$aoff" -ne "$boff" ]; then - note "the scan and the ELF disagree ($(hx "$aoff") vs $(hx "$boff")) — the ELF wins; a scan-only run of this image would compare the wrong bytes" + bread="" + esize="" + if [ -n "$elf" ]; then + nmbin=${NM:-nm} + if nmline=$("$nmbin" -S "$elf" 2>/dev/null | awk '$4=="default_environment"{print $1" "$2; found=1} END{exit !found}'); then + eaddr=$((0x$(echo "$nmline" | cut -d' ' -f1))) + esize=$((0x$(echo "$nmline" | cut -d' ' -f2))) + bread=$((eaddr - bload)) + if [ "$bread" -lt 0 ] || [ $((bread + esize)) -gt "$bsz" ]; then + bad "the ELF puts default_environment outside the uImage payload — $(hx "$eaddr") size $esize is not inside [$(hx "$bload"), +$bsz) — wrong ELF for this image?" + bread="" + esize="" + else + note "$nmbin -S: default_environment at $(hx "$eaddr") size $esize -> payload offset $(hx "$bread")" + fi + else + note "$nmbin -S found no default_environment in $elf (stripped, or a different link) — falling back to the scan" fi - elif [ -z "$bread" ]; then - note "scan: no usable anchor beyond the blob it found" - fi - if [ -n "$esize" ] && [ "$esize" -ne "$blen" ]; then - note "the ELF symbol is $esize B and the blob is $blen B — the symbol also covers the string literal's own trailing NUL and any alignment padding after the NUL-NUL terminator (plan §6: stock is 1,150 B of a 1,151 B symbol)" fi - dd if="$stock" bs=1 skip=$((spay + sread)) count="$slen" of="$tmp/stock.env.bin" 2>/dev/null - dd if="$built" bs=1 skip=$((bpay + boff)) count="$blen" of="$tmp/built.env.bin" 2>/dev/null - if [ "$blen" -ne "$slen" ]; then - bad "environment is $blen bytes, stock's is $slen bytes ($bcnt entries vs $scnt)" - fi - if cmp -s "$tmp/stock.env.bin" "$tmp/built.env.bin"; then - ok "environment is byte-identical to stock: $slen bytes, $scnt entries (\`mt\` carried, plan §3.4 — no allowed delta)" + payscan "$built" "$bpay" "$bsz" "$bload" env "${bread:--1}" > "$tmp/built.env.txt" || die "env scan of $built failed" + if grep -q '^ENVERR' "$tmp/built.env.txt"; then + bad "$(sed -n 's/^ENVERR //p' "$tmp/built.env.txt") in $built — no environment blob to compare" else - bad "environment differs from stock's — every byte of default_environment[] is a forbidden diff (plan §6)" - cmp "$tmp/stock.env.bin" "$tmp/built.env.bin" 2>&1 | sed 's/^/ /' >&2 || true - echo " entry-by-entry diagnostic (-stock +built):" >&2 - sed -n 's/^ENTRY [0-9]* [0-9]* //p' "$tmp/stock.env.txt" > "$tmp/stock.entries" - sed -n 's/^ENTRY [0-9]* [0-9]* //p' "$tmp/built.env.txt" > "$tmp/built.entries" - diff -u "$tmp/stock.entries" "$tmp/built.entries" 2>&1 | sed 's/^/ /' >&2 || true + boff=$(awk '$1=="ENV"{print $2}' "$tmp/built.env.txt") + blen=$(awk '$1=="ENV"{print $3}' "$tmp/built.env.txt") + bcnt=$(awk '$1=="ENV"{print $4}' "$tmp/built.env.txt") + note "built env: payload offset $(hx "$boff"), $blen bytes, $bcnt entries (file offset $(hx $((bpay + boff))))" + if [ -n "$bread" ]; then + note "located from the ELF symbol; the scan is only a cross-check" + fi + aoff=$(awk '$1=="ENVA"{print $2}' "$tmp/built.env.txt") + if [ -n "$aoff" ]; then + note "scan: $(awk '$1=="ENVA"{printf "anchored on \"%s\" (%d match(es) in the payload), blob at 0x%08x, %d bytes, %d entries", $5, $6, $2, $3, $4}' "$tmp/built.env.txt")" + if [ "$aoff" -ne "$boff" ]; then + note "the scan and the ELF disagree ($(hx "$aoff") vs $(hx "$boff")) — the ELF wins; a scan-only run of this image would compare the wrong bytes" + fi + elif [ -z "$bread" ]; then + note "scan: no usable anchor beyond the blob it found" + fi + if [ -n "$esize" ] && [ "$esize" -ne "$blen" ]; then + note "the ELF symbol is $esize B and the blob is $blen B — the symbol also covers the string literal's own trailing NUL and any alignment padding after the NUL-NUL terminator (plan §6: stock is 1,150 B of a 1,151 B symbol)" + fi + + dd if="$stock" bs=1 skip=$((spay + sread)) count="$slen" of="$tmp/stock.env.bin" 2>/dev/null + dd if="$built" bs=1 skip=$((bpay + boff)) count="$blen" of="$tmp/built.env.bin" 2>/dev/null + if [ "$blen" -ne "$slen" ]; then + bad "environment is $blen bytes, stock's is $slen bytes ($bcnt entries vs $scnt)" + fi + if cmp -s "$tmp/stock.env.bin" "$tmp/built.env.bin"; then + ok "environment is byte-identical to stock: $slen bytes, $scnt entries (\`mt\` carried, plan §3.4 — no allowed delta)" + else + bad "environment differs from stock's — every byte of default_environment[] is a forbidden diff (plan §6)" + cmp "$tmp/stock.env.bin" "$tmp/built.env.bin" 2>&1 | sed 's/^/ /' >&2 || true + echo " entry-by-entry diagnostic (-stock +built):" >&2 + sed -n 's/^ENTRY [0-9]* [0-9]* //p' "$tmp/stock.env.txt" > "$tmp/stock.entries" + sed -n 's/^ENTRY [0-9]* [0-9]* //p' "$tmp/built.env.txt" > "$tmp/built.entries" + diff -u "$tmp/stock.entries" "$tmp/built.entries" 2>&1 | sed 's/^/ /' >&2 || true + fi fi -fi -# --- 5. Command table -------------------------------------------------------- -echo -echo "[5] command table" + # --- 5. Command table -------------------------------------------------------- + echo + echo "[5] command table" -payscan "$stock" "$spay" "$ssz" "$sload" cmd > "$tmp/stock.cmd.txt" || die "command scan of $stock failed" -if grep -q '^CMDERR' "$tmp/stock.cmd.txt"; then - die "no command table found in the reference $stock" -fi -payscan "$built" "$bpay" "$bsz" "$bload" cmd > "$tmp/built.cmd.txt" || die "command scan of $built failed" + payscan "$stock" "$spay" "$ssz" "$sload" cmd > "$tmp/stock.cmd.txt" || die "command scan of $stock failed" + if grep -q '^CMDERR' "$tmp/stock.cmd.txt"; then + die "no command table found in the reference $stock" + fi + payscan "$built" "$bpay" "$bsz" "$bload" cmd > "$tmp/built.cmd.txt" || die "command scan of $built failed" -sed -n 's/^NAME //p' "$tmp/stock.cmd.txt" | sort -u > "$tmp/stock.names" -snames=$(wc -l < "$tmp/stock.names" | tr -d ' ') -note "stock: $(awk '$1=="TABLE"{print $2}' "$tmp/stock.cmd.txt") entries at payload offset $(hx "$(awk '$1=="TABLE"{print $3}' "$tmp/stock.cmd.txt")"), stride $(awk '$1=="TABLE"{print $4}' "$tmp/stock.cmd.txt") (allowed diff: offset and stride)" + sed -n 's/^NAME //p' "$tmp/stock.cmd.txt" | sort -u > "$tmp/stock.names" + snames=$(wc -l < "$tmp/stock.names" | tr -d ' ') + note "stock: $(awk '$1=="TABLE"{print $2}' "$tmp/stock.cmd.txt") entries at payload offset $(hx "$(awk '$1=="TABLE"{print $3}' "$tmp/stock.cmd.txt")"), stride $(awk '$1=="TABLE"{print $4}' "$tmp/stock.cmd.txt") (allowed diff: offset and stride)" -if grep -q '^CMDERR' "$tmp/built.cmd.txt"; then - bad "no command table found in $built" -else - sed -n 's/^NAME //p' "$tmp/built.cmd.txt" | sort -u > "$tmp/built.names" - bnames=$(wc -l < "$tmp/built.names" | tr -d ' ') - note "built: $(awk '$1=="TABLE"{print $2}' "$tmp/built.cmd.txt") entries at payload offset $(hx "$(awk '$1=="TABLE"{print $3}' "$tmp/built.cmd.txt")"), stride $(awk '$1=="TABLE"{print $4}' "$tmp/built.cmd.txt")" - comm -23 "$tmp/stock.names" "$tmp/built.names" > "$tmp/missing" - comm -13 "$tmp/stock.names" "$tmp/built.names" > "$tmp/extra" - if [ -s "$tmp/missing" ]; then - bad "$(wc -l < "$tmp/missing" | tr -d ' ') of stock's $snames commands are missing: $(tr '\n' ' ' < "$tmp/missing")" - else - ok "all $snames stock command names are present ($bnames total in the build)" - fi - if grep -qx 'mt' "$tmp/built.names"; then - ok "\`mt\` is present — stock's fpgacheck runs unmodified (plan §3.4)" + if grep -q '^CMDERR' "$tmp/built.cmd.txt"; then + bad "no command table found in $built" else - bad "\`mt\` is missing: stock's fpgacheck would fail on every boot (plan §3.4)" - fi - if [ -s "$tmp/extra" ]; then - note "extra commands, allowed: $(tr '\n' ' ' < "$tmp/extra")" - else - note "no extra commands beyond stock's $snames" + sed -n 's/^NAME //p' "$tmp/built.cmd.txt" | sort -u > "$tmp/built.names" + bnames=$(wc -l < "$tmp/built.names" | tr -d ' ') + note "built: $(awk '$1=="TABLE"{print $2}' "$tmp/built.cmd.txt") entries at payload offset $(hx "$(awk '$1=="TABLE"{print $3}' "$tmp/built.cmd.txt")"), stride $(awk '$1=="TABLE"{print $4}' "$tmp/built.cmd.txt")" + comm -23 "$tmp/stock.names" "$tmp/built.names" > "$tmp/missing" + comm -13 "$tmp/stock.names" "$tmp/built.names" > "$tmp/extra" + if [ -s "$tmp/missing" ]; then + bad "$(wc -l < "$tmp/missing" | tr -d ' ') of stock's $snames commands are missing: $(tr '\n' ' ' < "$tmp/missing")" + else + ok "all $snames stock command names are present ($bnames total in the build)" + fi + if grep -qx 'mt' "$tmp/built.names"; then + ok "\`mt\` is present — stock's fpgacheck runs unmodified (plan §3.4)" + else + bad "\`mt\` is missing: stock's fpgacheck would fail on every boot (plan §3.4)" + fi + if [ -s "$tmp/extra" ]; then + note "extra commands, allowed: $(tr '\n' ' ' < "$tmp/extra")" + else + note "no extra commands beyond stock's $snames" + fi fi +else + echo + echo "[4] default_environment[] parity -- SKIPPED (structural-only mode)" + note "no stock reference given; the built/stock comparison is recorded once in" + note "docs/verification/uboot-mainline.md section 4 (U2g) and re-run by release.yml," + note "which already has the pinned stock blob -- pass one here (\$2) to repeat it" + echo + echo "[5] command table -- SKIPPED (structural-only mode)" + note "no stock reference given; recorded once in docs/verification/uboot-mainline.md section 4" fi # --- 6. Verdict -------------------------------------------------------------- @@ -682,7 +717,11 @@ echo " table offsets inside the SPL; extra commands. Everything else above is echo " a hard contract." if [ "$fail" -eq 0 ]; then - echo "$prog: parity holds" + if [ -n "$stock" ]; then + echo "$prog: parity holds" + else + echo "$prog: structural checks hold (structural-only mode -- no stock reference given, [4]/[5] skipped)" + fi else echo "$prog: CONTRACT VIOLATED" >&2 fi diff --git a/scripts/ci-tests.sh b/scripts/ci-tests.sh index 14d929ff..6c0ed07e 100755 --- a/scripts/ci-tests.sh +++ b/scripts/ci-tests.sh @@ -55,6 +55,18 @@ # (default: /ci-tests-results.txt). Best # -effort: if the path is not writable the run still # passes. Upload this as a CI artifact. +# STOCK_UBOOT_IMG= a local stock uboot.img (docs/uboot-tasks.md U5): +# when this exists, the U-Boot section runs the +# FULL check-uboot-parity.sh/check-uboot-handoff.sh +# comparison against it; otherwise it runs their +# offline (structural-only / built-SPL-only) +# subset and the full comparison is left to +# .github/workflows/release.yml, which already +# has the blob on every tagged release. Default, +# same as scripts/test-uboot-parity.sh's: +# /output-sdcard-stage/mister-payload/ +# linux/uboot.img (scripts/fetch-sdcard-payload.sh's +# product path). set -u # Deliberately not -e: this script's entire job is "run every check, keep going, @@ -348,6 +360,154 @@ else fi fi +# ============================================================================= +section "U-Boot (DE10-Nano, ships nowhere) -- docs/uboot-tasks.md U5" +# ============================================================================= +# Owner decision 1: the DE10's mainline U-Boot builds inside +# configs/mister_de10nano_defconfig and SHIPS NOWHERE -- never named uboot.img, +# never staged into a release; the shipped bootloader stays the stock blob +# (docs/verification/uboot-mainline.md). This section asserts that boundary +# (always, offline, every PR) and then runs the two U4 gates +# (scripts/check-uboot-parity.sh, scripts/check-uboot-handoff.sh) at whatever +# depth the local tree supports: +# - offline (every run): structural-only parity (SPL/uImage headers, CRCs, +# the four SPL copies -- properties of the built .sfp alone, no stock +# needed) and the handoff tables' presence in the built SPL (the same +# image passed as both arguments -- scripts/check-uboot-handoff.sh's own +# header documents this as a legitimate call shape); +# - full (when a stock uboot.img is on disk -- STOCK_UBOOT_IMG, or a prior +# scripts/fetch-sdcard-payload.sh run): the complete environment/ +# command-table comparison against stock. This is NOT required to pass in +# every PR -- CI stays offline by design (no 126 MB download per run) -- +# it is what .github/workflows/release.yml runs unconditionally right +# after its own stock-payload fetch, so every tagged release proves it. + +UBOOT_SFP="$IMAGES/u-boot-with-spl.sfp" +UBOOT_SHIPPED_IMG="$IMAGES/uboot.img" + +if [ -f "$UBOOT_SFP" ]; then + pass "images/u-boot-with-spl.sfp exists ($(wc -c < "$UBOOT_SFP" | tr -d ' ') bytes)" +else + fail "images/u-boot-with-spl.sfp exists" "not found at $UBOOT_SFP -- owner decision 1 builds it into every configs/mister_de10nano_defconfig image" +fi + +if [ -e "$UBOOT_SHIPPED_IMG" ]; then + fail "no images/uboot.img" "$UBOOT_SHIPPED_IMG exists -- the mainline build must never be named uboot.img (docs/uboot-tasks.md's naming rule; that name is reserved for the stock blob)" +else + pass "no images/uboot.img (the mainline build ships nowhere)" +fi + +# scripts/mk-release.sh stages named files only (linux.img, zImage_dtb[-rt], +# buildroot.config, linux.config, linux-rt.config, legal-info.tar.gz, +# release_.7z, SHA256SUMS) -- asserted anyway, so a future edit that +# widens the stage cannot silently pick up either bootloader artifact. +# +# NOTE ON WHERE THIS BITES IN CI: in BOTH workflows this suite runs BEFORE +# scripts/mk-release.sh creates dist/ (build.yml never creates one at all; +# release.yml runs verify-image first and mk-release.sh after), so in CI this +# check always SKIPs -- it can only fire for a developer who runs the suite +# after a local mk-release.sh. The CI-side teeth for the same invariant are in +# .github/workflows/release.yml's "Verify U-Boot (DE10) parity against the +# stock blob" step, which runs after dist/ exists and greps it there. Keep +# both: this one catches it on the desk, that one catches it on a tag. +UBOOT_RELEASE_STAGE="$ROOT/dist" +if [ -d "$UBOOT_RELEASE_STAGE" ]; then + _ub_stage_hits=$(find "$UBOOT_RELEASE_STAGE" -maxdepth 1 \( -name 'uboot.img' -o -name '*.sfp' \) 2>/dev/null) + if [ -n "$_ub_stage_hits" ]; then + fail "no uboot.img/*.sfp in the release stage" "$UBOOT_RELEASE_STAGE has: $(printf '%s' "$_ub_stage_hits" | tr '\n' ' ')" + else + pass "no uboot.img/*.sfp in the release stage ($UBOOT_RELEASE_STAGE)" + fi +else + skip "no uboot.img/*.sfp in the release stage" "no $UBOOT_RELEASE_STAGE yet -- this suite runs before scripts/mk-release.sh in both workflows, so the release-time assertion lives in .github/workflows/release.yml's U-Boot step instead" +fi + +# The single build tree under this $BUILD_DIR -- uniqueness-guarded the same +# way the initramfs section guards the kernel .config glob above, but under a +# LOCAL `nullglob`: without it bash keeps an unmatched glob as its literal +# string, so "no U-Boot build tree at all" reported itself as "found 1" in the +# skip reason below. Saved and restored rather than set globally -- the rest of +# this suite's globs are written for the default (literal) behaviour. +if shopt -q nullglob; then _ub_nullglob_was_set=1; else _ub_nullglob_was_set=0; shopt -s nullglob; fi +_ub_uboot_tree=("$BUILD_DIR"/build/uboot-*) +[ "$_ub_nullglob_was_set" -eq 1 ] || shopt -u nullglob +UBOOT_BUILD_TREE="" +if [ "${#_ub_uboot_tree[@]}" -eq 1 ] && [ -d "${_ub_uboot_tree[0]}" ]; then + UBOOT_BUILD_TREE="${_ub_uboot_tree[0]}" +fi +UBOOT_QTS_DIR="$UBOOT_BUILD_TREE/board/terasic/de10-nano/qts" +UBOOT_ELF="$UBOOT_BUILD_TREE/u-boot" + +echo +printf -- '--- check-uboot-parity.sh (structural-only: no stock reference -- offline) ---\n' +if [ -f "$UBOOT_SFP" ]; then + if "$ROOT/scripts/check-uboot-parity.sh" "$UBOOT_SFP"; then + pass "check-uboot-parity.sh (structural-only, offline)" + else + fail "check-uboot-parity.sh (structural-only, offline)" "'check-uboot-parity.sh $UBOOT_SFP' exited nonzero -- see output above" + fi +else + skip "check-uboot-parity.sh (structural-only, offline)" "no $UBOOT_SFP to check" +fi + +echo +printf -- '--- check-uboot-handoff.sh (built SPL only: same image as both arguments -- offline) ---\n' +if [ -f "$UBOOT_SFP" ] && [ -d "$UBOOT_QTS_DIR" ]; then + if "$ROOT/scripts/check-uboot-handoff.sh" "$UBOOT_QTS_DIR" "$UBOOT_SFP" "$UBOOT_SFP"; then + pass "check-uboot-handoff.sh (built SPL only, offline)" + else + fail "check-uboot-handoff.sh (built SPL only, offline)" "'check-uboot-handoff.sh $UBOOT_QTS_DIR $UBOOT_SFP $UBOOT_SFP' exited nonzero -- see output above" + fi +elif [ ! -f "$UBOOT_SFP" ]; then + skip "check-uboot-handoff.sh (built SPL only, offline)" "no $UBOOT_SFP to check" +elif [ -z "$UBOOT_BUILD_TREE" ]; then + skip "check-uboot-handoff.sh (built SPL only, offline)" "expected exactly one $BUILD_DIR/build/uboot-*/ to read the QTS headers from, found ${#_ub_uboot_tree[@]}" +else + skip "check-uboot-handoff.sh (built SPL only, offline)" "no $UBOOT_QTS_DIR -- the U-Boot tree is there but carries no QTS headers (patch 0003 did not apply?)" +fi + +echo +# Two distinct reasons the full comparison can be unavailable, reported +# separately: an image that was never built is a different problem from a +# stock blob that was never fetched, and collapsing them into one message sent +# a reader looking for STOCK_UBOOT_IMG when the real fault was upstream of it. +UBOOT_STOCK_IMG="${STOCK_UBOOT_IMG:-$ROOT/output-sdcard-stage/mister-payload/linux/uboot.img}" +_ub_full_why="" +if [ ! -f "$UBOOT_SFP" ]; then + _ub_full_why="no $UBOOT_SFP to compare -- nothing was built (see the FAIL above); the stock reference is not the missing piece" +elif [ ! -f "$UBOOT_STOCK_IMG" ]; then + _ub_full_why="no stock uboot.img at $UBOOT_STOCK_IMG -- set STOCK_UBOOT_IMG, run scripts/fetch-sdcard-payload.sh, or leave it to .github/workflows/release.yml, which runs this full comparison on every tagged release (CI stays offline by design: no 126 MB download per PR)" +fi +if [ -z "$_ub_full_why" ]; then + printf -- '--- check-uboot-parity.sh (full, against %s) ---\n' "$UBOOT_STOCK_IMG" + _ub_parity_cmd=("$ROOT/scripts/check-uboot-parity.sh" "$UBOOT_SFP" "$UBOOT_STOCK_IMG") + [ -f "$UBOOT_ELF" ] && _ub_parity_cmd+=("$UBOOT_ELF") + _ub_spl_limit="" + [ -x "$UBOOT_BUILD_TREE/tools/spl_size_limit" ] && _ub_spl_limit=$("$UBOOT_BUILD_TREE/tools/spl_size_limit" 2>/dev/null || true) + if SPL_SIZE_LIMIT="$_ub_spl_limit" "${_ub_parity_cmd[@]}"; then + pass "check-uboot-parity.sh (full, stock reference)" + else + fail "check-uboot-parity.sh (full, stock reference)" "'${_ub_parity_cmd[*]}' exited nonzero -- see output above" + fi + + echo + printf -- '--- check-uboot-handoff.sh (full, against %s) ---\n' "$UBOOT_STOCK_IMG" + if [ -d "$UBOOT_QTS_DIR" ]; then + if "$ROOT/scripts/check-uboot-handoff.sh" "$UBOOT_QTS_DIR" "$UBOOT_SFP" "$UBOOT_STOCK_IMG"; then + pass "check-uboot-handoff.sh (full, stock reference)" + else + fail "check-uboot-handoff.sh (full, stock reference)" "'check-uboot-handoff.sh $UBOOT_QTS_DIR $UBOOT_SFP $UBOOT_STOCK_IMG' exited nonzero -- see output above" + fi + elif [ -z "$UBOOT_BUILD_TREE" ]; then + skip "check-uboot-handoff.sh (full, stock reference)" "expected exactly one $BUILD_DIR/build/uboot-*/ to read the QTS headers from, found ${#_ub_uboot_tree[@]}" + else + skip "check-uboot-handoff.sh (full, stock reference)" "no $UBOOT_QTS_DIR -- the U-Boot tree is there but carries no QTS headers (patch 0003 did not apply?)" + fi +else + skip "check-uboot-parity.sh (full, stock reference)" "$_ub_full_why" + skip "check-uboot-handoff.sh (full, stock reference)" "$_ub_full_why" +fi + # ============================================================================= section "DE25-Nano -- QSPI-write audit, Linux side (de25-boot-chain.md section 7 row 11)" # ============================================================================= diff --git a/scripts/lint-kernel-patches.sh b/scripts/lint-kernel-patches.sh index 94661420..ca949f37 100755 --- a/scripts/lint-kernel-patches.sh +++ b/scripts/lint-kernel-patches.sh @@ -33,8 +33,8 @@ # # BOTH SERIES ARE LINTED # ---------------------- -# There are two patch directories, and the second one needs this check MORE than the -# first, not less: +# There are two kernel patch directories, and the second one needs this check MORE than +# the first, not less: # # / carried — applied by Buildroot to the shipped # image and replayed by the export; @@ -48,10 +48,24 @@ # can sit for months. So the default here is both directories, and CI runs it with no # arguments. # +# THE TWO U-BOOT SERIES, TOO (docs/uboot-tasks.md U5) +# ---------------------------------------------------- +# board/mister/de10nano/patches/uboot/ and board/mister/de25nano/patches/uboot/ carry the +# same kind of provenance header (CONTRIBUTING section 2) as the kernel series, resolved +# by `patch -p1` the same blind way, and U7-prep/DU5-prep exist precisely to replay them +# upstream as `git am` mail one day — so a malformed `From:` there is the identical latent +# defect, just waiting on a different export. Each is resolved from its OWN defconfig's +# `BR2_GLOBAL_PATCH_DIR` (Buildroot appends the package name, `uboot`, itself — U1) rather +# than hardcoded, and treated exactly like the kernel's `-upstream` series: missing is +# "not landed on this branch yet", present-but-empty gets a note, present-and-populated is +# linted. +# # Usage: scripts/lint-kernel-patches.sh [patch-dir...] # With no arguments, lints the series named by BR2_LINUX_KERNEL_PATCH in -# configs/mister_de10nano_defconfig, plus the upstream-only series alongside it -# ("-upstream") when that directory exists. +# configs/mister_de10nano_defconfig, the upstream-only series alongside it +# ("-upstream") when that directory exists and holds patches, and +# board/mister/{de10nano,de25nano}/patches/uboot/ (each resolved from that board's own +# defconfig) when they exist and hold patches. # A directory named on the command line must exist and contain patches. # # Exit: 0 = every patch is am-able; 1 = at least one is not (details on stderr). @@ -101,6 +115,31 @@ if ((${#patch_dirs[@]} == 0)); then printf 'note: %s holds no patches yet — nothing to lint there.\n' \ "${default_dir}-upstream" fi + + # The two U-Boot series (docs/uboot-tasks.md U5), same leniency as the kernel + # upstream-only series just above: resolved from each BOARD's own defconfig + # (BR2_GLOBAL_PATCH_DIR; Buildroot appends the package name -- "uboot" -- itself, + # same as it does "linux" for BR2_LINUX_KERNEL_PATCH), so a moved patch dir cannot + # silently go unlinted. Missing is "not landed on this branch yet"; present-but-empty + # gets a note, not a failure; present-and-populated is linted like everything else. + for ub_board in de10nano de25nano; do + ub_defconfig="$REPO_ROOT/configs/mister_${ub_board}_defconfig" + [[ -f $ub_defconfig ]] || continue + + ub_global_patch_dir="$(sed -n 's/^BR2_GLOBAL_PATCH_DIR="\([^"]*\)".*$/\1/p' "$ub_defconfig" | tail -1)" + ub_global_patch_dir="${ub_global_patch_dir//\$(BR2_EXTERNAL_MISTER_PATH)/$REPO_ROOT}" + [[ -n $ub_global_patch_dir ]] || continue + ub_patch_dir="$ub_global_patch_dir/uboot" + + shopt -s nullglob + ub_found=("$ub_patch_dir"/*.patch) + shopt -u nullglob + if ((${#ub_found[@]})); then + patch_dirs+=("$ub_patch_dir") + elif [[ -d $ub_patch_dir ]]; then + printf 'note: %s holds no patches yet — nothing to lint there.\n' "$ub_patch_dir" + fi + done fi for dir in "${patch_dirs[@]}"; do