From 657e99fcbf74b024c22087fdd4e750b9eee9ced7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Oct 2026 06:56:35 +0200 Subject: [PATCH] ME: Let kernel code on the main CPU reach RAM and VRAM through kseg1 Yabause-PSP's me.prx ORs 0xA0000000 onto its own addresses to share a buffer with the ME uncached, which is 0xA8xxxxxx for a module in kernel RAM. The main CPU's space has no views there, so it crashed. All of kernel segment 1 (0xA0000000-0xBFFFFFFF) now counts as MMIO, and a device below the hardware registers maps RAM and VRAM onto their uncached mirrors at 0x40000000. The main CPU's native kernel loads and stores send the whole segment to that path; the ME keeps 0xBC000000 as the boundary, since its space maps the rest. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HW/MMIO.cpp | 38 ++++++++++++++++++++++++ Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp | 12 ++++++-- Core/MIPS/x86/X64IRCompLoadStore.cpp | 5 ++-- Core/MemMap.h | 6 ++-- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Core/HW/MMIO.cpp b/Core/HW/MMIO.cpp index 5e3dfa451d..d2e06770a1 100644 --- a/Core/HW/MMIO.cpp +++ b/Core/HW/MMIO.cpp @@ -91,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() { @@ -106,6 +143,7 @@ void Init() { MeMMIO::ClockDevice(), MeMMIO::DdrDevice(), MeMMIO::AudioDevice(), + &g_kernelUncached, }; for (MMIODevice *device : g_devices) { device->Reset(); 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/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