// This happens at 4 khz, since 32 bytes at 4khz = 4 bytes at 32 khz (16bit stereo pcm) void UpdateAudioDMA() { static short zero_samples[8*2] = { 0 }; if (g_audioDMA.AudioDMAControl.Enable) { // Read audio at g_audioDMA.current_source_address in RAM and push onto an // external audio fifo in the emulator, to be mixed with the disc // streaming output. g_audioDMA.remaining_blocks_count--; g_audioDMA.current_source_address += 32; if (g_audioDMA.remaining_blocks_count == 0) { g_audioDMA.current_source_address = g_audioDMA.SourceAddress; g_audioDMA.remaining_blocks_count = g_audioDMA.AudioDMAControl.NumBlocks; if (g_audioDMA.AudioDMAControl.NumBlocks == 0) { g_audioDMA.AudioDMAControl.Enable = 0; } else { // We make the samples ready as soon as possible void *address = Memory::GetPointer(g_audioDMA.SourceAddress); AudioCommon::SendAIBuffer((short*)address, g_audioDMA.AudioDMAControl.NumBlocks * 8); } GenerateDSPInterrupt(DSP::INT_AID); } } else { AudioCommon::SendAIBuffer(&zero_samples[0], 8); } }
// This happens at 4 khz, since 32 bytes at 4khz = 4 bytes at 32 khz (16bit stereo pcm) void UpdateAudioDMA() { if (g_audioDMA.AudioDMAControl.Enable && g_audioDMA.BlocksLeft) { // Read audio at g_audioDMA.ReadAddress in RAM and push onto an // external audio fifo in the emulator, to be mixed with the disc // streaming output. If that audio queue fills up, we delay the // emulator. g_audioDMA.BlocksLeft--; g_audioDMA.ReadAddress += 32; if (g_audioDMA.BlocksLeft == 0) { dsp_emulator->DSP_SendAIBuffer(g_audioDMA.SourceAddress, 8*g_audioDMA.AudioDMAControl.NumBlocks); GenerateDSPInterrupt(DSP::INT_AID); g_audioDMA.BlocksLeft = g_audioDMA.AudioDMAControl.NumBlocks; g_audioDMA.ReadAddress = g_audioDMA.SourceAddress; } } else { // Send silence. Yeah, it's a bit of a waste to sample rate convert // silence. or hm. Maybe we shouldn't do this :) dsp_emulator->DSP_SendAIBuffer(0, AudioInterface::GetAIDSampleRate()); } }
void Do_ARAM_DMA() { if (g_arDMA.Cnt.count == 32) { // Beyond Good and Evil (GGEE41) sends count 32 // Lost Kingdoms 2 needs the exception check here in DSP HLE mode GenerateDSPInterrupt(INT_ARAM); CoreTiming::ForceExceptionCheck(100); } else { g_dspState.DSPControl.DMAState = 1; CoreTiming::ScheduleEvent_Threadsafe(0, et_GenerateDSPInterrupt, INT_ARAM | (1<<16)); // Force an early exception check on large transfers. Fixes RE2 audio. // NFS:HP2 (<= 6144) // Viewtiful Joe (<= 6144) // Sonic Mega Collection (> 2048) // Paper Mario battles (> 32) // Mario Super Baseball (> 32) // Knockout Kings 2003 loading (> 32) // WWE DOR (> 32) if (g_arDMA.Cnt.count > 2048 && g_arDMA.Cnt.count <= 6144) CoreTiming::ForceExceptionCheck(100); } // Real hardware DMAs in 32byte chunks, but we can get by with 8byte chunks if (g_arDMA.Cnt.dir) { // ARAM -> MRAM INFO_LOG(DSPINTERFACE, "DMA %08x bytes from ARAM %08x to MRAM %08x PC: %08x", g_arDMA.Cnt.count, g_arDMA.ARAddr, g_arDMA.MMAddr, PC); // Outgoing data from ARAM is mirrored every 64MB (verified on real HW) g_arDMA.ARAddr &= 0x3ffffff; g_arDMA.MMAddr &= 0x3ffffff; if (g_arDMA.ARAddr < g_ARAM.size) { while (g_arDMA.Cnt.count) { // These are logically seperated in code to show that a memory map has been set up // See below in the write section for more information if ((g_ARAM_Info.Hex & 0xf) == 3) { Memory::Write_U64_Swap(*(u64*)&g_ARAM.ptr[g_arDMA.ARAddr & g_ARAM.mask], g_arDMA.MMAddr); } else if ((g_ARAM_Info.Hex & 0xf) == 4) { Memory::Write_U64_Swap(*(u64*)&g_ARAM.ptr[g_arDMA.ARAddr & g_ARAM.mask], g_arDMA.MMAddr); } else { Memory::Write_U64_Swap(*(u64*)&g_ARAM.ptr[g_arDMA.ARAddr & g_ARAM.mask], g_arDMA.MMAddr); } g_arDMA.MMAddr += 8; g_arDMA.ARAddr += 8; g_arDMA.Cnt.count -= 8; } } else { // Assuming no external ARAM installed; returns zeroes on out of bounds reads (verified on real HW) while (g_arDMA.Cnt.count) { Memory::Write_U64(0, g_arDMA.MMAddr); g_arDMA.MMAddr += 8; g_arDMA.ARAddr += 8; g_arDMA.Cnt.count -= 8; } } } else { // MRAM -> ARAM INFO_LOG(DSPINTERFACE, "DMA %08x bytes from MRAM %08x to ARAM %08x PC: %08x", g_arDMA.Cnt.count, g_arDMA.MMAddr, g_arDMA.ARAddr, PC); // Incoming data into ARAM is mirrored every 64MB (verified on real HW) g_arDMA.ARAddr &= 0x3ffffff; g_arDMA.MMAddr &= 0x3ffffff; if (g_arDMA.ARAddr < g_ARAM.size) { while (g_arDMA.Cnt.count) { if ((g_ARAM_Info.Hex & 0xf) == 3) { *(u64*)&g_ARAM.ptr[g_arDMA.ARAddr & g_ARAM.mask] = Common::swap64(Memory::Read_U64(g_arDMA.MMAddr)); } else if ((g_ARAM_Info.Hex & 0xf) == 4) { if (g_arDMA.ARAddr < 0x400000) { *(u64*)&g_ARAM.ptr[(g_arDMA.ARAddr + 0x400000) & g_ARAM.mask] = Common::swap64(Memory::Read_U64(g_arDMA.MMAddr)); } *(u64*)&g_ARAM.ptr[g_arDMA.ARAddr & g_ARAM.mask] = Common::swap64(Memory::Read_U64(g_arDMA.MMAddr)); } else { *(u64*)&g_ARAM.ptr[g_arDMA.ARAddr & g_ARAM.mask] = Common::swap64(Memory::Read_U64(g_arDMA.MMAddr)); } g_arDMA.MMAddr += 8; g_arDMA.ARAddr += 8; g_arDMA.Cnt.count -= 8; } } else { // Assuming no external ARAM installed; writes nothing to ARAM when out of bounds (verified on real HW) g_arDMA.MMAddr += g_arDMA.Cnt.count; g_arDMA.ARAddr += g_arDMA.Cnt.count; g_arDMA.Cnt.count = 0; } } }
void RegisterMMIO(MMIO::Mapping* mmio, u32 base) { // Declare all the boilerplate direct MMIOs. struct { u32 addr; u16* ptr; bool align_writes_on_32_bytes; } directly_mapped_vars[] = { { AR_INFO, &g_ARAM_Info.Hex }, { AR_MODE, &g_AR_MODE }, { AR_REFRESH, &g_AR_REFRESH }, { AR_DMA_MMADDR_H, MMIO::Utils::HighPart(&g_arDMA.MMAddr) }, { AR_DMA_MMADDR_L, MMIO::Utils::LowPart(&g_arDMA.MMAddr), true }, { AR_DMA_ARADDR_H, MMIO::Utils::HighPart(&g_arDMA.ARAddr) }, { AR_DMA_ARADDR_L, MMIO::Utils::LowPart(&g_arDMA.ARAddr), true }, { AR_DMA_CNT_H, MMIO::Utils::HighPart(&g_arDMA.Cnt.Hex) }, // AR_DMA_CNT_L triggers DMA { AUDIO_DMA_START_HI, MMIO::Utils::HighPart(&g_audioDMA.SourceAddress) }, { AUDIO_DMA_START_LO, MMIO::Utils::LowPart(&g_audioDMA.SourceAddress) }, }; for (auto& mapped_var : directly_mapped_vars) { u16 write_mask = mapped_var.align_writes_on_32_bytes ? 0xFFE0 : 0xFFFF; mmio->Register(base | mapped_var.addr, MMIO::DirectRead<u16>(mapped_var.ptr), MMIO::DirectWrite<u16>(mapped_var.ptr, write_mask) ); } // DSP mail MMIOs call DSP emulator functions to get results or write data. mmio->Register(base | DSP_MAIL_TO_DSP_HI, MMIO::ComplexRead<u16>([](u32) { if (dsp_slice > DSP_MAIL_SLICE && dsp_is_lle) { dsp_emulator->DSP_Update(DSP_MAIL_SLICE); dsp_slice -= DSP_MAIL_SLICE; } return dsp_emulator->DSP_ReadMailBoxHigh(true); }), MMIO::ComplexWrite<u16>([](u32, u16 val) { dsp_emulator->DSP_WriteMailBoxHigh(true, val); }) ); mmio->Register(base | DSP_MAIL_TO_DSP_LO, MMIO::ComplexRead<u16>([](u32) { return dsp_emulator->DSP_ReadMailBoxLow(true); }), MMIO::ComplexWrite<u16>([](u32, u16 val) { dsp_emulator->DSP_WriteMailBoxLow(true, val); }) ); mmio->Register(base | DSP_MAIL_FROM_DSP_HI, MMIO::ComplexRead<u16>([](u32) { if (dsp_slice > DSP_MAIL_SLICE && dsp_is_lle) { dsp_emulator->DSP_Update(DSP_MAIL_SLICE); dsp_slice -= DSP_MAIL_SLICE; } return dsp_emulator->DSP_ReadMailBoxHigh(false); }), MMIO::InvalidWrite<u16>() ); mmio->Register(base | DSP_MAIL_FROM_DSP_LO, MMIO::ComplexRead<u16>([](u32) { return dsp_emulator->DSP_ReadMailBoxLow(false); }), MMIO::InvalidWrite<u16>() ); mmio->Register(base | DSP_CONTROL, MMIO::ComplexRead<u16>([](u32) { return (g_dspState.DSPControl.Hex & ~DSP_CONTROL_MASK) | (dsp_emulator->DSP_ReadControlRegister() & DSP_CONTROL_MASK); }), MMIO::ComplexWrite<u16>([](u32, u16 val) { UDSPControl tmpControl; tmpControl.Hex = (val & ~DSP_CONTROL_MASK) | (dsp_emulator->DSP_WriteControlRegister(val) & DSP_CONTROL_MASK); // Not really sure if this is correct, but it works... // Kind of a hack because DSP_CONTROL_MASK should make this bit // only viewable to dsp emulator if (val & 1 /*DSPReset*/) { g_audioDMA.AudioDMAControl.Hex = 0; } // Update DSP related flags g_dspState.DSPControl.DSPReset = tmpControl.DSPReset; g_dspState.DSPControl.DSPAssertInt = tmpControl.DSPAssertInt; g_dspState.DSPControl.DSPHalt = tmpControl.DSPHalt; g_dspState.DSPControl.DSPInit = tmpControl.DSPInit; // Interrupt (mask) g_dspState.DSPControl.AID_mask = tmpControl.AID_mask; g_dspState.DSPControl.ARAM_mask = tmpControl.ARAM_mask; g_dspState.DSPControl.DSP_mask = tmpControl.DSP_mask; // Interrupt if (tmpControl.AID) g_dspState.DSPControl.AID = 0; if (tmpControl.ARAM) g_dspState.DSPControl.ARAM = 0; if (tmpControl.DSP) g_dspState.DSPControl.DSP = 0; // unknown g_dspState.DSPControl.DSPInitCode = tmpControl.DSPInitCode; g_dspState.DSPControl.pad = tmpControl.pad; if (g_dspState.DSPControl.pad != 0) { PanicAlert("DSPInterface (w) g_dspState.DSPControl (CC00500A) gets a value with junk in the padding %08x", val); } UpdateInterrupts(); }) ); // ARAM MMIO controlling the DMA start. mmio->Register(base | AR_DMA_CNT_L, MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&g_arDMA.Cnt.Hex)), MMIO::ComplexWrite<u16>([](u32, u16 val) { g_arDMA.Cnt.Hex = (g_arDMA.Cnt.Hex & 0xFFFF0000) | (val & ~31); Do_ARAM_DMA(); }) ); // Audio DMA MMIO controlling the DMA start. mmio->Register(base | AUDIO_DMA_CONTROL_LEN, MMIO::DirectRead<u16>(&g_audioDMA.AudioDMAControl.Hex), MMIO::ComplexWrite<u16>([](u32, u16 val) { bool already_enabled = g_audioDMA.AudioDMAControl.Enable; g_audioDMA.AudioDMAControl.Hex = val; // Only load new values if were not already doing a DMA transfer, // otherwise just let the new values be autoloaded in when the // current transfer ends. if (!already_enabled && g_audioDMA.AudioDMAControl.Enable) { g_audioDMA.current_source_address = g_audioDMA.SourceAddress; g_audioDMA.remaining_blocks_count = g_audioDMA.AudioDMAControl.NumBlocks; // We make the samples ready as soon as possible void *address = Memory::GetPointer(g_audioDMA.SourceAddress); AudioCommon::SendAIBuffer((short*)address, g_audioDMA.AudioDMAControl.NumBlocks * 8); GenerateDSPInterrupt(DSP::INT_AID); } }) ); // Audio DMA blocks remaining is invalid to write to, and requires logic on // the read side. mmio->Register(base | AUDIO_DMA_BLOCKS_LEFT, MMIO::DirectRead<u16>(&g_audioDMA.remaining_blocks_count), MMIO::InvalidWrite<u16>() ); // 32 bit reads/writes are a combination of two 16 bit accesses. for (int i = 0; i < 0x1000; i += 4) { mmio->Register(base | i, MMIO::ReadToSmaller<u32>(mmio, base | i, base | (i + 2)), MMIO::WriteToSmaller<u32>(mmio, base | i, base | (i + 2)) ); } }