Files
psxrecomp/runtime/include/sio1.h
T
Alex Vanderveen db5f401af3 chore: sync
2026-08-17 23:03:33 -04:00

159 lines
6.6 KiB
C

/*
* sio1.h -- PS1 Serial Port (SIO1) controller, 0x1F801050-0x1F80105F.
*
* The link-cable serial port -- a separate device from SIO0 (pads/memcards,
* sio.h). Register semantics follow nocash psx-spx "Serial Port (SIO1)";
* see accuracy/axis4_sio1_serial.md for the audit and known simplifications.
*
* Two API layers:
* 1. Sio1Device -- a pure instance (injected clock, injected IRQ callback,
* link endpoint attached via psx_link.h). No runtime dependencies:
* unit tests link sio1.c + psx_link.c standalone, and two instances can
* coexist (the dual-console driver needs exactly that).
* 2. sio1_* singleton wrapper (sio1_runtime.c) -- wires the instance to
* psx_cycle_count / psx_irq_raise / config for the live machine.
*/
#ifndef PSX_SIO1_H
#define PSX_SIO1_H
#include <stdint.h>
#include "psx_link.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SIO1_BASE 0x1F801050u
/* STAT bits (low half; 11..31 = baud timer, computed lazily at read). */
#define SIO1_STAT_TXRDY1 (1u << 0)
#define SIO1_STAT_RXNE (1u << 1)
#define SIO1_STAT_TXDONE (1u << 2)
#define SIO1_STAT_PARITY (1u << 3)
#define SIO1_STAT_OVERRUN (1u << 4)
#define SIO1_STAT_BADSTOP (1u << 5)
#define SIO1_STAT_DSR (1u << 7)
#define SIO1_STAT_CTS (1u << 8)
#define SIO1_STAT_IRQ (1u << 9)
/* CTRL bits. */
#define SIO1_CTRL_TXEN (1u << 0)
#define SIO1_CTRL_DTR (1u << 1)
#define SIO1_CTRL_RXEN (1u << 2)
#define SIO1_CTRL_TXLEVEL (1u << 3)
#define SIO1_CTRL_ACK (1u << 4) /* strobe, reads back 0 */
#define SIO1_CTRL_RTS (1u << 5)
#define SIO1_CTRL_RESET (1u << 6) /* strobe, reads back 0 */
#define SIO1_CTRL_TX_IRQ (1u << 10)
#define SIO1_CTRL_RX_IRQ (1u << 11)
#define SIO1_CTRL_DSR_IRQ (1u << 12)
#define SIO1_CTRL_STORED_MASK 0x1FAFu
#define SIO1_RX_FIFO_DEPTH 8u
/* IRQ edge detail values (psx_irq_raise detail / device_trace). */
#define SIO1_IRQ_DETAIL_RX 1u
#define SIO1_IRQ_DETAIL_TX 2u
#define SIO1_IRQ_DETAIL_DSR 3u
/* ===== instance API ====================================================== */
typedef struct Sio1Device Sio1Device;
typedef void (*Sio1IrqFn)(void *user, uint32_t detail);
Sio1Device *sio1_device_create(void);
void sio1_device_destroy(Sio1Device *d);
void sio1_device_attach(Sio1Device *d, PsxLinkEndpoint *ep);
void sio1_device_set_irq(Sio1Device *d, Sio1IrqFn fn, void *user);
/* Full block reset (power-on / CTRL.6). `now` re-anchors the baud timer. */
void sio1_device_reset(Sio1Device *d, uint64_t now);
/* MMIO. `addr` is the UNALIGNED guest address (lane decode happens here --
* MODE/CTRL share word 0x1058, BAUD shares 0x105C), `width` is 1/2/4,
* `now` the current guest cycle. */
uint32_t sio1_device_read (Sio1Device *d, uint32_t addr, uint32_t width,
uint64_t now);
void sio1_device_write(Sio1Device *d, uint32_t addr, uint32_t width,
uint32_t value, uint64_t now);
/* Advance device time by `cycles`, ending at guest cycle `now`. Completes TX
* shifts, drains due inbound characters into the RX FIFO, samples handshake
* lines, fires IRQ edges. At most safe to call with any chunk size; event
* slicing keeps chunks landing on byte boundaries. */
void sio1_device_advance(Sio1Device *d, uint32_t cycles, uint64_t now);
/* Conservative under-estimate of cycles until the next internal event
* (TX shift completion / inbound char due). 0xFFFFFFFF = none pending or
* IRQ8 masked in `i_mask`. Pass 0xFFFFFFFF as i_mask for mask-blind event
* chunking. */
uint32_t sio1_device_cycles_to_irq(const Sio1Device *d, uint32_t i_mask);
int sio1_device_active(const Sio1Device *d);
/* Derived timing (test/diag hooks). */
uint32_t sio1_device_bit_cycles (const Sio1Device *d);
uint32_t sio1_device_char_cycles(const Sio1Device *d);
/* Side-effect-free register peeks (debug server: the observer must not
* perturb the observed -- same rule as sio.h). */
uint32_t sio1_device_peek_stat(const Sio1Device *d, uint64_t now);
uint16_t sio1_device_peek_mode(const Sio1Device *d);
uint16_t sio1_device_peek_ctrl(const Sio1Device *d);
uint16_t sio1_device_peek_baud(const Sio1Device *d);
void sio1_device_get_counters(const Sio1Device *d, uint32_t *tx_chars,
uint32_t *rx_chars, uint32_t *overruns,
uint32_t *irqs);
/* Snapshot (device + attached endpoint). Timeline values serialize as deltas
* from `now`. Section ends (cumulative offsets): [0]=regs, [1]=fsm+endpoint
* (pace -- netplay digest folds through here), [2]=telemetry meta = total. */
uint32_t sio1_device_snap_bytes(Sio1Device *d);
void sio1_device_snap_write(Sio1Device *d, uint8_t *p, uint64_t now);
int sio1_device_snap_read (Sio1Device *d, const uint8_t *p, uint32_t len,
uint64_t now);
void sio1_device_snap_section_ends(Sio1Device *d, uint32_t out[3]);
/* ===== singleton wrapper (sio1_runtime.c; live machine only) ============= */
void sio1_init(void);
uint32_t sio1_read (uint32_t addr, uint32_t width);
void sio1_write(uint32_t addr, uint32_t width, uint32_t value);
void sio1_advance(uint32_t cycles);
uint32_t sio1_cycles_to_irq(uint32_t i_mask);
/* Hot-path guard for the advance/slicing paths (mirrors g_sio_timing_active):
* nonzero only while a shift is in flight or inbound chars are queued. */
extern volatile int g_sio1_active;
/* Kill-switch: PSX_SIO1_REGS=0 restores the legacy fold-into-SIO0 decode
* (reads 0 / writes dropped). Default 1. Read by memory.c dispatchers. */
extern int g_sio1_regs_enabled;
/* Boot-state / digest glue (BS_SEC_SIO1). */
uint32_t sio1_snapshot_bytes(void);
void sio1_snapshot_write(uint8_t *p);
int sio1_snapshot_read(const uint8_t *p, uint32_t len);
void sio1_snapshot_section_ends(uint32_t out[3]);
/* Config ([runtime.link] / env): backend "null" | "loopback" | "crossover".
* "crossover" is reserved for the dual-console driver, which attaches the
* endpoints itself; selecting it here without that driver leaves "null".
* Returns 1 on success. */
int sio1_set_backend(const char *name);
void sio1_set_latency_cycles(uint32_t cycles);
const char *sio1_get_backend(void);
/* Debug-server peeks into the live singleton. */
Sio1Device *sio1_get_device(void);
/* Dual-console support (dual_machine.c): swap which per-machine device the
* singleton wrapper drives, and suppress BS_SEC_SIO1 snapshot APPLY while
* dual mode is active (the crossover wire is host-owned and must survive
* machine switches byte-exact; writes still serialize normally). */
void sio1_dual_install(Sio1Device *d);
void sio1_dual_suppress_snapshot_apply(int on);
#ifdef __cplusplus
}
#endif
#endif /* PSX_SIO1_H */