Files
ppsspp/Core/HLE/sceKernelInterrupt.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

1092 lines
37 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <list>
#include <map>
#include <string>
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/Serialize/SerializeList.h"
#include "Common/Serialize/SerializeMap.h"
#include "Core/MemMapHelpers.h"
#include "Core/Reporting.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/MIPS/MIPS.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelInterrupt.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/sceKernelMutex.h"
#include "GPU/GPUCommon.h"
#include "GPU/GPUState.h"
// Seems like some > 16 are taken but not available. Probably kernel only?
static const u32 PSP_NUMBER_SUBINTERRUPTS = 32;
// InterruptsManager
//////////////////////////////////////////////////////////////////////////
// INTERRUPT MANAGEMENT
//////////////////////////////////////////////////////////////////////////
class InterruptState {
public:
void save();
void restore();
void clear();
void DoState(PointerWrap &p) {
auto s = p.Section("InterruptState", 1);
if (!s)
return;
Do(p, savedCpu);
}
PSPThreadContext savedCpu;
};
// STATE
InterruptState intState;
IntrHandler* intrHandlers[PSP_NUMBER_INTERRUPTS];
std::list<PendingInterrupt> pendingInterrupts;
// Yeah, this bit is a bit silly.
static int interruptsEnabled = 1;
static bool inInterrupt;
static SceUID threadBeforeInterrupt;
static int sceKernelCpuSuspendIntr()
{
VERBOSE_LOG(Log::sceIntc, "sceKernelCpuSuspendIntr");
int returnValue;
if (__InterruptsEnabled())
{
returnValue = 1;
__DisableInterrupts();
}
else
{
returnValue = 0;
}
hleEatCycles(15);
return hleNoLog(returnValue);
}
static void sceKernelCpuResumeIntr(u32 enable)
{
VERBOSE_LOG(Log::sceIntc, "sceKernelCpuResumeIntr(%i)", enable);
if (enable)
{
__EnableInterrupts();
hleRunInterrupts();
hleReSchedule("interrupts resumed");
}
else
{
__DisableInterrupts();
}
hleEatCycles(15);
hleNoLogVoid();
}
static int sceKernelIsCpuIntrEnable() {
u32 retVal = __InterruptsEnabled();
return hleLogVerbose(Log::sceIntc, retVal);
}
static int sceKernelIsCpuIntrSuspended(int flag)
{
int retVal = flag == 0 ? 1 : 0;
return hleLogDebug(Log::sceIntc, retVal);
}
static void sceKernelCpuResumeIntrWithSync(u32 enable)
{
// Just a forward, don't bother with hleCall.
sceKernelCpuResumeIntr(enable);
}
bool IntrHandler::run(PendingInterrupt& pend)
{
SubIntrHandler *handler = get(pend.subintr);
if (!handler) {
WARN_LOG(Log::sceIntc, "Ignoring interrupt, already been released.");
return false;
}
copyArgsToCPU(pend);
return true;
}
void IntrHandler::copyArgsToCPU(PendingInterrupt& pend)
{
SubIntrHandler* handler = get(pend.subintr);
DEBUG_LOG(Log::CPU, "Entering interrupt handler %08x", handler->handlerAddress);
currentMIPS->pc = handler->handlerAddress;
currentMIPS->r[MIPS_REG_A0] = handler->subIntrNumber;
currentMIPS->r[MIPS_REG_A1] = handler->handlerArg;
// RA is already taken care of
}
void IntrHandler::handleResult(PendingInterrupt& pend)
{
//u32 result = currentMIPS->r[MIPS_REG_V0];
}
SubIntrHandler* IntrHandler::add(int subIntrNum)
{
return &subIntrHandlers[subIntrNum];
}
void IntrHandler::remove(int subIntrNum)
{
if (has(subIntrNum))
{
subIntrHandlers.erase(subIntrNum);
}
}
bool IntrHandler::has(int subIntrNum) const
{
return subIntrHandlers.find(subIntrNum) != subIntrHandlers.end();
}
void IntrHandler::enable(int subIntrNum)
{
subIntrHandlers[subIntrNum].enabled = true;
}
void IntrHandler::disable(int subIntrNum)
{
subIntrHandlers[subIntrNum].enabled = false;
}
SubIntrHandler* IntrHandler::get(int subIntrNum)
{
if (has(subIntrNum))
return &subIntrHandlers[subIntrNum];
else
return NULL;
}
void IntrHandler::clear()
{
subIntrHandlers.clear();
}
void IntrHandler::queueUp(int subintr) {
if (subintr == PSP_INTR_SUB_NONE) {
pendingInterrupts.push_back(PendingInterrupt(intrNumber, subintr));
} else {
// Just call execute on all the subintr handlers for this interrupt.
// They will get queued up.
for (auto iter = subIntrHandlers.begin(); iter != subIntrHandlers.end(); ++iter) {
if ((subintr == PSP_INTR_SUB_ALL || iter->first == subintr) && iter->second.enabled && iter->second.handlerAddress != 0) {
pendingInterrupts.push_back(PendingInterrupt(intrNumber, iter->first));
}
}
}
}
void IntrHandler::DoState(PointerWrap &p)
{
auto s = p.Section("IntrHandler", 1);
if (!s)
return;
Do(p, intrNumber);
Do<int, SubIntrHandler>(p, subIntrHandlers);
}
void PendingInterrupt::DoState(PointerWrap &p)
{
auto s = p.Section("PendingInterrupt", 1);
if (!s)
return;
Do(p, intr);
Do(p, subintr);
}
void __InterruptsInit()
{
interruptsEnabled = 1;
inInterrupt = false;
for (int i = 0; i < (int)ARRAY_SIZE(intrHandlers); ++i)
intrHandlers[i] = new IntrHandler(i);
intState.clear();
threadBeforeInterrupt = 0;
}
void __InterruptsDoState(PointerWrap &p)
{
auto s = p.Section("sceKernelInterrupt", 1);
if (!s)
return;
int numInterrupts = PSP_NUMBER_INTERRUPTS;
Do(p, numInterrupts);
if (numInterrupts != PSP_NUMBER_INTERRUPTS)
{
p.SetError(p.ERROR_FAILURE);
ERROR_LOG(Log::sceIntc, "Savestate failure: wrong number of interrupts, can't load.");
return;
}
intState.DoState(p);
PendingInterrupt pi(0, 0);
Do(p, pendingInterrupts, pi);
Do(p, interruptsEnabled);
Do(p, inInterrupt);
Do(p, threadBeforeInterrupt);
}
void __InterruptsDoStateLate(PointerWrap &p)
{
// We do these later to ensure the handlers have been registered.
for (int i = 0; i < PSP_NUMBER_INTERRUPTS; ++i)
intrHandlers[i]->DoState(p);
p.DoMarker("sceKernelInterrupt Late");
}
void __InterruptsShutdown()
{
for (size_t i = 0; i < ARRAY_SIZE(intrHandlers); ++i)
intrHandlers[i]->clear();
for (size_t i = 0; i < ARRAY_SIZE(intrHandlers); ++i)
{
if (intrHandlers[i])
{
delete intrHandlers[i];
intrHandlers[i] = 0;
}
}
pendingInterrupts.clear();
}
void __DisableInterrupts()
{
interruptsEnabled = 0;
}
void __EnableInterrupts()
{
interruptsEnabled = 1;
}
bool __InterruptsEnabled()
{
return interruptsEnabled != 0;
}
bool __IsInInterrupt()
{
return inInterrupt;
}
void InterruptState::save()
{
__KernelSaveContext(&savedCpu, true);
}
void InterruptState::restore()
{
__KernelLoadContext(&savedCpu, true);
}
void InterruptState::clear()
{
savedCpu.reset();
}
// http://forums.ps2dev.org/viewtopic.php?t=5687
// http://www.google.se/url?sa=t&rct=j&q=&esrc=s&source=web&cd=7&ved=0CFYQFjAG&url=http%3A%2F%2Fdev.psnpt.com%2Fredmine%2Fprojects%2Fuofw%2Frepository%2Frevisions%2F65%2Fraw%2Ftrunk%2Finclude%2Finterruptman.h&ei=J4pCUKvyK4nl4QSu-YC4Cg&usg=AFQjCNFxJcgzQnv6dK7aiQlht_BM9grfQQ&sig2=GGk5QUEWI6qouYDoyE07YQ
// Returns true if anything was executed.
bool __RunOnePendingInterrupt()
{
bool needsThreadReturn = false;
if (inInterrupt || !interruptsEnabled) {
// Already in an interrupt! We'll keep going when it's done.
return false;
}
// Can easily prioritize between different kinds of interrupts if necessary.
retry:
if (!pendingInterrupts.empty()) {
PendingInterrupt pend = pendingInterrupts.front();
IntrHandler* handler = intrHandlers[pend.intr];
if (handler == NULL) {
WARN_LOG(Log::sceIntc, "Ignoring interrupt");
pendingInterrupts.pop_front();
goto retry;
}
// If we came from CoreTiming::Advance(), we might've come from a waiting thread's callback.
// To avoid "injecting" return values into our saved state, we context switch here.
SceUID savedThread = __KernelGetCurThread();
if (__KernelSwitchOffThread("interrupt")) {
threadBeforeInterrupt = savedThread;
needsThreadReturn = true;
}
intState.save();
inInterrupt = true;
if (!handler->run(pend)) {
pendingInterrupts.pop_front();
inInterrupt = false;
goto retry;
}
currentMIPS->r[MIPS_REG_RA] = __KernelInterruptReturnAddress();
return true;
} else {
if (needsThreadReturn)
__KernelSwitchToThread(threadBeforeInterrupt, "left interrupt");
// DEBUG_LOG(Log::sceIntc, "No more interrupts!");
return false;
}
}
static void __TriggerRunInterrupts(int type)
{
// If interrupts aren't enabled, we run them later.
if (interruptsEnabled && !inInterrupt)
{
if ((type & PSP_INTR_HLE) != 0)
hleRunInterrupts();
else if ((type & PSP_INTR_ALWAYS_RESCHED) != 0)
{
// "Always" only means if dispatch is enabled.
if (!__RunOnePendingInterrupt() && __KernelIsDispatchEnabled())
{
SceUID savedThread = __KernelGetCurThread();
if (__KernelSwitchOffThread("interrupt"))
threadBeforeInterrupt = savedThread;
}
}
else
__RunOnePendingInterrupt();
}
}
void __TriggerInterrupt(int type, PSPInterrupt intno, int subintr)
{
if (interruptsEnabled || (type & PSP_INTR_ONLY_IF_ENABLED) == 0)
{
intrHandlers[intno]->queueUp(subintr);
VERBOSE_LOG(Log::sceIntc, "Triggering subinterrupts for interrupt %i sub %i (%i in queue)", intno, subintr, (u32)pendingInterrupts.size());
__TriggerRunInterrupts(type);
}
}
void __KernelReturnFromInterrupt()
{
VERBOSE_LOG(Log::sceIntc, "Left interrupt handler at %08x", currentMIPS->pc);
hleSkipDeadbeef();
// This is what we just ran.
PendingInterrupt pend = pendingInterrupts.front();
pendingInterrupts.pop_front();
if (pend.intr >= 0 && pend.intr < ARRAY_SIZE(intrHandlers)) {
intrHandlers[pend.intr]->handleResult(pend);
} else {
_assert_msg_(false, "Bad pend.intr: %d", pend.intr);
}
inInterrupt = false;
// Restore context after running the interrupt.
intState.restore();
// All should now be back to normal, including PC.
// Alright, let's see if there's any more interrupts queued...
if (!__RunOnePendingInterrupt())
{
// Otherwise, we reschedule when dispatch was enabled, or switch back otherwise.
if (__KernelIsDispatchEnabled())
__KernelReSchedule("left interrupt");
else
__KernelSwitchToThread(threadBeforeInterrupt, "left interrupt");
}
hleNoLogVoid();
}
void __RegisterIntrHandler(u32 intrNumber, IntrHandler* handler)
{
delete intrHandlers[intrNumber];
intrHandlers[intrNumber] = handler;
}
SubIntrHandler *__RegisterSubIntrHandler(u32 intrNumber, u32 subIntrNumber, u32 handler, u32 handlerArg, u32 &error) {
if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
error = SCE_KERNEL_ERROR_ILLEGAL_INTRCODE;
return NULL;
}
IntrHandler *intr = intrHandlers[intrNumber];
if (intr->has(subIntrNumber)) {
if (intr->get(subIntrNumber)->handlerAddress != 0) {
error = SCE_KERNEL_ERROR_FOUND_HANDLER;
return NULL;
} else {
SubIntrHandler *subIntrHandler = intr->get(subIntrNumber);
subIntrHandler->handlerAddress = handler;
subIntrHandler->handlerArg = handlerArg;
error = SCE_KERNEL_ERROR_OK;
return subIntrHandler;
}
}
SubIntrHandler *subIntrHandler = intr->add(subIntrNumber);
subIntrHandler->subIntrNumber = subIntrNumber;
subIntrHandler->intrNumber = intrNumber;
subIntrHandler->handlerAddress = handler;
subIntrHandler->handlerArg = handlerArg;
subIntrHandler->enabled = false;
error = SCE_KERNEL_ERROR_OK;
return subIntrHandler;
}
int __ReleaseSubIntrHandler(int intrNumber, int subIntrNumber) {
if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
return SCE_KERNEL_ERROR_ILLEGAL_INTRCODE;
}
IntrHandler *intr = intrHandlers[intrNumber];
if (!intr->has(subIntrNumber) || intr->get(subIntrNumber)->handlerAddress == 0) {
return SCE_KERNEL_ERROR_NOTFOUND_HANDLER;
}
for (auto it = pendingInterrupts.begin(); it != pendingInterrupts.end(); ) {
if (it->intr == intrNumber && it->subintr == subIntrNumber) {
pendingInterrupts.erase(it++);
} else {
++it;
}
}
// This also implicitly disables it, which is correct.
intrHandlers[intrNumber]->remove(subIntrNumber);
return 0;
}
u32 sceKernelRegisterSubIntrHandler(u32 intrNumber, u32 subIntrNumber, u32 handler, u32 handlerArg) {
if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid interrupt");
}
if (subIntrNumber >= PSP_NUMBER_SUBINTERRUPTS) {
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid subinterrupt");
}
u32 error;
SubIntrHandler *subIntrHandler = __RegisterSubIntrHandler(intrNumber, subIntrNumber, handler, handlerArg, error);
if (subIntrHandler) {
if (handler == 0) {
return hleLogWarning(Log::sceIntc, error, "ignored NULL handler");
} else {
return hleLogDebug(Log::sceIntc, error);
}
} else if (error == SCE_KERNEL_ERROR_FOUND_HANDLER) {
// Pretty common. Used to report here, but not useful.
return hleLogError(Log::sceIntc, error, "duplicate handler");
}
return hleReportError(Log::sceIntc, error);
}
u32 sceKernelReleaseSubIntrHandler(u32 intrNumber, u32 subIntrNumber) {
if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid interrupt");
}
if (subIntrNumber >= PSP_NUMBER_SUBINTERRUPTS) {
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid subinterrupt");
}
u32 error = __ReleaseSubIntrHandler(intrNumber, subIntrNumber);
return hleLogDebugOrError(Log::sceIntc, error);
}
u32 sceKernelEnableSubIntr(u32 intrNumber, u32 subIntrNumber) {
if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid interrupt");
}
if (subIntrNumber >= PSP_NUMBER_SUBINTERRUPTS) {
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid subinterrupt");
}
u32 error;
if (!intrHandlers[intrNumber]->has(subIntrNumber)) {
// Enabling a handler before registering it works fine.
__RegisterSubIntrHandler(intrNumber, subIntrNumber, 0, 0, error);
}
intrHandlers[intrNumber]->enable(subIntrNumber);
return hleLogDebug(Log::sceIntc, 0);
}
static u32 sceKernelDisableSubIntr(u32 intrNumber, u32 subIntrNumber) {
if (intrNumber >= PSP_NUMBER_INTERRUPTS) {
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid interrupt");
}
if (subIntrNumber >= PSP_NUMBER_SUBINTERRUPTS) {
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid subinterrupt");
}
if (!intrHandlers[intrNumber]->has(subIntrNumber)) {
// Disabling when not registered is not an error.
return 0;
}
intrHandlers[intrNumber]->disable(subIntrNumber);
return hleLogDebug(Log::sceIntc, 0);
}
struct PspIntrHandlerOptionParam {
int size; //+00
u32 entry; //+04
u32 common; //+08
u32 gp; //+0C
u16 intr_code; //+10
u16 sub_count; //+12
u16 intr_level; //+14
u16 enabled; //+16
u32 calls; //+18
u32 field_1C; //+1C
u32 total_clock_lo; //+20
u32 total_clock_hi; //+24
u32 min_clock_lo; //+28
u32 min_clock_hi; //+2C
u32 max_clock_lo; //+30
u32 max_clock_hi; //+34
}; //=38
static int QueryIntrHandlerInfo()
{
ERROR_LOG_REPORT(Log::sceIntc, "QueryIntrHandlerInfo()");
return 0;
}
static u32 sceKernelMemset(u32 addr, u32 fillc, u32 n) {
u8 c = fillc & 0xff;
bool skip = false;
if (n != 0) {
if (Memory::IsVRAMAddress(addr)) {
skip = gpu->PerformMemorySet(addr, fillc, n);
}
if (!skip) {
Memory::Memset(addr, c, n);
}
}
NotifyMemInfo(MemBlockFlags::WRITE, addr, n, "KernelMemset");
return hleLogDebug(Log::sceKernel, addr);
}
static u32 sceKernelMemcpy(u32 dst, u32 src, u32 size) {
// Some games copy from executable code. We need to flush emuhack ops.
if (size != 0) {
currentMIPS->InvalidateICacheRangeDeferred(src, size);
}
bool skip = false;
if (Memory::IsVRAMAddress(src) || Memory::IsVRAMAddress(dst)) {
skip = gpu->PerformMemoryCopy(dst, src, size);
}
// Technically should crash if these are invalid and size > 0...
if (!skip && Memory::IsValidAddress(dst) && Memory::IsValidAddress(src) && Memory::IsValidAddress(dst + size - 1) && Memory::IsValidAddress(src + size - 1)) {
u8 *dstp = Memory::GetPointerWriteUnchecked(dst);
const u8 *srcp = Memory::GetPointerUnchecked(src);
// If it's non-overlapping, just do it in one go.
if (dst + size < src || src + size < dst) {
memcpy(dstp, srcp, size);
} else {
// Try to handle overlapped copies with similar properties to hardware, just in case.
// Not that anyone ought to rely on it.
for (u32 size64 = size / 8; size64 > 0; --size64)
{
memmove(dstp, srcp, 8);
dstp += 8;
srcp += 8;
}
for (u32 size8 = size % 8; size8 > 0; --size8)
*dstp++ = *srcp++;
}
}
if (MemBlockInfoDetailed(size)) {
NotifyMemInfoCopy(dst, src, size, "KernelMemcpy/");
}
return hleLogDebug(Log::sceKernel, dst);
}
const HLEFunction Kernel_Library[] =
{
{0x092968F4, &WrapI_V<sceKernelCpuSuspendIntr>, "sceKernelCpuSuspendIntr", 'i', "" },
{0X5F10D406, &WrapV_U<sceKernelCpuResumeIntr>, "sceKernelCpuResumeIntr", 'v', "x" },
{0X3B84732D, &WrapV_U<sceKernelCpuResumeIntrWithSync>, "sceKernelCpuResumeIntrWithSync", 'v', "x" },
{0X47A0B729, &WrapI_I<sceKernelIsCpuIntrSuspended>, "sceKernelIsCpuIntrSuspended", 'i', "i" },
{0xb55249d2, &WrapI_V<sceKernelIsCpuIntrEnable>, "sceKernelIsCpuIntrEnable", 'i', "", },
{0XA089ECA4, &WrapU_UUU<sceKernelMemset>, "sceKernelMemset", 'x', "xxx" },
{0XDC692EE3, &WrapI_UI<sceKernelTryLockLwMutex>, "sceKernelTryLockLwMutex", 'i', "xi" },
{0X37431849, &WrapI_UI<sceKernelTryLockLwMutex_600>, "sceKernelTryLockLwMutex_600", 'i', "xi" },
{0XBEA46419, &WrapI_UIU<sceKernelLockLwMutex>, "sceKernelLockLwMutex", 'i', "xix", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
{0X1FC64E09, &WrapI_UIU<sceKernelLockLwMutexCB>, "sceKernelLockLwMutexCB", 'i', "xix", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
{0X15B6446B, &WrapI_UI<sceKernelUnlockLwMutex>, "sceKernelUnlockLwMutex", 'i', "xi" },
{0XC1734599, &WrapI_UU<sceKernelReferLwMutexStatus>, "sceKernelReferLwMutexStatus", 'i', "xp" },
{0X293B45B8, &WrapI_V<sceKernelGetThreadId>, "sceKernelGetThreadId", 'i', "" },
{0XD13BDE95, &WrapI_V<sceKernelCheckThreadStack>, "sceKernelCheckThreadStack", 'i', "" },
{0X1839852A, &WrapU_UUU<sceKernelMemcpy>, "sceKernelMemcpy", 'x', "xxx" },
{0XFA835CDE, &WrapI_I<sceKernelGetTlsAddr>, "sceKernelGetTlsAddr", 'i', "i" },
{0X05572A5F, &WrapV_V<sceKernelExitGame>, "sceKernelExitGame", 'v', "" },
{0X4AC57943, &WrapI_I<sceKernelRegisterExitCallback>, "sceKernelRegisterExitCallback", 'i', "i" },
};
static u32 sysclib_memcpy(u32 dst, u32 src, u32 size) {
if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) {
memcpy(Memory::GetPointerWriteUnchecked(dst), Memory::GetPointerUnchecked(src), size);
}
if (MemBlockInfoDetailed(size)) {
NotifyMemInfoCopy(dst, src, size, "KernelMemcpy/");
}
return dst;
}
static u32 sysclib_strcat(u32 dst, u32 src) {
if (Memory::IsValidNullTerminatedString(dst) && Memory::IsValidNullTerminatedString(src)) {
strcat((char *)Memory::GetPointerWriteUnchecked(dst), (const char *)Memory::GetPointerUnchecked(src));
}
return hleLogVerbose(Log::sceKernel, dst);
}
static int sysclib_strcmp(u32 dst, u32 src) {
if (Memory::IsValidNullTerminatedString(dst) && Memory::IsValidNullTerminatedString(src)) {
const int retval = strcmp((const char *)Memory::GetPointerUnchecked(dst), (const char *)Memory::GetPointerUnchecked(src));
return hleLogVerbose(Log::sceKernel, retval);
} else {
// What to do? Crash, probably.
return hleLogError(Log::sceKernel, 0);
}
}
static u32 sysclib_strcpy(u32 dst, u32 src) {
ERROR_LOG(Log::sceKernel, "Untested sysclib_strcpy(dest=%08x, src=%08x)", dst, src);
if (Memory::IsValidAddress(dst) && Memory::IsValidNullTerminatedString(src)) {
strcpy((char *)Memory::GetPointerWriteUnchecked(dst), (const char *)Memory::GetPointerUnchecked(src));
}
return hleLogVerbose(Log::sceKernel, dst);
}
static u32 sysclib_strlen(u32 src) {
if (Memory::IsValidNullTerminatedString(src)) { // TODO: This computes the length, could reuse it maybe.
const u32 retval = (u32)strlen(Memory::GetCharPointerUnchecked(src));
return hleLogVerbose(Log::sceKernel, retval);
} else {
// What to do? Crash, probably.
return hleLogError(Log::sceKernel, 0);
}
}
static int sysclib_memcmp(u32 dst, u32 src, u32 size) {
if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) {
const int retval = memcmp(Memory::GetCharPointerUnchecked(dst), Memory::GetCharPointerUnchecked(src), size);
return hleLogVerbose(Log::sceKernel, retval);
} else {
// What to do? Crash, probably.
return hleLogError(Log::sceKernel, 0);
}
}
// NOTE: This doesn't yet obey the limit parameter, needed for correct snprintf behavior.
static int sysclib_sprintf_impl(u32 dst, int limit, u32 fmt, int paramOffset) {
if (!Memory::IsValidNullTerminatedString(fmt)) {
ERROR_LOG(Log::sceKernel, "sysclib_sprintf bad fmt");
return 0;
}
VERBOSE_LOG(Log::sceKernel, "sysclib_sprintf fmt: %s", Memory::GetCharPointerUnchecked(fmt));
VERBOSE_LOG(Log::sceKernel, "sysclib_sprintf a0-a4, t0-t4: 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x",
currentMIPS->r[MIPS_REG_A0],
currentMIPS->r[MIPS_REG_A1],
currentMIPS->r[MIPS_REG_A2],
currentMIPS->r[MIPS_REG_A3],
currentMIPS->r[MIPS_REG_T0],
currentMIPS->r[MIPS_REG_T1],
currentMIPS->r[MIPS_REG_T2],
currentMIPS->r[MIPS_REG_T3]
);
bool processing_specifier = false;
std::string specifier = "";
int bytes_to_read = 0;
int arg_idx = paramOffset;
std::string result = "";
for (const char *c = Memory::GetCharPointerUnchecked(fmt); *c != '\0'; c++) {
if (!processing_specifier) {
if (*c == '%') {
specifier = "%";
processing_specifier = true;
bytes_to_read = 0;
} else {
result.append(1, *c);
}
} else {
specifier.append(1, *c);
// going by https://cplusplus.com/reference/cstdio/printf/#compatibility
// no idea what the kernel module really supports as of writing this
switch (*c) {
case '%':
{
result.append(specifier);
processing_specifier = false;
break;
}
case 's':
{
// consume 4 bytes from arguments
u32 val = 0;
if (arg_idx <= 1) {
val = currentMIPS->r[MIPS_REG_A2 + arg_idx];
} else if(arg_idx <= 5) {
val = currentMIPS->r[MIPS_REG_T0 + arg_idx - 2];
} else {
int stack_idx = arg_idx - 6;
u32 stack_cur = currentMIPS->r[MIPS_REG_SP] + stack_idx * 4;
if (!Memory::IsValid4AlignedAddress(stack_cur)) {
ERROR_LOG(Log::sceKernel, "sysclib_sprintf bad stack pointer %08x", stack_cur);
return 0;
}
val = Memory::ReadUnchecked_U32(stack_cur);
VERBOSE_LOG(Log::sceKernel, "sysclib_sprintf fetching %08x from sp + %u", val, stack_idx * 4);
}
arg_idx++;
if (!Memory::IsValidNullTerminatedString(val)) {
ERROR_LOG(Log::sceKernel, "sysclib_sprintf bad string reference at %08x", val);
return 0;
}
result.append(Memory::GetCharPointerUnchecked(val));
processing_specifier = false;
break;
}
case 'd':
case 'i':
case 'u':
case 'o':
case 'x':
case 'X':
case 'f':
case 'e':
case 'E':
case 'g':
case 'G':
case 'c':
case 'p':
case 'n':
{
u64 val = 0;
if (bytes_to_read == 0) {
bytes_to_read = 4;
}
int read_cnt = 0;
while (bytes_to_read != 0) {
u32 val_from_arg = 0;
if (arg_idx <= 1) {
val_from_arg = currentMIPS->r[MIPS_REG_A2 + arg_idx];
} else if (arg_idx <= 5) {
val_from_arg = currentMIPS->r[MIPS_REG_T0 + arg_idx - 2];
} else {
int stack_idx = arg_idx - 6;
u32 stack_cur = currentMIPS->r[MIPS_REG_SP] + stack_idx * 4;
if (!Memory::IsValid4AlignedAddress(stack_cur)) {
ERROR_LOG(Log::sceKernel, "sysclib_sprintf bad stack pointer %08x", stack_cur);
return 0;
}
val_from_arg = Memory::ReadUnchecked_U32(stack_cur);
DEBUG_LOG(Log::sceKernel, "sysclib_sprintf fetching %08x from sp + %u", val_from_arg, stack_idx * 4);
}
arg_idx++;
val = val | ((u64)val_from_arg << (read_cnt * 32));
bytes_to_read = bytes_to_read - 4;
read_cnt++;
}
char buf[128] = {0};
snprintf(buf, sizeof(buf), specifier.c_str(), val);
buf[sizeof(buf) - 1] = '\0';
result.append(buf);
processing_specifier = false;
break;
}
case 'h':
{
// allegrex calling convention is 4 bytes aligned
bytes_to_read = 4;
break;
}
case 'l':
{
bytes_to_read = bytes_to_read + 4;
break;
}
}
}
}
const size_t retval = result.size();
// Implement the snprintf length check.
if (limit != 0 && (int)result.length() >= limit) {
result.resize(limit - 1);
}
VERBOSE_LOG(Log::sceKernel, "sysclib_sprintf result string has length %d (retval: %d), content:", (int)result.length(), (int)retval);
VERBOSE_LOG(Log::sceKernel, "%s", result.c_str());
// Since this is a sprintf function and not an actual printf, we don't log to the Sprintf log.
// INFO_LOG(Log::Printf, "%s", result.c_str());
if (!Memory::IsValidRange(dst, (u32)result.length() + 1)) {
ERROR_LOG(Log::sceKernel, "sysclib_sprintf result string is too long or dst is invalid");
return 0;
}
memcpy((char *)Memory::GetPointerUnchecked(dst), result.c_str(), (int)result.length() + 1);
return (int)retval;
}
static int sysclib_sprintf(u32 dst, u32 fmt) {
DEBUG_LOG(Log::sceKernel, "Not fully implemented: sysclib_sprintf(dst=%08x, fmt=%08x)", dst, fmt);
return hleLogDebug(Log::sceKernel, sysclib_sprintf_impl(dst, 0, fmt, 0));
}
static int sysclib_snprintf(u32 dst, int size, u32 fmt) {
DEBUG_LOG(Log::sceKernel, "Not fully implemented: sysclib_snprintf(dst=%08x, fmt=%08x)", dst, fmt);
return hleLogDebug(Log::sceKernel, sysclib_sprintf_impl(dst, size, fmt, 1));
}
static u32 sysclib_memset(u32 destAddr, int data, int size) {
if (Memory::IsValidRange(destAddr, size)) {
memset(Memory::GetPointerWriteUnchecked(destAddr), data, size);
}
NotifyMemInfo(MemBlockFlags::WRITE, destAddr, size, "KernelMemset");
return hleLogVerbose(Log::sceKernel, 0);
}
static int sysclib_strstr(u32 s1, u32 s2) {
if (Memory::IsValidNullTerminatedString(s1) && Memory::IsValidNullTerminatedString(s2)) {
std::string str1 = Memory::GetCharPointerUnchecked(s1);
std::string str2 = Memory::GetCharPointerUnchecked(s2);
size_t index = str1.find(str2);
if (index == str1.npos) {
return 0;
}
return s1 + (uint32_t)index;
}
return hleLogVerbose(Log::sceKernel, 0);
}
static int sysclib_strncmp(u32 s1, u32 s2, u32 size) {
if (Memory::IsValidRange(s1, size) && Memory::IsValidRange(s2, size)) {
const char * str1 = Memory::GetCharPointerUnchecked(s1);
const char * str2 = Memory::GetCharPointerUnchecked(s2);
const int retval = strncmp(str1, str2, size);
return hleLogVerbose(Log::sceKernel, retval);
}
return hleLogError(Log::sceKernel, 0, "Bad addresses");
}
static u32 sysclib_memmove(u32 dst, u32 src, u32 size) {
if (Memory::IsValidRange(dst, size) && Memory::IsValidRange(src, size)) {
memmove(Memory::GetPointerWriteUnchecked(dst), Memory::GetPointerUnchecked(src), size);
}
if (MemBlockInfoDetailed(size)) {
NotifyMemInfoCopy(dst, src, size, "KernelMemmove/");
}
return hleLogVerbose(Log::sceKernel, 0);
}
static u32 sysclib_strncpy(u32 dest, u32 src, u32 size) {
if (!Memory::IsValidAddress(dest) || !Memory::IsValidAddress(src)) {
return hleLogError(Log::sceKernel, 0, "invalid address");
}
// This is just regular strncpy, but being explicit to avoid warnings/safety fixes on missing null.
u32 i = 0;
u32 srcSize = Memory::ClampValidSizeAt(src, size);
const u8 *srcp = Memory::GetPointerUnchecked(src);
u8 *destp = Memory::GetPointerWriteUnchecked(dest);
for (i = 0; i < srcSize; ++i) {
u8 c = *srcp++;
if (c == 0)
break;
*destp++ = c;
}
u32 destSize = Memory::ClampValidSizeAt(dest, size);
for (; i < destSize; ++i) {
*destp++ = 0;
}
return hleLogVerbose(Log::sceKernel, dest);
}
static u32 sysclib_strtol(u32 strPtr, u32 endPtrPtr, int base) {
if (!Memory::IsValidNullTerminatedString(strPtr)) {
return hleLogError(Log::sceKernel, 0, "invalid address");
}
const char* str = Memory::GetCharPointer(strPtr);
char* end = nullptr;
int result = (int)strtol(str, &end, base);
if (Memory::IsValidRange(endPtrPtr, 4))
Memory::WriteUnchecked_U32(strPtr + (end - str), endPtrPtr);
return hleLogVerbose(Log::sceKernel, result);
}
static u32 sysclib_strchr(u32 src, int c) {
if (!Memory::IsValidNullTerminatedString(src)) {
return hleLogError(Log::sceKernel, 0, "invalid address");
}
const std::string str = Memory::GetCharPointer(src);
size_t cpos = str.find(str, c);
if (cpos == std::string::npos) {
return hleLogVerbose(Log::sceKernel, 0);
}
return hleLogVerbose(Log::sceKernel, src + (int)cpos);
}
static u32 sysclib_strrchr(u32 src, int c) {
if (!Memory::IsValidNullTerminatedString(src)) {
return hleLogError(Log::sceKernel, 0, "invalid address");
}
const std::string str = Memory::GetCharPointer(src);
size_t cpos = str.rfind(str, c);
if (cpos == std::string::npos) {
return hleLogVerbose(Log::sceKernel, 0);
}
return hleLogVerbose(Log::sceKernel, src + (int)cpos);
}
static u32 sysclib_toupper(u32 c) {
return hleLogVerbose(Log::sceKernel, toupper(c));
}
const HLEFunction SysclibForKernel[] =
{
{0xAB7592FF, &WrapU_UUU<sysclib_memcpy>, "memcpy", 'x', "xxx", HLE_KERNEL_SYSCALL },
{0x476FD94A, &WrapU_UU<sysclib_strcat>, "strcat", 'x', "xx", HLE_KERNEL_SYSCALL },
{0xC0AB8932, &WrapI_UU<sysclib_strcmp>, "strcmp", 'i', "xx", HLE_KERNEL_SYSCALL },
{0xEC6F1CF2, &WrapU_UU<sysclib_strcpy>, "strcpy", 'x', "xx", HLE_KERNEL_SYSCALL },
{0x52DF196C, &WrapU_U<sysclib_strlen>, "strlen", 'x', "x", HLE_KERNEL_SYSCALL },
{0x81D0D1F7, &WrapI_UUU<sysclib_memcmp>, "memcmp", 'i', "xxx", HLE_KERNEL_SYSCALL },
{0x7661E728, &WrapI_UU<sysclib_sprintf>, "sprintf", 'i', "xx", HLE_KERNEL_SYSCALL },
{0x10F3BB61, &WrapU_UII<sysclib_memset>, "memset", 'x', "xii", HLE_KERNEL_SYSCALL },
{0x0D188658, &WrapI_UU<sysclib_strstr>, "strstr", 'i', "xx", HLE_KERNEL_SYSCALL },
{0x7AB35214, &WrapI_UUU<sysclib_strncmp>, "strncmp", 'i', "xxx", HLE_KERNEL_SYSCALL },
{0xA48D2592, &WrapU_UUU<sysclib_memmove>, "memmove", 'x', "xxx", HLE_KERNEL_SYSCALL },
{0xB49A7697, &WrapU_UUU<sysclib_strncpy>, "strncpy", 'x', "xxi", HLE_KERNEL_SYSCALL },
{0x47DD934D, &WrapU_UUI<sysclib_strtol>, "strtol", 'x', "xxi", HLE_KERNEL_SYSCALL },
{0xB1DC2AE8, &WrapU_UI<sysclib_strchr>, "strchr", 'x', "xx", HLE_KERNEL_SYSCALL },
{0x4C0E0274, &WrapU_UI<sysclib_strrchr>, "strrchr", 'x', "xx", HLE_KERNEL_SYSCALL },
{0xCE2F7487, &WrapU_U<sysclib_toupper>, "toupper", 'x', "x", HLE_KERNEL_SYSCALL },
{0XC2145E80, &WrapI_UIU<sysclib_snprintf>, "snprintf", 'i', "xx", HLE_KERNEL_SYSCALL },
};
void Register_Kernel_Library()
{
RegisterHLEModule("Kernel_Library", ARRAY_SIZE(Kernel_Library), Kernel_Library);
}
void Register_SysclibForKernel()
{
RegisterHLEModule("SysclibForKernel", ARRAY_SIZE(SysclibForKernel), SysclibForKernel);
}
const HLEFunction InterruptManager[] =
{
{0XCA04A2B9, &WrapU_UUUU<sceKernelRegisterSubIntrHandler>, "sceKernelRegisterSubIntrHandler", 'x', "xxxx" },
{0XD61E6961, &WrapU_UU<sceKernelReleaseSubIntrHandler>, "sceKernelReleaseSubIntrHandler", 'x', "xx" },
{0XFB8E22EC, &WrapU_UU<sceKernelEnableSubIntr>, "sceKernelEnableSubIntr", 'x', "xx" },
{0X8A389411, &WrapU_UU<sceKernelDisableSubIntr>, "sceKernelDisableSubIntr", 'x', "xx" },
{0X5CB5A78B, nullptr, "sceKernelSuspendSubIntr", '?', "" },
{0X7860E0DC, nullptr, "sceKernelResumeSubIntr", '?', "" },
{0XFC4374B8, nullptr, "sceKernelIsSubInterruptOccurred", '?', "" },
{0xD2E8363F, &WrapI_V<QueryIntrHandlerInfo>, "QueryIntrHandlerInfo", 'i', "" }, // No sce prefix for some reason
{0XEEE43F47, nullptr, "sceKernelRegisterUserSpaceIntrStack", '?', "" },
};
void Register_InterruptManager()
{
RegisterHLEModule("InterruptManager", ARRAY_SIZE(InterruptManager), InterruptManager);
}
static int sceKernelIsIntrContext() {
return hleLogDebug(Log::sceKernel, __IsInInterrupt() ? 1 : 0);
}
const HLEFunction InterruptManagerForKernel[] =
{
{0x092968F4, &WrapI_V<sceKernelCpuSuspendIntr>, "sceKernelCpuSuspendIntr", 'i', "" ,HLE_KERNEL_SYSCALL },
{0X5F10D406, &WrapV_U<sceKernelCpuResumeIntr>, "sceKernelCpuResumeIntr", 'v', "x" ,HLE_KERNEL_SYSCALL },
{0X3B84732D, &WrapV_U<sceKernelCpuResumeIntrWithSync>, "sceKernelCpuResumeIntrWithSync", 'v', "x" ,HLE_KERNEL_SYSCALL },
{0X47A0B729, &WrapI_I<sceKernelIsCpuIntrSuspended>, "sceKernelIsCpuIntrSuspended", 'i', "i" ,HLE_KERNEL_SYSCALL },
{0xb55249d2, &WrapI_V<sceKernelIsCpuIntrEnable>, "sceKernelIsCpuIntrEnable", 'i', "", HLE_KERNEL_SYSCALL },
{0XA089ECA4, &WrapU_UUU<sceKernelMemset>, "sceKernelMemset", 'x', "xxx" ,HLE_KERNEL_SYSCALL },
{0XDC692EE3, &WrapI_UI<sceKernelTryLockLwMutex>, "sceKernelTryLockLwMutex", 'i', "xi" ,HLE_KERNEL_SYSCALL },
{0X37431849, &WrapI_UI<sceKernelTryLockLwMutex_600>, "sceKernelTryLockLwMutex_600", 'i', "xi" ,HLE_KERNEL_SYSCALL },
{0XBEA46419, &WrapI_UIU<sceKernelLockLwMutex>, "sceKernelLockLwMutex", 'i', "xix", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED | HLE_KERNEL_SYSCALL},
{0X1FC64E09, &WrapI_UIU<sceKernelLockLwMutexCB>, "sceKernelLockLwMutexCB", 'i', "xix", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED | HLE_KERNEL_SYSCALL},
{0X15B6446B, &WrapI_UI<sceKernelUnlockLwMutex>, "sceKernelUnlockLwMutex", 'i', "xi" ,HLE_KERNEL_SYSCALL },
{0XC1734599, &WrapI_UU<sceKernelReferLwMutexStatus>, "sceKernelReferLwMutexStatus", 'i', "xp" ,HLE_KERNEL_SYSCALL },
{0X293B45B8, &WrapI_V<sceKernelGetThreadId>, "sceKernelGetThreadId", 'i', "" ,HLE_KERNEL_SYSCALL },
{0XD13BDE95, &WrapI_V<sceKernelCheckThreadStack>, "sceKernelCheckThreadStack", 'i', "" ,HLE_KERNEL_SYSCALL },
{0X1839852A, &WrapU_UUU<sceKernelMemcpy>, "sceKernelMemcpy", 'x', "xxx" ,HLE_KERNEL_SYSCALL },
{0XFA835CDE, &WrapI_I<sceKernelGetTlsAddr>, "sceKernelGetTlsAddr", 'i', "i" ,HLE_KERNEL_SYSCALL },
{0X05572A5F, &WrapV_V<sceKernelExitGame>, "sceKernelExitGame", 'v', "" ,HLE_KERNEL_SYSCALL },
{0X4AC57943, &WrapI_I<sceKernelRegisterExitCallback>, "sceKernelRegisterExitCallback", 'i', "i" ,HLE_KERNEL_SYSCALL },
{0XFE28C6D9, &WrapI_V<sceKernelIsIntrContext>, "sceKernelIsIntrContext", 'i', "" ,HLE_KERNEL_SYSCALL },
// NOT added on purpose, even though JPCSP implements all four: sceKernelRegisterIntrHandler
// (0x58DD8978), sceKernelReleaseIntrHandler (0xF987B1F0), sceKernelEnableIntr (0x4D6E7305)
// and sceKernelDisableIntr (0xD774BA45). JPCSP can honour them because it emulates the
// interrupt controller as MMIO; we dispatch the few interrupts we emulate ourselves (see
// __RegisterIntrHandler and its callers in sceGe/sceKernelAlarm/sceKernelVTimer) and have no
// way to run a guest handler for one. Stubbing them to return success is therefore a lie the
// real flash0 drivers act on - measured while booting the VSH, they make 31 such calls
// (interrupts 4, 12, 15-18, 20-24, 31), and the boot then stalls in GE list execution without
// ever starting a plugin module, where leaving them unresolved reaches the shell.
};
void Register_InterruptManagerForKernel()
{
RegisterHLEModule("InterruptManagerForKernel", ARRAY_SIZE(InterruptManagerForKernel), InterruptManagerForKernel);
}