Merge pull request #22705 from hrydgard/pops-prep

Media Engine part 6: The audio block, and what the POPS manager needs from the kernel
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-10-21 13:14:52 +02:00
commit 4f80c08fc9
26 files changed
+530 -20

No files matched your search

+2
View File
@@ -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
+2
View File
@@ -608,6 +608,7 @@
<ClCompile Include="HLE\proAdhocServer.cpp" />
<ClCompile Include="HLE\ReplaceTables.cpp" />
<ClCompile Include="HLE\sceAdler.cpp" />
<ClCompile Include="HLE\sceAmctrl.cpp" />
<ClCompile Include="HLE\sceAtrac.cpp" />
<ClCompile Include="HLE\sceAudio.cpp" />
<ClCompile Include="HLE\sceAudiocodec.cpp" />
@@ -946,6 +947,7 @@
<ClInclude Include="HLE\proAdhocServer.h" />
<ClInclude Include="HLE\ReplaceTables.h" />
<ClInclude Include="HLE\sceAdler.h" />
<ClInclude Include="HLE\sceAmctrl.h" />
<ClInclude Include="HLE\sceAtrac.h" />
<ClInclude Include="HLE\sceAudio.h" />
<ClInclude Include="HLE\sceAudiocodec.h" />
+6
View File
@@ -198,6 +198,9 @@
<ClCompile Include="HLE\sceAdler.cpp">
<Filter>HLE\Libraries</Filter>
</ClCompile>
<ClCompile Include="HLE\sceAmctrl.cpp">
<Filter>HLE\Libraries</Filter>
</ClCompile>
<ClCompile Include="HLE\sceAtrac.cpp">
<Filter>HLE\Libraries</Filter>
</ClCompile>
@@ -1503,6 +1506,9 @@
<ClInclude Include="HLE\sceAdler.h">
<Filter>HLE\Libraries</Filter>
</ClInclude>
<ClInclude Include="HLE\sceAmctrl.h">
<Filter>HLE\Libraries</Filter>
</ClInclude>
<ClInclude Include="HLE\sceAtrac.h">
<Filter>HLE\Libraries</Filter>
</ClInclude>
+15 -1
View File
@@ -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;
+3
View File
@@ -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);
+5
View File
@@ -327,6 +327,11 @@ template<auto func> void WrapI_UCI(MIPSState *mips) {
RETURN(retval);
}
template<auto func> void WrapI_UIIUUU(MIPSState *mips) {
int retval = HLECall<int(u32, int, int, u32, u32, u32), func>(mips, PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5));
RETURN(retval);
}
template<auto func> void WrapU_UIIIII(MIPSState *mips) {
u32 retval = HLECall<u32(u32, int , int , int, int, int), func>(mips, PARAM(0), PARAM(1), PARAM(2), PARAM(3), PARAM(4), PARAM(5));
RETURN(retval);
+2
View File
@@ -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();
+6
View File
@@ -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) {
+117
View File
@@ -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<Hle_sceDrmBBMacInit>, "sceDrmBBMacInit", 'i', "xi", HLE_KERNEL_SYSCALL },
{ 0x58163FBE, &WrapI_UUI<Hle_sceDrmBBMacUpdate>, "sceDrmBBMacUpdate", 'i', "xxi", HLE_KERNEL_SYSCALL },
{ 0xEF95A213, &WrapI_UUU<Hle_sceDrmBBMacFinal>, "sceDrmBBMacFinal", 'i', "xxx", HLE_KERNEL_SYSCALL },
{ 0xF5186D8E, &WrapI_UUU<Hle_sceDrmBBMacFinal2>, "sceDrmBBMacFinal2", 'i', "xxx", HLE_KERNEL_SYSCALL },
{ 0x1CCB66D2, &WrapI_UIIUUU<Hle_sceDrmBBCipherInit>, "sceDrmBBCipherInit", 'i', "xiixxx", HLE_KERNEL_SYSCALL },
{ 0x0785C974, &WrapI_UUI<Hle_sceDrmBBCipherUpdate>, "sceDrmBBCipherUpdate", 'i', "xxi", HLE_KERNEL_SYSCALL },
{ 0x9951C50F, &WrapI_U<Hle_sceDrmBBCipherFinal>, "sceDrmBBCipherFinal", 'i', "x", HLE_KERNEL_SYSCALL },
};
void Register_sceAmctrl_driver() {
RegisterHLEModule("sceAmctrl_driver", ARRAY_SIZE(sceAmctrl_driver), sceAmctrl_driver);
}
+17
View File
@@ -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();
+54 -5
View File
@@ -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>, "sceSysregMeResetEnable", 'i', "", HLE_KERNEL_SYSCALL },
{0x2DB0EB28, &WrapI_V<sceSysregMeResetDisable>, "sceSysregMeResetDisable", 'i', "", HLE_KERNEL_SYSCALL },
@@ -90,6 +128,17 @@ const HLEFunction sceSysreg_driver[] = {
{0xA9997109, &WrapI_V<sceSysregMeResetEnable>, "sceSysregMeResetEnable371", 'i', "", HLE_KERNEL_SYSCALL },
{0x76220E94, &WrapI_V<sceSysregMeResetDisable>, "sceSysregMeResetDisable371", 'i', "", HLE_KERNEL_SYSCALL },
{0x3199CF1C, &WrapI_V<sceSysregMeBusClockEnable>, "sceSysregMeBusClockEnable371", 'i', "", HLE_KERNEL_SYSCALL },
// 6.60 and 6.61, which popsman.prx uses.
{0x457FEBA9, &WrapI_V<sceSysregMeResetEnable>, "sceSysregMeResetEnable660", 'i', "", HLE_KERNEL_SYSCALL },
{0x48F1C4AD, &WrapI_V<sceSysregMeResetDisable>, "sceSysregMeResetDisable660", 'i', "", HLE_KERNEL_SYSCALL },
{0x38527743, &WrapI_V<sceSysregMeBusClockEnable>, "sceSysregMeBusClockEnable660", 'i', "", HLE_KERNEL_SYSCALL },
{0x0AE8E549, &WrapI_V<sceSysregAvcResetEnable>, "sceSysregAvcResetEnable660", 'i', "", HLE_KERNEL_SYSCALL },
{0x8B1DD83A, &WrapI_I<sceSysreg_driver_8B1DD83A>, "sceSysreg_driver_8B1DD83A", 'i', "i", HLE_KERNEL_SYSCALL },
{0xB1751B06, &WrapI_I<sceSysreg_driver_B1751B06>, "sceSysreg_driver_B1751B06", 'i', "i", HLE_KERNEL_SYSCALL },
{0x4634C9DC, &WrapI_I<sceSysreg_driver_4634C9DC>, "sceSysreg_driver_4634C9DC", 'i', "i", HLE_KERNEL_SYSCALL },
{0x4B4CCE80, &WrapI_V<sceSysreg_driver_4B4CCE80>, "sceSysreg_driver_4B4CCE80", 'i', "", HLE_KERNEL_SYSCALL },
{0x41B0337B, &WrapI_I<sceSysreg_driver_41B0337B>, "sceSysreg_driver_41B0337B", 'i', "i", HLE_KERNEL_SYSCALL },
{0xC36775AD, &WrapI_II<sceSysreg_driver_C36775AD>, "sceSysreg_driver_C36775AD", 'i', "ii", HLE_KERNEL_SYSCALL },
};
// Custom-core homebrew (mcidclan's libraries) walks this to find me_wrapper's "SceMeRpc" handler,
+43
View File
@@ -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<MMIODevice *> 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();
+143 -1
View File
@@ -20,6 +20,7 @@
#include <vector>
#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<u32> 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
+8
View File
@@ -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
+9 -3
View File
@@ -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);
+16 -2
View File
@@ -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
+12 -3
View File
@@ -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;
+35 -1
View File
@@ -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) {
+21
View File
@@ -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);
+3 -2
View File
@@ -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);
+4 -2
View File
@@ -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
+2
View File
@@ -184,6 +184,7 @@
<ClInclude Include="..\..\Core\HLE\proAdhocServer.h" />
<ClInclude Include="..\..\Core\HLE\ReplaceTables.h" />
<ClInclude Include="..\..\Core\HLE\sceAdler.h" />
<ClInclude Include="..\..\Core\HLE\sceAmctrl.h" />
<ClInclude Include="..\..\Core\HLE\sceAtrac.h" />
<ClInclude Include="..\..\Core\HLE\sceAudio.h" />
<ClInclude Include="..\..\Core\HLE\sceAudiocodec.h" />
@@ -484,6 +485,7 @@
<ClCompile Include="..\..\Core\HLE\proAdhocServer.cpp" />
<ClCompile Include="..\..\Core\HLE\ReplaceTables.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAdler.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAmctrl.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAtrac.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAudio.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAudiocodec.cpp" />
+2
View File
@@ -92,6 +92,7 @@
<ClCompile Include="..\..\Core\HLE\proAdhocServer.cpp" />
<ClCompile Include="..\..\Core\HLE\ReplaceTables.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAdler.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAmctrl.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAtrac.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAudio.cpp" />
<ClCompile Include="..\..\Core\HLE\sceAudiocodec.cpp" />
@@ -510,6 +511,7 @@
<ClInclude Include="..\..\Core\HLE\proAdhocServer.h" />
<ClInclude Include="..\..\Core\HLE\ReplaceTables.h" />
<ClInclude Include="..\..\Core\HLE\sceAdler.h" />
<ClInclude Include="..\..\Core\HLE\sceAmctrl.h" />
<ClInclude Include="..\..\Core\HLE\sceAtrac.h" />
<ClInclude Include="..\..\Core\HLE\sceAudio.h" />
<ClInclude Include="..\..\Core\HLE\sceAudiocodec.h" />
+1
View File
@@ -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 \
+1
View File
@@ -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 \
+1
View File
@@ -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",