diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index e925f90329..2770aee2dd 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -578,6 +578,8 @@ add_library(Core STATIC HW/Display.h HW/GpioMMIO.cpp HW/GpioMMIO.h + HW/MeMMIO.cpp + HW/MeMMIO.h HW/MMIO.cpp HW/MMIO.h HW/AvcDecoder.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index c4d4d0734c..2348b5a217 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -494,6 +494,7 @@ + @@ -867,6 +868,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index f82b5b8d60..70a1be4ee9 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1177,6 +1177,9 @@ HW + + HW + HLE\Libraries @@ -2184,6 +2187,9 @@ HW + + HW + HLE\Libraries diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index f1db1aad15..7b05326d93 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -38,6 +38,7 @@ #include "Core/HLE/ReplaceTables.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceDisplay.h" +#include "Core/HW/MMIO.h" #include "GPU/Math3D.h" #include "GPU/GPU.h" @@ -119,6 +120,33 @@ static int Replace_ceilf(MIPSState *mips) { // Should probably do JIT versions of this, possibly ones that only delegate // large copies to a C function. +// Kernel code copies Media Engine code to the boot page at 0xBFC00000, which the main CPU reaches +// through MMIO. Byte by byte, forward like memmove when the destination is lower. Returns false if +// neither side is MMIO. +static bool CopyThroughMMIO(MIPSState *mips, u32 destPtr, u32 srcPtr, u32 bytes) { + if (!Memory::IsMMIOAccess(destPtr) && !Memory::IsMMIOAccess(srcPtr)) { + return false; + } + const bool backward = destPtr > srcPtr && destPtr < srcPtr + bytes; + for (u32 i = 0; i < bytes; ++i) { + const u32 offset = backward ? bytes - 1 - i : i; + const u32 src = srcPtr + offset; + const u32 dest = destPtr + offset; + u8 value = 0; + if (Memory::IsMMIOAccess(src)) { + value = (u8)MMIO::Read(mips, src, 1); + } else if (Memory::IsValidAddress(src)) { + value = Memory::ReadUnchecked_U8(src); + } + if (Memory::IsMMIOAccess(dest)) { + MMIO::Write(mips, dest, 1, value); + } else if (Memory::IsValidAddress(dest)) { + Memory::WriteUnchecked_U8(value, dest); + } + } + return true; +} + static int Replace_memcpy(MIPSState *mips) { u32 destPtr = PARAM(0); u32 srcPtr = PARAM(1); @@ -135,7 +163,7 @@ static int Replace_memcpy(MIPSState *mips) { } } gpu->NotifyVideoCopy(destPtr, srcPtr, bytes); - if (!skip && bytes != 0) { + if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) { u8 *dst = Memory::GetPointerWriteRangeOrException(destPtr, bytes); const u8 *src = Memory::GetPointerRangeOrException(srcPtr, bytes); @@ -265,7 +293,7 @@ static int Replace_memcpy16(MIPSState *mips) { } } gpu->NotifyVideoCopy(destPtr, srcPtr, bytes); - if (!skip && bytes != 0) { + if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) { u8 *dst = Memory::GetPointerWriteRangeOrException(destPtr, bytes); const u8 *src = Memory::GetPointerRangeOrException(srcPtr, bytes); if (dst && src) { @@ -332,7 +360,7 @@ static int Replace_memmove(MIPSState *mips) { } } gpu->NotifyVideoCopy(destPtr, srcPtr, bytes); - if (!skip && bytes != 0) { + if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) { u8 *dst = Memory::GetPointerWriteRangeOrException(destPtr, bytes); const u8 *src = Memory::GetPointerRangeOrException(srcPtr, bytes); if (dst && src) { diff --git a/Core/HLE/sceKernelInterrupt.cpp b/Core/HLE/sceKernelInterrupt.cpp index 66f60ba855..2d85e89970 100644 --- a/Core/HLE/sceKernelInterrupt.cpp +++ b/Core/HLE/sceKernelInterrupt.cpp @@ -30,6 +30,7 @@ #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/ErrorCodes.h" #include "Core/MIPS/MIPS.h" +#include "Core/MIPS/MeCore.h" #include "Core/Debugger/MemBlockInfo.h" #include "Core/HLE/sceKernel.h" @@ -132,6 +133,19 @@ static void sceKernelCpuResumeIntrWithSync(u32 enable) bool IntrHandler::run(PendingInterrupt& pend) { + if (pend.subintr == PSP_INTR_SUB_NONE) { + if (!baseEnabled_ || baseHandler_ == 0 || baseHandler_ == FIRMWARE_HANDLER) { + return false; + } + // As interruptman calls it (6.61): the interrupt, the arg, the interrupted pc. + DEBUG_LOG(Log::CPU, "Entering base interrupt handler %08x", baseHandler_); + currentMIPS->pc = baseHandler_; + currentMIPS->r[MIPS_REG_A0] = intrNumber; + currentMIPS->r[MIPS_REG_A1] = baseArg_; + currentMIPS->r[MIPS_REG_A2] = intState.savedCpu.pc; + return true; + } + SubIntrHandler *handler = get(pend.subintr); if (!handler) { WARN_LOG(Log::sceIntc, "Ignoring interrupt, already been released."); @@ -155,7 +169,16 @@ void IntrHandler::copyArgsToCPU(PendingInterrupt& pend) void IntrHandler::handleResult(PendingInterrupt& pend) { - //u32 result = currentMIPS->r[MIPS_REG_V0]; + if (pend.subintr != PSP_INTR_SUB_NONE) + return; + // From interruptman (6.61): -1 is done, -2 disables the interrupt, and n >= 0 runs the handler's + // own sub-interrupt n, which nothing here registers. + const int result = (int)currentMIPS->r[MIPS_REG_V0]; + if (result == -2) { + enableBase(false); + } else if (result >= 0) { + WARN_LOG(Log::sceIntc, "Base handler for interrupt %d asked for sub-interrupt %d", intrNumber, result); + } } SubIntrHandler* IntrHandler::add(int subIntrNum) @@ -248,6 +271,11 @@ void __InterruptsInit() inInterrupt = false; for (int i = 0; i < (int)ARRAY_SIZE(intrHandlers); ++i) intrHandlers[i] = new IntrHandler(i); + if (MeCore::g_meState) { + // me_wrapper's, in the firmware. + intrHandlers[PSP_MECODEC_INTR]->setBaseHandler(IntrHandler::FIRMWARE_HANDLER, 0); + intrHandlers[PSP_MECODEC_INTR]->enableBase(true); + } intState.clear(); threadBeforeInterrupt = 0; } diff --git a/Core/HLE/sceKernelInterrupt.h b/Core/HLE/sceKernelInterrupt.h index 02c2c407be..cbecadff4d 100644 --- a/Core/HLE/sceKernelInterrupt.h +++ b/Core/HLE/sceKernelInterrupt.h @@ -130,8 +130,8 @@ public: SubIntrHandler *get(int subIntrNum); void clear(); - // The handler a kernel driver registered for the whole interrupt (sceKernelRegisterIntrHandler). - // Nothing raises those interrupts yet, so it's only kept. + // The handler a kernel driver registered for the whole interrupt (sceKernelRegisterIntrHandler), + // raised with PSP_INTR_SUB_NONE. Only the Media Engine raises one so far. bool hasBaseHandler() const { return baseHandler_ != 0; } void setBaseHandler(u32 handler, u32 arg) { baseHandler_ = handler; @@ -139,6 +139,8 @@ public: baseEnabled_ = false; } void enableBase(bool enable) { baseEnabled_ = enable; } + // Stands in for a firmware driver's handler, which homebrew releases to register its own. + static constexpr u32 FIRMWARE_HANDLER = 1; void DoState(PointerWrap &p); diff --git a/Core/HW/MMIO.cpp b/Core/HW/MMIO.cpp index ff263ce32f..e6ba500927 100644 --- a/Core/HW/MMIO.cpp +++ b/Core/HW/MMIO.cpp @@ -23,6 +23,7 @@ #include "Common/Serialize/SerializeFuncs.h" #include "Core/HW/GpioMMIO.h" #include "Core/HW/MMIO.h" +#include "Core/HW/MeMMIO.h" #include "Core/MemMap.h" #include "Core/MIPS/MIPS.h" @@ -93,6 +94,9 @@ void Init() { GpioMMIO::Device(), SysconSerialMMIO::Device(), &g_bootPage, + MeMMIO::SysregDevice(), + MeMMIO::InterruptDevice(), + MeMMIO::MemProtectDevice(), }; for (MMIODevice *device : g_devices) { device->Reset(); diff --git a/Core/HW/MeMMIO.cpp b/Core/HW/MeMMIO.cpp new file mode 100644 index 0000000000..ff41fe6f87 --- /dev/null +++ b/Core/HW/MeMMIO.cpp @@ -0,0 +1,181 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Common/Log.h" +#include "Core/HW/MMIO.h" +#include "Core/HW/MeMMIO.h" +#include "Core/MemMap.h" +#include "Core/MIPS/MeCore.h" +#include "Core/MIPS/MIPS.h" + +namespace MeMMIO { + +using MeCore::g_meState; + +static bool IsME(const MIPSState *mips) { + return mips && mips->IsMediaEngine(); +} + +class MeSysregDevice : public MMIODevice { +public: + MeSysregDevice() : MMIODevice("ME sysreg", 0xBC100000, 0x100) {} + + bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override { + u32 *reg = Register(offset); + if (!reg) { + return false; + } + *value = *reg; + return true; + } + + bool Write(MIPSState *mips, u32 offset, int size, u32 value) override { + if (!g_meState) { + return false; + } + switch (offset) { + case 0x44: + // Interrupts the other CPU. + if (value & 1) { + if (IsME(mips)) { + MeCore::InterruptMainCPU(); + } else { + MeCore::RaiseInterrupt(MeCore::ME_INTR_SOFT); + } + } + return true; + case 0x48: + { + // Each CPU sets or clears its own bit, and can only set it while the other's is clear. + const u32 own = IsME(mips) ? 2 : 1; + const u32 other = own ^ 3; + if (!(value & own)) { + g_meState->mutex &= ~own; + } else if (!(g_meState->mutex & other)) { + g_meState->mutex |= own; + } + return true; + } + case 0x4C: + g_meState->resetFlags = value; + MeCore::SetReset((value & 4) != 0); + return true; + default: + if (u32 *reg = Register(offset)) { + *reg = value; + return true; + } + return false; + } + } + + void Reset() override {} + void DoState(PointerWrap &p) override {} + +private: + static u32 *Register(u32 offset) { + if (!g_meState) { + return nullptr; + } + switch (offset) { + case 0x04: return &g_meState->nmiFlags; + case 0x40: return &g_meState->ramConfig; + case 0x48: return &g_meState->mutex; + case 0x4C: return &g_meState->resetFlags; + case 0x50: return &g_meState->busClock; + default: return nullptr; + } + } +}; + +class MeInterruptDevice : public MMIODevice { +public: + MeInterruptDevice() : MMIODevice("ME interrupts", 0xBC300000, 0x10) {} + + bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override { + if (!g_meState || !IsME(mips)) { + return false; + } + switch (offset) { + case 0x0: *value = g_meState->intcRaised; return true; + case 0x8: *value = g_meState->intcEnabled; return true; + default: return false; + } + } + + bool Write(MIPSState *mips, u32 offset, int size, u32 value) override { + if (!g_meState || !IsME(mips)) { + return false; + } + switch (offset) { + case 0x0: + // Writing a source's bit clears it. + g_meState->intcRaised &= ~value; + return true; + case 0x8: + g_meState->intcEnabled = value; + return true; + default: + return false; + } + } + + void Reset() override {} + void DoState(PointerWrap &p) override {} +}; + +class MeMemProtectDevice : public MMIODevice { +public: + MeMemProtectDevice() : MMIODevice("ME memory protection", 0xBC000000, 0x50) {} + + bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override { + if (!g_meState || (offset & 3)) { + return false; + } + *value = g_meState->memProtect[offset / 4]; + return true; + } + + bool Write(MIPSState *mips, u32 offset, int size, u32 value) override { + if (!g_meState || (offset & 3)) { + return false; + } + g_meState->memProtect[offset / 4] = value; + return true; + } + + void Reset() override {} + void DoState(PointerWrap &p) override {} +}; + +static MeSysregDevice g_sysreg; +static MeInterruptDevice g_interrupts; +static MeMemProtectDevice g_memProtect; + +MMIODevice *SysregDevice() { + return &g_sysreg; +} + +MMIODevice *InterruptDevice() { + return &g_interrupts; +} + +MMIODevice *MemProtectDevice() { + return &g_memProtect; +} + +} // namespace MeMMIO diff --git a/Core/HW/MeMMIO.h b/Core/HW/MeMMIO.h new file mode 100644 index 0000000000..3f837fedad --- /dev/null +++ b/Core/HW/MeMMIO.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +// The registers homebrew's Media Engine code uses to run the ME and talk to it. Their state is the +// ME's (MeCore::MEState); without an ME they're left unhandled, as before. + +#pragma once + +class MMIODevice; + +namespace MeMMIO { + +// Reset, bus clock, soft interrupt and hardware mutex (0xBC100000). +MMIODevice *SysregDevice(); +// The ME's interrupt controller (0xBC300000). The main CPU has its own there. +MMIODevice *InterruptDevice(); +// Memory protection (0xBC000000), which the ME's startup code unlocks. +MMIODevice *MemProtectDevice(); + +} // namespace MeMMIO diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index 7cb3e1ecbb..3b153b7e9f 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -313,7 +313,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &m js.hadBreakpoints = false; js.blockWrotePrefixes = false; js.inDelaySlot = false; - js.kernelMode = Memory::IsKernelCodeAddress(em_address); + js.kernelMode = Memory::MayRunInKernelMode(em_address); js.PrefixStart(); ir.Clear(); diff --git a/Core/MIPS/Interpreter.cpp b/Core/MIPS/Interpreter.cpp index 6535a02d53..c2411a87a0 100644 --- a/Core/MIPS/Interpreter.cpp +++ b/Core/MIPS/Interpreter.cpp @@ -104,7 +104,7 @@ int MIPS_InterpretSingleStep(MIPSState *mips) { // Hardware registers, for kernel code only. static u32 ReadMMIO(MIPSState *mips, u32 addr, int size) { // The Media Engine always runs in kernel mode. - if (!mips->IsMediaEngine() && !Memory::IsKernelCodeAddress(mips->pc)) { + if (!mips->IsMediaEngine() && !Memory::MayRunInKernelMode(mips->pc)) { Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only"); return MMIO::UNHANDLED_READ; } @@ -112,7 +112,7 @@ static u32 ReadMMIO(MIPSState *mips, u32 addr, int size) { } static void WriteMMIO(MIPSState *mips, u32 addr, int size, u32 value) { - if (!mips->IsMediaEngine() && !Memory::IsKernelCodeAddress(mips->pc)) { + if (!mips->IsMediaEngine() && !Memory::MayRunInKernelMode(mips->pc)) { Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::WRITE_WORD, "Kernel mode only"); return; } @@ -757,8 +757,10 @@ namespace MIPSInt { // calibration code of a nonzero seed/divisor. Tie it to CoreTiming instead; // still not real COP0 semantics (no Compare-triggered interrupt), just a // closer approximation. See docs/VSHBootInvestigation.md "Attempt 9". - if (rd == 9) - R(rt) = (u32)(mips->IsMediaEngine() ? MeCore::GetTicks() : CoreTiming::GetTicks(mips)); + if (mips->IsMediaEngine()) + R(rt) = MeCore::ReadCop0(rd); + else if (rd == 9) + R(rt) = (u32)CoreTiming::GetTicks(mips); else R(rt) = mips->cop0[rd]; } @@ -774,7 +776,10 @@ namespace MIPSInt { break; case 4: //mtc0 - mips->cop0[rd] = R(rt); + if (mips->IsMediaEngine()) + MeCore::WriteCop0(rd, R(rt)); + else + mips->cop0[rd] = R(rt); break; case 10: //rdpgpr @@ -1040,6 +1045,16 @@ namespace MIPSInt { PC += 4; } + // Only the Media Engine runs code that halts. It sleeps until an interrupt. + void Int_Halt(MIPSState *mips, MIPSOpcode op) { + if (!mips->IsMediaEngine()) { + Core_ExecException(PC, PC, ExecExceptionType::ILLEGAL); + return; + } + PC += 4; + MeCore::Halt(); + } + // The interrupt enable flag, shared with the HLE sceKernelCpuSuspendIntr/ResumeIntr, which on // hardware are just mfic v0, $0; mtic zero, $0 and mtic a0, $0. Only $0 exists. void Int_Special2(MIPSState *mips, MIPSOpcode op) { diff --git a/Core/MIPS/Interpreter.h b/Core/MIPS/Interpreter.h index c690b8b1bc..3ff319e854 100644 --- a/Core/MIPS/Interpreter.h +++ b/Core/MIPS/Interpreter.h @@ -48,6 +48,7 @@ namespace MIPSInt void Int_FPUBranch(MIPSState *mips, MIPSOpcode op); void Int_CallReplacement(MIPSState *mips, MIPSOpcode op); void Int_Special2(MIPSState *mips, MIPSOpcode op); + void Int_Halt(MIPSState *mips, MIPSOpcode op); void Int_Special3(MIPSState *mips, MIPSOpcode op); void Int_Interrupt(MIPSState *mips, MIPSOpcode op); void Int_Cache(MIPSState *mips, MIPSOpcode op); diff --git a/Core/MIPS/InterpreterDispatch.cpp b/Core/MIPS/InterpreterDispatch.cpp index ac87dcb217..11526c1a8e 100644 --- a/Core/MIPS/InterpreterDispatch.cpp +++ b/Core/MIPS/InterpreterDispatch.cpp @@ -391,6 +391,10 @@ int ExecInstruction(MIPSState *mips, MIPSOpcode op) { case 28: { switch ((op.encoding >> 0) & 0x3f) { + // halt + case 0: + MIPSInt::Int_Halt(mips, op); + return 1; // mfic, mtic case 36: case 38: diff --git a/Core/MIPS/MIPSTables.cpp b/Core/MIPS/MIPSTables.cpp index cc4f4f074c..223a1472ce 100644 --- a/Core/MIPS/MIPSTables.cpp +++ b/Core/MIPS/MIPSTables.cpp @@ -257,7 +257,7 @@ static const MIPSInstruction tableSpecial[64] = // 000000 ..... ..... ..... .... // Theoretically should not hit these. static const MIPSInstruction tableSpecial2[64] = // 011100 ..... ..... ..... ..... xxxxxx { - INSTR("halt", JITFUNC(Comp_Generic), Dis_Generic, 0, ENDS_BLOCK), + INSTR("halt", JITFUNC(Comp_Generic), Dis_Generic, Int_Halt, ENDS_BLOCK), INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, //8 INVALID_X_8, diff --git a/Core/MIPS/MeCore.cpp b/Core/MIPS/MeCore.cpp index 31142a2149..0c04be2911 100644 --- a/Core/MIPS/MeCore.cpp +++ b/Core/MIPS/MeCore.cpp @@ -15,36 +15,86 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include + #include "Common/Log.h" #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Core/Config.h" +#include "Core/Core.h" #include "Core/CoreTiming.h" +#include "Core/HLE/sceKernelInterrupt.h" #include "Core/MemMap.h" #include "Core/MIPS/MeCore.h" #include "Core/MIPS/MIPSTables.h" namespace MeCore { -// COP0 register 22, which the reset vector tests to send the ME on to 0x40 (the main CPU's reads 0). -// Only known to be nonzero. +// The ME's state after a reset, read on a PSP-1000 (a kernel-mode probe, not kept as a test: the +// values left over from Sony's ME code aren't deterministic): COP0 register 22 is 1, which sends it +// on to 0x40 from the reset vector; Status 0x20500404 (CU1, BEV, SR, IM2, ERL); the GPRs keep +// whatever was in them, sp 0x801FFFE0 from Sony's code (top of the 2MB eDRAM); mfic 0; Count 0, and +// it doesn't run, with ERL set or Status cleared. +// Sysreg 0x40 read 0x20000002 and 0x50 0xD61D. static const u32 ME_CPU_ID = 1; +static const u32 RESET_STATUS = 0x20500404; +static const u32 STATUS_RESET_BITS = 0x00500004; + +// Pending interrupts are checked between runs of this many cycles at most. +static const int INTERRUPT_CHECK_CYCLES = 256; + +enum : u32 { + STATUS_IE = 0x01, + STATUS_EXL = 0x02, + STATUS_ERL = 0x04, + CAUSE_IP2 = 0x0400, // The interrupt controller. + CAUSE_IP7 = 0x8000, // Count reached Compare. Never on its own, since Count doesn't run. + CAUSE_IP_MASK = 0xFF00, + CAUSE_SOFT_IP = 0x0300, + CAUSE_EXCCODE_BD = 0x8000007C, +}; MEState *g_meState = nullptr; +static int meIntrEvent = -1; // Booting the VSH counts as homebrew: vshmain.prx loads as an ELF. bool Wanted(bool homebrew) { return homebrew && g_Config.bEmulateMediaEngine; } -// To the state reset leaves it in, with its clock running on. +static void MeIntrCallback(u64 userdata, int cyclesLate) { + __TriggerInterrupt(PSP_INTR_IMMEDIATE, PSP_MECODEC_INTR, PSP_INTR_SUB_NONE); +} + +// Reset keeps the GPRs, and homebrew relies on that: mcidclan's samples run C on the stack Sony's +// code left in sp. static void ResetCPU(MEState *me) { - const u64 ticks = GetTicks(); - me->cpu.InitRegisters(); - me->cpu.cop0[22] = ME_CPU_ID; - me->cpu.pc = Memory::BOOT_PAGE_ADDRESS; - me->tickBase = ticks; + MIPSState &cpu = me->cpu; + cpu.cop0[30] = cpu.pc; // ErrorEPC + cpu.cop0[12] |= STATUS_RESET_BITS; + cpu.pc = Memory::BOOT_PAGE_ADDRESS; + cpu.nextPC = 0; + cpu.inDelaySlot = false; + cpu.llBit = 0; + cpu.cop0[9] = 0; + me->halted = false; me->interruptEnable = 0; + me->intcRaised = 0; + me->intcEnabled = 0; +} + +// What Sony's ME code leaves, which homebrew finds there. +static void PowerOn(MEState *me) { + MIPSState &cpu = me->cpu; + cpu.InitRegisters(); + cpu.cop0[12] = RESET_STATUS; + cpu.cop0[22] = ME_CPU_ID; + cpu.r[MIPS_REG_SP] = 0x80000000 + cpu.space->edramSize - 0x20; + me->ramConfig = 0x20000002; + me->busClock = 0xD61D; + // Held in reset, as the register says: a read-modify-write of it mustn't release the ME. + me->resetFlags = 4; + ResetCPU(me); } bool Init() { @@ -58,7 +108,9 @@ bool Init() { g_meState->cpu.space = space; g_meState->cpu.noJit = true; g_meState->tickBase = CoreTiming::GetTicks(&mipsr4k); - ResetCPU(g_meState); + PowerOn(g_meState); + meIntrEvent = CoreTiming::RegisterEvent("MeIntr", &MeIntrCallback); + Memory::g_kernelModeAnywhere = true; INFO_LOG(Log::ME, "Media Engine created, %d KB eDRAM", space->edramSize / 1024); return true; } @@ -70,6 +122,7 @@ void Shutdown() { delete g_meState; g_meState = nullptr; Memory::DestroyMESpace(); + Memory::g_kernelModeAnywhere = false; } void DoState(PointerWrap &p) { @@ -83,9 +136,13 @@ void DoState(PointerWrap &p) { if (p.mode == PointerWrap::MODE_READ) { if (!present) { Shutdown(); - } else if (!g_meState && !Init()) { - p.SetError(PointerWrap::ERROR_FAILURE); - return; + } else if (!g_meState) { + if (!Init()) { + p.SetError(PointerWrap::ERROR_FAILURE); + return; + } + // Compiled for user mode. + mipsr4k.ClearJitCacheDeferred(); } } if (!g_meState) { @@ -95,10 +152,50 @@ void DoState(PointerWrap &p) { MEState *me = g_meState; me->cpu.DoState(p); Do(p, me->inReset); + Do(p, me->halted); Do(p, me->tickBase); Do(p, me->interruptEnable); + Do(p, me->intcRaised); + Do(p, me->intcEnabled); + Do(p, me->resetFlags); + Do(p, me->busClock); + Do(p, me->nmiFlags); + Do(p, me->ramConfig); + Do(p, me->mutex); + DoArray(p, me->memProtect, ARRAY_SIZE(me->memProtect)); DoArray(p, me->cpu.space->GetPointerUnchecked(0), me->cpu.space->edramSize); DoArray(p, me->cpu.space->GetPointerUnchecked(0x04000000), me->cpu.space->localSize); + Do(p, meIntrEvent); + CoreTiming::RestoreRegisterEvent(meIntrEvent, "MeIntr", &MeIntrCallback); +} + +static u32 PendingInterrupts(const MEState *me) { + u32 cause = me->cpu.cop0[13] & CAUSE_IP_MASK; + if (me->intcRaised & me->intcEnabled) { + cause |= CAUSE_IP2; + } + return cause; +} + +// An unmasked interrupt wakes it from halt, and is taken if interrupts are enabled. Never in a +// delay slot: the run loop only stops outside one. +static void CheckInterrupts(MEState *me) { + MIPSState &cpu = me->cpu; + u32 &status = cpu.cop0[12]; + if ((PendingInterrupts(me) & status & CAUSE_IP_MASK) == 0) { + return; + } + me->halted = false; + // mfic reads 0 after reset, and homebrew takes interrupts without mtic, so it doesn't gate them. + if ((status & (STATUS_IE | STATUS_EXL | STATUS_ERL)) != STATUS_IE) { + return; + } + // Interrupts go straight to EBase (COP0 25), with ExcCode 0. + cpu.cop0[14] = cpu.pc; + cpu.cop0[13] &= ~CAUSE_EXCCODE_BD; + status |= STATUS_EXL; + cpu.pc = cpu.cop0[25]; + cpu.llBit = 0; } void RunFor(int cycles) { @@ -110,8 +207,23 @@ void RunFor(int cycles) { if (me->inReset) { return; } - me->cpu.downcount += cycles; - MIPSInterpret_RunDowncount(&me->cpu); + MIPSState &cpu = me->cpu; + cpu.downcount += cycles; + while (cpu.downcount >= 0 && coreState == CORE_RUNNING_CPU) { + CheckInterrupts(me); + if (me->halted) { + cpu.downcount = -1; + } else { + // Run a part, with the rest set aside so the clock stays right. + const int part = std::min(cpu.downcount, INTERRUPT_CHECK_CYCLES); + const int rest = cpu.downcount - part; + cpu.downcount = part; + me->tickBase -= rest; + MIPSInterpret_RunDowncount(&cpu); + cpu.downcount += rest; + me->tickBase += rest; + } + } } void SetReset(bool asserted) { @@ -128,4 +240,44 @@ u64 GetTicks() { return (u64)(g_meState->tickBase - g_meState->cpu.downcount); } +u32 ReadCop0(int reg) { + MEState *me = g_meState; + switch (reg) { + case 13: + return (me->cpu.cop0[13] & ~CAUSE_IP_MASK) | PendingInterrupts(me); + default: + return me->cpu.cop0[reg]; + } +} + +void WriteCop0(int reg, u32 value) { + MEState *me = g_meState; + switch (reg) { + case 11: + me->cpu.cop0[11] = value; + me->cpu.cop0[13] &= ~CAUSE_IP7; + break; + case 13: + me->cpu.cop0[13] = (me->cpu.cop0[13] & ~CAUSE_SOFT_IP) | (value & CAUSE_SOFT_IP); + break; + default: + me->cpu.cop0[reg] = value; + break; + } +} + +void Halt() { + g_meState->halted = true; + // Ends the run, the rest of it spent halted. + g_meState->cpu.downcount = std::min(g_meState->cpu.downcount, -1); +} + +void RaiseInterrupt(u32 sources) { + g_meState->intcRaised |= sources; +} + +void InterruptMainCPU() { + CoreTiming::ScheduleEvent(0, meIntrEvent); +} + } // namespace MeCore diff --git a/Core/MIPS/MeCore.h b/Core/MIPS/MeCore.h index e3ddf07368..537951cee9 100644 --- a/Core/MIPS/MeCore.h +++ b/Core/MIPS/MeCore.h @@ -29,14 +29,33 @@ class PointerWrap; namespace MeCore { +// The ME's interrupt controller's source for the main CPU's soft interrupt (0xBC100044). +enum : u32 { + ME_INTR_SOFT = 0x80000000, +}; + struct MEState { MIPSState cpu; // Held in reset until the main CPU releases it. bool inReset = true; + // Stopped by halt, until an interrupt it doesn't mask is raised. + bool halted = false; // The ME's clock is tickBase - cpu.downcount. s64 tickBase = 0; // mfic/mtic's interrupt enable, which is per CPU. u32 interruptEnable = 0; + + // Its interrupt controller (0xBC300000): raised sources, and the ones that reach the CPU (IP2). + u32 intcRaised = 0; + u32 intcEnabled = 0; + + // System registers (0xBC100000): those the ME code we run uses. + u32 resetFlags = 0; // 0x4C. Bit 2 holds the ME in reset. + u32 busClock = 0; // 0x50 + u32 nmiFlags = 0; // 0x04 + u32 ramConfig = 0; // 0x40. Bits 0-1: RAM size, 16 << n MB. + u32 mutex = 0; // 0x48. Bit 0 is the main CPU's, bit 1 the ME's. + u32 memProtect[20]{}; // 0xBC000000-0xBC00004C }; // Null when the ME isn't emulated. @@ -54,7 +73,17 @@ void DoState(PointerWrap &p); void RunFor(int cycles); // Asserting reset stops the ME. Releasing it starts the ME at the reset vector. void SetReset(bool asserted); -// Count (COP0 register 9) on the ME. +// The ME's clock. u64 GetTicks(); +// COP0 registers with behaviour on the ME: Compare (11), Cause (13). +u32 ReadCop0(int reg); +void WriteCop0(int reg, u32 value); +void Halt(); + +// Raises sources in the ME's interrupt controller. +void RaiseInterrupt(u32 sources); +// The ME interrupts the main CPU (it wrote 0xBC100044). +void InterruptMainCPU(); + } // namespace MeCore diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index 720d57c5b3..69ded52416 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -49,6 +49,7 @@ u8* base = nullptr; u8 *blockShadow = nullptr; AddressSpace g_mainSpace; +bool g_kernelModeAnywhere = false; static const size_t BLOCK_SHADOW_SIZE = (size_t)BLOCK_SHADOW_MASK + 1; // The MemArena class diff --git a/Core/MemMap.h b/Core/MemMap.h index 627611034e..be2bea54a2 100644 --- a/Core/MemMap.h +++ b/Core/MemMap.h @@ -586,6 +586,17 @@ inline bool IsKernelCodeAddress(u32 address) { return ((address & 0x0F800003) == 0x08000000) && address >= 0x08000100; } +// Homebrew that runs the Media Engine calls its own code in kernel mode, through a kernel module of +// its own ("kcall"), so while the ME is emulated any code may access hardware registers. +// HACK: This makes every block check its computed-address loads and stores for hardware registers. +// Kernel mode should be part of the CPU's state instead, switched on while a kernel module's export +// runs, with blocks checking that they were compiled for the mode they run in. +extern bool g_kernelModeAnywhere; + +inline bool MayRunInKernelMode(u32 pc) { + return g_kernelModeAnywhere || IsKernelCodeAddress(pc); +} + // I believe this is the same, but let's keep a separate function. inline bool IsValidCLUTAddress(const u32 address) { return IsValidTextureAddress(address); diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 35bdb8bc70..a631a7493e 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -262,6 +262,7 @@ + @@ -559,6 +560,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 7108a48265..530f196a30 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -169,6 +169,7 @@ + @@ -587,6 +588,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 2c0c6ff976..4554ccc1f4 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -657,6 +657,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HW/Display.cpp \ $(SRC)/Core/HW/GpioMMIO.cpp \ $(SRC)/Core/HW/MMIO.cpp \ + $(SRC)/Core/HW/MeMMIO.cpp \ $(SRC)/Core/HW/MemoryStick.cpp \ $(SRC)/Core/HW/MpegDemux.cpp.arm \ $(SRC)/Core/HW/AvcDecoder.cpp.arm \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 4181f46e4f..4ef0d59edd 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -849,6 +849,7 @@ SOURCES_CXX += \ $(COREDIR)/HW/Display.cpp \ $(COREDIR)/HW/GpioMMIO.cpp \ $(COREDIR)/HW/MMIO.cpp \ + $(COREDIR)/HW/MeMMIO.cpp \ $(COREDIR)/HW/SimpleAudioDec.cpp \ $(COREDIR)/HW/Atrac3Standalone.cpp \ $(COREDIR)/HW/AsyncIOManager.cpp \ diff --git a/unittest/JitHarness.cpp b/unittest/JitHarness.cpp index fac48cbeaa..9df6ce5890 100644 --- a/unittest/JitHarness.cpp +++ b/unittest/JitHarness.cpp @@ -758,7 +758,10 @@ bool TestMeCore() { // Saved and loaded. struct State { - void DoState(PointerWrap &p) { MeCore::DoState(p); } + void DoState(PointerWrap &p) { + CoreTiming::DoState(p); + MeCore::DoState(p); + } } state; std::vector saved; me->cpu.space->WriteUnchecked_U32(0xCAFE0001, 0x80001000); @@ -784,6 +787,114 @@ bool TestMeCore() { return success; } +// The main CPU releases the ME, which waits in halt for the main CPU's soft interrupt, and answers +// each one with its own. +bool TestMeInterrupts() { + Memory::g_PSPModel = PSP_MODEL_FAT; + SetupJitHarness(); + MMIO::Init(); + bool success = true; + + if (!MeCore::Init()) { + printf("No Media Engine on this platform, skipping\n"); + MMIO::Shutdown(); + DestroyJitHarness(); + return true; + } + MeCore::MEState *me = MeCore::g_meState; + + auto assemble = [&](u32 addr, const std::vector &lines) { + for (const char *line : lines) { + std::string error; + if (!MipsAssembleOpcode(line, currentDebugMIPS, addr, &error)) { + printf("ERROR: %s: %s\n", line, error.c_str()); + success = false; + } + addr += 4; + } + }; + // Shared RAM, which the ME runs from too. + const u32 code = 0x08900000; + assemble(code, { + "lui k0, 0x0890", + "addiu k0, k0, 0x100", + "mtc0 k0, $25", // EBase: the handler + "lui k1, 0xBC30", + "lui k0, 0x8000", + "sw k0, 8(k1)", // the soft interrupt reaches the ME + "ori k0, zero, 0x401", + "mtc0 k0, $12", // IM2, IE + "nop", + "nop", // 0x08900024: halt, which armips doesn't know + "j 0x08900024", + "nop", + }); + assemble(code + 0x100, { + "lui k1, 0xBC30", + "lui k0, 0x8000", + "sw k0, 0(k1)", // acknowledged + "lui k1, 0x0890", + "lw k0, 0x200(k1)", + "addiu k0, k0, 1", + "sw k0, 0x200(k1)", + "lui k1, 0xBC10", + "ori k0, zero, 1", + "sw k0, 0x44(k1)", // interrupts the main CPU + "eret", + }); + Memory::WriteUnchecked_U32(0x70000000, code + 0x24); + // From the reset vector's 0x40. + u8 *page = Memory::GetBootPage(); + const u32 jump[] = { MIPS_MAKE_LUI(MIPS_REG_T0, 0x0890), 0x01000008 /* jr t0 */, MIPS_MAKE_NOP() }; + memcpy(page + 0x40, jump, sizeof(jump)); + auto counter = [&]() { return Memory::ReadUnchecked_U32(code + 0x200); }; + + MMIO::Write(&mipsr4k, 0xBC10004C, 4, 0x04); + MMIO::Write(&mipsr4k, 0xBC10004C, 4, 0x00); + EXPECT_FALSE(me->inReset); + MeCore::RunFor(2000); + EXPECT_TRUE(me->halted); + EXPECT_EQ_HEX(me->cpu.pc, code + 0x28); + EXPECT_EQ_INT(counter(), 0); + + for (int i = 1; i <= 2; ++i) { + MMIO::Write(&mipsr4k, 0xBC100044, 4, 1); + MeCore::RunFor(2000); + EXPECT_EQ_INT(counter(), i); + EXPECT_TRUE(me->halted); + EXPECT_EQ_HEX(me->intcRaised, 0); + EXPECT_EQ_HEX(me->cpu.cop0[12] & 2, 0); + } + EXPECT_TRUE(CoreTiming::GetScheduledEventsSummary().find("MeIntr") != std::string::npos); + + // The mutex: each CPU only gets it while the other doesn't have it. + MMIO::Write(&mipsr4k, 0xBC100048, 4, 1); + MMIO::Write(&me->cpu, 0xBC100048, 4, 2); + EXPECT_EQ_HEX(MMIO::Read(&me->cpu, 0xBC100048, 4), 1); + MMIO::Write(&mipsr4k, 0xBC100048, 4, 0); + MMIO::Write(&me->cpu, 0xBC100048, 4, 2); + EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC100048, 4), 2); + + // The main CPU has its own interrupt controller at the same address. + EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC300008, 4), MMIO::UNHANDLED_READ); + EXPECT_EQ_HEX(MMIO::Read(&me->cpu, 0xBC300008, 4), 0x80000000); + + // Count doesn't run on the ME. + MeCore::WriteCop0(9, 1234); + MeCore::RunFor(1000); + EXPECT_EQ_INT(MeCore::ReadCop0(9), 1234); + EXPECT_TRUE(me->halted); + + // Reset again. + MMIO::Write(&mipsr4k, 0xBC10004C, 4, 0x04); + EXPECT_TRUE(me->inReset); + + MeCore::Shutdown(); + MMIO::Shutdown(); + DestroyJitHarness(); + return success; +} + // Differential fuzzing of the CPU cores. Random straight-line code (ALU, FPU, VFPU, loads and // stores) runs from a random register state on each core, and every core has to end up with the // interpreter's state, NaN payloads aside. diff --git a/unittest/JitHarness.h b/unittest/JitHarness.h index f48bff1b45..2c6efce620 100644 --- a/unittest/JitHarness.h +++ b/unittest/JitHarness.h @@ -24,6 +24,7 @@ bool TestCPUEret(); bool TestMMIO(); bool TestMESpace(); bool TestMeCore(); +bool TestMeInterrupts(); bool TestCPUEquivalence(); bool TestCPURoundingMode(); bool TestVFPUDotJit(); diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index dc8dcb48c5..ef503994b7 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -3737,6 +3737,7 @@ TestItem availableTests[] = { TEST_ITEM(MMIO), TEST_ITEM(MESpace), TEST_ITEM(MeCore), + TEST_ITEM(MeInterrupts), TEST_ITEM(CPUEquivalence), TEST_ITEM(CPURoundingMode), TEST_ITEM(VFPUDotJit),