From 8a0093de231c399c712bf1e15a94ae393195dc7b Mon Sep 17 00:00:00 2001 From: TotalNerd Date: Tue, 8 Jul 2014 12:06:11 -0500 Subject: [PATCH 1/3] Emulate GameCube memory card speeds --- Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp | 44 +++++++++++++++++--- Source/Core/Core/HW/EXI_DeviceMemoryCard.h | 11 +++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp index 809e1c79ba..d311feff74 100644 --- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp +++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp @@ -18,6 +18,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 +29,12 @@ #define MC_STATUS_READY 0x01 #define SIZE_TO_Mb (1024 * 8 * 16) +// These are rough estimates based on GameCube video evidence of memory card "speeds" +// This will need to be refined, and perhaps the idea behind the speed thing is wrong +// But it works with (near?) perfect speed as far as saving goes +static const float MC_TRANSFER_RATE_WRITE = 22.0f * 1024.0f; +static const float MC_TRANSFER_RATE_READ = 73.84f * 1024.0f; + void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate) { // note that userdata is forbidden to be a pointer, due to the implementation of EventDoState @@ -227,26 +234,47 @@ void CEXIMemoryCard::SetCS(int cs) //??? - CmdDoneLater(5000); + if (address >= MC_DATA_FILES) + CmdDoneLater(BLOCK_SIZE * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE)); + else + CmdDoneLater(5000); } break; case cmdChipErase: if (m_uPosition > 2) { - // TODO: Investigate on HW, I (LPFaint99) believe that this only erases the system area (Blocks 0-4) - memorycard->ClearAll(); + // Clear only the system blocks + for (int i = 0; i < 5; i++) + memorycard->ClearBlock(BLOCK_SIZE * i); + status &= ~MC_STATUS_BUSY; m_bDirty = true; + + if (address >= MC_DATA_FILES) + CmdDoneLater((BLOCK_SIZE * 5) * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE)); + else + CmdDoneLater(5000); + } + break; + + case cmdReadArray: + if (m_uPosition > 8) + { + // Each read is 512 bytes + if (address >= MC_DATA_FILES) + CmdDoneLater(512 * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_READ)); + else + CmdDoneLater(5000); } break; case cmdPageProgram: - if (m_uPosition >= 5) + if (m_uPosition > 4) { int count = m_uPosition - 5; int i=0; - status &= ~0x80; + status &= ~MC_STATUS_BUSY; while (count--) { @@ -255,7 +283,11 @@ void CEXIMemoryCard::SetCS(int cs) address = (address & ~0x1FF) | ((address+1) & 0x1FF); } - CmdDoneLater(5000); + // Each write is 128 bytes + if (address >= MC_DATA_FILES) + CmdDoneLater(128 * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE)); + else + CmdDoneLater(5000); } break; } diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.h b/Source/Core/Core/HW/EXI_DeviceMemoryCard.h index 12747268ed..ce7bdc87d8 100644 --- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.h +++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.h @@ -39,6 +39,17 @@ private: // Variant of CmdDone which schedules an event later in the future to complete the command. void CmdDoneLater(u64 cycles); + // Memory card data layout + enum + { + MC_DATA_HEADER = 0x0000, + MC_DATA_DIRECTORY = 0x2000, + MC_DATA_DIRECTORYBACKUP = 0x4000, + MC_DATA_BLOCKALLOCMAP = 0x6000, + MC_DATA_BLOCKALLOCMAPBACKUP = 0x8000, + MC_DATA_FILES = 0xA000, + }; + enum { cmdNintendoID = 0x00, From f6023728858d319957bdf5e35f4d7c18754b4147 Mon Sep 17 00:00:00 2001 From: TotalNerd Date: Sun, 13 Jul 2014 12:00:13 -0500 Subject: [PATCH 2/3] Redo timing in DMAWrite/DMARead --- Source/Core/Core/HW/EXI_Channel.cpp | 23 ++--- Source/Core/Core/HW/EXI_Channel.h | 8 +- Source/Core/Core/HW/EXI_Device.cpp | 10 ++- Source/Core/Core/HW/EXI_Device.h | 7 +- Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp | 88 ++++++++------------ Source/Core/Core/HW/EXI_DeviceMemoryCard.h | 19 ++--- 6 files changed, 71 insertions(+), 84 deletions(-) diff --git a/Source/Core/Core/HW/EXI_Channel.cpp b/Source/Core/Core/HW/EXI_Channel.cpp index 5e1a88ca9f..9cb72c7db0 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, m_ChannelId)); + device.reset(EXIDevice_Create(EXIDEVICE_NONE, this)); updateInterrupts = CoreTiming::RegisterEvent("EXIInterrupt", UpdateInterrupts); } @@ -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,12 @@ 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; + + if (pDevice->m_deviceType != EXIDEVICE_MEMORYCARD) + SendTransferComplete(); } }) ); @@ -152,6 +149,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) @@ -160,7 +163,7 @@ void CEXIChannel::RemoveDevices() void CEXIChannel::AddDevice(const TEXIDevices device_type, const int device_num) { - IEXIDevice* pNewDevice = EXIDevice_Create(device_type, m_ChannelId); + IEXIDevice* pNewDevice = EXIDevice_Create(device_type, this); AddDevice(pNewDevice, device_num); } @@ -232,7 +235,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, m_ChannelId); + IEXIDevice* pSaveDevice = (type == pDevice->m_deviceType) ? pDevice : EXIDevice_Create(type, this); 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 4156a40439..76cabf2d47 100644 --- a/Source/Core/Core/HW/EXI_Channel.h +++ b/Source/Core/Core/HW/EXI_Channel.h @@ -80,9 +80,6 @@ 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); @@ -96,6 +93,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); @@ -108,4 +107,7 @@ 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 dbd2807112..60bddbb80b 100644 --- a/Source/Core/Core/HW/EXI_Device.cpp +++ b/Source/Core/Core/HW/EXI_Device.cpp @@ -5,6 +5,7 @@ #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" @@ -88,7 +89,7 @@ public: // F A C T O R Y -IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num) +IEXIDevice* EXIDevice_Create(TEXIDevices device_type, CEXIChannel* channel) { IEXIDevice* result = nullptr; @@ -102,7 +103,8 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num) case EXIDEVICE_MEMORYCARDFOLDER: { bool gci_folder = (device_type == EXIDEVICE_MEMORYCARDFOLDER); - result = new CEXIMemoryCard(channel_num, gci_folder); + device_type = EXIDEVICE_MEMORYCARD; + result = new CEXIMemoryCard(channel->m_ChannelId, gci_folder); break; } case EXIDEVICE_MASKROM: @@ -114,7 +116,7 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num) break; case EXIDEVICE_MIC: - result = new CEXIMic(channel_num); + result = new CEXIMic(channel->m_ChannelId); break; case EXIDEVICE_ETH: @@ -138,5 +140,7 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num) 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 58b3a426cd..d9e388b8fa 100644 --- a/Source/Core/Core/HW/EXI_Device.h +++ b/Source/Core/Core/HW/EXI_Device.h @@ -7,6 +7,8 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +class CEXIChannel; + enum TEXIDevices { EXIDEVICE_DUMMY, @@ -48,9 +50,12 @@ 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, const int channel_num); +IEXIDevice* EXIDevice_Create(const TEXIDevices device_type, CEXIChannel* channel); diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp index d311feff74..618c4e441e 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" @@ -29,11 +30,8 @@ #define MC_STATUS_READY 0x01 #define SIZE_TO_Mb (1024 * 8 * 16) -// These are rough estimates based on GameCube video evidence of memory card "speeds" -// This will need to be refined, and perhaps the idea behind the speed thing is wrong -// But it works with (near?) perfect speed as far as saving goes -static const float MC_TRANSFER_RATE_WRITE = 22.0f * 1024.0f; -static const float MC_TRANSFER_RATE_READ = 73.84f * 1024.0f; +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) { @@ -56,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) @@ -63,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) ? "memcardTransferCompleteB" : "memcardTransferCompleteB", TransferCompleteCallback); interruptSwitch = 0; m_bInterruptSet = 0; @@ -206,6 +215,17 @@ void CEXIMemoryCard::CmdDone() m_bDirty = true; } +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); +} + void CEXIMemoryCard::CmdDoneLater(u64 cycles) { CoreTiming::RemoveEvent(et_cmd_done); @@ -234,47 +254,26 @@ void CEXIMemoryCard::SetCS(int cs) //??? - if (address >= MC_DATA_FILES) - CmdDoneLater(BLOCK_SIZE * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE)); - else - CmdDoneLater(5000); + CmdDoneLater(5000); } break; case cmdChipErase: if (m_uPosition > 2) { - // Clear only the system blocks - for (int i = 0; i < 5; i++) - memorycard->ClearBlock(BLOCK_SIZE * i); - + // TODO: Investigate on HW, I (LPFaint99) believe that this only erases the system area (Blocks 0-4) + memorycard->ClearAll(); status &= ~MC_STATUS_BUSY; m_bDirty = true; - - if (address >= MC_DATA_FILES) - CmdDoneLater((BLOCK_SIZE * 5) * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE)); - else - CmdDoneLater(5000); - } - break; - - case cmdReadArray: - if (m_uPosition > 8) - { - // Each read is 512 bytes - if (address >= MC_DATA_FILES) - CmdDoneLater(512 * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_READ)); - else - CmdDoneLater(5000); } break; case cmdPageProgram: - if (m_uPosition > 4) + if (m_uPosition >= 5) { int count = m_uPosition - 5; int i=0; - status &= ~MC_STATUS_BUSY; + status &= ~0x80; while (count--) { @@ -283,11 +282,7 @@ void CEXIMemoryCard::SetCS(int cs) address = (address & ~0x1FF) | ((address+1) & 0x1FF); } - // Each write is 128 bytes - if (address >= MC_DATA_FILES) - CmdDoneLater(128 * (SystemTimers::GetTicksPerSecond() / MC_TRANSFER_RATE_WRITE)); - else - CmdDoneLater(5000); + CmdDoneLater(5000); } break; } @@ -505,10 +500,7 @@ 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 + 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 @@ -516,19 +508,5 @@ 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); - } + 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 ce7bdc87d8..c1bb1af792 100644 --- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.h +++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.h @@ -30,26 +30,21 @@ 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); - // Memory card data layout - enum - { - MC_DATA_HEADER = 0x0000, - MC_DATA_DIRECTORY = 0x2000, - MC_DATA_DIRECTORYBACKUP = 0x4000, - MC_DATA_BLOCKALLOCMAP = 0x6000, - MC_DATA_BLOCKALLOCMAPBACKUP = 0x8000, - MC_DATA_FILES = 0xA000, - }; - enum { cmdNintendoID = 0x00, @@ -70,7 +65,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 From b2ac65bb2124c4fb8fba9392038687ec0bc57688 Mon Sep 17 00:00:00 2001 From: TotalNerd Date: Tue, 15 Jul 2014 01:55:22 -0500 Subject: [PATCH 3/3] Revised channel access from EXI_DeviceMemoryCard --- Source/Core/Core/HW/EXI.cpp | 5 +++ Source/Core/Core/HW/EXI.h | 3 ++ Source/Core/Core/HW/EXI_Channel.cpp | 9 ++--- Source/Core/Core/HW/EXI_Channel.h | 7 ++-- Source/Core/Core/HW/EXI_Device.cpp | 10 ++---- Source/Core/Core/HW/EXI_Device.h | 9 ++--- Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp | 38 ++++++++++++++++---- Source/Core/Core/HW/EXI_DeviceMemoryCard.h | 1 + 8 files changed, 55 insertions(+), 27 deletions(-) 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;