diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt
index 556d938586..fcb2f3c3e1 100644
--- a/Core/CMakeLists.txt
+++ b/Core/CMakeLists.txt
@@ -396,6 +396,8 @@ add_library(Core STATIC
HLE/__sceAudio.h
HLE/sceAdler.cpp
HLE/sceAdler.h
+ HLE/sceAmctrl.cpp
+ HLE/sceAmctrl.h
HLE/AtracBase.h
HLE/AtracCtx.cpp
HLE/AtracCtx.h
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index fff3741a09..1c07a66c9a 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -608,6 +608,7 @@
+
@@ -946,6 +947,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index f3fd104aae..cd7094d8b3 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -198,6 +198,9 @@
HLE\Libraries
+
+ HLE\Libraries
+
HLE\Libraries
@@ -1503,6 +1506,9 @@
HLE\Libraries
+
+ HLE\Libraries
+
HLE\Libraries
diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp
index 8b06f48617..b64b877c84 100644
--- a/Core/CoreTiming.cpp
+++ b/Core/CoreTiming.cpp
@@ -58,6 +58,8 @@ static Event *eventPool = 0;
// Downcount has been moved to currentMIPS, to save a couple of clocks in every ARM JIT block
// as we can already reach that structure through a register.
int slicelength;
+// How much of the current slice the other CPUs have already run (CatchUpOtherCPUs).
+static int otherCPUsRan = 0;
alignas(16) static s64 globalTimer;
static s64 idledCycles;
@@ -218,6 +220,7 @@ void UnregisterAllEvents() {
void Init(MIPSState *mips) {
mips->downcount = INITIAL_SLICE_LENGTH;
slicelength = INITIAL_SLICE_LENGTH;
+ otherCPUsRan = 0;
globalTimer = 0;
idledCycles = 0;
lastGlobalTimeTicks = 0;
@@ -417,7 +420,16 @@ void ProcessEvents() {
// The Media Engine catches up with the main CPU.
static void RunOtherCPUs(int cycles) {
if (MeCore::g_meState) {
- MeCore::RunFor(cycles);
+ MeCore::RunFor(cycles - otherCPUsRan);
+ }
+ otherCPUsRan = 0;
+}
+
+void CatchUpOtherCPUs(MIPSState *mips) {
+ const int ran = slicelength - mips->downcount;
+ if (MeCore::g_meState && ran > otherCPUsRan) {
+ MeCore::RunFor(ran - otherCPUsRan);
+ otherCPUsRan = ran;
}
}
@@ -553,6 +565,8 @@ void DoState(PointerWrap &p) {
auto s = p.Section("CoreTiming", 1, 3);
if (!s)
return;
+ // States are saved between slices.
+ otherCPUsRan = 0;
int n = (int)event_types.size();
int current = n;
diff --git a/Core/CoreTiming.h b/Core/CoreTiming.h
index bc52e6cc38..0a4cf2870c 100644
--- a/Core/CoreTiming.h
+++ b/Core/CoreTiming.h
@@ -127,6 +127,9 @@ namespace CoreTiming {
bool IsScheduled(int event_type);
void Advance(MIPSState *mips);
void ForceCheck(MIPSState *mips);
+ // Runs the other CPUs (the Media Engine) up to where this one is in the slice, as they otherwise
+ // run at its end: for when this one looks at what they share, such as the boot page.
+ void CatchUpOtherCPUs(MIPSState *mips);
// Pretend that the main CPU has executed enough cycles to reach the next event.
void Idle(MIPSState *mips, int maxIdle = 0);
diff --git a/Core/HLE/FunctionWrappers.h b/Core/HLE/FunctionWrappers.h
index 3cb7eac601..3647227e7a 100644
--- a/Core/HLE/FunctionWrappers.h
+++ b/Core/HLE/FunctionWrappers.h
@@ -327,6 +327,11 @@ template void WrapI_UCI(MIPSState *mips) {
RETURN(retval);
}
+template void WrapI_UIIUUU(MIPSState *mips) {
+ int retval = HLECall(mips, PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5));
+ RETURN(retval);
+}
+
template void WrapU_UIIIII(MIPSState *mips) {
u32 retval = HLECall(mips, PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5));
RETURN(retval);
diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp
index 13731b43c9..b3a58074b7 100644
--- a/Core/HLE/HLETables.cpp
+++ b/Core/HLE/HLETables.cpp
@@ -87,6 +87,7 @@
#include "sceSfmt19937.h"
#include "sceG729.h"
#include "KUBridge.h"
+#include "sceAmctrl.h"
#include "sceNetInet.h"
#include "sceNetResolver.h"
// #include "sceNp2.h"
@@ -359,6 +360,7 @@ void RegisterAllModules() {
Register_ExceptionManagerForKernel();
Register_sceSysreg_driver();
Register_sceSysEventForKernel();
+ Register_sceAmctrl_driver();
// Not ready to enable this due to apparent softlocks in Patapon 3.
// Register_sceNpMatching2();
diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp
index 266b46674f..1f35e600b9 100644
--- a/Core/HLE/__sceAudio.cpp
+++ b/Core/HLE/__sceAudio.cpp
@@ -43,6 +43,8 @@
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelInterrupt.h"
+#include "Core/HW/MeMMIO.h"
+#include "Core/MIPS/MeCore.h"
#include "Core/Util/AudioFormat.h"
// Should be used to lock anything related to the outAudioQueue.
@@ -641,6 +643,10 @@ void __AudioUpdate(bool resetRecording) {
woke |= __AudioMixChannel(chan);
}
woke |= __AudioMixSRC(g_audioSRC);
+ // What the ME played through the audio block (the PS1 emulator's sound).
+ if (MeCore::g_meState) {
+ MeMMIO::AudioMix(mixBuffer, hwBlockSize);
+ }
audioMixing = false;
if (g_Config.bEnableSound) {
diff --git a/Core/HLE/sceAmctrl.cpp b/Core/HLE/sceAmctrl.cpp
new file mode 100644
index 0000000000..33f4b34c25
--- /dev/null
+++ b/Core/HLE/sceAmctrl.cpp
@@ -0,0 +1,117 @@
+// 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/
+
+// amctrl.prx's PGD MAC and cipher functions, which the POPS manager uses on a store PS1 game's disc.
+// libkirk's amctrl.c reimplements the module, and its MAC_KEY and CIPHER_KEY have the layout of the
+// contexts callers keep in their memory, so they're used in place.
+
+#include "ext/libkirk/amctrl.h"
+
+#include "Core/Debugger/MemBlockInfo.h"
+#include "Core/HLE/HLE.h"
+#include "Core/HLE/FunctionWrappers.h"
+#include "Core/HLE/sceAmctrl.h"
+#include "Core/HLE/sceChnnlsv.h"
+#include "Core/MemMap.h"
+
+static MAC_KEY *MacKey(u32 addr) {
+ return Memory::IsValidRange(addr, sizeof(MAC_KEY)) ? (MAC_KEY *)Memory::GetPointerWriteUnchecked(addr) : nullptr;
+}
+
+static CIPHER_KEY *CipherKey(u32 addr) {
+ return Memory::IsValidRange(addr, sizeof(CIPHER_KEY)) ? (CIPHER_KEY *)Memory::GetPointerWriteUnchecked(addr) : nullptr;
+}
+
+// A 16-byte key argument, which may be absent.
+static u8 *Key16(u32 addr) {
+ return Memory::IsValidRange(addr, 16) ? Memory::GetPointerWriteUnchecked(addr) : nullptr;
+}
+
+static int Hle_sceDrmBBMacInit(u32 mkeyAddr, int type) {
+ MAC_KEY *mkey = MacKey(mkeyAddr);
+ if (!mkey) {
+ return hleLogError(Log::sceKernel, -1, "bad context");
+ }
+ return hleLogDebug(Log::sceKernel, sceDrmBBMacInit(mkey, type));
+}
+
+static int Hle_sceDrmBBMacUpdate(u32 mkeyAddr, u32 buf, int size) {
+ MAC_KEY *mkey = MacKey(mkeyAddr);
+ if (!mkey || size < 0 || !Memory::IsValidRange(buf, size)) {
+ return hleLogError(Log::sceKernel, -1, "bad arguments");
+ }
+ return hleLogDebug(Log::sceKernel, sceDrmBBMacUpdate(__ChnnlsvKirkState(), mkey, Memory::GetPointerWriteUnchecked(buf), size));
+}
+
+static int Hle_sceDrmBBMacFinal(u32 mkeyAddr, u32 out, u32 vkey) {
+ MAC_KEY *mkey = MacKey(mkeyAddr);
+ if (!mkey || !Key16(out)) {
+ return hleLogError(Log::sceKernel, -1, "bad arguments");
+ }
+ return hleLogDebug(Log::sceKernel, sceDrmBBMacFinal(__ChnnlsvKirkState(), mkey, Key16(out), Key16(vkey)));
+}
+
+// Compares with the expected MAC: 0x80510300 when they differ.
+static int Hle_sceDrmBBMacFinal2(u32 mkeyAddr, u32 mac, u32 vkey) {
+ MAC_KEY *mkey = MacKey(mkeyAddr);
+ if (!mkey || !Key16(mac)) {
+ return hleLogError(Log::sceKernel, -1, "bad arguments");
+ }
+ const int result = sceDrmBBMacFinal2(__ChnnlsvKirkState(), mkey, Key16(mac), Key16(vkey));
+ if (result != 0) {
+ return hleLogWarning(Log::sceKernel, result, "MAC mismatch");
+ }
+ return hleLogDebug(Log::sceKernel, result);
+}
+
+static int Hle_sceDrmBBCipherInit(u32 ckeyAddr, int type, int mode, u32 headerKey, u32 versionKey, u32 seed) {
+ CIPHER_KEY *ckey = CipherKey(ckeyAddr);
+ if (!ckey || !Key16(headerKey)) {
+ return hleLogError(Log::sceKernel, -1, "bad arguments");
+ }
+ return hleLogDebug(Log::sceKernel, sceDrmBBCipherInit(__ChnnlsvKirkState(), ckey, type, mode, Key16(headerKey), Key16(versionKey), seed));
+}
+
+static int Hle_sceDrmBBCipherUpdate(u32 ckeyAddr, u32 data, int size) {
+ CIPHER_KEY *ckey = CipherKey(ckeyAddr);
+ if (!ckey || size < 0 || !Memory::IsValidRange(data, size)) {
+ return hleLogError(Log::sceKernel, -1, "bad arguments");
+ }
+ const int result = sceDrmBBCipherUpdate(__ChnnlsvKirkState(), ckey, Memory::GetPointerWriteUnchecked(data), size);
+ NotifyMemInfo(MemBlockFlags::WRITE, data, size, "BBCipher");
+ return hleLogDebug(Log::sceKernel, result);
+}
+
+static int Hle_sceDrmBBCipherFinal(u32 ckeyAddr) {
+ CIPHER_KEY *ckey = CipherKey(ckeyAddr);
+ if (!ckey) {
+ return hleLogError(Log::sceKernel, -1, "bad context");
+ }
+ return hleLogDebug(Log::sceKernel, sceDrmBBCipherFinal(ckey));
+}
+
+// 6.60 NIDs.
+const HLEFunction sceAmctrl_driver[] = {
+ { 0x525B8218, &WrapI_UI, "sceDrmBBMacInit", 'i', "xi", HLE_KERNEL_SYSCALL },
+ { 0x58163FBE, &WrapI_UUI, "sceDrmBBMacUpdate", 'i', "xxi", HLE_KERNEL_SYSCALL },
+ { 0xEF95A213, &WrapI_UUU, "sceDrmBBMacFinal", 'i', "xxx", HLE_KERNEL_SYSCALL },
+ { 0xF5186D8E, &WrapI_UUU, "sceDrmBBMacFinal2", 'i', "xxx", HLE_KERNEL_SYSCALL },
+ { 0x1CCB66D2, &WrapI_UIIUUU, "sceDrmBBCipherInit", 'i', "xiixxx", HLE_KERNEL_SYSCALL },
+ { 0x0785C974, &WrapI_UUI, "sceDrmBBCipherUpdate", 'i', "xxi", HLE_KERNEL_SYSCALL },
+ { 0x9951C50F, &WrapI_U, "sceDrmBBCipherFinal", 'i', "x", HLE_KERNEL_SYSCALL },
+};
+
+void Register_sceAmctrl_driver() {
+ RegisterHLEModule("sceAmctrl_driver", ARRAY_SIZE(sceAmctrl_driver), sceAmctrl_driver);
+}
diff --git a/Core/HLE/sceAmctrl.h b/Core/HLE/sceAmctrl.h
new file mode 100644
index 0000000000..c3f6746e1b
--- /dev/null
+++ b/Core/HLE/sceAmctrl.h
@@ -0,0 +1,17 @@
+// 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/
+
+#pragma once
+
+void Register_sceAmctrl_driver();
diff --git a/Core/HLE/sceSysreg.cpp b/Core/HLE/sceSysreg.cpp
index 7226e60282..d94208f8c5 100644
--- a/Core/HLE/sceSysreg.cpp
+++ b/Core/HLE/sceSysreg.cpp
@@ -15,11 +15,10 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
-// The parts of the firmware's system register driver homebrew uses to start the Media Engine. Each
-// sets or clears a bit of a register the way the driver does (lowio.prx 6.61 at 080001b8 and
-// 080004b8, sysreg.prx 1.50 the same), returning whether it was set. Only the NIDs of the firmware
-// versions homebrew was built for (1.50, and 3.71 and later's for the ME calls), so the VSH's own
-// later lowio.prx keeps its exports.
+// The parts of the firmware's system register driver homebrew and the POPS manager use to start the
+// Media Engine. Each sets or clears a bit of a register the way the driver does (lowio.prx 6.61 at
+// 080001b8, 080004b8, 08000880 and 08000cd8, sysreg.prx 1.50 the same), returning whether it was
+// set. Under --vsh these stand in for lowio.prx's own, which write the same registers.
#include "Common/CommonTypes.h"
#include "Core/HLE/HLE.h"
@@ -32,6 +31,9 @@
enum : u32 {
SYSREG_RESET = 0xBC10004C,
SYSREG_BUS_CLOCK = 0xBC100050,
+ SYSREG_CLOCK_58 = 0xBC100058,
+ SYSREG_60 = 0xBC100060,
+ SYSREG_IO_78 = 0xBC100078,
RESET_ME = 0x04,
RESET_VME = 0x10,
@@ -77,6 +79,42 @@ static int sceSysregAvcResetDisable(MIPSState *mips) {
return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_AVC, false));
}
+// The 6.60/6.61 calls popsman.prx makes whose names aren't known, after what lowio.prx does.
+// Those taking an index only take 0 or 1.
+static int SetSysregIndexedBit(MIPSState *mips, u32 reg, u32 bit0, int index) {
+ if (index < 0 || index >= 2)
+ return hleLogError(Log::ME, 0x80000102, "bad index");
+ return hleLogDebug(Log::ME, SetSysregBit(mips, reg, bit0 << index, true));
+}
+
+static int sceSysreg_driver_8B1DD83A(MIPSState *mips, int index) {
+ return SetSysregIndexedBit(mips, SYSREG_BUS_CLOCK, 0x8000, index);
+}
+
+static int sceSysreg_driver_B1751B06(MIPSState *mips, int index) {
+ return SetSysregIndexedBit(mips, SYSREG_CLOCK_58, 0x10000, index);
+}
+
+static int sceSysreg_driver_4634C9DC(MIPSState *mips, int index) {
+ return SetSysregIndexedBit(mips, SYSREG_IO_78, 0x80, index);
+}
+
+static int sceSysreg_driver_4B4CCE80(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_IO_78, 0x800, true));
+}
+
+// Sets 0x60 bit 18 to value, returning what it was.
+static int sceSysreg_driver_41B0337B(MIPSState *mips, int value) {
+ if (value < 0 || value >= 2)
+ return hleLogError(Log::ME, 0x800001FE, "bad value");
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_60, 1 << 18, value != 0));
+}
+
+// popsman calls it with (0, 1) before setting up audio. Not read yet.
+static int sceSysreg_driver_C36775AD(int a, int b) {
+ return hleLogWarning(Log::ME, 0, "unimplemented");
+}
+
const HLEFunction sceSysreg_driver[] = {
{0xDE59DACB, &WrapI_V, "sceSysregMeResetEnable", 'i', "", HLE_KERNEL_SYSCALL },
{0x2DB0EB28, &WrapI_V, "sceSysregMeResetDisable", 'i', "", HLE_KERNEL_SYSCALL },
@@ -90,6 +128,17 @@ const HLEFunction sceSysreg_driver[] = {
{0xA9997109, &WrapI_V, "sceSysregMeResetEnable371", 'i', "", HLE_KERNEL_SYSCALL },
{0x76220E94, &WrapI_V, "sceSysregMeResetDisable371", 'i', "", HLE_KERNEL_SYSCALL },
{0x3199CF1C, &WrapI_V, "sceSysregMeBusClockEnable371", 'i', "", HLE_KERNEL_SYSCALL },
+ // 6.60 and 6.61, which popsman.prx uses.
+ {0x457FEBA9, &WrapI_V, "sceSysregMeResetEnable660", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x48F1C4AD, &WrapI_V, "sceSysregMeResetDisable660", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x38527743, &WrapI_V, "sceSysregMeBusClockEnable660", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x0AE8E549, &WrapI_V, "sceSysregAvcResetEnable660", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x8B1DD83A, &WrapI_I, "sceSysreg_driver_8B1DD83A", 'i', "i", HLE_KERNEL_SYSCALL },
+ {0xB1751B06, &WrapI_I, "sceSysreg_driver_B1751B06", 'i', "i", HLE_KERNEL_SYSCALL },
+ {0x4634C9DC, &WrapI_I, "sceSysreg_driver_4634C9DC", 'i', "i", HLE_KERNEL_SYSCALL },
+ {0x4B4CCE80, &WrapI_V, "sceSysreg_driver_4B4CCE80", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x41B0337B, &WrapI_I, "sceSysreg_driver_41B0337B", 'i', "i", HLE_KERNEL_SYSCALL },
+ {0xC36775AD, &WrapI_II, "sceSysreg_driver_C36775AD", 'i', "ii", HLE_KERNEL_SYSCALL },
};
// Custom-core homebrew (mcidclan's libraries) walks this to find me_wrapper's "SceMeRpc" handler,
diff --git a/Core/HW/MMIO.cpp b/Core/HW/MMIO.cpp
index 9f0651755a..d2e06770a1 100644
--- a/Core/HW/MMIO.cpp
+++ b/Core/HW/MMIO.cpp
@@ -21,6 +21,7 @@
#include "Common/Log.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
+#include "Core/CoreTiming.h"
#include "Core/HW/GpioMMIO.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/MeMMIO.h"
@@ -37,6 +38,9 @@ public:
bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
if (offset + size > Memory::BOOT_PAGE_SIZE)
return false;
+ // What the ME has written by now: a main CPU polling a mailbox here sees it as on a PSP.
+ if (mips == currentMIPS)
+ CoreTiming::CatchUpOtherCPUs(mips);
const u8 *ptr = Memory::GetBootPage() + offset;
switch (size) {
case 1: *value = *ptr; break;
@@ -87,6 +91,43 @@ public:
static BootPageDevice g_bootPage;
+// Kernel code reaches RAM and VRAM uncached at 0xA8000000 and 0xA4000000 (Yabause's me.prx does, by
+// ORing 0xA0000000 onto its own addresses). The main CPU's space has no views there, so it goes
+// through the uncached mirrors at 0x40000000 instead. The Media Engine's space maps them.
+class KernelUncachedDevice : public MMIODevice {
+public:
+ KernelUncachedDevice() : MMIODevice("kseg1 memory", 0xA0000000, 0x1C000000) {}
+
+ bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
+ const u32 address = 0x40000000 + offset;
+ if ((mips && mips->IsMediaEngine()) || !Memory::IsValidRange(address, size))
+ return false;
+ switch (size) {
+ case 1: *value = Memory::ReadUnchecked_U8(address); break;
+ case 2: *value = Memory::ReadUnchecked_U16(address); break;
+ default: *value = Memory::ReadUnchecked_U32(address); break;
+ }
+ return true;
+ }
+
+ bool Write(MIPSState *mips, u32 offset, int size, u32 value) override {
+ const u32 address = 0x40000000 + offset;
+ if ((mips && mips->IsMediaEngine()) || !Memory::IsValidRange(address, size))
+ return false;
+ switch (size) {
+ case 1: Memory::WriteUnchecked_U8((u8)value, address); break;
+ case 2: Memory::WriteUnchecked_U16((u16)value, address); break;
+ default: Memory::WriteUnchecked_U32(value, address); break;
+ }
+ return true;
+ }
+
+ void Reset() override {}
+ void DoState(PointerWrap &p) override {}
+};
+
+static KernelUncachedDevice g_kernelUncached;
+
static std::vector g_devices;
void Init() {
@@ -101,6 +142,8 @@ void Init() {
MeMMIO::VmeControlDevice(),
MeMMIO::ClockDevice(),
MeMMIO::DdrDevice(),
+ MeMMIO::AudioDevice(),
+ &g_kernelUncached,
};
for (MMIODevice *device : g_devices) {
device->Reset();
diff --git a/Core/HW/MeMMIO.cpp b/Core/HW/MeMMIO.cpp
index 7aca8d16e4..bf3cf36e39 100644
--- a/Core/HW/MeMMIO.cpp
+++ b/Core/HW/MeMMIO.cpp
@@ -20,6 +20,7 @@
#include
#include "Common/Log.h"
+#include "Core/CoreTiming.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/HLE/sceDisplay.h"
#include "Core/HLE/sceMpegbase.h"
@@ -117,7 +118,7 @@ private:
case 0x54: return &g_meState->sysreg54;
case 0x5C: return &g_meState->sysreg5C;
case 0x70: return &g_meState->sysreg70;
- default: return nullptr;
+ default: return (offset & 3) ? nullptr : &g_meState->sysregOther[offset / 4];
}
}
};
@@ -514,6 +515,94 @@ public:
void DoState(PointerWrap &p) override {}
};
+// The audio block (0xBE000000), as POPS's ME loop (popsman.prx +2F88) drives it: it sets the block
+// up, then writes one stereo sample (left in the low half) to 0x70 whenever status 0x28 bit 5 says
+// the FIFO has room, sleeping in halt in between on interrupt source 12. As measured from the ME on
+// a PSP-1000 (pspautotest me/audiofifo): the FIFO holds 24 samples and plays them at 44.1kHz while
+// 0x04 bit 1 is set; status 0x28 has bit 4 while playing, bit 5 while there's room, and bit 1 once
+// it has played out its last sample, which comes a sample after the FIFO is empty; and source 12 is a level, up while there's room, which halt
+// wakes on with interrupts disabled.
+class MeAudioDevice : public MMIODevice {
+public:
+ MeAudioDevice() : MMIODevice("audio", 0xBE000000, 0x80) {}
+
+ bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
+ if (!g_meState || (offset & 3))
+ return false;
+ MeMMIO::AudioUpdate(MeCore::GetTicks());
+ const MeCore::MEState::Audio &a = g_meState->audio;
+ switch (offset) {
+ case REG_STATUS: *value = 0; break;
+ case REG_FIFO_STATUS: *value = FifoStatus(a); break;
+ default: *value = a.regs[offset / 4]; break;
+ }
+ return true;
+ }
+
+ bool Write(MIPSState *mips, u32 offset, int size, u32 value) override {
+ if (!g_meState || (offset & 3))
+ return false;
+ const s64 now = MeCore::GetTicks();
+ MeMMIO::AudioUpdate(now);
+ MeCore::MEState::Audio &a = g_meState->audio;
+ if (offset != REG_DATA) {
+ a.regs[offset / 4] = value;
+ return true;
+ }
+ if (a.fifoCount == FIFO_DEPTH) {
+ WARN_LOG(Log::ME, "Audio FIFO overflow");
+ return true;
+ }
+ if (a.fifoCount == 0)
+ a.nextPlayed = now + SamplePeriod();
+ a.fifo[a.fifoCount++] = value;
+ a.underrun = false;
+ a.lastPlayed = INT64_MAX;
+ MeMMIO::AudioUpdate(now);
+ return true;
+ }
+
+ void Reset() override {}
+ void DoState(PointerWrap &p) override {}
+
+ static constexpr u32 FIFO_DEPTH = ARRAY_SIZE(MeCore::MEState::Audio::fifo);
+ static constexpr u32 REG_CONTROL = 0x04;
+ static constexpr u32 REG_STATUS = 0x0C;
+ static constexpr u32 REG_FIFO_STATUS = 0x28;
+ static constexpr u32 REG_DATA = 0x70;
+ static constexpr u32 CONTROL_PLAY = 2;
+ static constexpr u32 FIFO_ROOM = 0x20;
+ static constexpr u32 FIFO_PLAYING = 0x10;
+ static constexpr u32 FIFO_UNDERRUN = 0x02;
+ static constexpr u32 INTR_AUDIO = 1 << 12;
+
+ static s64 SamplePeriod() {
+ return CoreTiming::GetClockFrequencyHz() / 44100;
+ }
+
+ static bool Playing(const MeCore::MEState::Audio &a) {
+ return (a.regs[REG_CONTROL / 4] & CONTROL_PLAY) != 0;
+ }
+
+ static u32 FifoStatus(const MeCore::MEState::Audio &a) {
+ u32 status = a.fifoCount < FIFO_DEPTH ? FIFO_ROOM : 0;
+ if (Playing(a))
+ status |= FIFO_PLAYING;
+ if (a.underrun)
+ status |= FIFO_UNDERRUN;
+ return status;
+ }
+};
+
+// Played samples on their way to the host's mix. Not saved: a load starts with silence.
+static std::vector g_audioPlayed;
+static size_t g_audioPlayedRead = 0;
+// The ME plays in bursts, a PS1 frame at a time: keep a little more than a frame queued.
+static const size_t AUDIO_MIX_PRIME = 1536;
+static const size_t AUDIO_MIX_MAX = 8192;
+static bool g_audioMixPrimed = false;
+
+static MeAudioDevice g_audio;
static MeDdrDevice g_ddr;
static MeClockDevice g_clocks;
static MeVmeControlDevice g_vmeControl;
@@ -550,4 +639,57 @@ MMIODevice *DdrDevice() {
return &g_ddr;
}
+MMIODevice *AudioDevice() {
+ return &g_audio;
+}
+
+void AudioUpdate(s64 now) {
+ MeCore::MEState::Audio &a = g_meState->audio;
+ const bool playing = MeAudioDevice::Playing(a);
+ while (playing && a.fifoCount > 0 && a.nextPlayed <= now) {
+ if (g_audioPlayed.size() - g_audioPlayedRead < AUDIO_MIX_MAX * 2)
+ g_audioPlayed.push_back(a.fifo[0]);
+ memmove(a.fifo, a.fifo + 1, (a.fifoCount - 1) * sizeof(u32));
+ a.fifoCount--;
+ a.nextPlayed += MeAudioDevice::SamplePeriod();
+ // The sample leaving the FIFO still has to play: it ran dry a sample later.
+ a.lastPlayed = a.nextPlayed;
+ }
+ if (playing && a.fifoCount == 0 && a.lastPlayed <= now)
+ a.underrun = true;
+ if (playing && a.fifoCount < MeAudioDevice::FIFO_DEPTH)
+ g_meState->intcRaised |= MeAudioDevice::INTR_AUDIO;
+ else
+ g_meState->intcRaised &= ~MeAudioDevice::INTR_AUDIO;
+}
+
+s64 AudioNextEvent() {
+ const MeCore::MEState::Audio &a = g_meState->audio;
+ return MeAudioDevice::Playing(a) && a.fifoCount == MeAudioDevice::FIFO_DEPTH ? a.nextPlayed : INT64_MAX;
+}
+
+void AudioMix(s32 *mix, int count) {
+ size_t queued = g_audioPlayed.size() - g_audioPlayedRead;
+ if (!g_audioMixPrimed) {
+ if (queued < AUDIO_MIX_PRIME)
+ return;
+ g_audioMixPrimed = true;
+ }
+ if (queued > AUDIO_MIX_MAX) {
+ g_audioPlayedRead = g_audioPlayed.size() - AUDIO_MIX_PRIME;
+ queued = AUDIO_MIX_PRIME;
+ }
+ if (queued < (size_t)count)
+ g_audioMixPrimed = false;
+ for (int i = 0; i < count && g_audioPlayedRead < g_audioPlayed.size(); i++) {
+ const u32 sample = g_audioPlayed[g_audioPlayedRead++];
+ mix[i * 2] += (s16)(sample & 0xFFFF);
+ mix[i * 2 + 1] += (s16)(sample >> 16);
+ }
+ if (g_audioPlayedRead > AUDIO_MIX_MAX * 4) {
+ g_audioPlayed.erase(g_audioPlayed.begin(), g_audioPlayed.begin() + g_audioPlayedRead);
+ g_audioPlayedRead = 0;
+ }
+}
+
} // namespace MeMMIO
diff --git a/Core/HW/MeMMIO.h b/Core/HW/MeMMIO.h
index 56a47ebff8..e4d3555de8 100644
--- a/Core/HW/MeMMIO.h
+++ b/Core/HW/MeMMIO.h
@@ -38,5 +38,13 @@ MMIODevice *VmeControlDevice();
MMIODevice *MemProtectDevice();
// The DDR controller's flush register (0xBD000004), always done.
MMIODevice *DdrDevice();
+// The audio block (0xBE000000), which the ME feeds in a POPS boot.
+MMIODevice *AudioDevice();
+// Plays what's due by the ME's clock, and sets the ME's audio interrupt level.
+void AudioUpdate(s64 now);
+// When a full FIFO next has room, in ME ticks, or INT64_MAX.
+s64 AudioNextEvent();
+// Adds up to count of the samples it played to a stereo mix.
+void AudioMix(s32 *mix, int count);
} // namespace MeMMIO
diff --git a/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp b/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp
index 0301f21d20..3ba1d2f2ce 100644
--- a/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp
+++ b/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp
@@ -411,7 +411,8 @@ void Arm64JitBackend::CompIR_LoadShift(IRInst inst) {
}
// Memory directly, or through the fixed code for a hardware register (0xBC000000-0xBFFFFFFF), where
-// the address may also be one the CPU has no memory at.
+// the address may also be one the CPU has no memory at. The main CPU has no views anywhere in kernel
+// segment 1 (0xA0000000-0xBFFFFFFF), so all of it goes there (see Memory::IsMMIOAccess).
void Arm64JitBackend::CompIR_KernelLoadStore(IRInst inst) {
CONDITIONAL_DISABLE;
@@ -420,8 +421,13 @@ void Arm64JitBackend::CompIR_KernelLoadStore(IRInst inst) {
ADDI2R(SCRATCH1, regs_.R(inst.src1), inst.constant, SCRATCH2);
const ARM64Reg reg = isStore ? regs_.R(inst.src3) : regs_.R(inst.dest);
- LSR(SCRATCH2, SCRATCH1, 26);
- CMPI2R(SCRATCH2, 0xBC000000 >> 26);
+ if (mips_->IsMediaEngine()) {
+ LSR(SCRATCH2, SCRATCH1, 26);
+ CMPI2R(SCRATCH2, 0xBC000000 >> 26);
+ } else {
+ LSR(SCRATCH2, SCRATCH1, 29);
+ CMPI2R(SCRATCH2, 0xA0000000 >> 29);
+ }
FixupBranch memory = B(CC_NEQ);
if (isStore)
MOV(SCRATCH2, reg);
diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp
index dd196c0af6..3c7344303c 100644
--- a/Core/MIPS/IR/IRCompBranch.cpp
+++ b/Core/MIPS/IR/IRCompBranch.cpp
@@ -24,6 +24,7 @@
#include "Core/HLE/HLETables.h"
#include "Core/MIPS/MIPS.h"
+#include "Core/MIPS/MeCore.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSTables.h"
@@ -464,8 +465,21 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) {
void IRFrontend::Comp_Syscall(MIPSOpcode op) {
if (IsMediaEngine()) {
- // There's no HLE on the ME, the interpreter traps it.
- Comp_Generic(op);
+ // There's no HLE on the ME. A call to a stub that does nothing there returns 0, ending the
+ // block as a syscall does on the main CPU (a jr with it in its delay slot leaves the exit to
+ // it); the interpreter traps the rest.
+ if (!MeCore::SyscallDoesNothing(op, GetCompilerPC())) {
+ Comp_Generic(op);
+ return;
+ }
+ ir.Write(IROp::Downcount, 0, 0, 0, js.downcountAmount);
+ js.downcountAmount = 0;
+ if (!js.inDelaySlot)
+ ir.Write(IROp::SetPCConst, 0, 0, 0, GetCompilerPC() + 4);
+ ir.WriteSetConstant(MIPS_REG_V0, 0);
+ FlushAll();
+ ir.Write(IROp::ExitToPC);
+ js.compiling = false;
return;
}
// Like the interpreter, the syscall sees every earlier instruction charged (including a branch it's
diff --git a/Core/MIPS/Interpreter.cpp b/Core/MIPS/Interpreter.cpp
index 92cd84ecaa..9b8fe9b630 100644
--- a/Core/MIPS/Interpreter.cpp
+++ b/Core/MIPS/Interpreter.cpp
@@ -183,9 +183,18 @@ namespace MIPSInt {
void Int_Syscall(MIPSState *mips, MIPSOpcode op) {
if (mips->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);
+ // TODO: Its exception vector.
+ if (!MeCore::SyscallDoesNothing(op, mips->pc)) {
+ Core_ExecException(mips->pc, mips->pc, ExecExceptionType::ILLEGAL);
+ return;
+ }
+ mips->r[MIPS_REG_V0] = 0;
+ if (mips->inDelaySlot) {
+ mips->pc = mips->nextPC;
+ } else {
+ mips->pc += 4;
+ }
+ mips->inDelaySlot = false;
return;
}
const u32 syscallPC = mips->pc - 4;
diff --git a/Core/MIPS/MeCore.cpp b/Core/MIPS/MeCore.cpp
index 94edfe7fcd..367dd299a5 100644
--- a/Core/MIPS/MeCore.cpp
+++ b/Core/MIPS/MeCore.cpp
@@ -27,8 +27,10 @@
#include "Core/CoreTiming.h"
#include "Core/ELF/PrxDecrypter.h"
#include "Core/FileSystems/MetaFileSystem.h"
+#include "Core/HLE/HLE.h"
#include "Core/HLE/sceKernelInterrupt.h"
#include "Core/HLE/sceKernelMemory.h"
+#include "Core/HW/MeMMIO.h"
#include "Core/MemMap.h"
#include "Core/System.h"
#include "Core/MIPS/MeCore.h"
@@ -318,6 +320,13 @@ void DoState(PointerWrap &p) {
Do(p, me->sysreg5C);
Do(p, me->sysreg70);
Do(p, me->booting);
+ DoArray(p, me->sysregOther, ARRAY_SIZE(me->sysregOther));
+ DoArray(p, me->audio.regs, ARRAY_SIZE(me->audio.regs));
+ DoArray(p, me->audio.fifo, ARRAY_SIZE(me->audio.fifo));
+ Do(p, me->audio.fifoCount);
+ Do(p, me->audio.underrun);
+ Do(p, me->audio.lastPlayed);
+ Do(p, me->audio.nextPlayed);
DoArray(p, me->memProtect, ARRAY_SIZE(me->memProtect));
DoArray(p, me->vmeControl, ARRAY_SIZE(me->vmeControl));
DoArray(p, me->clocks, ARRAY_SIZE(me->clocks));
@@ -513,10 +522,16 @@ void RunFor(int cycles) {
// The main CPU may have given a waiting loop what it waits for.
me->waiting = false;
while (cpu.downcount >= 0 && coreState == CORE_RUNNING_CPU) {
+ MeMMIO::AudioUpdate(GetTicks());
CheckInterrupts(me);
if (!me->halted && !me->waiting)
me->waiting = WaitsForMainCPU(me);
- if (me->halted || me->waiting) {
+ if (me->halted) {
+ // Asleep until the audio FIFO next has room, if that's within this slice: POPS's ME
+ // loop sleeps for each sample it plays.
+ const s64 wake = MeMMIO::AudioNextEvent();
+ cpu.downcount = wake <= me->tickBase ? (int)(me->tickBase - wake) : -1;
+ } else if (me->waiting) {
cpu.downcount = -1;
} else {
// Run a part, with the rest set aside so the clock stays right.
@@ -551,6 +566,25 @@ static void RunUntilIdle(MEState *me) {
}
}
+bool SyscallDoesNothing(MIPSOpcode op, u32 pc) {
+ static const char *const cacheFunctions[] = {
+ "sceKernelDcacheWritebackAll",
+ "sceKernelDcacheWritebackInvalidateAll",
+ "sceKernelDcacheWritebackRange",
+ "sceKernelDcacheWritebackInvalidateRange",
+ "sceKernelDcacheInvalidateRange",
+ };
+ const HLEFunction *func = GetSyscallFunctionData(op, pc);
+ if (!func)
+ return true;
+ for (const char *name : cacheFunctions) {
+ if (!strcmp(func->name, name))
+ return true;
+ }
+ ERROR_LOG(Log::ME, "syscall to %s on the Media Engine at %08x", func->name, pc);
+ return false;
+}
+
void SetReset(bool asserted) {
MEState *me = g_meState;
if (asserted) {
diff --git a/Core/MIPS/MeCore.h b/Core/MIPS/MeCore.h
index 790f477c09..7fa72971ae 100644
--- a/Core/MIPS/MeCore.h
+++ b/Core/MIPS/MeCore.h
@@ -60,6 +60,7 @@ struct MEState {
u32 sysreg54 = 0; // 0x54. Sony's ME firmware clears bit 0. Unknown.
u32 sysreg5C = 0; // 0x5C. Sony's ME firmware sets bit 1. Unknown.
u32 sysreg70 = 0; // 0x70. MIST's meEasyShot sets bits 0 and 2. Unknown.
+ u32 sysregOther[0x40]{}; // The rest, kept: clock and IO enables (0x58, 0x60, 0x6C, 0x78...).
// Sony's ME firmware still has to boot to its idle halt (RunUntilIdle).
bool booting = false;
u32 memProtect[20]{}; // 0xBC000000-0xBC00004C
@@ -75,6 +76,21 @@ struct MEState {
u32 channels[4][5]{}; // 0x180-0x1FF: source, destination, next LLI, control, configuration
} dmacplus;
+ // The audio block (0xBE000000), which the ME feeds directly in a POPS boot: a FIFO of stereo
+ // samples (0x70) that plays at 44.1kHz. Only what POPS's ME loop uses.
+ struct Audio {
+ u32 regs[0x20]{};
+ u32 fifo[24]{};
+ u32 fifoCount = 0;
+ // It played out its last sample. Status 0x28 bit 1, until the next one is written.
+ bool underrun = false;
+ // When the last sample to leave the FIFO has played, if it's empty. INT64_MAX until one has
+ // left it since the last write.
+ s64 lastPlayed = INT64_MAX;
+ // When the oldest sample in the FIFO has played, in ME ticks.
+ s64 nextPlayed = 0;
+ } audio;
+
// The sysevent handler list sceKernelReferSysEventHandler returns, in kernel memory.
u32 sysEventHandlers = 0;
@@ -111,6 +127,11 @@ u64 GetTicks();
u32 ReadCop0(int reg);
void WriteCop0(int reg, u32 value);
void Halt();
+// Whether a syscall the ME makes is to a firmware function it may call, all of which do nothing on
+// it: kernel code it runs (POPS's ME loop) calls the cache functions, which only work on the calling
+// CPU's caches, and those aren't emulated. An unresolved import does nothing, as on the main CPU.
+// Logs the others.
+bool SyscallDoesNothing(MIPSOpcode op, u32 pc);
// mtic.
void SetInterruptEnable(u32 value);
diff --git a/Core/MIPS/x86/X64IRCompLoadStore.cpp b/Core/MIPS/x86/X64IRCompLoadStore.cpp
index 61d2842834..e1d1a7bb2a 100644
--- a/Core/MIPS/x86/X64IRCompLoadStore.cpp
+++ b/Core/MIPS/x86/X64IRCompLoadStore.cpp
@@ -89,7 +89,8 @@ Gen::OpArg X64JitBackend::PrepareSrc1Address(IRInst inst) {
#if PPSSPP_ARCH(AMD64)
// Memory directly, or through the fixed code for a hardware register (0xBC000000-0xBFFFFFFF), where
-// the address may also be one the CPU has no memory at.
+// the address may also be one the CPU has no memory at. The main CPU has no views anywhere in kernel
+// segment 1 (0xA0000000-0xBFFFFFFF), so all of it goes there (see Memory::IsMMIOAccess).
void X64JitBackend::CompIR_KernelLoadStore(IRInst inst) {
CONDITIONAL_DISABLE;
@@ -98,7 +99,7 @@ void X64JitBackend::CompIR_KernelLoadStore(IRInst inst) {
LEA(32, SCRATCH1, MDisp(regs_.RX(inst.src1), inst.constant));
const X64Reg reg = isStore ? regs_.RX(inst.src3) : regs_.RX(inst.dest);
- CMP(32, R(SCRATCH1), Imm32(0xBC000000));
+ CMP(32, R(SCRATCH1), Imm32(mips_->IsMediaEngine() ? 0xBC000000 : 0xA0000000));
FixupBranch below = J_CC(CC_B);
CMP(32, R(SCRATCH1), Imm32(0xC0000000));
FixupBranch above = J_CC(CC_AE);
diff --git a/Core/MemMap.h b/Core/MemMap.h
index be2bea54a2..63307b937c 100644
--- a/Core/MemMap.h
+++ b/Core/MemMap.h
@@ -409,9 +409,11 @@ inline bool IsDepthTexVRAMAddress(const u32 address) {
return ((address & 0xBFE00000) == 0x04200000) || ((address & 0xBFE00000) == 0x04600000);
}
-// TODO: To re-evaluate.
+// Kernel segment 1 (0xA0000000-0xBFFFFFFF): the hardware registers from 0xBC000000, and below them
+// uncached RAM and VRAM, which only the Media Engine's space maps. Kernel code reaches the rest
+// through MMIO::Read/Write.
inline bool IsMMIOAccess(const u32 address) {
- return ((address & 0xFC000000) == 0xBC000000);
+ return (address & 0xE0000000) == 0xA0000000;
}
// 0x08000000 -> 0x08800000
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj
index ea06a733ed..ec65fce10b 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj
+++ b/UWP/CoreUWP/CoreUWP.vcxproj
@@ -184,6 +184,7 @@
+
@@ -484,6 +485,7 @@
+
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters
index 8f36ee64f4..1213e96991 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj.filters
+++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters
@@ -92,6 +92,7 @@
+
@@ -510,6 +511,7 @@
+
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 5a7db3140c..9b670d77c3 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -753,6 +753,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/HLE/SocketManager.cpp \
$(SRC)/Core/HLE/Plugins.cpp \
$(SRC)/Core/HLE/sceAdler.cpp \
+ $(SRC)/Core/HLE/sceAmctrl.cpp \
$(SRC)/Core/HLE/sceAtrac.cpp \
$(SRC)/Core/HLE/AtracCtx.cpp \
$(SRC)/Core/HLE/AtracCtx2.cpp \
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index a65a8f875c..793294ec1c 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -769,6 +769,7 @@ SOURCES_CXX += \
$(COREDIR)/HLE/AtracCtx.cpp \
$(COREDIR)/HLE/AtracCtx2.cpp \
$(COREDIR)/HLE/sceAdler.cpp \
+ $(COREDIR)/HLE/sceAmctrl.cpp \
$(COREDIR)/HLE/sceAtrac.cpp \
$(COREDIR)/HLE/sceAudio.cpp \
$(COREDIR)/HLE/sceAudiocodec.cpp \
diff --git a/test.py b/test.py
index e816220c14..730444c6ab 100755
--- a/test.py
+++ b/test.py
@@ -632,6 +632,7 @@ tests_next = [
"gpu/signals/simple",
"gpu/simple/simple",
"intr/waits",
+ "me/audiofifo", # needs the Media Engine on, and Sony's ME firmware installed
"me/vram", # needs the Media Engine on, and Sony's ME firmware installed
"sysmem/kernel/heapgrow",
"io/file/file",