mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 12:47:58 -10:00
fix(uart): gate the ASI 68K interrupt behind TOM's C_JERENA
Activating the Ultra Vortek Voice Modem corrupted video: scanlines out of order, content stamped repeatedly down the screen at wrong Y offsets, alternating garbage/clean at 60 Hz. It needed no connection -- starting the dial or answer sequence was enough -- and stopped when the modem closed. UARTRaiseIRQ() raised a 68K level-2 exception after checking only JERRY's own mask. But JERRY's DINT reaches the 68K solely through TOM INT1 bit 4 (C_JERENA), and m68k_set_irq() -> checkForIRQToHandle -> m68ki_exception_interrupt() never consults TOMIRQRequestActive(), so every raise site must gate itself. JERRYPIT1Callback (jerry.c:262) and JERRYPIT2Callback (jerry.c:281) both do. The UART did not. Ultra Vortek sets RINTEN and JINTCTRL bit 4 but never enables C_JERENA -- measured tomRam8[0xE1] == $01 for 2598 of 2600 frames -- because the DSP's I2S ISR polls ASISTAT and the 68K never uses the RX interrupt. On silicon those bytes raise nothing. Here each one took a spurious IPL2 that re-entered the display handler at an arbitrary raster position; m68ki_exception_interrupt also clears regs.stopped, waking a STOPped main loop early. Measured, buggy vs gated, over 2600 frames: avg IRQ2/frame 3.26 / 3.11 -> 0.999 max IRQ2 in one frame 16-29 -> 1 frames with extra IRQ2 631 / 470 -> 0 framebuffer churn ~100% -> 10.1% Every frame carrying more than one IRQ2 showed a changed framebuffer. Both arms sit on the same screen, so this is not state divergence. The latch stays outside the gate: JERRYSetPendingIRQ() still runs, so a later C_JERENA write is picked up by tom.c:1838's newlyEnabled & TOMPendingMask(), which folds in JERRYIRQRequestActive(). No interrupt is lost, only correctly withheld. Fixed in uart.c rather than m68kinterface.c so the UART matches the two PIT callbacks and the silicon; changing delivery centrally would alter every raise site at once. test_uart_loopback gains a stub for TOMIRQEnabled (it deliberately does not link tom.c) and a case pinning both halves. Its existing stubIpl==2 assertion was asserting behaviour the hardware does not perform, so fresh() now defaults the stub to enabled to preserve that test's intent while the new case pins the real gate. Same class, deliberately untouched -- neither implicated here and both on paths with regression history: dsp.c:787 (DSP CPUINT) and gpu.c:1044 (GPU CPUINT).
This commit is contained in:
2 files changed
+32
-1
No files matched your search
+6
-1
@@ -19,6 +19,7 @@
|
||||
#include "settings.h"
|
||||
#include "state.h"
|
||||
#include "jlink.h"
|
||||
#include "tom.h"
|
||||
#include "m68000/m68kinterface.h"
|
||||
|
||||
/* ASICTRL write bits */
|
||||
@@ -60,7 +61,11 @@ static void UARTRaiseIRQ(void)
|
||||
if (JERRYIRQEnabled(IRQ2_ASI))
|
||||
{
|
||||
JERRYSetPendingIRQ(IRQ2_ASI);
|
||||
m68k_set_irq(2);
|
||||
/* JERRY's interrupt output (DINT) reaches the 68K only through
|
||||
TOM's INT1 bit 4 (C_JERENA) -- the same gate JERRYPIT1Callback
|
||||
and JERRYPIT2Callback apply before raising IPL2. */
|
||||
if (TOMIRQEnabled(IRQ_DSP))
|
||||
m68k_set_irq(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,12 @@ static int stubIpl = -1;
|
||||
bool JERRYIRQEnabled(int irq) { return (stubIrqMask & irq) != 0; }
|
||||
void JERRYSetPendingIRQ(int irq) { stubPending |= irq; }
|
||||
void m68k_set_irq(unsigned int level) { stubIpl = (int)level; }
|
||||
/* TOM INT1 enable byte: JERRY's interrupt output reaches the 68K only
|
||||
through bit 4 (C_JERENA, IRQ_DSP). Stubbed rather than pulling in
|
||||
tom.c, which this test deliberately does not link. */
|
||||
#define STUB_IRQ_DSP 4
|
||||
static int stubTomInt1 = 0;
|
||||
int TOMIRQEnabled(int irq) { return stubTomInt1 & (1 << irq); }
|
||||
|
||||
#define ASIDATA 0xF10030u
|
||||
#define ASISTAT 0xF10032u
|
||||
@@ -55,6 +61,7 @@ static void fresh(void)
|
||||
stubIrqMask = IRQ2_ASI; /* J_ASYNENA on unless a test clears it */
|
||||
stubPending = 0;
|
||||
stubIpl = -1;
|
||||
stubTomInt1 = 1 << STUB_IRQ_DSP; /* C_JERENA on unless a test clears it */
|
||||
}
|
||||
|
||||
/* Fire JERRY events one at a time, mirroring the production loop:
|
||||
@@ -189,6 +196,24 @@ static void test_rx_interrupt_masked(void)
|
||||
CHECK(stubIpl == -1, "no IPL2 when masked");
|
||||
}
|
||||
|
||||
/* Regression (#UV voice-modem video corruption): Ultra Vortek enables
|
||||
RINTEN and JINTCTRL bit 4 but leaves TOM INT1 = $01 (video only), so
|
||||
C_JERENA is clear and the ASI interrupt must never reach the 68K.
|
||||
Without this gate every received byte took a spurious level-2
|
||||
exception, re-entering the game's display handler mid-field. */
|
||||
static void test_rx_interrupt_tom_gate(void)
|
||||
{
|
||||
fresh();
|
||||
stubTomInt1 = 0x01; /* video only: C_JERENA clear */
|
||||
UARTWriteWord(ASICLK, 0);
|
||||
UARTWriteWord(ASICTRL, CT_RINTEN);
|
||||
UARTWriteWord(ASIDATA, 0x99);
|
||||
pump(2);
|
||||
CHECK((stubPending & IRQ2_ASI) != 0,
|
||||
"C_JERENA clear: JERRY still latches ASI pending");
|
||||
CHECK(stubIpl == -1, "C_JERENA clear: no 68K IPL2 from UART RX");
|
||||
}
|
||||
|
||||
static void test_rx_interrupt_disabled(void)
|
||||
{
|
||||
fresh(); /* mask on, RINTEN off */
|
||||
@@ -221,6 +246,7 @@ int main(void)
|
||||
test_disabled_link();
|
||||
test_rx_interrupt();
|
||||
test_rx_interrupt_masked();
|
||||
test_rx_interrupt_tom_gate();
|
||||
test_rx_interrupt_disabled();
|
||||
test_tx_interrupt_on_holding_drain();
|
||||
printf(failures ? "FAILED (%d)\n" : "OK\n", failures);
|
||||
|
||||
Reference in new issue
Block a user