rose/tools/emu-dec-probe.py
2026-09-11 10:47:51 -04:00

121 lines
4.3 KiB
Python

# tools/emu-dec-probe.py -- in-process DEC capture (no GDB server needed).
#
# Reproduces emu/gdb_decode.gdb semantics from inside renode: on every
# execution of the firmware's touch-conversion decode at 0x01035618, log
# pc/regs/flags/shadow exactly like the gdb recipe's printf:
# DEC pc=%08x r0=%08x r1=%08x r2=%08x r3=%08x r4=%08x flags=%08x sh=%08x,%08x
# where flags = [0x010C5B18], sh = ([0x03FFF140], [0x03FFF144]).
#
# Why not GDB: renode 1.16's GDB stub fatally crashes on stray socket bytes,
# and this sandbox's runtime probes fresh localhost listeners -- any
# StartGdbServer port gets garbage within seconds. A cpu hook has no socket
# and runs at full emulation speed.
#
# Gate: active only when ROSE_DEC_TRACE=1. Log path: ROSE_DEC_LOG (default
# .build/emu/dec-trace.log relative to the renode host cwd -- prefer an
# absolute path). Bounded: first ROSE_DEC_MAX lines (default 200), then only
# a counter line every 10000 hits.
#
# Wire: include from a boot .resc BEFORE `start`, e.g.
# ROSE_EXTRA_INCLUDE=/path/to/emu-dec-probe.py
# or via emu/boot_probe.py --dec-trace.
#
# Manual use (already-running machine, paused):
# include @tools/emu-dec-probe.py
import os
DEC_PC = 0x01035618
FLAGS_ADDR = 0x010C5B18
SHADOW_ADDR = 0x03FFF140
TRANSFER_PTR_ADDR = 0x0183A458
_enabled = os.environ.get("ROSE_DEC_TRACE", "0") == "1"
def _env_int(name, default):
try:
return int(os.environ.get(name, str(default)))
except Exception:
return default
def _install():
# NOTE: keep the default absolute (/tmp); renode's python resolves
# relative paths against its install dir, not the repo. Harness runs
# always export an absolute ROSE_DEC_LOG.
log_path = os.environ.get("ROSE_DEC_LOG", "/tmp/rose_dec_trace.log")
max_lines = _env_int("ROSE_DEC_MAX", 200)
try:
parent = os.path.dirname(os.path.abspath(log_path))
if parent and not os.path.exists(parent):
os.makedirs(parent)
except Exception as e:
print("dec-probe: mkdir failed: %s" % e)
return
try:
log = open(log_path, "w")
log.write("# DEC trace (emu-dec-probe.py)\n")
log.flush()
except Exception as e:
print("dec-probe: open %s failed: %s" % (log_path, e))
return
sysbus = self.Machine["sysbus"]
state = {"n": 0}
def _reg(cpu, n):
try:
return cpu.GetRegister(n).RawValue & 0xFFFFFFFF
except Exception:
return 0
def _rd32(a):
try:
return int(sysbus.ReadDoubleWord(a)) & 0xFFFFFFFF
except Exception:
return 0
def _dec(cpu, _):
state["n"] += 1
n = state["n"]
tptr = _rd32(TRANSFER_PTR_ADDR)
tval0 = _rd32(tptr) if tptr else 0
tval4 = _rd32(tptr + 4) if tptr else 0
# Always log hits that carry a nonzero sample word (the press/lift
# records the driver publishes), even past the bounded cap, so the
# decode of an actual touch is never missed behind boot-turn DEC spam.
if n <= max_lines or tval4 != 0 or tval0 != 0:
log.write(
"DEC pc=%08X r0=%08X r1=%08X r2=%08X r3=%08X r4=%08X "
"flags=%08X sh=%08X,%08X raw=%08X,%08X,%08X,%08X "
"tptr=%08X tval=%08X,%08X\n"
% (_reg(cpu, 15), _reg(cpu, 0), _reg(cpu, 1),
_reg(cpu, 2), _reg(cpu, 3), _reg(cpu, 4),
_rd32(FLAGS_ADDR),
_rd32(SHADOW_ADDR), _rd32(SHADOW_ADDR + 4),
_rd32(SHADOW_ADDR + 6), _rd32(SHADOW_ADDR + 10),
_rd32(SHADOW_ADDR + 14), _rd32(SHADOW_ADDR + 18),
tptr, tval0, tval4))
log.flush()
elif n % 10000 == 0:
log.write("# DEC hits=%d\n" % n)
log.flush()
for cpu in sysbus.GetCPUs():
try:
name = cpu.GetName()
except Exception:
continue
if name.endswith("cpu0"):
cpu.AddHook(DEC_PC, _dec)
print("dec-probe: installed on %s -> %s (max %d)"
% (name, log_path, max_lines))
if _enabled:
try:
_install()
except Exception as e:
print("dec-probe: install failed: %s" % e)
else:
print("dec-probe: disabled (set ROSE_DEC_TRACE=1 to capture DEC tuples)")