<feed xmlns='http://www.w3.org/2005/Atom'>
<title>amd-psp-re.git/mes, branch main</title>
<subtitle>AMD PSP reverse engineering, emulator and vulnerability research</subtitle>
<id>https://git.sl0p.foo/amd-psp-re.git/atom/mes?h=main</id>
<link rel='self' href='https://git.sl0p.foo/amd-psp-re.git/atom/mes?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/'/>
<updated>2026-09-28T18:22:35Z</updated>
<entry>
<title>MEC/PM4 round 9: stop the KIQ port on cost/benefit, name the cheaper route</title>
<updated>2026-09-28T18:22:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T18:22:35Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=46df799f3997cf30574e61573e81755816317572'/>
<id>urn:sha1:46df799f3997cf30574e61573e81755816317572</id>
<content type='text'>
Ported the last two prerequisites from gfx_v11_0.c: RS64 compute enable turns on
all four pipes together with MEC_HALT=0 (CP_MEC_RS64_CNTL 0x04000000 -&gt;
0x3c000000), and kiq_init_register programs CP_MEC_DOORBELL_RANGE_LOWER/UPPER,
outside which the CP silently ignores doorbells.

Neither closed the gap. With the right slot, CP_HQD_ACTIVE=1, the scheduler
pointed at the queue, all pipes active, the MEC unhalted and the doorbell range
programmed, the queue still does not retire a NOP. The remaining candidate is the
MEC firmware's own per-pipe initialisation, since amdgpu loads MEC microcode per
pipe and the RS64 MEC likely only services pipes initialised at load time --
a substantially larger port.

Cost decides it: each failed attempt leaves an undequeueable HQD and GRBM_STATUS
0xa800302c that only a cold ATX cycle clears, and three have now been spent on
one open question. Stopping the thread.

The cheaper route is recorded for whoever picks it up: MAP_QUEUES sits at 0x3241c
in the validated PM4 table, inside the ~12 KB dispatcher sub_31360, and whether
any path from that label consults a privilege bit is a bounded static reverse
with no hardware cost and no power cycles.
</content>
</entry>
<entry>
<title>MEC/PM4 round 8: port amdgpu's KIQ slot selection; queue activates, still stalls</title>
<updated>2026-09-28T18:09:38Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T18:09:38Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=bc77fa1b47ee906d3775a017a301f7d18e455f62'/>
<id>urn:sha1:bc77fa1b47ee906d3775a017a301f7d18e455f62</id>
<content type='text'>
Continuing round 7's blocker by reading amdgpu's KIQ bring-up and porting it.

Three concrete fixes. The KIQ slot was wrong, not merely unallocated:
amdgpu_gfx_kiq_acquire scans the queue bitmap downward requiring queue == 0, and
gfx11 has num_mec = 1, so ring-&gt;me = mec + 1 = 1. tinygrad hardcodes me=2/pipe=1,
a VF layout, which is exactly why round 7 saw CP_HQD_ACTIVE = 0. The real slot is
me=1 pipe=3 queue=0, reachable through setup_ring's ordinary path at idx=12 so
the MQD is still built by driver code -- CP_HQD_ACTIVE now reads 1. The HIQ
pointer encoding is RLC_CP_SCHEDULERS = (me&lt;&lt;5)|(pipe&lt;&lt;3)|queue|0x80 per
gfx_v11_0_kiq_setting. And because gfx11 is RS64, per-pipe enables live in
CP_MEC_RS64_CNTL rather than CP_MEC_CNTL; it read 0x04000000, only pipe0 active.

Still stalled: with the right slot, an active HQD, the scheduler pointed at it and
pipe3 enabled, CP_HQD_PQ_RPTR stays 0 while the doorbell and CP_HQD_PQ_WPTR_LO
both reach the queue. A pipe-0 queue on the identical code path retires the same
NOP, so the submission mechanism is sound and the gap is pipe-3 specific --
most likely the per-pipe HPD/EOP programming from amdgpu's compute resume.

