Files
ppsspp/Core/HW/MMIO.cpp
T
Henrik RydgårdandClaude Opus 5.5 41851007a2 Memory: Give the Media Engine an address space of its own
The ME sees the main CPU's RAM, but has its own eDRAM at 0 (with
0x80000000 and 0xA0000000 mirrors) where the main CPU has its scratchpad,
its own memory where the main CPU has VRAM, and both CPUs share the boot
page at 0xBFC00000.

- The ME's own memory is views in the main arena flagged MV_ME_ONLY, which
  only Memory::CreateMESpace() maps, into a second reservation along with
  the shared views at their same arena positions. It gets its own block
  shadow.
- The eDRAM (MV_EDRAM) is the ME's half of the eDRAM chip, the GE's VRAM
  being the other: 2MB on the PSP-1000 (whose resident ME image says so),
  4MB on the others (from mcidclan's libmecore, not hardware-checked).
- At VRAM's addresses, through 0x44000000 and 0xA4000000 alike, the ME
  doesn't see VRAM but memory of its own (pspautotest me/vram, on a
  PSP-1000): where homebrew keeps the VME's buffers. Its size isn't known;
  2MB with VRAM's mirrors here, and no depth layout on 16-bit accesses.
- 0xA0000000 reaches physical memory uncached, so RAM is mapped at
  0xA8000000 too.
- The boot page is a view too, which the main CPU reaches through a new
  MMIO device (in savestates from MMIO section version 2). The ME runs
  code from it, uncached, so the main CPU's writes invalidate code there.
  On masked platforms it isn't mapped, and the device uses a plain
  buffer.
- AddressSpace has IsValidAddress etc, with the ME's rules when edramSize
  is set, and the interpreters and the IR interpreter check and fetch
  instructions through their state's space. The IR compiler's checks still
  use the main CPU's, until the ME gets a JIT.
- On Darwin, MemArena can hold several view reservations.

Masked platforms (iOS, 32-bit) don't get an ME space yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 22:22:40 +02:00

131 lines
3.9 KiB
C++

// 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/.
#include <cstring>
#include <vector>
#include "Common/Log.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Core/HW/GpioMMIO.h"
#include "Core/HW/MMIO.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
namespace MMIO {
// The boot page (see Memory::GetBootPage) is RAM, which the main CPU reaches through here.
class BootPageDevice : public MMIODevice {
public:
BootPageDevice() : MMIODevice("boot page", Memory::BOOT_PAGE_ADDRESS, Memory::BOOT_PAGE_SIZE) {}
bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
if (offset + size > Memory::BOOT_PAGE_SIZE)
return false;
const u8 *ptr = Memory::GetBootPage() + offset;
switch (size) {
case 1: *value = *ptr; break;
case 2: *value = *(const u16_le *)ptr; break;
default: *value = *(const u32_le *)ptr; break;
}
return true;
}
bool Write(MIPSState *mips, u32 offset, int size, u32 value) override {
if (offset + size > Memory::BOOT_PAGE_SIZE)
return false;
u8 *ptr = Memory::GetBootPage() + offset;
switch (size) {
case 1: *ptr = (u8)value; break;
case 2: *(u16_le *)ptr = (u16)value; break;
default: *(u32_le *)ptr = value; break;
}
// The ME runs code from here, uncached, so it sees new code at once (MIST patches the
// bootstrap, me/vram puts it back after its own stub). Reaches the ME's JIT too.
if (mips)
mips->InvalidateICacheRangeDeferred(Memory::BOOT_PAGE_ADDRESS + offset, size);
return true;
}
void Reset() override {
memset(Memory::GetBootPage(), 0, Memory::BOOT_PAGE_SIZE);
}
void DoState(PointerWrap &p) override {
DoArray(p, Memory::GetBootPage(), Memory::BOOT_PAGE_SIZE);
}
};
static BootPageDevice g_bootPage;
static std::vector<MMIODevice *> g_devices;
void Init() {
g_devices = {
GpioMMIO::Device(),
SysconSerialMMIO::Device(),
&g_bootPage,
};
for (MMIODevice *device : g_devices) {
device->Reset();
}
}
void Shutdown() {
g_devices.clear();
}
void DoState(PointerWrap &p) {
auto s = p.Section("MMIO", 1, 2);
if (!s)
return;
// Every device, whether or not this boot registered it, so the layout doesn't depend on that.
GpioMMIO::Device()->DoState(p);
SysconSerialMMIO::Device()->DoState(p);
if (s >= 2) {
g_bootPage.DoState(p);
} else if (p.mode == PointerWrap::MODE_READ) {
g_bootPage.Reset();
}
}
static MMIODevice *FindDevice(u32 address) {
for (MMIODevice *device : g_devices) {
if (device->Contains(address))
return device;
}
return nullptr;
}
u32 Read(MIPSState *mips, u32 address, int size) {
MMIODevice *device = FindDevice(address);
u32 value;
if (device && device->Read(mips, address - device->Base(), size, &value))
return value;
WARN_LOG(Log::CPU, "Unhandled MMIO Read%d at %08x%s%s", size * 8, address, device ? " in " : "", device ? device->Name() : "");
return UNHANDLED_READ;
}
void Write(MIPSState *mips, u32 address, int size, u32 value) {
MMIODevice *device = FindDevice(address);
if (device && device->Write(mips, address - device->Base(), size, value))
return;
WARN_LOG(Log::CPU, "Unhandled MMIO Write%d at %08x = %08x%s%s", size * 8, address, value, device ? " in " : "", device ? device->Name() : "");
}
} // namespace MMIO