Files
ppsspp/Core/HLE/sceKernelHeap.cpp
T
Henrik RydgårdandClaude Opus 5 f449fa6ab2 Implement a batch of the VSH's unresolved imports (and document three we must not)
Mostly small stubs:

- sceImpose: GetParam/SetParam/Changes/SetStatus, plus the 6.60 alias of
  sceImposeGetBatteryIconStatus. Also fixes that function's first output - it
  is a plain "is it charging" boolean, not a BATTICON_ value. We wrote
  PSP_IMPOSE_BATTICON_NONE (0x80000000) there, which games ignore but which the
  VSH reads as "no battery" and draws the empty-battery indicator for. These
  are the bulk of the traffic: the VSH calls sceImposeChanges once a frame, so
  this alone removes ~10000 trapped calls from a boot.
- SysMemForKernel: sceKernelSetRebootKernel, sceKernelSetUmdCacheOn.
- scePower_driver: scePowerSetWakeupCondition.
- sceHprm_driver, sceUsb: one NID-named call each, as in JPCSP.

Three groups are deliberately left unresolved, with comments explaining why,
because resolving them lets real flash0 drivers walk into hardware we do not
emulate and the boot dies where it used to reach the shell:

- ThreadManForKernel mutex/fpl NIDs: the NAND and ID storage drivers use these
  to init, then poll the NAND controller at 0xbd101300 forever.
- InterruptManagerForKernel intr registration: 31 calls, then a stall in GE
  list execution with no plugin module ever started.

73 unresolved import hits over 37 distinct module/NID pairs remain in a VSH
boot, mostly sceSysEventForKernel, sceSuspendForKernel and the various
*_driver modules that need real hardware behind them.

The sceImpose savestate section goes to v2 for the two new state variables.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 10:58:35 +02:00

201 lines
8.5 KiB
C++

#include <string>
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/StringUtils.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelHeap.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/Reporting.h"
#include "Core/Util/BlockAllocator.h"
static const u32 KERNEL_HEAP_BLOCK_HEADER_SIZE = 8;
static const bool g_fromBottom = false;
// This object and the functions here are available for kernel code only, not game code.
// This differs from code like sceKernelMutex, which is available for games.
// This exists in PPSSPP mainly because certain game patches use these kernel modules.
struct KernelHeap : public KernelObject {
int uid = 0;
int partitionId = 0;
u32 size = 0;
int flags = 0;
u32 address = 0;
std::string name;
BlockAllocator alloc;
static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_UID; }
static int GetStaticIDType() { return PPSSPP_KERNEL_TMID_Heap; }
int GetIDType() const override { return PPSSPP_KERNEL_TMID_Heap; }
const char *GetTypeName() override { return GetStaticTypeName(); }
static const char *GetStaticTypeName() { return "Heap"; }
void DoState(PointerWrap &p) override {
Do(p, uid);
Do(p, partitionId);
Do(p, size);
Do(p, flags);
Do(p, address);
Do(p, name);
Do(p, alloc);
}
};
static int sceKernelCreateHeap(int partitionId, int size, int flags, const char *Name) {
u32 allocSize = (size + 3) & ~3;
BlockAllocator *allocator = BlockAllocatorFromID(partitionId);
// TODO: Validate error code.
if (!allocator)
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition");
// TODO: This should probably actually use flags? Name?
u32 addr = allocator->Alloc(allocSize, g_fromBottom, StringFromFormat("KernelHeap/%s", Name).c_str());
if (addr == (u32)-1) {
// TODO: Validate error code.
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_NO_MEMORY, "fFailed to allocate %d bytes of memory", size);
}
KernelHeap *heap = new KernelHeap();
SceUID uid = kernelObjects.Create(heap);
heap->partitionId = partitionId;
heap->flags = flags;
heap->name = Name ? Name : ""; // Not sure if this needs validation.
heap->size = allocSize;
heap->address = addr;
heap->alloc.Init(heap->address + 128, heap->size - 128, true);
heap->uid = uid;
return hleLogInfo(Log::sceKernel, uid);
}
static int sceKernelAllocHeapMemory(int heapId, int size) {
u32 error;
KernelHeap *heap = kernelObjects.Get<KernelHeap>(heapId, error);
if (!heap) {
return hleLogError(Log::sceKernel, error, "invalid heapId");
}
// There's 8 bytes at the end of every block, reserved.
u32 memSize = KERNEL_HEAP_BLOCK_HEADER_SIZE + size;
u32 addr = heap->alloc.Alloc(memSize, true);
return hleLogInfo(Log::sceKernel, addr);
}
static int sceKernelDeleteHeap(int heapId) {
u32 error;
KernelHeap *heap = kernelObjects.Get<KernelHeap>(heapId, error);
if (!heap)
return hleLogError(Log::sceKernel, error, "invalid heapId");
// Not using heap->partitionId here for backwards compatibility with old save states.
BlockAllocator *allocator = BlockAllocatorFromAddr(heap->address);
if (allocator)
allocator->Free(heap->address);
kernelObjects.Destroy<KernelHeap>(heap->uid);
return hleLogInfo(Log::sceKernel, 0);
}
static u32 sceKernelPartitionTotalFreeMemSize(int partitionId) {
BlockAllocator *allocator = BlockAllocatorFromID(partitionId);
// TODO: Validate error code.
if (!allocator)
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition");
return hleLogWarning(Log::sceKernel, allocator->GetTotalFreeBytes());
}
static u32 sceKernelPartitionMaxFreeMemSize(int partitionId) {
BlockAllocator *allocator = BlockAllocatorFromID(partitionId);
// TODO: Validate error code.
if (!allocator)
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition");
return hleLogWarning(Log::sceKernel, allocator->GetLargestFreeBlockSize());
}
static u32 sceKernelGetUidmanCB()
{
ERROR_LOG_REPORT(Log::sceKernel, "UNIMP sceKernelGetUidmanCB");
return 0;
}
static int sceKernelFreeHeapMemory(int heapId, u32 block) {
u32 error;
KernelHeap* heap = kernelObjects.Get<KernelHeap>(heapId, error);
if (!heap)
return hleLogError(Log::sceKernel, error, "invalid heapId");
if (block == 0) {
return hleLogInfo(Log::sceKernel, 0, "heapId,0: block");
}
if (!heap->alloc.FreeExact(block)) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_INVALID_POINTER, "invalid pointer %08x", block);
}
return hleLogInfo(Log::sceKernel, 0, "heapId, block");
}
static int sceKernelAllocHeapMemoryWithOption(int heapId, u32 memSize, u32 paramsPtr) {
u32 error;
KernelHeap* heap = kernelObjects.Get<KernelHeap>(heapId, error);
if (!heap)
return hleLogError(Log::sceKernel, error, "invalid heapId");
u32 grain = 4;
// 0 is ignored.
if (paramsPtr != 0) {
if (!Memory::IsValid4AlignedRange(paramsPtr, 8))
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "invalid paramsPtr");
u32 size = Memory::ReadUnchecked_U32(paramsPtr); // size of the params struct
if (size < 8)
return hleLogError(Log::sceKernel, 0, "invalid param size");
if (size > 8)
WARN_LOG(Log::HLE, "sceKernelAllocHeapMemoryWithOption(): unexpected param size %d", size);
grain = Memory::ReadUnchecked_U32(paramsPtr + 4);
}
INFO_LOG(Log::HLE, "sceKernelAllocHeapMemoryWithOption(%08x, %08x, %08x)", heapId, memSize, paramsPtr);
// There's 8 bytes at the end of every block, reserved.
memSize += 8;
u32 addr = heap->alloc.AllocAligned(memSize, grain, grain, true);
return addr;
}
static int sceKernelGetModel() {
constexpr u32 model = 2; // 2 = original slim.
return hleLogWarning(Log::sceKernel, model - 1);
}
// Both configure things PPSSPP has no equivalent of - which kernel image a reboot would use, and
// whether the UMD read cache is on. Accepted and ignored; the VSH calls them once each during
// startup and only cares that they succeed.
static int sceKernelSetRebootKernel(u32 arg) {
return hleLogWarning(Log::sceKernel, 0, "UNIMPL");
}
static int sceKernelSetUmdCacheOn(int on) {
return hleLogWarning(Log::sceKernel, 0, "UNIMPL");
}
const HLEFunction SysMemForKernel[] = {
{ 0X96A3CE2C, &WrapI_U<sceKernelSetRebootKernel>, "sceKernelSetRebootKernel", 'i', "x", HLE_KERNEL_SYSCALL },
{ 0X1404C1AA, &WrapI_I<sceKernelSetUmdCacheOn>, "sceKernelSetUmdCacheOn", 'i', "i", HLE_KERNEL_SYSCALL },
{ 0X636C953B, &WrapI_II<sceKernelAllocHeapMemory>, "sceKernelAllocHeapMemory", 'x', "ii", HLE_KERNEL_SYSCALL },
{ 0XC9805775, &WrapI_I<sceKernelDeleteHeap>, "sceKernelDeleteHeap", 'i', "i" , HLE_KERNEL_SYSCALL },
{ 0X1C1FBFE7, &WrapI_IIIC<sceKernelCreateHeap>, "sceKernelCreateHeap", 'i', "iixs", HLE_KERNEL_SYSCALL },
{ 0X237DBD4F, &WrapI_ICIUU<sceKernelAllocPartitionMemory>, "sceKernelAllocPartitionMemory", 'i', "isixx", HLE_KERNEL_SYSCALL },
{ 0XB6D61D02, &WrapI_I<sceKernelFreePartitionMemory>, "sceKernelFreePartitionMemory", 'i', "i", HLE_KERNEL_SYSCALL },
{ 0X9D9A5BA1, &WrapU_I<sceKernelGetBlockHeadAddr>, "sceKernelGetBlockHeadAddr", 'x', "i", HLE_KERNEL_SYSCALL },
{ 0x9697CD32, &WrapU_I<sceKernelPartitionTotalFreeMemSize>, "sceKernelPartitionTotalFreeMemSize", 'x', "i" , HLE_KERNEL_SYSCALL },
{ 0xE6581468, &WrapU_I<sceKernelPartitionMaxFreeMemSize>, "sceKernelPartitionMaxFreeMemSize", 'x', "i" , HLE_KERNEL_SYSCALL },
{ 0X3FC9AE6A, &WrapU_V<sceKernelDevkitVersion>, "sceKernelDevkitVersion", 'x', "" , HLE_KERNEL_SYSCALL },
{ 0X536AD5E1, &WrapU_V<sceKernelGetUidmanCB>, "sceKernelGetUidmanCB", 'i', "i" , HLE_KERNEL_SYSCALL },
{ 0X7B749390, &WrapI_IU<sceKernelFreeHeapMemory>, "sceKernelFreeHeapMemory", 'i', "ix" , HLE_KERNEL_SYSCALL },
{ 0XEB7A74DB, &WrapI_IUU<sceKernelAllocHeapMemoryWithOption>, "sceKernelAllocHeapMemoryWithOption", 'i', "ixp" , HLE_KERNEL_SYSCALL },
{ 0x6373995d, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL}, // 220
{ 0x07C586A1, &WrapI_V<sceKernelGetModel>, "sceKernelGetModel", 'i', "", HLE_KERNEL_SYSCALL }, // 220
};
void Register_SysMemForKernel() {
RegisterHLEModule("SysMemForKernel", ARRAY_SIZE(SysMemForKernel), SysMemForKernel);
}