CP_MEC_RS64_CNTL restored to its original value. MAP_QUEUES remains untested, but
the slot, scheduler encoding and pipe-enable register are now known and verified,
leaving one named piece.
</content>
</entry>
<entry>
<title>MEC/PM4 round 7: HIQ plumbing blocked, blocker characterised precisely</title>
<updated>2026-09-28T14:39:10Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T14:39:10Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=74d6b371f7792d7448e00d2df942777dc8fc63b8'/>
<id>urn:sha1:74d6b371f7792d7448e00d2df942777dc8fc63b8</id>
<content type='text'>
Attempted to stand up a KIQ/HIQ ring to give round 6 its missing control.

Cleared two obstacles: tinygrad's mqd_paddr has 2+is_vf entries while
setup_ring(kiq_xcc=0) indexes 2 (fixed by appending an allocation so the
driver's own MQD code runs unchanged), and the KIQ's doorbell index 0 is
outside the apertures AM enables (re-pointed at MEC_RING0+4, an index the
compute probe had already rung).

Did not clear the third: CP_HQD_ACTIVE reads back 0 after setup_ring, so the CP
rejects the queue and no NOP retires. Root cause is that tinygrad's KIQ path is
VF-only and hardcodes me=2/pipe=1/queue=0, while amdgpu picks them dynamically
via amdgpu_gfx_kiq_acquire() and additionally needs the HPD EOP region, MEC pipe
enablement and the KIQ doorbell aperture -- an amdgpu KIQ port rather than a
patch.

The MAP_QUEUES chain therefore stays open and untested, with no claim in either
direction. It would have been easy to combine round 6's stall with this round's
dead KIQ into "MAP_QUEUES is gated everywhere"; neither result supports that,
because a queue that never activates cannot refuse anything.
</content>
</entry>
<entry>
<title>MEC/PM4 round 6: MAP_QUEUES chain is untested, not closed</title>
<updated>2026-09-28T14:33:50Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T14:33:50Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=d82188b66ce0095269148553b0663cf776e6f1f4'/>
<id>urn:sha1:d82188b66ce0095269148553b0663cf776e6f1f4</id>
<content type='text'>
Round 5 closed the direct route (unprivileged queues cannot write registers), so
the follow-up is the indirect one: use MAP_QUEUES from an unprivileged queue to
map a queue whose MQD sets priv_state=1, then write registers through it. That
would be a user-&gt;kernel chain requiring no memory-safety bug at all.

The test is inconclusive and is recorded that way. MAP_QUEUES stalls the queue
identically at priv_state=0 and priv_state=1, and identically again after seeding
the target MQD with the one the driver had just built. Because the privileged
queue stalls too, the stall is not a privilege effect -- MAP_QUEUES is an
HIQ/KIQ-pipe packet and is not serviced on an ordinary compute ring.

Worth stating because the failure mode is deceptive: a stall looks exactly like
the round-5 privilege fault, and without the priv_state=1 comparison this would
have been written up as "the gate covers MAP_QUEUES too". The probe's verdict
text is corrected to say a stall alone proves nothing.

The chain stays open. Testing it needs HIQ submission with a user-owned ring,
which tinygrad's AM does not expose (setup_ring(kiq_xcc=0) indexes past its
allocated MQD slots) -- a driver-plumbing blocker, not a firmware one.
</content>
</entry>
<entry>
<title>MEC/PM4 round 5 [HW]: the user-&gt;kernel register boundary holds</title>
<updated>2026-09-28T14:31:23Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T14:31:23Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=3093c0cddcd73ed7749b060067f13fcc636c4e6f'/>
<id>urn:sha1:3093c0cddcd73ed7749b060067f13fcc636c4e6f</id>
<content type='text'>
PM4 is submitted by unprivileged user queues and WRITE_DATA(dst_sel=register)
names a memory-mapped register, so an ungated path would be the user-&gt;kernel
primitive none of the DMCUB findings can be.

Tested as a 2x2 on silicon, because either factor alone is uninterpretable:

                  dst=memory        dst=register
  priv_state=0    wrote             refused, queue then faults
  priv_state=1    wrote             wrote, queue alive

The memory column proves the packet encoding, ring, doorbell and submission path
are correct -- without it, "the register did not change" is equally consistent
with a malformed packet, which is exactly what made the first SDMA RB_PRIV=0 run
inconclusive. The priv_state=1 row rules out the other explanation, that
WRITE_DATA-&gt;register is simply unsupported on MEC.

