diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index ba4bddf2..6dc3eed6 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -29,6 +29,8 @@ set(PSX_SHARED_SOURCES src/psx_oracle_cmds.c src/psx_cycles.c src/starvation_ring.c + src/card_read_summary.c + src/card_data_writes.c ) # ---- Native target: recompiled BIOS, stub interpreter ---- diff --git a/runtime/include/card_data_writes.h b/runtime/include/card_data_writes.h new file mode 100644 index 00000000..1fdc182f --- /dev/null +++ b/runtime/include/card_data_writes.h @@ -0,0 +1,70 @@ +/* + * card_data_writes.h — always-on capture of where the BIOS stores + * incoming SIO data bytes during memory-card READ_DATA. + * + * The chain handler reads each byte from 0x1F801040 (SIO_RX_DATA) and + * stores it into a destination buffer in RAM. We capture (cycle, addr, + * value, mc_state, mc_data_idx, store_pc, func_addr) for every byte + * that lands in RAM during a card data phase. + * + * Mechanism: + * - sio_read(0x1F801040) sets a "pending" flag when mc_state is in + * READ_DATA..READ_END and active_device==DEV_MEMCARD. + * - The next byte/halfword/word RAM write whose new_val matches the + * armed RX value is captured into the ring; the flag is cleared. + * - Non-matching writes between read and store don't disarm us. + * + * Ring is non-evicting up to CARD_DATA_WRITES_CAP, then keeps a + * rolling tail (so we never lose the LATEST writes). For first-32- + * read audit, this gives ~32*128=4096 byte writes — enough to see + * the full destination buffer for several consecutive reads. + */ +#ifndef PSXRECOMP_V4_CARD_DATA_WRITES_H +#define PSXRECOMP_V4_CARD_DATA_WRITES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CARD_DATA_WRITES_CAP 4096 + +typedef struct { + uint64_t seq; /* monotonic across all entries */ + uint64_t psx_cycle_count; + uint32_t addr; /* dest physical RAM address */ + uint32_t store_pc; /* g_debug_last_store_pc when stored */ + uint32_t func_addr; /* g_debug_current_func_addr */ + uint32_t value; /* value written (zero-extended) */ + uint16_t mc_state_at_read; /* mc_state at the SIO read */ + uint8_t mc_data_idx_at_read; /* mc_data_idx at the SIO read (0..127) */ + uint8_t width; /* 1, 2, or 4 */ + uint8_t slot; /* mc_slot at read */ + uint8_t pad0; + uint8_t pad1; + uint8_t pad2; +} CardDataWriteEntry; + +/* Called from sio_read(0x1F801040) inside the MC_READ_DATA..READ_END + * window. Records the byte we just delivered to BIOS so the next + * matching RAM write can be tagged. */ +void card_data_writes_arm(uint8_t value, uint16_t mc_state, + uint8_t mc_data_idx, uint8_t slot); + +/* Called from psx_write_byte/half/word. If armed and value matches + * the armed RX byte, record + disarm. Returns 1 if captured, 0 + * otherwise. */ +int card_data_writes_check(uint32_t phys, uint32_t value, uint8_t width); + +uint32_t card_data_writes_get(const CardDataWriteEntry **buf_out, + uint64_t *total_seq_out, + uint32_t *head_out); + +void card_data_writes_reset(void); + +#ifdef __cplusplus +} +#endif + +#endif /* PSXRECOMP_V4_CARD_DATA_WRITES_H */ diff --git a/runtime/include/card_read_summary.h b/runtime/include/card_read_summary.h new file mode 100644 index 00000000..bd8edfaa --- /dev/null +++ b/runtime/include/card_read_summary.h @@ -0,0 +1,61 @@ +/* + * card_read_summary.h — non-evicting evidence ring for the first 32 + * completed memory-card reads. + * + * The card-txn ring (256 entries, evicting) covers recent activity + * but the long history is gone by the time we audit. This ring is + * fixed-size and STOPS recording after 32 entries, so the FIRST 32 + * successful reads post-CROSS are preserved indefinitely. + * + * Each entry captures the SIO-layer evidence at the MC_READ_END + * transition: slot/cmd/sector/checksum/first 16 bytes/cycle counts/ + * caller function. The destination RAM pointer is filled in later + * by the chain-handler trace (Phase 3). + */ +#ifndef PSXRECOMP_V4_CARD_READ_SUMMARY_H +#define PSXRECOMP_V4_CARD_READ_SUMMARY_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CARD_READ_SUMMARY_CAP 32 +#define CARD_READ_SUMMARY_PEEK 16 /* bytes captured from start of buffer */ + +typedef struct { + uint64_t seq; /* monotonic; first record = 0 */ + uint64_t psx_cycle_count; /* guest cycle clock at completion */ + uint32_t current_func; /* g_debug_current_func_addr at MC_READ_END */ + uint32_t last_store_pc; /* g_debug_last_store_pc at MC_READ_END */ + uint16_t sector; + uint8_t slot; + uint8_t cmd; /* mc_cmd at completion (e.g. 0x52) */ + uint8_t checksum_card; /* simulated card's computed checksum */ + uint8_t data_idx_at_end; /* should be 128 for clean READ_END */ + uint8_t pad0; + uint8_t pad1; + uint32_t dest_ram_addr; /* filled by chain-handler trace; 0 if unknown */ + uint8_t data_peek[CARD_READ_SUMMARY_PEEK]; +} CardReadSummary; + +void card_read_summary_record(uint8_t slot, uint8_t cmd, uint16_t sector, + uint8_t checksum, uint8_t data_idx, + const uint8_t *data128); + +/* Read access for debug_server. Returns count of recorded entries. */ +uint32_t card_read_summary_get(const CardReadSummary **buf_out); + +/* Reset the ring (for clean audit re-runs). */ +void card_read_summary_reset(void); + +/* Late-fill the destination RAM pointer for the most recent record. + * Called by chain-handler trace once the BIOS commits the byte to RAM. */ +void card_read_summary_set_dest(uint32_t dest_addr); + +#ifdef __cplusplus +} +#endif + +#endif /* PSXRECOMP_V4_CARD_READ_SUMMARY_H */ diff --git a/runtime/src/card_data_writes.c b/runtime/src/card_data_writes.c new file mode 100644 index 00000000..38a2c4c6 --- /dev/null +++ b/runtime/src/card_data_writes.c @@ -0,0 +1,73 @@ +/* card_data_writes.c — capture BIOS RAM destination of card data bytes. */ + +#include "card_data_writes.h" +#include "psx_cycles.h" +#include + +extern uint32_t g_debug_current_func_addr; +extern uint32_t g_debug_last_store_pc; + +/* "Armed" byte from the most recent SIO_RX_DATA read inside a card + * data phase. The next matching RAM write captures it. */ +static volatile int s_armed = 0; +static uint8_t s_armed_value = 0; +static uint16_t s_armed_state = 0; +static uint8_t s_armed_idx = 0; +static uint8_t s_armed_slot = 0; + +/* Ring (rolling: keeps last CAP entries; total_seq lets clients see + * how many were dropped). */ +static CardDataWriteEntry s_ring[CARD_DATA_WRITES_CAP]; +static uint64_t s_seq = 0; +static uint32_t s_head = 0; + +void card_data_writes_arm(uint8_t value, uint16_t mc_state, + uint8_t mc_data_idx, uint8_t slot) { + s_armed_value = value; + s_armed_state = mc_state; + s_armed_idx = mc_data_idx; + s_armed_slot = slot; + s_armed = 1; +} + +int card_data_writes_check(uint32_t phys, uint32_t value, uint8_t width) { + if (!s_armed) return 0; + /* Match on the byte value zero-extended. For width==1, compare bottom + * byte. For wider, compare bottom byte of stored value. */ + uint8_t low = (uint8_t)(value & 0xFF); + if (low != s_armed_value) return 0; + + CardDataWriteEntry *e = &s_ring[s_head]; + e->seq = s_seq++; + e->psx_cycle_count = psx_get_cycle_count(); + e->addr = phys; + e->store_pc = g_debug_last_store_pc; + e->func_addr = g_debug_current_func_addr; + e->value = value; + e->mc_state_at_read = s_armed_state; + e->mc_data_idx_at_read = s_armed_idx; + e->width = width; + e->slot = s_armed_slot; + s_head = (s_head + 1) % CARD_DATA_WRITES_CAP; + + s_armed = 0; + return 1; +} + +uint32_t card_data_writes_get(const CardDataWriteEntry **buf_out, + uint64_t *total_seq_out, + uint32_t *head_out) { + if (buf_out) *buf_out = s_ring; + if (total_seq_out) *total_seq_out = s_seq; + if (head_out) *head_out = s_head; + uint32_t avail = (s_seq < CARD_DATA_WRITES_CAP) + ? (uint32_t)s_seq : CARD_DATA_WRITES_CAP; + return avail; +} + +void card_data_writes_reset(void) { + memset(s_ring, 0, sizeof(s_ring)); + s_seq = 0; + s_head = 0; + s_armed = 0; +} diff --git a/runtime/src/card_read_summary.c b/runtime/src/card_read_summary.c new file mode 100644 index 00000000..be11b852 --- /dev/null +++ b/runtime/src/card_read_summary.c @@ -0,0 +1,51 @@ +/* card_read_summary.c — non-evicting first-32 completed card reads. */ + +#include "card_read_summary.h" +#include "psx_cycles.h" +#include + +extern uint32_t g_debug_current_func_addr; +extern uint32_t g_debug_last_store_pc; + +static CardReadSummary s_ring[CARD_READ_SUMMARY_CAP]; +static uint32_t s_count = 0; +static uint64_t s_seq = 0; + +void card_read_summary_record(uint8_t slot, uint8_t cmd, uint16_t sector, + uint8_t checksum, uint8_t data_idx, + const uint8_t *data128) { + if (s_count >= CARD_READ_SUMMARY_CAP) return; + CardReadSummary *e = &s_ring[s_count]; + memset(e, 0, sizeof(*e)); + e->seq = s_seq++; + e->psx_cycle_count = psx_get_cycle_count(); + e->current_func = g_debug_current_func_addr; + e->last_store_pc = g_debug_last_store_pc; + e->slot = slot; + e->cmd = cmd; + e->sector = sector; + e->checksum_card = checksum; + e->data_idx_at_end = data_idx; + e->dest_ram_addr = 0; /* filled by chain trace */ + if (data128) { + memcpy(e->data_peek, data128, CARD_READ_SUMMARY_PEEK); + } + s_count++; +} + +uint32_t card_read_summary_get(const CardReadSummary **buf_out) { + if (buf_out) *buf_out = s_ring; + return s_count; +} + +void card_read_summary_reset(void) { + memset(s_ring, 0, sizeof(s_ring)); + s_count = 0; + s_seq = 0; +} + +void card_read_summary_set_dest(uint32_t dest_addr) { + if (s_count == 0) return; + /* Set on the most recent record (the one that just completed). */ + s_ring[s_count - 1].dest_ram_addr = dest_addr; +} diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index 277bf3b4..1da4cffe 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -15,6 +15,8 @@ #include "memcard.h" #include "interrupts.h" #include "psx_cycles.h" +#include "card_read_summary.h" +#include "card_data_writes.h" #include #include @@ -1736,6 +1738,98 @@ static void handle_card_txn_dump(int id, const char *json) send_fmt("],\"emitted\":%d}\n", emitted); } +static void handle_card_read_summary(int id, const char *json) +{ + (void)json; + const CardReadSummary *buf = NULL; + uint32_t n = card_read_summary_get(&buf); + + char out[16 * 1024]; + int o = snprintf(out, sizeof(out), + "{\"id\":%d,\"ok\":true,\"count\":%u,\"cap\":%d,\"entries\":[", + id, (unsigned)n, CARD_READ_SUMMARY_CAP); + for (uint32_t i = 0; i < n; i++) { + const CardReadSummary *e = &buf[i]; + char peek[2 * CARD_READ_SUMMARY_PEEK + 1]; + int pi = 0; + for (int b = 0; b < CARD_READ_SUMMARY_PEEK; b++) { + pi += snprintf(peek + pi, sizeof(peek) - pi, "%02X", e->data_peek[b]); + } + peek[pi] = 0; + o += snprintf(out + o, sizeof(out) - o, + "%s{\"seq\":%llu,\"cyc\":%llu," + "\"slot\":%u,\"cmd\":\"0x%02X\",\"sector\":%u," + "\"checksum\":\"0x%02X\",\"data_idx\":%u," + "\"current_func\":\"0x%08X\",\"last_store_pc\":\"0x%08X\"," + "\"dest_ram\":\"0x%08X\",\"data_peek\":\"%s\"}", + i == 0 ? "" : ",", + (unsigned long long)e->seq, + (unsigned long long)e->psx_cycle_count, + e->slot, e->cmd, e->sector, + e->checksum_card, e->data_idx_at_end, + e->current_func, e->last_store_pc, + e->dest_ram_addr, peek); + if (o >= (int)sizeof(out) - 512) break; + } + snprintf(out + o, sizeof(out) - o, "]}"); + send_fmt("%s", out); +} + +static void handle_card_read_summary_reset(int id, const char *json) +{ + (void)json; + card_read_summary_reset(); + send_fmt("{\"id\":%d,\"ok\":true}\n", id); +} + +static void handle_card_data_writes(int id, const char *json) +{ + int count = json_get_int(json, "count", 256); + if (count < 1) count = 1; + if (count > CARD_DATA_WRITES_CAP) count = CARD_DATA_WRITES_CAP; + + const CardDataWriteEntry *buf = NULL; + uint64_t total_seq = 0; + uint32_t head = 0; + uint32_t avail = card_data_writes_get(&buf, &total_seq, &head); + if ((uint32_t)count > avail) count = (int)avail; + + char out[64 * 1024]; + int o = snprintf(out, sizeof(out), + "{\"id\":%d,\"ok\":true,\"total_seq\":%llu," + "\"avail\":%u,\"count\":%d,\"entries\":[", + id, (unsigned long long)total_seq, avail, count); + + /* Iterate the OLDEST `count` entries first (reading from + * (head - avail) forward, then taking the last `count`). */ + int start = ((int)head - count + (int)CARD_DATA_WRITES_CAP) % (int)CARD_DATA_WRITES_CAP; + for (int i = 0; i < count; i++) { + int idx = (start + i) % (int)CARD_DATA_WRITES_CAP; + const CardDataWriteEntry *e = &buf[idx]; + o += snprintf(out + o, sizeof(out) - o, + "%s{\"seq\":%llu,\"cyc\":%llu,\"addr\":\"0x%08X\"," + "\"value\":\"0x%08X\",\"width\":%u," + "\"mc_state\":%u,\"mc_idx\":%u,\"slot\":%u," + "\"store_pc\":\"0x%08X\",\"func\":\"0x%08X\"}", + i == 0 ? "" : ",", + (unsigned long long)e->seq, + (unsigned long long)e->psx_cycle_count, + e->addr, e->value, e->width, + e->mc_state_at_read, e->mc_data_idx_at_read, + e->slot, e->store_pc, e->func_addr); + if (o >= (int)sizeof(out) - 512) break; + } + snprintf(out + o, sizeof(out) - o, "]}"); + send_fmt("%s", out); +} + +static void handle_card_data_writes_reset(int id, const char *json) +{ + (void)json; + card_data_writes_reset(); + send_fmt("{\"id\":%d,\"ok\":true}\n", id); +} + static void handle_watch(int id, const char *json) { char addr_str[32]; @@ -2886,6 +2980,10 @@ static const CmdEntry s_commands[] = { { "sio_pc_trace", handle_sio_pc_trace }, { "dirty_ram_stats", handle_dirty_ram_stats }, { "card_txn_dump", handle_card_txn_dump }, + { "card_read_summary", handle_card_read_summary }, + { "card_read_summary_reset", handle_card_read_summary_reset }, + { "card_data_writes", handle_card_data_writes }, + { "card_data_writes_reset", handle_card_data_writes_reset }, { "sio_irq_dump", handle_sio_irq_dump }, { "evcb_snapshot", handle_evcb_snapshot }, { "evcb_walk_dump", handle_evcb_walk_dump }, diff --git a/runtime/src/memory.c b/runtime/src/memory.c index 0a31af9e..914fc9b8 100644 --- a/runtime/src/memory.c +++ b/runtime/src/memory.c @@ -140,6 +140,9 @@ extern void debug_server_trace_write_check(uint32_t phys, uint32_t old_val, uint32_t new_val, uint8_t width); extern void debug_server_trace_mmio_write(uint32_t addr, uint32_t val, uint8_t width); +/* Card-byte destination capture (Phase 3 audit). Always-on. */ +extern int card_data_writes_check(uint32_t phys, uint32_t value, uint8_t width); + static inline uint32_t read_ram_word(uint32_t phys) { return (uint32_t)ram[phys] | ((uint32_t)ram[phys + 1] << 8) @@ -470,6 +473,7 @@ void psx_write_word(uint32_t addr, uint32_t val) { if (phys < RAM_SIZE) { debug_server_trace_write_check(phys, read_ram_word(phys), val, 4); + card_data_writes_check(phys, val, 4); dirty_ram_mark(phys); ram[phys] = (uint8_t)(val); ram[phys + 1] = (uint8_t)(val >> 8); @@ -527,6 +531,7 @@ void psx_write_half(uint32_t addr, uint16_t val) { if (phys < RAM_SIZE) { debug_server_trace_write_check(phys, (uint32_t)read_ram_half(phys), (uint32_t)val, 2); + card_data_writes_check(phys, (uint32_t)val, 2); dirty_ram_mark(phys); ram[phys] = (uint8_t)(val); ram[phys + 1] = (uint8_t)(val >> 8); @@ -576,6 +581,7 @@ void psx_write_byte(uint32_t addr, uint8_t val) { if (phys < RAM_SIZE) { debug_server_trace_write_check(phys, (uint32_t)ram[phys], (uint32_t)val, 1); + card_data_writes_check(phys, (uint32_t)val, 1); dirty_ram_mark(phys); ram[phys] = val; return; diff --git a/runtime/src/sio.c b/runtime/src/sio.c index a5bf375c..cd3ea6d5 100644 --- a/runtime/src/sio.c +++ b/runtime/src/sio.c @@ -708,15 +708,23 @@ static void mc_process_byte(uint8_t tx_byte) { sio_stat |= SIO_STAT_ACK; break; - case MC_READ_END: + case MC_READ_END: { mc_state = MC_IDLE; sio_mc_read_done++; + extern void card_read_summary_record(uint8_t slot, uint8_t cmd, + uint16_t sector, + uint8_t checksum, + uint8_t data_idx, + const uint8_t *data128); + card_read_summary_record(mc_slot, mc_cmd, mc_sector, + mc_checksum, mc_data_idx, mc_data); if (mc_sector < MEMCARD_SECTORS) { sio_rx_data = 0x47; /* 'G' = Good */ } else { sio_rx_data = 0xFF; } break; + } /* ---- WRITE states ---- */ case MC_WRITE_DATA: @@ -966,6 +974,15 @@ uint32_t sio_read(uint32_t addr) { uint8_t b = sio_rx_data; sio_stat &= ~SIO_STAT_RX_RDY; sr_record(SR_EVT_RX_DATA_READ, 0, b); + if (active_device == DEV_MEMCARD && + mc_state >= MC_READ_DATA && mc_state <= MC_READ_END) { + extern void card_data_writes_arm(uint8_t value, + uint16_t mc_state, + uint8_t mc_data_idx, + uint8_t slot); + card_data_writes_arm(b, (uint16_t)mc_state, + mc_data_idx, (uint8_t)mc_slot); + } return b; } diff --git a/tools/peek_cards.py b/tools/peek_cards.py new file mode 100644 index 00000000..6dbac078 --- /dev/null +++ b/tools/peek_cards.py @@ -0,0 +1,7 @@ +"""Print first 16 bytes of each on-disk card image.""" +for path in ('dummy.0.mcr', 'dummy.1.mcr', 'card1.mcd', 'card2.mcd'): + try: + d = open(path, 'rb').read()[:16] + print(f'{path:>15}: ' + ' '.join(f'{b:02X}' for b in d)) + except Exception as ex: + print(f'{path}: ERR {ex}') diff --git a/tools/probe_card_reads.py b/tools/probe_card_reads.py new file mode 100644 index 00000000..c3cd8152 --- /dev/null +++ b/tools/probe_card_reads.py @@ -0,0 +1,41 @@ +"""Dump the card-read summary ring (first 32 successful reads, non-evicting).""" +import socket, json, sys + +host, port = '127.0.0.1', 4370 +s = socket.create_connection((host, port)) +s.settimeout(3.0) +s.sendall(json.dumps({'id': 1, 'cmd': 'card_read_summary'}).encode() + b'\n') + +buf = b'' +while True: + try: + chunk = s.recv(65536) + except socket.timeout: + break + if not chunk: + break + buf += chunk + depth = 0; in_str = False; esc = False + for c in buf: + if esc: + esc = False; continue + if c == 0x5C: + esc = True; continue + if c == 0x22: + in_str = not in_str; continue + if in_str: + continue + if c == 0x7B: + depth += 1 + elif c == 0x7D: + depth -= 1 + if depth == 0 and buf.strip(): + break +s.close() + +obj = json.loads(buf.decode()) +print(f"# count={obj['count']} cap={obj['cap']}") +for e in obj.get('entries', []): + print(f"#{e['seq']:>2} cyc={e['cyc']:>14} slot={e['slot']} cmd={e['cmd']} sec={e['sector']:>5} " + f"chk={e['checksum']} idx={e['data_idx']:>3} func={e['current_func']} pc={e['last_store_pc']} " + f"dest={e['dest_ram']} peek={e['data_peek']}") diff --git a/tools/probe_card_writes.py b/tools/probe_card_writes.py new file mode 100644 index 00000000..21b3620c --- /dev/null +++ b/tools/probe_card_writes.py @@ -0,0 +1,73 @@ +"""Dump the card-data-writes ring (BIOS RAM destinations of incoming card bytes). + +Each entry: (seq, cyc, addr, value, width, mc_state, mc_idx, slot, store_pc, func). +The mc_state values: 8=READ_DATA, 9=READ_CHK, 10=READ_END (or whatever your enum says). +mc_idx is 0..127 — the offset within the 128-byte sector that this byte represents. +""" +import socket, json, sys + +count = int(sys.argv[1]) if len(sys.argv) > 1 else 1024 +host, port = '127.0.0.1', 4370 +s = socket.create_connection((host, port)) +s.settimeout(3.0) +s.sendall(json.dumps({'id': 1, 'cmd': 'card_data_writes', 'count': count}).encode() + b'\n') + +buf = b'' +while True: + try: + chunk = s.recv(65536) + except socket.timeout: + break + if not chunk: + break + buf += chunk + depth = 0; in_str = False; esc = False + for c in buf: + if esc: + esc = False; continue + if c == 0x5C: + esc = True; continue + if c == 0x22: + in_str = not in_str; continue + if in_str: + continue + if c == 0x7B: + depth += 1 + elif c == 0x7D: + depth -= 1 + if depth == 0 and buf.strip(): + break +s.close() + +obj = json.loads(buf.decode()) +print(f"# total_seq={obj['total_seq']} avail={obj['avail']} count={obj['count']}") + +# Group consecutive entries by destination buffer base. +entries = obj.get('entries', []) +if not entries: + print("# no entries") + sys.exit(0) + +# Show summary: dest base inferred from each run of mc_idx 0..127. +i = 0 +while i < len(entries): + run = [entries[i]] + j = i + 1 + while j < len(entries): + # consecutive idx OR new run starts at idx 0 + prev = run[-1] + cur = entries[j] + if cur['mc_idx'] == prev['mc_idx'] + 1 and cur['slot'] == prev['slot']: + run.append(cur) + j += 1 + else: + break + base = run[0] + end = run[-1] + base_addr = int(base['addr'], 16) + end_addr = int(end['addr'], 16) + print(f"slot={base['slot']} idx_range=[{base['mc_idx']}..{end['mc_idx']}] " + f"addr=0x{base_addr:08X}..0x{end_addr:08X} ({len(run)} bytes) " + f"first_func={base['func']} first_pc={base['store_pc']} " + f"first_val={base['value']}") + i = j diff --git a/tools/probe_card_writes_raw.py b/tools/probe_card_writes_raw.py new file mode 100644 index 00000000..1b8d1c40 --- /dev/null +++ b/tools/probe_card_writes_raw.py @@ -0,0 +1,31 @@ +"""Dump card-data-writes ring entry-by-entry.""" +import socket, json, sys + +count = int(sys.argv[1]) if len(sys.argv) > 1 else 200 +host, port = '127.0.0.1', 4370 +s = socket.create_connection((host, port)) +s.settimeout(3.0) +s.sendall(json.dumps({'id': 1, 'cmd': 'card_data_writes', 'count': count}).encode() + b'\n') +buf = b'' +while True: + try: chunk = s.recv(65536) + except socket.timeout: break + if not chunk: break + buf += chunk + depth = 0; in_str = False; esc = False + for c in buf: + if esc: esc = False; continue + if c == 0x5C: esc = True; continue + if c == 0x22: in_str = not in_str; continue + if in_str: continue + if c == 0x7B: depth += 1 + elif c == 0x7D: depth -= 1 + if depth == 0 and buf.strip(): break +s.close() +obj = json.loads(buf.decode()) +print(f"# total_seq={obj['total_seq']} avail={obj['avail']} count={obj['count']}") +for e in obj.get('entries', []): + val_lo = int(e['value'], 16) & 0xFF + print(f"#{e['seq']:>4} cyc={e['cyc']:>14} slot={e['slot']} idx={e['mc_idx']:>3} " + f"st={e['mc_state']:>2} addr={e['addr']} val=0x{val_lo:02X} w={e['width']} " + f"pc={e['store_pc']} func={e['func']}")