mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 17:32:37 -10:00
fix(cd): I2CNTRL FIFO-not-empty status bit is read-only (Dragon's Lair wedge)
Device log on Dragon's Lair (bios boot): after streaming an FMV segment the game does a read-modify-write of I2CNTRL that carries the set b4 (FIFO-not-empty, read-only status per the BUTCH register doc and MiSTer butch.v) back in the write data, then flushes the FIFO with a "read FIFO_DATA; btst #4,I2CNTRL; bne" loop. We stored the whole written word and echoed it on reads, so the flush loop never saw the FIFO go empty: the 68K wedged at $36B2, the GPU parked in its mailbox wait at $F03270, and video froze right after the play command (cd_seek_wedge frame=1473, fifo_drains frozen at 62964). Mask b4 on both the word and byte write paths; reads already compute it from fifoDataReady. Same silicon pattern as the existing BUTCH+2 enable/status split. Also add an I2S_CTRL event to the CD trace ring (fires on data-enable bit-2 edges) -- the refill loop silently parks while that bit is clear, and the missing OFF-edge signal is what this diagnosis needed. Verified: Dragon's Lair now streams five more transfer cycles (seeks 2 -> 10, drains 62k -> 214k) and reaches in-game artwork; new contract test butch_i2cntrl_status_bit_readonly (18/18 BUTCH tests); full suite green; c89-lint clean. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
ba8e1fe5ac
commit
f74ce090d7
2 files changed
+71
-2
No files matched your search
+51
-2
@@ -335,12 +335,17 @@ enum
|
||||
CD_TRACE_FIFO_FILL, /* fifoDataReady transitioned to true */
|
||||
CD_TRACE_FIFO_DRAIN, /* FIFO_DRAIN_READS reached, fifoDataReady cleared */
|
||||
CD_TRACE_STOP, /* $0200 (STOP) command processed */
|
||||
CD_TRACE_HLE_READ /* HLE CD_read (see jagcd_hle.c) -- carries LBA */
|
||||
CD_TRACE_HLE_READ, /* HLE CD_read (see jagcd_hle.c) -- carries LBA */
|
||||
CD_TRACE_I2S_CTRL /* I2CNTRL data-enable (bit 2) transition; value =
|
||||
new low word. The FIFO refill loop silently
|
||||
parks while this bit is clear, so an OFF edge
|
||||
with no later ON edge explains every "drains
|
||||
frozen" wedge that isn't a STOP. */
|
||||
};
|
||||
|
||||
static const char * const cdTraceKindName[] = {
|
||||
"DSA_TX", "DSA_RX", "SEEK_START", "SEEK_DONE",
|
||||
"FIFO_FILL", "FIFO_DRAIN", "STOP", "HLE_READ"
|
||||
"FIFO_FILL", "FIFO_DRAIN", "STOP", "HLE_READ", "I2S_CTRL"
|
||||
};
|
||||
|
||||
typedef struct
|
||||
@@ -374,6 +379,11 @@ static uint32_t cdSeekStartCount = 0;
|
||||
static uint32_t cdSeekDoneCount = 0;
|
||||
static uint32_t cdFifoDrainCount = 0;
|
||||
|
||||
/* Last-observed I2CNTRL data-enable state (bit 2 of the low byte), so
|
||||
* writes that don't change it stay out of the trace ring. -1 = unknown
|
||||
* (traces the first write after reset regardless of value). */
|
||||
static int cdTraceLastI2SEnable = -1;
|
||||
|
||||
/* First (boot-relevant) seek target LBA, captured post-redirect the first
|
||||
* time a non-redundant $12xx seek runs after reset. 0xFFFFFFFF = none. */
|
||||
static uint32_t diag_firstSeekBlock = 0xFFFFFFFFu;
|
||||
@@ -416,6 +426,22 @@ static void CDTracePush(uint16_t kind, uint16_t value, uint32_t blk)
|
||||
cdTraceCount++;
|
||||
}
|
||||
|
||||
/* Trace I2CNTRL data-enable (bit 2) edges. Called after any store that
|
||||
* touches the I2CNTRL low byte (word or byte path -- the GPU ISR uses
|
||||
* 32-bit stores that arrive as two word writes, but byte stores exist). */
|
||||
static void CDTraceI2SWrite(void)
|
||||
{
|
||||
int enable = (cdRam[I2CNTRL + 3] & 0x04) ? 1 : 0;
|
||||
|
||||
if (enable != cdTraceLastI2SEnable)
|
||||
{
|
||||
CDTracePush(CD_TRACE_I2S_CTRL,
|
||||
(uint16_t)((cdRam[I2CNTRL + 2] << 8) | cdRam[I2CNTRL + 3]),
|
||||
block);
|
||||
cdTraceLastI2SEnable = enable;
|
||||
}
|
||||
}
|
||||
|
||||
/* Public wrapper for jagcd_hle.c -- the HLE CD_read path performs a
|
||||
* synchronous seek+transfer in one call (no separate seek-delay state
|
||||
* machine like the real-BIOS path), so a single HLE_READ event carrying
|
||||
@@ -500,6 +526,7 @@ void CDROMReset(void)
|
||||
dsaQueueHead = 0;
|
||||
dsaQueueTail = 0;
|
||||
dsaQueueCount = 0;
|
||||
cdTraceLastI2SEnable = -1;
|
||||
|
||||
diag_butchExecCalls = 0;
|
||||
diag_fifoIRQsFired = 0;
|
||||
@@ -1100,7 +1127,13 @@ TOC: 2 10 00 b 00:00:00 00 54:26:17 <-- Track #11
|
||||
void CDROMWriteByte(uint32_t offset, uint8_t data, uint32_t who/*=UNKNOWN*/)
|
||||
{
|
||||
offset &= 0xFF;
|
||||
/* I2CNTRL bit 4 (FIFO-not-empty) is read-only status, computed from
|
||||
* FIFO state on read -- see CDROMWriteWord below. */
|
||||
if (offset == I2CNTRL + 3)
|
||||
data &= ~0x10;
|
||||
cdRam[offset] = data;
|
||||
if (offset == I2CNTRL + 3)
|
||||
CDTraceI2SWrite();
|
||||
}
|
||||
|
||||
void CDROMWriteWord(uint32_t offset, uint16_t data, uint32_t who/*=UNKNOWN*/)
|
||||
@@ -1123,8 +1156,24 @@ void CDROMWriteWord(uint32_t offset, uint16_t data, uint32_t who/*=UNKNOWN*/)
|
||||
return;
|
||||
}
|
||||
|
||||
/* I2CNTRL bit 4 (FIFO-not-empty) is read-only status ("When read: b4 -
|
||||
* FIFO state is not empty if 1" -- BUTCH register doc above), computed
|
||||
* from fifoDataReady in the read handler. Games do read-modify-write
|
||||
* cycles on I2CNTRL, so a set status bit rides along in the write data;
|
||||
* storing it made the bit stick at 1 forever. Device-traced on Dragon's
|
||||
* Lair (bios): end-of-transfer writes $0011 (RMW carrying b4), then
|
||||
* drains the FIFO with a "read FIFO_DATA; btst #4,I2CNTRL; bne" flush
|
||||
* loop that can never see empty -- 68K wedges, GPU parks in its mailbox
|
||||
* wait, video freezes after the play command. Same silicon pattern as
|
||||
* the BUTCH+2 enable/status split above. */
|
||||
if (offset == I2CNTRL + 2)
|
||||
data &= ~0x0010;
|
||||
|
||||
SET16(cdRam, offset, data);
|
||||
|
||||
if (offset == I2CNTRL + 2)
|
||||
CDTraceI2SWrite();
|
||||
|
||||
if (offset < UNKNOWN) // Don't log EEPROM bus writes ($2C/$2E) — too noisy
|
||||
CD_LOG("WriteWord offset=0x%02X data=0x%04X [PC=$%06X]\n", offset, data, m68k_get_reg(NULL, M68K_REG_PC));
|
||||
|
||||
|
||||
@@ -281,6 +281,25 @@ TEST(butch_int_fifo_requires_both_bits)
|
||||
* $70nn echo is silently replaced by the $17nn mode status; the game polls
|
||||
* DS_DATA repeatedly for its echo and wedges (cd_seek_wedge, GPU spinning).
|
||||
* Real hardware queues each drive response in the DSA RX path. */
|
||||
TEST(butch_i2cntrl_status_bit_readonly)
|
||||
{
|
||||
uint16_t readback;
|
||||
core.CDROMReset();
|
||||
/* Games do read-modify-write cycles on I2CNTRL, so a set FIFO-not-empty
|
||||
* status bit (b4, read-only per butch.v) rides along in the write data.
|
||||
* Device-traced on Dragon's Lair (bios): end-of-transfer writes $0011,
|
||||
* then flushes the FIFO with a "btst #4; bne" loop. If the written b4
|
||||
* is stored and echoed, the loop never sees FIFO-empty and the 68K
|
||||
* wedges. With no FIFO data pending, b4 must read back 0 regardless
|
||||
* of what was written. */
|
||||
core.CDROMWriteWord(BUTCH_I2CNTRL + 2,
|
||||
BUTCH_I2S_DRIVE | BUTCH_I2S_FIFONEMPTY, CALLER_M68K);
|
||||
readback = core.CDROMReadWord(BUTCH_I2CNTRL + 2, CALLER_M68K);
|
||||
CHECK_EQ(readback & BUTCH_I2S_FIFONEMPTY, 0);
|
||||
/* the writable bits still stick */
|
||||
CHECK_EQ(readback & BUTCH_I2S_DRIVE, BUTCH_I2S_DRIVE);
|
||||
}
|
||||
|
||||
TEST(butch_dsa_back_to_back_responses)
|
||||
{
|
||||
bool (*openImage)(const char *);
|
||||
@@ -435,6 +454,7 @@ int main(int argc, char *argv[])
|
||||
RUN_TEST(butch_dsa_command_write);
|
||||
RUN_TEST(butch_dsa_read_toc_command);
|
||||
RUN_TEST(butch_dsa_get_status_command);
|
||||
RUN_TEST(butch_i2cntrl_status_bit_readonly);
|
||||
RUN_TEST(butch_dsa_back_to_back_responses);
|
||||
|
||||
/* FIFO */
|
||||
|
||||
Reference in new issue
Block a user