skidau c3344eaa88 Preliminary update to the GameCube to GBA link cable emulation. Fixes Zelda Wind Waker's Tingle Tuner connection, Pac-Man Vs, Final Fantasy: Crystal Chronicles multiplayer, and most other Gamecube to GBA link cable games.
* Changed the SI buffer processing so that transfers do not have to be completed instantly
* Added a second socket at port 49420 (0xc10c) which sends clock information to the GBA slaves
* Handled disconnections from the GBA and GC
* Made the transfers asynchronous
* Blocks the socket before the connection times out

Requires VBA-M SVN 1235 or later.
2015-03-20 12:43:55 +11:00

592 lines
16 KiB
C++

// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include <algorithm>
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/Movie.h"
#include "Core/NetPlayProto.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/SI.h"
#include "Core/HW/SI_DeviceGBA.h"
#include "Core/HW/SystemTimers.h"
#include "Core/HW/VideoInterface.h"
namespace SerialInterface
{
static int changeDevice;
static int et_transfer_pending;
void RunSIBuffer(u64 userdata, int cyclesLate);
void UpdateInterrupts();
// SI Interrupt Types
enum SIInterruptType
{
INT_RDSTINT = 0,
INT_TCINT = 1,
};
static void GenerateSIInterrupt(SIInterruptType _SIInterrupt);
// SI Internal Hardware Addresses
enum
{
SI_CHANNEL_0_OUT = 0x00,
SI_CHANNEL_0_IN_HI = 0x04,
SI_CHANNEL_0_IN_LO = 0x08,
SI_CHANNEL_1_OUT = 0x0C,
SI_CHANNEL_1_IN_HI = 0x10,
SI_CHANNEL_1_IN_LO = 0x14,
SI_CHANNEL_2_OUT = 0x18,
SI_CHANNEL_2_IN_HI = 0x1C,
SI_CHANNEL_2_IN_LO = 0x20,
SI_CHANNEL_3_OUT = 0x24,
SI_CHANNEL_3_IN_HI = 0x28,
SI_CHANNEL_3_IN_LO = 0x2C,
SI_POLL = 0x30,
SI_COM_CSR = 0x34,
SI_STATUS_REG = 0x38,
SI_EXI_CLOCK_COUNT = 0x3C,
};
// SI Channel Output
union USIChannelOut
{
u32 Hex;
struct
{
u32 OUTPUT1 : 8;
u32 OUTPUT0 : 8;
u32 CMD : 8;
u32 : 8;
};
};
// SI Channel Input High u32
union USIChannelIn_Hi
{
u32 Hex;
struct
{
u32 INPUT3 : 8;
u32 INPUT2 : 8;
u32 INPUT1 : 8;
u32 INPUT0 : 6;
u32 ERRLATCH : 1; // 0: no error 1: Error latched. Check SISR.
u32 ERRSTAT : 1; // 0: no error 1: error on last transfer
};
};
// SI Channel Input Low u32
union USIChannelIn_Lo
{
u32 Hex;
struct
{
u32 INPUT7 : 8;
u32 INPUT6 : 8;
u32 INPUT5 : 8;
u32 INPUT4 : 8;
};
};
// SI Channel
struct SSIChannel
{
USIChannelOut m_Out;
USIChannelIn_Hi m_InHi;
USIChannelIn_Lo m_InLo;
ISIDevice* m_pDevice;
};
// SI Poll: Controls how often a device is polled
union USIPoll
{
u32 Hex;
struct
{
u32 VBCPY3 : 1; // 1: write to output buffer only on vblank
u32 VBCPY2 : 1;
u32 VBCPY1 : 1;
u32 VBCPY0 : 1;
u32 EN3 : 1; // Enable polling of channel
u32 EN2 : 1; // does not affect communication RAM transfers
u32 EN1 : 1;
u32 EN0 : 1;
u32 Y : 8; // Polls per frame
u32 X : 10; // Polls per X lines. begins at vsync, min 7, max depends on video mode
u32 : 6;
};
};
// SI Communication Control Status Register
union USIComCSR
{
u32 Hex;
struct
{
u32 TSTART : 1; // write: start transfer read: transfer status
u32 CHANNEL : 2; // determines which SI channel will be used on the communication interface.
u32 : 3;
u32 CALLBEN : 1; // Callback enable
u32 CMDEN : 1; // Command enable?
u32 INLNGTH : 7;
u32 : 1;
u32 OUTLNGTH : 7; // Communication Channel Output Length in bytes
u32 : 1;
u32 CHANEN : 1; // Channel enable?
u32 CHANNUM : 2; // Channel number?
u32 RDSTINTMSK : 1; // Read Status Interrupt Status Mask
u32 RDSTINT : 1; // Read Status Interrupt Status
u32 COMERR : 1; // Communication Error (set 0)
u32 TCINTMSK : 1; // Transfer Complete Interrupt Mask
u32 TCINT : 1; // Transfer Complete Interrupt
};
USIComCSR() {Hex = 0;}
USIComCSR(u32 _hex) {Hex = _hex;}
};
// SI Status Register
union USIStatusReg
{
u32 Hex;
struct
{
u32 UNRUN3 : 1; // (RWC) write 1: bit cleared read 1: main proc underrun error
u32 OVRUN3 : 1; // (RWC) write 1: bit cleared read 1: overrun error
u32 COLL3 : 1; // (RWC) write 1: bit cleared read 1: collision error
u32 NOREP3 : 1; // (RWC) write 1: bit cleared read 1: response error
u32 WRST3 : 1; // (R) 1: buffer channel0 not copied
u32 RDST3 : 1; // (R) 1: new Data available
u32 : 2; // 7:6
u32 UNRUN2 : 1; // (RWC) write 1: bit cleared read 1: main proc underrun error
u32 OVRUN2 : 1; // (RWC) write 1: bit cleared read 1: overrun error
u32 COLL2 : 1; // (RWC) write 1: bit cleared read 1: collision error
u32 NOREP2 : 1; // (RWC) write 1: bit cleared read 1: response error
u32 WRST2 : 1; // (R) 1: buffer channel0 not copied
u32 RDST2 : 1; // (R) 1: new Data available
u32 : 2; // 15:14
u32 UNRUN1 : 1; // (RWC) write 1: bit cleared read 1: main proc underrun error
u32 OVRUN1 : 1; // (RWC) write 1: bit cleared read 1: overrun error
u32 COLL1 : 1; // (RWC) write 1: bit cleared read 1: collision error
u32 NOREP1 : 1; // (RWC) write 1: bit cleared read 1: response error
u32 WRST1 : 1; // (R) 1: buffer channel0 not copied
u32 RDST1 : 1; // (R) 1: new Data available
u32 : 2; // 23:22
u32 UNRUN0 : 1; // (RWC) write 1: bit cleared read 1: main proc underrun error
u32 OVRUN0 : 1; // (RWC) write 1: bit cleared read 1: overrun error
u32 COLL0 : 1; // (RWC) write 1: bit cleared read 1: collision error
u32 NOREP0 : 1; // (RWC) write 1: bit cleared read 1: response error
u32 WRST0 : 1; // (R) 1: buffer channel0 not copied
u32 RDST0 : 1; // (R) 1: new Data available
u32 : 1;
u32 WR : 1; // (RW) write 1 start copy, read 0 copy done
};
USIStatusReg() {Hex = 0;}
USIStatusReg(u32 _hex) {Hex = _hex;}
};
// SI EXI Clock Count
union USIEXIClockCount
{
u32 Hex;
struct
{
u32 LOCK : 1; // 1: prevents CPU from setting EXI clock to 32MHz
u32 : 0;
};
};
// STATE_TO_SAVE
static SSIChannel g_Channel[MAX_SI_CHANNELS];
static USIPoll g_Poll;
static USIComCSR g_ComCSR;
static USIStatusReg g_StatusReg;
static USIEXIClockCount g_EXIClockCount;
static u8 g_SIBuffer[128];
void DoState(PointerWrap &p)
{
for (int i = 0; i < MAX_SI_CHANNELS; i++)
{
p.Do(g_Channel[i].m_InHi.Hex);
p.Do(g_Channel[i].m_InLo.Hex);
p.Do(g_Channel[i].m_Out.Hex);
ISIDevice* pDevice = g_Channel[i].m_pDevice;
SIDevices type = pDevice->GetDeviceType();
p.Do(type);
ISIDevice* pSaveDevice = (type == pDevice->GetDeviceType()) ? pDevice : SIDevice_Create(type, i);
pSaveDevice->DoState(p);
if (pSaveDevice != pDevice)
{
// if we had to create a temporary device, discard it if we're not loading.
// also, if no movie is active, we'll assume the user wants to keep their current devices
// instead of the ones they had when the savestate was created.
if (p.GetMode() != PointerWrap::MODE_READ || !Movie::IsMovieActive())
{
delete pSaveDevice;
}
else
{
AddDevice(pSaveDevice);
}
}
}
p.Do(g_Poll);
p.DoPOD(g_ComCSR);
p.DoPOD(g_StatusReg);
p.Do(g_EXIClockCount);
p.Do(g_SIBuffer);
}
void Init()
{
for (int i = 0; i < MAX_SI_CHANNELS; i++)
{
g_Channel[i].m_Out.Hex = 0;
g_Channel[i].m_InHi.Hex = 0;
g_Channel[i].m_InLo.Hex = 0;
if (Movie::IsMovieActive())
AddDevice(Movie::IsUsingPad(i) ? (Movie::IsUsingBongo(i) ? SIDEVICE_GC_TARUKONGA : SIDEVICE_GC_CONTROLLER) : SIDEVICE_NONE, i);
else if (!NetPlay::IsNetPlayRunning())
AddDevice(SConfig::GetInstance().m_SIDevice[i], i);
}
g_Poll.Hex = 0;
g_Poll.X = 7;
g_ComCSR.Hex = 0;
g_StatusReg.Hex = 0;
g_EXIClockCount.Hex = 0;
//g_EXIClockCount.LOCK = 1; // Supposedly set on reset, but logs from real Wii don't look like it is...
memset(g_SIBuffer, 0, 128);
changeDevice = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback);
et_transfer_pending = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer);
}
void Shutdown()
{
for (int i = 0; i < MAX_SI_CHANNELS; i++)
RemoveDevice(i);
GBAConnectionWaiter_Shutdown();
}
void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
{
// Register SI buffer direct accesses.
for (int i = 0; i < 0x80; i += 4)
mmio->Register(base | (0x80 + i),
MMIO::DirectRead<u32>((u32*)&g_SIBuffer[i]),
MMIO::DirectWrite<u32>((u32*)&g_SIBuffer[i])
);
// In and out for the 4 SI channels.
for (int i = 0; i < MAX_SI_CHANNELS; ++i)
{
// We need to clear the RDST bit for the SI channel when reading.
// CH0 -> Bit 24 + 5
// CH1 -> Bit 16 + 5
// CH2 -> Bit 8 + 5
// CH3 -> Bit 0 + 5
int rdst_bit = 8 * (3 - i) + 5;
mmio->Register(base | (SI_CHANNEL_0_OUT + 0xC * i),
MMIO::DirectRead<u32>(&g_Channel[i].m_Out.Hex),
MMIO::DirectWrite<u32>(&g_Channel[i].m_Out.Hex)
);
mmio->Register(base | (SI_CHANNEL_0_IN_HI + 0xC * i),
MMIO::ComplexRead<u32>([i, rdst_bit](u32) {
g_StatusReg.Hex &= ~(1 << rdst_bit);
UpdateInterrupts();
return g_Channel[i].m_InHi.Hex;
}),
MMIO::DirectWrite<u32>(&g_Channel[i].m_InHi.Hex)
);
mmio->Register(base | (SI_CHANNEL_0_IN_LO + 0xC * i),
MMIO::ComplexRead<u32>([i, rdst_bit](u32) {
g_StatusReg.Hex &= ~(1 << rdst_bit);
UpdateInterrupts();
return g_Channel[i].m_InLo.Hex;
}),
MMIO::DirectWrite<u32>(&g_Channel[i].m_InLo.Hex)
);
}
mmio->Register(base | SI_POLL,
MMIO::DirectRead<u32>(&g_Poll.Hex),
MMIO::DirectWrite<u32>(&g_Poll.Hex)
);
mmio->Register(base | SI_COM_CSR,
MMIO::DirectRead<u32>(&g_ComCSR.Hex),
MMIO::ComplexWrite<u32>([](u32, u32 val) {
USIComCSR tmpComCSR(val);
g_ComCSR.CHANNEL = tmpComCSR.CHANNEL;
g_ComCSR.INLNGTH = tmpComCSR.INLNGTH;
g_ComCSR.OUTLNGTH = tmpComCSR.OUTLNGTH;
g_ComCSR.RDSTINTMSK = tmpComCSR.RDSTINTMSK;
g_ComCSR.TCINTMSK = tmpComCSR.TCINTMSK;
g_ComCSR.COMERR = 0;
if (tmpComCSR.RDSTINT) g_ComCSR.RDSTINT = 0;
if (tmpComCSR.TCINT) g_ComCSR.TCINT = 0;
// be careful: run si-buffer after updating the INT flags
if (tmpComCSR.TSTART)
{
g_ComCSR.TSTART = 1;
RunSIBuffer(0, 0);
}
else if (g_ComCSR.TSTART)
{
CoreTiming::RemoveEvent(et_transfer_pending);
}
if (!g_ComCSR.TSTART)
UpdateInterrupts();
})
);
mmio->Register(base | SI_STATUS_REG,
MMIO::DirectRead<u32>(&g_StatusReg.Hex),
MMIO::ComplexWrite<u32>([](u32, u32 val) {
USIStatusReg tmpStatus(val);
// clear bits ( if (tmp.bit) SISR.bit=0 )
if (tmpStatus.NOREP0) g_StatusReg.NOREP0 = 0;
if (tmpStatus.COLL0) g_StatusReg.COLL0 = 0;
if (tmpStatus.OVRUN0) g_StatusReg.OVRUN0 = 0;
if (tmpStatus.UNRUN0) g_StatusReg.UNRUN0 = 0;
if (tmpStatus.NOREP1) g_StatusReg.NOREP1 = 0;
if (tmpStatus.COLL1) g_StatusReg.COLL1 = 0;
if (tmpStatus.OVRUN1) g_StatusReg.OVRUN1 = 0;
if (tmpStatus.UNRUN1) g_StatusReg.UNRUN1 = 0;
if (tmpStatus.NOREP2) g_StatusReg.NOREP2 = 0;
if (tmpStatus.COLL2) g_StatusReg.COLL2 = 0;
if (tmpStatus.OVRUN2) g_StatusReg.OVRUN2 = 0;
if (tmpStatus.UNRUN2) g_StatusReg.UNRUN2 = 0;
if (tmpStatus.NOREP3) g_StatusReg.NOREP3 = 0;
if (tmpStatus.COLL3) g_StatusReg.COLL3 = 0;
if (tmpStatus.OVRUN3) g_StatusReg.OVRUN3 = 0;
if (tmpStatus.UNRUN3) g_StatusReg.UNRUN3 = 0;
// send command to devices
if (tmpStatus.WR)
{
g_Channel[0].m_pDevice->SendCommand(g_Channel[0].m_Out.Hex, g_Poll.EN0);
g_Channel[1].m_pDevice->SendCommand(g_Channel[1].m_Out.Hex, g_Poll.EN1);
g_Channel[2].m_pDevice->SendCommand(g_Channel[2].m_Out.Hex, g_Poll.EN2);
g_Channel[3].m_pDevice->SendCommand(g_Channel[3].m_Out.Hex, g_Poll.EN3);
g_StatusReg.WR = 0;
g_StatusReg.WRST0 = 0;
g_StatusReg.WRST1 = 0;
g_StatusReg.WRST2 = 0;
g_StatusReg.WRST3 = 0;
}
})
);
mmio->Register(base | SI_EXI_CLOCK_COUNT,
MMIO::DirectRead<u32>(&g_EXIClockCount.Hex),
MMIO::DirectWrite<u32>(&g_EXIClockCount.Hex)
);
}
void UpdateInterrupts()
{
// check if we have to update the RDSTINT flag
if (g_StatusReg.RDST0 || g_StatusReg.RDST1 ||
g_StatusReg.RDST2 || g_StatusReg.RDST3)
g_ComCSR.RDSTINT = 1;
else
g_ComCSR.RDSTINT = 0;
// check if we have to generate an interrupt
if ((g_ComCSR.RDSTINT & g_ComCSR.RDSTINTMSK) ||
(g_ComCSR.TCINT & g_ComCSR.TCINTMSK))
{
ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_SI, true);
}
else
{
ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_SI, false);
}
}
void GenerateSIInterrupt(SIInterruptType _SIInterrupt)
{
switch (_SIInterrupt)
{
case INT_RDSTINT: g_ComCSR.RDSTINT = 1; break;
case INT_TCINT: g_ComCSR.TCINT = 1; break;
}
UpdateInterrupts();
}
void RemoveDevice(int _iDeviceNumber)
{
delete g_Channel[_iDeviceNumber].m_pDevice;
g_Channel[_iDeviceNumber].m_pDevice = nullptr;
}
void AddDevice(ISIDevice* pDevice)
{
int _iDeviceNumber = pDevice->GetDeviceNumber();
//_dbg_assert_(SERIALINTERFACE, _iDeviceNumber < MAX_SI_CHANNELS);
// delete the old device
RemoveDevice(_iDeviceNumber);
// create the new one
g_Channel[_iDeviceNumber].m_pDevice = pDevice;
}
void AddDevice(const SIDevices _device, int _iDeviceNumber)
{
ISIDevice *pDevice = SIDevice_Create(_device, _iDeviceNumber);
AddDevice(pDevice);
}
static void SetNoResponse(u32 channel)
{
// raise the NO RESPONSE error
switch (channel)
{
case 0: g_StatusReg.NOREP0 = 1; break;
case 1: g_StatusReg.NOREP1 = 1; break;
case 2: g_StatusReg.NOREP2 = 1; break;
case 3: g_StatusReg.NOREP3 = 1; break;
}
g_ComCSR.COMERR = 1;
}
void ChangeDeviceCallback(u64 userdata, int cyclesLate)
{
u8 channel = (u8)(userdata >> 32);
SIDevices device = (SIDevices)(u32)userdata;
// Skip redundant (spammed) device changes
if (GetDeviceType(channel) != device)
{
g_Channel[channel].m_Out.Hex = 0;
g_Channel[channel].m_InHi.Hex = 0;
g_Channel[channel].m_InLo.Hex = 0;
SetNoResponse(channel);
AddDevice(device, channel);
}
}
void ChangeDevice(SIDevices device, int channel)
{
// Called from GUI, so we need to make it thread safe.
// Let the hardware see no device for .5b cycles
// TODO: Calling GetDeviceType here isn't threadsafe.
if (GetDeviceType(channel) != device)
{
CoreTiming::ScheduleEvent_Threadsafe(0, changeDevice, ((u64)channel << 32) | SIDEVICE_NONE);
CoreTiming::ScheduleEvent_Threadsafe(500000000, changeDevice, ((u64)channel << 32) | device);
}
}
void UpdateDevices()
{
// Update channels and set the status bit if there's new data
g_StatusReg.RDST0 = !!g_Channel[0].m_pDevice->GetData(g_Channel[0].m_InHi.Hex, g_Channel[0].m_InLo.Hex);
g_StatusReg.RDST1 = !!g_Channel[1].m_pDevice->GetData(g_Channel[1].m_InHi.Hex, g_Channel[1].m_InLo.Hex);
g_StatusReg.RDST2 = !!g_Channel[2].m_pDevice->GetData(g_Channel[2].m_InHi.Hex, g_Channel[2].m_InLo.Hex);
g_StatusReg.RDST3 = !!g_Channel[3].m_pDevice->GetData(g_Channel[3].m_InHi.Hex, g_Channel[3].m_InLo.Hex);
UpdateInterrupts();
}
SIDevices GetDeviceType(int channel)
{
if (channel < 0 || channel > 3)
return SIDEVICE_NONE;
else
return g_Channel[channel].m_pDevice->GetDeviceType();
}
void RunSIBuffer(u64 userdata, int cyclesLate)
{
if (g_ComCSR.TSTART)
{
// Math inLength
int inLength = g_ComCSR.INLNGTH;
if (inLength == 0)
inLength = 128;
else
inLength++;
// Math outLength
int outLength = g_ComCSR.OUTLNGTH;
if (outLength == 0)
outLength = 128;
else
outLength++;
int numOutput = 0;
numOutput = g_Channel[g_ComCSR.CHANNEL].m_pDevice->RunBuffer(g_SIBuffer, inLength);
DEBUG_LOG(SERIALINTERFACE, "RunSIBuffer chan: %d inLen: %i outLen: %i processed: %i", g_ComCSR.CHANNEL, inLength, outLength, numOutput);
if (numOutput != 0)
{
g_ComCSR.TSTART = 0;
GenerateSIInterrupt(INT_TCINT);
}
else
{
CoreTiming::ScheduleEvent(g_Channel[g_ComCSR.CHANNEL].m_pDevice->TransferInterval() - cyclesLate, et_transfer_pending);
}
}
}
int GetTicksToNextSIPoll()
{
// Poll for input at regular intervals (once per frame) when playing or recording a movie
if (Movie::IsMovieActive())
{
if (Movie::IsNetPlayRecording())
return SystemTimers::GetTicksPerSecond() / VideoInterface::TargetRefreshRate / 2;
else
return SystemTimers::GetTicksPerSecond() / VideoInterface::TargetRefreshRate;
}
if (NetPlay::IsNetPlayRunning())
return SystemTimers::GetTicksPerSecond() / VideoInterface::TargetRefreshRate / 2;
if (!g_Poll.Y && g_Poll.X)
return VideoInterface::GetTicksPerLine() * g_Poll.X;
else if (!g_Poll.Y)
return SystemTimers::GetTicksPerSecond() / 60;
return std::min(VideoInterface::GetTicksPerFrame() / g_Poll.Y, VideoInterface::GetTicksPerLine() * g_Poll.X);
}
} // end of namespace SerialInterface