/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2010 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "PrecompiledHeader.h" #include "Common.h" #include "R5900.h" #include "R3000A.h" #include "VUmicro.h" #include "COP0.h" #include "MTVU.h" #include "System/SysThreads.h" #include "R5900Exceptions.h" #include "Hardware.h" #include "IPU/IPUdma.h" #include "Elfheader.h" #include "CDVD/CDVD.h" #include "Patch.h" #include "GameDatabase.h" #include "../DebugTools/Breakpoints.h" #include "R5900OpcodeTables.h" using namespace R5900; // for R5900 disasm tools s32 EEsCycle; // used to sync the IOP to the EE u32 EEoCycle; __aligned16 cpuRegisters cpuRegs; __aligned16 fpuRegisters fpuRegs; __aligned16 tlbs tlb[48]; R5900cpu *Cpu = NULL; bool g_SkipBiosHack; // set at boot if the skip bios hack is on, reset before the game has started bool g_GameStarted; // set when we reach the game's entry point or earlier if the entry point cannot be determined bool g_GameLoading; // EELOAD has been called to load the game static const uint eeWaitCycles = 3072; bool eeEventTestIsActive = false; u32 eeloadMain = 0; extern SysMainMemory& GetVmMemory(); void cpuReset() { vu1Thread.WaitVU(); if (GetMTGS().IsOpen()) GetMTGS().WaitGS(); // GS better be done processing before we reset the EE, just in case. GetVmMemory().ResetAll(); memzero(cpuRegs); memzero(fpuRegs); memzero(tlb); 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 g_nextEventCycle = cpuRegs.cycle + 4; EEsCycle = 0; EEoCycle = cpuRegs.cycle; hwReset(); rcntInit(); psxReset(); extern void Deci2Reset(); // lazy, no good header for it yet. Deci2Reset(); g_GameStarted = false; g_GameLoading = false; g_SkipBiosHack = EmuConfig.UseBOOT2Injection; ElfCRC = 0; DiscSerial = L""; ElfEntry = -1; // Probably not the right place, but it has to be done when the ram is actually initialized if(USBsetRAM != 0) USBsetRAM(iopMem->Main); // FIXME: LastELF should be reset on media changes as well as on CPU resets, in // the very unlikely case that a user swaps to another media source that "looks" // the same (identical ELF names) but is actually different (devs actually could // run into this while testing minor binary hacked changes to ISO images, which // is why I found out about this) --air LastELF = L""; eeloadMain = 0; } void cpuShutdown() { hwShutdown(); } __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:%x, 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( 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)(g_nextEventCycle - startCycle) > delta ) { g_nextEventCycle = startCycle + delta; } } // sets a branch to occur some time from the current cycle __fi void cpuSetNextEventDelta( s32 delta ) { cpuSetNextEvent( cpuRegs.cycle, delta ); } // tests the cpu cycle against the given start and delta values. // Returns true if the delta time has passed. __fi int cpuTestCycle( 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)(cpuRegs.cycle - startCycle) >= delta; } // tells the EE to run the branch test the next time it gets a chance. __fi void cpuSetEvent() { g_nextEventCycle = cpuRegs.cycle; } __fi void cpuClearInt( uint i ) { pxAssume( i < 32 ); cpuRegs.interrupt &= ~(1 << i); } static __fi void TESTINT( u8 n, void (*callback)() ) { if( !(cpuRegs.interrupt & (1 << n)) ) return; if( cpuTestCycle( cpuRegs.sCycle[n], cpuRegs.eCycle[n] ) ) { cpuClearInt( n ); callback(); } else cpuSetNextEvent( cpuRegs.sCycle[n], cpuRegs.eCycle[n] ); } // [TODO] move this function to LegacyDmac.cpp, and remove most of the DMAC-related headers from // being included into R5900.cpp. static __fi void _cpuTestInterrupts() { if (!dmacRegs.ctrl.DMAE || (psHu8(DMAC_ENABLER+2) & 1)) { //Console.Write("DMAC Disabled or suspended"); return; } /* These are 'pcsx2 interrupts', they handle asynchronous stuff that depends on the cycle timings */ TESTINT(DMAC_VIF1, vif1Interrupt); TESTINT(DMAC_GIF, gifInterrupt); 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 & 0x60F19 ) // Bits 0 3 4 8 9 10 11 17 18( 1100000111100011001 ) { TESTINT(DMAC_VIF0, vif0Interrupt); TESTINT(DMAC_FROM_IPU, ipu0Interrupt); TESTINT(DMAC_TO_IPU, ipu1Interrupt); TESTINT(DMAC_FROM_SPR, SPRFROMinterrupt); TESTINT(DMAC_TO_SPR, SPRTOinterrupt); TESTINT(DMAC_MFIFO_VIF, vifMFIFOInterrupt); TESTINT(DMAC_MFIFO_GIF, gifMFIFOInterrupt); TESTINT(VIF_VU0_FINISH, vif0VUFinish); TESTINT(VIF_VU1_FINISH, vif1VUFinish); } } static __fi void _cpuTestTIMR() { cpuRegs.CP0.n.Count += cpuRegs.cycle-s_iLastCOP0Cycle; s_iLastCOP0Cycle = 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); } // if cpuRegs.cycle is greater than this cycle, should check cpuEventTest for updates u32 g_nextEventCycle = 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() { ScopedBool etest(eeEventTestIsActive); g_nextEventCycle = cpuRegs.cycle + eeWaitCycles; // ---- 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); // ---- Counters ------------- // Important: the vsync counter must be the first to be checked. It includes emulation // escape/suspend hooks, and it's really a good idea to suspend/resume emulation before // doing any actual meaningful branchtest logic. if( cpuTestCycle( nextsCounter, nextCounter ) ) { rcntUpdate(); _cpuTestPERF(); } rcntUpdate_hScanline(); _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. _cpuTestInterrupts(); // ---- 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. EEsCycle += cpuRegs.cycle - EEoCycle; EEoCycle = cpuRegs.cycle; if( EEsCycle > 0 ) iopEventAction = true; iopEventTest(); if( iopEventAction ) { //if( EEsCycle < -450 ) // Console.WriteLn( " IOP ahead by: %d cycles", -EEsCycle ); EEsCycle = psxCpu->ExecuteBlock( EEsCycle ); iopEventAction = false; } // ---- VU0 ------------- // We're in a EventTest. All dynarec registers are flushed // so there is no need to freeze registers here. CpuVU0->ExecuteBlock(); // Note: We don't update the VU1 here because it runs it's micro-programs in // one shot always. That is, when a program is executed the VU1 doesn't even // bother to return until the program is completely finished. // ---- Schedule Next Event Test -------------- if( EEsCycle > 192 ) { // 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 ); } // The IOP could be running ahead/behind of us, so adjust the iop's next branch by its // relative position to the EE (via EEsCycle) cpuSetNextEventDelta( ((g_iopNextEventCycle-psxRegs.cycle)*8) - EEsCycle ); // Apply the hsync counter's nextCycle cpuSetNextEvent( hsyncCounter.sCycle, hsyncCounter.CycleT ); // Apply vsync and other counter nextCycles cpuSetNextEvent( nextsCounter, nextCounter ); } __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 && (iopCycleEE > 0)) { iopBreak += iopCycleEE; // record the number of cycles the IOP didn't run. 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 && (iopCycleEE > 0)) { iopBreak += iopCycleEE; // record the number of cycles the IOP didn't run. iopCycleEE = 0; } } __fi void cpuTestTIMRInts() { if ((cpuRegs.CP0.n.Status.val & 0x10007) == 0x10001) { _cpuTestPERF(); _cpuTestTIMR(); } } __fi void cpuTestHwInts() { cpuTestINTCInts(); cpuTestDMACInts(); cpuTestTIMRInts(); } __fi void CPU_INT( EE_EventType n, s32 ecycle) { // 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) 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 && iopCycleEE > 0 ) { // If running in the IOP, force it to break immediately into the EE. // the EE's branch test is due to run. iopBreak += iopCycleEE; // record the number of cycles the IOP didn't run. iopCycleEE = 0; } cpuSetNextEventDelta( cpuRegs.eCycle[n] ); } // Called from recompilers; __fastcall define is mandatory. void __fastcall eeGameStarting() { if (!g_GameStarted) { //Console.WriteLn( Color_Green, "(R5900) ELF Entry point! [addr=0x%08X]", ElfEntry ); g_GameStarted = true; g_GameLoading = false; GetCoreThread().GameStartingInThread(); // GameStartingInThread may issue a reset of the cpu and/or recompilers. Check for and // handle such things here: Cpu->CheckExecutionState(); } else { Console.WriteLn( Color_Green, "(R5900) Re-executed ELF Entry point (ignored) [addr=0x%08X]", ElfEntry ); } } // Called from recompilers; __fastcall define is mandatory. void __fastcall eeloadHook() { const wxString &elf_override = GetCoreThread().GetElfOverride(); if (!elf_override.IsEmpty()) cdvdReloadElfInfo(L"host:" + elf_override); else cdvdReloadElfInfo(); wxString discelf; int disctype = GetPS2ElfName(discelf); std::string elfname; if (cpuRegs.GPR.n.a0.SD[0] >= 2) // argc >= 2 elfname = (char*)PSM(memRead32(cpuRegs.GPR.n.a1.UD[0] + 4)); // argv[1] if (g_SkipBiosHack && elfname.empty()) { std::string elftoload; if (!elf_override.IsEmpty()) { elftoload = "host:"; elftoload += elf_override.ToUTF8(); } else { if (disctype == 2) elftoload = discelf.ToUTF8(); } if (!elftoload.empty()) { #if 0 // FIXME: you'd think that changing argc and argv would work but no, need to work out why not // This method would support adding command line arguments to homebrew (and is generally less hacky) // It works if you hook on rom0:PS2LOGO loading, but not on the first call with no arguments cpuRegs.GPR.n.a0.SD[0] = 2; // argc = 2 // argv[0] = "EELOAD" strcpy((char*)PSM(cpuRegs.GPR.n.a1.UD[0] + 0x40), "EELOAD"); memWrite32(cpuRegs.GPR.n.a1.UD[0] + 0, cpuRegs.GPR.n.a1.UD[0] + 0x40); // argv[1] = elftoload strcpy((char*)PSM(cpuRegs.GPR.n.a1.UD[0] + 0x47), elftoload.c_str()); memWrite32(cpuRegs.GPR.n.a1.UD[0] + 4, cpuRegs.GPR.n.a1.UD[0] + 0x47); memWrite32(cpuRegs.GPR.n.a1.UD[0] + 8, 0); g_GameLoading = true; return; #else // The strings are all 64-bit aligned. Why? I don't know, but they are for (u32 osdsys_str = EELOAD_START; osdsys_str < EELOAD_START + EELOAD_SIZE; osdsys_str += 8) { if (!strcmp((char*)PSM(osdsys_str), "rom0:OSDSYS")) { for (u32 osdsys_ptr = osdsys_str - 4; osdsys_ptr >= EELOAD_START; osdsys_ptr -= 4) { if (memRead32(osdsys_ptr) == osdsys_str) { strcpy((char*)PSM(cpuRegs.GPR.n.a1.UD[0] + 0x40), elftoload.c_str()); memWrite32(osdsys_ptr, cpuRegs.GPR.n.a1.UD[0] + 0x40); g_GameLoading = true; return; } } } } #endif } } if (!g_GameStarted && disctype == 2 && elfname == discelf) g_GameLoading = true; } inline bool isBranchOrJump(u32 addr) { u32 op = memRead32(addr); const OPCODE& opcode = 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 isBreakpointNeeded(u32 addr) { int bpFlags = 0; if (CBreakPoints::IsAddressBreakPoint(addr)) bpFlags += 1; // there may be a breakpoint in the delay slot if (isBranchOrJump(addr) && CBreakPoints::IsAddressBreakPoint(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; }