mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
838 lines
26 KiB
C++
838 lines
26 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
||
// SPDX-License-Identifier: GPL-3.0+
|
||
|
||
#include "Common.h"
|
||
|
||
#include "common/StringUtil.h"
|
||
#include "ps2/BiosTools.h"
|
||
#include "R5900.h"
|
||
#include "R3000A.h"
|
||
#include "ps2/pgif.h" // pgif init
|
||
#include "VUmicro.h"
|
||
#include "COP0.h"
|
||
#include "MTVU.h"
|
||
#include "VMManager.h"
|
||
|
||
#include "Hardware.h"
|
||
#include "IPU/IPUdma.h"
|
||
#include "arm64/aDMAC.h"
|
||
|
||
#include "Elfheader.h"
|
||
#include "CDVD/CDVD.h"
|
||
#include "Patch.h"
|
||
#include "GameDatabase.h"
|
||
#include "GSDumpReplayer.h"
|
||
|
||
#include "DebugTools/Breakpoints.h"
|
||
#include "DebugTools/MIPSAnalyst.h"
|
||
#include "DebugTools/SymbolGuardian.h"
|
||
#include "R5900OpcodeTables.h"
|
||
|
||
#include "fmt/format.h"
|
||
|
||
using namespace R5900; // for R5900 disasm tools
|
||
|
||
s32 EEsCycle; // used to sync the IOP to the EE
|
||
u64 EEoCycle;
|
||
|
||
alignas(16) cpuRegistersPack _cpuRegistersPack;
|
||
alignas(16) tlbs tlb[48];
|
||
cachedTlbs_t cachedTlbs;
|
||
|
||
R5900cpu *Cpu = NULL;
|
||
|
||
static constexpr uint eeWaitCycles = 3072;
|
||
|
||
bool eeEventTestIsActive = false;
|
||
EE_intProcessStatus eeRunInterruptScan = INT_NOT_RUNNING;
|
||
|
||
u32 g_eeloadMain = 0, g_eeloadExec = 0, g_osdsys_str = 0;
|
||
|
||
/* I don't know how much space for args there is in the memory block used for args in full boot mode,
|
||
but in fast boot mode, the block we use can fit at least 16 argv pointers (varies with BIOS version).
|
||
The second EELOAD call during full boot has three built-in arguments ("EELOAD rom0:PS2LOGO <ELF>"),
|
||
meaning that only the first 13 game arguments supplied by the user can be added on and passed through.
|
||
In fast boot mode, 15 arguments can fit because the only call to EELOAD is "<ELF> <<args>>". */
|
||
const int kMaxArgs = 16;
|
||
uptr g_argPtrs[kMaxArgs];
|
||
#define DEBUG_LAUNCHARG 0 // show lots of helpful console messages as the launch arguments are passed to the game
|
||
|
||
void cpuReset()
|
||
{
|
||
std::memset(&cpuRegs, 0, sizeof(cpuRegs));
|
||
std::memset(&fpuRegs, 0, sizeof(fpuRegs));
|
||
std::memset(&tlb, 0, sizeof(tlb));
|
||
cachedTlbs.count = 0;
|
||
|
||
cpuRegs.pc = 0xbfc00000; //set pc reg to stack
|
||
cpuRegs.CP0.n.Config = 0x440;
|
||
cpuRegs.CP0.n.Status.val= 0x70400004; //0x10900000 <-- wrong; // COP0 enabled | BEV = 1 | TS = 1
|
||
cpuRegs.CP0.n.PRid = 0x00002e20; // PRevID = Revision ID, same as R5900
|
||
fpuRegs.fprc[0] = 0x00002e30; // fpu Revision..
|
||
fpuRegs.fprc[31] = 0x01000001; // fpu Status/Control
|
||
|
||
cpuRegs.nextEventCycle = cpuRegs.cycle + 4;
|
||
EEsCycle = 0;
|
||
EEoCycle = cpuRegs.cycle;
|
||
|
||
psxReset();
|
||
pgifInit();
|
||
|
||
extern void Deci2Reset(); // lazy, no good header for it yet.
|
||
Deci2Reset();
|
||
|
||
AllowParams1 = !VMManager::Internal::IsFastBootInProgress();
|
||
AllowParams2 = !VMManager::Internal::IsFastBootInProgress();
|
||
ParamsRead = false;
|
||
|
||
g_eeloadMain = 0;
|
||
g_eeloadExec = 0;
|
||
g_osdsys_str = 0;
|
||
|
||
CBreakPoints::ClearSkipFirst();
|
||
}
|
||
|
||
__ri void cpuException(u32 code, u32 bd)
|
||
{
|
||
bool errLevel2, checkStatus;
|
||
u32 offset = 0;
|
||
|
||
cpuRegs.branch = 0; // Tells the interpreter that an exception occurred during a branch.
|
||
cpuRegs.CP0.n.Cause = code & 0xffff;
|
||
|
||
if(cpuRegs.CP0.n.Status.b.ERL == 0)
|
||
{
|
||
//Error Level 0-1
|
||
errLevel2 = false;
|
||
checkStatus = (cpuRegs.CP0.n.Status.b.BEV == 0); // for TLB/general exceptions
|
||
|
||
if (((code & 0x7C) >= 0x8) && ((code & 0x7C) <= 0xC))
|
||
offset = 0x0; //TLB Refill
|
||
else if ((code & 0x7C) == 0x0)
|
||
offset = 0x200; //Interrupt
|
||
else
|
||
offset = 0x180; // Everything else
|
||
}
|
||
else
|
||
{
|
||
//Error Level 2
|
||
errLevel2 = true;
|
||
checkStatus = (cpuRegs.CP0.n.Status.b.DEV == 0); // for perf/debug exceptions
|
||
|
||
Console.Error("*PCSX2* FIX ME: Level 2 cpuException");
|
||
if ((code & 0x38000) <= 0x8000 )
|
||
{
|
||
//Reset / NMI
|
||
cpuRegs.pc = 0xBFC00000;
|
||
Console.Warning("Reset request");
|
||
cpuUpdateOperationMode();
|
||
return;
|
||
}
|
||
else if ((code & 0x38000) == 0x10000)
|
||
offset = 0x80; //Performance Counter
|
||
else if ((code & 0x38000) == 0x18000)
|
||
offset = 0x100; //Debug
|
||
else
|
||
Console.Error("Unknown Level 2 Exception!! Cause %x", code);
|
||
}
|
||
|
||
if (cpuRegs.CP0.n.Status.b.EXL == 0)
|
||
{
|
||
cpuRegs.CP0.n.Status.b.EXL = 1;
|
||
if (bd)
|
||
{
|
||
Console.Warning("branch delay!!");
|
||
cpuRegs.CP0.n.EPC = cpuRegs.pc - 4;
|
||
cpuRegs.CP0.n.Cause |= 0x80000000;
|
||
}
|
||
else
|
||
{
|
||
cpuRegs.CP0.n.EPC = cpuRegs.pc;
|
||
cpuRegs.CP0.n.Cause &= ~0x80000000;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
offset = 0x180; //Override the cause
|
||
if (errLevel2) Console.Warning("cpuException: Status.EXL = 1 cause %x", code);
|
||
}
|
||
|
||
if (checkStatus)
|
||
cpuRegs.pc = 0x80000000 + offset;
|
||
else
|
||
cpuRegs.pc = 0xBFC00200 + offset;
|
||
|
||
cpuUpdateOperationMode();
|
||
}
|
||
|
||
void cpuTlbMiss(u32 addr, u32 bd, u32 excode)
|
||
{
|
||
// Avoid too much spamming on the interpreter
|
||
if (Cpu != &intCpu || IsDebugBuild) {
|
||
Console.Error("cpuTlbMiss pc:%x, cycl:%llx, addr: %x, status=%x, code=%x",
|
||
cpuRegs.pc, cpuRegs.cycle, addr, cpuRegs.CP0.n.Status.val, excode);
|
||
}
|
||
|
||
cpuRegs.CP0.n.BadVAddr = addr;
|
||
cpuRegs.CP0.n.Context &= 0xFF80000F;
|
||
cpuRegs.CP0.n.Context |= (addr >> 9) & 0x007FFFF0;
|
||
cpuRegs.CP0.n.EntryHi = (addr & 0xFFFFE000) | (cpuRegs.CP0.n.EntryHi & 0x1FFF);
|
||
|
||
cpuRegs.pc -= 4;
|
||
cpuException(excode, bd);
|
||
}
|
||
|
||
void cpuTlbMissR(u32 addr, u32 bd) {
|
||
cpuTlbMiss(addr, bd, EXC_CODE_TLBL);
|
||
}
|
||
|
||
void cpuTlbMissW(u32 addr, u32 bd) {
|
||
cpuTlbMiss(addr, bd, EXC_CODE_TLBS);
|
||
}
|
||
|
||
// sets a branch test to occur some time from an arbitrary starting point.
|
||
__fi void cpuSetNextEvent( u64 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)(cpuRegs.nextEventCycle - startCycle) > delta )
|
||
{
|
||
cpuRegs.nextEventCycle = startCycle + delta;
|
||
}
|
||
}
|
||
|
||
// sets a branch to occur some time from the current cycle
|
||
__fi void cpuSetNextEventDelta( s32 delta )
|
||
{
|
||
cpuSetNextEvent( cpuRegs.cycle, delta );
|
||
}
|
||
|
||
__fi int cpuGetCycles(int interrupt)
|
||
{
|
||
if(interrupt == VU_MTVU_BUSY && (!THREAD_VU1 || INSTANT_VU1))
|
||
return 1;
|
||
else
|
||
{
|
||
const int cycles = (cpuRegs.sCycle[interrupt] + cpuRegs.eCycle[interrupt]) - cpuRegs.cycle;
|
||
return std::max(1, cycles);
|
||
}
|
||
|
||
}
|
||
|
||
// tests the cpu cycle against the given start and delta values.
|
||
// Returns true if the delta time has passed.
|
||
__fi int cpuTestCycle( u64 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)(cpuRegs.cycle - startCycle) >= delta;
|
||
}
|
||
|
||
// tells the EE to run the branch test the next time it gets a chance.
|
||
__fi void cpuSetEvent()
|
||
{
|
||
cpuRegs.nextEventCycle = cpuRegs.cycle;
|
||
}
|
||
|
||
__fi void cpuClearInt( uint i )
|
||
{
|
||
pxAssume( i < 32 );
|
||
cpuRegs.interrupt &= ~(1 << i);
|
||
cpuRegs.dmastall &= ~(1 << i);
|
||
}
|
||
|
||
static __fi void TESTINT( u8 n, void (*callback)() )
|
||
{
|
||
if( !(cpuRegs.interrupt & (1 << n)) ) return;
|
||
|
||
if(CHECK_INSTANTDMAHACK || cpuTestCycle( cpuRegs.sCycle[n], cpuRegs.eCycle[n] ) )
|
||
{
|
||
cpuClearInt( n );
|
||
callback();
|
||
}
|
||
else
|
||
cpuSetNextEvent( cpuRegs.sCycle[n], cpuRegs.eCycle[n] );
|
||
}
|
||
|
||
// [TODO] move this function to Dmac.cpp, and remove most of the DMAC-related headers from
|
||
// being included into R5900.cpp.
|
||
static __fi bool _cpuTestInterrupts()
|
||
{
|
||
|
||
if (!dmacRegs.ctrl.DMAE || (psHu8(DMAC_ENABLER+2) & 1))
|
||
{
|
||
//Console.Write("DMAC Disabled or suspended");
|
||
return false;
|
||
}
|
||
|
||
eeRunInterruptScan = INT_RUNNING;
|
||
|
||
while (eeRunInterruptScan == INT_RUNNING)
|
||
{
|
||
/* These are 'pcsx2 interrupts', they handle asynchronous stuff
|
||
that depends on the cycle timings */
|
||
TESTINT(VU_MTVU_BUSY, MTVUInterrupt);
|
||
TESTINT(DMAC_VIF1, recDMACInterrupt_VIF1);
|
||
TESTINT(DMAC_GIF, recDMACInterrupt_GIF);
|
||
TESTINT(DMAC_SIF0, EEsif0Interrupt);
|
||
TESTINT(DMAC_SIF1, EEsif1Interrupt);
|
||
// Profile-guided Optimization (sorta)
|
||
// The following ints are rarely called. Encasing them in a conditional
|
||
// as follows helps speed up most games.
|
||
|
||
if (cpuRegs.interrupt & ((1 << DMAC_VIF0) | (1 << DMAC_FROM_IPU) | (1 << DMAC_TO_IPU)
|
||
| (1 << DMAC_FROM_SPR) | (1 << DMAC_TO_SPR) | (1 << DMAC_MFIFO_VIF) | (1 << DMAC_MFIFO_GIF)
|
||
| (1 << VIF_VU0_FINISH) | (1 << VIF_VU1_FINISH) | (1 << IPU_PROCESS)))
|
||
{
|
||
TESTINT(DMAC_VIF0, recDMACInterrupt_VIF0);
|
||
|
||
TESTINT(DMAC_FROM_IPU, recDMACInterrupt_IPU0);
|
||
TESTINT(DMAC_TO_IPU, recDMACInterrupt_IPU1);
|
||
TESTINT(IPU_PROCESS, ipuCMDProcess);
|
||
|
||
TESTINT(DMAC_FROM_SPR, recDMACInterrupt_SPR0);
|
||
TESTINT(DMAC_TO_SPR, recDMACInterrupt_SPR1);
|
||
|
||
TESTINT(DMAC_MFIFO_VIF, vifMFIFOInterrupt);
|
||
TESTINT(DMAC_MFIFO_GIF, gifMFIFOInterrupt);
|
||
|
||
TESTINT(VIF_VU0_FINISH, vif0VUFinish);
|
||
TESTINT(VIF_VU1_FINISH, vif1VUFinish);
|
||
}
|
||
|
||
if (eeRunInterruptScan == INT_REQ_LOOP)
|
||
eeRunInterruptScan = INT_RUNNING;
|
||
else
|
||
break;
|
||
}
|
||
|
||
eeRunInterruptScan = INT_NOT_RUNNING;
|
||
|
||
if ((cpuRegs.interrupt & 0x1FFFF) & ~cpuRegs.dmastall)
|
||
return true;
|
||
else
|
||
return false;
|
||
}
|
||
|
||
static __fi void _cpuTestTIMR()
|
||
{
|
||
cpuRegs.CP0.n.Count += cpuRegs.cycle - cpuRegs.lastCOP0Cycle;
|
||
cpuRegs.lastCOP0Cycle = cpuRegs.cycle;
|
||
|
||
// fixme: this looks like a hack to make up for the fact that the TIMR
|
||
// doesn't yet have a proper mechanism for setting itself up on a nextEventCycle.
|
||
// A proper fix would schedule the TIMR to trigger at a specific cycle anytime
|
||
// the Count or Compare registers are modified.
|
||
|
||
if ( (cpuRegs.CP0.n.Status.val & 0x8000) &&
|
||
cpuRegs.CP0.n.Count >= cpuRegs.CP0.n.Compare && cpuRegs.CP0.n.Count < cpuRegs.CP0.n.Compare+1000 )
|
||
{
|
||
Console.WriteLn( Color_Magenta, "timr intr: %x, %x", cpuRegs.CP0.n.Count, cpuRegs.CP0.n.Compare);
|
||
cpuException(0x808000, cpuRegs.branch);
|
||
}
|
||
}
|
||
|
||
static __fi void _cpuTestPERF()
|
||
{
|
||
// Perfs are updated when read by games (COP0's MFC0/MTC0 instructions), so we need
|
||
// only update them at semi-regular intervals to keep cpuRegs.cycle from wrapping
|
||
// around twice on us btween updates. Hence this function is called from the cpu's
|
||
// Counters update.
|
||
|
||
COP0_UpdatePCCR();
|
||
}
|
||
|
||
// Checks the COP0.Status for exception enablings.
|
||
// Exception handling for certain modes is *not* currently supported, this function filters
|
||
// them out. Exceptions while the exception handler is active (EIE), or exceptions of any
|
||
// level other than 0 are ignored here.
|
||
|
||
static bool cpuIntsEnabled(int Interrupt)
|
||
{
|
||
bool IntType = !!(cpuRegs.CP0.n.Status.val & Interrupt); //Choose either INTC or DMAC, depending on what called it
|
||
|
||
return IntType && cpuRegs.CP0.n.Status.b.EIE && cpuRegs.CP0.n.Status.b.IE &&
|
||
!cpuRegs.CP0.n.Status.b.EXL && (cpuRegs.CP0.n.Status.b.ERL == 0);
|
||
}
|
||
|
||
// Shared portion of the branch test, called from both the Interpreter
|
||
// and the recompiler. (moved here to help alleviate redundant code)
|
||
__fi void _cpuEventTest_Shared()
|
||
{
|
||
eeEventTestIsActive = true;
|
||
cpuRegs.nextEventCycle = cpuRegs.cycle + eeWaitCycles;
|
||
cpuRegs.lastEventCycle = cpuRegs.cycle;
|
||
// ---- INTC / DMAC (CPU-level Exceptions) -----------------
|
||
// Done first because exceptions raised during event tests need to be postponed a few
|
||
// cycles (fixes Grandia II [PAL], which does a spin loop on a vsync and expects to
|
||
// be able to read the value before the exception handler clears it).
|
||
|
||
uint mask = intcInterrupt() | dmacInterrupt();
|
||
|
||
if (cpuIntsEnabled(mask))
|
||
cpuException(mask, cpuRegs.branch);
|
||
|
||
// ---- IOP -------------
|
||
// * It's important to run a iopEventTest before calling ExecuteBlock. This
|
||
// is because the IOP does not always perform branch tests before returning
|
||
// (during the prev branch) and also so it can act on the state the EE has
|
||
// given it before executing any code.
|
||
//
|
||
// * The IOP cannot always be run. If we run IOP code every time through the
|
||
// cpuEventTest, the IOP generally starts to run way ahead of the EE.
|
||
|
||
// It's also important to sync up the IOP before updating the timers, since gates will depend on starting/stopping in the right place!
|
||
EEsCycle += cpuRegs.cycle - EEoCycle;
|
||
EEoCycle = cpuRegs.cycle;
|
||
|
||
if (EEsCycle > 0)
|
||
iopEventAction = true;
|
||
|
||
if (iopEventAction)
|
||
{
|
||
//if( EEsCycle < -450 )
|
||
// Console.WriteLn( " IOP ahead by: %d cycles", -EEsCycle );
|
||
|
||
EEsCycle = psxCpu->ExecuteBlock(EEsCycle);
|
||
|
||
iopEventAction = false;
|
||
}
|
||
|
||
iopEventTest();
|
||
|
||
if (cpuTestCycle(nextStartCounter, nextDeltaCounter))
|
||
{
|
||
rcntUpdate();
|
||
_cpuTestPERF();
|
||
}
|
||
|
||
_cpuTestTIMR();
|
||
|
||
// ---- Interrupts -------------
|
||
// These are basically just DMAC-related events, which also piggy-back the same bits as
|
||
// the PS2's own DMA channel IRQs and IRQ Masks.
|
||
|
||
if (cpuRegs.interrupt)
|
||
{
|
||
// This is a BIOS hack because the coding in the BIOS is terrible but the bug is masked by Data Cache
|
||
// where a DMA buffer is overwritten without waiting for the transfer to end, which causes the fonts to get all messed up
|
||
// so to fix it, we run all the DMA's instantly when in the BIOS.
|
||
// Only use the lower 17 bits of the cpuRegs.interrupt as the upper bits are for VU0/1 sync which can't be done in a tight loop
|
||
if (CHECK_INSTANTDMAHACK && dmacRegs.ctrl.DMAE && !(psHu8(DMAC_ENABLER + 2) & 1) && (cpuRegs.interrupt & 0x1FFFF))
|
||
{
|
||
while ((cpuRegs.interrupt & 0x1FFFF) && _cpuTestInterrupts())
|
||
;
|
||
}
|
||
else
|
||
_cpuTestInterrupts();
|
||
}
|
||
|
||
// ---- VU Sync -------------
|
||
// We're in a EventTest. All dynarec registers are flushed
|
||
// so there is no need to freeze registers here.
|
||
CpuVU0->ExecuteBlock();
|
||
CpuVU1->ExecuteBlock();
|
||
|
||
// ---- Schedule Next Event Test --------------
|
||
// Hot path: PS2 mode has PSXCLK=36864000 fixed, so PS2CLK/PSXCLK is exactly
|
||
// 8 — encode as shift to skip the per-event u32→float conversion + float
|
||
// division + truncate. PS1 mode (PSXCLK=33868800, set via SBUS_F240 write
|
||
// in HwWrite.cpp) keeps the precise float path so the IOP↔EE ratio matches
|
||
// R3000AInterpreter.cpp's PS1 path (cnum=1280/cdenom=147). Mirrors the
|
||
// old armsx2 port's Android fast-path but gated on PSXCLK so PS1 stays
|
||
// correct. _cpuEventTest_Shared is 8–10% of CPU at BIOS idle.
|
||
const s32 iopDelta = static_cast<s32>(psxRegs.iopNextEventCycle - psxRegs.cycle);
|
||
s32 nextIopEventDeta;
|
||
if (PSXCLK == 36864000) [[likely]]
|
||
{
|
||
nextIopEventDeta = iopDelta << 3;
|
||
}
|
||
else
|
||
{
|
||
const float mutiplier = static_cast<float>(PS2CLK) / static_cast<float>(PSXCLK);
|
||
nextIopEventDeta = static_cast<s32>(iopDelta * mutiplier);
|
||
}
|
||
// 8 or more cycles behind and there's an event scheduled
|
||
if (EEsCycle >= nextIopEventDeta)
|
||
{
|
||
// EE's running way ahead of the IOP still, so we should branch quickly to give the
|
||
// IOP extra timeslices in short order.
|
||
|
||
cpuSetNextEventDelta(48);
|
||
//Console.Warning( "EE ahead of the IOP -- Rapid Event! %d", EEsCycle );
|
||
}
|
||
else
|
||
{
|
||
// Otherwise IOP is caught up/not doing anything so we can wait for the next event.
|
||
cpuSetNextEventDelta(nextIopEventDeta - EEsCycle);
|
||
}
|
||
|
||
// Apply vsync and other counter nextCycles
|
||
cpuSetNextEvent(nextStartCounter, nextDeltaCounter);
|
||
|
||
eeEventTestIsActive = false;
|
||
}
|
||
|
||
__ri void cpuTestINTCInts()
|
||
{
|
||
// Check the COP0's Status register for general interrupt disables, and the 0x400
|
||
// bit (which is INTC master toggle).
|
||
if (!cpuIntsEnabled(0x400))
|
||
return;
|
||
|
||
if ((psHu32(INTC_STAT) & psHu32(INTC_MASK)) == 0)
|
||
return;
|
||
|
||
cpuSetNextEventDelta(4);
|
||
if (eeEventTestIsActive && (psxRegs.iopCycleEE > 0))
|
||
{
|
||
psxRegs.iopBreak += psxRegs.iopCycleEE; // record the number of cycles the IOP didn't run.
|
||
psxRegs.iopCycleEE = 0;
|
||
}
|
||
}
|
||
|
||
__fi void cpuTestDMACInts()
|
||
{
|
||
// Check the COP0's Status register for general interrupt disables, and the 0x800
|
||
// bit (which is the DMAC master toggle).
|
||
if (!cpuIntsEnabled(0x800))
|
||
return;
|
||
|
||
if (((psHu16(0xe012) & psHu16(0xe010)) == 0) &&
|
||
((psHu16(0xe010) & 0x8000) == 0))
|
||
return;
|
||
|
||
cpuSetNextEventDelta(4);
|
||
if (eeEventTestIsActive && (psxRegs.iopCycleEE > 0))
|
||
{
|
||
psxRegs.iopBreak += psxRegs.iopCycleEE; // record the number of cycles the IOP didn't run.
|
||
psxRegs.iopCycleEE = 0;
|
||
}
|
||
}
|
||
|
||
__fi void cpuTestTIMRInts()
|
||
{
|
||
if ((cpuRegs.CP0.n.Status.val & 0x10007) == 0x10001)
|
||
{
|
||
_cpuTestPERF();
|
||
_cpuTestTIMR();
|
||
}
|
||
}
|
||
|
||
__fi void cpuTestHwInts()
|
||
{
|
||
cpuTestINTCInts();
|
||
cpuTestDMACInts();
|
||
cpuTestTIMRInts();
|
||
}
|
||
|
||
__fi void CPU_SET_DMASTALL(EE_EventType n, bool set)
|
||
{
|
||
if (set)
|
||
cpuRegs.dmastall |= 1 << n;
|
||
else
|
||
cpuRegs.dmastall &= ~(1 << n);
|
||
}
|
||
|
||
__fi void CPU_INT( EE_EventType n, s32 ecycle)
|
||
{
|
||
// If it's retunning too quick, just rerun the DMA, there's no point in running the EE for < 4 cycles.
|
||
// This causes a huge uplift in performance for ONI FMV's.
|
||
if (ecycle < 4 && !(cpuRegs.dmastall & (1 << n)) && eeRunInterruptScan != INT_NOT_RUNNING)
|
||
{
|
||
eeRunInterruptScan = INT_REQ_LOOP;
|
||
cpuRegs.interrupt |= 1 << n;
|
||
cpuRegs.sCycle[n] = cpuRegs.cycle;
|
||
cpuRegs.eCycle[n] = 0;
|
||
return;
|
||
}
|
||
|
||
// EE events happen 8 cycles in the future instead of whatever was requested.
|
||
// This can be used on games with PATH3 masking issues for example, or when
|
||
// some FMV look bad.
|
||
if (CHECK_EETIMINGHACK && n < VIF_VU0_FINISH)
|
||
ecycle = 8;
|
||
|
||
cpuRegs.interrupt |= 1 << n;
|
||
cpuRegs.sCycle[n] = cpuRegs.cycle;
|
||
cpuRegs.eCycle[n] = ecycle;
|
||
|
||
// Interrupt is happening soon: make sure both EE and IOP are aware.
|
||
|
||
if (ecycle <= 28 && psxRegs.iopCycleEE > 0)
|
||
{
|
||
// If running in the IOP, force it to break immediately into the EE.
|
||
// the EE's branch test is due to run.
|
||
|
||
psxRegs.iopBreak += psxRegs.iopCycleEE; // record the number of cycles the IOP didn't run.
|
||
psxRegs.iopCycleEE = 0;
|
||
}
|
||
|
||
cpuSetNextEventDelta(cpuRegs.eCycle[n]);
|
||
}
|
||
|
||
// Count arguments, save their starting locations, and replace the space separators with null terminators so they're separate strings
|
||
int ParseArgumentString(u32 arg_block)
|
||
{
|
||
if (!arg_block)
|
||
return 0;
|
||
|
||
int argc = 0;
|
||
bool wasSpace = true; // status of last char. scanned
|
||
int args_len = strlen((char *)PSM(arg_block));
|
||
for (int i = 0; i < args_len; i++)
|
||
{
|
||
char curchar = *(char *)PSM(arg_block + i);
|
||
if (curchar == '\0')
|
||
break; // should never reach this
|
||
|
||
bool isSpace = (curchar == ' ');
|
||
if (isSpace)
|
||
memset(PSM(arg_block + i), 0, 1);
|
||
else if (wasSpace) // then we're at a new arg
|
||
{
|
||
if (argc < kMaxArgs)
|
||
{
|
||
g_argPtrs[argc] = arg_block + i;
|
||
argc++;
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLn("ParseArgumentString: Discarded additional arguments beyond the maximum of %d.", kMaxArgs);
|
||
break;
|
||
}
|
||
}
|
||
wasSpace = isSpace;
|
||
}
|
||
#if DEBUG_LAUNCHARG
|
||
// Check our args block
|
||
Console.WriteLn("ParseArgumentString: Saving these strings:");
|
||
for (int a = 0; a < argc; a++)
|
||
Console.WriteLn("%p -> '%s'.", g_argPtrs[a], (char *)PSM(g_argPtrs[a]));
|
||
#endif
|
||
return argc;
|
||
}
|
||
|
||
// Called from recompilers; define is mandatory.
|
||
void eeloadHook()
|
||
{
|
||
std::string elfname;
|
||
int argc = cpuRegs.GPR.n.a0.SD[0];
|
||
if (argc) // calls to EELOAD *after* the first one during the startup process will come here
|
||
{
|
||
#if DEBUG_LAUNCHARG
|
||
Console.WriteLn("eeloadHook: EELOAD was called with %d arguments according to $a0 and %d according to vargs block:",
|
||
argc, memRead32(cpuRegs.GPR.n.a1.UD[0] - 4));
|
||
for (int a = 0; a < argc; a++)
|
||
Console.WriteLn("argv[%d]: %p -> %p -> '%s'", a, cpuRegs.GPR.n.a1.UL[0] + (a * 4),
|
||
memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4)), (char *)PSM(memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4))));
|
||
#endif
|
||
if (argc > 1)
|
||
elfname = (char*)PSM(memRead32(cpuRegs.GPR.n.a1.UD[0] + 4)); // argv[1] in OSDSYS's invocation "EELOAD <game ELF>"
|
||
|
||
// This code fires if the user chooses "full boot". First the Sony Computer Entertainment screen appears. This is the result
|
||
// of an EELOAD call that does not want to accept launch arguments (but we patch it to do so in eeloadHook2() in fast boot
|
||
// mode). Then EELOAD is called with the argument "rom0:PS2LOGO". At this point, we do not need any additional tricks
|
||
// because EELOAD is now ready to accept launch arguments. So in full-boot mode, we simply wait for PS2LOGO to be called,
|
||
// then we add the desired launch arguments. PS2LOGO passes those on to the game itself as it calls EELOAD a third time.
|
||
if (!EmuConfig.CurrentGameArgs.empty() && elfname == "rom0:PS2LOGO")
|
||
{
|
||
const char *argString = EmuConfig.CurrentGameArgs.c_str();
|
||
Console.WriteLn("eeloadHook: Supplying launch argument(s) '%s' to module '%s'...", argString, elfname.c_str());
|
||
|
||
// Join all arguments by space characters so they can be processed as one string by ParseArgumentString(), then add the
|
||
// user's launch arguments onto the end
|
||
u32 arg_ptr = 0;
|
||
int arg_len = 0;
|
||
for (int a = 0; a < argc; a++)
|
||
{
|
||
arg_ptr = memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4));
|
||
arg_len = strlen((char *)PSM(arg_ptr));
|
||
memset(PSM(arg_ptr + arg_len), 0x20, 1);
|
||
}
|
||
strcpy((char *)PSM(arg_ptr + arg_len + 1), EmuConfig.CurrentGameArgs.c_str());
|
||
u32 first_arg_ptr = memRead32(cpuRegs.GPR.n.a1.UD[0]);
|
||
#if DEBUG_LAUNCHARG
|
||
Console.WriteLn("eeloadHook: arg block is '%s'.", (char *)PSM(first_arg_ptr));
|
||
#endif
|
||
argc = ParseArgumentString(first_arg_ptr);
|
||
|
||
// Write pointer to next slot in $a1
|
||
for (int a = 0; a < argc; a++)
|
||
memWrite32(cpuRegs.GPR.n.a1.UD[0] + (a * 4), g_argPtrs[a]);
|
||
cpuRegs.GPR.n.a0.SD[0] = argc;
|
||
#if DEBUG_LAUNCHARG
|
||
// Check our work
|
||
Console.WriteLn("eeloadHook: New arguments are:");
|
||
for (int a = 0; a < argc; a++)
|
||
Console.WriteLn("argv[%d]: %p -> '%s'", a, memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4)),
|
||
(char *)PSM(memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4))));
|
||
#endif
|
||
}
|
||
// else it's presumed that the invocation is "EELOAD <game ELF> <<launch args>>", coming from PS2LOGO, and we needn't do
|
||
// anything more
|
||
}
|
||
#if DEBUG_LAUNCHARG
|
||
// This code fires in full/fast boot mode when EELOAD is called the first/only time. When EELOAD is not given any arguments,
|
||
// it calls rom0:OSDSYS by default, which displays the Sony Computer Entertainment screen. OSDSYS then calls "EELOAD
|
||
// rom0:PS2LOGO" and we end up above.
|
||
else
|
||
Console.WriteLn("eeloadHook: EELOAD was called with no arguments.");
|
||
#endif
|
||
|
||
// If "fast boot" was chosen, then on EELOAD's first call we won't yet know what the game's ELF is. Find the name and write it
|
||
// into EELOAD's memory.
|
||
if (VMManager::Internal::IsFastBootInProgress() && elfname.empty())
|
||
{
|
||
const std::string& elf_override = VMManager::Internal::GetELFOverride();
|
||
if (!elf_override.empty())
|
||
{
|
||
elfname = fmt::format("host:{}", elf_override);
|
||
}
|
||
else
|
||
{
|
||
CDVDDiscType disc_type;
|
||
std::string disc_elf;
|
||
cdvdGetDiscInfo(nullptr, &disc_elf, nullptr, nullptr, &disc_type);
|
||
if (disc_type == CDVDDiscType::PS2Disc)
|
||
{
|
||
// only allow fast boot for PS2 games
|
||
elfname = std::move(disc_elf);
|
||
}
|
||
else
|
||
{
|
||
Console.Warning(fmt::format("Not allowing fast boot for non-PS2 ELF {}", disc_elf));
|
||
}
|
||
}
|
||
|
||
// When fast-booting, we insert the game's ELF name into EELOAD so that the game is called instead of the default call of
|
||
// "rom0:OSDSYS"; any launch arguments supplied by the user will be inserted into EELOAD later by eeloadHook2()
|
||
if (!elfname.empty())
|
||
{
|
||
// Find and save location of default/fallback call "rom0:OSDSYS"; to be used later by eeloadHook2()
|
||
for (g_osdsys_str = EELOAD_START; g_osdsys_str < EELOAD_START + EELOAD_SIZE; g_osdsys_str += 8) // strings are 64-bit aligned
|
||
{
|
||
if (!strcmp((char*)PSM(g_osdsys_str), "rom0:OSDSYS"))
|
||
{
|
||
// Overwrite OSDSYS with game's ELF name
|
||
strcpy((char*)PSM(g_osdsys_str), elfname.c_str());
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Stop fast forwarding if we're doing that for boot.
|
||
VMManager::Internal::DisableFastBoot();
|
||
AllowParams1 = true;
|
||
AllowParams2 = true;
|
||
}
|
||
}
|
||
|
||
VMManager::Internal::ELFLoadingOnCPUThread(std::move(elfname));
|
||
|
||
if (CHECK_EXTRAMEM)
|
||
{
|
||
// Map extra memory.
|
||
vtlb_VMap(Ps2MemSize::MainRam, Ps2MemSize::MainRam, Ps2MemSize::ExtraRam);
|
||
|
||
// Map RAM mirrors for extra memory.
|
||
vtlb_VMap(0x20000000 | Ps2MemSize::MainRam, Ps2MemSize::MainRam, Ps2MemSize::ExtraRam);
|
||
vtlb_VMap(0x30000000 | Ps2MemSize::MainRam, Ps2MemSize::MainRam, Ps2MemSize::ExtraRam);
|
||
}
|
||
}
|
||
|
||
// Called from recompilers; define is mandatory.
|
||
// Only called if g_SkipBiosHack is true
|
||
void eeloadHook2()
|
||
{
|
||
if (EmuConfig.CurrentGameArgs.empty())
|
||
return;
|
||
|
||
if (!g_osdsys_str)
|
||
{
|
||
Console.WriteLn("eeloadHook2: Called before \"rom0:OSDSYS\" was found by eeloadHook()!");
|
||
return;
|
||
}
|
||
|
||
const char *argString = EmuConfig.CurrentGameArgs.c_str();
|
||
Console.WriteLn("eeloadHook2: Supplying launch argument(s) '%s' to ELF '%s'.", argString, (char *)PSM(g_osdsys_str));
|
||
|
||
// Add args string after game's ELF name that was written over "rom0:OSDSYS" by eeloadHook(). In between the ELF name and args
|
||
// string we insert a space character so that ParseArgumentString() has one continuous string to process.
|
||
int game_len = strlen((char *)PSM(g_osdsys_str));
|
||
memset(PSM(g_osdsys_str + game_len), 0x20, 1);
|
||
strcpy((char *)PSM(g_osdsys_str + game_len + 1), EmuConfig.CurrentGameArgs.c_str());
|
||
#if DEBUG_LAUNCHARG
|
||
Console.WriteLn("eeloadHook2: arg block is '%s'.", (char *)PSM(g_osdsys_str));
|
||
#endif
|
||
int argc = ParseArgumentString(g_osdsys_str);
|
||
|
||
// Back up 4 bytes from start of args block for every arg + 4 bytes for start of argv pointer block, write pointers
|
||
uptr block_start = g_osdsys_str - (argc * 4);
|
||
for (int a = 0; a < argc; a++)
|
||
{
|
||
#if DEBUG_LAUNCHARG
|
||
Console.WriteLn("eeloadHook2: Writing address %p to location %p.", g_argPtrs[a], block_start + (a * 4));
|
||
#endif
|
||
memWrite32(block_start + (a * 4), g_argPtrs[a]);
|
||
}
|
||
|
||
// Save argc and argv as incoming arguments for EELOAD function which calls ExecPS2()
|
||
#if DEBUG_LAUNCHARG
|
||
Console.WriteLn("eeloadHook2: Saving %d and %p in $a0 and $a1.", argc, block_start);
|
||
#endif
|
||
cpuRegs.GPR.n.a0.SD[0] = argc;
|
||
cpuRegs.GPR.n.a1.UD[0] = block_start;
|
||
}
|
||
|
||
inline bool isBranchOrJump(u32 addr)
|
||
{
|
||
u32 op = memRead32(addr);
|
||
const OPCODE& opcode = GetInstruction(op);
|
||
|
||
// Return false for eret & syscall as they are branch type in pcsx2 debugging tools,
|
||
// but shouldn't have delay slot in isBreakpointNeeded/isMemcheckNeeded.
|
||
if ((opcode.flags == (IS_BRANCH | BRANCHTYPE_SYSCALL)) || (opcode.flags == (IS_BRANCH | BRANCHTYPE_ERET)))
|
||
return false;
|
||
|
||
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 isBreakpointNeeded(u32 addr)
|
||
{
|
||
int bpFlags = 0;
|
||
if (CBreakPoints::IsAddressBreakPoint(BREAKPOINT_EE, addr))
|
||
bpFlags += 1;
|
||
|
||
// there may be a breakpoint in the delay slot
|
||
if (isBranchOrJump(addr) && CBreakPoints::IsAddressBreakPoint(BREAKPOINT_EE, addr+4))
|
||
bpFlags += 2;
|
||
|
||
return bpFlags;
|
||
}
|
||
|
||
int isMemcheckNeeded(u32 pc)
|
||
{
|
||
if (CBreakPoints::GetNumMemchecks() == 0)
|
||
return 0;
|
||
|
||
u32 addr = pc;
|
||
if (isBranchOrJump(addr))
|
||
addr += 4;
|
||
|
||
u32 op = memRead32(addr);
|
||
const OPCODE& opcode = GetInstruction(op);
|
||
|
||
if (opcode.flags & IS_MEMORY)
|
||
return addr == pc ? 1 : 2;
|
||
|
||
return 0;
|
||
}
|