dolphin/Source/Core/Core/HW/SI.cpp

618 lines
18 KiB
C++

// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <cstring>
#include <memory>
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/SI.h"
#include "Core/HW/SI_DeviceGBA.h"
#include "Core/Movie.h"
#include "Core/NetPlayProto.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
namespace SerialInterface
{
static int changeDevice;
static int et_transfer_pending;
static void RunSIBuffer(u64 userdata, s64 cyclesLate);
static 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;
std::unique_ptr<ISIDevice> m_device;
};
// 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 std::array<SSIChannel, MAX_SI_CHANNELS> g_Channel;
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);
std::unique_ptr<ISIDevice>& device = g_Channel[i].m_device;
SIDevices type = device->GetDeviceType();
p.Do(type);
if (type == device->GetDeviceType())
{
device->DoState(p);
}
else
{
// 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.
// But we need to restore the current devices first just in case.
SIDevices original_device = device->GetDeviceType();
std::unique_ptr<ISIDevice> save_device = SIDevice_Create(type, i);
save_device->DoState(p);
AddDevice(std::move(save_device));
ChangeDeviceDeterministic(original_device, i);
}
}
p.Do(g_Poll);
p.DoPOD(g_ComCSR);
p.DoPOD(g_StatusReg);
p.Do(g_EXIClockCount);
p.Do(g_SIBuffer);
}
static void ChangeDeviceCallback(u64 userdata, s64 cyclesLate);
static void RunSIBuffer(u64 userdata, s64 cyclesLate);
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())
{
if (Movie::IsUsingPad(i))
{
SIDevices current = SConfig::GetInstance().m_SIDevice[i];
// GC pad-compatible devices can be used for both playing and recording
if (SIDevice_IsGCController(current))
AddDevice(Movie::IsUsingBongo(i) ? SIDEVICE_GC_TARUKONGA : current, i);
else
AddDevice(Movie::IsUsingBongo(i) ? SIDEVICE_GC_TARUKONGA : SIDEVICE_GC_CONTROLLER, i);
}
else
{
AddDevice(SIDEVICE_NONE, i);
}
}
else if (!NetPlay::IsNetPlayRunning())
{
AddDevice(SConfig::GetInstance().m_SIDevice[i], i);
}
}
g_Poll.Hex = 0;
g_Poll.X = 492;
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_device->SendCommand(g_Channel[0].m_Out.Hex, g_Poll.EN0);
g_Channel[1].m_device->SendCommand(g_Channel[1].m_Out.Hex, g_Poll.EN1);
g_Channel[2].m_device->SendCommand(g_Channel[2].m_Out.Hex, g_Poll.EN2);
g_Channel[3].m_device->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));
}
static 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 device_number)
{
g_Channel.at(device_number).m_device.reset();
}
void AddDevice(std::unique_ptr<ISIDevice> device)
{
int device_number = device->GetDeviceNumber();
// Delete the old device
RemoveDevice(device_number);
// Set the new one
g_Channel.at(device_number).m_device = std::move(device);
}
void AddDevice(const SIDevices device, int device_number)
{
AddDevice(SIDevice_Create(device, device_number));
}
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;
}
static void ChangeDeviceCallback(u64 userdata, s64 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 ChangeDeviceDeterministic(SIDevices device, int channel)
{
// Called from savestates, so no need to make it thread safe.
if (GetDeviceType(channel) != device)
{
CoreTiming::ScheduleEvent(0, changeDevice, ((u64)channel << 32) | SIDEVICE_NONE);
CoreTiming::ScheduleEvent(500000000, changeDevice, ((u64)channel << 32) | device);
}
}
void UpdateDevices()
{
// Update inputs at the rate of SI
// Typically 120hz but is variable
g_controller_interface.UpdateInput();
// Update channels and set the status bit if there's new data
g_StatusReg.RDST0 =
!!g_Channel[0].m_device->GetData(g_Channel[0].m_InHi.Hex, g_Channel[0].m_InLo.Hex);
g_StatusReg.RDST1 =
!!g_Channel[1].m_device->GetData(g_Channel[1].m_InHi.Hex, g_Channel[1].m_InLo.Hex);
g_StatusReg.RDST2 =
!!g_Channel[2].m_device->GetData(g_Channel[2].m_InHi.Hex, g_Channel[2].m_InLo.Hex);
g_StatusReg.RDST3 =
!!g_Channel[3].m_device->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;
return g_Channel[channel].m_device->GetDeviceType();
}
static void RunSIBuffer(u64 userdata, s64 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++;
std::unique_ptr<ISIDevice>& device = g_Channel[g_ComCSR.CHANNEL].m_device;
int numOutput = device->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(device->TransferInterval() - cyclesLate, et_transfer_pending);
}
}
}
u32 GetPollXLines()
{
return g_Poll.X;
}
} // end of namespace SerialInterface