# dmcub/hw — hardware PoC path for FINDING-2 (Option A: bring up the DMCUB) **Status: RUN ON HARDWARE, 2026-09 (Navi 31 over USB4, tinygrad AM).** The DMCUB was brought up from a cold, uninitialised display block and FINDING-2 was reproduced: see `EVIDENCE-type8-poc.txt`. Every script also keeps its offline `--selftest` (header parsing, address arithmetic, window layout, register write order, frame encoding, and a model of the target firmware case). Results in one line each: * `dmcub_probe.py` — DCN is powered and DMU registers are live; DMCUB was halted. * `dmcub_bringup.py` — **backdoor load is blocked on this part** (CW0/CW1 and SEC_CNTL writes are dropped); `--phase psp_load` boots the core (`SCRATCH0 = 0x3`). * `dmcub_poc_type8.py` — 5/5 out-of-range `panel_inst` values returned planted bytes; confirmed to k = 227. ## Why bring-up is needed at all The rig drives the card through tinygrad's AM userspace driver, which **initialises no display IP** (`psp_out/ATTACK-SURFACE.md:190` — the same blocker that stalled the HDCP display-state path). So there is no DMCUB firmware loaded, no cache windows and no inbox1 ring to submit a command to. This directory ports `dmub_srv_hw_init` + `dmub_dcn32_*` (vendored in `../ref/`) onto that stack. **Corrected on hardware:** the plan was to avoid the PSP entirely via `dmub_dcn32_backdoor_load`, on the assumption that the instruction window is fetched from framebuffer with no signature check in our path. That is **wrong on Navi 31** — `DMCUB_REGION3_CW0_*`, `CW1_*` and `DMCUB_SEC_CNTL` silently drop host writes and read back 0, while `CW3/4/6`, `INBOX1_*` and `DMCUB_CNTL` retain them. The backdoor sequence therefore runs to completion and the core never boots. The working path is `--phase psp_load`: `GFX_CMD_ID_LOAD_IP_FW` with `GFX_FW_TYPE_DMUB` and the signed `$PS1` payload (status 0), then `windows`, `mailbox`, `release` — after which `DMCUB_SCRATCH0 = 0x3` (`dal_fw`, `mailbox_rdy`). The **stock signed image** is used unmodified throughout. A hung DMCUB is revived by re-running `psp_load` + `release`; no ATX cycle was needed at any point, and the rest of the GPU stayed healthy. ## Order of operations ```sh # 0. offline, no hardware: python3 dmcub_hwregs.py --selftest python3 dmcub_bringup.py --selftest python3 dmcub_poc_type8.py --selftest # 1. READ-ONLY gate: is DCN powered, is the DMCUB already running? python3 dmcub_probe.py --json probe.json # 2. review the exact register sequence (writes nothing) python3 dmcub_bringup.py --state-out bringup.json # 3. bring the core up python3 dmcub_bringup.py --state-out bringup.json --apply # 4. the PoC: plant markers, submit type-8, check the reply python3 dmcub_poc_type8.py --state bringup.json # dry run python3 dmcub_poc_type8.py --state bringup.json --apply ``` `dmcub_probe.py` decides whether steps 2-3 are needed at all: if something has already brought the DMCUB up, the PoC can inject into the existing ring. ## Observed safety (and one PoC artifact) Reads whose source leaves every mapped window return **zeros** and do not fault the core (verified at `panel_inst` = 300, 1000, 0x4000, 0xFFFF), so the "unbacked access" hazard this PoC was designed around did not materialise. One DMCUB hang *was* seen, after a sweep that planted gate bytes across the whole fw_state window — i.e. the PoC corrupting live firmware state, not the bug. Plant only what a run needs. ## Why this PoC cannot wedge the card The leak is `memcpy(frame + 4, ctx + 8*panel_inst + 0xaea0, 8)` with `ctx = 0x66000300` — inside **cache window 6**, whose backing VRAM *we allocate*. So the PoC: * plants known 8-byte markers at `ctx + 8*k + 0xaea0` and sets the stride-0x7b8 gate byte, both from the host, for `k` in the derived deterministic range **2..23** (source *and* gate mapped); * treats `k = 0, 1` as the baseline — those are the only legitimate values, since the part has two panels; * refuses any `k` whose source leaves the window (`k > 227` for this image) unless `--allow-wild` is passed. That is the unbacked-access case `psp_out/CONSTRAINTS.md` warns about ("any probe that makes an on-die engine access unbacked memory can take out the SMU"); * re-checks liveness (`DMCUB_SCRATCH0.dal_fw` + `DMCUB_TIMER_CURRENT` advancing) after every submission and stops at the first anomaly. Every address touched stays inside a window the host mapped. The evidence is therefore stronger *and* safer than a wild read: the reply must contain **our** bytes from an offset the command is never supposed to reach. ## Expected result, and what would falsify the finding | panel_inst | expectation if FINDING-2 is real | | --- | --- | | 0, 1 | firmware's own per-panel confirm colour (no marker planted) — observed `0000000000000000` / `0000000000000100` | | 2..23 | the planted marker returned verbatim in `frame[4:12]` — **observed** | | 24..227 | leaks too, once a gate is satisfied; the per-index gate is unmapped there, so set `[ctx+0xab28]` instead — **observed** | | >227 | source outside every window: returns zeros, core stays alive — **observed** | If indices ≥ 2 come back as zeros, or identical to `k = 1`, or the frame's `ret_status` bit is never cleared, then the index is bounded somewhere the static audit missed and **FINDING-2 must be retracted**. ## Known unknowns (updated after the run) 1. ~~DCN power/clock~~ — **resolved**: DCN is already powered on this card after ATX-on; DENTIST/DCCG read real values and DMU registers are live. No display power work was needed. 2. **Register access.** Use `adev.rreg/wreg(idx)`; they pick BAR vs RSMU indirection themselves. Do **not** hand-roll a `wreg(14)/rreg(15)` window (see the mistake recorded in `dmcub_hwregs.py`). `validate()` now proves the path with a known-value control plus a scratch write/readback, because every DMCUB register reads 0 before initialisation. 3. ~~Backdoor load may be fused off~~ — **resolved: it is blocked** (FINDING-5). 4. **VBIOS window** is a zeroed 64 KB stand-in; no VBIOS command is exercised, but if boot itself reads VBIOS tables this may need the real image. 5. `DMCUB_SCRATCH15` is written 0 (the field is `psp_version & 0x001100FF`), which is what a non-PSP load implies; if the firmware gates behaviour on it, expect boot to differ.