fix(libkernel): report the largest free direct memory block from sceKernelAvailableDirectMemorySize

The call returned the first free gap in the range instead of the largest free block.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
xXPBSXx 2026-10-11 00:58:28 +02:00
parent 91b1341319
commit aace6a3f38
4 changed files with 70 additions and 6 deletions

View File

@ -26,6 +26,7 @@ std::vector<DirectMemoryView> DirectMemoryViews(int64_t start, size_t len);
void UnmapDirectMemoryViews(const std::vector<DirectMemoryView>& views);
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);
bool DirectMemoryLargestFree(int64_t searchStart, int64_t searchEnd, size_t alignment, int64_t* startOut, size_t* sizeOut);
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, size_t alignment = PS5_PAGE_SIZE);

View File

@ -133,12 +133,11 @@ int APS5_VABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, in
int APS5_VABI sceKernelAvailableDirectMemorySize(int64_t search_start, int64_t search_end, size_t alignment, int64_t* phys_addr_out, size_t* size_out) {
if (!phys_addr_out || !size_out) return SCE_KERNEL_ERROR_EINVAL;
int64_t tmpPhys = 0;
int ret = DirectMemoryAlloc(search_start, search_end, PS5_PAGE_SIZE, alignment, -1, &tmpPhys);
if (ret != 0) { *phys_addr_out = 0; *size_out = 0; return ret; }
DirectMemoryFree(tmpPhys, PS5_PAGE_SIZE);
*phys_addr_out = tmpPhys;
*size_out = DirectMemoryFreeRun(static_cast<uint64_t>(tmpPhys), static_cast<uint64_t>(search_end));
int64_t start = 0;
size_t size = 0;
if (!DirectMemoryLargestFree(search_start, search_end, alignment, &start, &size)) { *phys_addr_out = 0; *size_out = 0; return SCE_KERNEL_ERROR_EAGAIN; }
*phys_addr_out = start;
*size_out = size;
return 0;
}

View File

@ -72,6 +72,34 @@ struct PhysicalMemoryPool {
}
}
bool LargestFree(int64_t searchStart, int64_t searchEnd, size_t alignment, int64_t* startOut, size_t* sizeOut) {
std::lock_guard<std::mutex> lock(_mutex);
if (searchStart < 0 || searchEnd <= searchStart) return false;
const uint64_t align = (alignment == 0) ? PS5_PAGE_SIZE : alignment;
const uint64_t limit = std::min<uint64_t>(static_cast<uint64_t>(searchEnd), DIRECT_MEMORY_SIZE);
uint64_t bestStart = 0;
uint64_t bestSize = 0;
uint64_t cur = (static_cast<uint64_t>(searchStart) + PS5_PAGE_SIZE - 1) & ~static_cast<uint64_t>(PS5_PAGE_SIZE - 1);
while (cur + PS5_PAGE_SIZE <= limit) {
if (!_isFree(cur, PS5_PAGE_SIZE)) {
cur += PS5_PAGE_SIZE;
continue;
}
uint64_t runEnd = cur;
while (runEnd + PS5_PAGE_SIZE <= limit && _isFree(runEnd, PS5_PAGE_SIZE)) runEnd += PS5_PAGE_SIZE;
const uint64_t alignedStart = (cur + align - 1) & ~(align - 1);
if (alignedStart < runEnd && runEnd - alignedStart > bestSize) {
bestStart = alignedStart;
bestSize = runEnd - alignedStart;
}
cur = runEnd;
}
if (bestSize == 0) return false;
*startOut = static_cast<int64_t>(bestStart);
*sizeOut = static_cast<size_t>(bestSize);
return true;
}
size_t FreeRun(uint64_t offset, uint64_t limit) {
std::lock_guard<std::mutex> lock(_mutex);
uint64_t cur = offset & ~static_cast<uint64_t>(PS5_PAGE_SIZE - 1);
@ -145,6 +173,10 @@ void DirectMemoryRetype(int64_t start, size_t len, int memoryType) {
PhysicalMemoryPool::Instance().Retype(static_cast<uint64_t>(start), len, memoryType);
}
bool DirectMemoryLargestFree(int64_t searchStart, int64_t searchEnd, size_t alignment, int64_t* startOut, size_t* sizeOut) {
return PhysicalMemoryPool::Instance().LargestFree(searchStart, searchEnd, alignment, startOut, sizeOut);
}
size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit) {
return PhysicalMemoryPool::Instance().FreeRun(offset, limit);
}

View File

@ -59,6 +59,7 @@ int APS5_VABI sceKernelClearVirtualRangeName(const void*, std::uint64_t);
int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*);
int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t);
int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t);
int APS5_VABI sceKernelAvailableDirectMemorySize(std::int64_t, std::int64_t, std::size_t, std::int64_t*, std::size_t*);
int APS5_VABI sceKernelCheckedReleaseDirectMemory(std::int64_t, std::size_t);
int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t);
int APS5_VABI sceKernelMemoryPoolReserve(void*, std::size_t, std::size_t, int, void**);
@ -1621,10 +1622,41 @@ static void CheckVirtualQueryPartialMunmap() {
Require(munmap_nid_postfix(memory + page * 2, page) == 0);
}
static void CheckAvailableDirectMemoryIsLargestFreeBlock() {
constexpr std::int64_t window = 0x4000000;
const std::int64_t top = static_cast<std::int64_t>(DIRECT_MEMORY_SIZE) - window;
std::int64_t first = -1;
std::int64_t gap = -1;
std::int64_t last = -1;
Require(sceKernelAllocateDirectMemory(top, top + window, 0x200000, 0x200000, 0, &first) == 0 && first == top);
Require(sceKernelAllocateDirectMemory(top, top + window, 0x400000, 0x200000, 0, &gap) == 0 && gap == top + 0x200000);
Require(sceKernelAllocateDirectMemory(top, top + window, 0x200000, 0x200000, 0, &last) == 0 && last == top + 0x600000);
Require(sceKernelReleaseDirectMemory(gap, 0x400000) == 0);
std::int64_t start = -1;
std::size_t size = 0;
Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, &start, &size) == 0);
Require(start == top + 0x800000 && size == static_cast<std::size_t>(window - 0x800000));
Require(sceKernelAvailableDirectMemorySize(top, top + 0x800000, 0, &start, &size) == 0);
Require(start == top + 0x200000 && size == 0x400000);
Require(sceKernelAvailableDirectMemorySize(top, top + window, 0x1000000, &start, &size) == 0);
Require(start == top + 0x1000000 && size == static_cast<std::size_t>(window - 0x1000000));
Require(sceKernelAvailableDirectMemorySize(top, top + 0x200000, 0, &start, &size) == SCE_KERNEL_ERROR_EAGAIN);
Require(start == 0 && size == 0);
Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, nullptr, &size) == SCE_KERNEL_ERROR_EINVAL);
Require(sceKernelReleaseDirectMemory(first, 0x200000) == 0);
Require(sceKernelReleaseDirectMemory(last, 0x200000) == 0);
Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, &start, &size) == 0);
Require(start == top && size == static_cast<std::size_t>(window));
}
int main() {
#ifdef _WIN32
_putenv_s("APS5_PIN_WAIT_MS", "1000");
#endif
CheckAvailableDirectMemoryIsLargestFreeBlock();
CheckVirtualQuerySplitFlexibleRanges();
CheckVirtualQueryForNonReadableGuestRange();
CheckReleaseFlexibleMemory();