Privilege was changed by live-flipping CP_HQD_PQ_CONTROL (0x8509 -&gt; 0xc0008509)
on an already-running queue, so that bit is the only difference between the two
runs -- the same isolation technique used for SDMA's RB_PRIV.

Verdict: the boundary is real and enforced on gfx11. Across both user-submittable
engines the pattern is identical -- memory confined by the VM, registers gated by
a per-queue privilege bit, and the gate holds. Also fixes the probe's verdict
text, which printed the unprivileged conclusion for a privileged run.
</content>
</entry>
<entry>
<title>MEC/PM4 round 4: correct the dispatch table, refute the length-bound guess</title>
<updated>2026-09-28T14:25:42Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T14:25:42Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=3b017f81e10b7cccce75c941ce2b9c9240afb879'/>
<id>urn:sha1:3b017f81e10b7cccce75c941ce2b9c9240afb879</id>
<content type='text'>
Acting on the synthesis, which put PM4 at the top because it is the only
user-submittable surface left unaudited.

Round 1's table base was 0x100 low -- the same nested-$PS1 error that shifted
every DMCUB address in this tree. At the corrected base 0x415d8 all five of
Round 1's anchor opcodes verify, and 5 of 7 populated slots carry published
PACKET3_* names (MAP_QUEUES, UNMAP_QUEUES, QUERY_STATUS, RUN_LIST,
MAP_PROCESS_VM). At the old base, zero did, which is what makes the naming a
validation rather than a decoration.

That table covers queue management only. Two scans found no second dispatch
structure -- no further (handler, flags) pairs and no plain pointer table that
opcode-indexes onto the enum -- so the remaining packets are dispatched by
compare chains, matching Round 2's reading.

Round 1's suspected per-packet length bound is refuted: MAP_QUEUES carries 2241
where UNMAP/QUERY/RUN_LIST carry 2 and MAP_PROCESS_VM carries 1. Those are not
dword counts for those packets, so the low half is a flags field and the parser's
length validation lives in code.

New tooling (mes/mec_audit.py: table/bounds/taint) taints from the packet data
port 0x6F8(tp) with dataflow that respects both calls and basic blocks. The
block rule was added after the pass produced a striking false lead: a raw packet
dword shifted left 36 into MMIO-aperture bits and used as a store address, which
would have been a host-controlled register write. An unconditional j sits just
before that block, so the base comes from elsewhere; block-awareness removed it
and 16 other sinks. Of the 75 survivors, 27 assemble a 64-bit GPU VA from two
packet dwords and are VM-confined as proven for SDMA.

Still no finding.
</content>
</entry>
<entry>
<title>Audit MES, PFP and ME; settle the IMU ISA as Xtensa</title>
<updated>2026-09-28T13:47:22Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-28T13:47:22Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/amd-psp-re.git/commit/?id=7691d9a3b091eec96ce58bf049955da0f8d6bc65'/>
<id>urn:sha1:7691d9a3b091eec96ce58bf049955da0f8d6bc65</id>
<content type='text'>
MES is the queue scheduler and, like the DMCUB inbox, is driven by host API
packets -- it had never been read here. Confirm RV64 by real decode (92%),
recover the container (code at 0xf0005000, data at 0xf0100000), and audit the
packet surface: the 256-byte stride matches MES_API_MAX, both dispatch sites
are bounds-checked, and 31 of 33 memcpy sites use constant lengths, so DMCUB's
FINDING-1 pattern is absent.

Retract, in place, an earlier identification of the two dispatchers against the
kernel enums. The bounds were exact ABI fits, which felt like confirmation, but
the table entries disassemble as mid-block fragments and epilogues; the
prologue-scan function boundaries behind that model are unreliable on this
image. MESAPI_MISC__WRITE_REG is left explicitly unresolved because the
screening dataflow is not call-clobber aware.

PFP and ME are also RV64 (container-parsed, load addr 0x3000). IMU is
Tensilica Xtensa, correcting this tree's "undetermined": the old call was made
over the whole container, which is dominated by the zero-padded DRAM half.
</content>
</entry>
</feed>
