1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
# MES (Micro Engine Scheduler) — first-pass audit
Evidence class **[ST]** static, plus one **[HW]** loadability check on the card.
Target: `fw/gc_11_0_0_mes_2.bin` and `gc_11_0_0_mes1.bin` (gfx11 / Navi 31),
with `gc_11_0_0_pfp.bin`, `gc_11_0_0_me.bin` and the in-tree `mec_11_0_0.bin`
alongside as controls. Tool: `mes_audit.py` (needs capstone RISC-V;
`/home/user/.venvs/cs6/bin/python`).
## Why it was a gap
MES is the queue scheduler the driver talks to with API packets
(`mes_v11_api_def.h`), i.e. it has the same *shape* of host-reachable command
surface as the DMCUB inbox — and this tree had never examined it. The ISA map in
`psp_out/FIRMWARE-INVENTORY.md` classified "MEC/MES" as RISC-V from a dword
histogram, but no MES image was even in the corpus and no code was ever read.
**It is loadable on this card [HW]:** the PSP accepts the MES ucode under
`GFX_FW_TYPE_CP_MES` (33) — `RS64_MES` (76) is refused with 0xffff0006, i.e. not a
supported type on this ASIC. Separately, gfx11 pulls PFP/ME/MES in through PSP
**RLC autoload** from the TOC rather than per-type `LOAD_IP_FW`, which is why the
tinygrad AM driver's load list shows only MEC.
## Container (verified)
```
0x000 common_firmware_header + mes_firmware_header_v1_0 (header_size 0x48)
0x100 payload; its first 0x100 bytes are a nested PSP header ($PS1 at +0x10)
0x200 ucode image, size mes_ucode_size_bytes
-> the first 0x2000 bytes are zero and mes_uc_start_addr = 0xf0005000,
so the image base is 0xf0003000 and code starts at file 0x2200
+ data image at mes_ucode_data_offset_bytes -> mes_data_start_addr 0xf0100000
```
The **nested `$PS1` header is not a DMCUB quirk** — it is present identically in
`mes`, `mes1`, `mes_2`, `mec`, `pfp`, `me` and `imu`. Any offset derived from a
payload without skipping it is 0x100 low, which is exactly the error corrected in
`dmcub/AUDIT-DMCUB-02.md`.
## ISA and inventory
| image | ucode | RV64 decode | fn prologues | indirect sites |
| --- | --- | --- | --- | --- |
| `gc_11_0_0_mes_2.bin` | 0x1f910 | 92.1% | 257 | 18 |
| `gc_11_0_0_mes1.bin` (KIQ) | 0x15c40 | — | 67 | 8 |
| `gc_11_0_0_pfp.bin` | 0x169a0 | 90.4% | 15 | — |
| `gc_11_0_0_me.bin` | 0x2adf0 | 95.1% | 7 | — |
| `mec_11_0_0.bin` | 0x41300 | 89.5% | 46 | — |
Confirmed **RV64** by real decode, not by histogram: the first instruction at
`mes_uc_start_addr` is a textbook prologue —
```
f0005000 130101f1 addi sp, sp, -0xf0
f0005004 233c910c sd s1, 0xd8(sp)
f0005008 9b042000 addiw s1, zero, 2
```
PFP/ME/MEC share the load address `0x3000` and are also RV64; their low prologue
counts reflect flat state-machine style code, not a decode failure (90-95%
coverage).
## Command dispatch — all bounded
`mes_audit.py <img> dispatch` recovers every indirect jump/call with its table and
guard. Three are table dispatches of the kind the API packet handler compiles to,
and each is bounds-checked before the `jr`:
**Superseded in part by `AUDIT-MES-02-API.md`:** the three "tables" are one adjacent
array read at three bases, and the attempt to map table entries to named opcodes was
retracted after the entries turned out to be mid-block fragments. The *bounds* below
are direct reads of the code and stand; the "36 handlers" framing does not.
| site | table | entries | bound |
| --- | --- | --- | --- |
| `0xf0008aa4` | `0xf0100000` | 36 | `li a5,4; bltu a5,s1` → index ≤ 4 |
| `0xf001919c` | `0xf0100014` | 31 | `li a4,0xa; bltu a4,a5` → index ≤ 10 |
| `0xf00196b0` | `0xf0100040` | 20 | index ≤ 19 |
The canonical one reads the opcode straight out of the host packet and bounds it:
```
f0019178 lw a5, 4(s0) ; opcode from the API packet
f001917c li a4, 0xa
f0019180 bltu a4, a5, 0xf001961c ; opcode > 10 -> default
f0019188 lui a4, 0xf0100 ; addi a4, a4, 0x14
f0019190 slli a5, a5, 2 ; add ; lw a5, 0(a5)
f001919c jr a5
```
Of the 18 indirect sites, 17 have a guard in the preceding window. The one that
does not (`0xf0021d10`) is **not** a command dispatch: it is a PC-relative
computed goto (`auipc t0,0; add a3,a3,t0; jalr -0x68(a3)`) inside an unrolled
byte-splat/`memset` routine, with the index derived from a length. Recorded as a
library artefact, not a finding.
## What is *not* audited here
This is a first pass: container, ISA, inventory and dispatch bounds only. The part
that matters for security is untouched:
* **API packet field handling.** MES API packets carry host-supplied 64-bit
addresses (queue base, doorbell, rptr/wptr addresses, HQD state) and counts.
Nothing here checks whether those are validated — that is the real equivalent of
the DMCUB work and the obvious next step.
* No copy-length audit (MES's byte-copy routines were not identified).
* No per-opcode handler audit behind the three dispatch tables.
* `mes1` (KIQ) only counted, not read.
* PFP/ME/MEC: classified and container-parsed only.
Emulation is plausible here in a way it was not for DMUB: RV64 is supported by
both Unicorn and QEMU, so a `psp_emu`-style harness for MES would not need a new
CPU model.
|