Merge pull request #181 from shadps4-emu/new_memory

core: Rework memory manager
This commit is contained in:
georgemoralis 2024-06-10 12:33:57 +03:00 committed by GitHub
commit 4287bfcb91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 341 additions and 189 deletions

View File

@ -20,7 +20,7 @@ std::string NativeErrorToString(int e) {
DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), nullptr, e, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
reinterpret_cast<LPSTR>(&err_str), 1, nullptr); reinterpret_cast<LPSTR>(&err_str), 1, nullptr);
if (!res) { if (!res) {
return "(FormatMessageA failed to format error)"; return "(FormatMessageA failed to format error)";

View File

@ -6,7 +6,6 @@
#include "common/error.h" #include "common/error.h"
#include "core/address_space.h" #include "core/address_space.h"
#include "core/libraries/kernel/memory_management.h" #include "core/libraries/kernel/memory_management.h"
#include "core/virtual_memory.h"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
@ -17,11 +16,40 @@
namespace Core { namespace Core {
static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE; static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE;
static constexpr size_t VirtualSize = USER_MAX - USER_MIN + 1;
#ifdef _WIN32 #ifdef _WIN32
struct AddressSpace::Impl { struct AddressSpace::Impl {
Impl() : process{GetCurrentProcess()} { Impl() : process{GetCurrentProcess()} {
// Allocate virtual address placeholder for our address space.
MEM_ADDRESS_REQUIREMENTS req{};
MEM_EXTENDED_PARAMETER param{};
req.LowestStartingAddress = reinterpret_cast<PVOID>(SYSTEM_MANAGED_MIN);
// The ending address must align to page boundary - 1
// https://stackoverflow.com/questions/54223343/virtualalloc2-with-memextendedparameteraddressrequirements-always-produces-error
req.HighestEndingAddress = reinterpret_cast<PVOID>(USER_MIN + UserSize - 1);
req.Alignment = 0;
param.Type = MemExtendedParameterAddressRequirements;
param.Pointer = &req;
// Typically, lower parts of system managed area is already reserved in windows.
// If reservation fails attempt again by reducing the area size a little bit.
// System managed is about 31GB in size so also cap the number of times we can reduce it
// to a reasonable amount.
static constexpr size_t ReductionOnFail = 1_GB;
static constexpr size_t MaxReductions = 10;
virtual_size = SystemSize + UserSize + ReductionOnFail;
for (u32 i = 0; i < MaxReductions && !virtual_base; i++) {
virtual_size -= ReductionOnFail;
virtual_base = static_cast<u8*>(VirtualAlloc2(process, NULL, virtual_size,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
PAGE_NOACCESS, &param, 1));
}
ASSERT_MSG(virtual_base, "Unable to reserve virtual address space!");
// Initializer placeholder tracker
const uintptr_t virtual_addr = reinterpret_cast<uintptr_t>(virtual_base);
placeholders.insert({virtual_addr, virtual_addr + virtual_size});
// Allocate backing file that represents the total physical memory. // Allocate backing file that represents the total physical memory.
backing_handle = backing_handle =
CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ, CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ,
@ -35,21 +63,6 @@ struct AddressSpace::Impl {
void* const ret = MapViewOfFile3(backing_handle, process, backing_base, 0, BackingSize, void* const ret = MapViewOfFile3(backing_handle, process, backing_base, 0, BackingSize,
MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0); MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0);
ASSERT(ret == backing_base); ASSERT(ret == backing_base);
// Allocate virtual address placeholder for our address space.
MEM_ADDRESS_REQUIREMENTS req{};
MEM_EXTENDED_PARAMETER param{};
req.LowestStartingAddress = reinterpret_cast<PVOID>(USER_MIN);
req.HighestEndingAddress = reinterpret_cast<PVOID>(USER_MAX);
req.Alignment = 0;
param.Type = MemExtendedParameterAddressRequirements;
param.Pointer = &req;
virtual_base = static_cast<u8*>(VirtualAlloc2(process, nullptr, VirtualSize,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
PAGE_NOACCESS, &param, 1));
ASSERT(virtual_base);
const uintptr_t virtual_addr = reinterpret_cast<uintptr_t>(virtual_base);
placeholders.insert({virtual_addr, virtual_addr + VirtualSize});
} }
~Impl() { ~Impl() {
@ -71,7 +84,7 @@ struct AddressSpace::Impl {
} }
} }
void* MapUser(VAddr virtual_addr, PAddr phys_addr, size_t size, ULONG prot) { void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, ULONG prot) {
const auto it = placeholders.find(virtual_addr); const auto it = placeholders.find(virtual_addr);
ASSERT_MSG(it != placeholders.end(), "Cannot map already mapped region"); ASSERT_MSG(it != placeholders.end(), "Cannot map already mapped region");
ASSERT_MSG(virtual_addr >= it->lower() && virtual_addr + size <= it->upper(), ASSERT_MSG(virtual_addr >= it->lower() && virtual_addr + size <= it->upper(),
@ -106,54 +119,25 @@ struct AddressSpace::Impl {
ptr = MapViewOfFile3(backing_handle, process, reinterpret_cast<PVOID>(virtual_addr), ptr = MapViewOfFile3(backing_handle, process, reinterpret_cast<PVOID>(virtual_addr),
phys_addr, size, MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0); phys_addr, size, MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0);
} else { } else {
ptr = VirtualAlloc2(process, reinterpret_cast<PVOID>(virtual_addr), size, ptr =
MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0); VirtualAlloc2(process, reinterpret_cast<PVOID>(virtual_addr), size,
MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0);
} }
ASSERT_MSG(ptr, "{}", Common::GetLastErrorMsg()); ASSERT_MSG(ptr, "{}", Common::GetLastErrorMsg());
return ptr; return ptr;
} }
void* MapPrivate(VAddr virtual_addr, size_t size, u64 alignment, ULONG prot, void Unmap(VAddr virtual_addr, PAddr phys_addr, size_t size) {
bool no_commit = false) { bool ret;
// Map a private allocation if (phys_addr != -1) {
PVOID addr = reinterpret_cast<PVOID>(virtual_addr); ret = UnmapViewOfFile2(process, reinterpret_cast<PVOID>(virtual_addr),
MEM_ADDRESS_REQUIREMENTS req{}; MEM_PRESERVE_PLACEHOLDER);
MEM_EXTENDED_PARAMETER param{};
// req.LowestStartingAddress =
// (virtual_addr == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MIN)
// : reinterpret_cast<PVOID>(virtual_addr));
req.HighestEndingAddress = reinterpret_cast<PVOID>(SYSTEM_MANAGED_MAX);
req.Alignment = alignment < 64_KB ? 0 : alignment;
param.Type = MemExtendedParameterAddressRequirements;
param.Pointer = &req;
ULONG alloc_type = MEM_RESERVE | (alignment > 2_MB ? MEM_LARGE_PAGES : 0);
if (!no_commit) {
alloc_type |= MEM_COMMIT;
}
// Check if the area has been reserved beforehand (typically for tesselation buffer)
// and in that case don't reserve it again as Windows complains.
if (virtual_addr) {
MEMORY_BASIC_INFORMATION info;
VirtualQuery(addr, &info, sizeof(info));
if (info.State == MEM_RESERVE) {
alloc_type &= ~MEM_RESERVE;
}
}
void* ptr{};
if (virtual_addr) {
ptr = VirtualAlloc2(process, addr, size, alloc_type, prot, NULL, 0);
ASSERT_MSG(ptr && VAddr(ptr) == virtual_addr, "{}", Common::GetLastErrorMsg());
} else { } else {
ptr = VirtualAlloc2(process, nullptr, size, alloc_type, prot, &param, 1); ret = VirtualFreeEx(process, reinterpret_cast<PVOID>(virtual_addr), size,
ASSERT_MSG(ptr, "{}", Common::GetLastErrorMsg()); MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
} }
return ptr; ASSERT_MSG(ret, "Unmap operation on virtual_addr={:#X} failed: {}", virtual_addr,
} Common::GetLastErrorMsg());
void UnmapUser(VAddr virtual_addr, size_t size) {
const bool ret = UnmapViewOfFile2(process, reinterpret_cast<PVOID>(virtual_addr),
MEM_PRESERVE_PLACEHOLDER);
ASSERT_MSG(ret, "Unmap operation on virtual_addr={:#X} failed", virtual_addr);
// The unmap call will create a new placeholder region. We need to see if we can coalesce it // The unmap call will create a new placeholder region. We need to see if we can coalesce it
// with neighbors. // with neighbors.
@ -186,12 +170,6 @@ struct AddressSpace::Impl {
placeholders.insert({placeholder_start, placeholder_end}); placeholders.insert({placeholder_start, placeholder_end});
} }
void UnmapPrivate(VAddr virtual_addr, size_t size) {
const bool ret =
VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_addr), 0, MEM_RELEASE);
ASSERT_MSG(ret, "{}", Common::GetLastErrorMsg());
}
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) { void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
DWORD new_flags{}; DWORD new_flags{};
if (read && write) { if (read && write) {
@ -221,6 +199,7 @@ struct AddressSpace::Impl {
HANDLE backing_handle{}; HANDLE backing_handle{};
u8* backing_base{}; u8* backing_base{};
u8* virtual_base{}; u8* virtual_base{};
size_t virtual_size{};
boost::icl::separate_interval_set<uintptr_t> placeholders; boost::icl::separate_interval_set<uintptr_t> placeholders;
}; };
#else #else
@ -239,22 +218,12 @@ struct AddressSpace::Impl {
UNREACHABLE(); UNREACHABLE();
} }
void* MapUser(VAddr virtual_addr, PAddr phys_addr, size_t size, PosixPageProtection prot) { void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, PosixPageProtection prot) {
UNREACHABLE(); UNREACHABLE();
return nullptr; return nullptr;
} }
void* MapPrivate(VAddr virtual_addr, size_t size, u64 alignment, PosixPageProtection prot, void Unmap(VAddr virtual_addr, PAddr phys_addr, size_t size) {
bool no_commit = false) {
UNREACHABLE();
return nullptr;
}
void UnmapUser(VAddr virtual_addr, size_t size) {
UNREACHABLE();
}
void UnmapPrivate(VAddr virtual_addr, size_t size) {
UNREACHABLE(); UNREACHABLE();
} }
@ -264,36 +233,30 @@ struct AddressSpace::Impl {
u8* backing_base{}; u8* backing_base{};
u8* virtual_base{}; u8* virtual_base{};
size_t virtual_size{};
}; };
#endif #endif
AddressSpace::AddressSpace() : impl{std::make_unique<Impl>()} { AddressSpace::AddressSpace() : impl{std::make_unique<Impl>()} {
virtual_base = impl->virtual_base; virtual_base = impl->virtual_base;
backing_base = impl->backing_base; backing_base = impl->backing_base;
virtual_size = impl->virtual_size;
} }
AddressSpace::~AddressSpace() = default; AddressSpace::~AddressSpace() = default;
void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr) { void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr,
if (virtual_addr >= USER_MIN) { bool is_exec) {
return impl->MapUser(virtual_addr, phys_addr, size, PAGE_READWRITE); return impl->Map(virtual_addr, phys_addr, size,
} is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
return impl->MapPrivate(virtual_addr, size, alignment, PAGE_READWRITE);
} }
void AddressSpace::Unmap(VAddr virtual_addr, size_t size) { void AddressSpace::Unmap(VAddr virtual_addr, size_t size, PAddr phys_addr) {
if (virtual_addr >= USER_MIN) { return impl->Unmap(virtual_addr, phys_addr, size);
return impl->UnmapUser(virtual_addr, size);
}
return impl->UnmapPrivate(virtual_addr, size);
} }
void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) { void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) {
return impl->Protect(virtual_addr, size, true, true, true); return impl->Protect(virtual_addr, size, true, true, true);
} }
void* AddressSpace::Reserve(size_t size, u64 alignment) {
return impl->MapPrivate(0, size, alignment, PAGE_READWRITE, true);
}
} // namespace Core } // namespace Core

