#include "dma.h" #include "bus.h" #include "cdrom.h" #include "common/log.h" #include "common/state_wrapper.h" #include "common/string_util.h" #include "gpu.h" #include "interrupt_controller.h" #include "mdec.h" #include "spu.h" #include "system.h" Log_SetChannel(DMA); DMA::DMA() = default; DMA::~DMA() = default; void DMA::Initialize(System* system, Bus* bus, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, SPU* spu, MDEC* mdec) { m_system = system; m_bus = bus; m_interrupt_controller = interrupt_controller; m_gpu = gpu; m_cdrom = cdrom; m_spu = spu; m_mdec = mdec; m_transfer_buffer.resize(32); for (u32 i = 0; i < NUM_CHANNELS; i++) { m_state[i].transfer_event = system->CreateTimingEvent( StringUtil::StdStringFromFormat("DMA%u Transfer", i), 1, 1, std::bind(&DMA::TransferChannel, this, static_cast(i), std::placeholders::_2), false); } } void DMA::Reset() { m_DPCR.bits = 0x07654321; m_DICR.bits = 0; for (u32 i = 0; i < NUM_CHANNELS; i++) { ChannelState& cs = m_state[i]; cs.base_address = 0; cs.block_control.bits = 0; cs.channel_control.bits = 0; cs.request = false; cs.transfer_event->Deactivate(); } } bool DMA::DoState(StateWrapper& sw) { for (u32 i = 0; i < NUM_CHANNELS; i++) { ChannelState& cs = m_state[i]; sw.Do(&cs.base_address); sw.Do(&cs.block_control.bits); sw.Do(&cs.channel_control.bits); sw.Do(&cs.request); } sw.Do(&m_DPCR.bits); sw.Do(&m_DICR.bits); if (sw.IsReading()) { for (u32 i = 0; i < NUM_CHANNELS; i++) { m_state[i].transfer_event->Deactivate(); UpdateChannelTransferEvent(static_cast(i)); } } return !sw.HasError(); } u32 DMA::ReadRegister(u32 offset) { const u32 channel_index = offset >> 4; if (channel_index < 7) { switch (offset & UINT32_C(0x0F)) { case 0x00: { Log_TracePrintf("DMA%u base address -> 0x%08X", channel_index, m_state[channel_index].base_address); return m_state[channel_index].base_address; } case 0x04: { Log_TracePrintf("DMA%u block control -> 0x%08X", channel_index, m_state[channel_index].block_control.bits); return m_state[channel_index].block_control.bits; } case 0x08: { Log_TracePrintf("DMA%u channel control -> 0x%08X", channel_index, m_state[channel_index].channel_control.bits); return m_state[channel_index].channel_control.bits; } default: break; } } else { if (offset == 0x70) { Log_TracePrintf("DPCR -> 0x%08X", m_DPCR.bits); return m_DPCR.bits; } else if (offset == 0x74) { Log_TracePrintf("DPCR -> 0x%08X", m_DPCR.bits); return m_DICR.bits; } } Log_ErrorPrintf("Unhandled register read: %02X", offset); return UINT32_C(0xFFFFFFFF); } void DMA::WriteRegister(u32 offset, u32 value) { const u32 channel_index = offset >> 4; if (channel_index < 7) { ChannelState& state = m_state[channel_index]; switch (offset & UINT32_C(0x0F)) { case 0x00: { state.base_address = value & BASE_ADDRESS_MASK; Log_TracePrintf("DMA channel %u base address <- 0x%08X", channel_index, state.base_address); return; } case 0x04: { Log_TracePrintf("DMA channel %u block control <- 0x%08X", channel_index, value); state.block_control.bits = value; UpdateChannelTransferEvent(static_cast(channel_index)); return; } case 0x08: { state.channel_control.bits = (state.channel_control.bits & ~ChannelState::ChannelControl::WRITE_MASK) | (value & ChannelState::ChannelControl::WRITE_MASK); Log_TracePrintf("DMA channel %u channel control <- 0x%08X", channel_index, state.channel_control.bits); UpdateChannelTransferEvent(static_cast(channel_index)); return; } default: break; } } else { switch (offset) { case 0x70: { Log_TracePrintf("DPCR <- 0x%08X", value); m_DPCR.bits = value; for (u32 i = 0; i < NUM_CHANNELS; i++) UpdateChannelTransferEvent(static_cast(i)); return; } case 0x74: { Log_TracePrintf("DCIR <- 0x%08X", value); m_DICR.bits = (m_DICR.bits & ~DICR_WRITE_MASK) | (value & DICR_WRITE_MASK); m_DICR.bits = m_DICR.bits & ~(value & DICR_RESET_MASK); m_DICR.UpdateMasterFlag(); return; } default: break; } } Log_ErrorPrintf("Unhandled register write: %02X <- %08X", offset, value); } void DMA::SetRequest(Channel channel, bool request) { ChannelState& cs = m_state[static_cast(channel)]; if (cs.request == request) return; cs.request = request; if (request) UpdateChannelTransferEvent(channel); } TickCount DMA::GetTransferDelay(Channel channel) const { const ChannelState& cs = m_state[static_cast(channel)]; switch (channel) { case Channel::MDECin: case Channel::MDECout: return 1; case Channel::SPU: { if (cs.channel_control.sync_mode == SyncMode::Request) return (cs.block_control.request.GetBlockCount() * (cs.block_control.request.GetBlockSize() / 2)); else return 1; } break; default: return 0; } } bool DMA::CanTransferChannel(Channel channel) const { if (!m_DPCR.GetMasterEnable(channel)) return false; const ChannelState& cs = m_state[static_cast(channel)]; if (!cs.channel_control.enable_busy) return false; if (!cs.request && channel != Channel::OTC) return false; if (cs.channel_control.sync_mode == SyncMode::Manual && !cs.channel_control.start_trigger) return false; return true; } bool DMA::CanRunAnyChannels() const { for (u32 i = 0; i < NUM_CHANNELS; i++) { if (CanTransferChannel(static_cast(i))) return true; } return false; } void DMA::UpdateIRQ() { m_DICR.UpdateMasterFlag(); if (m_DICR.master_flag) { Log_TracePrintf("Firing DMA master interrupt"); m_interrupt_controller->InterruptRequest(InterruptController::IRQ::DMA); } } void DMA::UpdateChannelTransferEvent(Channel channel) { ChannelState& cs = m_state[static_cast(channel)]; if (!CanTransferChannel(channel)) { cs.transfer_event->Deactivate(); return; } if (cs.transfer_event->IsActive()) return; const TickCount ticks = GetTransferDelay(channel); if (ticks == 0) { // immediate transfer TransferChannel(channel, 0); return; } cs.transfer_event->SetPeriodAndSchedule(ticks); } void DMA::TransferChannel(Channel channel, TickCount ticks_late) { ChannelState& cs = m_state[static_cast(channel)]; cs.transfer_event->Deactivate(); const bool copy_to_device = cs.channel_control.copy_to_device; // start/trigger bit is cleared on beginning of transfer cs.channel_control.start_trigger = false; PhysicalMemoryAddress current_address = cs.base_address; const PhysicalMemoryAddress increment = cs.channel_control.address_step_reverse ? static_cast(-4) : UINT32_C(4); switch (cs.channel_control.sync_mode) { case SyncMode::Manual: { const u32 word_count = cs.block_control.manual.GetWordCount(); Log_DebugPrintf("DMA%u: Copying %u words %s 0x%08X", static_cast(channel), word_count, copy_to_device ? "from" : "to", current_address); if (copy_to_device) TransferMemoryToDevice(channel, current_address, increment, word_count); else TransferDeviceToMemory(channel, current_address, increment, word_count); } break; case SyncMode::LinkedList: { if (!copy_to_device) { Panic("Linked list not implemented for DMA reads"); } else { Log_DebugPrintf("DMA%u: Copying linked list starting at 0x%08X to device", static_cast(channel), current_address); for (;;) { u32 header; m_bus->DispatchAccess(current_address & ADDRESS_MASK, header); const u32 word_count = header >> 24; const u32 next_address = header & UINT32_C(0x00FFFFFF); Log_TracePrintf(" .. linked list entry at 0x%08X size=%u(%u words) next=0x%08X", current_address, word_count * UINT32_C(4), word_count, next_address); if (word_count > 0) TransferMemoryToDevice(channel, (current_address + sizeof(header)) & ADDRESS_MASK, 4, word_count); // Self-referencing DMA loops.. not sure how these are happening? if (current_address == next_address) { Log_ErrorPrintf("HACK: Aborting self-referencing DMA loop @ 0x%08X. Something went wrong to generate this.", current_address); break; } current_address = next_address; if (current_address & UINT32_C(0x800000)) break; } } cs.base_address = current_address; } break; case SyncMode::Request: { Log_DebugPrintf("DMA%u: Copying %u blocks of size %u (%u total words) %s 0x%08X", static_cast(channel), cs.block_control.request.GetBlockCount(), cs.block_control.request.GetBlockSize(), cs.block_control.request.GetBlockCount() * cs.block_control.request.GetBlockSize(), copy_to_device ? "from" : "to", current_address); const u32 block_size = cs.block_control.request.GetBlockSize(); u32 blocks_remaining = cs.block_control.request.block_count; if (copy_to_device) { do { blocks_remaining--; TransferMemoryToDevice(channel, current_address & ADDRESS_MASK, increment, block_size); current_address = (current_address + (increment * block_size)); } while (cs.request && blocks_remaining > 0); } else { do { blocks_remaining--; TransferDeviceToMemory(channel, current_address & ADDRESS_MASK, increment, block_size); current_address = (current_address + (increment * block_size)); } while (cs.request && blocks_remaining > 0); } cs.base_address = current_address & BASE_ADDRESS_MASK; cs.block_control.request.block_count = blocks_remaining; // finish transfer later if the request was cleared if (blocks_remaining > 0) return; } break; default: Panic("Unimplemented sync mode"); break; } // start/busy bit is cleared on end of transfer cs.channel_control.enable_busy = false; if (m_DICR.IsIRQEnabled(channel)) { Log_DebugPrintf("Set DMA interrupt for channel %u", static_cast(channel)); m_DICR.SetIRQFlag(channel); UpdateIRQ(); } } void DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count) { // Read from memory. Wrap-around? if (m_transfer_buffer.size() < word_count) m_transfer_buffer.resize(word_count); if (increment > 0 && ((address + (increment * word_count)) & ADDRESS_MASK) > address) { m_bus->ReadWords(address, m_transfer_buffer.data(), word_count); } else { for (u32 i = 0; i < word_count; i++) { m_bus->DispatchAccess(address, m_transfer_buffer[i]); address = (address + increment) & ADDRESS_MASK; } } switch (channel) { case Channel::GPU: m_gpu->DMAWrite(m_transfer_buffer.data(), word_count); break; case Channel::SPU: m_spu->DMAWrite(m_transfer_buffer.data(), word_count); break; case Channel::MDECin: m_mdec->DMAWrite(m_transfer_buffer.data(), word_count); break; case Channel::CDROM: case Channel::MDECout: case Channel::PIO: default: Panic("Unhandled DMA channel for device write"); break; } } void DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count) { if (m_transfer_buffer.size() < word_count) m_transfer_buffer.resize(word_count); // Read from device. switch (channel) { case Channel::OTC: { // clear ordering table // this always goes in reverse, so we can generate values in reverse order and write it forwards if (((address - (4 * word_count)) & ADDRESS_MASK) < address) { const u32 end_address = (address - (4 * (word_count - 1))) & ADDRESS_MASK; u32 value = end_address; m_transfer_buffer[0] = UINT32_C(0xFFFFFF); for (u32 i = 1; i < word_count; i++) { m_transfer_buffer[i] = value; value = (value + 4) & ADDRESS_MASK; } m_bus->WriteWords(end_address, m_transfer_buffer.data(), word_count); } else { for (u32 i = 0; i < word_count; i++) { u32 value = (i == word_count - 1) ? UINT32_C(0xFFFFFFF) : ((address - 4) & ADDRESS_MASK); m_bus->DispatchAccess(address, value); address = (address - 4) & ADDRESS_MASK; } } return; } break; case Channel::GPU: m_gpu->DMARead(m_transfer_buffer.data(), word_count); break; case Channel::CDROM: m_cdrom->DMARead(m_transfer_buffer.data(), word_count); break; case Channel::SPU: m_spu->DMARead(m_transfer_buffer.data(), word_count); break; case Channel::MDECout: m_mdec->DMARead(m_transfer_buffer.data(), word_count); break; case Channel::MDECin: case Channel::PIO: default: Panic("Unhandled DMA channel for device read"); std::fill_n(m_transfer_buffer.begin(), word_count, UINT32_C(0xFFFFFFFF)); break; } if (increment > 0 && ((address + (increment * word_count)) & ADDRESS_MASK) > address) { m_bus->WriteWords(address, m_transfer_buffer.data(), word_count); } else { for (u32 i = 0; i < word_count; i++) { m_bus->DispatchAccess(address, m_transfer_buffer[i]); address = (address + increment) & ADDRESS_MASK; } } }