mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 18:29:05 +02:00
CPU: Add the Media Engine core, off by default
MeCore owns a second MIPSState in the ME's address space, held in reset until released (SetReset). It runs on the interpreter, a slice behind the main CPU: CoreTiming runs it for the cycles the main CPU has just used, in Advance and ForceCheck. Its own PRId (COP0 22) and mfic/mtic flag; MMIO counts as kernel mode on it, and a syscall stops it with an error rather than reaching HLE. The interpreter's block stepping reads delay slots through the stepping CPU's memory, and cache instructions log at verbose level, as ME code flushes its caches in a loop. Created at boot only for homebrew (PBP with MG category or no SFO, ELF; the VSH counts, as vshmain.prx loads as an ELF), when "Enable ME emulation for homebrew" is on: a global checkbox in developer settings, off by default and not translated yet, or --me-core. Saved in its own section after MMIO (SaveStart v5), with its own memory; loading a state creates or removes the ME to match. The boot page now starts with a reset stub that sends the ME on to 0xBFC00040, where homebrew puts its code (from the firmware's reset code, read on a PSP-1000). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
41851007a2
commit
fef84cf0ca
26 files changed
+377
-10
No files matched your search
@@ -634,6 +634,8 @@ add_library(Core STATIC
|
||||
MIPS/MIPSVFPUUtils.h
|
||||
MIPS/MIPSTracer.cpp
|
||||
MIPS/MIPSTracer.h
|
||||
MIPS/MeCore.cpp
|
||||
MIPS/MeCore.h
|
||||
MIPS/JitCommon/JitCommon.cpp
|
||||
MIPS/JitCommon/JitCommon.h
|
||||
MIPS/JitCommon/JitProfiler.cpp
|
||||
|
||||
@@ -210,6 +210,7 @@ static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(dumpCompat), CmdParamType::Bool, "dump-compat", '\0', "Apply the game's compat.ini settings when replaying a GE frame dump (off by default)", CmdLineMode::Headless},
|
||||
{POFF(hardwareTransform), CmdParamType::Bool, "hardware-transform", '\0', "Transform vertices on the GPU (default true); false transforms them on the CPU", CmdLineMode::Headless},
|
||||
{POFF(vertexJit), CmdParamType::Bool, "vertex-jit", '\0', "Decode vertices with the JIT where the host can (default; =false for the C++ decoder)", CmdLineMode::Headless},
|
||||
{POFF(emulateMediaEngine), CmdParamType::Bool, "me-core", '\0', "Emulate the Media Engine for homebrew that runs code on it (default false)"},
|
||||
{POFF(shaderCache), CmdParamType::Bool, "shader-cache", '\0', "Load and save the per-game shader cache (default true); =false to time shader compiles like a first boot"},
|
||||
{POFF(disableGPUFeatures), CmdParamType::String, "disable-gpu-features", '\0', "Act as if the GPU lacked these, to test the fallbacks: comma-separated clip-distance, cull-distance, depth-clamp, framebuffer-fetch, dual-source-blend", CmdLineMode::Headless},
|
||||
{POFF(depthFilenameSave), CmdParamType::String, "depth-save", '\0', "Save the depth buffer to FILE along with the screenshot (u32 width, u32 height, u16 depths; float depths if FILE ends in .f32)", CmdLineMode::Headless},
|
||||
@@ -568,6 +569,10 @@ void CommandLineOptions::ApplyToConfig() const {
|
||||
g_Config.bHardwareTransform = hardwareTransform.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.bHardwareTransform);
|
||||
}
|
||||
if (emulateMediaEngine.has_value()) {
|
||||
g_Config.bEmulateMediaEngine = emulateMediaEngine.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.bEmulateMediaEngine);
|
||||
}
|
||||
if (shaderCache.has_value()) {
|
||||
g_Config.bShaderCache = shaderCache.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.bShaderCache);
|
||||
|
||||
@@ -205,6 +205,7 @@ struct CommandLineOptions {
|
||||
std::optional<bool> hardwareTransform;
|
||||
std::optional<bool> vertexJit;
|
||||
std::optional<bool> shaderCache;
|
||||
std::optional<bool> emulateMediaEngine;
|
||||
// Headless: comma-separated clip-distance, cull-distance, depth-clamp, framebuffer-fetch, dual-source-blend.
|
||||
std::optional<std::string> disableGPUFeatures;
|
||||
std::optional<bool> screenshotRenderTarget;
|
||||
|
||||
@@ -440,6 +440,7 @@ static const ConfigSetting cpuSettings[] = {
|
||||
ConfigSetting("SeparateSASThread", SETTING(g_Config, bSeparateSASThread), &DefaultSasThread, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("IOTimingMethod", SETTING(g_Config, iIOTimingMethod), IOTIMING_FAST, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("FastMemoryAccess", SETTING(g_Config, bFastMemory), true, CfgFlag::PER_GAME),
|
||||
ConfigSetting("EmulateMediaEngine", SETTING(g_Config, bEmulateMediaEngine), false, CfgFlag::REPORT),
|
||||
ConfigSetting("FunctionReplacements", SETTING(g_Config, bFuncReplacements), true, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("HideSlowWarnings", SETTING(g_Config, bHideSlowWarnings), false, CfgFlag::DEFAULT),
|
||||
ConfigSetting("HideStateWarnings", SETTING(g_Config, bHideStateWarnings), false, CfgFlag::DEFAULT),
|
||||
|
||||
@@ -233,6 +233,8 @@ public:
|
||||
|
||||
bool bFastMemory;
|
||||
int iCpuCore;
|
||||
// Run homebrew's Media Engine code (Core/MIPS/MeCore.h). Global for now, in developer settings.
|
||||
bool bEmulateMediaEngine;
|
||||
bool bCheckForNewVersion;
|
||||
bool bWow64WarningDismissed;
|
||||
bool bForceLagSync;
|
||||
|
||||
@@ -718,6 +718,7 @@
|
||||
<ClCompile Include="MIPS\MIPSTables.cpp" />
|
||||
<ClCompile Include="MIPS\MIPSVFPUUtils.cpp" />
|
||||
<ClCompile Include="MIPS\MIPSTracer.cpp" />
|
||||
<ClCompile Include="MIPS\MeCore.cpp" />
|
||||
<ClCompile Include="PSPLoaders.cpp" />
|
||||
<ClCompile Include="Reporting.cpp" />
|
||||
<ClCompile Include="RetroAchievements.cpp" />
|
||||
@@ -1043,6 +1044,7 @@
|
||||
<ClInclude Include="MIPS\MIPSTables.h" />
|
||||
<ClInclude Include="MIPS\MIPSVFPUUtils.h" />
|
||||
<ClInclude Include="MIPS\MIPSTracer.h" />
|
||||
<ClInclude Include="MIPS\MeCore.h" />
|
||||
<ClInclude Include="Opcode.h" />
|
||||
<ClInclude Include="PSPLoaders.h" />
|
||||
<ClInclude Include="Reporting.h" />
|
||||
|
||||
@@ -129,6 +129,9 @@
|
||||
<ClCompile Include="MIPS\MIPSTracer.cpp">
|
||||
<Filter>MIPS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\MeCore.cpp">
|
||||
<Filter>MIPS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\MIPSAnalyst.cpp">
|
||||
<Filter>MIPS</Filter>
|
||||
</ClCompile>
|
||||
@@ -1422,6 +1425,9 @@
|
||||
<ClInclude Include="MIPS\MIPSTracer.h">
|
||||
<Filter>MIPS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\MeCore.h">
|
||||
<Filter>MIPS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\MIPSAnalyst.h">
|
||||
<Filter>MIPS</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "Core/HLE/sceKernelThread.h"
|
||||
#include "Core/HLE/__sceAudio.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/MeCore.h"
|
||||
|
||||
static const int initialHz = 222000000;
|
||||
int CPU_HZ = 222000000;
|
||||
@@ -413,8 +414,16 @@ void ProcessEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
// The Media Engine catches up with the main CPU.
|
||||
static void RunOtherCPUs(int cycles) {
|
||||
if (MeCore::g_meState) {
|
||||
MeCore::RunFor(cycles);
|
||||
}
|
||||
}
|
||||
|
||||
void ForceCheck(MIPSState *mips) {
|
||||
int cyclesExecuted = slicelength - mips->downcount;
|
||||
RunOtherCPUs(cyclesExecuted);
|
||||
globalTimer += cyclesExecuted;
|
||||
// This will cause us to check for new events immediately.
|
||||
mips->downcount = -1;
|
||||
@@ -429,6 +438,7 @@ void ForceCheck(MIPSState *mips) {
|
||||
void Advance(MIPSState *mips) {
|
||||
PROFILE_THIS_SCOPE("advance");
|
||||
int cyclesExecuted = slicelength - mips->downcount;
|
||||
RunOtherCPUs(cyclesExecuted);
|
||||
globalTimer += cyclesExecuted;
|
||||
mips->downcount = slicelength;
|
||||
|
||||
|
||||
+15
-1
@@ -61,8 +61,22 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Both CPUs reset to the start of the page. The firmware leaves code there that sends the ME
|
||||
// (whose COP0 register 22 is nonzero) on to 0x40, where homebrew puts its ME code (read on a
|
||||
// PSP-1000 under PSPLink). The main CPU's half isn't needed, it never resets under HLE.
|
||||
void Reset() override {
|
||||
memset(Memory::GetBootPage(), 0, Memory::BOOT_PAGE_SIZE);
|
||||
u8 *page = Memory::GetBootPage();
|
||||
memset(page, 0, Memory::BOOT_PAGE_SIZE);
|
||||
const u32 code[] = {
|
||||
0x4002B000, // mfc0 v0, $22
|
||||
0x1440000E, // bnez v0, 0xBFC00040
|
||||
0x00000000, // nop
|
||||
0x1000FFFF, // b .
|
||||
0x00000000, // nop
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_SIZE(code); ++i) {
|
||||
((u32_le *)page)[i] = code[i];
|
||||
}
|
||||
}
|
||||
|
||||
void DoState(PointerWrap &p) override {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/MeCore.h"
|
||||
#include "Core/MIPS/MIPSCodeUtils.h"
|
||||
#include "Core/MIPS/Interpreter.h"
|
||||
#include "Core/MIPS/MIPSTables.h"
|
||||
@@ -102,7 +103,8 @@ int MIPS_InterpretSingleStep(MIPSState *mips) {
|
||||
|
||||
// Hardware registers, for kernel code only.
|
||||
static u32 ReadMMIO(MIPSState *mips, u32 addr, int size) {
|
||||
if (!Memory::IsKernelCodeAddress(mips->pc)) {
|
||||
// The Media Engine always runs in kernel mode.
|
||||
if (!mips->space->IsMediaEngine() && !Memory::IsKernelCodeAddress(mips->pc)) {
|
||||
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only");
|
||||
return MMIO::UNHANDLED_READ;
|
||||
}
|
||||
@@ -110,7 +112,7 @@ static u32 ReadMMIO(MIPSState *mips, u32 addr, int size) {
|
||||
}
|
||||
|
||||
static void WriteMMIO(MIPSState *mips, u32 addr, int size, u32 value) {
|
||||
if (!Memory::IsKernelCodeAddress(mips->pc)) {
|
||||
if (!mips->space->IsMediaEngine() && !Memory::IsKernelCodeAddress(mips->pc)) {
|
||||
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::WRITE_WORD, "Kernel mode only");
|
||||
return;
|
||||
}
|
||||
@@ -173,13 +175,19 @@ namespace MIPSInt {
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_LOG(Log::CPU, "cache instruction affecting %08x : function %i", addr, func);
|
||||
VERBOSE_LOG(Log::CPU, "cache instruction affecting %08x : function %i", addr, func);
|
||||
}
|
||||
|
||||
PC += 4;
|
||||
}
|
||||
|
||||
void Int_Syscall(MIPSState *mips, MIPSOpcode op) {
|
||||
if (mips->space->IsMediaEngine()) {
|
||||
// There's no HLE on the ME. TODO: Its exception vector.
|
||||
ERROR_LOG(Log::ME, "syscall on the Media Engine at %08x", mips->pc);
|
||||
Core_ExecException(mips->pc, mips->pc, ExecExceptionType::ILLEGAL);
|
||||
return;
|
||||
}
|
||||
const u32 syscallPC = mips->pc - 4;
|
||||
// Need to pre-move PC, as CallSyscall may result in a rescheduling!
|
||||
// To do this neater, we'll need a little generated kernel loop that syscall can jump to and then RFI from
|
||||
@@ -750,7 +758,7 @@ namespace MIPSInt {
|
||||
// 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)CoreTiming::GetTicks(mips);
|
||||
R(rt) = (u32)(mips->space->IsMediaEngine() ? MeCore::GetTicks() : CoreTiming::GetTicks(mips));
|
||||
else
|
||||
R(rt) = mips->cop0[rd];
|
||||
}
|
||||
@@ -1038,10 +1046,16 @@ namespace MIPSInt {
|
||||
int rt = _RT;
|
||||
switch (op & 0x3F) {
|
||||
case 36: // mfic
|
||||
if (rt != 0)
|
||||
if (rt != 0 && mips->space->IsMediaEngine())
|
||||
R(rt) = MeCore::g_meState->interruptEnable;
|
||||
else if (rt != 0)
|
||||
R(rt) = __InterruptsEnabled() ? 1 : 0;
|
||||
break;
|
||||
case 38: // mtic
|
||||
if (mips->space->IsMediaEngine()) {
|
||||
MeCore::g_meState->interruptEnable = R(rt) & 1;
|
||||
break;
|
||||
}
|
||||
// Only bit 0 counts: mtic 2 disables. Pending interrupts run at the next scheduling point,
|
||||
// not right away as with sceKernelCpuResumeIntr.
|
||||
if (R(rt) & 1)
|
||||
|
||||
@@ -322,6 +322,10 @@ void MIPSState::Shutdown() {
|
||||
}
|
||||
|
||||
void MIPSState::Reset() {
|
||||
if (noJit) {
|
||||
InitRegisters();
|
||||
return;
|
||||
}
|
||||
Shutdown();
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -325,6 +325,8 @@ public:
|
||||
MIPSComp::JitInterface *jit = nullptr;
|
||||
// What this CPU sees of memory. The main CPU's is Memory::g_mainSpace.
|
||||
const Memory::AddressSpace *space = nullptr;
|
||||
// Interpreter only, and Reset() leaves the main CPU's replacement hooks alone (the Media Engine).
|
||||
bool noJit = false;
|
||||
};
|
||||
|
||||
// Access control ('private') break standard layout. Let's not allow it, to avoid excessive
|
||||
|
||||
@@ -1189,6 +1189,18 @@ struct IRBlockState {
|
||||
bool skipNext = false;
|
||||
};
|
||||
|
||||
// The instruction after the branch at pc, as this CPU sees it. The Media Engine's code can be where
|
||||
// the main CPU has no memory.
|
||||
static MIPSOpcode ReadDelaySlot(const MIPSState *mips, u32 pc) {
|
||||
if (!mips->space->IsMediaEngine()) {
|
||||
return Memory::Read_Instruction(pc + 4);
|
||||
}
|
||||
if (!mips->space->IsValid4AlignedAddress(pc + 4)) {
|
||||
return MIPSOpcode(0);
|
||||
}
|
||||
return MIPSOpcode(mips->space->ReadUnchecked_U32(pc + 4));
|
||||
}
|
||||
|
||||
// After running op from pc, whether the block is over. In a delay slot, the caller runs on anyway.
|
||||
static inline bool StepIRBlock(IRBlockState &b, const MIPSState *mips, MIPSOpcode op, u32 pc) {
|
||||
if (b.endAfterNext) {
|
||||
@@ -1220,7 +1232,7 @@ static inline bool StepIRBlock(IRBlockState &b, const MIPSState *mips, MIPSOpcod
|
||||
}
|
||||
|
||||
const u32 enc = op.encoding;
|
||||
if (((enc >> 26) == 0x14 || (enc >> 26) == 0x15) && (enc & 0xFFFF) == 1 && (Memory::Read_Instruction(pc + 4).encoding & 0xFC00003F) == 0x0D) {
|
||||
if (((enc >> 26) == 0x14 || (enc >> 26) == 0x15) && (enc & 0xFFFF) == 1 && (ReadDelaySlot(mips, pc).encoding & 0xFC00003F) == 0x0D) {
|
||||
// beql/bnel over a break: BranchRSRTComp drops both and goes on.
|
||||
b.numInstructions++;
|
||||
b.skipNext = mips->inDelaySlot;
|
||||
@@ -1236,7 +1248,7 @@ static inline bool StepIRBlock(IRBlockState &b, const MIPSState *mips, MIPSOpcod
|
||||
return true;
|
||||
}
|
||||
// Not taken: the delay slot runs next, as an ordinary instruction.
|
||||
const MIPSInfo delaySlotInfo = MIPSGetInfo(Memory::Read_Instruction(pc + 4));
|
||||
const MIPSInfo delaySlotInfo = MIPSGetInfo(ReadDelaySlot(mips, pc));
|
||||
if (IR_CONTINUE_BRANCHES && IsContinuableBranch(op) && (delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) == 0) {
|
||||
b.continueAfterNext = true;
|
||||
} else {
|
||||
@@ -1472,6 +1484,12 @@ int MIPSInterpret_RunUntil(MIPSState *mips, u64 globalTicks) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void MIPSInterpret_RunDowncount(MIPSState *mips) {
|
||||
ApplyHostRoundingMode(mips);
|
||||
RunUntilDowncountZeroFast(mips);
|
||||
RestoreHostRoundingMode();
|
||||
}
|
||||
|
||||
const char *MIPSGetName(MIPSOpcode op) {
|
||||
static const char * const noname = "unk";
|
||||
const MIPSInstruction *instr = MIPSGetInstruction(op);
|
||||
|
||||
@@ -135,6 +135,8 @@ void MIPSDisAsm(MIPSOpcode op, u32 pc, char *out, size_t outSize, bool tabsToSpa
|
||||
MIPSInfo MIPSGetInfo(MIPSOpcode op);
|
||||
void MIPSInterpret(MIPSState *mips, MIPSOpcode op); //only for those rare ones
|
||||
int MIPSInterpret_RunUntil(MIPSState *mips, u64 globalTicks);
|
||||
// Runs until the downcount goes negative, without CoreTiming or breakpoints. For the Media Engine.
|
||||
void MIPSInterpret_RunDowncount(MIPSState *mips);
|
||||
MIPSInterpretFunc MIPSGetInterpretFunc(MIPSOpcode op);
|
||||
|
||||
// MIPSInterpret for the JIT backends' interpreter fallback (CompIR_Interpret), which calls it from
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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 "Common/Serialize/Serializer.h"
|
||||
#include "Common/Serialize/SerializeFuncs.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/CoreTiming.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.
|
||||
static const u32 ME_CPU_ID = 1;
|
||||
|
||||
MEState *g_meState = nullptr;
|
||||
|
||||
// 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 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;
|
||||
me->interruptEnable = 0;
|
||||
}
|
||||
|
||||
bool Init() {
|
||||
_dbg_assert_(!g_meState);
|
||||
Memory::AddressSpace *space = Memory::CreateMESpace();
|
||||
if (!space) {
|
||||
WARN_LOG(Log::ME, "No Media Engine address space on this platform, not emulating the ME");
|
||||
return false;
|
||||
}
|
||||
g_meState = new MEState();
|
||||
g_meState->cpu.space = space;
|
||||
g_meState->cpu.noJit = true;
|
||||
g_meState->tickBase = CoreTiming::GetTicks(&mipsr4k);
|
||||
ResetCPU(g_meState);
|
||||
INFO_LOG(Log::ME, "Media Engine created, %d KB eDRAM", space->edramSize / 1024);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (!g_meState) {
|
||||
return;
|
||||
}
|
||||
delete g_meState;
|
||||
g_meState = nullptr;
|
||||
Memory::DestroyMESpace();
|
||||
}
|
||||
|
||||
void DoState(PointerWrap &p) {
|
||||
auto s = p.Section("MeCore", 1);
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool present = g_meState != nullptr;
|
||||
Do(p, present);
|
||||
if (p.mode == PointerWrap::MODE_READ) {
|
||||
if (!present) {
|
||||
Shutdown();
|
||||
} else if (!g_meState && !Init()) {
|
||||
p.SetError(PointerWrap::ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!g_meState) {
|
||||
return;
|
||||
}
|
||||
|
||||
MEState *me = g_meState;
|
||||
me->cpu.DoState(p);
|
||||
Do(p, me->inReset);
|
||||
Do(p, me->tickBase);
|
||||
Do(p, me->interruptEnable);
|
||||
DoArray(p, me->cpu.space->GetPointerUnchecked(0), me->cpu.space->edramSize);
|
||||
DoArray(p, me->cpu.space->GetPointerUnchecked(0x04000000), me->cpu.space->localSize);
|
||||
}
|
||||
|
||||
void RunFor(int cycles) {
|
||||
MEState *me = g_meState;
|
||||
if (cycles <= 0) {
|
||||
return;
|
||||
}
|
||||
me->tickBase += cycles;
|
||||
if (me->inReset) {
|
||||
return;
|
||||
}
|
||||
me->cpu.downcount += cycles;
|
||||
MIPSInterpret_RunDowncount(&me->cpu);
|
||||
}
|
||||
|
||||
void SetReset(bool asserted) {
|
||||
MEState *me = g_meState;
|
||||
if (asserted) {
|
||||
me->inReset = true;
|
||||
} else if (me->inReset) {
|
||||
ResetCPU(me);
|
||||
me->inReset = false;
|
||||
}
|
||||
}
|
||||
|
||||
u64 GetTicks() {
|
||||
return (u64)(g_meState->tickBase - g_meState->cpu.downcount);
|
||||
}
|
||||
|
||||
} // namespace MeCore
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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 Media Engine, the PSP's second Allegrex. The firmware runs its codecs on it, and homebrew
|
||||
// whatever it likes. It runs in its own address space (Memory::CreateMESpace()), on the
|
||||
// interpreter, a slice behind the main CPU: CoreTiming runs it for the cycles the main CPU has just
|
||||
// used. Only emulated for homebrew, when enabled; commercial games get the HLE codecs.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
namespace MeCore {
|
||||
|
||||
struct MEState {
|
||||
MIPSState cpu;
|
||||
// Held in reset until the main CPU releases it.
|
||||
bool inReset = true;
|
||||
// The ME's clock is tickBase - cpu.downcount.
|
||||
s64 tickBase = 0;
|
||||
// mfic/mtic's interrupt enable, which is per CPU.
|
||||
u32 interruptEnable = 0;
|
||||
};
|
||||
|
||||
// Null when the ME isn't emulated.
|
||||
extern MEState *g_meState;
|
||||
|
||||
// Whether the config and this boot (homebrew) want the ME.
|
||||
bool Wanted(bool homebrew);
|
||||
// Creates the ME, in reset. Fails on platforms without an ME address space.
|
||||
bool Init();
|
||||
void Shutdown();
|
||||
// Creates or removes the ME to match the state.
|
||||
void DoState(PointerWrap &p);
|
||||
|
||||
// Runs the ME for the cycles the main CPU has just run.
|
||||
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.
|
||||
u64 GetTicks();
|
||||
|
||||
} // namespace MeCore
|
||||
+7
-1
@@ -65,6 +65,7 @@
|
||||
#include "Core/RetroAchievements.h"
|
||||
#include "HW/MemoryStick.h"
|
||||
#include "Core/HW/MMIO.h"
|
||||
#include "Core/MIPS/MeCore.h"
|
||||
|
||||
#ifndef MOBILE_DEVICE
|
||||
#include "Core/AVIDump.h"
|
||||
@@ -211,7 +212,7 @@ int g_screenshotFailures;
|
||||
if (gpu)
|
||||
gpu->FlushPendingDrawing();
|
||||
|
||||
auto s = p.Section("SaveStart", 1, 4);
|
||||
auto s = p.Section("SaveStart", 1, 5);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
@@ -274,6 +275,11 @@ int g_screenshotFailures;
|
||||
} else if (p.mode == p.MODE_READ) {
|
||||
MMIO::Init();
|
||||
}
|
||||
if (s >= 5) {
|
||||
MeCore::DoState(p);
|
||||
} else if (p.mode == p.MODE_READ) {
|
||||
MeCore::Shutdown();
|
||||
}
|
||||
|
||||
if (p.mode == p.MODE_READ && p.error == PointerWrap::ERROR_FAILURE) {
|
||||
// RAM may be loaded and the JIT not told, or the other way round. Keep nothing.
|
||||
|
||||
+8
-1
@@ -61,6 +61,7 @@
|
||||
#include "Core/HLE/sceUtility.h"
|
||||
#include "Core/HW/Display.h"
|
||||
#include "Core/HW/MMIO.h"
|
||||
#include "Core/MIPS/MeCore.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Util/PathUtil.h"
|
||||
@@ -383,6 +384,7 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
|
||||
std::string geDumpDiscID;
|
||||
|
||||
std::string gameTitle = "Unidentified PSP title";
|
||||
bool homebrew = false;
|
||||
|
||||
switch (type) {
|
||||
case IdentifiedFileType::PSP_ISO:
|
||||
@@ -437,7 +439,7 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
|
||||
// would, and the program would then overwrite its own heap and stacks.
|
||||
// Homebrew is identified by the MG ("memory stick game") category, or no PARAM.SFO at all.
|
||||
// (g_paramSFO only gets a few keys copied over for a PBP, so the category is in the raw copy.)
|
||||
const bool homebrew = !hasSFO || g_paramSFORaw.GetValueString("CATEGORY") == "MG";
|
||||
homebrew = !hasSFO || g_paramSFORaw.GetValueString("CATEGORY") == "MG";
|
||||
if (homebrew && Memory::g_PSPModel != PSP_MODEL_FAT && Memory::g_MemorySize == Memory::RAM_NORMAL_SIZE) {
|
||||
INFO_LOG(Log::Loader, "Homebrew PBP: mapping the PSP-2000 extra RAM, outside the user partition");
|
||||
Memory::g_MemorySize = Memory::RAM_DOUBLE_SIZE;
|
||||
@@ -446,6 +448,7 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
|
||||
break;
|
||||
}
|
||||
case IdentifiedFileType::PSP_ELF:
|
||||
homebrew = true;
|
||||
if (Memory::g_PSPModel != PSP_MODEL_FAT) {
|
||||
INFO_LOG(Log::Loader, "ELF, using full PSP-2000 memory access");
|
||||
Memory::g_MemorySize = Memory::RAM_DOUBLE_SIZE;
|
||||
@@ -554,6 +557,9 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
|
||||
|
||||
CoreTiming::Init(&mipsr4k);
|
||||
MMIO::Init();
|
||||
if (MeCore::Wanted(homebrew)) {
|
||||
MeCore::Init();
|
||||
}
|
||||
|
||||
DisplayHWInit();
|
||||
|
||||
@@ -678,6 +684,7 @@ void CPU_Shutdown(bool success) {
|
||||
Replacement_Shutdown();
|
||||
|
||||
CoreTiming::Shutdown();
|
||||
MeCore::Shutdown();
|
||||
MMIO::Shutdown();
|
||||
__KernelShutdown();
|
||||
HLEShutdown();
|
||||
|
||||
@@ -160,6 +160,8 @@ void DeveloperToolsScreen::CreateGeneralTab(UI::LinearLayout *list) {
|
||||
// Only the interpreter raises these, but don't hide it behind the CPU core choice - it'd just
|
||||
// look like the setting had vanished.
|
||||
list->Add(new CheckBox(&g_Config.bEnableFPUExceptionTraps, dev->T("Enable FPU exceptions")));
|
||||
// Experimental, so not translated yet.
|
||||
list->Add(new CheckBox(&g_Config.bEmulateMediaEngine, dev->T("Enable ME emulation for homebrew")));
|
||||
|
||||
list->Add(new Choice(dev->T("JIT debug tools")))->OnClick.Handle(this, &DeveloperToolsScreen::OnJitDebugTools);
|
||||
list->Add(new CheckBox(&g_Config.bShowDeveloperMenu, dev->T("Show in-game developer menu")));
|
||||
|
||||
@@ -310,6 +310,7 @@
|
||||
<ClInclude Include="..\..\Core\MIPS\MIPSTables.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\MIPSVFPUUtils.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\MIPSTracer.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\MeCore.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\x86\X64IRJit.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\x86\X64IRRegCache.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\x86\X64VfpuDot.h" />
|
||||
@@ -622,6 +623,7 @@
|
||||
<ClCompile Include="..\..\Core\MIPS\MIPSTables.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\MIPSVFPUUtils.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\MIPSTracer.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\MeCore.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\x86\X64IRAsm.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\x86\X64IRCompALU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\x86\X64IRCompBranch.cpp" />
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
<ClCompile Include="..\..\Core\MIPS\MIPSTables.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\MIPSVFPUUtils.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\MIPSTracer.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\MeCore.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\x86\X64IRAsm.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\x86\X64IRCompALU.cpp" />
|
||||
<ClCompile Include="..\..\Core\MIPS\x86\X64IRCompBranch.cpp" />
|
||||
@@ -633,6 +634,7 @@
|
||||
<ClInclude Include="..\..\Core\MIPS\MIPSTables.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\MIPSVFPUUtils.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\MIPSTracer.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\MeCore.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\x86\X64IRJit.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\x86\X64IRRegCache.h" />
|
||||
<ClInclude Include="..\..\Core\MIPS\x86\X64VfpuDot.h" />
|
||||
|
||||
@@ -568,6 +568,7 @@ EXEC_AND_LIB_FILES := \
|
||||
$(SRC)/Core/MIPS/MIPSCodeUtils.cpp.arm \
|
||||
$(SRC)/Core/MIPS/MIPSDebugInterface.cpp \
|
||||
$(SRC)/Core/MIPS/MIPSTracer.cpp \
|
||||
$(SRC)/Core/MIPS/MeCore.cpp \
|
||||
$(SRC)/Core/MIPS/IR/IRAnalysis.cpp \
|
||||
$(SRC)/Core/MIPS/IR/IRFrontend.cpp \
|
||||
$(SRC)/Core/MIPS/IR/IRJit.cpp \
|
||||
|
||||
@@ -890,6 +890,7 @@ SOURCES_CXX += \
|
||||
$(COREDIR)/MIPS/MIPSStackWalk.cpp \
|
||||
$(COREDIR)/MIPS/MIPSVFPUUtils.cpp \
|
||||
$(COREDIR)/MIPS/MIPSTracer.cpp \
|
||||
$(COREDIR)/MIPS/MeCore.cpp \
|
||||
$(COREDIR)/MemFault.cpp \
|
||||
$(COREDIR)/MemMap.cpp \
|
||||
$(COREDIR)/MemMapFunctions.cpp \
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/MIPS/JitCommon/JitBlockCache.h"
|
||||
#include "Core/MIPS/Interpreter.h"
|
||||
#include "Core/MIPS/MeCore.h"
|
||||
#include "Core/MIPS/MIPSCodeUtils.h"
|
||||
#include "Core/MIPS/MIPSDebugInterface.h"
|
||||
#include "Core/MIPS/MIPSAsm.h"
|
||||
@@ -714,6 +715,75 @@ bool TestMESpace() {
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TestMeCore() {
|
||||
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;
|
||||
|
||||
// The classic homebrew ME code at 0xBFC00040 (pspsdk me/basic): count at 0xBFC00060, forever.
|
||||
const u32 code[] = {
|
||||
MIPS_MAKE_LUI(MIPS_REG_T1, 0xBFC0),
|
||||
MIPS_MAKE_LW(MIPS_REG_T0, MIPS_REG_T1, 0x60),
|
||||
MIPS_MAKE_ADDIU(MIPS_REG_T0, MIPS_REG_T0, 1),
|
||||
(0x2Bu << 26) | (MIPS_REG_T1 << 21) | (MIPS_REG_T0 << 16) | 0x60, // sw t0, 0x60(t1)
|
||||
MIPS_MAKE_B(-4),
|
||||
MIPS_MAKE_NOP(),
|
||||
};
|
||||
u8 *page = Memory::GetBootPage();
|
||||
memcpy(page + 0x40, code, sizeof(code));
|
||||
auto counter = [&]() { return *(u32_le *)(page + 0x60); };
|
||||
|
||||
// Held in reset, its clock still runs.
|
||||
MeCore::RunFor(1000);
|
||||
EXPECT_EQ_INT(counter(), 0);
|
||||
EXPECT_EQ_INT((int)MeCore::GetTicks(), 1000);
|
||||
|
||||
// Released, through the reset stub to 0x40.
|
||||
MeCore::SetReset(false);
|
||||
EXPECT_EQ_HEX(me->cpu.pc, 0xBFC00000);
|
||||
MeCore::RunFor(1000);
|
||||
const u32 count = counter();
|
||||
EXPECT_TRUE(count > 100);
|
||||
EXPECT_TRUE(me->cpu.pc >= 0xBFC00040 && me->cpu.pc < 0xBFC00058);
|
||||
EXPECT_TRUE(MeCore::GetTicks() >= 2000 && MeCore::GetTicks() < 2010);
|
||||
|
||||
// Saved and loaded.
|
||||
struct State {
|
||||
void DoState(PointerWrap &p) { MeCore::DoState(p); }
|
||||
} state;
|
||||
std::vector<u8> saved;
|
||||
me->cpu.space->WriteUnchecked_U32(0xCAFE0001, 0x80001000);
|
||||
const u32 pc = me->cpu.pc;
|
||||
EXPECT_EQ_INT(CChunkFileReader::MeasureAndSavePtr(state, &saved), CChunkFileReader::ERROR_NONE);
|
||||
me->cpu.space->WriteUnchecked_U32(0, 0x80001000);
|
||||
MeCore::RunFor(100);
|
||||
std::string error;
|
||||
EXPECT_EQ_INT(CChunkFileReader::LoadPtr(saved.data(), saved.size(), state, &error), CChunkFileReader::ERROR_NONE);
|
||||
me = MeCore::g_meState;
|
||||
EXPECT_EQ_HEX(me->cpu.pc, pc);
|
||||
EXPECT_EQ_HEX(me->cpu.space->ReadUnchecked_U32(0x80001000), 0xCAFE0001);
|
||||
|
||||
// Back in reset, it stops.
|
||||
MeCore::SetReset(true);
|
||||
const u32 stopped = counter();
|
||||
MeCore::RunFor(1000);
|
||||
EXPECT_EQ_INT(counter(), stopped);
|
||||
|
||||
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.
|
||||
|
||||
@@ -23,6 +23,7 @@ bool TestJitReplacementHooks();
|
||||
bool TestCPUEret();
|
||||
bool TestMMIO();
|
||||
bool TestMESpace();
|
||||
bool TestMeCore();
|
||||
bool TestCPUEquivalence();
|
||||
bool TestCPURoundingMode();
|
||||
bool TestVFPUDotJit();
|
||||
@@ -3736,6 +3736,7 @@ TestItem availableTests[] = {
|
||||
TEST_ITEM(CPUEret),
|
||||
TEST_ITEM(MMIO),
|
||||
TEST_ITEM(MESpace),
|
||||
TEST_ITEM(MeCore),
|
||||
TEST_ITEM(CPUEquivalence),
|
||||
TEST_ITEM(CPURoundingMode),
|
||||
TEST_ITEM(VFPUDotJit),
|
||||
|
||||
Reference in new issue
Block a user