mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-22 10:49:05 +02:00
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>
173 lines
5.3 KiB
C++
173 lines
5.3 KiB
C++
// Copyright (C) 2003 Dolphin 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 SVN repository and contact information can be found at
|
|
// http://code.google.com/p/dolphin-emu/
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#if defined(__APPLE__)
|
|
|
|
#include <cstdint>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
|
|
#include <mach/mach.h>
|
|
#include <mach/vm_map.h>
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/File/FileUtil.h"
|
|
#include "Common/MemoryUtil.h"
|
|
#include "Common/MemArena.h"
|
|
|
|
MemArena::Reservation MemArena::reservations_[MemArena::MAX_RESERVATIONS];
|
|
|
|
size_t MemArena::roundup(size_t x) {
|
|
return x;
|
|
}
|
|
|
|
bool MemArena::GrabMemSpace(size_t size) {
|
|
vm_size = size;
|
|
kern_return_t retval = vm_allocate(mach_task_self(), &vm_mem, size, VM_FLAGS_ANYWHERE);
|
|
if (retval != KERN_SUCCESS) {
|
|
ERROR_LOG(Log::MemMap, "Failed to grab a block of virtual memory");
|
|
return false;
|
|
} else {
|
|
INFO_LOG(Log::MemMap, "Successfully allocated %d bytes at %p", (int)size, (void *)vm_mem);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void MemArena::ReleaseSpace() {
|
|
vm_deallocate(mach_task_self(), vm_mem, vm_size);
|
|
vm_size = 0;
|
|
vm_mem = 0;
|
|
}
|
|
|
|
void *MemArena::CreateView(s64 offset, size_t size, void *base) {
|
|
mach_port_t self = mach_task_self();
|
|
vm_address_t target = (vm_address_t)base;
|
|
uint64_t mask = 0;
|
|
// Inside a reservation, replace the reserved pages in place.
|
|
const bool reserved = InReservation(target, size);
|
|
const int flags = VM_FLAGS_FIXED | (reserved ? VM_FLAGS_OVERWRITE : 0);
|
|
vm_address_t source = vm_mem + offset;
|
|
vm_prot_t cur_protection = 0;
|
|
vm_prot_t max_protection = 0;
|
|
kern_return_t retval =
|
|
vm_remap(self, &target, size, mask, flags,
|
|
self, source, false,
|
|
&cur_protection, &max_protection, VM_INHERIT_DEFAULT);
|
|
if (retval != KERN_SUCCESS) {
|
|
// 1 == KERN_INVALID_ADDRESS
|
|
// 3 == KERN_NO_SPACE (race?)
|
|
// 4 == KERN_INVALID_ARGUMENT
|
|
ERROR_LOG(Log::MemMap, "vm_remap failed (%d) - could not remap from %llx (offset %llx) of size %llx to %p",
|
|
(int)retval, (uint64_t)source, (uint64_t)offset, (uint64_t)size, base);
|
|
return nullptr;
|
|
}
|
|
return (void *)target;
|
|
}
|
|
|
|
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
|
|
vm_address_t addr = (vm_address_t)view;
|
|
if (InReservation(addr, size)) {
|
|
// Put the reserved pages back instead of unmapping. A hole would let another thread's allocation
|
|
// (Metal maps its command buffer memory at any free address) land in it, and ReleaseViewSpace()
|
|
// would then unmap that too.
|
|
kern_return_t retval = vm_map(mach_task_self(), &addr, size, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, MACH_PORT_NULL, 0, false,
|
|
VM_PROT_NONE, VM_PROT_ALL, VM_INHERIT_DEFAULT);
|
|
if (retval == KERN_SUCCESS) {
|
|
return;
|
|
}
|
|
WARN_LOG(Log::MemMap, "Couldn't restore the reservation under a view at %p (%d)", view, (int)retval);
|
|
}
|
|
vm_deallocate(mach_task_self(), addr, size);
|
|
}
|
|
|
|
u8 *MemArena::ReserveViewSpace(size_t size, size_t alignMask) {
|
|
int slot = 0;
|
|
while (slot < MAX_RESERVATIONS && reservations_[slot].size)
|
|
slot++;
|
|
if (slot == MAX_RESERVATIONS) {
|
|
ERROR_LOG(Log::MemMap, "Too many view reservations");
|
|
return nullptr;
|
|
}
|
|
vm_address_t addr = 0;
|
|
// No access: the gaps between the views should fault, like unmapped memory.
|
|
kern_return_t retval = vm_map(mach_task_self(), &addr, size, alignMask, VM_FLAGS_ANYWHERE, MACH_PORT_NULL, 0, false,
|
|
VM_PROT_NONE, VM_PROT_ALL, VM_INHERIT_DEFAULT);
|
|
if (retval != KERN_SUCCESS) {
|
|
WARN_LOG(Log::MemMap, "Couldn't reserve %llx bytes (alignment mask %llx) for the memory views (%d)", (uint64_t)size, (uint64_t)alignMask, (int)retval);
|
|
return nullptr;
|
|
}
|
|
reservations_[slot].addr = addr;
|
|
reservations_[slot].size = size;
|
|
INFO_LOG(Log::MemMap, "Reserved %llx bytes for the memory views at %p", (uint64_t)size, (void *)addr);
|
|
return (u8 *)addr;
|
|
}
|
|
|
|
void MemArena::ReleaseViewSpace(u8 *base) {
|
|
for (Reservation &r : reservations_) {
|
|
if (r.size && r.addr == (vm_address_t)base) {
|
|
vm_deallocate(mach_task_self(), r.addr, r.size);
|
|
r = Reservation();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MemArena::InReservation(vm_address_t addr, size_t size) {
|
|
for (const Reservation &r : reservations_) {
|
|
if (r.size && addr >= r.addr && addr + size <= r.addr + r.size)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MemArena::NeedsProbing() {
|
|
#if PPSSPP_PLATFORM(IOS) && PPSSPP_ARCH(64BIT)
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
u8* MemArena::Find4GBBase() {
|
|
#if PPSSPP_PLATFORM(IOS) && PPSSPP_ARCH(64BIT)
|
|
// The caller will need to do probing, like on 32-bit Windows.
|
|
return nullptr;
|
|
#else
|
|
size_t size;
|
|
#if PPSSPP_ARCH(64BIT)
|
|
size = 0xE1000000;
|
|
#else
|
|
size = 0x10000000;
|
|
#endif
|
|
|
|
vm_address_t addr = 0;
|
|
kern_return_t retval = vm_allocate(mach_task_self(), &addr, size, VM_FLAGS_ANYWHERE);
|
|
if (retval == KERN_SUCCESS) {
|
|
// Don't need the memory now, was just probing.
|
|
vm_deallocate(mach_task_self(), addr, size);
|
|
return (u8 *)addr;
|
|
}
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
#endif // __APPLE__
|