Files
AnyPS5/core/libs/tests/GuestPageTableStats.cpp
T
hugodearaujo 5106b98df6 fix(libkernel): count page-table entries the way the console does
A PS5 (firmware 13.60) reports 8192 entries per side. It takes one cpu entry for each 2 MiB granule holding a committed mapping of any protection, and one gpu entry for each 1 GiB region holding a GPU-accessible mapping plus one for each granule of such a mapping that is not a whole granule of direct memory at a 2 MiB-aligned physical address. Shared granules and regions count once. sceKernelGetPageTableStats reported 2048 entries and charged each range per granule, CPU-accessible ones only to the cpu side.
2026-10-16 09:49:46 -03:00

121 lines
4.8 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <source_location>
extern "C" {
int APS5_VABI sceKernelGetPageTableStats(int*, int*, int*, int*);
int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int);
int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t);
int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*);
int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t);
int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t);
int APS5_VABI sceKernelMprotect(const void*, std::size_t, int);
int APS5_VABI sceKernelMunmap(void*, std::size_t);
}
static void Require(bool condition, std::source_location location = std::source_location::current()) {
if (!condition) {
std::fprintf(stderr, "Page table stats check failed at %s:%u\n", location.file_name(), static_cast<unsigned>(location.line()));
std::abort();
}
}
constexpr std::size_t PageTableGranule = 0x200000;
constexpr std::size_t GpuDirectorySpan = 0x40000000;
constexpr int PageTableTotal = 8192;
struct Usage {
int cpu;
int gpu;
};
static Usage Used() {
int cpuTotal = 0, cpuAvailable = 0, gpuTotal = 0, gpuAvailable = 0;
Require(sceKernelGetPageTableStats(&cpuTotal, &cpuAvailable, &gpuTotal, &gpuAvailable) == 0);
Require(cpuTotal == PageTableTotal && gpuTotal == PageTableTotal);
Require(cpuAvailable >= 0 && cpuAvailable <= cpuTotal && gpuAvailable >= 0 && gpuAvailable <= gpuTotal);
return {cpuTotal - cpuAvailable, gpuTotal - gpuAvailable};
}
static void RequireTaken(const Usage& before, int cpu, int gpu, std::source_location location = std::source_location::current()) {
const auto now = Used();
Require(now.cpu - before.cpu == cpu && now.gpu - before.gpu == gpu, location);
}
static unsigned char* Reserve() {
void* reserved = nullptr;
Require(sceKernelReserveVirtualRange(&reserved, GpuDirectorySpan * 2, 0, GpuDirectorySpan) == 0);
Require(reinterpret_cast<std::uintptr_t>(reserved) % GpuDirectorySpan == 0);
return static_cast<unsigned char*>(reserved);
}
static void Map(unsigned char* at, std::size_t len, int prot) {
void* address = at;
Require(sceKernelMapFlexibleMemory(&address, len, prot, 0x10) == 0 && address == at);
}
static void CheckRejectedArguments() {
int value = 0;
Require(sceKernelGetPageTableStats(nullptr, &value, &value, &value) == SCE_KERNEL_ERROR_EFAULT);
Require(sceKernelGetPageTableStats(&value, nullptr, &value, &value) == SCE_KERNEL_ERROR_EFAULT);
Require(sceKernelGetPageTableStats(&value, &value, nullptr, &value) == SCE_KERNEL_ERROR_EFAULT);
Require(sceKernelGetPageTableStats(&value, &value, &value, nullptr) == SCE_KERNEL_ERROR_EFAULT);
}
static void CheckCpuGranules() {
auto* base = Reserve();
const auto before = Used();
Map(base, 0x4000, 3);
Map(base + 0x4000, 0x4000, 3);
Map(base + 0x8000, 0x4000, 1);
RequireTaken(before, 1, 0);
Map(base + PageTableGranule, 0x4000, 0);
RequireTaken(before, 2, 0);
Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0);
RequireTaken(before, 0, 0);
}
static void CheckGpuEntries() {
auto* base = Reserve();
const auto before = Used();
Map(base, 3 * PageTableGranule, 3);
RequireTaken(before, 3, 0);
Require(sceKernelMprotect(base, 3 * PageTableGranule, 0x30) == 0);
RequireTaken(before, 3, 4);
Map(base + GpuDirectorySpan, 0x4000, 0x33);
RequireTaken(before, 4, 6);
Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0);
RequireTaken(before, 0, 0);
}
static void CheckGpuDirectMapping() {
auto* base = Reserve();
const auto before = Used();
std::int64_t phys = 0;
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, 3 * PageTableGranule, PageTableGranule, 0, &phys) == 0);
Require(phys % static_cast<std::int64_t>(PageTableGranule) == 0);
void* large = base;
Require(sceKernelMapDirectMemory(&large, 2 * PageTableGranule, 0x33, 0x10, phys, PageTableGranule) == 0 && large == base);
RequireTaken(before, 2, 1);
void* small = base + 2 * PageTableGranule + 0x10000;
Require(sceKernelMapDirectMemory(&small, 0x10000, 0x33, 0x10, phys + static_cast<std::int64_t>(2 * PageTableGranule), 0x10000) == 0);
Require(small == base + 2 * PageTableGranule + 0x10000);
RequireTaken(before, 3, 2);
Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0);
Require(sceKernelReleaseDirectMemory(phys, 3 * PageTableGranule) == 0);
RequireTaken(before, 0, 0);
}
int main() {
CheckRejectedArguments();
CheckCpuGranules();
CheckGpuEntries();
CheckGpuDirectMapping();
return 0;
}