aboutsummaryrefslogtreecommitdiffstats

F32 (RLC / SDMA) — the tree's oldest blocker is lifted

Evidence class [ST]. Tool: f32_audit.py (offline --selftest), built on fail0verflow's radeon-tools F32 disassembler (fetched, not vendored — see below).

What was blocked, and why this changes it

psp_out/FIRMWARE-COVERAGE-MATRIX.md carried RLC and SDMA as blocked: fixed-width 32-bit words, NOP 0x90000000, and no public disassembler. psp_out/f32_probe.py had verified that rather than inheriting it — no capstone architecture fits, and the best scorer (Thumb, 98%) is meaningless because Thumb decodes almost anything.

fail0verflow's radeon-tools contains f32/f32dis.py, a disassembler for the Radeon microcode processor ("F32", despite being 64-bit capable), written for Sea Islands (CI) CE/ME/MEC/PFP/RLC. Its README says "SDMA uses a related but incompatible variant, so will not work".

Two things had to be checked before trusting any of it on gfx11 hardware that is three generations newer, and one of them contradicts upstream.

The ISA matches — but "percentage decoded" is not the evidence

The decoder is permissive, so raw coverage proves nothing:

blob decoded ret calls resolving in-image
BONAIRE_rlc (CI, tool's own target) — positive control 100.0% 57 100%
BONAIRE_pfp (CI) — positive control 100.0% 3 100%
gfx11 mec (RV64) — negative control 78.4% 0 0%
random bytes — negative control 25.9% 0 n/a

An RV64 image scores 78% decoded. What actually separates real F32 from noise is ret count and whether call targets resolve to in-image labels — both are zero for every negative and high for every positive.

Results on this card's blobs

f32_audit.py info parses the containers (which upstream cannot: it hardcodes a 0x100 skip) and reports per sub-blob:

SDMA — decodes cleanly, contradicting upstream's caveat

blob      offset    size      $PS1   insns   unk   ret  calls-ok  verdict
TH0       0x100     0x4400    True    4096   107    61    100.0%  F32 CODE (base 0x0)
TH1       0x4500    0x4200    True    4096   106    71    100.0%  F32 CODE (base 0x0)

Upstream's "SDMA will not work" holds for CI-era SDMA; the gfx11 SDMA threads decode with every call target resolving and 61/71 returns. Sample from TH0's entry:

    nop
    mov r5, #0x107f
_start_0:
    ldw r8, reg[r0, #0x5980]     ; poll a register
    cbz r8, _start_1
    bl  _PKT_0xf00_44
    b   _start_0                 ; back-edge
_start_1:
    ldw r5, reg[r0, #0x4af8]
    lsr r6, r5, #30              ; extract a bit field
    and r6, r6, #0x1
    cbz r6, _start_2

RLC — three code blobs, and a load base that had to be recovered

blob      offset    size      $PS1   insns   unk   ret  calls-ok  verdict
RLC_G     0x100     0x6200    True    6144   187   191     99.3%  F32 CODE (base 0x2000)
IRAM      0xfc20    0x10200   True   16384  6917     0    100.0%  data (no returns; 42% undecodable)
DRAM      0x1fe20   0x8200    True    8192   635     0    100.0%  data (no returns; 8% undecodable)
RLCP      0x28020   0x2200    True    2048    43    15     98.8%  F32 CODE (base 0x0)
RLCV      0x2a220   0x3200    True    3072    96    98     99.2%  F32 CODE (base 0x0)
SRL_GPM   0x6300    0xa00     True       0     0     0              empty
SRL_SRM   0x6d00    0x8f20    True    8192   258     0     81.8%  data (no returns; 3% undecodable)

RLC_G is loaded at word address 0x2000. Disassembled at base 0 it looks broken — 915 branches point outside the image. Their targets span 0x2004..0x37ba, which is exactly the blob's own size shifted by 0x2000, and subtracting that puts 100% of them in range. Upstream hides this: its loc() prints the literal string True for an out-of-range target, so the giveaway is invisible. One-hunk fix in f32dis-loc.patch.

IRAM, DRAM and SRL_SRM have no return instruction anywhere, which is the discriminator — they are data (register save/restore lists), matching their names. Stray "calls" there are data words that happen to decode as branches, which is exactly why call count alone is not used for classification.

What is NOT established

  • The register namespace is unmapped. reg[r0, #0x4a14] and friends are the microengine's own index space, not the driver's dword offsets: only 6 of 54 distinct indices appear in gc_11_0_0_offset.h (11%, i.e. coincidence), and a search over all deltas in ±0x2000 peaks at 9%. Until this is resolved, register accesses in the listing cannot be named — unlike DMCUB, where dmcub_regmap.py validated the register-address table semantically.
  • The opcode table is CI-era. 2.6–3% of words in the gfx11 code blobs decode as unknown (dw 0x...). Some of that is embedded data/jump tables, but some is probably gfx11 opcodes the CI-era table lacks, and the SDMA variant may differ further. Treat the listing as a strong draft, not ground truth.
  • No security audit has been done. This establishes readability only. Nothing here looks for bugs yet.

Why this matters next

SDMA is the highest-value target now readable: it is a DMA engine driven by host-supplied packets from rings the driver writes, i.e. exactly the shape that produced DMCUB FINDING-1/2 and the DCN4 LSDMA candidate (FINDING-9). It is also loadable on this card (SDMA_UCODE_TH0/TH1 are 2 of the 37 accepted types), so unlike FINDING-9 anything found here is testable on the silicon we have. RLC matters for a different reason: it owns power/clock-gating state and the register save/restore lists.

Reproducing

./fetch_ref.sh                                   # clones radeon-tools, applies our patch
./f32_audit.py --selftest                        # controls
./f32_audit.py info ../psp_out/gc_11_0_0_rlc.bin
./f32_audit.py dis  ../psp_out/sdma_6_0_0.bin --blob TH0 | less

Upstream is not vendored: the repository has no LICENSE file and f32dis.py no license header, so it is fetched into the gitignored ref/ like dmcub/ref/, pinned to commit e2eb366, with our change kept as f32dis-loc.patch.