DMA: Properly handle infinite linked lists, add cycle stealing
Fixes Tekken 2, Hot Wheels Turbo Racing, probably others.
This commit is contained in:
parent
07e8ddcae2
commit
9d1eb321ec
116
src/core/dma.cpp
116
src/core/dma.cpp
|
@ -26,12 +26,12 @@ void DMA::Initialize(System* system, Bus* bus, InterruptController* interrupt_co
|
||||||
m_spu = spu;
|
m_spu = spu;
|
||||||
m_mdec = mdec;
|
m_mdec = mdec;
|
||||||
m_transfer_buffer.resize(32);
|
m_transfer_buffer.resize(32);
|
||||||
|
m_unhalt_event = system->CreateTimingEvent("DMA Transfer Unhalt", 1, m_max_slice_ticks,
|
||||||
|
std::bind(&DMA::UnhaltTransfer, this, std::placeholders::_1), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DMA::Reset()
|
void DMA::Reset()
|
||||||
{
|
{
|
||||||
m_DPCR.bits = 0x07654321;
|
|
||||||
m_DICR.bits = 0;
|
|
||||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||||
{
|
{
|
||||||
ChannelState& cs = m_state[i];
|
ChannelState& cs = m_state[i];
|
||||||
|
@ -40,10 +40,18 @@ void DMA::Reset()
|
||||||
cs.channel_control.bits = 0;
|
cs.channel_control.bits = 0;
|
||||||
cs.request = false;
|
cs.request = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_DPCR.bits = 0x07654321;
|
||||||
|
m_DICR.bits = 0;
|
||||||
|
|
||||||
|
m_halt_ticks_remaining = 0;
|
||||||
|
m_unhalt_event->Deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DMA::DoState(StateWrapper& sw)
|
bool DMA::DoState(StateWrapper& sw)
|
||||||
{
|
{
|
||||||
|
sw.Do(&m_halt_ticks_remaining);
|
||||||
|
|
||||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||||
{
|
{
|
||||||
ChannelState& cs = m_state[i];
|
ChannelState& cs = m_state[i];
|
||||||
|
@ -56,6 +64,14 @@ bool DMA::DoState(StateWrapper& sw)
|
||||||
sw.Do(&m_DPCR.bits);
|
sw.Do(&m_DPCR.bits);
|
||||||
sw.Do(&m_DICR.bits);
|
sw.Do(&m_DICR.bits);
|
||||||
|
|
||||||
|
if (sw.IsReading())
|
||||||
|
{
|
||||||
|
if (m_halt_ticks_remaining > 0)
|
||||||
|
m_unhalt_event->SetIntervalAndSchedule(m_halt_ticks_remaining);
|
||||||
|
else
|
||||||
|
m_unhalt_event->Deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
return !sw.HasError();
|
return !sw.HasError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,8 +137,6 @@ void DMA::WriteRegister(u32 offset, u32 value)
|
||||||
{
|
{
|
||||||
Log_TracePrintf("DMA channel %u block control <- 0x%08X", channel_index, value);
|
Log_TracePrintf("DMA channel %u block control <- 0x%08X", channel_index, value);
|
||||||
state.block_control.bits = value;
|
state.block_control.bits = value;
|
||||||
if (CanTransferChannel(static_cast<Channel>(channel_index)))
|
|
||||||
TransferChannel(static_cast<Channel>(channel_index));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +150,7 @@ void DMA::WriteRegister(u32 offset, u32 value)
|
||||||
if (static_cast<Channel>(channel_index) == Channel::OTC)
|
if (static_cast<Channel>(channel_index) == Channel::OTC)
|
||||||
SetRequest(static_cast<Channel>(channel_index), state.channel_control.start_trigger);
|
SetRequest(static_cast<Channel>(channel_index), state.channel_control.start_trigger);
|
||||||
|
|
||||||
if (CanTransferChannel(static_cast<Channel>(channel_index)))
|
if (!IsTransferHalted() && CanTransferChannel(static_cast<Channel>(channel_index)))
|
||||||
TransferChannel(static_cast<Channel>(channel_index));
|
TransferChannel(static_cast<Channel>(channel_index));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -153,10 +167,16 @@ void DMA::WriteRegister(u32 offset, u32 value)
|
||||||
{
|
{
|
||||||
Log_TracePrintf("DPCR <- 0x%08X", value);
|
Log_TracePrintf("DPCR <- 0x%08X", value);
|
||||||
m_DPCR.bits = value;
|
m_DPCR.bits = value;
|
||||||
|
if (!IsTransferHalted())
|
||||||
|
{
|
||||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||||
{
|
{
|
||||||
if (CanTransferChannel(static_cast<Channel>(i)))
|
if (CanTransferChannel(static_cast<Channel>(i)))
|
||||||
TransferChannel(static_cast<Channel>(i));
|
{
|
||||||
|
if (!TransferChannel(static_cast<Channel>(i)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -185,7 +205,7 @@ void DMA::SetRequest(Channel channel, bool request)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
cs.request = request;
|
cs.request = request;
|
||||||
if (CanTransferChannel(channel))
|
if (!IsTransferHalted() && CanTransferChannel(channel))
|
||||||
TransferChannel(channel);
|
TransferChannel(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +221,11 @@ bool DMA::CanTransferChannel(Channel channel) const
|
||||||
return cs.request;
|
return cs.request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DMA::IsTransferHalted() const
|
||||||
|
{
|
||||||
|
return m_unhalt_event->IsActive();
|
||||||
|
}
|
||||||
|
|
||||||
void DMA::UpdateIRQ()
|
void DMA::UpdateIRQ()
|
||||||
{
|
{
|
||||||
m_DICR.UpdateMasterFlag();
|
m_DICR.UpdateMasterFlag();
|
||||||
|
@ -211,7 +236,7 @@ void DMA::UpdateIRQ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DMA::TransferChannel(Channel channel)
|
bool DMA::TransferChannel(Channel channel)
|
||||||
{
|
{
|
||||||
ChannelState& cs = m_state[static_cast<u32>(channel)];
|
ChannelState& cs = m_state[static_cast<u32>(channel)];
|
||||||
|
|
||||||
|
@ -229,15 +254,20 @@ void DMA::TransferChannel(Channel channel)
|
||||||
const u32 word_count = cs.block_control.manual.GetWordCount();
|
const u32 word_count = cs.block_control.manual.GetWordCount();
|
||||||
Log_DebugPrintf("DMA%u: Copying %u words %s 0x%08X", static_cast<u32>(channel), word_count,
|
Log_DebugPrintf("DMA%u: Copying %u words %s 0x%08X", static_cast<u32>(channel), word_count,
|
||||||
copy_to_device ? "from" : "to", current_address & ADDRESS_MASK);
|
copy_to_device ? "from" : "to", current_address & ADDRESS_MASK);
|
||||||
|
|
||||||
|
TickCount used_ticks;
|
||||||
if (copy_to_device)
|
if (copy_to_device)
|
||||||
TransferMemoryToDevice(channel, current_address & ADDRESS_MASK, increment, word_count);
|
used_ticks = TransferMemoryToDevice(channel, current_address & ADDRESS_MASK, increment, word_count);
|
||||||
else
|
else
|
||||||
TransferDeviceToMemory(channel, current_address & ADDRESS_MASK, increment, word_count);
|
used_ticks = TransferDeviceToMemory(channel, current_address & ADDRESS_MASK, increment, word_count);
|
||||||
|
|
||||||
|
m_system->StallCPU(used_ticks);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SyncMode::LinkedList:
|
case SyncMode::LinkedList:
|
||||||
{
|
{
|
||||||
|
TickCount used_ticks = 0;
|
||||||
if (!copy_to_device)
|
if (!copy_to_device)
|
||||||
{
|
{
|
||||||
Panic("Linked list not implemented for DMA reads");
|
Panic("Linked list not implemented for DMA reads");
|
||||||
|
@ -248,17 +278,21 @@ void DMA::TransferChannel(Channel channel)
|
||||||
current_address & ADDRESS_MASK);
|
current_address & ADDRESS_MASK);
|
||||||
|
|
||||||
u8* ram_pointer = m_bus->GetRAM();
|
u8* ram_pointer = m_bus->GetRAM();
|
||||||
while (cs.request)
|
while (cs.request && used_ticks < m_max_slice_ticks)
|
||||||
{
|
{
|
||||||
u32 header;
|
u32 header;
|
||||||
std::memcpy(&header, &ram_pointer[current_address & ADDRESS_MASK], sizeof(header));
|
std::memcpy(&header, &ram_pointer[current_address & ADDRESS_MASK], sizeof(header));
|
||||||
|
used_ticks++;
|
||||||
|
|
||||||
const u32 word_count = header >> 24;
|
const u32 word_count = header >> 24;
|
||||||
const u32 next_address = header & UINT32_C(0x00FFFFFF);
|
const u32 next_address = header & UINT32_C(0x00FFFFFF);
|
||||||
Log_TracePrintf(" .. linked list entry at 0x%08X size=%u(%u words) next=0x%08X",
|
Log_TracePrintf(" .. linked list entry at 0x%08X size=%u(%u words) next=0x%08X",
|
||||||
current_address & ADDRESS_MASK, word_count * UINT32_C(4), word_count, next_address);
|
current_address & ADDRESS_MASK, word_count * UINT32_C(4), word_count, next_address);
|
||||||
if (word_count > 0)
|
if (word_count > 0)
|
||||||
|
{
|
||||||
|
used_ticks +=
|
||||||
TransferMemoryToDevice(channel, (current_address + sizeof(header)) & ADDRESS_MASK, 4, word_count);
|
TransferMemoryToDevice(channel, (current_address + sizeof(header)) & ADDRESS_MASK, 4, word_count);
|
||||||
|
}
|
||||||
|
|
||||||
current_address = next_address;
|
current_address = next_address;
|
||||||
if (current_address & UINT32_C(0x800000))
|
if (current_address & UINT32_C(0x800000))
|
||||||
|
@ -267,11 +301,20 @@ void DMA::TransferChannel(Channel channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.base_address = current_address;
|
cs.base_address = current_address;
|
||||||
|
m_system->StallCPU(used_ticks);
|
||||||
|
|
||||||
|
if (used_ticks >= m_max_slice_ticks)
|
||||||
|
{
|
||||||
|
// stall the transfer for a bit if we ran for too long
|
||||||
|
//Log_WarningPrintf("breaking dma chain at 0x%08X", current_address);
|
||||||
|
HaltTransfer(m_halt_ticks);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if ((current_address & UINT32_C(0x800000)) == 0)
|
if ((current_address & UINT32_C(0x800000)) == 0)
|
||||||
{
|
{
|
||||||
// linked list not yet complete
|
// linked list not yet complete
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -285,13 +328,14 @@ void DMA::TransferChannel(Channel channel)
|
||||||
|
|
||||||
const u32 block_size = cs.block_control.request.GetBlockSize();
|
const u32 block_size = cs.block_control.request.GetBlockSize();
|
||||||
u32 blocks_remaining = cs.block_control.request.GetBlockCount();
|
u32 blocks_remaining = cs.block_control.request.GetBlockCount();
|
||||||
|
TickCount used_ticks = 0;
|
||||||
|
|
||||||
if (copy_to_device)
|
if (copy_to_device)
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
blocks_remaining--;
|
blocks_remaining--;
|
||||||
TransferMemoryToDevice(channel, current_address & ADDRESS_MASK, increment, block_size);
|
used_ticks += TransferMemoryToDevice(channel, current_address & ADDRESS_MASK, increment, block_size);
|
||||||
current_address = (current_address + (increment * block_size));
|
current_address = (current_address + (increment * block_size));
|
||||||
} while (cs.request && blocks_remaining > 0);
|
} while (cs.request && blocks_remaining > 0);
|
||||||
}
|
}
|
||||||
|
@ -300,17 +344,18 @@ void DMA::TransferChannel(Channel channel)
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
blocks_remaining--;
|
blocks_remaining--;
|
||||||
TransferDeviceToMemory(channel, current_address & ADDRESS_MASK, increment, block_size);
|
used_ticks += TransferDeviceToMemory(channel, current_address & ADDRESS_MASK, increment, block_size);
|
||||||
current_address = (current_address + (increment * block_size));
|
current_address = (current_address + (increment * block_size));
|
||||||
} while (cs.request && blocks_remaining > 0);
|
} while (cs.request && blocks_remaining > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.base_address = current_address & BASE_ADDRESS_MASK;
|
cs.base_address = current_address & BASE_ADDRESS_MASK;
|
||||||
cs.block_control.request.block_count = blocks_remaining;
|
cs.block_control.request.block_count = blocks_remaining;
|
||||||
|
m_system->StallCPU(used_ticks);
|
||||||
|
|
||||||
// finish transfer later if the request was cleared
|
// finish transfer later if the request was cleared
|
||||||
if (blocks_remaining > 0)
|
if (blocks_remaining > 0)
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -327,9 +372,41 @@ void DMA::TransferChannel(Channel channel)
|
||||||
m_DICR.SetIRQFlag(channel);
|
m_DICR.SetIRQFlag(channel);
|
||||||
UpdateIRQ();
|
UpdateIRQ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count)
|
void DMA::HaltTransfer(TickCount duration)
|
||||||
|
{
|
||||||
|
m_halt_ticks_remaining += duration;
|
||||||
|
Log_DebugPrintf("Halting DMA for %d ticks", m_halt_ticks_remaining);
|
||||||
|
|
||||||
|
DebugAssert(!m_unhalt_event->IsActive());
|
||||||
|
m_unhalt_event->SetIntervalAndSchedule(m_halt_ticks_remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMA::UnhaltTransfer(TickCount ticks)
|
||||||
|
{
|
||||||
|
Log_DebugPrintf("Resuming DMA after %d ticks, %d ticks late", ticks, -(m_halt_ticks_remaining - ticks));
|
||||||
|
m_halt_ticks_remaining -= ticks;
|
||||||
|
m_unhalt_event->Deactivate();
|
||||||
|
|
||||||
|
// TODO: Use channel priority. But doing it in ascending order is probably good enough.
|
||||||
|
// Main thing is that OTC happens after GPU, because otherwise it'll wipe out the LL.
|
||||||
|
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||||
|
{
|
||||||
|
if (CanTransferChannel(static_cast<Channel>(i)))
|
||||||
|
{
|
||||||
|
if (!TransferChannel(static_cast<Channel>(i)))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't run too long, so reset timer.
|
||||||
|
m_halt_ticks_remaining = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count)
|
||||||
{
|
{
|
||||||
const u32* src_pointer = reinterpret_cast<u32*>(m_bus->GetRAM() + address);
|
const u32* src_pointer = reinterpret_cast<u32*>(m_bus->GetRAM() + address);
|
||||||
if (static_cast<s32>(increment) < 0 || ((address + (increment * word_count)) & ADDRESS_MASK) <= address)
|
if (static_cast<s32>(increment) < 0 || ((address + (increment * word_count)) & ADDRESS_MASK) <= address)
|
||||||
|
@ -362,9 +439,11 @@ void DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u3
|
||||||
Panic("Unhandled DMA channel for device write");
|
Panic("Unhandled DMA channel for device write");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return m_bus->GetDMARAMTickCount(word_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count)
|
TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count)
|
||||||
{
|
{
|
||||||
if (channel == Channel::OTC)
|
if (channel == Channel::OTC)
|
||||||
{
|
{
|
||||||
|
@ -381,7 +460,7 @@ void DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u3
|
||||||
const u32 terminator = UINT32_C(0xFFFFFFF);
|
const u32 terminator = UINT32_C(0xFFFFFFF);
|
||||||
std::memcpy(&ram_pointer[address], &terminator, sizeof(terminator));
|
std::memcpy(&ram_pointer[address], &terminator, sizeof(terminator));
|
||||||
m_bus->InvalidateCodePages(address, word_count);
|
m_bus->InvalidateCodePages(address, word_count);
|
||||||
return;
|
return m_bus->GetDMARAMTickCount(word_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32* dest_pointer = reinterpret_cast<u32*>(&m_bus->m_ram[address]);
|
u32* dest_pointer = reinterpret_cast<u32*>(&m_bus->m_ram[address]);
|
||||||
|
@ -429,4 +508,5 @@ void DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u3
|
||||||
}
|
}
|
||||||
|
|
||||||
m_bus->InvalidateCodePages(address, word_count);
|
m_bus->InvalidateCodePages(address, word_count);
|
||||||
|
return m_bus->GetDMARAMTickCount(word_count);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,15 +66,19 @@ private:
|
||||||
|
|
||||||
// is everything enabled for a channel to operate?
|
// is everything enabled for a channel to operate?
|
||||||
bool CanTransferChannel(Channel channel) const;
|
bool CanTransferChannel(Channel channel) const;
|
||||||
|
bool IsTransferHalted() const;
|
||||||
void UpdateIRQ();
|
void UpdateIRQ();
|
||||||
|
|
||||||
void TransferChannel(Channel channel);
|
// returns false if the DMA should now be halted
|
||||||
|
bool TransferChannel(Channel channel);
|
||||||
|
void HaltTransfer(TickCount duration);
|
||||||
|
void UnhaltTransfer(TickCount ticks);
|
||||||
|
|
||||||
// from device -> memory
|
// from device -> memory
|
||||||
void TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count);
|
TickCount TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count);
|
||||||
|
|
||||||
// from memory -> device
|
// from memory -> device
|
||||||
void TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count);
|
TickCount TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count);
|
||||||
|
|
||||||
System* m_system = nullptr;
|
System* m_system = nullptr;
|
||||||
Bus* m_bus = nullptr;
|
Bus* m_bus = nullptr;
|
||||||
|
@ -84,7 +88,13 @@ private:
|
||||||
SPU* m_spu = nullptr;
|
SPU* m_spu = nullptr;
|
||||||
MDEC* m_mdec = nullptr;
|
MDEC* m_mdec = nullptr;
|
||||||
|
|
||||||
|
// configuration
|
||||||
|
TickCount m_max_slice_ticks = 1000;
|
||||||
|
TickCount m_halt_ticks = 100;
|
||||||
|
|
||||||
std::vector<u32> m_transfer_buffer;
|
std::vector<u32> m_transfer_buffer;
|
||||||
|
std::unique_ptr<TimingEvent> m_unhalt_event;
|
||||||
|
TickCount m_halt_ticks_remaining = 0;
|
||||||
|
|
||||||
struct ChannelState
|
struct ChannelState
|
||||||
{
|
{
|
||||||
|
|
|
@ -394,7 +394,7 @@ protected:
|
||||||
#if 0
|
#if 0
|
||||||
const u32 draw_ticks = static_cast<u32>((std::abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) + 1u) / 2u);
|
const u32 draw_ticks = static_cast<u32>((std::abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) + 1u) / 2u);
|
||||||
#else
|
#else
|
||||||
const u32 draw_ticks = ((width + 1) / 2) * height;
|
const u32 draw_ticks = ((width + 2) / 3) * height;
|
||||||
#endif
|
#endif
|
||||||
AddCommandTicks(draw_ticks);
|
AddCommandTicks(draw_ticks);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||||
static constexpr u32 SAVE_STATE_VERSION = 28;
|
static constexpr u32 SAVE_STATE_VERSION = 29;
|
||||||
|
|
||||||
#pragma pack(push, 4)
|
#pragma pack(push, 4)
|
||||||
struct SAVE_STATE_HEADER
|
struct SAVE_STATE_HEADER
|
||||||
|
|
|
@ -766,8 +766,10 @@ bool System::SetExpansionROM(const char* filename)
|
||||||
void System::StallCPU(TickCount ticks)
|
void System::StallCPU(TickCount ticks)
|
||||||
{
|
{
|
||||||
m_cpu->AddPendingTicks(ticks);
|
m_cpu->AddPendingTicks(ticks);
|
||||||
|
#if 0
|
||||||
if (m_cpu->GetPendingTicks() >= m_cpu->GetDowncount() && !m_running_events)
|
if (m_cpu->GetPendingTicks() >= m_cpu->GetDowncount() && !m_running_events)
|
||||||
RunEvents();
|
RunEvents();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller* System::GetController(u32 slot) const
|
Controller* System::GetController(u32 slot) const
|
||||||
|
|
Loading…
Reference in New Issue