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",