diff --git a/Source/Core/Core/HW/EXI.cpp b/Source/Core/Core/HW/EXI.cpp index ab11dcfdc6..a6bdcc392d 100644 --- a/Source/Core/Core/HW/EXI.cpp +++ b/Source/Core/Core/HW/EXI.cpp @@ -97,6 +97,11 @@ void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 devi CoreTiming::ScheduleEvent_Threadsafe(500000000, changeDevice, ((u64)channel << 32) | ((u64)device_type << 16) | device_num); } +CEXIChannel* GetChannel(u32 index) +{ + return g_Channels[index]; +} + IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex) { for (auto& channel : g_Channels) diff --git a/Source/Core/Core/HW/EXI.h b/Source/Core/Core/HW/EXI.h index 23cda914d3..c54118716e 100644 --- a/Source/Core/Core/HW/EXI.h +++ b/Source/Core/Core/HW/EXI.h @@ -31,6 +31,9 @@ void UpdateInterrupts(); void ChangeDeviceCallback(u64 userdata, int cyclesLate); void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num); + +CEXIChannel* GetChannel(u32 index); + IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1); } // end of namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI_Channel.cpp b/Source/Core/Core/HW/EXI_Channel.cpp index 5e1a88ca9f..4822aedf37 100644 --- a/Source/Core/Core/HW/EXI_Channel.cpp +++ b/Source/Core/Core/HW/EXI_Channel.cpp @@ -123,7 +123,6 @@ void CEXIChannel::RegisterMMIO(MMIO::Mapping* mmio, u32 base) case EXI_READWRITE: pDevice->ImmReadWrite(m_ImmData, m_Control.TLEN + 1); break; default: _dbg_assert_msg_(EXPANSIONINTERFACE,0,"EXI Imm: Unknown transfer type %i", m_Control.RW); } - m_Control.TSTART = 0; } else { @@ -134,14 +133,13 @@ void CEXIChannel::RegisterMMIO(MMIO::Mapping* mmio, u32 base) case EXI_WRITE: pDevice->DMAWrite(m_DMAMemoryAddress, m_DMALength); break; default: _dbg_assert_msg_(EXPANSIONINTERFACE,0,"EXI DMA: Unknown transfer type %i", m_Control.RW); } - m_Control.TSTART = 0; } - if (!m_Control.TSTART) // completed ! - { - m_Status.TCINT = 1; - CoreTiming::ScheduleEvent_Threadsafe_Immediate(updateInterrupts, 0); - } + m_Control.TSTART = 0; + + // Check if device needs specific timing, otherwise just complete transfer immediately + if (!pDevice->UseDelayedTransferCompletion()) + SendTransferComplete(); } }) ); @@ -152,6 +150,12 @@ void CEXIChannel::RegisterMMIO(MMIO::Mapping* mmio, u32 base) ); } +void CEXIChannel::SendTransferComplete() +{ + m_Status.TCINT = 1; + CoreTiming::ScheduleEvent_Threadsafe_Immediate(updateInterrupts, 0); +} + void CEXIChannel::RemoveDevices() { for (auto& device : m_pDevices) diff --git a/Source/Core/Core/HW/EXI_Channel.h b/Source/Core/Core/HW/EXI_Channel.h index 4156a40439..c106a5759d 100644 --- a/Source/Core/Core/HW/EXI_Channel.h +++ b/Source/Core/Core/HW/EXI_Channel.h @@ -86,6 +86,7 @@ private: int updateInterrupts; static void UpdateInterrupts(u64 userdata, int cyclesLate); + public: // get device IEXIDevice* GetDevice(const u8 _CHIP_SELECT); @@ -96,6 +97,8 @@ public: void RegisterMMIO(MMIO::Mapping* mmio, u32 base); + void SendTransferComplete(); + void AddDevice(const TEXIDevices device_type, const int device_num); void AddDevice(IEXIDevice* pDevice, const int device_num, bool notifyPresenceChanged=true); diff --git a/Source/Core/Core/HW/EXI_Device.h b/Source/Core/Core/HW/EXI_Device.h index 58b3a426cd..b09732eb06 100644 --- a/Source/Core/Core/HW/EXI_Device.h +++ b/Source/Core/Core/HW/EXI_Device.h @@ -38,6 +38,8 @@ public: virtual void DMAWrite(u32 _uAddr, u32 _uSize); virtual void DMARead (u32 _uAddr, u32 _uSize); + virtual bool UseDelayedTransferCompletion() {return false;} + virtual bool IsPresent() {return false;} virtual void SetCS(int) {} virtual void DoState(PointerWrap&) {} diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp index 711c1e11b3..bc903ede8a 100644 --- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp +++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp @@ -11,6 +11,7 @@ #include "Core/CoreTiming.h" #include "Core/Movie.h" #include "Core/HW/EXI.h" +#include "Core/HW/EXI_Channel.h" #include "Core/HW/EXI_Device.h" #include "Core/HW/EXI_DeviceMemoryCard.h" #include "Core/HW/GCMemcard.h" @@ -18,6 +19,7 @@ #include "Core/HW/GCMemcardRaw.h" #include "Core/HW/Memmap.h" #include "Core/HW/Sram.h" +#include "Core/HW/SystemTimers.h" #include "DiscIO/NANDContentLoader.h" #define MC_STATUS_BUSY 0x80 @@ -28,6 +30,9 @@ #define MC_STATUS_READY 0x01 #define SIZE_TO_Mb (1024 * 8 * 16) +static const u32 MC_TRANSFER_RATE_READ = 512 * 1024; +static const u32 MC_TRANSFER_RATE_WRITE = (u32)(96.125f * 1024.0f); + void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate) { // note that userdata is forbidden to be a pointer, due to the implementation of EventDoState @@ -49,6 +54,16 @@ void CEXIMemoryCard::CmdDoneCallback(u64 userdata, int cyclesLate) pThis->CmdDone(); } +void CEXIMemoryCard::TransferCompleteCallback(u64 userdata, int cyclesLate) +{ + int card_index = (int)userdata; + CEXIMemoryCard* pThis = (CEXIMemoryCard*)ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARD, card_index); + if (pThis == nullptr) + pThis = (CEXIMemoryCard*)ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARDFOLDER, card_index); + if (pThis) + pThis->TransferComplete(); +} + CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder) : card_index(index) , m_bDirty(false) @@ -56,6 +71,7 @@ CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder) // we're potentially leaking events here, since there's no UnregisterEvent until emu shutdown, but I guess it's inconsequential et_this_card = CoreTiming::RegisterEvent((index == 0) ? "memcardFlushA" : "memcardFlushB", FlushCallback); et_cmd_done = CoreTiming::RegisterEvent((index == 0) ? "memcardDoneA" : "memcardDoneB", CmdDoneCallback); + et_transfer_complete = CoreTiming::RegisterEvent((index == 0) ? "memcardTransferCompleteA" : "memcardTransferCompleteB", TransferCompleteCallback); interruptSwitch = 0; m_bInterruptSet = 0; @@ -185,6 +201,11 @@ CEXIMemoryCard::~CEXIMemoryCard() memorycard.reset(); } +bool CEXIMemoryCard::UseDelayedTransferCompletion() +{ + return true; +} + bool CEXIMemoryCard::IsPresent() { return true; @@ -199,6 +220,12 @@ void CEXIMemoryCard::CmdDone() m_bDirty = true; } +void CEXIMemoryCard::TransferComplete() +{ + // Transfer complete, send interrupt + ExpansionInterface::GetChannel(card_index)->SendTransferComplete(); +} + void CEXIMemoryCard::CmdDoneLater(u64 cycles) { CoreTiming::RemoveEvent(et_cmd_done); @@ -473,10 +500,14 @@ IEXIDevice* CEXIMemoryCard::FindDevice(TEXIDevices device_type, int customIndex) void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize) { memorycard->Read(address, _uSize, Memory::GetPointer(_uAddr)); + #ifdef _DEBUG if ((address + _uSize) % BLOCK_SIZE == 0) INFO_LOG(EXPANSIONINTERFACE, "reading from block: %x", address / BLOCK_SIZE); #endif + + // Schedule transfer complete later based on read speed + CoreTiming::ScheduleEvent(_uSize * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_READ), et_transfer_complete, (u64)card_index); } // DMA write are preceded by all of the necessary setup via IMMWrite @@ -499,4 +530,7 @@ void CEXIMemoryCard::DMAWrite(u32 _uAddr, u32 _uSize) CoreTiming::RemoveEvent(et_this_card); CoreTiming::ScheduleEvent(500000000, et_this_card, (u64)card_index); } + + // Schedule transfer complete later based on write speed + CoreTiming::ScheduleEvent(_uSize * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE), et_transfer_complete, (u64)card_index); } diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.h b/Source/Core/Core/HW/EXI_DeviceMemoryCard.h index 12747268ed..02e5d4d7a1 100644 --- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.h +++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.h @@ -13,6 +13,7 @@ public: virtual ~CEXIMemoryCard(); void SetCS(int cs) override; bool IsInterruptSet() override; + bool UseDelayedTransferCompletion() override; bool IsPresent() override; void DoState(PointerWrap &p) override; void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) override; @@ -30,12 +31,18 @@ private: // Scheduled when a command that required delayed end signaling is done. static void CmdDoneCallback(u64 userdata, int cyclesLate); + // Scheduled when memory card is done transferring data + static void TransferCompleteCallback(u64 userdata, int cyclesLate); + // Flushes the memory card contents to disk. void Flush(bool exiting = false); // Signals that the command that was previously executed is now done. void CmdDone(); + // Signals that the transfer that was previously executed is now done. + void TransferComplete(); + // Variant of CmdDone which schedules an event later in the future to complete the command. void CmdDoneLater(u64 cycles); @@ -59,7 +66,7 @@ private: }; int card_index; - int et_this_card, et_cmd_done; + int et_this_card, et_cmd_done, et_transfer_complete; //! memory card state // STATE_TO_SAVE