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