mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-22 10:49:05 +02:00
Sysreg (0xBC100000) gets the ME's reset bit (0x4C bit 2), the soft interrupt (0x44, which interrupts the other CPU: the ME's controller from the main CPU, PSP_MECODEC_INTR from the ME), the hardware mutex (0x48, each CPU sets its own bit only while the other's is clear) and the clock and config registers it writes. The ME's interrupt controller (0xBC300000, write-1-to-clear, mask at 8) answers the ME only. All of it is the ME's state and stays unhandled without an ME. On the ME: interrupts to EBase with EPC and EXL, halt sleeping until an unmasked interrupt, and Count, which doesn't run: it stays 0 across 10ms with ERL set after reset and with Status cleared, and reads back what was written. Reset keeps the GPRs and sets Status ERL/BEV/SR, which homebrew relies on: mcidclan's samples run C on the stack Sony's ME code left. The values are from a kernel probe on a PSP-1000 (sp 0x801FFFE0, COP0 22 = 1, Status 0x20500404, mfic 0, which doesn't gate interrupts). The main CPU runs base interrupt handlers the way interruptman calls them (a0 intno, a1 arg, a2 interrupted pc; -2 disables), and interrupt 31 has a stand-in for me_wrapper's handler, which homebrew releases first. While the ME is emulated, any main CPU code may access hardware registers: homebrew calls its own code in kernel mode through a kcall module. This is a stopgap (g_kernelModeAnywhere, marked HACK): kernel mode should be part of the CPU's state, switched on while a kernel module's export runs. The memcpy/memmove replacements copy through MMIO when an end is a register address, for code copied to the boot page. mcidclan's me-soft-interrupt sample now runs, counting each interrupt in both directions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1163 lines
42 KiB
C++
1163 lines
42 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/MIPS/MeCore.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)
|
|
{
|
|
if (pend.subintr == PSP_INTR_SUB_NONE) {
|
|
if (!baseEnabled_ || baseHandler_ == 0 || baseHandler_ == FIRMWARE_HANDLER) {
|
|
return false;
|
|
}
|
|
// As interruptman calls it (6.61): the interrupt, the arg, the interrupted pc.
|
|
DEBUG_LOG(Log::CPU, "Entering base interrupt handler %08x", baseHandler_);
|
|
currentMIPS->pc = baseHandler_;
|
|
currentMIPS->r[MIPS_REG_A0] = intrNumber;
|
|
currentMIPS->r[MIPS_REG_A1] = baseArg_;
|
|
currentMIPS->r[MIPS_REG_A2] = intState.savedCpu.pc;
|
|
return true;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (pend.subintr != PSP_INTR_SUB_NONE)
|
|
return;
|
|
// From interruptman (6.61): -1 is done, -2 disables the interrupt, and n >= 0 runs the handler's
|
|
// own sub-interrupt n, which nothing here registers.
|
|
const int result = (int)currentMIPS->r[MIPS_REG_V0];
|
|
if (result == -2) {
|
|
enableBase(false);
|
|
} else if (result >= 0) {
|
|
WARN_LOG(Log::sceIntc, "Base handler for interrupt %d asked for sub-interrupt %d", intrNumber, result);
|
|
}
|
|
}
|
|
|
|
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();
|
|
setBaseHandler(0, 0);
|
|
}
|
|
|
|
int IntrHandler::queueUp(int subintr) {
|
|
if (subintr == PSP_INTR_SUB_NONE) {
|
|
pendingInterrupts.push_back(PendingInterrupt(intrNumber, subintr));
|
|
return 1;
|
|
}
|
|
// Just call execute on all the subintr handlers for this interrupt.
|
|
// They will get queued up.
|
|
int count = 0;
|
|
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));
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void IntrHandler::DoState(PointerWrap &p)
|
|
{
|
|
auto s = p.Section("IntrHandler", 1, 2);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, intrNumber);
|
|
Do<int, SubIntrHandler>(p, subIntrHandlers);
|
|
if (s >= 2) {
|
|
Do(p, baseHandler_);
|
|
Do(p, baseArg_);
|
|
Do(p, baseEnabled_);
|
|
} else {
|
|
setBaseHandler(0, 0);
|
|
}
|
|
if (p.mode == p.MODE_READ && (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS)) {
|
|
ERROR_LOG(Log::sceIntc, "Savestate failure: invalid interrupt handler %d", intrNumber);
|
|
p.SetError(p.ERROR_FAILURE);
|
|
}
|
|
}
|
|
|
|
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);
|
|
if (MeCore::g_meState) {
|
|
// me_wrapper's, in the firmware.
|
|
intrHandlers[PSP_MECODEC_INTR]->setBaseHandler(IntrHandler::FIRMWARE_HANDLER, 0);
|
|
intrHandlers[PSP_MECODEC_INTR]->enableBase(true);
|
|
}
|
|
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);
|
|
if (p.mode == p.MODE_READ) {
|
|
for (const PendingInterrupt &pend : pendingInterrupts) {
|
|
if (pend.intr < 0 || pend.intr >= PSP_NUMBER_INTERRUPTS) {
|
|
p.SetError(p.ERROR_FAILURE);
|
|
ERROR_LOG(Log::sceIntc, "Savestate failure: invalid pending interrupt %d", pend.intr);
|
|
pendingInterrupts.clear();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
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->downcount -= handler->entryCycles();
|
|
|
|
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())
|
|
{
|
|
// With no handler to run and return from, just pick a thread again. This used to
|
|
// switch to idle, and the interrupted thread sat there until some later event
|
|
// rescheduled: ~775us of every frame at a vblank with no handler registered.
|
|
__KernelReSchedule("interrupt");
|
|
}
|
|
}
|
|
else
|
|
__RunOnePendingInterrupt();
|
|
}
|
|
}
|
|
|
|
int __TriggerInterrupt(int type, PSPInterrupt intno, int subintr)
|
|
{
|
|
if (interruptsEnabled || (type & PSP_INTR_ONLY_IF_ENABLED) == 0)
|
|
{
|
|
// Returning from an interrupt clears the ll bit, handler or not (pspautotests cpu/lsu/llsc).
|
|
currentMIPS->llBit = 0;
|
|
int queued = 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);
|
|
return queued;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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(MIPSState *mips)
|
|
{
|
|
VERBOSE_LOG(Log::sceIntc, "Left interrupt handler at %08x", mips->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);
|
|
// Charged once the last queued handler for this interrupt is done.
|
|
bool moreOfThis = false;
|
|
for (const PendingInterrupt &other : pendingInterrupts) {
|
|
moreOfThis = moreOfThis || other.intr == pend.intr;
|
|
}
|
|
if (!moreOfThis) {
|
|
mips->downcount -= intrHandlers[pend.intr]->exitCycles();
|
|
}
|
|
} 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 __SetIntrHandlerCosts(PSPInterrupt intno, int entryCycles, int exitCycles) {
|
|
intrHandlers[intno]->setCosts(entryCycles, exitCycles);
|
|
}
|
|
|
|
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) {
|
|
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 u32 sysclib_strnlen(u32 src, u32 maxLen) {
|
|
u32 len = 0;
|
|
while (len < maxLen && Memory::IsValidAddress(src + len) && Memory::ReadUnchecked_U8(src + len) != 0) {
|
|
len++;
|
|
}
|
|
return hleLogVerbose(Log::sceKernel, len);
|
|
}
|
|
|
|
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(MIPSState *mips, u32 dst, int limit, u32 fmt, int firstVarArg) {
|
|
std::string result;
|
|
if (!HLEFormatPrintf(mips, 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(MIPSState *mips, 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(mips, dst, 0, fmt, 2));
|
|
}
|
|
|
|
static int sysclib_snprintf(MIPSState *mips, 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(mips, 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 },
|
|
{0x90C5573D, &WrapU_UU<sysclib_strnlen>, "strnlen", 'x', "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);
|
|
}
|
|
|
|
|
|
// What these return is from tests/intr/kernel/intrhandler, recorded on a PSP. Interrupts there go up to
|
|
// 67, one more than PSP_NUMBER_INTERRUPTS, which the firmware's drivers use anyway.
|
|
static int sceKernelRegisterIntrHandler(int intrNumber, u32 unknown, u32 handler, u32 handlerArg, u32 opt) {
|
|
if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS || handler == 0)
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE);
|
|
IntrHandler *intr = intrHandlers[intrNumber];
|
|
if (intr->hasBaseHandler())
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_FOUND_HANDLER);
|
|
intr->setBaseHandler(handler, handlerArg);
|
|
return hleLogDebug(Log::sceIntc, 0);
|
|
}
|
|
|
|
static int sceKernelReleaseIntrHandler(int intrNumber) {
|
|
if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS)
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_ILLEGAL_INTRCODE);
|
|
IntrHandler *intr = intrHandlers[intrNumber];
|
|
if (!intr->hasBaseHandler())
|
|
return hleLogError(Log::sceIntc, SCE_KERNEL_ERROR_NOTFOUND_HANDLER);
|
|
intr->setBaseHandler(0, 0);
|
|
return hleLogDebug(Log::sceIntc, 0);
|
|
}
|
|
|
|
static int SetIntrEnabled(int intrNumber, bool enable) {
|
|
if (intrNumber < 0 || intrNumber >= PSP_NUMBER_INTERRUPTS)
|
|
return SCE_KERNEL_ERROR_ILLEGAL_INTRCODE;
|
|
IntrHandler *intr = intrHandlers[intrNumber];
|
|
if (!intr->hasBaseHandler())
|
|
return SCE_KERNEL_ERROR_NOTFOUND_HANDLER;
|
|
intr->enableBase(enable);
|
|
return 0;
|
|
}
|
|
|
|
static int sceKernelEnableIntr(int intrNumber) {
|
|
return hleLogDebugOrError(Log::sceIntc, SetIntrEnabled(intrNumber, true));
|
|
}
|
|
|
|
static int sceKernelDisableIntr(int intrNumber) {
|
|
return hleLogDebugOrError(Log::sceIntc, SetIntrEnabled(intrNumber, false));
|
|
}
|
|
|
|
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 },
|
|
{0X58DD8978, &WrapI_IUUUU<sceKernelRegisterIntrHandler>, "sceKernelRegisterIntrHandler", 'i', "ixxxx", HLE_KERNEL_SYSCALL },
|
|
{0XF987B1F0, &WrapI_I<sceKernelReleaseIntrHandler>, "sceKernelReleaseIntrHandler", 'i', "i" ,HLE_KERNEL_SYSCALL },
|
|
{0X4D6E7305, &WrapI_I<sceKernelEnableIntr>, "sceKernelEnableIntr", 'i', "i" ,HLE_KERNEL_SYSCALL },
|
|
{0XD774BA45, &WrapI_I<sceKernelDisableIntr>, "sceKernelDisableIntr", 'i', "i" ,HLE_KERNEL_SYSCALL },
|
|
};
|
|
|
|
void Register_InterruptManagerForKernel()
|
|
{
|
|
RegisterHLEModule("InterruptManagerForKernel", ARRAY_SIZE(InterruptManagerForKernel), InterruptManagerForKernel);
|
|
}
|