Merge pull request #581 from TotalNerd/memcard-emu
Emulate GameCube memory card speeds
This commit is contained in:
commit
963e1a698c
|
@ -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);
|
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)
|
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex)
|
||||||
{
|
{
|
||||||
for (auto& channel : g_Channels)
|
for (auto& channel : g_Channels)
|
||||||
|
|
|
@ -31,6 +31,9 @@ void UpdateInterrupts();
|
||||||
|
|
||||||
void ChangeDeviceCallback(u64 userdata, int cyclesLate);
|
void ChangeDeviceCallback(u64 userdata, int cyclesLate);
|
||||||
void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num);
|
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);
|
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1);
|
||||||
|
|
||||||
} // end of namespace ExpansionInterface
|
} // end of namespace ExpansionInterface
|
||||||
|
|
|
@ -123,7 +123,6 @@ void CEXIChannel::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||||
case EXI_READWRITE: pDevice->ImmReadWrite(m_ImmData, m_Control.TLEN + 1); break;
|
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);
|
default: _dbg_assert_msg_(EXPANSIONINTERFACE,0,"EXI Imm: Unknown transfer type %i", m_Control.RW);
|
||||||
}
|
}
|
||||||
m_Control.TSTART = 0;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -134,14 +133,13 @@ void CEXIChannel::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||||
case EXI_WRITE: pDevice->DMAWrite(m_DMAMemoryAddress, m_DMALength); break;
|
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);
|
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_Control.TSTART = 0;
|
||||||
{
|
|
||||||
m_Status.TCINT = 1;
|
// Check if device needs specific timing, otherwise just complete transfer immediately
|
||||||
CoreTiming::ScheduleEvent_Threadsafe_Immediate(updateInterrupts, 0);
|
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()
|
void CEXIChannel::RemoveDevices()
|
||||||
{
|
{
|
||||||
for (auto& device : m_pDevices)
|
for (auto& device : m_pDevices)
|
||||||
|
|
|
@ -86,6 +86,7 @@ private:
|
||||||
int updateInterrupts;
|
int updateInterrupts;
|
||||||
|
|
||||||
static void UpdateInterrupts(u64 userdata, int cyclesLate);
|
static void UpdateInterrupts(u64 userdata, int cyclesLate);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// get device
|
// get device
|
||||||
IEXIDevice* GetDevice(const u8 _CHIP_SELECT);
|
IEXIDevice* GetDevice(const u8 _CHIP_SELECT);
|
||||||
|
@ -96,6 +97,8 @@ public:
|
||||||
|
|
||||||
void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
|
void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
|
||||||
|
|
||||||
|
void SendTransferComplete();
|
||||||
|
|
||||||
void AddDevice(const TEXIDevices device_type, const int device_num);
|
void AddDevice(const TEXIDevices device_type, const int device_num);
|
||||||
void AddDevice(IEXIDevice* pDevice, const int device_num, bool notifyPresenceChanged=true);
|
void AddDevice(IEXIDevice* pDevice, const int device_num, bool notifyPresenceChanged=true);
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ public:
|
||||||
virtual void DMAWrite(u32 _uAddr, u32 _uSize);
|
virtual void DMAWrite(u32 _uAddr, u32 _uSize);
|
||||||
virtual void DMARead (u32 _uAddr, u32 _uSize);
|
virtual void DMARead (u32 _uAddr, u32 _uSize);
|
||||||
|
|
||||||
|
virtual bool UseDelayedTransferCompletion() {return false;}
|
||||||
|
|
||||||
virtual bool IsPresent() {return false;}
|
virtual bool IsPresent() {return false;}
|
||||||
virtual void SetCS(int) {}
|
virtual void SetCS(int) {}
|
||||||
virtual void DoState(PointerWrap&) {}
|
virtual void DoState(PointerWrap&) {}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "Core/CoreTiming.h"
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/Movie.h"
|
#include "Core/Movie.h"
|
||||||
#include "Core/HW/EXI.h"
|
#include "Core/HW/EXI.h"
|
||||||
|
#include "Core/HW/EXI_Channel.h"
|
||||||
#include "Core/HW/EXI_Device.h"
|
#include "Core/HW/EXI_Device.h"
|
||||||
#include "Core/HW/EXI_DeviceMemoryCard.h"
|
#include "Core/HW/EXI_DeviceMemoryCard.h"
|
||||||
#include "Core/HW/GCMemcard.h"
|
#include "Core/HW/GCMemcard.h"
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
#include "Core/HW/GCMemcardRaw.h"
|
#include "Core/HW/GCMemcardRaw.h"
|
||||||
#include "Core/HW/Memmap.h"
|
#include "Core/HW/Memmap.h"
|
||||||
#include "Core/HW/Sram.h"
|
#include "Core/HW/Sram.h"
|
||||||
|
#include "Core/HW/SystemTimers.h"
|
||||||
#include "DiscIO/NANDContentLoader.h"
|
#include "DiscIO/NANDContentLoader.h"
|
||||||
|
|
||||||
#define MC_STATUS_BUSY 0x80
|
#define MC_STATUS_BUSY 0x80
|
||||||
|
@ -28,6 +30,9 @@
|
||||||
#define MC_STATUS_READY 0x01
|
#define MC_STATUS_READY 0x01
|
||||||
#define SIZE_TO_Mb (1024 * 8 * 16)
|
#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)
|
void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate)
|
||||||
{
|
{
|
||||||
// note that userdata is forbidden to be a pointer, due to the implementation of EventDoState
|
// 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();
|
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)
|
CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder)
|
||||||
: card_index(index)
|
: card_index(index)
|
||||||
, m_bDirty(false)
|
, 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
|
// 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_this_card = CoreTiming::RegisterEvent((index == 0) ? "memcardFlushA" : "memcardFlushB", FlushCallback);
|
||||||
et_cmd_done = CoreTiming::RegisterEvent((index == 0) ? "memcardDoneA" : "memcardDoneB", CmdDoneCallback);
|
et_cmd_done = CoreTiming::RegisterEvent((index == 0) ? "memcardDoneA" : "memcardDoneB", CmdDoneCallback);
|
||||||
|
et_transfer_complete = CoreTiming::RegisterEvent((index == 0) ? "memcardTransferCompleteA" : "memcardTransferCompleteB", TransferCompleteCallback);
|
||||||
|
|
||||||
interruptSwitch = 0;
|
interruptSwitch = 0;
|
||||||
m_bInterruptSet = 0;
|
m_bInterruptSet = 0;
|
||||||
|
@ -185,6 +201,11 @@ CEXIMemoryCard::~CEXIMemoryCard()
|
||||||
memorycard.reset();
|
memorycard.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CEXIMemoryCard::UseDelayedTransferCompletion()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool CEXIMemoryCard::IsPresent()
|
bool CEXIMemoryCard::IsPresent()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -199,6 +220,12 @@ void CEXIMemoryCard::CmdDone()
|
||||||
m_bDirty = true;
|
m_bDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CEXIMemoryCard::TransferComplete()
|
||||||
|
{
|
||||||
|
// Transfer complete, send interrupt
|
||||||
|
ExpansionInterface::GetChannel(card_index)->SendTransferComplete();
|
||||||
|
}
|
||||||
|
|
||||||
void CEXIMemoryCard::CmdDoneLater(u64 cycles)
|
void CEXIMemoryCard::CmdDoneLater(u64 cycles)
|
||||||
{
|
{
|
||||||
CoreTiming::RemoveEvent(et_cmd_done);
|
CoreTiming::RemoveEvent(et_cmd_done);
|
||||||
|
@ -473,10 +500,14 @@ IEXIDevice* CEXIMemoryCard::FindDevice(TEXIDevices device_type, int customIndex)
|
||||||
void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize)
|
void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize)
|
||||||
{
|
{
|
||||||
memorycard->Read(address, _uSize, Memory::GetPointer(_uAddr));
|
memorycard->Read(address, _uSize, Memory::GetPointer(_uAddr));
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
if ((address + _uSize) % BLOCK_SIZE == 0)
|
if ((address + _uSize) % BLOCK_SIZE == 0)
|
||||||
INFO_LOG(EXPANSIONINTERFACE, "reading from block: %x", address / BLOCK_SIZE);
|
INFO_LOG(EXPANSIONINTERFACE, "reading from block: %x", address / BLOCK_SIZE);
|
||||||
#endif
|
#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
|
// 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::RemoveEvent(et_this_card);
|
||||||
CoreTiming::ScheduleEvent(500000000, et_this_card, (u64)card_index);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ public:
|
||||||
virtual ~CEXIMemoryCard();
|
virtual ~CEXIMemoryCard();
|
||||||
void SetCS(int cs) override;
|
void SetCS(int cs) override;
|
||||||
bool IsInterruptSet() override;
|
bool IsInterruptSet() override;
|
||||||
|
bool UseDelayedTransferCompletion() override;
|
||||||
bool IsPresent() override;
|
bool IsPresent() override;
|
||||||
void DoState(PointerWrap &p) override;
|
void DoState(PointerWrap &p) override;
|
||||||
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) 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.
|
// Scheduled when a command that required delayed end signaling is done.
|
||||||
static void CmdDoneCallback(u64 userdata, int cyclesLate);
|
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.
|
// Flushes the memory card contents to disk.
|
||||||
void Flush(bool exiting = false);
|
void Flush(bool exiting = false);
|
||||||
|
|
||||||
// Signals that the command that was previously executed is now done.
|
// Signals that the command that was previously executed is now done.
|
||||||
void CmdDone();
|
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.
|
// Variant of CmdDone which schedules an event later in the future to complete the command.
|
||||||
void CmdDoneLater(u64 cycles);
|
void CmdDoneLater(u64 cycles);
|
||||||
|
|
||||||
|
@ -59,7 +66,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
int card_index;
|
int card_index;
|
||||||
int et_this_card, et_cmd_done;
|
int et_this_card, et_cmd_done, et_transfer_complete;
|
||||||
//! memory card state
|
//! memory card state
|
||||||
|
|
||||||
// STATE_TO_SAVE
|
// STATE_TO_SAVE
|
||||||
|
|
Loading…
Reference in New Issue