X68000 now handles the serial hardware directly, bypassing DOS.
This commit is contained in:
parent
63e76f02e6
commit
e1cae64e7b
4 changed files with 230 additions and 10 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -45,6 +45,7 @@ Thumbs.db
|
|||
# Crap I added
|
||||
stuff/*
|
||||
docs/*
|
||||
tools/__pycache__/*
|
||||
|
||||
# AGI game data (Sierra/fan-made AGI games used for testing; never committed)
|
||||
examples/agi/gamedata/
|
||||
|
|
|
|||
|
|
@ -158,6 +158,11 @@ static void installTickIsr(void);
|
|||
|
||||
static void uploadGfxPalette(const jlSurfaceT *src);
|
||||
|
||||
// serial.c: drain the tiny (~64 B) RSDRV receive ring into the port's soft ring. Called from the
|
||||
// long-running paths below (present / VBL wait / input poll) so mid-frame serial bytes are never
|
||||
// lost while the HAL is busy -- see the soft-ring note in serial.c.
|
||||
extern void x68kSerialPump(void);
|
||||
|
||||
static void vdispHandler(void);
|
||||
static void vdispPoll(void);
|
||||
|
||||
|
|
@ -359,6 +364,7 @@ void jlpPresent(const jlSurfaceT *src) {
|
|||
uint16_t hi0, hi1, hi2, hi3;
|
||||
uint16_t lo0, lo1, lo2, lo3;
|
||||
uint8_t palBase;
|
||||
uint16_t blitRows = 0;
|
||||
|
||||
if (src == NULL) {
|
||||
return;
|
||||
|
|
@ -378,10 +384,18 @@ void jlpPresent(const jlSurfaceT *src) {
|
|||
if (pd == NULL) {
|
||||
return;
|
||||
}
|
||||
x68kSerialPump(); // the palette upload + spread-table build above are already line time
|
||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||
if (!STAGE_DIRTY_ROW_TOUCHED(y) || STAGE_DIRTY_ROW_CLEAN(y)) {
|
||||
continue;
|
||||
}
|
||||
// A blitted row costs ~0.5-0.7 ms, so pump every second one: that keeps the gap near ~1 ms,
|
||||
// inside the ~2.8 ms the 64-byte RSDRV ring covers at the port's fastest rate (230400).
|
||||
// Clean rows are a flag test and skip the pump, so an idle present stays trap-free.
|
||||
if ((blitRows & 1u) == 0u) {
|
||||
x68kSerialPump();
|
||||
}
|
||||
blitRows++;
|
||||
// Bands are 16-bit stage words = 4 pixels = 2 bytes of a 1bpp plane.
|
||||
firstByte = (uint16_t)(gStageMinWord[y] >> 1);
|
||||
rowBytes = (uint16_t)((gStageMaxWord[y] >> 1) - firstByte + 1u);
|
||||
|
|
@ -502,6 +516,9 @@ void jlpInputPoll(void) {
|
|||
uint8_t scan;
|
||||
|
||||
for (group = 0; group < X68K_KEYGROUP_COUNT; group++) {
|
||||
// Per-group, not per-poll: the whole bitsns trap storm can pass the ~2.8 ms the 64-byte RSDRV
|
||||
// ring covers at the port's fastest rate, but one group's trap never does.
|
||||
x68kSerialPump();
|
||||
groups[group] = (uint8_t)(_iocs_bitsns((int)group) & 0xFF);
|
||||
}
|
||||
// Write both states each poll: the bitmap is live key-DOWN data,
|
||||
|
|
@ -613,6 +630,11 @@ void jlpWaitVBL(void) {
|
|||
guard = 0ul;
|
||||
while (gFrameCount == start && guard < X68K_VBL_SPIN_LIMIT) {
|
||||
vdispPoll();
|
||||
// Every 64 spins ~= a few hundred microseconds: comfortably inside the ~2.8 ms the 64-byte
|
||||
// RSDRV ring covers at 230400. Trap cost is free here -- this is a blocking wait anyway.
|
||||
if ((guard & 63ul) == 0ul) {
|
||||
x68kSerialPump();
|
||||
}
|
||||
guard++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,17 +46,150 @@
|
|||
// write instead of hanging the frame. Same guard the ST HAL uses.
|
||||
#define X68K_TX_SPIN_LIMIT 200000ul
|
||||
|
||||
// ----- Soft receive ring -----------------------------------------------------
|
||||
//
|
||||
// RSDRV.SYS's own receive ring is tiny -- MEASURED at ~64 bytes on MAME: a live capture showed a
|
||||
// 203-byte frame delivered as its first 63 bytes, a 139-byte hole, then its final byte, exactly the
|
||||
// signature of a 64-entry ring filling while the client was busy and discarding until drained. Every
|
||||
// other JoeyLib target rides a big interrupt-fed ring (DOS hardware IRQ ring, ST XBIOS IOREC, and the
|
||||
// 8-bit clients' IRQ rings), so only this port could lose mid-frame bytes whenever the caller went
|
||||
// deaf for longer than ~66 ms at 9600 baud -- and jlpPresent's full-stage blit, jlpWaitVBL's V-DISP
|
||||
// spin, and jlpInputPoll's IOCS trap storm all can. The fix: drain the IOCS ring into this soft ring
|
||||
// from INSIDE those long-running paths (x68kSerialPump below), so the 64-byte hardware-side window
|
||||
// only ever has to cover the gap between pumps, never a whole present. No ISR is involved -- the
|
||||
// pump is a plain call -- so none of the vdispst-style vector hazards apply.
|
||||
//
|
||||
// SIZED FOR THE FASTEST LINE, not just the 9600 default: this port inherits RSDRV.SYS's boot rate
|
||||
// (the mode word is deliberately not written), and RSDRV's table reaches 230400, where the 64-byte
|
||||
// IOCS ring covers only ~2.8 ms -- so every pump site keeps its interval near 1 ms (present: every
|
||||
// 2 blitted rows; input poll: every bitsns group; VBL wait: every 64 spins). 4 KB here holds ~178 ms
|
||||
// of full-rate 230400 traffic, longer than the worst deaf window (a >100 ms all-dirty present), so
|
||||
// even an unpaced burst -- the server's flowWait timeout degrade streams a whole screen without acks
|
||||
// -- fits while the loop is busy. It is plain RAM on a 4 MB machine; do not shrink it to save bytes.
|
||||
#define X68K_RX_RING_CAP 4096u
|
||||
|
||||
// Shared with x68kSccRxIsr.s (the SCC channel A RX interrupt handler): the ISR is the producer
|
||||
// (writes gX68kRxTail), the mainline is the consumer (writes gX68kRxHead). The byte is stored
|
||||
// before the tail is published and the 68000 is strictly in-order, so volatile indices are the
|
||||
// whole interlock. When the ISR could not be installed, x68kSerialPump below produces instead.
|
||||
uint8_t gX68kRxRing[X68K_RX_RING_CAP];
|
||||
volatile uint16_t gX68kRxHead = 0u; // pop side
|
||||
volatile uint16_t gX68kRxTail = 0u; // push side
|
||||
|
||||
// ----- SCC channel A RX interrupt takeover -----------------------------------
|
||||
//
|
||||
// The proper fix for the tiny RSDRV ring: own the receive interrupt, the way every other JoeyLib
|
||||
// target does (DOS UART IRQ ring, the 8-bit ACIA rings). serial.c reads the SCC's programmed
|
||||
// vector base (RR2 on channel A returns it UNmodified) and steals the two channel A receive vectors
|
||||
// -- base|0x0C "character available" and base|0x0E "special condition" (Z8530 status-affects-vector
|
||||
// puts the source in bits 3-1) -- pointing them at the raw handlers in x68kSccRxIsr.s. RSDRV's own
|
||||
// RX handler simply never runs again; its 64-byte ring starves and the polled pump becomes a no-op.
|
||||
// If the base looks wrong (VIS off, unexpected layout) nothing is stolen and the polled pump keeps
|
||||
// carrying the port exactly as before -- the fallback is automatic.
|
||||
#define X68K_SCC_A_CTRL ((volatile uint8_t *)0xE98005L)
|
||||
#define X68K_VECTOR(v) ((volatile uint32_t *)((uint32_t)(v) << 2))
|
||||
|
||||
extern void x68kSccRxIsr(void);
|
||||
extern void x68kSccSpIsr(void);
|
||||
|
||||
static uint16_t x68kIrqMask(void);
|
||||
static void x68kIrqRestore(uint16_t sr);
|
||||
static void sccIsrInstall(void);
|
||||
static void sccIsrUninstall(void);
|
||||
|
||||
static bool gSccIsrInstalled = false;
|
||||
static uint8_t gSccRxVec = 0u;
|
||||
static uint32_t gSccOldRx = 0ul;
|
||||
static uint32_t gSccOldSp = 0ul;
|
||||
|
||||
|
||||
static uint16_t x68kIrqMask(void) {
|
||||
uint16_t sr;
|
||||
|
||||
__asm__ volatile("move.w %%sr,%0\n\tori.w #0x0700,%%sr" : "=d"(sr) : : "cc");
|
||||
return sr;
|
||||
}
|
||||
|
||||
|
||||
static void x68kIrqRestore(uint16_t sr) {
|
||||
__asm__ volatile("move.w %0,%%sr" : : "d"(sr) : "cc");
|
||||
}
|
||||
|
||||
|
||||
static void sccIsrInstall(void) {
|
||||
uint16_t sr;
|
||||
uint8_t base;
|
||||
|
||||
if (gSccIsrInstalled) {
|
||||
return;
|
||||
}
|
||||
// RR2 read is a two-access pointer sequence on the shared SCC, so no interrupt (the channel B
|
||||
// mouse) may interleave. We run supervisor (jlpInit's _dos_super), so the SR mask is legal.
|
||||
sr = x68kIrqMask();
|
||||
*X68K_SCC_A_CTRL = 2u;
|
||||
base = *X68K_SCC_A_CTRL;
|
||||
x68kIrqRestore(sr);
|
||||
// With status-affects-vector the base must have bits 3-1 clear; anything else means the layout
|
||||
// is not what we expect, and the polled pump stays in charge.
|
||||
if (base == 0u || (base & 0x0Eu) != 0u) {
|
||||
return;
|
||||
}
|
||||
gSccRxVec = (uint8_t)(base | 0x0Cu);
|
||||
sr = x68kIrqMask();
|
||||
gSccOldRx = *X68K_VECTOR(gSccRxVec);
|
||||
gSccOldSp = *X68K_VECTOR(gSccRxVec + 2u);
|
||||
*X68K_VECTOR(gSccRxVec) = (uint32_t)x68kSccRxIsr;
|
||||
*X68K_VECTOR(gSccRxVec + 2u) = (uint32_t)x68kSccSpIsr;
|
||||
x68kIrqRestore(sr);
|
||||
gSccIsrInstalled = true;
|
||||
}
|
||||
|
||||
|
||||
static void sccIsrUninstall(void) {
|
||||
uint16_t sr;
|
||||
|
||||
if (!gSccIsrInstalled) {
|
||||
return;
|
||||
}
|
||||
sr = x68kIrqMask();
|
||||
*X68K_VECTOR(gSccRxVec) = gSccOldRx;
|
||||
*X68K_VECTOR(gSccRxVec + 2u) = gSccOldSp;
|
||||
x68kIrqRestore(sr);
|
||||
gSccIsrInstalled = false;
|
||||
}
|
||||
|
||||
|
||||
// Drain everything the IOCS ring holds into the soft ring. Called from the HAL's own long-running
|
||||
// paths (present / VBL wait / input poll) and from every serial entry point. With the SCC ISR
|
||||
// installed this is a cheap no-op guard (RSDRV's ring never fills); without it, it is the polled
|
||||
// fallback that keeps the port working, with the caller deaf-tolerant up to the SOFT ring instead
|
||||
// of RSDRV's ~64 bytes.
|
||||
void x68kSerialPump(void) {
|
||||
if (gSccIsrInstalled) {
|
||||
return;
|
||||
}
|
||||
while (_iocs_isns232c() != 0) {
|
||||
uint16_t next = (uint16_t)((gX68kRxTail + 1u) % X68K_RX_RING_CAP);
|
||||
|
||||
if (next == gX68kRxHead) {
|
||||
break; // soft ring full: leave the rest in the IOCS ring rather than drop it here
|
||||
}
|
||||
gX68kRxRing[gX68kRxTail] = (uint8_t)(_iocs_inp232c() & 0xFF);
|
||||
gX68kRxTail = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- HAL entry points (alphabetical) -----
|
||||
|
||||
uint16_t jlpSerialAvailable(void) {
|
||||
// _iocs_isns232c reports presence, not a count: non-zero == at least one.
|
||||
return (_iocs_isns232c() != 0) ? 1u : 0u;
|
||||
x68kSerialPump();
|
||||
return (gX68kRxHead != gX68kRxTail) ? 1u : 0u;
|
||||
}
|
||||
|
||||
|
||||
void jlpSerialClose(void) {
|
||||
// IOCS path hijacks no vector -- nothing to tear down.
|
||||
sccIsrUninstall(); // hand channel A RX back to RSDRV before the vectors dangle
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -64,6 +197,7 @@ void jlpSerialFlush(void) {
|
|||
while (_iocs_isns232c() != 0) {
|
||||
(void)_iocs_inp232c();
|
||||
}
|
||||
gX68kRxHead = gX68kRxTail; // the ISR may be live: only the consumer side may move
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -72,26 +206,30 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
|||
if (config == NULL) {
|
||||
return false;
|
||||
}
|
||||
// config is accepted but NOT applied -- see the mode-word note above. The
|
||||
// port keeps whatever RSDRV.SYS set at boot.
|
||||
// Line config is accepted but NOT applied -- see the mode-word note above. The
|
||||
// port keeps whatever RSDRV.SYS set at boot. (An RTS/CTS enable via an
|
||||
// _iocs_set232c(-1) read-modify-write was tried and REVERTED: the -1-as-query
|
||||
// idiom is a _CRTMOD contract, undocumented for _SET232C -- XEiJ reads the
|
||||
// RSDRV work area instead -- so the write could not be trusted.)
|
||||
(void)config;
|
||||
sccIsrInstall(); // own the RX interrupt; falls back to the polled pump if the vectors look wrong
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void jlpSerialPoll(void) {
|
||||
// IOCS buffers RX itself; nothing to pump.
|
||||
x68kSerialPump();
|
||||
}
|
||||
|
||||
|
||||
uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
||||
uint16_t n;
|
||||
|
||||
x68kSerialPump();
|
||||
n = 0u;
|
||||
// Gate each read on the status call first: _iocs_inp232c BLOCKS when the
|
||||
// buffer is empty, which would stall the frame.
|
||||
while (n < max && _iocs_isns232c() != 0) {
|
||||
buf[n] = (uint8_t)(_iocs_inp232c() & 0xFF);
|
||||
while (n < max && gX68kRxHead != gX68kRxTail) {
|
||||
buf[n] = gX68kRxRing[gX68kRxHead];
|
||||
gX68kRxHead = (uint16_t)((gX68kRxHead + 1u) % X68K_RX_RING_CAP);
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
|
|
|
|||
59
src/x68000/x68kSccRxIsr.s
Normal file
59
src/x68000/x68kSccRxIsr.s
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
| Sharp X68000 RS-232C receive: own the Z8530 SCC channel A RX interrupt.
|
||||
|
|
||||
| RSDRV.SYS's receive ring is ~64 bytes -- far too small for a binary protocol
|
||||
| whose client can be busy blitting for >100 ms -- so instead of draining it by
|
||||
| polling, serial.c steals the SCC's channel A RX vectors and points them here:
|
||||
| every received byte lands straight in the port's big soft ring with no RSDRV
|
||||
| involvement at all. This is the same take-over-the-hardware model every other
|
||||
| JoeyLib target uses (the DOS UART IRQ ring, the 8-bit ACIA rings).
|
||||
|
|
||||
| Entered DIRECTLY from the 68000 exception vector the SCC supplies at IACK
|
||||
| (WR2 base | %110x for channel A RX -- serial.c reads RR2 and computes it), so
|
||||
| these are raw interrupt handlers: every register touched is saved, and both
|
||||
| return with RTE. Unlike the tick hook there is NO chaining -- the byte is
|
||||
| consumed here, so running RSDRV's handler afterwards would be wrong.
|
||||
|
|
||||
| Ring protocol (single consumer, single producer): the mainline reads gX68kRxHead,
|
||||
| this ISR writes gX68kRxTail; the byte is stored BEFORE the tail moves, and the
|
||||
| 68000 is strictly in-order, so no further interlock is needed. A full ring
|
||||
| drops the byte -- the frame CRC upstream turns that into a clean retransmit-less
|
||||
| drop rather than corruption.
|
||||
|
|
||||
| GAS m68k syntax, ELF target -- symbols carry NO leading underscore.
|
||||
|
||||
.equ SCC_A_CTRL, 0xE98005 | channel A command/status
|
||||
.equ SCC_A_DATA, 0xE98007 | channel A data (direct, no pointer)
|
||||
.equ RING_MASK, 4095 | X68K_RX_RING_CAP - 1 (power of two)
|
||||
|
||||
.text
|
||||
.even
|
||||
.globl x68kSccRxIsr
|
||||
.globl x68kSccSpIsr
|
||||
|
||||
| Channel A "receive character available".
|
||||
x68kSccRxIsr:
|
||||
movem.l %d0-%d2/%a0,-(%sp)
|
||||
move.b SCC_A_DATA,%d0 | the received byte
|
||||
move.w gX68kRxTail,%d1
|
||||
move.w %d1,%d2
|
||||
addq.w #1,%d2
|
||||
andi.w #RING_MASK,%d2 | next tail
|
||||
cmp.w gX68kRxHead,%d2
|
||||
beq.s rxFull | ring full: drop, CRC upstream recovers
|
||||
lea gX68kRxRing,%a0
|
||||
move.b %d0,(%a0,%d1.w) | store BEFORE publishing the new tail
|
||||
move.w %d2,gX68kRxTail
|
||||
rxFull: move.b #0x38,SCC_A_CTRL | WR0: reset highest IUS
|
||||
movem.l (%sp)+,%d0-%d2/%a0
|
||||
rte
|
||||
|
||||
| Channel A "special receive condition" (overrun / framing error). The byte is
|
||||
| suspect, so unlatch and discard it, clear the error, and drop the IUS -- the
|
||||
| frame CRC turns the loss into a clean drop.
|
||||
x68kSccSpIsr:
|
||||
move.l %d0,-(%sp)
|
||||
move.b SCC_A_DATA,%d0 | unlatch the offending byte
|
||||
move.b #0x30,SCC_A_CTRL | WR0: error reset
|
||||
move.b #0x38,SCC_A_CTRL | WR0: reset highest IUS
|
||||
move.l (%sp)+,%d0
|
||||
rte
|
||||
Loading…
Add table
Reference in a new issue