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
|
"""TMR base sweep on 0.49.0.51: infer what the PSP 0xFFFD_xxxxxxxx aperture
contains at the firmware-DMA destinations.
SETUP_TMR on .51 (guard absent) DMAs SPI-ROM firmware regions to base+offset
(offsets ~0x700000..0x900000+). This probes ONE base per boot (SETUP_TMR is
one-shot: qword_E264A8!=0 short-circuits later calls), driven by a shell loop
that power-cycles between bases.
Signal per base:
* SETUP_TMR status: 0x0 = all region DMAs succeeded + SVC 0xA1 committed
error = a spirom_xfer rejected/failed (dest not writable
as expected -> likely live register space)
TIMEOUT= PSP hung mid-DMA (dest was critical)
* sOS alive immediately after
* elapsed time (a DMA into register space may behave differently)
SAFETY: bases here stay BELOW the 0xFFFD_FC000000 register block. The firmware
footprint is ~10 MB (base .. base+0xA00000), so base <= 0xFFFD_FB000000 keeps the
writes under FC000000. The FC register block itself is deliberately NOT swept
(brick risk). base is chosen via --base=HEX; the shell loop supplies safe values.
usage: python3 probe_tmr_base_sweep.py --fw51 --base=0x0000FFFDFB000000
(run psp_recover.py FIRST each time)
"""
import ctypes, sys, time
import tinygrad.runtime.support.am.amdev as amdev
from tinygrad.runtime.autogen.am import am
from tinygrad.helpers import data64
FW51 = '--fw51' in sys.argv
BASE = None
for a in sys.argv:
if a.startswith('--base='):
BASE = int(a.split('=', 1)[1], 0)
assert BASE is not None, "need --base=HEX"
# hard safety rail: refuse bases whose 10 MB firmware footprint reaches the
# 0xFFFD_FC000000 register block, unless explicitly forced.
FOOTPRINT = 0xA00000
if '--force' not in sys.argv:
lo = BASE & 0xFFFFFFFF
if 0xFFFD00000000 <= BASE < 0xFFFE00000000 and lo + FOOTPRINT > 0xFC000000:
print("REFUSED: base 0x%x footprint reaches the FC000000 register block "
"(brick risk). Use --force to override." % BASE)
raise SystemExit(2)
if FW51:
d51 = open('/tmp/lfw/36934f24c008.bin', 'rb').read()
_of = amdev.fetch_fw
amdev.fetch_fw = lambda p, n, s: d51 if n == 'psp_13_0_0_sos.bin' else _of(p, n, s)
import tinygrad.runtime.support.am.ip as ip
_orig = ip.AM_PSP._tmr_load_cmd
RES = {}
def hooked(self):
v = self.adev.reg(f"{self.reg_pref}_58").read()
RES['fw'] = "%d.%d.%d.%d" % (v >> 24 & 0xff, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff)
cmd = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_SETUP_TMR)
hi, lo = data64(BASE)
cmd.cmd.cmd_setup_tmr.buf_phy_addr_hi, cmd.cmd.cmd_setup_tmr.buf_phy_addr_lo = hi, lo
shi, slo = data64(self.adev.paddr2xgmi(self.tmr_paddr) if self.tmr_paddr else 0)
cmd.cmd.cmd_setup_tmr.system_phy_addr_hi, cmd.cmd.cmd_setup_tmr.system_phy_addr_lo = shi, slo
cmd.cmd.cmd_setup_tmr.bitfield.virt_phy_addr = 1
cmd.cmd.cmd_setup_tmr.buf_size = self.tmr_size
SZ = ctypes.sizeof(am.struct_psp_gfx_cmd_resp)
t0 = time.time()
try:
self._ring_submit(cmd); RES['status'] = 0
except RuntimeError:
RES['status'] = int.from_bytes(bytes(self.adev.vram.view(self.cmd_paddr, SZ)[:])[864:868], "little")
except TimeoutError:
RES['status'] = -1
RES['dt'] = time.time() - t0
RES['alive'] = self.adev.reg(f"{self.reg_pref}_81").read() != 0
return cmd # do NOT run the real one; report and stop
ip.AM_PSP._tmr_load_cmd = hooked
print("base=0x%016x" % BASE)
try:
from tinygrad import Device
Device["AMD"]
except Exception as e:
RES.setdefault('initerr', type(e).__name__)
st = RES.get('status')
sts = 'TIMEOUT' if st == -1 else (hex(st) if st is not None else 'n/a')
print(" fw=%-10s status=%-10s dt=%.3fs alive=%s initerr=%s"
% (RES.get('fw'), sts, RES.get('dt', 0), RES.get('alive'), RES.get('initerr')))
|