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 9cb72c7db0..4822aedf37 100644 --- a/Source/Core/Core/HW/EXI_Channel.cpp +++ b/Source/Core/Core/HW/EXI_Channel.cpp @@ -31,7 +31,7 @@ CEXIChannel::CEXIChannel(u32 ChannelId) : m_Status.CHIP_SELECT = 1; for (auto& device : m_pDevices) - device.reset(EXIDevice_Create(EXIDEVICE_NONE, this)); + device.reset(EXIDevice_Create(EXIDEVICE_NONE, m_ChannelId)); updateInterrupts = CoreTiming::RegisterEvent("EXIInterrupt", UpdateInterrupts); } @@ -137,7 +137,8 @@ void CEXIChannel::RegisterMMIO(MMIO::Mapping* mmio, u32 base) m_Control.TSTART = 0; - if (pDevice->m_deviceType != EXIDEVICE_MEMORYCARD) + // Check if device needs specific timing, otherwise just complete transfer immediately + if (!pDevice->UseDelayedTransferCompletion()) SendTransferComplete(); } }) @@ -163,7 +164,7 @@ void CEXIChannel::RemoveDevices() void CEXIChannel::AddDevice(const TEXIDevices device_type, const int device_num) { - IEXIDevice* pNewDevice = EXIDevice_Create(device_type, this); + IEXIDevice* pNewDevice = EXIDevice_Create(device_type, m_ChannelId); AddDevice(pNewDevice, device_num); } @@ -235,7 +236,7 @@ void CEXIChannel::DoState(PointerWrap &p) IEXIDevice* pDevice = m_pDevices[d].get(); TEXIDevices type = pDevice->m_deviceType; p.Do(type); - IEXIDevice* pSaveDevice = (type == pDevice->m_deviceType) ? pDevice : EXIDevice_Create(type, this); + IEXIDevice* pSaveDevice = (type == pDevice->m_deviceType) ? pDevice : EXIDevice_Create(type, m_ChannelId); pSaveDevice->DoState(p); if (pSaveDevice != pDevice) { diff --git a/Source/Core/Core/HW/EXI_Channel.h b/Source/Core/Core/HW/EXI_Channel.h index 76cabf2d47..c106a5759d 100644 --- a/Source/Core/Core/HW/EXI_Channel.h +++ b/Source/Core/Core/HW/EXI_Channel.h @@ -80,9 +80,13 @@ private: std::unique_ptr m_pDevices[NUM_DEVICES]; + // Since channels operate a bit differently from each other + u32 m_ChannelId; + int updateInterrupts; static void UpdateInterrupts(u64 userdata, int cyclesLate); + public: // get device IEXIDevice* GetDevice(const u8 _CHIP_SELECT); @@ -107,7 +111,4 @@ public: // This should only be used to transition interrupts from SP1 to Channel 2 void SetEXIINT(bool exiint) { m_Status.EXIINT = !!exiint; } - - // Since channels operate a bit differently from each other - u32 m_ChannelId; }; diff --git a/Source/Core/Core/HW/EXI_Device.cpp b/Source/Core/Core/HW/EXI_Device.cpp index 60bddbb80b..dbd2807112 100644 --- a/Source/Core/Core/HW/EXI_Device.cpp +++ b/Source/Core/Core/HW/EXI_Device.cpp @@ -5,7 +5,6 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" -#include "Core/HW/EXI_Channel.h" #include "Core/HW/EXI_Device.h" #include "Core/HW/EXI_DeviceAD16.h" #include "Core/HW/EXI_DeviceAMBaseboard.h" @@ -89,7 +88,7 @@ public: // F A C T O R Y -IEXIDevice* EXIDevice_Create(TEXIDevices device_type, CEXIChannel* channel) +IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num) { IEXIDevice* result = nullptr; @@ -103,8 +102,7 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, CEXIChannel* channel) case EXIDEVICE_MEMORYCARDFOLDER: { bool gci_folder = (device_type == EXIDEVICE_MEMORYCARDFOLDER); - device_type = EXIDEVICE_MEMORYCARD; - result = new CEXIMemoryCard(channel->m_ChannelId, gci_folder); + result = new CEXIMemoryCard(channel_num, gci_folder); break; } case EXIDEVICE_MASKROM: @@ -116,7 +114,7 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, CEXIChannel* channel) break; case EXIDEVICE_MIC: - result = new CEXIMic(channel->m_ChannelId); + result = new CEXIMic(channel_num); break; case EXIDEVICE_ETH: @@ -140,7 +138,5 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, CEXIChannel* channel) if (result != nullptr) result->m_deviceType = device_type; - result->m_channel = channel; - return result; } diff --git a/Source/Core/Core/HW/EXI_Device.h b/Source/Core/Core/HW/EXI_Device.h index d9e388b8fa..b09732eb06 100644 --- a/Source/Core/Core/HW/EXI_Device.h +++ b/Source/Core/Core/HW/EXI_Device.h @@ -7,8 +7,6 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -class CEXIChannel; - enum TEXIDevices { EXIDEVICE_DUMMY, @@ -40,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&) {} @@ -50,12 +50,9 @@ public: virtual bool IsInterruptSet() {return false;} virtual ~IEXIDevice() {} - // Pointer to channel attached to the device - CEXIChannel* m_channel; - // for savestates. storing it here seemed cleaner than requiring each implementation to report its type. // I know this class is set up like an interface, but no code requires it to be strictly such. TEXIDevices m_deviceType; }; -IEXIDevice* EXIDevice_Create(const TEXIDevices device_type, CEXIChannel* channel); +IEXIDevice* EXIDevice_Create(const TEXIDevices device_type, const int channel_num); diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp index 618c4e441e..a49fa79c4f 100644 --- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp +++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp @@ -71,7 +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) ? "memcardTransferCompleteB" : "memcardTransferCompleteB", TransferCompleteCallback); + et_transfer_complete = CoreTiming::RegisterEvent((index == 0) ? "memcardTransferCompleteA" : "memcardTransferCompleteB", TransferCompleteCallback); interruptSwitch = 0; m_bInterruptSet = 0; @@ -201,6 +201,11 @@ CEXIMemoryCard::~CEXIMemoryCard() memorycard.reset(); } +bool CEXIMemoryCard::UseDelayedTransferCompletion() +{ + return true; +} + bool CEXIMemoryCard::IsPresent() { return true; @@ -218,12 +223,7 @@ void CEXIMemoryCard::CmdDone() void CEXIMemoryCard::TransferComplete() { // Transfer complete, send interrupt - m_channel->SendTransferComplete(); - - // Page written to memory card, not just to buffer - let's schedule a flush 0.5b cycles into the future (1 sec) - // But first we unschedule already scheduled flushes - no point in flushing once per page for a large write. - CoreTiming::RemoveEvent(et_this_card); - CoreTiming::ScheduleEvent(500000000, et_this_card, (u64)card_index); + ExpansionInterface::GetChannel(card_index)->SendTransferComplete(); } void CEXIMemoryCard::CmdDoneLater(u64 cycles) @@ -500,6 +500,13 @@ 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); } @@ -508,5 +515,22 @@ void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize) void CEXIMemoryCard::DMAWrite(u32 _uAddr, u32 _uSize) { memorycard->Write(address, _uSize, Memory::GetPointer(_uAddr)); + + // At the end of writing to a block flush to disk + // memory card blocks are always(?) written as a whole, + // but the dma calls are by page size (0x200) at a time + // just in case this is the last block that the game will be writing for a while + if (((address + _uSize) % BLOCK_SIZE) == 0) + { + INFO_LOG(EXPANSIONINTERFACE, "writing to block: %x", address / BLOCK_SIZE); + // Page written to memory card, not just to buffer - let's schedule a flush 0.5b cycles into the future (1 sec) + // But first we unschedule already scheduled flushes - no point in flushing once per page for a large write + // Scheduling event is mainly for raw memory cards as the flush the whole 16MB to disk + // Flushing the gci folder is free in comparison + 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 c1bb1af792..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;