// Copyright 2013 Dolphin Emulator Project // Licensed under GPLv2 // Refer to the license.txt file included. #include #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*)&g_SIBuffer[i]), MMIO::DirectWrite((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(&g_Channel[i].m_Out.Hex), MMIO::DirectWrite(&g_Channel[i].m_Out.Hex) ); mmio->Register(base | (SI_CHANNEL_0_IN_HI + 0xC * i), MMIO::ComplexRead([i, rdst_bit](u32) { g_StatusReg.Hex &= ~(1 << rdst_bit); UpdateInterrupts(); return g_Channel[i].m_InHi.Hex; }), MMIO::DirectWrite(&g_Channel[i].m_InHi.Hex) ); mmio->Register(base | (SI_CHANNEL_0_IN_LO + 0xC * i), MMIO::ComplexRead([i, rdst_bit](u32) { g_StatusReg.Hex &= ~(1 << rdst_bit); UpdateInterrupts(); return g_Channel[i].m_InLo.Hex; }), MMIO::DirectWrite(&g_Channel[i].m_InLo.Hex) ); } mmio->Register(base | SI_POLL, MMIO::DirectRead(&g_Poll.Hex), MMIO::DirectWrite(&g_Poll.Hex) ); mmio->Register(base | SI_COM_CSR, MMIO::DirectRead(&g_ComCSR.Hex), MMIO::ComplexWrite([](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(&g_StatusReg.Hex), MMIO::ComplexWrite([](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(&g_EXIClockCount.Hex), MMIO::DirectWrite(&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