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:
R0aftertee_cmd_dispatch-- returned0x206164, a code address;- ring frame
+20-- gave a clean0for 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.statusat 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
vdiffdirectory 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.
