Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #581 from TotalNerd/memcard-emu
Emulate GameCube memory card speeds
  • Loading branch information
delroth committed Jul 19, 2014
2 parents c189a34 + b2ac65b commit 963e1a6
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 8 deletions.
5 changes: 5 additions & 0 deletions Source/Core/Core/HW/EXI.cpp
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions Source/Core/Core/HW/EXI.h
Expand Up @@ -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
18 changes: 11 additions & 7 deletions Source/Core/Core/HW/EXI_Channel.cpp
Expand Up @@ -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
{
Expand All @@ -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();
}
})
);
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions Source/Core/Core/HW/EXI_Channel.h
Expand Up @@ -86,6 +86,7 @@ class CEXIChannel
int updateInterrupts;

static void UpdateInterrupts(u64 userdata, int cyclesLate);

public:
// get device
IEXIDevice* GetDevice(const u8 _CHIP_SELECT);
Expand All @@ -96,6 +97,8 @@ class CEXIChannel

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);

Expand Down
2 changes: 2 additions & 0 deletions Source/Core/Core/HW/EXI_Device.h
Expand Up @@ -38,6 +38,8 @@ class IEXIDevice
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&) {}
Expand Down
34 changes: 34 additions & 0 deletions Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp
Expand Up @@ -11,13 +11,15 @@
#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"
#include "Core/HW/GCMemcardDirectory.h"
#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
Expand All @@ -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
Expand All @@ -49,13 +54,24 @@ 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)
{
// 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;
Expand Down Expand Up @@ -185,6 +201,11 @@ CEXIMemoryCard::~CEXIMemoryCard()
memorycard.reset();
}

bool CEXIMemoryCard::UseDelayedTransferCompletion()
{
return true;
}

bool CEXIMemoryCard::IsPresent()
{
return true;
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
9 changes: 8 additions & 1 deletion Source/Core/Core/HW/EXI_DeviceMemoryCard.h
Expand Up @@ -13,6 +13,7 @@ class CEXIMemoryCard : public IEXIDevice
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;
Expand All @@ -30,12 +31,18 @@ class CEXIMemoryCard : public IEXIDevice
// 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);

Expand All @@ -59,7 +66,7 @@ class CEXIMemoryCard : public IEXIDevice
};

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
Expand Down

0 comments on commit 963e1a6

Please sign in to comment.