View File

@ -14,9 +14,23 @@ enum class MemoryPermission : u32 {
Write = 1 << 1, Write = 1 << 1,
ReadWrite = Read | Write, ReadWrite = Read | Write,
Execute = 1 << 2, Execute = 1 << 2,
ReadWriteExecute = Read | Write | Execute,
}; };
DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission) DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
constexpr VAddr SYSTEM_RESERVED = 0x800000000ULL;
constexpr VAddr CODE_BASE_OFFSET = 0x100000000ULL;
constexpr VAddr SYSTEM_MANAGED_MIN = 0x0000040000ULL;
constexpr VAddr SYSTEM_MANAGED_MAX = 0x07FFFFBFFFULL;
constexpr VAddr USER_MIN = 0x1000000000ULL;
constexpr VAddr USER_MAX = 0xFBFFFFFFFFULL;
// User area size is normally larger than this. However games are unlikely to map to high
// regions of that area, so by default we allocate a smaller virtual address space (about 1/4th).
// to save space on page tables.
static constexpr size_t UserSize = 1ULL << 38;
static constexpr size_t SystemSize = USER_MIN - SYSTEM_MANAGED_MIN;
/** /**
* Represents the user virtual address space backed by a dmem memory block * Represents the user virtual address space backed by a dmem memory block
*/ */
@ -25,12 +39,15 @@ public:
explicit AddressSpace(); explicit AddressSpace();
~AddressSpace(); ~AddressSpace();
[[nodiscard]] u8* VirtualBase() noexcept { [[nodiscard]] VAddr VirtualBase() noexcept {
return virtual_base; return reinterpret_cast<VAddr>(virtual_base);
} }
[[nodiscard]] const u8* VirtualBase() const noexcept { [[nodiscard]] const u8* VirtualBase() const noexcept {
return virtual_base; return virtual_base;
} }
[[nodiscard]] size_t VirtualSize() const noexcept {
return virtual_size;
}
/** /**
* @brief Maps memory to the specified virtual address. * @brief Maps memory to the specified virtual address.
@ -42,20 +59,20 @@ public:
* If zero is provided the mapping is considered as private. * If zero is provided the mapping is considered as private.
* @return A pointer to the mapped memory. * @return A pointer to the mapped memory.
*/ */
void* Map(VAddr virtual_addr, size_t size, u64 alignment = 0, PAddr phys_addr = -1); void* Map(VAddr virtual_addr, size_t size, u64 alignment = 0, PAddr phys_addr = -1,
bool exec = false);
/// Unmaps specified virtual memory area. /// Unmaps specified virtual memory area.
void Unmap(VAddr virtual_addr, size_t size); void Unmap(VAddr virtual_addr, size_t size, PAddr phys_addr);
void Protect(VAddr virtual_addr, size_t size, MemoryPermission perms); void Protect(VAddr virtual_addr, size_t size, MemoryPermission perms);
void* Reserve(size_t size, u64 alignment);
private: private:
struct Impl; struct Impl;
std::unique_ptr<Impl> impl; std::unique_ptr<Impl> impl;
u8* backing_base{}; u8* backing_base{};
u8* virtual_base{}; u8* virtual_base{};
size_t virtual_size{};
}; };
} // namespace Core } // namespace Core

