diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 6a8b32f5f..508179717 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -649,6 +649,33 @@ int DoMprotect(const void* addr, size_t len, int prot) { return 0; } +int DoMtypeprotect(const void* addr, size_t len, int type, int prot) { + const int result = DoMprotect(addr, len, prot); + const auto address = reinterpret_cast(addr); + constexpr auto pageMask = static_cast(PS5_PAGE_SIZE - 1); + const auto first = address & ~pageMask; + const auto end = (address + len + pageMask) & ~pageMask; + std::vector> physical; + { + std::lock_guard lock(g_directLock); + auto it = g_directMappings.lower_bound(first); + if (it != g_directMappings.begin() && std::prev(it)->second.end > first) --it; + while (it != g_directMappings.end() && it->first < end) { + const auto base = it->first; + const auto mapping = it->second; + it = g_directMappings.erase(it); + if (base < first) g_directMappings.emplace(base, DirectMapping{first, mapping.phys, mapping.memoryType, mapping.backing}); + const auto low = std::max(base, first); + const auto high = std::min(mapping.end, end); + g_directMappings.emplace(low, DirectMapping{high, mapping.phys + low - base, type, mapping.backing}); + physical.emplace_back(mapping.phys + low - base, high - low); + if (mapping.end > end) it = g_directMappings.emplace(end, DirectMapping{mapping.end, mapping.phys + end - base, mapping.memoryType, mapping.backing}).first; + } + } + for (const auto& [phys, bytes] : physical) DirectMemoryRetype(static_cast(phys), bytes, type); + return result; +} + int DoMunmap(void* addr, size_t len) { Trace("unmap %p+0x%zx", addr, len); if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0 || !addr) return SCE_KERNEL_ERROR_EINVAL; diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp index c1723243b..e2310818d 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp @@ -16,10 +16,12 @@ void CreateDirectMemoryBacking(int64_t start, size_t len, int memoryType); bool QueryDirectMapping(std::uintptr_t address, std::uintptr_t* start, std::uintptr_t* end, std::uint64_t* offset, int* memoryType); void ForgetDirectMemory(int64_t start, size_t len); bool DirectMemoryFind(int64_t offset, bool findNext, int64_t* start, int64_t* end, int* memoryType); +void DirectMemoryRetype(int64_t start, size_t len, int memoryType); size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit); int DoMapDirect(void** addr, size_t len, int prot, int flags, int64_t physStart, size_t alignment); int DoMapAnon(void** addr, size_t len, int prot, int flags); int DoMprotect(const void* addr, size_t len, int prot); +int DoMtypeprotect(const void* addr, size_t len, int type, int prot); int DoMunmap(void* addr, size_t len); int DoReserveVirtual(void** addr, size_t len, int flags, size_t alignment); bool GuestProtection(uintptr_t addr, int* prot); diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 86aee93c4..0b20146d0 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -316,8 +316,7 @@ int APS5_VABI sceKernelCheckedReleaseDirectMemory(int64_t start, size_t len) { } int APS5_VABI sceKernelMtypeprotect(const void* addr, size_t len, int type, int prot) { - (void)type; - return DoMprotect(addr, len, prot); + return DoMtypeprotect(addr, len, type, prot); } int APS5_VABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, int* prot) { @@ -426,9 +425,11 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries, result = sceKernelMunmap(reinterpret_cast(entry.start), entry.length); break; case OpProtect: - case OpTypeProtect: result = DoMprotect(entry.start, entry.length, static_cast(entry.protection)); break; + case OpTypeProtect: + result = DoMtypeprotect(entry.start, entry.length, static_cast(entry.type), static_cast(entry.protection)); + break; case OpMapFlexible: result = _mapFlexible(&entry.start, entry.length, static_cast(entry.protection), flags); break; diff --git a/core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp b/core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp index b1e04c262..645098b71 100644 --- a/core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp +++ b/core/libs/prx/libkernel/DirectMemory/MemoryPool.cpp @@ -1,5 +1,6 @@ #include "prx/libkernel/DirectMemory/MemoryPool.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" +#include #include #include @@ -56,6 +57,21 @@ struct PhysicalMemoryPool { return true; } + void Retype(uint64_t start, size_t len, int memoryType) { + std::lock_guard lock(_mutex); + const uint64_t end = start + len; + auto it = _blocks.upper_bound(start); + if (it != _blocks.begin() && std::prev(it)->second.end > start) --it; + while (it != _blocks.end() && it->first < end) { + const uint64_t blockStart = it->first; + const Block block = it->second; + it = _blocks.erase(it); + if (blockStart < start) _blocks[blockStart] = {start, block.type}; + _blocks[std::max(blockStart, start)] = {std::min(block.end, end), memoryType}; + if (block.end > end) it = _blocks.emplace(end, Block{block.end, block.type}).first; + } + } + size_t FreeRun(uint64_t offset, uint64_t limit) { std::lock_guard lock(_mutex); uint64_t cur = offset & ~static_cast(PS5_PAGE_SIZE - 1); @@ -125,6 +141,10 @@ bool DirectMemoryFind(int64_t offset, bool findNext, int64_t* start, int64_t* en return PhysicalMemoryPool::Instance().Find(static_cast(offset), findNext, start, end, memoryType); } +void DirectMemoryRetype(int64_t start, size_t len, int memoryType) { + PhysicalMemoryPool::Instance().Retype(static_cast(start), len, memoryType); +} + size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit) { return PhysicalMemoryPool::Instance().FreeRun(offset, limit); } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 59b3fb0ac..8f03fcdb3 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -43,6 +43,8 @@ int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); int APS5_VABI sceKernelMunmap(void*, std::size_t); int APS5_VABI sceKernelReleaseFlexibleMemory(void*, std::size_t); int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); +int APS5_VABI sceKernelMtypeprotect(const void*, std::size_t, int, int); +int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); int APS5_VABI sceKernelSetVirtualRangeName(const void*, std::uint64_t, const char*); int APS5_VABI sceKernelClearVirtualRangeName(const void*, std::uint64_t); @@ -293,6 +295,52 @@ static void CheckGetDirectMemoryType() { Require(sceKernelGetDirectMemoryType(second, &type, &start, &end) == SCE_KERNEL_ERROR_ENOENT); } +static void CheckMtypeprotect() { + constexpr std::size_t page = 0x4000; + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); + void* mapped = nullptr; + Require(sceKernelMapDirectMemory(&mapped, page * 3, 3, 0, phys, 0) == 0); + auto* bytes = static_cast(mapped); + Require(sceKernelMtypeprotect(bytes + page + 1, 1, 3, 1) == 0); + const auto check = [&](std::size_t index, int expectedType, int expectedProtection) { + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(bytes + page * index, 0, &info, sizeof(info)) == 0); + Require(info.is_direct && info.memory_type == expectedType && info.protection == expectedProtection); + Require(info.start == reinterpret_cast(bytes + page * index) && info.end == info.start + page); + Require(info.offset == static_cast(phys) + page * index); + int type = -1; + std::int64_t start = -1; + std::int64_t end = -1; + Require(sceKernelGetDirectMemoryType(phys + static_cast(page * index), &type, &start, &end) == 0); + Require(type == expectedType && start == phys + static_cast(page * index) && end == start + static_cast(page)); + }; + check(0, 0, 3); + check(1, 3, 1); + check(2, 0, 3); + KernelBatchMapEntry entry{}; + entry.start = bytes + page * 2; + entry.length = page; + entry.protection = 3; + entry.type = 5; + entry.operation = 4; + int processed = -1; + Require(sceKernelBatchMap2(&entry, 1, &processed, 0) == 0 && processed == 1); + check(0, 0, 3); + check(1, 3, 1); + check(2, 5, 3); + Require(sceKernelMtypeprotect(bytes, page * 3, 2, 3) == 0); + VirtualQueryInfo whole{}; + Require(sceKernelVirtualQuery(bytes, 0, &whole, sizeof(whole)) == 0); + Require(whole.memory_type == 2 && whole.protection == 3); + int type = -1; + std::int64_t start = -1; + std::int64_t end = -1; + Require(sceKernelGetDirectMemoryType(phys + static_cast(page * 2), &type, &start, &end) == 0 && type == 2); + Require(sceKernelMunmap(mapped, page * 3) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); +} + static void CheckFixedVirtualReservation() { constexpr std::size_t page = 0x4000; void* probe = nullptr; @@ -860,6 +908,7 @@ int main() { CheckMlock(); CheckSharedDirectMemoryLifecycle(); CheckGetDirectMemoryType(); + CheckMtypeprotect(); CheckHeapAfterMappingReuse(); #ifdef _WIN32 CheckNoOverwriteRejectsHostOccupiedMapping(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index d48bf6ed8..98d13d735 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -88,6 +88,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - behaviour from shadPS4 (one id per request for the Multiple submits, cancel turns any request aborted and id 0 reports processing, wait mode 2 returns once one request completed); the 128 requests or ids per batch limit is from SharpProspero, with an unknown error, so larger batches throw; wait modes other than 1 (and) and 2 (or) throw; null pointers, negative counts and invalid ids return the codes the single-request siblings use, checked before any state is written - [sceKernelGetAvailableCpumask](../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - returns the default thread affinity, 0x1FFF (CPUs 0 to 12), as KytyPS5 does. Titles pin threads within it (PPSA02664 passes 0x1FFB, Hades II PPSA36082 0x3 and 0x3F); whether CPU 13 is available to a title is not confirmed - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted +- [sceKernelMtypeprotect](../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - sets the memory type of the direct mappings in the page-rounded range and of the direct memory behind them, as shadPS4 and prosper do (KytyPS5 ignores the type); the type is not validated, flexible pages in the range only get the protection, and a later map of the retyped direct memory reports the type it was allocated with. `sceKernelBatchMap2` operation 4 takes the same path with the entry type - [Guest arena](../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature