# Hardware validation against an RX 7900 XTX (navi31 / psp_13_0_0) Card driven directly over a USB<->PCIe bridge (`root@192.168.1.200`, tinygrad, no amdgpu binding). Four open questions were taken to silicon. Two produced results, one is **not host-observable**, one needs a build that does not exist yet. Recorded with the boundary of each stated, because "checked on hardware" means nothing without saying what the host can actually see. ## 1. Ring status codes: emulator vs silicon -- 7/9 exact The host's only PSP interface is the GFX ring (`LOAD_TA`, `INVOKE_CMD`, `GET_FW_ATTESTATION`, ...). Submitting commands and reading `resp.status`: | command | hardware | emulator | | | --- | --- | --- | --- | | `GET_FW_ATTESTATION` (0x0f) | `0x0` | `0x0` | match | | invalid `0x00` | `0x100` | `0x100` | match | | invalid `0x55` | `0x100` | `0x100` | match | | invalid `0x99` | `0x100` | `0x100` | match | | invalid `0xfe` | `0x100` | `0x100` | match | | invalid `0x1000` | `0x100` | `0x100` | match | | `INVOKE_CMD` (0x03) | `0x117` | `0x117` | match | | `LOAD_TA` (0x01) | `0x5` | `0x116` | differs | | `UNLOAD_TA` (0x02) | `0x117` | `0x11a` | differs | `0x100` = unknown command id, `0x117` = bad session/TA, `0x5` = bad parameter. **The two differences are NOT reportable as firmware divergence.** They are the only two state-dependent paths in the set: hardware has a live TMR and loaded TAs, while `fuzz_sos_ring`'s harness is a bare sOS with neither. Inputs were equalised (`buf_size=0x400` on both sides, after finding tinygrad leaves it 0) and they still differ, which is consistent with differing *internal* state, not differing code. Equalising that state is what would make the comparison sound. ### Extraction trap worth keeping Three successive attempts to read the emulator's status were wrong, and each produced a confident, entirely fake comparison table: * `R0` after `tee_cmd_dispatch` -- returned `0x206164`, a code address; * ring frame `+20` -- gave a clean `0` for every command, which was **my own input echoed back**. A sentinel (`0xDEADBEEF`) proved sOS never writes there, and would have been reported as "emulator says 0 for everything, hardware says 0x100" -- a fabricated divergence; * the real location is `resp.status` at **offset 864** of the 1024-byte host command buffer, confirmed independently on both sides (ctypes offset on the host, sentinel-overwrite in the emulator). The sentinel is the whole reason this table is trustworthy. Any future emulator-vs-hardware comparison must sentinel the output location first. ## 2. TA versions: our RAS is the card's RAS, byte for byte. The others are not. `/lib/firmware/amdgpu/psp_13_0_0_ta.bin` carries four TAs. Comparing each container body (strip the `0x100` header) against our extracted images: | TA | card version | card body sha256 (head) | matches our image? | | --- | --- | --- | --- | | RAS | 27.0.2.5 | `db2ed53ef8e5c92f8a01...` | **YES, byte-identical to `ras2.elf`** | | HDCP | 23.0.0.63 | `edab4e4f61c67e922f4f...` | no -- ours is `0x7000`, card's `0x6000` | | DTM | 18.0.0.22 | `adbf8c8badaab3e4208c...` | no | | ASD | 33.0.0.217 | `c4c1e5bc9feedccb7fb8...` | no -- ours `0x33000`, card's `0x2f000` | Full RAS hash, both sides: `db2ed53ef8e5c92f8a011219191931cd93a6b473d42470a9dd1d4d2056b58385` This cuts both ways and both directions matter: * **RAS findings apply to this silicon directly.** The TA we have been emulating is bit-for-bit the one this card loads. * **HDCP, DTM and ASD findings are against versions this card does not run.** That is a scope qualification on the largest body of work in the tree (the HDCP fuzzing), and it was not previously stated anywhere. Our `vdiff` directory holds HDCP 23.0.0.49/.70/.82 -- the card runs .63, which we do not have in the tree at all. The card's own HDCP/DTM/ASD bodies are extractable from this blob, which is the cheap way to make the existing harnesses hardware-relevant. ## 3. SYS_DRV RPC opcode 0x3141 -- not decidable from the host The remaining emitter candidate was the host driver. It is architecturally the wrong place: the host speaks GFX ring commands, while `0x3141` is a *TA -> SYS_DRV* RPC internal to the PSP, on a path the host never addresses. Checked anyway. `amdgpu.ko` (28 MB, decompressed -- the first attempt ran `lzma` on a `.zst`, silently searched compressed bytes and returned a meaningless 0) contains the dword 37 times, all in `mov eax,[rax+8]; add eax,0x3141` register-offset arithmetic, none string-like, none an RPC emission. Controls passed (`amdgpu` 11354 hits, `psp` 696). So the static negative from the module is real but does not bear on the question, and the dynamic question is **unanswerable from the host**. The emitter search result stands as recorded in `OBSERVATION-SYSDRV-3141-NULL-PTR-NONZERO-LEN.md`. ## 4. sOS gate semantics -- not host-observable at all SVC permission checks and pointer region validation happen inside the PSP, between a TA and sOS. The host cannot issue an SVC, cannot observe one, and cannot read PSP SRAM. There is no host-side oracle for these, so hardware cannot confirm or refute the `gate.check_ptr` / `gate.check_svc` models. Stated plainly rather than substituting a weaker check and implying it covered them. The same applies to the `0xE575B8` capacity blocker: it is SYS_DRV-internal state. Driving RAS commands over the ring would be *indirect* evidence (success would imply the context gets populated on a real boot), but tinygrad has no `LOAD_TA` path and building one is a real piece of work with a wedge risk. Feasible -- the signed RAS blob is present and matches ours -- but not done. ## Incidental fix `vram_probe.py` reported `PSP heartbeat -> FROZEN` on a perfectly healthy card: it sampled the counter three times back-to-back, and the counter ticks ~132/s (~7.6 ms per tick), so all three reads landed inside one tick. Measured with 500 ms spacing: deltas 68/57/71/69. Patched on the box to space the samples; backup at `vram_probe.py.bak`. A detector that reports a working card as dead invites pointless cold-cycling.