View File

@ -10,7 +10,6 @@
#include "core/libraries/gnmdriver/gnmdriver.h" #include "core/libraries/gnmdriver/gnmdriver.h"
#include "core/libraries/libs.h" #include "core/libraries/libs.h"
#include "core/libraries/videoout/video_out.h" #include "core/libraries/videoout/video_out.h"
#include "core/memory.h"
#include "core/platform.h" #include "core/platform.h"
#include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/liverpool.h"
#include "video_core/amdgpu/pm4_cmds.h" #include "video_core/amdgpu/pm4_cmds.h"
@ -40,10 +39,7 @@ struct AscQueueInfo {
u32 ring_size_dw; u32 ring_size_dw;
}; };
static VideoCore::SlotVector<AscQueueInfo> asc_queues{}; static VideoCore::SlotVector<AscQueueInfo> asc_queues{};
static constexpr VAddr tessellation_factors_ring_addr = 0xFF0000000ULL;
static constexpr u32 TessellationFactorRingSize = 128_KB;
static constexpr u32 TessellationFactorRingAlignment = 64_KB; // toolkit is using this alignment
VAddr tessellation_factors_ring_addr{0};
static void DumpCommandList(std::span<const u32> cmd_list, const std::string& postfix) { static void DumpCommandList(std::span<const u32> cmd_list, const std::string& postfix) {
using namespace Common::FS; using namespace Common::FS;
@ -624,11 +620,6 @@ int PS4_SYSV_ABI sceGnmGetShaderStatus() {
VAddr PS4_SYSV_ABI sceGnmGetTheTessellationFactorRingBufferBaseAddress() { VAddr PS4_SYSV_ABI sceGnmGetTheTessellationFactorRingBufferBaseAddress() {
LOG_TRACE(Lib_GnmDriver, "called"); LOG_TRACE(Lib_GnmDriver, "called");
// Actual virtual buffer address is hardcoded in the driver to 0xff00'000 // Actual virtual buffer address is hardcoded in the driver to 0xff00'000
if (tessellation_factors_ring_addr == 0) {
auto* memory = Core::Memory::Instance();
tessellation_factors_ring_addr =
memory->Reserve(TessellationFactorRingSize, TessellationFactorRingAlignment);
}
return tessellation_factors_ring_addr; return tessellation_factors_ring_addr;
} }

View File

@ -55,6 +55,20 @@ s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment,
physAddrOut); physAddrOut);
} }
s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) {
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
auto* memory = Core::Memory::Instance();
memory->Free(start, len);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info,
size_t infoSize) {
LOG_INFO(Kernel_Vmm, "called addr = {}, flags = {:#x}", fmt::ptr(addr), flags);
auto* memory = Core::Memory::Instance();
return memory->VirtualQuery(std::bit_cast<VAddr>(addr), flags, info);
}
int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags, int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags,
s64 directMemoryStart, u64 alignment, s64 directMemoryStart, u64 alignment,
const char* name) { const char* name) {
@ -83,7 +97,7 @@ int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, i
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags); const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
return memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, "", return memory->MapMemory(addr, in_addr, len, mem_prot, map_flags, Core::VMAType::Direct, "",
directMemoryStart, alignment); false, directMemoryStart, alignment);
} }
int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags,

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include "common/bit_field.h"
#include "common/types.h" #include "common/types.h"
constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5376_MB; // ~ 6GB constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5376_MB; // ~ 6GB
@ -36,6 +37,22 @@ struct OrbisQueryInfo {
int memoryType; int memoryType;
}; };
struct OrbisVirtualQueryInfo {
uintptr_t start;
uintptr_t end;
size_t offset;
s32 protection;
s32 memory_type;
union {
BitField<0, 1, u32> is_flexible;
BitField<1, 1, u32> is_direct;
BitField<2, 1, u32> is_stack;
BitField<3, 1, u32> is_pooled;
BitField<4, 1, u32> is_commited;
};
std::array<char, 32> name;
};
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize(); u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize();
int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len,
u64 alignment, int memoryType, s64* physAddrOut); u64 alignment, int memoryType, s64* physAddrOut);
@ -46,6 +63,9 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl
s64 directMemoryStart, u64 alignment); s64 directMemoryStart, u64 alignment);
s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType,
s64* physAddrOut); s64* physAddrOut);
s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len);
s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info,
size_t infoSize);
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addrInOut, std::size_t len, int prot, s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addrInOut, std::size_t len, int prot,
int flags, const char* name); int flags, const char* name);
s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot, s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot,

