#!/usr/bin/env python3 """Which of the Windows-driver TAs does THIS Navi 31 PSP accept? `fw/AUDIT-WINDOWS-DRIVER-CORPUS.md` extracted 19 PSP module containers from the Windows Adrenalin 22.12.1 package, including ten TA types the linux-firmware bundle never ships (OPM, MERIT, GCP, WSPE, TMZ, AUC, FP, XGMI, RAP, POWERPLAY). Those containers carry `apu_family_id = 0xffffffff`, so nothing in the file says which ASIC they target. The PSP answers that for us: `LOAD_TA` authenticates the container against this part's keys, so the status separates "wrong part / not for this silicon" from "a TA this PSP will actually accept" — without invoking anything. Controls, both required: POS a TA carved from this machine's own `psp_13_0_0_ta.bin` must load (status 0). Without it, an all-reject result would be indistinguishable from a broken harness. NEG a junk buffer must be rejected, so "accept" means something. Any *successful* load allocates a PSP session and this rig has a known session-exhaustion wall (`hw/loadta_ring_exhaustion.py`), so every success is unloaded immediately. usage: PYTHONPATH=/root/psp/tinygrad loadta_windows_sweep.py --dir /root/psp/cont22 """ import argparse import glob import os import struct import sys import time STATUS = {0x00: 'ACCEPTED', 0x34: 'signature verify failed', 0x01: 'invalid cmd', 0x02: 'invalid params'} def main(): ap = argparse.ArgumentParser() ap.add_argument('--dir', default='/root/psp/cont22') ap.add_argument('--start', type=int, default=0, help='batch start index; the PSP hits a session wall around 45 ' 'loads per boot, so sweep in batches with AM_RESET=1 between') ap.add_argument('--count', type=int, default=0, help='batch size (0 = all)') ap.add_argument('--reverse', action='store_true', help='sweep in reverse order: results must not depend on ordering') ap.add_argument('--ta-bin', default='/lib/firmware/amdgpu/psp_13_0_0_ta.bin') args = ap.parse_args() from tinygrad import Device import tinygrad.runtime.autogen.am.am as am sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) adev = Device["AMD"].iface.dev_impl psp = adev.psp cmd_pa = adev.mm.palloc(0x10000, align=0x1000, zero=True, boot=False) mcc = adev.paddr2mc(cmd_pa) app_pa = adev.mm.palloc(0x80000, align=0x1000, zero=True, boot=False) def load_ta(blob): n = (len(blob) + 3) & ~3 # USB4 needs 4-byte aligned size adev.vram.view(app_pa, n)[:] = blob + b'\x00' * (n - len(blob)) adev.gmc.flush_hdp() mc = adev.paddr2mc(app_pa) 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 = mc & 0xFFFFFFFF, mc >> 32 t.app_len = len(blob) t.cmd_buf_phy_addr_lo, t.cmd_buf_phy_addr_hi = mcc & 0xFFFFFFFF, mcc >> 32 t.cmd_buf_len = 0x10000 try: r = psp._ring_submit(c) return r.resp.status & 0xFFFFFFFF, r.resp.session_id except Exception as e: try: return int(str(e).strip().split()[-1]) & 0xFFFFFFFF, None except Exception: return -1, None def unload(sid): """Unload and REPORT the status. The first full-corpus run swallowed this and leaked a session per accepted TA, hitting the PSP session wall after ~45 loads; every later command then returned a counter-like value incrementing by 0x10 instead of a status, and the condition persisted across processes until AM_RESET=1. """ c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA) c.cmd.cmd_unload_ta.session_id = sid try: return psp._ring_submit(c).resp.status & 0xFFFFFFFF except Exception as e: try: return int(str(e).strip().split()[-1]) & 0xFFFFFFFF except Exception: return -1 def show(tag, st, sid, extra=''): name = STATUS.get(st, 'status %#x' % st) print(' %-34s -> %-24s %s%s' % (tag, name, ('sid=%#x' % sid) if st == 0 and sid else '', extra), flush=True) # ---- POSITIVE control: a TA from this machine's own firmware bundle ---- import subprocess tab = args.ta_bin if not os.path.exists(tab) and os.path.exists(tab + '.zst'): subprocess.run(['zstd', '-d', '-f', '-o', '/tmp/ta.bin', tab + '.zst'], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) tab = '/tmp/ta.bin' pos_ok = False if os.path.exists(tab): data = open(tab, 'rb').read() # same carve rules as fw/carve_driver.py pos = 0 while True: m = data.find(b'$PS1', pos) if m < 0: break pos = m + 4 cont = m - 0x10 if cont < 0: continue n = struct.unpack_from(' len(data): continue body = data[cont + 0x100: cont + 0x200 + n] if body[:4] != b'\x00\x02\x00\x00': continue st, sid = load_ta(data[cont:cont + 0x200 + n]) show('POS ctrl: bundle TA @%#x' % cont, st, sid) if st == 0: pos_ok = True if sid: unload(sid) break if not pos_ok: print(' POSITIVE CONTROL FAILED -- a bundle TA did not load, so a') print(' sweep of rejects below would prove nothing. Stopping.') return 1 # ---- NEGATIVE control ---- st, sid = load_ta(b'\xa5' * 0x2000) show('NEG ctrl: junk 0x2000', st, sid) if st == 0: print(' NEGATIVE CONTROL FAILED -- junk accepted. Stopping.') return 1 # ---- the sweep ---- print('\nWindows-driver containers:') accepted = [] prev_unknown = [None] KNOWN_ST = {0x00, 0x34, 0xffff0000, 0xffff000a, 0xffff0006, 0xffff0008} def degraded(st): """Detect the session-wall signature: unknown statuses marching by 0x10.""" if st in KNOWN_ST: prev_unknown[0] = None return False if prev_unknown[0] is not None and st - prev_unknown[0] == 0x10: return True prev_unknown[0] = st return False files = sorted(glob.glob(os.path.join(args.dir, '*.bin')), reverse=args.reverse) if args.count: files = files[args.start:args.start + args.count] elif args.start: files = files[args.start:] for p in files: blob = open(p, 'rb').read() st, sid = load_ta(blob) extra = '' if st == 0: us = unload(sid) extra = ' unload=%#x%s' % (us, '' if us == 0 else ' <-- SESSION LEAK') accepted.append(os.path.basename(p)) show(os.path.basename(p), st, sid, extra) if degraded(st): print('\n ABORT: statuses are marching by 0x10 -- this is the PSP') print(' session wall, not a verdict. Results past this point would be') print(' fabricated. Re-run with AM_RESET=1.') return 2 time.sleep(0.05) print('\nACCEPTED by this PSP: %d %s' % (len(accepted), accepted)) return 0 if __name__ == '__main__': raise SystemExit(main())