diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt
index 2770aee2dd..556d938586 100644
--- a/Core/CMakeLists.txt
+++ b/Core/CMakeLists.txt
@@ -387,6 +387,8 @@ add_library(Core STATIC
HLE/PSPThreadContext.h
HLE/KUBridge.h
HLE/KUBridge.cpp
+ HLE/sceSysreg.h
+ HLE/sceSysreg.cpp
HLE/Plugins.h
HLE/Plugins.cpp
HLE/ThreadQueueList.h
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 2348b5a217..fff3741a09 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -474,6 +474,7 @@
+
@@ -848,6 +849,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 70a1be4ee9..f3fd104aae 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -673,6 +673,9 @@
HLE\Libraries
+
+ HLE\Libraries
+
FileSystems
@@ -2010,6 +2013,9 @@
HLE\Libraries
+
+ HLE\Libraries
+
FileSystems
diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp
index 38ef766c76..36b8033ccc 100644
--- a/Core/HLE/HLETables.cpp
+++ b/Core/HLE/HLETables.cpp
@@ -92,6 +92,7 @@
// #include "sceNp2.h"
#include "sceNet_lib.h"
#include "sceVideocodec.h"
+#include "sceSysreg.h"
static const HLEFunction FakeSysCalls[] = {
{NID_THREADRETURN, __KernelReturnFromThread, "__KernelReturnFromThread", 'x', ""},
@@ -356,6 +357,7 @@ void RegisterAllModules() {
Register_sceMePower_driver();
Register_SystemCtrlForUser();
Register_ExceptionManagerForKernel();
+ Register_sceSysreg_driver();
// Not ready to enable this due to apparent softlocks in Patapon 3.
// Register_sceNpMatching2();
diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp
index 7b05326d93..15c9167073 100644
--- a/Core/HLE/ReplaceTables.cpp
+++ b/Core/HLE/ReplaceTables.cpp
@@ -120,33 +120,6 @@ 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);
@@ -163,7 +136,7 @@ static int Replace_memcpy(MIPSState *mips) {
}
}
gpu->NotifyVideoCopy(destPtr, srcPtr, bytes);
- if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) {
+ if (!skip && bytes != 0 && !MMIO::Copy(mips, destPtr, srcPtr, bytes)) {
u8 *dst = Memory::GetPointerWriteRangeOrException(destPtr, bytes);
const u8 *src = Memory::GetPointerRangeOrException(srcPtr, bytes);
@@ -293,7 +266,7 @@ static int Replace_memcpy16(MIPSState *mips) {
}
}
gpu->NotifyVideoCopy(destPtr, srcPtr, bytes);
- if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) {
+ if (!skip && bytes != 0 && !MMIO::Copy(mips, destPtr, srcPtr, bytes)) {
u8 *dst = Memory::GetPointerWriteRangeOrException(destPtr, bytes);
const u8 *src = Memory::GetPointerRangeOrException(srcPtr, bytes);
if (dst && src) {
@@ -360,7 +333,7 @@ static int Replace_memmove(MIPSState *mips) {
}
}
gpu->NotifyVideoCopy(destPtr, srcPtr, bytes);
- if (!skip && bytes != 0 && !CopyThroughMMIO(mips, destPtr, srcPtr, bytes)) {
+ if (!skip && bytes != 0 && !MMIO::Copy(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 2d85e89970..95db13592f 100644
--- a/Core/HLE/sceKernelInterrupt.cpp
+++ b/Core/HLE/sceKernelInterrupt.cpp
@@ -31,6 +31,7 @@
#include "Core/HLE/ErrorCodes.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MeCore.h"
+#include "Core/HW/MMIO.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/HLE/sceKernel.h"
@@ -812,8 +813,10 @@ const HLEFunction Kernel_Library[] =
{0X4AC57943, &WrapI_I, "sceKernelRegisterExitCallback", 'i', "i" },
};
-static u32 sysclib_memcpy(u32 dst, u32 src, u32 size) {
- if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) {
+static u32 sysclib_memcpy(MIPSState *mips, u32 dst, u32 src, u32 size) {
+ if (MMIO::Copy(mips, dst, src, size)) {
+ // To or from hardware registers.
+ } else if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) {
memcpy(Memory::GetPointerWriteUnchecked(dst), Memory::GetPointerUnchecked(src), size);
}
if (MemBlockInfoDetailed(size)) {
@@ -957,8 +960,10 @@ static int sysclib_strncmp(u32 s1, u32 s2, u32 size) {
return hleLogError(Log::sceKernel, 0, "Bad addresses");
}
-static u32 sysclib_memmove(u32 dst, u32 src, u32 size) {
- if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) {
+static u32 sysclib_memmove(MIPSState *mips, u32 dst, u32 src, u32 size) {
+ if (MMIO::Copy(mips, dst, src, size)) {
+ // To or from hardware registers.
+ } else if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) {
memmove(Memory::GetPointerWriteUnchecked(dst), Memory::GetPointerUnchecked(src), size);
}
if (MemBlockInfoDetailed(size)) {
diff --git a/Core/HLE/sceSysreg.cpp b/Core/HLE/sceSysreg.cpp
new file mode 100644
index 0000000000..ba639bcdb1
--- /dev/null
+++ b/Core/HLE/sceSysreg.cpp
@@ -0,0 +1,96 @@
+// 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 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.
+
+#include "Common/CommonTypes.h"
+#include "Core/HLE/HLE.h"
+#include "Core/HLE/FunctionWrappers.h"
+#include "Core/HLE/sceSysreg.h"
+#include "Core/HW/MMIO.h"
+#include "Core/MIPS/MIPS.h"
+
+enum : u32 {
+ SYSREG_RESET = 0xBC10004C,
+ SYSREG_BUS_CLOCK = 0xBC100050,
+
+ RESET_ME = 0x04,
+ RESET_VME = 0x10,
+ RESET_AVC = 0x20,
+ BUS_CLOCK_ME = 0x01,
+};
+
+static int SetSysregBit(MIPSState *mips, u32 reg, u32 bit, bool set) {
+ const u32 old = MMIO::Read(mips, reg, 4);
+ MMIO::Write(mips, reg, 4, set ? (old | bit) : (old & ~bit));
+ return (old & bit) ? 1 : 0;
+}
+
+static int sceSysregMeResetEnable(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_ME, true));
+}
+
+static int sceSysregMeResetDisable(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_ME, false));
+}
+
+static int sceSysregMeBusClockEnable(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_BUS_CLOCK, BUS_CLOCK_ME, true));
+}
+
+static int sceSysregMeBusClockDisable(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_BUS_CLOCK, BUS_CLOCK_ME, false));
+}
+
+static int sceSysregVmeResetEnable(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_VME, true));
+}
+
+static int sceSysregVmeResetDisable(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_VME, false));
+}
+
+static int sceSysregAvcResetEnable(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_AVC, true));
+}
+
+static int sceSysregAvcResetDisable(MIPSState *mips) {
+ return hleLogDebug(Log::ME, SetSysregBit(mips, SYSREG_RESET, RESET_AVC, false));
+}
+
+const HLEFunction sceSysreg_driver[] = {
+ {0xDE59DACB, &WrapI_V, "sceSysregMeResetEnable", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x2DB0EB28, &WrapI_V, "sceSysregMeResetDisable", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x44F6CDA7, &WrapI_V, "sceSysregMeBusClockEnable", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x158AD4FC, &WrapI_V, "sceSysregMeBusClockDisable", 'i', "", HLE_KERNEL_SYSCALL },
+ {0xD20581EA, &WrapI_V, "sceSysregVmeResetEnable", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x7558064A, &WrapI_V, "sceSysregVmeResetDisable", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x9BB70D34, &WrapI_V, "sceSysregAvcResetEnable", 'i', "", HLE_KERNEL_SYSCALL },
+ {0xFD6C562B, &WrapI_V, "sceSysregAvcResetDisable", 'i', "", HLE_KERNEL_SYSCALL },
+ // 3.71 and later.
+ {0xA9997109, &WrapI_V, "sceSysregMeResetEnable371", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x76220E94, &WrapI_V, "sceSysregMeResetDisable371", 'i', "", HLE_KERNEL_SYSCALL },
+ {0x3199CF1C, &WrapI_V, "sceSysregMeBusClockEnable371", 'i', "", HLE_KERNEL_SYSCALL },
+};
+
+void Register_sceSysreg_driver() {
+ RegisterHLEModule("sceSysreg_driver", ARRAY_SIZE(sceSysreg_driver), sceSysreg_driver);
+}
diff --git a/Core/HLE/sceSysreg.h b/Core/HLE/sceSysreg.h
new file mode 100644
index 0000000000..053de1e933
--- /dev/null
+++ b/Core/HLE/sceSysreg.h
@@ -0,0 +1,20 @@
+// 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/.
+
+#pragma once
+
+void Register_sceSysreg_driver();
diff --git a/Core/HW/MMIO.cpp b/Core/HW/MMIO.cpp
index cad553324f..4e976d3e87 100644
--- a/Core/HW/MMIO.cpp
+++ b/Core/HW/MMIO.cpp
@@ -108,6 +108,30 @@ void Shutdown() {
g_devices.clear();
}
+bool Copy(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)Read(mips, src, 1);
+ } else if (Memory::IsValidAddress(src)) {
+ value = Memory::ReadUnchecked_U8(src);
+ }
+ if (Memory::IsMMIOAccess(dest)) {
+ Write(mips, dest, 1, value);
+ } else if (Memory::IsValidAddress(dest)) {
+ Memory::WriteUnchecked_U8(value, dest);
+ }
+ }
+ return true;
+}
+
void DoState(PointerWrap &p) {
auto s = p.Section("MMIO", 1, 2);
if (!s)
diff --git a/Core/HW/MMIO.h b/Core/HW/MMIO.h
index 950f9bc3b5..97e1d5f8a4 100644
--- a/Core/HW/MMIO.h
+++ b/Core/HW/MMIO.h
@@ -65,5 +65,9 @@ void DoState(PointerWrap &p);
// or 4. An access no device handles is logged, and reads as UNHANDLED_READ.
u32 Read(MIPSState *mips, u32 address, int size);
void Write(MIPSState *mips, u32 address, int size, u32 value);
+// A memory copy where either end may be hardware registers: kernel code copies Media Engine code
+// to the boot page at 0xBFC00000, which the main CPU reaches through MMIO. Byte by byte, and
+// backward where memmove would be. Returns false, having done nothing, if neither end is MMIO.
+bool Copy(MIPSState *mips, u32 destPtr, u32 srcPtr, u32 bytes);
} // namespace MMIO
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj
index a631a7493e..ea06a733ed 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj
+++ b/UWP/CoreUWP/CoreUWP.vcxproj
@@ -179,6 +179,7 @@
+
@@ -478,6 +479,7 @@
+
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters
index 530f196a30..8f36ee64f4 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj.filters
+++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters
@@ -87,6 +87,7 @@
+
@@ -504,6 +505,7 @@
+
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 4554ccc1f4..5a7db3140c 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -748,6 +748,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/HLE/ReplaceTables.cpp \
$(SRC)/Core/HLE/HLE.cpp \
$(SRC)/Core/HLE/KUBridge.cpp \
+ $(SRC)/Core/HLE/sceSysreg.cpp \
$(SRC)/Core/HLE/NetInetConstants.cpp \
$(SRC)/Core/HLE/SocketManager.cpp \
$(SRC)/Core/HLE/Plugins.cpp \
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index 4ef0d59edd..a65a8f875c 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -755,6 +755,7 @@ SOURCES_CXX += \
$(COREDIR)/Font/PGF.cpp \
$(COREDIR)/HLE/HLE.cpp \
$(COREDIR)/HLE/KUBridge.cpp \
+ $(COREDIR)/HLE/sceSysreg.cpp \
$(COREDIR)/HLE/NetInetConstants.cpp \
$(COREDIR)/HLE/SocketManager.cpp \
$(COREDIR)/HLE/Plugins.cpp \