View File

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
@ -13,49 +12,82 @@
namespace Core { namespace Core {
MemoryManager::MemoryManager() { MemoryManager::MemoryManager() {
// Insert a virtual memory area that covers the user area. // Insert an area that covers direct memory physical block.
const size_t user_size = USER_MAX - USER_MIN - 1; dmem_map.emplace(0, DirectMemoryArea{0, SCE_KERNEL_MAIN_DMEM_SIZE});
vma_map.emplace(USER_MIN, VirtualMemoryArea{USER_MIN, user_size});
// Insert a virtual memory area that covers the system managed area. // Insert a virtual memory area that covers the entire area we manage.
const size_t sys_size = SYSTEM_MANAGED_MAX - SYSTEM_MANAGED_MIN - 1; const VAddr virtual_base = impl.VirtualBase();
vma_map.emplace(SYSTEM_MANAGED_MIN, VirtualMemoryArea{SYSTEM_MANAGED_MIN, sys_size}); const size_t virtual_size = impl.VirtualSize();
vma_map.emplace(virtual_base, VirtualMemoryArea{virtual_base, virtual_size});
} }
MemoryManager::~MemoryManager() = default; MemoryManager::~MemoryManager() = default;
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment, PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment,
int memory_type) { int memory_type) {
PAddr free_addr = search_start; std::scoped_lock lk{mutex};
// Iterate through allocated blocked and find the next free position auto dmem_area = FindDmemArea(search_start);
for (const auto& block : allocations) {
const PAddr end = block.base + block.size; const auto is_suitable = [&] {
free_addr = std::max(end, free_addr); return dmem_area->second.is_free && dmem_area->second.size >= size;
};
while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) {
dmem_area++;
} }
ASSERT_MSG(is_suitable(), "Unable to find free direct memory area");
// Align free position // Align free position
PAddr free_addr = dmem_area->second.base;
free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr; free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr;
ASSERT(free_addr >= search_start && free_addr + size <= search_end);
// Add the allocated region to the list and commit its pages. // Add the allocated region to the list and commit its pages.
allocations.emplace_back(free_addr, size, memory_type); auto& area = AddDmemAllocation(free_addr, size);
area.memory_type = memory_type;
area.is_free = false;
return free_addr; return free_addr;
} }
void MemoryManager::Free(PAddr phys_addr, size_t size) { void MemoryManager::Free(PAddr phys_addr, size_t size) {
const auto it = std::ranges::find_if(allocations, [&](const auto& alloc) { std::scoped_lock lk{mutex};
return alloc.base == phys_addr && alloc.size == size;
});
ASSERT(it != allocations.end());
// Free the ranges. const auto dmem_area = FindDmemArea(phys_addr);
allocations.erase(it); ASSERT(dmem_area != dmem_map.end() && dmem_area->second.base == phys_addr &&
dmem_area->second.size == size);
// Release any dmem mappings that reference this physical block.
std::vector<std::pair<VAddr, u64>> remove_list;
for (const auto& [addr, mapping] : vma_map) {
if (mapping.type != VMAType::Direct) {
continue;
}
if (mapping.phys_base <= phys_addr && phys_addr < mapping.phys_base + mapping.size) {
LOG_INFO(Kernel_Vmm, "Unmaping direct mapping {:#x} with size {:#x}", addr,
mapping.size);
// Unmaping might erase from vma_map. We can't do it here.
remove_list.emplace_back(addr, mapping.size);
}
}
for (const auto& [addr, size] : remove_list) {
UnmapMemory(addr, size);
}
// Mark region as free and attempt to coalesce it with neighbours.
auto& area = dmem_area->second;
area.is_free = true;
area.memory_type = 0;
MergeAdjacent(dmem_map, dmem_area);
} }
int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name, MemoryMapFlags flags, VMAType type, std::string_view name,
PAddr phys_addr, u64 alignment) { bool is_exec, PAddr phys_addr, u64 alignment) {
std::scoped_lock lk{mutex};
// When virtual addr is zero, force it to virtual_base. The guest cannot pass Fixed
// flag so we will take the branch that searches for free (or reserved) mappings.
virtual_addr = (virtual_addr == 0) ? impl.VirtualBase() : virtual_addr;
VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr; VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr;
SCOPE_EXIT { SCOPE_EXIT {
auto& new_vma = AddMapping(mapped_addr, size); auto& new_vma = AddMapping(mapped_addr, size);
@ -65,18 +97,11 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M
new_vma.type = type; new_vma.type = type;
if (type == VMAType::Direct) { if (type == VMAType::Direct) {
new_vma.phys_base = phys_addr;
MapVulkanMemory(mapped_addr, size); MapVulkanMemory(mapped_addr, size);
} }
}; };
// When virtual addr is zero let the address space manager pick the address.
// Alignment matters here as we let the OS pick the address.
if (virtual_addr == 0) {
*out_addr = impl.Map(virtual_addr, size, alignment);
mapped_addr = std::bit_cast<VAddr>(*out_addr);
return ORBIS_OK;
}
// Fixed mapping means the virtual address must exactly match the provided one. // Fixed mapping means the virtual address must exactly match the provided one.
if (True(flags & MemoryMapFlags::Fixed) && True(flags & MemoryMapFlags::NoOverwrite)) { if (True(flags & MemoryMapFlags::Fixed) && True(flags & MemoryMapFlags::NoOverwrite)) {
// This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen. // This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen.
@ -92,21 +117,28 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M
it++; it++;
} }
ASSERT(it != vma_map.end()); ASSERT(it != vma_map.end());
mapped_addr = alignment > 0 ? Common::AlignUp(it->second.base, alignment) : it->second.base; const VAddr base = it->second.base;
mapped_addr = alignment > 0 ? Common::AlignUp(base, alignment) : base;
} }
// Perform the mapping. // Perform the mapping.
*out_addr = impl.Map(mapped_addr, size, alignment, phys_addr); *out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec);
return ORBIS_OK; return ORBIS_OK;
} }
void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
std::scoped_lock lk{mutex};
// TODO: Partial unmaps are technically supported by the guest. // TODO: Partial unmaps are technically supported by the guest.
const auto it = vma_map.find(virtual_addr); const auto it = vma_map.find(virtual_addr);
ASSERT_MSG(it != vma_map.end() && it->first == virtual_addr, ASSERT_MSG(it != vma_map.end() && it->first == virtual_addr,
"Attempting to unmap partially mapped range"); "Attempting to unmap partially mapped range");
if (it->second.type == VMAType::Direct) { const auto type = it->second.type;
fmt::print("{}\n", u32(type));
std::fflush(stdout);
const PAddr phys_addr = type == VMAType::Direct ? it->second.phys_base : -1;
if (type == VMAType::Direct) {
UnmapVulkanMemory(virtual_addr, size); UnmapVulkanMemory(virtual_addr, size);
} }
@ -115,13 +147,15 @@ void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
vma.type = VMAType::Free; vma.type = VMAType::Free;
vma.prot = MemoryProt::NoAccess; vma.prot = MemoryProt::NoAccess;
vma.phys_base = 0; vma.phys_base = 0;
MergeAdjacent(it); MergeAdjacent(vma_map, it);
// Unmap the memory region. // Unmap the memory region.
impl.Unmap(virtual_addr, size); impl.Unmap(virtual_addr, size, phys_addr);
} }
int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) { int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) {
std::scoped_lock lk{mutex};
const auto it = FindVMA(addr); const auto it = FindVMA(addr);
const auto& vma = it->second; const auto& vma = it->second;
ASSERT_MSG(vma.type != VMAType::Free, "Provided address is not mapped"); ASSERT_MSG(vma.type != VMAType::Free, "Provided address is not mapped");
@ -132,18 +166,70 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr
return ORBIS_OK; return ORBIS_OK;
} }
int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, int MemoryManager::VirtualQuery(VAddr addr, int flags,
Libraries::Kernel::OrbisQueryInfo* out_info) { Libraries::Kernel::OrbisVirtualQueryInfo* info) {
const auto it = std::ranges::find_if(allocations, [&](const DirectMemoryArea& alloc) { auto it = FindVMA(addr);
return alloc.base <= addr && addr < alloc.base + alloc.size; if (it->second.type == VMAType::Free && flags == 1) {
}); it++;
if (it == allocations.end()) { }
return SCE_KERNEL_ERROR_EACCES; if (it->second.type == VMAType::Free) {
LOG_WARNING(Kernel_Vmm, "VirtualQuery on free memory region");
return ORBIS_KERNEL_ERROR_EACCES;
} }
out_info->start = it->base; const auto& vma = it->second;
out_info->end = it->base + it->size; info->start = vma.base;
out_info->memoryType = it->memory_type; info->end = vma.base + vma.size;
info->is_flexible.Assign(vma.type == VMAType::Flexible);
info->is_direct.Assign(vma.type == VMAType::Direct);
info->is_commited.Assign(vma.type != VMAType::Free);
if (vma.type == VMAType::Direct) {
const auto dmem_it = FindDmemArea(vma.phys_base);
ASSERT(dmem_it != dmem_map.end());
info->memory_type = dmem_it->second.memory_type;
}
return ORBIS_OK;
}
int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next,
Libraries::Kernel::OrbisQueryInfo* out_info) {
std::scoped_lock lk{mutex};
auto dmem_area = FindDmemArea(addr);
if (dmem_area->second.is_free && find_next) {
dmem_area++;
}
if (dmem_area == dmem_map.end() || dmem_area->second.is_free) {
LOG_ERROR(Core, "Unable to find allocated direct memory region to query!");
return ORBIS_KERNEL_ERROR_EACCES;
}
const auto& area = dmem_area->second;
out_info->start = area.base;
out_info->end = area.GetEnd();
out_info->memoryType = area.memory_type;
return ORBIS_OK;
}
int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment,
PAddr* phys_addr_out, size_t* size_out) {
std::scoped_lock lk{mutex};
auto dmem_area = FindDmemArea(search_start);
PAddr paddr{};
size_t max_size{};
while (dmem_area != dmem_map.end() && dmem_area->second.GetEnd() <= search_end) {
if (dmem_area->second.size > max_size) {
paddr = dmem_area->second.base;
max_size = dmem_area->second.size;
}
dmem_area++;
}
*phys_addr_out = alignment > 0 ? Common::AlignUp(paddr, alignment) : paddr;
*size_out = max_size;
return ORBIS_OK; return ORBIS_OK;
} }
@ -178,6 +264,30 @@ VirtualMemoryArea& MemoryManager::AddMapping(VAddr virtual_addr, size_t size) {
return vma_handle->second; return vma_handle->second;
} }
DirectMemoryArea& MemoryManager::AddDmemAllocation(PAddr addr, size_t size) {
auto dmem_handle = FindDmemArea(addr);
ASSERT_MSG(dmem_handle != dmem_map.end(), "Physical address not in dmem_map");
const DirectMemoryArea& area = dmem_handle->second;
ASSERT_MSG(area.is_free && area.base <= addr,
"Adding an allocation to already allocated region");
const PAddr start_in_area = addr - area.base;
const PAddr end_in_vma = start_in_area + size;
ASSERT_MSG(end_in_vma <= area.size, "Mapping cannot fit inside free region");
if (end_in_vma != area.size) {
// Split VMA at the end of the allocated region
Split(dmem_handle, end_in_vma);
}
if (start_in_area != 0) {
// Split VMA at the start of the allocated region
dmem_handle = Split(dmem_handle, start_in_area);
}
return dmem_handle->second;
}
MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offset_in_vma) { MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offset_in_vma) {
auto& old_vma = vma_handle->second; auto& old_vma = vma_handle->second;
ASSERT(offset_in_vma < old_vma.size && offset_in_vma > 0); ASSERT(offset_in_vma < old_vma.size && offset_in_vma > 0);
@ -193,24 +303,17 @@ MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offse
return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma); return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma);
} }
MemoryManager::VMAHandle MemoryManager::MergeAdjacent(VMAHandle iter) { MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t offset_in_area) {
const auto next_vma = std::next(iter); auto& old_area = dmem_handle->second;
if (next_vma != vma_map.end() && iter->second.CanMergeWith(next_vma->second)) { ASSERT(offset_in_area < old_area.size && offset_in_area > 0);
iter->second.size += next_vma->second.size;
vma_map.erase(next_vma);
}
if (iter != vma_map.begin()) { auto new_area = old_area;
auto prev_vma = std::prev(iter); old_area.size = offset_in_area;
if (prev_vma->second.CanMergeWith(iter->second)) { new_area.base += offset_in_area;
prev_vma->second.size += iter->second.size; new_area.size -= offset_in_area;
vma_map.erase(iter);
iter = prev_vma;
}
}
return iter; return dmem_map.emplace_hint(std::next(dmem_handle), new_area.base, new_area);
} };
void MemoryManager::MapVulkanMemory(VAddr addr, size_t size) { void MemoryManager::MapVulkanMemory(VAddr addr, size_t size) {
return; return;

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <mutex>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include <boost/icl/split_interval_map.hpp> #include <boost/icl/split_interval_map.hpp>
@ -11,6 +12,7 @@
#include "common/singleton.h" #include "common/singleton.h"
#include "common/types.h" #include "common/types.h"
#include "core/address_space.h" #include "core/address_space.h"
#include "core/libraries/kernel/memory_management.h"
#include "video_core/renderer_vulkan/vk_common.h" #include "video_core/renderer_vulkan/vk_common.h"
namespace Vulkan { namespace Vulkan {
@ -47,12 +49,28 @@ enum class VMAType : u32 {
Flexible = 3, Flexible = 3,
Pooled = 4, Pooled = 4,
Stack = 5, Stack = 5,
Code = 6,
}; };
struct DirectMemoryArea { struct DirectMemoryArea {
PAddr base = 0; PAddr base = 0;
size_t size = 0; size_t size = 0;
int memory_type = 0; int memory_type = 0;
bool is_free = true;
PAddr GetEnd() const {
return base + size;
}
bool CanMergeWith(const DirectMemoryArea& next) const {
if (base + size != next.base) {
return false;
}
if (is_free != next.is_free) {
return false;
}
return true;
}
}; };
struct VirtualMemoryArea { struct VirtualMemoryArea {
@ -81,14 +99,10 @@ struct VirtualMemoryArea {
} }
}; };
constexpr VAddr SYSTEM_RESERVED = 0x800000000ULL;
constexpr VAddr CODE_BASE_OFFSET = 0x100000000ULL;
constexpr VAddr SYSTEM_MANAGED_MIN = 0x0000040000ULL;
constexpr VAddr SYSTEM_MANAGED_MAX = 0x07FFFFBFFFULL;
constexpr VAddr USER_MIN = 0x1000000000ULL;
constexpr VAddr USER_MAX = 0xFBFFFFFFFFULL;
class MemoryManager { class MemoryManager {
using DMemMap = std::map<PAddr, DirectMemoryArea>;
using DMemHandle = DMemMap::iterator;
using VMAMap = std::map<VAddr, VirtualMemoryArea>; using VMAMap = std::map<VAddr, VirtualMemoryArea>;
using VMAHandle = VMAMap::iterator; using VMAHandle = VMAMap::iterator;
@ -107,35 +121,57 @@ public:
int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name = "", MemoryMapFlags flags, VMAType type, std::string_view name = "",
PAddr phys_addr = -1, u64 alignment = 0); bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
void UnmapMemory(VAddr virtual_addr, size_t size); void UnmapMemory(VAddr virtual_addr, size_t size);
int QueryProtection(VAddr addr, void** start, void** end, u32* prot); int QueryProtection(VAddr addr, void** start, void** end, u32* prot);
int VirtualQuery(VAddr addr, int flags, Libraries::Kernel::OrbisVirtualQueryInfo* info);
int DirectMemoryQuery(PAddr addr, bool find_next, Libraries::Kernel::OrbisQueryInfo* out_info); int DirectMemoryQuery(PAddr addr, bool find_next, Libraries::Kernel::OrbisQueryInfo* out_info);
VAddr Reserve(size_t size, u64 alignment) { int DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment,
return reinterpret_cast<VAddr>(impl.Reserve(size, alignment)); PAddr* phys_addr_out, size_t* size_out);
}
std::pair<vk::Buffer, size_t> GetVulkanBuffer(VAddr addr); std::pair<vk::Buffer, size_t> GetVulkanBuffer(VAddr addr);
private: private:
VMAHandle FindVMA(VAddr target) { VMAHandle FindVMA(VAddr target) {
// Return first the VMA with base >= target. return std::prev(vma_map.upper_bound(target));
const auto it = vma_map.lower_bound(target); }
if (it != vma_map.end() && it->first == target) {
return it; DMemHandle FindDmemArea(PAddr target) {
return std::prev(dmem_map.upper_bound(target));
}
template <typename Handle>
Handle MergeAdjacent(auto& handle_map, Handle iter) {
const auto next_vma = std::next(iter);
if (next_vma != handle_map.end() && iter->second.CanMergeWith(next_vma->second)) {
iter->second.size += next_vma->second.size;
handle_map.erase(next_vma);
} }
return std::prev(it);
if (iter != handle_map.begin()) {
auto prev_vma = std::prev(iter);
if (prev_vma->second.CanMergeWith(iter->second)) {
prev_vma->second.size += iter->second.size;
handle_map.erase(iter);
iter = prev_vma;
}
}
return iter;
} }
VirtualMemoryArea& AddMapping(VAddr virtual_addr, size_t size); VirtualMemoryArea& AddMapping(VAddr virtual_addr, size_t size);
DirectMemoryArea& AddDmemAllocation(PAddr addr, size_t size);
VMAHandle Split(VMAHandle vma_handle, size_t offset_in_vma); VMAHandle Split(VMAHandle vma_handle, size_t offset_in_vma);
VMAHandle MergeAdjacent(VMAHandle iter); DMemHandle Split(DMemHandle dmem_handle, size_t offset_in_area);
void MapVulkanMemory(VAddr addr, size_t size); void MapVulkanMemory(VAddr addr, size_t size);
@ -143,8 +179,9 @@ private:
private: private:
AddressSpace impl; AddressSpace impl;
std::vector<DirectMemoryArea> allocations; DMemMap dmem_map;
VMAMap vma_map; VMAMap vma_map;
std::recursive_mutex mutex;
struct MappedMemory { struct MappedMemory {
vk::UniqueBuffer buffer; vk::UniqueBuffer buffer;

View File

@ -7,6 +7,7 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/aerolib/aerolib.h" #include "core/aerolib/aerolib.h"
#include "core/memory.h"
#include "core/module.h" #include "core/module.h"
#include "core/tls.h" #include "core/tls.h"
#include "core/virtual_memory.h" #include "core/virtual_memory.h"
@ -81,8 +82,11 @@ void Module::LoadModuleToMemory() {
aligned_base_size = Common::AlignUp(base_size, BlockAlign); aligned_base_size = Common::AlignUp(base_size, BlockAlign);
// Map module segments (and possible TLS trampolines) // Map module segments (and possible TLS trampolines)
base_virtual_addr = VirtualMemory::memory_alloc(LoadAddress, aligned_base_size + TrampolineSize, auto* memory = Core::Memory::Instance();
VirtualMemory::MemoryMode::ExecuteReadWrite); void** out_addr = reinterpret_cast<void**>(&base_virtual_addr);
const auto name = file.filename().string();
memory->MapMemory(out_addr, LoadAddress, aligned_base_size + TrampolineSize,
MemoryProt::CpuReadWrite, MemoryMapFlags::Fixed, VMAType::Code, name, true);
LoadAddress += CODE_BASE_INCR * (1 + aligned_base_size / CODE_BASE_INCR); LoadAddress += CODE_BASE_INCR * (1 + aligned_base_size / CODE_BASE_INCR);
// Initialize trampoline generator. // Initialize trampoline generator.

View File

@ -20,6 +20,7 @@
#include "core/libraries/libs.h" #include "core/libraries/libs.h"
#include "core/libraries/videoout/video_out.h" #include "core/libraries/videoout/video_out.h"
#include "core/linker.h" #include "core/linker.h"
#include "core/memory.h"
#include "input/controller.h" #include "input/controller.h"
#include "sdl_window.h" #include "sdl_window.h"
@ -30,6 +31,8 @@ int main(int argc, char* argv[]) {
fmt::print("Usage: {} <elf or eboot.bin path>\n", argv[0]); fmt::print("Usage: {} <elf or eboot.bin path>\n", argv[0]);
return -1; return -1;
} }
// Initialize memory system as early as possible to reserve mappings.
[[maybe_unused]] const auto* memory = Core::Memory::Instance();
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(config_dir / "config.toml"); Config::load(config_dir / "config.toml");
Common::Log::Initialize(); Common::Log::Initialize();