feat(xteink): report and dump guest RAM usage

Adds a 'memory' subcommand and matching web tab that show how much of each
ESP32-C3 RAM window the guest has written, plus raw region dumps via QMP
pmemsave.
This commit is contained in:
2026-07-27 12:39:55 -04:00
parent 09616c2093
commit 6e0e65218a
5 changed files with 105 additions and 1 deletions
+31
View File
@@ -31,6 +31,12 @@ BUTTONS = {
"power": ("/machine/gpio", "input-level[3]", False, True),
}
VARIANTS = ("x3", "x4")
# ESP32-C3 RAM windows. dram and iram alias the same internal SRAM, so their numbers overlap.
MEMORY_REGIONS = {
"dram": (0x3FC80000, 0x60000),
"iram": (0x4037C000, 0x60000 + 16 * 1024),
"rtcram": (0x50000000, 0x2000),
}
def default_state_dir():
@@ -302,6 +308,31 @@ def press(state, name, samples=4, timeout=90, hold_ms=500):
await_samples(state, prop, samples, timeout)
def dump_memory(state, region, output):
base, size = MEMORY_REGIONS[region]
output = Path(output).expanduser().resolve()
output.parent.mkdir(parents=True, exist_ok=True)
qmp_command(state, "pmemsave", {"val": base, "size": size, "filename": str(output)}, timeout=30)
return output
def memory_usage(state):
"""Bytes the guest has written per RAM region.
QEMU zeroes RAM at reset, so a non-zero byte is one the firmware touched. That makes this a
high-water mark rather than live heap usage: freed blocks keep their old contents.
"""
scratch = state / "memory.bin"
rows = []
try:
for region, (base, size) in MEMORY_REGIONS.items():
data = dump_memory(state, region, scratch).read_bytes()
rows.append({"region": region, "base": base, "size": size, "touched": len(data) - data.count(0)})
finally:
scratch.unlink(missing_ok=True)
return rows
def screendump(state, output, settle_ms=0):
# The e-ink panel needs a full refresh cycle before a capture shows the latest frame.
if settle_ms: