mirror of
https://github.com/iHaiDeeZ/OpenCE-PS4.git
synced 2026-10-26 18:01:06 +01:00
243 lines
8.0 KiB
C
243 lines
8.0 KiB
C
// PSChrome replacement for the OpenOrbis malloc.
|
|
//
|
|
// The OpenOrbis version maps one fixed 2.5 GB block of flexible memory on the
|
|
// first allocation and, when that fails (it does on FW 12.00/13.52), calls
|
|
// Sony's allocator with a NULL heap and crashes. This version sizes the heap
|
|
// from whatever memory pool the process can actually get and fails cleanly
|
|
// (NULL + ENOMEM) instead of crashing.
|
|
//
|
|
// Every block carries a 16-byte header (base pointer + requested size) so that
|
|
// aligned allocations, realloc and malloc_usable_size work without Sony's
|
|
// non-exported sceLibcMspaceMallocUsableSize.
|
|
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
|
|
typedef void* SceLibcMspace;
|
|
SceLibcMspace sceLibcMspaceCreate(const char* name, void* base, size_t capacity, unsigned int flags);
|
|
void* sceLibcMspaceMalloc(SceLibcMspace msp, size_t size);
|
|
int sceLibcMspaceFree(SceLibcMspace msp, void* ptr);
|
|
|
|
size_t sceKernelGetDirectMemorySize(void);
|
|
int sceKernelAvailableDirectMemorySize(int64_t searchStart, int64_t searchEnd, size_t alignment, int64_t* physAddrOut, size_t* sizeOut);
|
|
int sceKernelAllocateDirectMemory(int64_t searchStart, int64_t searchEnd, size_t len, size_t alignment, int memoryType, int64_t* physAddrOut);
|
|
int sceKernelMapDirectMemory(void** addr, size_t len, int prot, int flags, int64_t directMemoryStart, size_t alignment);
|
|
int sceKernelReleaseDirectMemory(int64_t start, size_t len);
|
|
int sceKernelAvailableFlexibleMemorySize(size_t* sizeOut);
|
|
int sceKernelMapNamedFlexibleMemory(void** addr, size_t len, int prot, int flags, const char* name);
|
|
|
|
#define MB ((size_t)1024 * 1024)
|
|
enum { kProtCpuReadWrite = 0x03, kMemoryTypeWbOnion = 0 };
|
|
|
|
// Direct memory left untouched for the GPU (Piglet compositor needs 512 MB+),
|
|
// audio, the system and WebKit's own mappings.
|
|
static const size_t kDirectReserve = 2560 * MB;
|
|
static const size_t kMaxHeap = 2048 * MB;
|
|
static const size_t kMinHeap = 64 * MB;
|
|
|
|
static SceLibcMspace g_mspace;
|
|
static volatile int g_initState; // 0 = not started, 1 = running, 2 = done
|
|
static const char* g_heapSource = "none";
|
|
static size_t g_heapSize;
|
|
static size_t g_directAvailable;
|
|
static size_t g_flexibleAvailable;
|
|
|
|
// Header placed immediately before every pointer we hand out.
|
|
typedef struct {
|
|
void* base;
|
|
size_t size;
|
|
} BlockHeader;
|
|
_Static_assert(sizeof(BlockHeader) == 16, "header keeps 16-byte alignment");
|
|
|
|
static int tryDirectMemory(void** base, size_t* size) {
|
|
size_t total = sceKernelGetDirectMemorySize();
|
|
int64_t phys = 0;
|
|
size_t largest = 0;
|
|
if (sceKernelAvailableDirectMemorySize(0, (int64_t)total, 2 * MB, &phys, &largest) != 0)
|
|
return 0;
|
|
g_directAvailable = largest;
|
|
if (largest < kDirectReserve + kMinHeap)
|
|
return 0;
|
|
size_t want = largest - kDirectReserve;
|
|
if (want > kMaxHeap)
|
|
want = kMaxHeap;
|
|
want &= ~(2 * MB - 1);
|
|
if (sceKernelAllocateDirectMemory(0, (int64_t)total, want, 2 * MB, kMemoryTypeWbOnion, &phys) != 0)
|
|
return 0;
|
|
void* addr = NULL;
|
|
if (sceKernelMapDirectMemory(&addr, want, kProtCpuReadWrite, 0, phys, 2 * MB) != 0) {
|
|
sceKernelReleaseDirectMemory(phys, want);
|
|
return 0;
|
|
}
|
|
*base = addr;
|
|
*size = want;
|
|
return 1;
|
|
}
|
|
|
|
static int tryFlexibleMemory(void** base, size_t* size) {
|
|
size_t available = 0;
|
|
if (sceKernelAvailableFlexibleMemorySize(&available) != 0)
|
|
return 0;
|
|
g_flexibleAvailable = available;
|
|
// Leave half for mmap users (WebKit's JavaScript heap, SDL, ...).
|
|
size_t want = (available / 2) & ~(16 * 1024 - 1);
|
|
if (want < kMinHeap)
|
|
return 0;
|
|
void* addr = NULL;
|
|
if (sceKernelMapNamedFlexibleMemory(&addr, want, kProtCpuReadWrite, 0, "DolphinPS4 heap") != 0)
|
|
return 0;
|
|
*base = addr;
|
|
*size = want;
|
|
return 1;
|
|
}
|
|
|
|
static int tryAnonymousMmap(void** base, size_t* size) {
|
|
for (size_t want = 256 * MB; want >= kMinHeap; want /= 2) {
|
|
void* addr = mmap(NULL, want, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
|
if (addr != MAP_FAILED) {
|
|
*base = addr;
|
|
*size = want;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void initHeap(void) {
|
|
void* base = NULL;
|
|
size_t size = 0;
|
|
sceKernelAvailableFlexibleMemorySize(&g_flexibleAvailable);
|
|
if (tryDirectMemory(&base, &size))
|
|
g_heapSource = "direct memory";
|
|
else if (tryFlexibleMemory(&base, &size))
|
|
g_heapSource = "flexible memory";
|
|
else if (tryAnonymousMmap(&base, &size))
|
|
g_heapSource = "anonymous mmap";
|
|
else
|
|
return;
|
|
g_mspace = sceLibcMspaceCreate("DolphinPS4", base, size, 0);
|
|
if (g_mspace)
|
|
g_heapSize = size;
|
|
else
|
|
g_heapSource = "mspace creation failed";
|
|
}
|
|
|
|
static SceLibcMspace heap(void) {
|
|
if (__atomic_load_n(&g_initState, __ATOMIC_ACQUIRE) == 2)
|
|
return g_mspace;
|
|
int expected = 0;
|
|
if (__atomic_compare_exchange_n(&g_initState, &expected, 1, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
|
|
initHeap();
|
|
__atomic_store_n(&g_initState, 2, __ATOMIC_RELEASE);
|
|
} else {
|
|
while (__atomic_load_n(&g_initState, __ATOMIC_ACQUIRE) != 2) {
|
|
}
|
|
}
|
|
return g_mspace;
|
|
}
|
|
|
|
static BlockHeader* headerOf(void* ptr) { return (BlockHeader*)ptr - 1; }
|
|
|
|
static void* allocateAligned(size_t alignment, size_t size) {
|
|
SceLibcMspace msp = heap();
|
|
if (!msp)
|
|
goto outOfMemory;
|
|
if (size == 0)
|
|
size = 1;
|
|
if (alignment < 16)
|
|
alignment = 16;
|
|
size_t extra = sizeof(BlockHeader) + (alignment > 16 ? alignment : 0);
|
|
if (size > SIZE_MAX - extra)
|
|
goto outOfMemory;
|
|
void* base = sceLibcMspaceMalloc(msp, size + extra);
|
|
if (!base)
|
|
goto outOfMemory;
|
|
uintptr_t user = ((uintptr_t)base + sizeof(BlockHeader) + alignment - 1) & ~(uintptr_t)(alignment - 1);
|
|
BlockHeader* header = headerOf((void*)user);
|
|
header->base = base;
|
|
header->size = size;
|
|
return (void*)user;
|
|
|
|
outOfMemory:
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
void* malloc(size_t size) { return allocateAligned(16, size); }
|
|
|
|
void free(void* ptr) {
|
|
if (!ptr || !g_mspace)
|
|
return;
|
|
sceLibcMspaceFree(g_mspace, headerOf(ptr)->base);
|
|
}
|
|
|
|
void* calloc(size_t count, size_t size) {
|
|
if (size && count > SIZE_MAX / size) {
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
void* ptr = malloc(count * size);
|
|
if (ptr)
|
|
memset(ptr, 0, count * size);
|
|
return ptr;
|
|
}
|
|
|
|
void* realloc(void* ptr, size_t size) {
|
|
if (!ptr)
|
|
return malloc(size);
|
|
if (size == 0) {
|
|
free(ptr);
|
|
return NULL;
|
|
}
|
|
size_t old = headerOf(ptr)->size;
|
|
if (size <= old && size >= old / 2) {
|
|
headerOf(ptr)->size = size;
|
|
return ptr;
|
|
}
|
|
void* grown = malloc(size);
|
|
if (!grown)
|
|
return NULL;
|
|
memcpy(grown, ptr, old < size ? old : size);
|
|
free(ptr);
|
|
return grown;
|
|
}
|
|
|
|
void* __memalign(size_t alignment, size_t size) {
|
|
if (alignment & (alignment - 1)) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
return allocateAligned(alignment, size);
|
|
}
|
|
|
|
void* memalign(size_t alignment, size_t size) { return __memalign(alignment, size); }
|
|
void* aligned_alloc(size_t alignment, size_t size) { return __memalign(alignment, size); }
|
|
|
|
int posix_memalign(void** out, size_t alignment, size_t size) {
|
|
if (alignment < sizeof(void*) || (alignment & (alignment - 1)))
|
|
return EINVAL;
|
|
void* ptr = allocateAligned(alignment, size);
|
|
if (!ptr)
|
|
return ENOMEM;
|
|
*out = ptr;
|
|
return 0;
|
|
}
|
|
|
|
void* valloc(size_t size) { return allocateAligned(16 * 1024, size); }
|
|
|
|
size_t malloc_usable_size(void* ptr) { return ptr ? headerOf(ptr)->size : 0; }
|
|
|
|
// OpenOrbis' malloc.o exports these; define them so it is never linked in.
|
|
int malloc_init(void) { return heap() ? 0 : 1; }
|
|
int malloc_finalize(void) { return 0; }
|
|
int __malloc_replaced;
|
|
|
|
// Diagnostics for test apps.
|
|
const char* ps4rt_heap_source(void) { heap(); return g_heapSource; }
|
|
size_t ps4rt_heap_size(void) { heap(); return g_heapSize; }
|
|
size_t ps4rt_direct_available(void) { heap(); return g_directAvailable; }
|
|
size_t ps4rt_flexible_available(void) { heap(); return g_flexibleAvailable; }
|