<feed xmlns='http://www.w3.org/2005/Atom'>
<title>amd-psp-re.git/mes/hw, branch main</title>
<subtitle>AMD PSP reverse engineering, emulator and vulnerability research</subtitle>
<id>https://git.sl0p.foo/amd-psp-re.git/atom/mes/hw?h=main</id>
<link rel='self' href='https://git.sl0p.foo/amd-psp-re.git/atom/mes/hw?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>
</feed>
