#include "common/commonSubsystem.h" #include "common/subsystems.h" #include "common/threads.h" #include "common/emulatorConfig.h" #include "kernel/memory.h" #include "libs/errno.h" #include "common/logging/log.h" #include #include #include #include #include namespace { using Libs::LibKernel::Memory::VirtualQueryInfo; // Prospero ABI? constexpr uint64_t SceKernelPageSize = 0x4000; constexpr int SceKernelProtCpuRead = 0x01; constexpr int SceKernelProtCpuRw = 0x02; constexpr int SceKernelMapFixed = 0x10; constexpr int SceKernelMapNoOverwrite = 0x80; constexpr int SceKernelMapNoCoalesce = 0x400000; constexpr int SceKernelVqFindNext = 1; constexpr int SceKernelMtypeC = 11; constexpr uint64_t SceKernelDirectMemoryStart = 0; constexpr uint64_t SceKernelMemoryPoolReserveLen = 0x200000; constexpr uint64_t SceKernelMemoryPoolCommitLen = 0x10000; constexpr uint64_t SceKernelMemoryPoolExpandLen = 0x400000; constexpr uint64_t SceKernelMemoryPoolAlignment = 0x10000; constexpr int ErrorAccess = Libs::LibKernel::KERNEL_ERROR_EACCES; struct TestFailure {}; int g_failed_tests = 0; [[noreturn]] void Fail(const char* test, const std::string& message) { std::fflush(stdout); std::fprintf(stderr, "VirtualMemoryAllocationTests: %s failed: %s\n", test, message.c_str()); g_failed_tests++; throw TestFailure {}; } void Check(const char* test, bool value, const std::string& message) { if (!value) { Fail(test, message); } } void CheckOk(const char* test, int result, const char* action) { if (result != OK) { char buffer[256] = {}; std::snprintf(buffer, sizeof(buffer), "%s returned 0x%08" PRIx32, action, static_cast(result)); Fail(test, buffer); } } void CheckFailed(const char* test, int result, const char* action) { if (result >= OK) { char buffer[256] = {}; std::snprintf(buffer, sizeof(buffer), "%s returned success, expected negative error", action); Fail(test, buffer); } } void InitSubsystems() { static bool initialized = false; if (initialized) { return; } static char arg0[] = "virtual_memory_allocation_tests"; static char* argv[] = {arg0}; auto* slist = Common::SubsystemsList::Instance(); auto* core = Common::CommonSubsystem::Instance(); auto* config = Config::ConfigSubsystem::Instance(); auto* log = Log::LogSubsystem::Instance(); auto* memory = Libs::LibKernel::Memory::MemorySubsystem::Instance(); auto* thread = Common::ThreadsSubsystem::Instance(); slist->SetArgs(1, argv); slist->Add(thread, {}); slist->Add(core, {}); slist->Add(config, {core}); Check("InitSubsystems", slist->InitAll(false), "failed to initialize base subsystems"); Config::ConfigOptions options; options.printf_direction = Config::OutputDirection::Silent; Config::Load(options); slist->Add(log, {core, config}); slist->Add(memory, {core, log, thread}); Check("InitSubsystems", slist->InitAll(false), "failed to initialize memory subsystem"); initialized = true; } void RunTest(void (*test_func)()) { if (g_failed_tests != 0) { return; } try { test_func(); } catch (const TestFailure&) { } } VirtualQueryInfo Query(const char* test, uint64_t addr, int flags = 0) { VirtualQueryInfo info {}; const int ret = Libs::LibKernel::Memory::KernelVirtualQuery(reinterpret_cast(addr), flags, &info, sizeof(info)); CheckOk(test, ret, "KernelVirtualQuery"); return info; } int QueryResult(uint64_t addr, int flags = 0) { VirtualQueryInfo info {}; return Libs::LibKernel::Memory::KernelVirtualQuery(reinterpret_cast(addr), flags, &info, sizeof(info)); } size_t AvailableFlexibleMemory(const char* test) { size_t size = 0; const int ret = Libs::LibKernel::Memory::KernelAvailableFlexibleMemorySize(&size); CheckOk(test, ret, "KernelAvailableFlexibleMemorySize"); return size; } uint64_t MapNamedFlexible(const char* test, uint64_t size, int prot, const char* name) { void* addr = nullptr; const int ret = Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(&addr, size, prot, 0, name); CheckOk(test, ret, "KernelMapNamedFlexibleMemory"); Check(test, addr != nullptr, "flexible mapping returned null"); return reinterpret_cast(addr); } void ExpectRange(const char* test, const VirtualQueryInfo& info, uint64_t start, uint64_t end, int prot, uint32_t flexible, uint32_t direct, uint32_t pooled, uint32_t committed, const char* name = nullptr, uint64_t offset = 0) { Check(test, info.start == start, "unexpected range start"); Check(test, info.end == end, "unexpected range end"); Check(test, info.protection == prot, "unexpected range protection"); Check(test, info.is_flexible == flexible, "unexpected flexible flag"); Check(test, info.is_direct == direct, "unexpected direct flag"); Check(test, info.is_pooled == pooled, "unexpected pooled flag"); Check(test, info.is_committed == committed, "unexpected committed flag"); Check(test, info.offset == offset, "unexpected range offset"); if (name != nullptr) { Check(test, std::strncmp(info.name, name, Libs::LibKernel::Memory::KERNEL_MAXIMUM_NAME_LENGTH) == 0, "unexpected range name"); } } void ExpectUnmapped(const char* test, uint64_t addr) { const int ret = QueryResult(addr); if (ret != ErrorAccess) { char buffer[256] = {}; std::snprintf(buffer, sizeof(buffer), "KernelVirtualQuery(unmapped) returned 0x%08" PRIx32, static_cast(ret)); Fail(test, buffer); } } void TestProsperoArgumentAndInfoSizeContracts() { const char* test = "ProsperoArgumentAndInfoSizeContracts"; void* addr = nullptr; Check(test, sizeof(VirtualQueryInfo) == 72, "SceKernelVirtualQueryInfo layout drifted"); CheckFailed(test, Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(&addr, 0, SceKernelProtCpuRw, 0, "zero_len"), "KernelMapNamedFlexibleMemory(len=0)"); CheckFailed(test, QueryResult(0), "KernelVirtualQuery(null)"); VirtualQueryInfo info {}; CheckFailed(test, Libs::LibKernel::Memory::KernelVirtualQuery(nullptr, 0, &info, sizeof(info) - 1), "KernelVirtualQuery(short info)"); CheckFailed(test, Libs::LibKernel::Memory::KernelVirtualQuery(nullptr, 2, &info, sizeof(info)), "KernelVirtualQuery(unknown flags)"); std::printf("[host] %-48s ok\n", test); } void TestFlexibleMapQueryAndWholeMunmap() { const char* test = "FlexibleMapQueryAndWholeMunmap"; const auto baseline = AvailableFlexibleMemory(test); const auto size = SceKernelPageSize * 2; const auto base = MapNamedFlexible(test, size, SceKernelProtCpuRw, "prospero_flex"); ExpectRange(test, Query(test, base), base, base + size, SceKernelProtCpuRw, 1, 0, 0, 1, "prospero_flex"); Check(test, AvailableFlexibleMemory(test) + size == baseline, "flexible allocation should consume Prospero-reported flexible budget"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, size), "KernelMunmap"); ExpectUnmapped(test, base); Check(test, AvailableFlexibleMemory(test) == baseline, "whole munmap should return flexible memory to Prospero-reported budget"); std::printf("[host] %-48s ok\n", test); } void TestPartialFlexibleMunmapAndFindNext() { const char* test = "PartialFlexibleMunmapAndFindNext"; const auto baseline = AvailableFlexibleMemory(test); const auto base = MapNamedFlexible(test, SceKernelPageSize * 3, SceKernelProtCpuRw, "prospero_part"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize), "KernelMunmap(middle page)"); ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, SceKernelProtCpuRw, 1, 0, 0, 1, "prospero_part"); ExpectUnmapped(test, base + SceKernelPageSize); ExpectRange(test, Query(test, base + SceKernelPageSize, SceKernelVqFindNext), base + SceKernelPageSize * 2, base + SceKernelPageSize * 3, SceKernelProtCpuRw, 1, 0, 0, 1, "prospero_part"); Check(test, AvailableFlexibleMemory(test) + SceKernelPageSize * 2 == baseline, "partial munmap should return only the unmapped flexible page"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap(left cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize * 2, SceKernelPageSize), "KernelMunmap(right cleanup)"); Check(test, AvailableFlexibleMemory(test) == baseline, "cleanup should return all flexible memory to Prospero-reported budget"); std::printf("[host] %-48s ok\n", test); } void TestReserveMapFixedAndNoOverwrite() { const char* test = "ReserveMapFixedAndNoOverwrite"; void* addr = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange(&addr, SceKernelPageSize * 3, 0, SceKernelPageSize), "KernelReserveVirtualRange"); const auto base = reinterpret_cast(addr); ExpectRange(test, Query(test, base), base, base + SceKernelPageSize * 3, 0, 0, 0, 0, 0); void* fixed = reinterpret_cast(base + SceKernelPageSize); CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( &fixed, SceKernelPageSize, SceKernelProtCpuRead, SceKernelMapFixed, "fixed_mid"), "KernelMapNamedFlexibleMemory(fixed)"); Check(test, reinterpret_cast(fixed) == base + SceKernelPageSize, "MAP_FIXED mapping moved"); ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, 0, 0, 0, 0, 0); ExpectRange(test, Query(test, base + SceKernelPageSize), base + SceKernelPageSize, base + SceKernelPageSize * 2, SceKernelProtCpuRead, 1, 0, 0, 1, "fixed_mid"); ExpectRange(test, Query(test, base + SceKernelPageSize * 2), base + SceKernelPageSize * 2, base + SceKernelPageSize * 3, 0, 0, 0, 0, 0); void* blocked = reinterpret_cast(base + SceKernelPageSize); CheckFailed(test, Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( &blocked, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoOverwrite, "blocked"), "KernelMapNamedFlexibleMemory(MAP_FIXED|MAP_NO_OVERWRITE)"); ExpectRange(test, Query(test, base + SceKernelPageSize), base + SceKernelPageSize, base + SceKernelPageSize * 2, SceKernelProtCpuRead, 1, 0, 0, 1, "fixed_mid"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap(left reserve cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize), "KernelMunmap(fixed cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize * 2, SceKernelPageSize), "KernelMunmap(right reserve cleanup)"); std::printf("[host] %-48s ok\n", test); } void TestFixedNoOverwriteRejectsReservedRange() { const char* test = "FixedNoOverwriteRejectsReservedRange"; void* addr = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange(&addr, SceKernelPageSize, 0, SceKernelPageSize), "KernelReserveVirtualRange"); const auto base = reinterpret_cast(addr); ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, 0, 0, 0, 0, 0); void* fixed = reinterpret_cast(base); const int ret = Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( &fixed, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoOverwrite, "reserved_blocked"); const bool rejected = ret < OK; if (ret == OK) { CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(fixed), SceKernelPageSize), "KernelMunmap(unexpected fixed map cleanup)"); if (reinterpret_cast(fixed) != base) { CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap(reserve cleanup)"); } } else { CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap(reserve cleanup)"); } Check(test, rejected, "MAP_FIXED|MAP_NO_OVERWRITE should reject an already reserved virtual range"); std::printf("[host] %-48s ok\n", test); } void TestDirectMapQueryOffsetAndPartialMunmap() { const char* test = "DirectMapQueryOffsetAndPartialMunmap"; int64_t phys_addr = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( SceKernelDirectMemoryStart, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize * 4, SceKernelPageSize, SceKernelMtypeC, &phys_addr), "KernelAllocateDirectMemory"); void* addr = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&addr, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize, "prospero_direct"), "KernelMapNamedDirectMemory"); const auto base = reinterpret_cast(addr); const auto phys = static_cast(phys_addr); void* alias = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( &alias, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize, "prospero_direct_alias"), "KernelMapNamedDirectMemory(alias)"); const auto alias_base = reinterpret_cast(alias); constexpr uint64_t alias_test_value = 0x4b595459444d454dull; // "KYTYDMEM" *reinterpret_cast(base) = alias_test_value; Check(test, *reinterpret_cast(alias_base) == alias_test_value, "direct mappings of the same physical offset must share backing storage"); uint64_t backing_read = 0; Check(test, Libs::LibKernel::Memory::TryReadBacking(base, &backing_read, sizeof(backing_read)), "TryReadBacking should resolve a direct mapping"); Check(test, backing_read == alias_test_value, "TryReadBacking should observe the physical backing bytes"); constexpr uint64_t backing_write = 0x524541444241434bull; // "READBACK" Check(test, Libs::LibKernel::Memory::TryWriteBacking(alias_base + sizeof(uint64_t), &backing_write, sizeof(backing_write)), "TryWriteBacking should resolve a direct alias"); backing_read = 0; Check(test, Libs::LibKernel::Memory::TryReadBacking(base + sizeof(uint64_t), &backing_read, sizeof(backing_read)), "TryReadBacking should resolve an aliased physical offset"); Check(test, backing_read == backing_write, "backing reads and writes should preserve direct-memory aliasing"); auto info = Query(test, base); ExpectRange(test, info, base, base + SceKernelPageSize * 4, SceKernelProtCpuRw, 0, 1, 0, 1, "prospero_direct", phys); Check(test, info.memory_type == SceKernelMtypeC, "unexpected direct memory type"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize), "KernelMunmap(direct middle page)"); ExpectUnmapped(test, base + SceKernelPageSize); constexpr uint64_t transaction_sentinel = 0x5452414e53414354ull; // "TRANSACT" constexpr uint64_t rejected_write = 0x4e4f504152544941ull; // "NOPARTIA" const auto crossing_address = base + SceKernelPageSize - sizeof(uint32_t); std::memcpy(reinterpret_cast(alias_base + SceKernelPageSize - sizeof(uint32_t)), &transaction_sentinel, sizeof(transaction_sentinel)); Check(test, !Libs::LibKernel::Memory::TryWriteBacking(crossing_address, &rejected_write, sizeof(rejected_write)), "TryWriteBacking should reject a range crossing an unmapped span"); uint64_t backing_after_rejected_write = 0; std::memcpy(&backing_after_rejected_write, reinterpret_cast(alias_base + SceKernelPageSize - sizeof(uint32_t)), sizeof(backing_after_rejected_write)); Check(test, backing_after_rejected_write == transaction_sentinel, "failed backing writes must not modify a validated prefix"); uint64_t rejected_read = transaction_sentinel; Check(test, !Libs::LibKernel::Memory::TryReadBacking(crossing_address, &rejected_read, sizeof(rejected_read)), "TryReadBacking should reject a range crossing an unmapped span"); Check(test, rejected_read == transaction_sentinel, "failed backing reads must not modify a destination prefix"); info = Query(test, base + SceKernelPageSize, SceKernelVqFindNext); ExpectRange(test, info, base + SceKernelPageSize * 2, base + SceKernelPageSize * 4, SceKernelProtCpuRw, 0, 1, 0, 1, "prospero_direct", phys + SceKernelPageSize * 2); Check(test, info.memory_type == SceKernelMtypeC, "unexpected right direct memory type"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap(direct left cleanup)"); CheckOk( test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize * 2, SceKernelPageSize * 2), "KernelMunmap(direct right cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(alias_base, SceKernelPageSize * 4), "KernelMunmap(direct alias cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize * 4), "KernelReleaseDirectMemory"); std::printf("[host] %-48s ok\n", test); } void TestReleasedReserveCanBeReused() { const char* test = "ReleasedReserveCanBeReused"; void* addr = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange(&addr, SceKernelPageSize, 0, SceKernelPageSize), "KernelReserveVirtualRange"); const auto base = reinterpret_cast(addr); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap"); void* reused = reinterpret_cast(base); CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange( &reused, SceKernelPageSize, SceKernelMapFixed | SceKernelMapNoOverwrite, SceKernelPageSize), "KernelReserveVirtualRange(reuse)"); Check(test, reinterpret_cast(reused) == base, "released host reservation was not reusable at the same address"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap(reuse cleanup)"); std::printf("[host] %-48s ok\n", test); } void TestMunmapAcrossAdjacentFlexibleMappings() { const char* test = "MunmapAcrossAdjacentFlexibleMappings"; const auto baseline = AvailableFlexibleMemory(test); void* reserve = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange( &reserve, SceKernelPageSize * 2, 0, SceKernelPageSize), "KernelReserveVirtualRange"); const auto base = reinterpret_cast(reserve); void* left = reinterpret_cast(base); void* right = reinterpret_cast(base + SceKernelPageSize); CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( &left, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed, "adjacent_left"), "KernelMapNamedFlexibleMemory(left)"); CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( &right, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed, "adjacent_right"), "KernelMapNamedFlexibleMemory(right)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2), "KernelMunmap(adjacent mappings)"); Check(test, AvailableFlexibleMemory(test) == baseline, "multi-range unmap leaked flexible-memory budget"); ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, 0, 0, 0, 0, 0, "adjacent_left"); ExpectRange(test, Query(test, base + SceKernelPageSize), base + SceKernelPageSize, base + SceKernelPageSize * 2, 0, 0, 0, 0, 0, "adjacent_right"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2), "KernelMunmap(restored reserve)"); std::printf("[host] %-48s ok\n", test); } void TestNonzeroDirectOffsetAliasesSharedBacking() { const char* test = "NonzeroDirectOffsetAliasesSharedBacking"; int64_t first = 0; int64_t second = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize, SceKernelPageSize, SceKernelMtypeC, &first), "KernelAllocateDirectMemory(first)"); CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize, SceKernelPageSize, SceKernelMtypeC, &second), "KernelAllocateDirectMemory(second)"); Check(test, second == first + static_cast(SceKernelPageSize), "second allocation should use a nonzero 16 KiB offset"); void* first_alias = nullptr; void* second_alias = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( &first_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, SceKernelPageSize, "prospero_nonzero_a"), "KernelMapNamedDirectMemory(first alias)"); CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( &second_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, SceKernelPageSize, "prospero_nonzero_b"), "KernelMapNamedDirectMemory(second alias)"); *reinterpret_cast(first_alias) = 0x4b59545931364b42ull; // "KYTY16KB" Check(test, *reinterpret_cast(second_alias) == 0x4b59545931364b42ull, "nonzero-offset mappings must share backing storage"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( reinterpret_cast(first_alias), SceKernelPageSize), "KernelMunmap(first alias)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( reinterpret_cast(second_alias), SceKernelPageSize), "KernelMunmap(second alias)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(second, SceKernelPageSize), "KernelReleaseDirectMemory(second)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(first, SceKernelPageSize), "KernelReleaseDirectMemory(first)"); std::printf("[host] %-48s ok\n", test); } void TestDirectPhysicalFreeRangeReuseAndCoalescing() { const char* test = "DirectPhysicalFreeRangeReuseAndCoalescing"; const auto end = Libs::LibKernel::Memory::KernelGetDirectMemorySize(); int64_t first = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, end, SceKernelPageSize * 3, SceKernelPageSize, SceKernelMtypeC, &first), "KernelAllocateDirectMemory(first)"); const auto middle = first + static_cast(SceKernelPageSize); const auto last = middle + static_cast(SceKernelPageSize); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(middle, SceKernelPageSize), "KernelReleaseDirectMemory(middle split)"); int64_t reused = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, end, SceKernelPageSize, SceKernelPageSize, SceKernelMtypeC, &reused), "KernelAllocateDirectMemory(reused)"); Check(test, reused == middle, "released physical gap was not reused"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(first, SceKernelPageSize), "KernelReleaseDirectMemory(left split)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(reused, SceKernelPageSize), "KernelReleaseDirectMemory(reused)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(last, SceKernelPageSize), "KernelReleaseDirectMemory(right split)"); int64_t coalesced = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, end, SceKernelPageSize * 3, SceKernelPageSize, SceKernelMtypeC, &coalesced), "KernelAllocateDirectMemory(coalesced)"); Check(test, coalesced == first, "adjacent released physical ranges were not coalesced"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(coalesced, SceKernelPageSize * 3), "KernelReleaseDirectMemory(coalesced)"); std::printf("[host] %-48s ok\n", test); } void TestDirectAlignmentStaysWithinSearchRange() { const char* test = "DirectAlignmentStaysWithinSearchRange"; constexpr int64_t search_start = SceKernelPageSize * 2; constexpr uint64_t alignment = SceKernelPageSize * 3; const auto search_end = Libs::LibKernel::Memory::KernelGetDirectMemorySize(); int64_t phys_addr = -1; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( search_start, search_end, SceKernelPageSize, alignment, SceKernelMtypeC, &phys_addr), "KernelAllocateDirectMemory(non-power-of-two alignment)"); Check(test, phys_addr >= search_start, "aligned allocation escaped below search_start"); Check(test, static_cast(phys_addr) % alignment == 0, "allocation did not honor the requested alignment"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize), "KernelReleaseDirectMemory"); constexpr size_t out_of_range_alignment = UINT64_MAX - (SceKernelPageSize - 1); phys_addr = -1; const int result = Libs::LibKernel::Memory::KernelAllocateDirectMemory( search_start, search_end, SceKernelPageSize, out_of_range_alignment, SceKernelMtypeC, &phys_addr); CheckFailed(test, result, "KernelAllocateDirectMemory(out-of-range alignment)"); Check(test, phys_addr == -1, "failed allocation modified physAddrOut"); std::printf("[host] %-48s ok\n", test); } void TestDefaultDirectMapUsesSystemAddressRange() { const char* test = "DefaultDirectMapUsesSystemAddressRange"; int64_t phys_addr = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize, SceKernelPageSize, SceKernelMtypeC, &phys_addr), "KernelAllocateDirectMemory"); void* address = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( &address, SceKernelPageSize, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize, "system_direct"), "KernelMapNamedDirectMemory"); Check(test, address != nullptr, "direct mapping returned null"); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS constexpr uint64_t SystemManagedMin = 0x0000040000ull; constexpr uint64_t SystemManagedMax = 0x07fffeffffull; const auto mapped = reinterpret_cast(address); Check(test, mapped >= SystemManagedMin && mapped + SceKernelPageSize - 1 <= SystemManagedMax, "default direct mapping fell outside the system-managed host range"); #endif CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(address), SceKernelPageSize), "KernelMunmap"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize), "KernelReleaseDirectMemory"); std::printf("[host] %-48s ok\n", test); } void TestLargeDirectMapAliasesAcrossChunks() { const char* test = "LargeDirectMapAliasesAcrossChunks"; constexpr uint64_t size = 0x400000; constexpr uint64_t boundary = 0x200000; int64_t phys_addr = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), size, 0x10000, SceKernelMtypeC, &phys_addr), "KernelAllocateDirectMemory"); void* first_alias = nullptr; void* second_alias = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( &first_alias, size, SceKernelProtCpuRw, 0, phys_addr, 0x10000, "large_direct_a"), "KernelMapNamedDirectMemory(first alias)"); CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( &second_alias, size, SceKernelProtCpuRw, 0, phys_addr, 0x10000, "large_direct_b"), "KernelMapNamedDirectMemory(second alias)"); auto* first = static_cast(first_alias); auto* second = static_cast(second_alias); *reinterpret_cast(first) = 0x1111222233334444ull; *reinterpret_cast(first + boundary - 8) = 0x5555666677778888ull; *reinterpret_cast(first + boundary) = 0x9999aaaabbbbccccull; *reinterpret_cast(first + size - 8) = 0xddddeeeeffff0001ull; Check(test, *reinterpret_cast(second) == 0x1111222233334444ull && *reinterpret_cast(second + boundary - 8) == 0x5555666677778888ull && *reinterpret_cast(second + boundary) == 0x9999aaaabbbbccccull && *reinterpret_cast(second + size - 8) == 0xddddeeeeffff0001ull, "large direct aliases diverged at a mapping chunk boundary"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(first_alias), size), "KernelMunmap(first alias)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(second_alias), size), "KernelMunmap(second alias)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, size), "KernelReleaseDirectMemory"); std::printf("[host] %-48s ok\n", test); } void TestDirectMapUnmapReusesHostAddress() { const char* test = "DirectMapUnmapReusesHostAddress"; int64_t phys_addr = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( SceKernelDirectMemoryStart, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize, SceKernelPageSize, SceKernelMtypeC, &phys_addr), "KernelAllocateDirectMemory"); uint64_t first_address = 0; for (int iteration = 0; iteration < 64; iteration++) { void* address = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( &address, SceKernelPageSize, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize, "reuse_direct"), "KernelMapNamedDirectMemory"); const auto current_address = reinterpret_cast(address); if (iteration == 0) { first_address = current_address; } else { char message[160] = {}; std::snprintf(message, sizeof(message), "direct map address changed from 0x%016" PRIx64 " to 0x%016" PRIx64, first_address, current_address); Check(test, current_address == first_address, message); } CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(current_address, SceKernelPageSize), "KernelMunmap"); } CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize), "KernelReleaseDirectMemory"); std::printf("[host] %-48s ok\n", test); } void TestLargeHintedReserveHostsSmallDirectMap() { const char* test = "LargeHintedReserveHostsSmallDirectMap"; constexpr uint64_t arena_base = 0x1000000000ull; constexpr uint64_t arena_size = 0x04000000ull; constexpr uint64_t window_size = 0x00200000ull; void* arena = reinterpret_cast(arena_base); CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange( &arena, arena_size, 0, 0x200000), "KernelReserveVirtualRange(arena)"); const auto actual_arena = reinterpret_cast(arena); Check(test, actual_arena >= arena_base && (actual_arena & (0x200000 - 1u)) == 0, "large hinted reserve violated its search start or alignment"); void* window = reinterpret_cast(arena_base); CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange( &window, window_size, 0, SceKernelPageSize), "KernelReserveVirtualRange(window)"); Check(test, reinterpret_cast(window) >= actual_arena + arena_size, "second hinted reserve overlaps the large arena"); int64_t phys = 0; CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize * 2, SceKernelPageSize, SceKernelMtypeC, &phys), "KernelAllocateDirectMemory"); void* mapped = window; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( &mapped, SceKernelPageSize * 2, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoCoalesce, phys, 0, "prospero_large_reserve"), "KernelMapNamedDirectMemory"); Check(test, mapped == window, "fixed direct mapping moved away from the reserved window"); *reinterpret_cast(mapped) = 0x4b59545952455356ull; // "KYTYRESV" CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( reinterpret_cast(mapped), SceKernelPageSize * 2), "KernelMunmap(direct)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory( phys, SceKernelPageSize * 2), "KernelReleaseDirectMemory"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( reinterpret_cast(window), window_size), "KernelMunmap(window reserve)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( reinterpret_cast(arena), arena_size), "KernelMunmap(arena reserve)"); std::printf("[host] %-48s ok\n", test); } void TestMemoryPoolAlignmentContracts() { const char* test = "MemoryPoolAlignmentContracts"; void* addr = nullptr; CheckFailed( test, Libs::LibKernel::Memory::KernelMemoryPoolReserve(nullptr, SceKernelPageSize, 0, 0, &addr), "KernelMemoryPoolReserve(16KiB len)"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolReserve(nullptr, SceKernelMemoryPoolReserveLen, 0, 0, &addr), "KernelMemoryPoolReserve"); const auto base = reinterpret_cast(addr); CheckFailed(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit(reinterpret_cast(base), SceKernelPageSize, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit(16KiB len)"); CheckFailed(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(reinterpret_cast(base), SceKernelPageSize, 0), "KernelMemoryPoolDecommit(16KiB len)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelMemoryPoolReserveLen), "KernelMunmap(pool reserve cleanup)"); std::printf("[host] %-48s ok\n", test); } void TestProsperoSampleMemoryPoolExpandCommit() { const char* test = "ProsperoSampleMemoryPoolExpandCommit"; int64_t pool_offset = -1; CheckFailed(test, Libs::LibKernel::Memory::KernelMemoryPoolExpand( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize, SceKernelMemoryPoolAlignment, &pool_offset), "KernelMemoryPoolExpand(16KiB len)"); CheckFailed(test, Libs::LibKernel::Memory::KernelMemoryPoolExpand( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelMemoryPoolExpandLen, SceKernelPageSize, &pool_offset), "KernelMemoryPoolExpand(16KiB alignment)"); CheckFailed(test, Libs::LibKernel::Memory::KernelMemoryPoolExpand( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelMemoryPoolExpandLen, SceKernelMemoryPoolAlignment * 3, &pool_offset), "KernelMemoryPoolExpand(non-power-of-two alignment)"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolExpand( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelMemoryPoolExpandLen, SceKernelMemoryPoolAlignment, &pool_offset), "KernelMemoryPoolExpand"); Check(test, pool_offset >= 0 && (static_cast(pool_offset) & (SceKernelMemoryPoolAlignment - 1u)) == 0, "expanded physical range is not 64 KiB aligned"); void* direct_alias = nullptr; CheckFailed(test, Libs::LibKernel::Memory::KernelMapDirectMemory( &direct_alias, SceKernelMemoryPoolCommitLen, SceKernelProtCpuRw, 0, pool_offset, SceKernelMemoryPoolAlignment), "KernelMapDirectMemory(pool expansion)"); Libs::LibKernel::Memory::KernelMemoryPoolBlockStats stats {}; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolGetBlockStats(&stats, sizeof(stats)), "KernelMemoryPoolGetBlockStats(expanded)"); Check(test, stats.available_flushed_blocks == static_cast(SceKernelMemoryPoolExpandLen / SceKernelMemoryPoolAlignment), "expanded pages were not added to the pool budget"); void* arena = reinterpret_cast(0x1000000000ull); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolReserve(arena, SceKernelMemoryPoolReserveLen, 0, 0, &arena), "KernelMemoryPoolReserve"); const auto base = reinterpret_cast(arena); const auto flexible_baseline = AvailableFlexibleMemory(test); const auto commit_len = SceKernelMemoryPoolCommitLen * 2; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit(arena, commit_len, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit"); ExpectRange(test, Query(test, base), base, base + commit_len, SceKernelProtCpuRw, 0, 0, 1, 1); Check(test, AvailableFlexibleMemory(test) == flexible_baseline, "pooled commit consumed flexible memory instead of expanded direct " "backing"); CheckFailed(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(pool_offset, SceKernelMemoryPoolExpandLen), "KernelReleaseDirectMemory(committed pool expansion)"); constexpr uint64_t first_value = 0x504f4f4c4241434bull; // "POOLBACK" constexpr uint64_t second_value = 0x5348415245444d45ull; // "SHAREDME" *reinterpret_cast(base) = first_value; *reinterpret_cast(base + SceKernelMemoryPoolCommitLen) = second_value; CheckOk( test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, SceKernelMemoryPoolCommitLen, 0), "KernelMemoryPoolDecommit(first page)"); ExpectRange(test, Query(test, base), base, base + SceKernelMemoryPoolCommitLen, 0, 0, 0, 1, 0); ExpectRange(test, Query(test, base + SceKernelMemoryPoolCommitLen), base + SceKernelMemoryPoolCommitLen, base + commit_len, SceKernelProtCpuRw, 0, 0, 1, 1); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolGetBlockStats(&stats, sizeof(stats)), "KernelMemoryPoolGetBlockStats(partially decommitted)"); Check(test, stats.available_flushed_blocks == static_cast(SceKernelMemoryPoolExpandLen / SceKernelMemoryPoolAlignment - 1) && stats.allocated_flushed_blocks == 1, "partial decommit returned the wrong number of pages to the expanded " "pool"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit(arena, SceKernelMemoryPoolCommitLen, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit(first-page recommit)"); Check(test, *reinterpret_cast(base) == first_value && *reinterpret_cast(base + SceKernelMemoryPoolCommitLen) == second_value, "partially recommitted pooled pages did not retain shared-backing " "contents"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, commit_len, 0), "KernelMemoryPoolDecommit(cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelMemoryPoolReserveLen), "KernelMunmap(pool reserve cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(pool_offset, SceKernelMemoryPoolExpandLen), "KernelReleaseDirectMemory(pool expansion)"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolGetBlockStats(&stats, sizeof(stats)), "KernelMemoryPoolGetBlockStats(released)"); Check(test, stats.available_flushed_blocks == 0 && stats.allocated_flushed_blocks == 0, "released expansion remained in the pool budget"); std::printf("[host] %-48s ok\n", test); } void TestFragmentedMemoryPoolBacking() { const char* test = "FragmentedMemoryPoolBacking"; int64_t first_pool = -1; int64_t direct_gap = -1; int64_t second_pool = -1; const auto direct_end = static_cast(Libs::LibKernel::Memory::KernelGetDirectMemorySize()); CheckOk( test, Libs::LibKernel::Memory::KernelMemoryPoolExpand(0, direct_end, SceKernelMemoryPoolCommitLen, SceKernelMemoryPoolAlignment, &first_pool), "KernelMemoryPoolExpand(first)"); CheckOk(test, Libs::LibKernel::Memory::KernelAllocateDirectMemory( 0, direct_end, SceKernelMemoryPoolCommitLen, SceKernelMemoryPoolAlignment, SceKernelMtypeC, &direct_gap), "KernelAllocateDirectMemory(gap)"); CheckOk( test, Libs::LibKernel::Memory::KernelMemoryPoolExpand(0, direct_end, SceKernelMemoryPoolCommitLen, SceKernelMemoryPoolAlignment, &second_pool), "KernelMemoryPoolExpand(second)"); Check(test, first_pool + static_cast(SceKernelMemoryPoolCommitLen) == direct_gap && direct_gap + static_cast(SceKernelMemoryPoolCommitLen) == second_pool, "test setup did not create nonadjacent pool expansions"); void* arena = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolReserve(nullptr, SceKernelMemoryPoolReserveLen, 0, 0, &arena), "KernelMemoryPoolReserve"); const auto base = reinterpret_cast(arena); const auto commit_len = SceKernelMemoryPoolCommitLen * 2; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit(arena, commit_len, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit(fragmented)"); CheckFailed(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit( reinterpret_cast(base + commit_len), SceKernelMemoryPoolCommitLen, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit(exhausted)"); ExpectRange(test, Query(test, base + commit_len), base + commit_len, base + SceKernelMemoryPoolReserveLen, 0, 0, 0, 1, 0); *reinterpret_cast(base) = 0x465241474d454e54ull; // "FRAGMENT" *reinterpret_cast(base + SceKernelMemoryPoolCommitLen) = 0x504f4f4c50414745ull; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, commit_len, 0), "KernelMemoryPoolDecommit(fragmented)"); Libs::LibKernel::Memory::KernelMemoryPoolBlockStats stats {}; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolGetBlockStats(&stats, sizeof(stats)), "KernelMemoryPoolGetBlockStats(fragmented decommit)"); Check(test, stats.available_flushed_blocks == 2 && stats.allocated_flushed_blocks == 0, "fragmented decommit did not restore both pool pages"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit(arena, commit_len, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit(fragmented recommit)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, commit_len), "KernelMunmap(fragmented commit)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelMemoryPoolReserveLen), "KernelMunmap(fragmented reserve cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(first_pool, SceKernelMemoryPoolCommitLen), "KernelReleaseDirectMemory(first pool)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(second_pool, SceKernelMemoryPoolCommitLen), "KernelReleaseDirectMemory(second pool)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(direct_gap, SceKernelMemoryPoolCommitLen), "KernelReleaseDirectMemory(gap)"); std::printf("[host] %-48s ok\n", test); } void TestMemoryPoolMultiRangeDecommit() { const char* test = "MemoryPoolMultiRangeDecommit"; const auto expand_len = SceKernelMemoryPoolCommitLen * 2; int64_t pool_offset = -1; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolExpand( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), expand_len, SceKernelMemoryPoolAlignment, &pool_offset), "KernelMemoryPoolExpand"); void* arena = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolReserve(nullptr, SceKernelMemoryPoolReserveLen, 0, 0, &arena), "KernelMemoryPoolReserve"); const auto base = reinterpret_cast(arena); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit(arena, SceKernelMemoryPoolCommitLen, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit(read-write)"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit( reinterpret_cast(base + SceKernelMemoryPoolCommitLen), SceKernelMemoryPoolCommitLen, SceKernelMtypeC, SceKernelProtCpuRead, 0), "KernelMemoryPoolCommit(read-only)"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, expand_len, 0), "KernelMemoryPoolDecommit(different protections)"); const auto decommitted = Query(test, base); Check(test, decommitted.start <= base && decommitted.end >= base + expand_len && decommitted.is_pooled == 1 && decommitted.is_committed == 0, "multi-range decommit did not restore the reserved pool span"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit( reinterpret_cast(base + SceKernelMemoryPoolCommitLen), SceKernelMemoryPoolCommitLen, SceKernelMtypeC, SceKernelProtCpuRead, 0), "KernelMemoryPoolCommit(mixed span)"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, expand_len, 0), "KernelMemoryPoolDecommit(reserved and committed span)"); const auto mixed_decommitted = Query(test, base); Check(test, mixed_decommitted.start <= base && mixed_decommitted.end >= base + expand_len && mixed_decommitted.is_pooled == 1 && mixed_decommitted.is_committed == 0, "mixed reserved/committed decommit left committed pages behind"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit(arena, SceKernelMemoryPoolCommitLen, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit(preflight prefix)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelMemoryPoolCommitLen, SceKernelMemoryPoolCommitLen), "KernelMunmap(preflight tail reserve)"); void* flexible_tail = reinterpret_cast(base + SceKernelMemoryPoolCommitLen); CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( &flexible_tail, SceKernelMemoryPoolCommitLen, SceKernelProtCpuRead, SceKernelMapFixed, "pool_invalid_tail"), "KernelMapNamedFlexibleMemory(preflight tail)"); CheckFailed(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, expand_len, 0), "KernelMemoryPoolDecommit(invalid tail)"); ExpectRange(test, Query(test, base), base, base + SceKernelMemoryPoolCommitLen, SceKernelProtCpuRw, 0, 0, 1, 1); ExpectRange(test, Query(test, base + SceKernelMemoryPoolCommitLen), base + SceKernelMemoryPoolCommitLen, base + expand_len, SceKernelProtCpuRead, 1, 0, 0, 1, "pool_invalid_tail"); CheckOk( test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, SceKernelMemoryPoolCommitLen, 0), "KernelMemoryPoolDecommit(preflight prefix cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelMemoryPoolReserveLen), "KernelMunmap(pool reserve cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(pool_offset, expand_len), "KernelReleaseDirectMemory(pool expansion)"); std::printf("[host] %-48s ok\n", test); } void TestMemoryPoolCommitDecommitQueryFlags() { const char* test = "MemoryPoolCommitDecommitQueryFlags"; void* addr = nullptr; int64_t pool_offset = -1; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolExpand( 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelMemoryPoolCommitLen, SceKernelMemoryPoolAlignment, &pool_offset), "KernelMemoryPoolExpand"); CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolReserve(nullptr, SceKernelMemoryPoolReserveLen, 0, 0, &addr), "KernelMemoryPoolReserve"); const auto base = reinterpret_cast(addr); const auto reserved = Query(test, base); const bool reserved_ok = reserved.start <= base && base < reserved.end && reserved.is_pooled == 1 && reserved.is_committed == 0; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolCommit(reinterpret_cast(base), SceKernelMemoryPoolCommitLen, SceKernelMtypeC, SceKernelProtCpuRw, 0), "KernelMemoryPoolCommit"); const auto committed = Query(test, base); const bool committed_ok = committed.start == base && committed.end == base + SceKernelMemoryPoolCommitLen && committed.protection == SceKernelProtCpuRw && committed.is_pooled == 1 && committed.is_committed == 1; CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(reinterpret_cast(base), SceKernelMemoryPoolCommitLen, 0), "KernelMemoryPoolDecommit"); const auto decommitted = Query(test, base); const bool decommitted_ok = decommitted.start <= base && base < decommitted.end && decommitted.is_pooled == 1 && decommitted.is_committed == 0; CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelMemoryPoolReserveLen), "KernelMunmap(pool reserve cleanup)"); Check(test, reserved_ok, "pool reserve should query as pooled/uncommitted"); Check(test, committed_ok, "pool commit should query as pooled/committed"); Check(test, decommitted_ok, "pool decommit should return to pooled/uncommitted"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(pool_offset, SceKernelMemoryPoolCommitLen), "KernelReleaseDirectMemory(pool expansion)"); std::printf("[host] %-48s ok\n", test); } void TestProgramMemoryRegistrationAndProtection() { const char* test = "ProgramMemoryRegistrationAndProtection"; const auto size = SceKernelPageSize * 3; const auto base = Common::VirtualMemory::Alloc(0, size, Common::VirtualMemory::Mode::ReadWrite); Check(test, base != 0, "program host allocation failed"); Libs::LibKernel::Memory::RegisterProgramMemory(base, size, Common::VirtualMemory::Mode::ReadWrite, "program_test"); ExpectRange(test, Query(test, base), base, base + size, SceKernelProtCpuRead | SceKernelProtCpuRw, 0, 0, 0, 1, "program_test"); Libs::LibKernel::Memory::UpdateProgramMemoryProtection( base, SceKernelPageSize, Common::VirtualMemory::Mode::Read); ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, SceKernelProtCpuRead, 0, 0, 0, 1, "program_test"); CheckOk(test, Libs::LibKernel::Memory::KernelMprotect( reinterpret_cast(base + SceKernelPageSize - 0x10), 0x20, SceKernelProtCpuRead | SceKernelProtCpuRw), "KernelMprotect(program split span)"); ExpectRange(test, Query(test, base), base, base + size, SceKernelProtCpuRead | SceKernelProtCpuRw, 0, 0, 0, 1, "program_test"); Libs::LibKernel::Memory::UpdateProgramMemoryProtection( base + SceKernelPageSize * 2, SceKernelPageSize, Common::VirtualMemory::Mode::Read); ExpectRange(test, Query(test, base + SceKernelPageSize * 2), base + SceKernelPageSize * 2, base + size, SceKernelProtCpuRead, 0, 0, 0, 1, "program_test"); Libs::LibKernel::Memory::UnregisterProgramMemory(base, size); ExpectUnmapped(test, base); Check(test, Common::VirtualMemory::Free(base), "program host free failed"); std::printf("[host] %-48s ok\n", test); } } // namespace int main() { InitSubsystems(); RunTest(TestProsperoArgumentAndInfoSizeContracts); RunTest(TestFlexibleMapQueryAndWholeMunmap); RunTest(TestPartialFlexibleMunmapAndFindNext); RunTest(TestReserveMapFixedAndNoOverwrite); RunTest(TestFixedNoOverwriteRejectsReservedRange); RunTest(TestReleasedReserveCanBeReused); RunTest(TestMunmapAcrossAdjacentFlexibleMappings); RunTest(TestDirectMapQueryOffsetAndPartialMunmap); RunTest(TestNonzeroDirectOffsetAliasesSharedBacking); RunTest(TestDirectPhysicalFreeRangeReuseAndCoalescing); RunTest(TestDirectAlignmentStaysWithinSearchRange); RunTest(TestDefaultDirectMapUsesSystemAddressRange); RunTest(TestLargeDirectMapAliasesAcrossChunks); RunTest(TestDirectMapUnmapReusesHostAddress); RunTest(TestLargeHintedReserveHostsSmallDirectMap); RunTest(TestMemoryPoolAlignmentContracts); RunTest(TestProsperoSampleMemoryPoolExpandCommit); RunTest(TestFragmentedMemoryPoolBacking); RunTest(TestMemoryPoolMultiRangeDecommit); RunTest(TestMemoryPoolCommitDecommitQueryFlags); RunTest(TestProgramMemoryRegistrationAndProtection); if (g_failed_tests != 0) { std::printf("VirtualMemoryAllocationTests: %d case(s) failed\n", g_failed_tests); return 1; } std::printf("VirtualMemoryAllocationTests: all cases passed\n"); return 0; }