1
0
This commit is contained in:
Aiden
2026-05-27 11:50:10 +10:00
parent 0d099235c5
commit c0304c575c
55 changed files with 26035 additions and 16 deletions

View File

@@ -18,6 +18,7 @@ from .constants import (
from .eeprom_image import write_eeprom_snapshot
from .errors import UnsupportedInstruction
from .memory import MemoryAccess
from .panel import PanelAction, parse_panel_action, resolve_panel_input
from .runner import H8536Emulator
from .uart import UartTiming
@@ -33,6 +34,13 @@ CONNECT_LCD_FRAMES = (
)
WATCH_PCS = {
0x15E0: "main_panel_scanner",
0x1BA0: "panel_f6d4_edge_dispatch",
0x1BF8: "panel_f6db_edge_dispatch",
0x1C0E: "panel_bit_dispatch",
0x1F40: "cam_power_handler",
0x20A1: "call_handler",
0x3E54: "report_queue_enqueue",
0xBB57: "sci1_eri_entry",
0xBB67: "sci1_rxi_entry",
0xBBD6: "rx_checksum_seed",
@@ -56,6 +64,10 @@ WATCH_RANGES = (
)
ACCESS_RANGES = (
(0xF000, 0xF10F, "panel_external_bytes"),
(0xF6D0, 0xF6DF, "panel_shadow_bytes"),
(0xF6E0, 0xF6EF, "panel_previous_shadow_bytes"),
(0xF6F0, 0xF6F3, "panel_dirty_bytes"),
(0xF850, 0xF85D, "tx_staging_or_frame"),
(0xF860, 0xF86D, "rx_validation_or_capture"),
(0xF870, 0xF96F, "report_queue"),
@@ -64,8 +76,12 @@ ACCESS_RANGES = (
(0xFAA2, 0xFAA6, "serial_latches"),
(0xFAF0, 0xFAFF, "lcd_line_buffer"),
(0xE000, 0xE001, "primary_table_index_0000"),
(0xE00E, 0xE00F, "primary_cam_power_index_0007"),
(0xE02A, 0xE02B, "primary_call_index_0015"),
(0xE400, 0xE401, "secondary_table_index_0000"),
(0xE800, 0xE801, "current_table_index_0000"),
(0xE80E, 0xE80F, "current_cam_power_index_0007"),
(0xE82A, 0xE82B, "current_call_index_0015"),
(0xEC00, 0xEC01, "flag_table_index_0000"),
(0xE000, 0xE3FF, "primary_table_E000"),
(0xE400, 0xE7FF, "secondary_table_E400"),
@@ -77,6 +93,12 @@ ACCESS_RANGES = (
)
STATE_BYTES = {
0xF6D4: "panel_shadow_F6D4_cam_lane",
0xF6DB: "panel_shadow_F6DB_call_lane",
0xF6E4: "panel_previous_F6E4_cam_lane",
0xF6EB: "panel_previous_F6EB_call_lane",
0xF6F2: "panel_dirty_F6F2",
0xF6F3: "panel_dirty_F6F3",
0xF9B0: "queue_head",
0xF9B5: "queue_tail",
0xF9C0: "tx_gate",
@@ -104,12 +126,14 @@ STATE_WORDS = {
0xE000: "E000_index_0000_primary",
0xE004: "E000_index_0002_primary",
0xE008: "E000_index_0004_primary",
0xE00E: "E000_index_0007_cam_power_primary",
0xE024: "E000_index_0012_primary",
0xE026: "E000_index_0013_primary",
0xE02A: "E000_index_0015_primary",
0xE104: "E000_index_0082_primary",
0xE400: "E400_index_0000_secondary",
0xE800: "E800_index_0000_current",
0xE80E: "E800_index_0007_cam_power_current",
0xE804: "E800_index_0002_current",
0xE808: "E800_index_0004_current",
0xE824: "E800_index_0012_current",
@@ -189,6 +213,50 @@ class FrameResult:
return lines
@dataclass(frozen=True)
class PanelActionResult:
action: PanelAction
injection_summary: str
steps: int
stopped_reason: str
new_tx_bytes: bytes
new_tx_frames: list[bytes]
state_before: dict[str, int | str]
state_after: dict[str, int | str]
accesses: list[MemoryAccess]
context: RunContext
def lines(self, index: int) -> list[str]:
lines = [
f"panel_action[{index}]={self.action.label} input={self.action.panel_input.spec}",
f" injection={self.injection_summary}",
f" stopped={self.stopped_reason} steps={self.steps}",
f" new_tx_bytes={format_frame(self.new_tx_bytes) if self.new_tx_bytes else 'none'}",
]
if self.new_tx_frames:
lines.append(" new_tx_frames=" + " | ".join(format_frame(frame) for frame in self.new_tx_frames))
else:
lines.append(" new_tx_frames=none")
lcd_display = self.state_after.get("lcd_display_ascii")
if isinstance(lcd_display, str):
lines.append(f" lcd_display={lcd_display!r}")
state_changes = _state_change_lines(self.state_before, self.state_after)
if state_changes:
lines.append(" state_changes:")
lines.extend(f" {line}" for line in state_changes)
pc_lines = _pc_hit_lines(self.context)
if pc_lines:
lines.append(" pc_hits:")
lines.extend(f" {line}" for line in pc_lines)
access_lines = _access_lines(self.accesses)
if access_lines:
lines.append(" interesting_accesses:")
lines.extend(f" {line}" for line in access_lines)
if self.context.unsupported:
lines.append(f" unsupported={self.context.unsupported}")
return lines
def parse_frame(text: str) -> bytes:
normalized = text.strip().replace(",", " ").replace(":", " ").replace("-", " ").replace("_", " ")
parts = normalized.split()
@@ -209,6 +277,27 @@ def parse_frame(text: str) -> bytes:
return bytes(values)
def parse_panel_action_arg(text: str) -> PanelAction:
try:
return parse_panel_action(text)
except ValueError as exc:
raise argparse.ArgumentTypeError(str(exc)) from exc
def parse_panel_press_arg(text: str) -> PanelAction:
try:
return PanelAction(resolve_panel_input(text), pressed=True, raw=text)
except ValueError as exc:
raise argparse.ArgumentTypeError(str(exc)) from exc
def parse_panel_release_arg(text: str) -> PanelAction:
try:
return PanelAction(resolve_panel_input(text), pressed=False, raw=text)
except ValueError as exc:
raise argparse.ArgumentTypeError(str(exc)) from exc
def frame_checksum(data: bytes) -> int:
checksum = CHECKSUM_SEED
for value in data[: FRAME_LENGTH - 1]:
@@ -318,6 +407,11 @@ def build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument("--per-byte-steps", type=int, default=5_000, help="polite mode byte-consume limit, or UART mode step limit between byte arrivals")
parser.add_argument("--post-frame-steps", type=int, default=80_000, help="maximum steps after a full injected frame")
parser.add_argument("--post-frame-ms", type=int, help="run this many emulated milliseconds after each injected frame")
parser.add_argument("--panel", action="append", type=parse_panel_action_arg, default=[], help="synthetic panel action, e.g. cam-power, call=release, or F6D4.3=press; preserves order across repeated --panel uses")
parser.add_argument("--panel-press", action="append", type=parse_panel_press_arg, default=[], help="synthetic panel press alias/spec, e.g. cam-power, call, or F6D4.3")
parser.add_argument("--panel-release", action="append", type=parse_panel_release_arg, default=[], help="synthetic panel release alias/spec, e.g. call or F6DB.5")
parser.add_argument("--post-panel-steps", type=int, default=120_000, help="maximum steps after each synthetic panel action")
parser.add_argument("--post-panel-ms", type=int, help="run this many emulated milliseconds after each synthetic panel action")
parser.add_argument("--wait-heartbeats", type=int, default=0, help="wait for this many heartbeat frames before injecting the first host frame")
parser.add_argument("--wait-heartbeat-steps", type=int, default=1_500_000, help="maximum steps while waiting for pre-injection heartbeat frames")
parser.add_argument("--uart-timing", action="store_true", help="inject frame bytes at real UART inter-byte timing instead of waiting for RDRF consumption")
@@ -347,8 +441,9 @@ def main(argv: list[str] | None = None) -> int:
frames = list(args.frames)
if args.preset == "connect-lcd":
frames.extend(CONNECT_LCD_FRAMES)
if not frames:
raise SystemExit("pass at least one frame or use --preset connect-lcd")
panel_actions = [*args.panel, *args.panel_press, *args.panel_release]
if not frames and not panel_actions:
raise SystemExit("pass at least one frame, use --preset connect-lcd, or add --panel/--panel-press")
rom_path, emulator, boot_summary, results = run_rx_probe(
frames,
@@ -384,6 +479,19 @@ def main(argv: list[str] | None = None) -> int:
for index, result in enumerate(results):
for line in result.lines(index):
print(line)
panel_results = [
_run_panel_action(
emulator,
action,
post_panel_steps=args.post_panel_steps,
post_panel_ms=args.post_panel_ms,
stop_after_tx_frame=not args.keep_listening,
)
for action in panel_actions
]
for index, result in enumerate(panel_results):
for line in result.lines(index):
print(line)
print("total_tx_frames=" + " | ".join(format_frame(frame) for frame in emulator.sci1.tx_frames))
eeprom_writes = emulator.memory.p9_bus.x24164_bus.write_log_lines(limit=80)
if eeprom_writes:
@@ -484,6 +592,49 @@ def _run_frame(
)
def _run_panel_action(
emulator: H8536Emulator,
action: PanelAction,
*,
post_panel_steps: int,
post_panel_ms: int | None,
stop_after_tx_frame: bool,
) -> PanelActionResult:
state_before = _state_snapshot(emulator)
log_start = len(emulator.memory.access_log)
tx_byte_start = len(emulator.sci1.tx_bytes)
tx_frame_start = len(emulator.sci1.tx_frames)
injection = emulator.inject_panel_input(action.panel_input, pressed=action.pressed)
context = RunContext()
def post_predicate(inner: H8536Emulator) -> bool:
if not stop_after_tx_frame:
return False
return any(frame != HEARTBEAT_FRAME for frame in inner.sci1.tx_frames[tx_frame_start:])
if post_panel_ms is not None:
steps, reason = _run_cycles_for_ms(emulator, post_panel_ms, context)
stopped_reason = reason
else:
steps, reason = _run_until(emulator, post_panel_steps, post_predicate, context)
stopped_reason = "non_heartbeat_tx_frame" if reason == "predicate" and stop_after_tx_frame else reason
log_end = len(emulator.memory.access_log)
state_after = _state_snapshot(emulator)
return PanelActionResult(
action=action,
injection_summary=injection.summary(),
steps=steps,
stopped_reason=stopped_reason,
new_tx_bytes=bytes(emulator.sci1.tx_bytes[tx_byte_start:]),
new_tx_frames=list(emulator.sci1.tx_frames[tx_frame_start:]),
state_before=state_before,
state_after=state_after,
accesses=emulator.memory.access_log[log_start:log_end],
context=context,
)
def _inject_frame_uart_timed(
emulator: H8536Emulator,
frame: bytes,
@@ -672,11 +823,15 @@ def _parse_byte(text: str) -> int:
__all__ = [
"CONNECT_LCD_FRAMES",
"PanelActionResult",
"format_frame",
"frame_checksum",
"frame_checksum_ok",
"main",
"parse_frame",
"parse_panel_action_arg",
"parse_panel_press_arg",
"parse_panel_release_arg",
"run_rx_probe",
"UartTiming",
]