mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
intr/registersub, re-recorded on a 6.61 PSP with every test in the directory rebuilt, finds no handler on interrupt 8 where the old recording found one that didn't take user sub-interrupts. The old one was probably made on an earlier firmware, whose drivers hooked it. Follow 6.61, the firmware PPSSPP models. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1053 lines
38 KiB
C++
1053 lines
38 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);
|
|
// This is mtic a0, $0, which only looks at bit 0 (tests/intr/mfic).
|
|
if (enable & 1)
|
|
{
|
|
__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);
|
|
}
|
|
}
|
|
|
|
int __CancelRaisedInterrupts(PSPInterrupt intno) {
|
|
int count = 0;
|
|
auto it = pendingInterrupts.begin();
|
|
// The one at the front is what's running, if anything is, and gets popped when it returns.
|
|
if (inInterrupt && it != pendingInterrupts.end())
|
|
++it;
|
|
while (it != pendingInterrupts.end()) {
|
|
if (it->intr == intno) {
|
|
it = pendingInterrupts.erase(it);
|
|
count++;
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// What a user mode caller finds on each interrupt, as interruptman.prx checks it: whether a driver
|
|
// has installed a handler for the interrupt at all, whether it has sub-interrupt slots, and whether
|
|
// user handlers are allowed in them. This is system state rather than a rule, read back from
|
|
// intr/registersub and intr/releasesub on a 6.61 PSP (running PSPLink, whose USB drivers may count.)
|
|
// Interrupt 8 had a handler in an older recording and doesn't now, so treat this as approximate.
|
|
enum class IntrUserAccess : u8 {
|
|
NO_HANDLER, // SCE_KERNEL_ERROR_NOTFOUND_HANDLER
|
|
NO_SUBS, // SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, the sub number is always out of range
|
|
KERNEL_SUBS, // SCE_KERNEL_ERROR_ILLEGAL_INTRCODE to register, empty slots to release
|
|
USER,
|
|
};
|
|
|
|
static IntrUserAccess GetIntrUserAccess(u32 intrNumber) {
|
|
switch (intrNumber) {
|
|
case PSP_GE_INTR:
|
|
case PSP_VBLANK_INTR:
|
|
return IntrUserAccess::USER;
|
|
case 4: case 6: case 21:
|
|
return IntrUserAccess::KERNEL_SUBS;
|
|
case 7: case 10: case 12: case 15: case 16: case 17: case 18: case 19: case 20: case 22:
|
|
case 23: case 24: case 26: case 31: case 36: case 50: case 56: case 57: case 58: case 59:
|
|
case 60: case 61: case 65:
|
|
return IntrUserAccess::NO_SUBS;
|
|
default:
|
|
return IntrUserAccess::NO_HANDLER;
|
|
}
|
|
}
|
|
|
|
// The vblank slots a user handler may take (the rest are the kernel's), and those the display
|
|
// driver already holds.
|
|
static bool IsUserVblankSubIntr(u32 subIntrNumber) {
|
|
return subIntrNumber < 16;
|
|
}
|
|
static bool IsKernelHeldVblankSubIntr(u32 subIntrNumber) {
|
|
return (subIntrNumber >= 18 && subIntrNumber <= 20) || (subIntrNumber >= 24 && subIntrNumber <= 26);
|
|
}
|
|
|
|
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");
|
|
}
|
|
switch (GetIntrUserAccess(intrNumber)) {
|
|
case IntrUserAccess::NO_HANDLER:
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_NOTFOUND_HANDLER, "no handler for this interrupt");
|
|
case IntrUserAccess::NO_SUBS:
|
|
case IntrUserAccess::KERNEL_SUBS:
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "no user subinterrupts");
|
|
case IntrUserAccess::USER:
|
|
break;
|
|
}
|
|
if (subIntrNumber >= PSP_NUMBER_SUBINTERRUPTS) {
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid subinterrupt");
|
|
}
|
|
if (intrNumber == PSP_VBLANK_INTR) {
|
|
if (IsKernelHeldVblankSubIntr(subIntrNumber))
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_FOUND_HANDLER, "held by the kernel");
|
|
if (!IsUserVblankSubIntr(subIntrNumber))
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "kernel only 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");
|
|
}
|
|
const IntrUserAccess access = GetIntrUserAccess(intrNumber);
|
|
if (access == IntrUserAccess::NO_HANDLER) {
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_NOTFOUND_HANDLER, "no handler for this interrupt");
|
|
}
|
|
if (access == IntrUserAccess::NO_SUBS || subIntrNumber >= PSP_NUMBER_SUBINTERRUPTS) {
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE, "invalid subinterrupt");
|
|
}
|
|
// User code can't have put anything in the kernel's slots, and can't release what's there.
|
|
if (access == IntrUserAccess::KERNEL_SUBS || (intrNumber == PSP_VBLANK_INTR && !IsUserVblankSubIntr(subIntrNumber))) {
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_NOTFOUND_HANDLER, "not a user 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);
|
|
}
|
|
gpu->NotifyVideoCopy(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)) {
|
|
char *dstp = (char *)Memory::GetPointerWriteUnchecked(dst);
|
|
const char *srcp = Memory::GetCharPointerUnchecked(src);
|
|
// The string checks above only cover the strings as they are - the concatenation is longer,
|
|
// and has to fit too, or we'd write past the end of guest memory.
|
|
const size_t dstLen = strlen(dstp);
|
|
const size_t size = dstLen + strlen(srcp) + 1;
|
|
if (!Memory::IsValidRange(dst, (u32)size)) {
|
|
return hleLogError(Log::sceKernel, dst, "result doesn't fit at %08x", dst);
|
|
}
|
|
memcpy(dstp + dstLen, srcp, size - dstLen);
|
|
}
|
|
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::IsValidNullTerminatedString(src)) {
|
|
const char *srcp = Memory::GetCharPointerUnchecked(src);
|
|
// Note: the destination has to fit the whole string. IsValidAddress would only check one byte.
|
|
const size_t size = strlen(srcp) + 1;
|
|
if (!Memory::IsValidRange(dst, (u32)size)) {
|
|
return hleLogError(Log::sceKernel, dst, "string doesn't fit at %08x", dst);
|
|
}
|
|
memcpy(Memory::GetPointerWriteUnchecked(dst), srcp, size);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
static int sysclib_sprintf_impl(u32 dst, int limit, u32 fmt, int firstVarArg) {
|
|
std::string result;
|
|
if (!HLEFormatPrintf(fmt, firstVarArg, &result)) {
|
|
return 0;
|
|
}
|
|
|
|
const size_t retval = result.size();
|
|
|
|
// Implement the snprintf length check.
|
|
// Note: > 0, not != 0. A negative size from snprintf would resize() to a huge value and throw.
|
|
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.
|
|
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);
|
|
// dst is a0 and fmt a1, so the varargs start at a2.
|
|
return hleLogDebug(Log::sceKernel, sysclib_sprintf_impl(dst, 0, fmt, 2));
|
|
}
|
|
|
|
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, 3));
|
|
}
|
|
|
|
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);
|
|
}
|