mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
0d15e2bd4e
Adds the cycle-paced sio_tick body, the sio_tick_quantum() wrapper, and
the pace_state TCP telemetry handler. Crucial change vs. failed 1.0c:
the dispatch-loop quantum tick is gated by g_sio_timing_active (volatile
int), so on the psx_check_interrupts hot path the cost is one load + one
branch when no SIO time is pending — not a full function call into the
cycle-paced loop.
In 1.0c-v2 the TX path is still synchronous and nothing arms shift_active
or pending_ack, so g_sio_timing_active stays 0. Phase 1.0d will set it
when arming the shifter and clear it when the shifter+buffer+ack are all
empty.
Files:
- sio.h: SIO_MODEL_CYCLE_PACED moved to header (default 1); declares
g_sio_timing_active (extern volatile int) and sio_tick_quantum()
- sio.c: defines g_sio_timing_active=0; adds cycle-paced helpers
(sio_consume_ack_event, sio_fire_ack_irq, sio_handle_shift_complete);
sio_tick gains a cycle-paced loop that runs only if shift_active or
pending_ack is set (currently never), then falls through to the legacy
IRQ countdown body which remains the live IRQ delivery path; adds
sio_get_pace_state(uint64_t out[16]) accessor
- interrupts.c: in psx_check_interrupts !in_exception block, calls
sio_tick_quantum only if g_sio_timing_active is set
- debug_server.c: new pace_state TCP handler exposing the snapshot
Validation against 64e0194 baseline:
- t=20s/30s TCP responsive; pad arm audit 2840/2840 no_ack=0
- 4x CROSS succeeds
- card txn distribution unchanged (10 txns reach bytes=15 ts=10)
- txn 0 still reads "MC" magic at bytes 10-11 (0x4D 0x43)
- pace_state: timing_active=0, all dynamic fields 0 throughout
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
186 lines
8.0 KiB
C
186 lines
8.0 KiB
C
#ifndef PSXRECOMP_V4_SIO_H
|
||
#define PSXRECOMP_V4_SIO_H
|
||
|
||
#include <stdint.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* SIO0 register base: 0x1F801040 */
|
||
#define SIO_BASE 0x1F801040
|
||
|
||
/* Phase 1.0c-v2: cycle-paced SIO model. Default 1 enables the dispatch-
|
||
* loop quantum tick (gated by g_sio_timing_active). Set to 0 to revert
|
||
* to legacy access-paced behavior across all callers. */
|
||
#ifndef SIO_MODEL_CYCLE_PACED
|
||
#define SIO_MODEL_CYCLE_PACED 1
|
||
#endif
|
||
|
||
void sio_init(void);
|
||
|
||
/* MMIO read/write (0x1F801040-0x1F80104E) */
|
||
uint32_t sio_read(uint32_t addr);
|
||
void sio_write(uint32_t addr, uint32_t value);
|
||
|
||
/* Advance SIO timing by `cycles` PSX cycles.
|
||
*
|
||
* In cycle-paced mode (SIO_MODEL_CYCLE_PACED=1, default in 1.0c+): walks
|
||
* shift-complete and ACK-fire events scheduled in cycles ahead. In 1.0c
|
||
* the shifter is never armed (TX path still synchronous), so the body
|
||
* is effectively inert. Phase 1.0d will route TX through the shifter.
|
||
*
|
||
* The legacy access-paced IRQ countdown body always runs and is the
|
||
* actual IRQ delivery mechanism in 1.0c. */
|
||
void sio_tick(int cycles);
|
||
|
||
/* Convenience: advance SIO by the dispatch-loop quantum. Called from
|
||
* psx_check_interrupts inside !in_exception, gated by g_sio_timing_active
|
||
* (no-op when no SIO cycle-paced work is pending). No-op under macro=0. */
|
||
void sio_tick_quantum(void);
|
||
|
||
/* Hot-path active guard. Set to 1 by future 1.0d code when sio_shift or
|
||
* sio_pending_ack arms; cleared back to 0 when both shifter and buffer
|
||
* and ACK are empty. The dispatch loop reads this BEFORE calling
|
||
* sio_tick_quantum, so when no SIO time is pending the per-call cost is
|
||
* one load + one branch. In 1.0c-v2 nothing sets this — it stays 0. */
|
||
extern volatile int g_sio_timing_active;
|
||
|
||
/* Update pad button state. Buttons use PS1 convention: 0=pressed, 1=released.
|
||
Bit layout: SELECT, L3, R3, START, UP, RIGHT, DOWN, LEFT,
|
||
L2, R2, L1, R1, TRIANGLE, CIRCLE, CROSS, SQUARE */
|
||
void sio_set_pad_state(uint16_t buttons);
|
||
|
||
/* Connect a pad to a slot (0=port1, 1=port2). By default no pads are
|
||
* connected during initial BIOS boot. */
|
||
void sio_connect_pad(int slot);
|
||
|
||
/* Return current pad button state (for debug server). */
|
||
uint16_t sio_get_pad_buttons(void);
|
||
|
||
/* ---- SIO byte-level trace ring buffer ----
|
||
* 1M entries × ~28 B ≈ 32 MB. At ~600 byte/sec that's ~30 min of history. */
|
||
#define SIO_TRACE_CAP (1 << 20)
|
||
|
||
typedef struct {
|
||
uint32_t seq; /* monotonic sequence number */
|
||
uint8_t tx; /* byte written to TX */
|
||
uint8_t rx; /* byte produced in RX */
|
||
uint8_t mc_state_pre; /* mc_state BEFORE processing */
|
||
uint8_t mc_state_post; /* mc_state AFTER processing */
|
||
uint8_t dev_pre; /* active_device BEFORE (0=NONE,1=PAD,2=MC) */
|
||
uint8_t dev_post; /* active_device AFTER */
|
||
uint16_t ctrl; /* CTRL register value */
|
||
uint32_t func_addr; /* g_debug_current_func_addr */
|
||
uint8_t was_abort; /* 1 if this byte caused mc_state abort */
|
||
uint8_t irq_countdown; /* sio_irq_countdown at entry */
|
||
uint8_t in_exception; /* psx_get_in_exception() at time of byte */
|
||
uint8_t counter_7514; /* RAM[0x7514] at time of byte */
|
||
uint32_t cop0_sr; /* COP0 SR at time of byte (IEc=bit0, IM2=bit10) */
|
||
/* Saved slot states captured AT TRACE TIME (after the byte was processed
|
||
* and any mc_save/load_slot has run). Lets the audit show whether a
|
||
* SELECT-deassert preserved state into the next 0x81. */
|
||
uint8_t slot0_state;
|
||
uint8_t slot1_state;
|
||
} SioTraceEntry;
|
||
|
||
/* Get pointer to ring buffer and current write index.
|
||
* Returns number of entries ever written (seq of next write). */
|
||
uint32_t sio_get_trace(const SioTraceEntry **buf_out, int *write_idx_out);
|
||
|
||
/* Current SIO byte sequence — used by SIO PC tracer for cross-referencing. */
|
||
uint32_t sio_get_seq(void);
|
||
|
||
/* Returns 1 if a memcard protocol exchange is in progress on either
|
||
* slot. Callers (notably the VBlank scheduler) use this to defer
|
||
* interrupt delivery so the BIOS's pad polling routine — which fires
|
||
* from the VBlank handler — doesn't preempt an in-flight card
|
||
* transaction by issuing 0x01 on the SIO bus mid-read. */
|
||
int sio_card_protocol_active(void);
|
||
|
||
/* Snapshot SIO IRQ-pacing internals for the freeze_check diagnostic. */
|
||
void sio_get_freeze_diag(int *out_irq_pending, int *out_irq_countdown,
|
||
uint16_t *out_sio_stat, uint16_t *out_sio_ctrl,
|
||
int *out_card_active);
|
||
|
||
/* ---- Card transaction ring buffer ----
|
||
*
|
||
* One entry per card protocol transaction (0x81 → terminal state / abort).
|
||
* Always-on. 64K entries × ~544 B ≈ 35 MB — holds tens of minutes of card
|
||
* activity at typical detection-cycle rates. */
|
||
#define SIO_TXN_CAP (1 << 16)
|
||
#define SIO_TXN_MAX_BYTES 256
|
||
|
||
typedef enum {
|
||
SIO_TXN_END_OPEN = 0, /* still in progress (only on the live txn) */
|
||
SIO_TXN_END_SUCCESS = 1, /* mc_state reached IDLE via natural progression */
|
||
SIO_TXN_END_ABORT_RESELECT= 2, /* 0x81 received while txn already non-IDLE */
|
||
SIO_TXN_END_ABORT_RESET = 3, /* CTRL.RESET written */
|
||
SIO_TXN_END_ABORT_SLOT = 4, /* slot mismatch reset (DEV_NONE non-0x81 path) */
|
||
SIO_TXN_END_ABORT_BAD_CMD = 5, /* CMD byte not 0x52/0x57/0x53 */
|
||
SIO_TXN_END_ABORT_OTHER = 6, /* state machine fell through */
|
||
} SioTxnEndReason;
|
||
|
||
typedef struct {
|
||
uint32_t txn_seq; /* monotonic transaction id */
|
||
uint32_t start_byte_seq; /* SIO byte ring seq at first byte of txn */
|
||
uint32_t end_byte_seq; /* SIO byte ring seq at last byte (inclusive) */
|
||
uint32_t start_func; /* g_debug_current_func_addr at first byte */
|
||
uint32_t end_func; /* g_debug_current_func_addr at last byte */
|
||
uint8_t slot; /* mc_slot when txn opened */
|
||
uint8_t cmd; /* 0x52/0x57/0x53 once observed; 0 before */
|
||
uint16_t sector; /* 0xFFFF if address phase not reached */
|
||
uint16_t ack_count; /* SIO ACKs returned during txn */
|
||
uint8_t terminal_state; /* mc_state at termination */
|
||
uint8_t end_reason; /* SioTxnEndReason */
|
||
uint16_t byte_count; /* total bytes processed (may exceed MAX_BYTES) */
|
||
uint8_t tx[SIO_TXN_MAX_BYTES];
|
||
uint8_t rx[SIO_TXN_MAX_BYTES];
|
||
} SioTxnEntry;
|
||
|
||
/* Returns ring buffer + next-write index + whether a live txn is currently
|
||
* open. The live txn (when open_out=1) is NOT yet in the ring; query
|
||
* sio_get_card_txn_live() if you want it too. Total seq = number of CLOSED
|
||
* transactions ever recorded. */
|
||
uint32_t sio_get_card_txns(const SioTxnEntry **buf_out, int *write_idx_out, int *open_out);
|
||
|
||
/* Returns pointer to live (open) txn, or NULL if none. Lifetime: pointer
|
||
* is valid until the next sio_process_byte call. */
|
||
const SioTxnEntry *sio_get_card_txn_live(void);
|
||
|
||
/* ---- SIO IRQ #7 delivery ring ----
|
||
*
|
||
* Captures every time IRQ #7 (SIO0) is raised into I_STAT. Always-on.
|
||
* 1M entries × ~64 B ≈ 64 MB — holds tens of minutes of IRQ history. */
|
||
#define SIO_IRQ_RING_CAP (1 << 20)
|
||
|
||
typedef enum {
|
||
SIO_IRQ_SRC_UNKNOWN = 0,
|
||
SIO_IRQ_SRC_CARD_ACK = 1, /* IRQ from card-side ACK after card byte */
|
||
SIO_IRQ_SRC_PAD_ACK = 2, /* IRQ from pad-side ACK after pad byte */
|
||
} SioIrqSource;
|
||
|
||
typedef struct {
|
||
uint32_t seq;
|
||
uint32_t byte_seq; /* corresponding sio_trace_seq when this IRQ was scheduled */
|
||
uint32_t i_stat_before; /* I_STAT just before raising bit 7 */
|
||
uint32_t i_stat_after; /* I_STAT immediately after */
|
||
uint32_t mc_state; /* mc_state at time of fire */
|
||
uint32_t active_device; /* DEV_NONE/PAD/MEMCARD at time of fire */
|
||
uint32_t ctrl; /* sio_ctrl at fire */
|
||
uint32_t func_addr; /* g_debug_current_func_addr at fire */
|
||
uint32_t counter_7514; /* card chain counter byte at fire */
|
||
uint8_t source; /* SioIrqSource */
|
||
uint8_t slot; /* selected_slot at fire */
|
||
uint8_t delay_applied; /* the SIO_IRQ_DELAY_* value used */
|
||
uint8_t pad;
|
||
} SioIrqEntry;
|
||
|
||
uint32_t sio_get_irq_ring(const SioIrqEntry **buf_out, int *write_idx_out);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* PSXRECOMP_V4_SIO_H */
|