diff --git a/runtime/include/audio_trace.h b/runtime/include/audio_trace.h index d8cab71dd..6d55b102c 100644 --- a/runtime/include/audio_trace.h +++ b/runtime/include/audio_trace.h @@ -60,6 +60,7 @@ enum { stage 0=post-ADPCM-decode (native rate), stage 1=post-resample+volume (44100). */ AUDIO_EV_SINK_DROP = 10, /* turbo host sink; a=guest SPU frames discarded */ + AUDIO_EV_DMA_READ = 11, /* SPU RAM -> CPU DMA; a=words, b=dest RAM addr */ }; typedef struct { diff --git a/runtime/include/spu.h b/runtime/include/spu.h index b652c816b..5d5a67ebb 100644 --- a/runtime/include/spu.h +++ b/runtime/include/spu.h @@ -102,6 +102,7 @@ void spu_write(uint32_t addr, uint32_t value); /* DMA channel 4 interface */ void spu_dma_write(uint32_t word); +uint32_t spu_dma_read(void); int spu_dma_ready(void); /* CD-ROM XA/CDDA input path. Samples are stereo 44.1 kHz PCM entering the diff --git a/runtime/src/dma.c b/runtime/src/dma.c index 12c2c5072..d203b082c 100644 --- a/runtime/src/dma.c +++ b/runtime/src/dma.c @@ -795,9 +795,11 @@ static uint32_t execute_ch4_spu(void) { channels[4].madr & 0x1FFFFCu); } else { for (uint32_t i = 0; i < total_words; i++) { - psx_write_word(addr, 0); + psx_write_word(addr, spu_dma_read()); addr = (addr + addr_step) & 0x1FFFFCu; } + audio_trace_event(AUDIO_EV_DMA_READ, total_words, + channels[4].madr & 0x1FFFFCu); } channels[4].madr = addr; diff --git a/runtime/src/interrupts.c b/runtime/src/interrupts.c index c267a87da..a1e78bce8 100644 --- a/runtime/src/interrupts.c +++ b/runtime/src/interrupts.c @@ -119,9 +119,32 @@ extern uint32_t i_mask; * I_STAT bit so the device-event ring sees every raise from one place with the * exact guest cycle. Pure addition over `i_stat |= (1<0 */ + psx_irq_refresh_cause_ip2(); } static void interrupt_write_mask_masked(uint32_t val, uint32_t mask, uint8_t width) { uint32_t old = i_mask; i_mask = ((i_mask & ~mask) | (val & mask)) & 0x7FFu; imask_trace_record(old, i_mask, width); + psx_irq_refresh_cause_ip2(); } /* Getters for debug server */ diff --git a/runtime/src/spu.c b/runtime/src/spu.c index 841ea18d3..c2a4b056e 100644 --- a/runtime/src/spu.c +++ b/runtime/src/spu.c @@ -23,6 +23,35 @@ static uint8_t spu_ram[SPU_RAM_SIZE]; static uint16_t spu_regs[SPU_REG_COUNT]; static uint32_t transfer_addr; + +/* ---- SPU IRQ (I_STAT bit 9) ---- + * Real hardware raises the SPU interrupt when ANY SPU-RAM access (voice + * ADPCM fetch, FIFO/DMA transfer, CD-audio write) touches the address in + * 0x1F801DA4 (units of 8 bytes), while SPUCNT bit 6 enables it. The flag + * mirrors in SPUSTAT bit 6 and is acknowledged by clearing SPUCNT bit 6. + * Games use this to detect sound-bank upload completion by parking the IRQ + * address at the end of the upload region — MOHU's Mission 1 parks it after + * the weapon sound bank and busy-waits on kernel event class 0xF0000009 + * spec 0x20; without this IRQ the wait never ends (the weapon-draw freeze). + * Modeled triggers: transfer writes/reads (FIFO + DMA4 both directions). + * NOT yet modeled: voice playback fetch crossing the address (streaming + * double-buffer engines) — extend spu_irq_check_range from the voice + * decode loop when a consumer needs it. */ +static uint32_t spu_irq_addr; /* byte address (reg value << 3) */ +static int spu_irq_flag; /* SPUSTAT bit 6 latch, one-shot until re-arm */ + +extern void psx_irq_raise(uint32_t bit, uint32_t detail); + +static void spu_irq_check_range(uint32_t lo, uint32_t len) +{ + uint16_t cnt = spu_regs[(0x1F801DAAu - 0x1F801C00u) >> 1]; + if (!(cnt & 0x0040u)) return; /* SPUCNT.6: IRQ disabled */ + if (spu_irq_flag) return; /* already latched */ + if (spu_irq_addr >= lo && spu_irq_addr < lo + len) { + spu_irq_flag = 1; + psx_irq_raise(9, spu_irq_addr); /* I_STAT bit 9 = SPU */ + } +} static uint32_t key_on_count; static uint64_t render_frames; static uint64_t nonzero_frames; @@ -314,6 +343,12 @@ static void decode_block(SpuVoice *v) { uint32_t addr = v->cur_addr & (SPU_RAM_SIZE - 1u); if (addr + 16u > SPU_RAM_SIZE) addr = 0; + /* Voice ADPCM fetch is an SPU-RAM access: it triggers the SPU IRQ when + * the 16-byte block covers the programmed IRQ address. This is the sync + * technique MOHU's engine waits on at Mission 1 start (event class + * 0xF0000009): a keyed-on voice plays into the parked IRQ address. */ + spu_irq_check_range(addr, 16u); + uint8_t header = spu_ram[addr + 0u]; uint8_t flags = spu_ram[addr + 1u]; int shift = header & 0x0F; @@ -544,6 +579,8 @@ void spu_init(void) { memset(voices, 0, sizeof(voices)); memset(s_events, 0, sizeof(s_events)); transfer_addr = 0; + spu_irq_addr = 0; + spu_irq_flag = 0; key_on_count = 0; render_frames = 0; nonzero_frames = 0; @@ -765,7 +802,8 @@ uint32_t spu_read(uint32_t addr) { * OpenBIOS's shell MOD player waits for (SPUSTAT & 0x7FF) * == 0 after clearing SPUCNT and spun forever. */ uint16_t cnt = spu_regs[reg_index(0x1F801DAAu)]; - return (uint32_t)((cnt & 0x3Fu) | (((cnt >> 5) & 1u) << 7)); + return (uint32_t)((cnt & 0x3Fu) | (((cnt >> 5) & 1u) << 7) + | ((uint32_t)(spu_irq_flag ? 1u : 0u) << 6)); } /* ENDX (end-block-reached latch). Real hw sets bit v when voice * v decodes a block whose flag byte has bit 0; KEYON[v] clears @@ -839,6 +877,17 @@ void spu_write(uint32_t addr, uint32_t value) { key_off((uint32_t)(uint16_t)value << 16); } + if (addr == 0x1F801DA4u) { + spu_irq_addr = (((uint32_t)(uint16_t)value) << 3) + & (SPU_RAM_SIZE - 1u); + } + + if (addr == 0x1F801DAAu) { + /* Clearing SPUCNT bit 6 acknowledges/re-arms the IRQ. */ + if (!((uint16_t)value & 0x0040u)) + spu_irq_flag = 0; + } + if (addr == 0x1F801DA6u) { transfer_addr = ((uint32_t)(uint16_t)value) << 3; if (transfer_addr >= SPU_RAM_SIZE) transfer_addr = 0; @@ -849,6 +898,7 @@ void spu_write(uint32_t addr, uint32_t value) { spu_ram[transfer_addr] = (uint8_t)(value & 0xFF); spu_ram[transfer_addr + 1] = (uint8_t)((value >> 8) & 0xFF); } + spu_irq_check_range(transfer_addr, 2); transfer_addr = (transfer_addr + 2) % SPU_RAM_SIZE; } } @@ -862,7 +912,26 @@ void spu_dma_write(uint32_t word) { spu_ram[transfer_addr + 2] = (uint8_t)((word >> 16) & 0xFF); spu_ram[transfer_addr + 3] = (uint8_t)((word >> 24) & 0xFF); } + spu_irq_check_range(transfer_addr, 4); + transfer_addr = (transfer_addr + 4) % SPU_RAM_SIZE; +} + +/* SPU RAM -> CPU RAM (DMA4 read direction, SPUCNT transfer mode 3). + * Gran Turismo carries its cross-EXE GAMESTATUS block ('GTos' magic + + * CRC-CCITT) through SPU RAM across GTMENU->GTMAIN Exec transitions; a + * zero-stubbed readback fails that checksum and the game falls back to + * its cold-boot chain (race load "resets" to the intro FMV). */ +uint32_t spu_dma_read(void) { + uint32_t word = 0; + if (transfer_addr + 3 < SPU_RAM_SIZE) { + word = (uint32_t)spu_ram[transfer_addr] + | ((uint32_t)spu_ram[transfer_addr + 1] << 8) + | ((uint32_t)spu_ram[transfer_addr + 2] << 16) + | ((uint32_t)spu_ram[transfer_addr + 3] << 24); + } + spu_irq_check_range(transfer_addr, 4); /* any SPU-RAM access triggers */ transfer_addr = (transfer_addr + 4) % SPU_RAM_SIZE; + return word; } int spu_dma_ready(void) { diff --git a/runtime/src/traps.c b/runtime/src/traps.c index 77402276f..d4ac51fd0 100644 --- a/runtime/src/traps.c +++ b/runtime/src/traps.c @@ -340,7 +340,14 @@ static uint32_t psx_restore_context_from_tcb(CPUState* cpu, uint32_t tcb) uint32_t saved_sr = cpu->read_word(save + 140u); cpu->cop0[12] = (saved_sr & 0xFFFFFFC0u) | ((saved_sr >> 2) & 0x0Fu); } + /* Restore the saved CAUSE but re-derive IP2: on hardware the IP bits are + * live interrupt-line state, never memory — restoring a stale IP2 from a + * context saved before the ack re-latches a phantom interrupt. */ cpu->cop0[13] = cpu->read_word(save + 144u); + { + extern void psx_irq_refresh_cause_ip2(void); + psx_irq_refresh_cause_ip2(); + } cpu->gpr[26] = cpu->read_word(save + 128u); psx_assert_no_sentinel_pc("restore_context_from_tcb", tcb, cpu->gpr[26]); thread_ctx_ring_log(cpu, tcb, cpu->gpr[26], 1);