aboutsummaryrefslogtreecommitdiffstats

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.