// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ #include "R3000A.h" #include "Common.h" #include "SIO/Sio0.h" #include "Sif.h" #include "DebugTools/Breakpoints.h" #include "R5900OpcodeTables.h" #include "IopCounters.h" #include "IopBios.h" #include "IopHw.h" #include "IopDma.h" #include "CDVD/Ps1CD.h" #include "CDVD/CDVD.h" using namespace R3000A; R3000Acpu *psxCpu; // used for constant propagation u32 g_psxConstRegs[32]; u32 g_psxHasConstReg, g_psxFlushedConstReg; // Used to signal to the EE when important actions that need IOP-attention have // happened (hsyncs, vsyncs, IOP exceptions, etc). IOP runs code whenever this // is true, even if it's already running ahead a bit. bool iopEventAction = false; static constexpr uint iopWaitCycles = 384; // Keep inline with EE wait cycle max. bool iopEventTestIsActive = false; alignas(16) psxRegisters psxRegs; void psxReset() { std::memset(&psxRegs, 0, sizeof(psxRegs)); psxRegs.pc = 0xbfc00000; // Start in bootstrap psxRegs.CP0.n.Status = 0x00400000; // BEV = 1 psxRegs.CP0.n.PRid = 0x0000001f; // PRevID = Revision ID, same as the IOP R3000A psxRegs.iopBreak = 0; psxRegs.iopCycleEE = -1; psxRegs.iopCycleEECarry = 0; psxRegs.iopNextEventCycle = psxRegs.cycle + 4; psxHwReset(); PSXCLK = 36864000; ioman::reset(); psxBiosReset(); } void psxShutdown() { //psxCpu->Shutdown(); } void psxException(u32 code, u32 bd) { // PSXCPU_LOG("psxException %x: %x, %x", code, psxHu32(0x1070), psxHu32(0x1074)); //Console.WriteLn("!! psxException %x: %x, %x", code, psxHu32(0x1070), psxHu32(0x1074)); // Set the Cause psxRegs.CP0.n.Cause &= ~0x7f; psxRegs.CP0.n.Cause |= code; // Set the EPC & PC if (bd) { PSXCPU_LOG("bd set"); psxRegs.CP0.n.Cause|= 0x80000000; psxRegs.CP0.n.EPC = (psxRegs.pc - 4); } else psxRegs.CP0.n.EPC = (psxRegs.pc); if (psxRegs.CP0.n.Status & 0x400000) psxRegs.pc = 0xbfc00180; else psxRegs.pc = 0x80000080; // Set the Status psxRegs.CP0.n.Status = (psxRegs.CP0.n.Status &~0x3f) | ((psxRegs.CP0.n.Status & 0xf) << 2); /*if ((((PSXMu32(psxRegs.CP0.n.EPC) >> 24) & 0xfe) == 0x4a)) { // "hokuto no ken" / "Crash Bandicot 2" ... fix PSXMu32(psxRegs.CP0.n.EPC)&= ~0x02000000; }*/ /*if (psxRegs.CP0.n.Cause == 0x400 && (!(psxHu32(0x1450) & 0x8))) { hwIntcIrq(INTC_SBUS); }*/ } __fi void psxSetNextBranch( u32 startCycle, s32 delta ) { // typecast the conditional to signed so that things don't blow up // if startCycle is greater than our next branch cycle. if( (int)(psxRegs.iopNextEventCycle - startCycle) > delta ) psxRegs.iopNextEventCycle = startCycle + delta; } __fi void psxSetNextBranchDelta( s32 delta ) { psxSetNextBranch( psxRegs.cycle, delta ); } __fi int psxTestCycle( u32 startCycle, s32 delta ) { // typecast the conditional to signed so that things don't explode // if the startCycle is ahead of our current cpu cycle. return (int)(psxRegs.cycle - startCycle) >= delta; } __fi int psxRemainingCycles(IopEventId n) { if (psxRegs.interrupt & (1 << n)) return ((psxRegs.cycle - psxRegs.sCycle[n]) + psxRegs.eCycle[n]); else return 0; } __fi void PSX_INT( IopEventId n, s32 ecycle ) { // 19 is CDVD read int, it's supposed to be high. //if (ecycle > 8192 && n != 19) // DevCon.Warning( "IOP cycles high: %d, n %d", ecycle, n ); psxRegs.interrupt |= 1 << n; psxRegs.sCycle[n] = psxRegs.cycle; psxRegs.eCycle[n] = ecycle; psxSetNextBranchDelta(ecycle); const float mutiplier = static_cast(PS2CLK) / static_cast(PSXCLK); const s32 iopDelta = (psxRegs.iopNextEventCycle - psxRegs.cycle) * mutiplier; if (psxRegs.iopCycleEE < iopDelta) { // The EE called this int, so inform it to branch as needed: cpuSetNextEventDelta(iopDelta - psxRegs.iopCycleEE); } } static __fi void IopTestEvent( IopEventId n, void (*callback)() ) { if( !(psxRegs.interrupt & (1 << n)) ) return; if( psxTestCycle( psxRegs.sCycle[n], psxRegs.eCycle[n] ) ) { psxRegs.interrupt &= ~(1 << n); callback(); } else psxSetNextBranch( psxRegs.sCycle[n], psxRegs.eCycle[n] ); } static __fi void Sio0TestEvent(IopEventId n) { if (!(psxRegs.interrupt & (1 << n))) { return; } if (psxTestCycle(psxRegs.sCycle[n], psxRegs.eCycle[n])) { psxRegs.interrupt &= ~(1 << n); g_Sio0.Interrupt(Sio0Interrupt::TEST_EVENT); } else { psxSetNextBranch(psxRegs.sCycle[n], psxRegs.eCycle[n]); } } static __fi void _psxTestInterrupts() { IopTestEvent(IopEvt_SIF0, sif0Interrupt); // SIF0 IopTestEvent(IopEvt_SIF1, sif1Interrupt); // SIF1 IopTestEvent(IopEvt_SIF2, sif2Interrupt); // SIF2 Sio0TestEvent(IopEvt_SIO); IopTestEvent(IopEvt_CdvdSectorReady, cdvdSectorReady); IopTestEvent(IopEvt_CdvdRead, cdvdReadInterrupt); // Profile-guided Optimization (sorta) // The following ints are rarely called. Encasing them in a conditional // as follows helps speed up most games. if( psxRegs.interrupt & ((1 << IopEvt_Cdvd) | (1 << IopEvt_Dma11) | (1 << IopEvt_Dma12) | (1 << IopEvt_Cdrom) | (1 << IopEvt_CdromRead) | (1 << IopEvt_DEV9) | (1 << IopEvt_USB))) { IopTestEvent(IopEvt_Cdvd, cdvdActionInterrupt); IopTestEvent(IopEvt_Dma11, psxDMA11Interrupt); // SIO2 IopTestEvent(IopEvt_Dma12, psxDMA12Interrupt); // SIO2 IopTestEvent(IopEvt_Cdrom, cdrInterrupt); IopTestEvent(IopEvt_CdromRead, cdrReadInterrupt); IopTestEvent(IopEvt_DEV9, dev9Interrupt); IopTestEvent(IopEvt_USB, usbInterrupt); } } __ri void iopEventTest() { psxRegs.iopNextEventCycle = psxRegs.cycle + iopWaitCycles; if (psxTestCycle(psxNextStartCounter, psxNextDeltaCounter)) { psxRcntUpdate(); iopEventAction = true; } else { // start the next branch at the next counter event by default // the interrupt code below will assign nearer branches if needed. if (psxNextDeltaCounter < static_cast(psxRegs.iopNextEventCycle - psxNextStartCounter)) psxRegs.iopNextEventCycle = psxNextStartCounter + psxNextDeltaCounter; } if (psxRegs.interrupt) { iopEventTestIsActive = true; _psxTestInterrupts(); iopEventTestIsActive = false; } if ((psxHu32(0x1078) != 0) && ((psxHu32(0x1070) & psxHu32(0x1074)) != 0)) { if ((psxRegs.CP0.n.Status & 0xFE01) >= 0x401) { PSXCPU_LOG("Interrupt: %x %x", psxHu32(0x1070), psxHu32(0x1074)); psxException(0, 0); iopEventAction = true; } } } void iopTestIntc() { if( psxHu32(0x1078) == 0 ) return; if( (psxHu32(0x1070) & psxHu32(0x1074)) == 0 ) return; if( !eeEventTestIsActive ) { // An iop exception has occurred while the EE is running code. // Inform the EE to branch so the IOP can handle it promptly: cpuSetNextEventDelta( 16 ); iopEventAction = true; //Console.Error( "** IOP Needs an EE EventText, kthx ** %d", iopCycleEE ); // Note: No need to set the iop's branch delta here, since the EE // will run an IOP branch test regardless. } else if ( !iopEventTestIsActive ) psxSetNextBranchDelta( 2 ); } inline bool psxIsBranchOrJump(u32 addr) { u32 op = iopMemRead32(addr); const R5900::OPCODE& opcode = R5900::GetInstruction(op); return (opcode.flags & IS_BRANCH) != 0; } // The next two functions return 0 if no breakpoint is needed, // 1 if it's needed on the current pc, 2 if it's needed in the delay slot // 3 if needed in both int psxIsBreakpointNeeded(u32 addr) { int bpFlags = 0; if (CBreakPoints::IsAddressBreakPoint(BREAKPOINT_IOP, addr)) bpFlags += 1; // there may be a breakpoint in the delay slot if (psxIsBranchOrJump(addr) && CBreakPoints::IsAddressBreakPoint(BREAKPOINT_IOP, addr + 4)) bpFlags += 2; return bpFlags; } int psxIsMemcheckNeeded(u32 pc) { if (CBreakPoints::GetNumMemchecks() == 0) return 0; u32 addr = pc; if (psxIsBranchOrJump(addr)) addr += 4; u32 op = iopMemRead32(addr); const R5900::OPCODE& opcode = R5900::GetInstruction(op); if (opcode.flags & IS_MEMORY) return addr == pc ? 1 : 2; return 0; }