aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/probe_tmr_base_sweep.py
blob: b14022b3c7829b200d7fc315520396a803a7f7d9 (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
"""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')))