mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Semaphores, event flags, mutexes, lwmutexes, mbx, msgpipes, fpl, vpl, tlspl and WaitThreadEnd each had their own CoreTiming event, handler registration and savestate entry for wait timeouts, and their own function to schedule one. Now one event (WaitThreadEnd's, renamed) times out all of them, keyed by thread, and dispatches on the thread's wait type to a timeoutFunc registered alongside the begin/end callback functions. __KernelWaitCurThreadWithTimeout() starts such a wait, and the HLEKernel helpers have overloads that use the shared event. Old savestates still load: each object's section reads its old event id and points it at the shared handler, so a timeout pending in the state goes off as before. Checked with a state saved mid-wait by the previous build, and with four games. The one behaviour change: tlspl timeouts now follow the same hardware rule as the others, where they used the raw timeout. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
4168 lines
135 KiB
C++
4168 lines
135 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 <mutex>
|
|
#include <set>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Log/LogManager.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Common/Serialize/SerializeList.h"
|
|
#include "Common/Serialize/SerializeMap.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/HLE/HLE.h"
|
|
#include "Core/HLE/ErrorCodes.h"
|
|
#include "Core/HLE/HLETables.h"
|
|
#include "Core/MIPS/MIPSAnalyst.h"
|
|
#include "Core/MIPS/MIPSCodeUtils.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/MemMapHelpers.h"
|
|
#include "Core/MIPS/JitCommon/JitCommon.h"
|
|
#include "Core/Reporting.h"
|
|
|
|
#include "Core/HLE/sceAudio.h"
|
|
#include "Core/HLE/sceKernel.h"
|
|
#include "Core/HLE/sceKernelMemory.h"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
#include "Core/HLE/sceKernelModule.h"
|
|
#include "Core/HLE/sceKernelInterrupt.h"
|
|
#include "Core/HLE/KernelThreadDebugInterface.h"
|
|
#include "Core/HLE/KernelWaitHelpers.h"
|
|
#include "Core/HLE/ThreadQueueList.h"
|
|
|
|
struct WaitTypeNames {
|
|
WaitType type;
|
|
const char *name;
|
|
};
|
|
|
|
const WaitTypeNames waitTypeNames[] = {
|
|
{ WAITTYPE_NONE, "None" },
|
|
{ WAITTYPE_SLEEP, "Sleep" },
|
|
{ WAITTYPE_DELAY, "Delay" },
|
|
{ WAITTYPE_SEMA, "Semaphore" },
|
|
{ WAITTYPE_EVENTFLAG, "Event flag", },
|
|
{ WAITTYPE_MBX, "MBX" },
|
|
{ WAITTYPE_VPL, "VPL" },
|
|
{ WAITTYPE_FPL, "FPL" },
|
|
{ WAITTYPE_MSGPIPE, "Message pipe" },
|
|
{ WAITTYPE_THREADEND, "Thread end" },
|
|
{ WAITTYPE_AUDIOCHANNEL, "Audio channel" },
|
|
{ WAITTYPE_UMD, "UMD" },
|
|
{ WAITTYPE_VBLANK, "VBlank" },
|
|
{ WAITTYPE_MUTEX, "Mutex" },
|
|
{ WAITTYPE_LWMUTEX, "LwMutex" },
|
|
{ WAITTYPE_CTRL, "Control" },
|
|
{ WAITTYPE_IO, "IO" },
|
|
{ WAITTYPE_GEDRAWSYNC, "GeDrawSync" },
|
|
{ WAITTYPE_GELISTSYNC, "GeListSync" },
|
|
{ WAITTYPE_MODULE, "Module" },
|
|
{ WAITTYPE_HLEDELAY, "HleDelay" },
|
|
{ WAITTYPE_TLSPL, "TLS" },
|
|
{ WAITTYPE_VMEM, "Volatile Mem" },
|
|
{ WAITTYPE_ASYNCIO, "AsyncIO" },
|
|
{ WAITTYPE_MICINPUT, "Microphone input"},
|
|
{ WAITTYPE_NET, "Network"},
|
|
{ WAITTYPE_USB, "USB" },
|
|
{ WAITTYPE_PLUGIN, "Initial plugin load" },
|
|
};
|
|
|
|
const char *WaitTypeToString(WaitType type) {
|
|
for (WaitTypeNames info : waitTypeNames) {
|
|
if (info.type == type)
|
|
return info.name;
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
enum ThreadEventType {
|
|
THREADEVENT_CREATE = 1,
|
|
THREADEVENT_START = 2,
|
|
THREADEVENT_EXIT = 4,
|
|
THREADEVENT_DELETE = 8,
|
|
THREADEVENT_SUPPORTED = THREADEVENT_CREATE | THREADEVENT_START | THREADEVENT_EXIT | THREADEVENT_DELETE,
|
|
};
|
|
|
|
bool __KernelThreadTriggerEvent(bool isKernel, SceUID threadID, ThreadEventType type);
|
|
|
|
// NativeCallback/PSPCallback itself now live in sceKernelThread.h - see the comment on the class
|
|
// there for why.
|
|
void PSPCallback::DoState(PointerWrap &p)
|
|
{
|
|
auto s = p.Section("Callback", 1);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, nc);
|
|
// Saved values were moved to mips call, ignoring here.
|
|
u32 legacySaved = 0;
|
|
Do(p, legacySaved);
|
|
Do(p, legacySaved);
|
|
Do(p, legacySaved);
|
|
Do(p, legacySaved);
|
|
Do(p, legacySaved);
|
|
}
|
|
|
|
// Owns outstanding MIPS calls and provides a way to get them by ID.
|
|
class MipsCallManager {
|
|
public:
|
|
MipsCallManager() : idGen_(0) {}
|
|
u32 add(MipsCall *call) {
|
|
u32 id = genId();
|
|
calls_.emplace(id, call);
|
|
return id;
|
|
}
|
|
MipsCall *get(u32 id) {
|
|
auto iter = calls_.find(id);
|
|
if (iter == calls_.end())
|
|
return NULL;
|
|
return iter->second;
|
|
}
|
|
MipsCall *pop(u32 id) {
|
|
MipsCall *temp = calls_[id];
|
|
calls_.erase(id);
|
|
return temp;
|
|
}
|
|
void clear() {
|
|
for (auto it = calls_.begin(), end = calls_.end(); it != end; ++it) {
|
|
delete it->second;
|
|
}
|
|
calls_.clear();
|
|
types_.clear();
|
|
idGen_ = 0;
|
|
}
|
|
|
|
int registerActionType(ActionCreator creator) {
|
|
types_.push_back(creator);
|
|
return (int) types_.size() - 1;
|
|
}
|
|
|
|
void restoreActionType(int actionType, ActionCreator creator) {
|
|
if (actionType >= (int) types_.size())
|
|
types_.resize(actionType + 1, NULL);
|
|
types_[actionType] = creator;
|
|
}
|
|
|
|
PSPAction *createActionByType(int actionType) {
|
|
if (actionType < (int) types_.size() && types_[actionType] != NULL) {
|
|
PSPAction *a = types_[actionType]();
|
|
a->actionTypeID = actionType;
|
|
return a;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void DoState(PointerWrap &p) {
|
|
auto s = p.Section("MipsCallManager", 1);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, calls_);
|
|
Do(p, idGen_);
|
|
}
|
|
|
|
private:
|
|
u32 genId() { return ++idGen_; }
|
|
std::map<u32, MipsCall *> calls_;
|
|
std::vector<ActionCreator> types_;
|
|
u32 idGen_;
|
|
};
|
|
|
|
class ActionAfterMipsCall : public PSPAction {
|
|
ActionAfterMipsCall() = default;
|
|
|
|
public:
|
|
void run(MipsCall &call) override;
|
|
|
|
static PSPAction *Create() {
|
|
return new ActionAfterMipsCall();
|
|
}
|
|
|
|
void DoState(PointerWrap &p) override
|
|
{
|
|
auto s = p.Section("ActionAfterMipsCall", 1);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, threadID);
|
|
Do(p, status);
|
|
Do(p, waitType);
|
|
Do(p, waitID);
|
|
Do(p, waitInfo);
|
|
Do(p, isProcessingCallbacks);
|
|
Do(p, currentCallbackId);
|
|
|
|
int chainedActionType = 0;
|
|
if (chainedAction != NULL)
|
|
chainedActionType = chainedAction->actionTypeID;
|
|
Do(p, chainedActionType);
|
|
|
|
if (chainedActionType != 0)
|
|
{
|
|
if (p.mode == p.MODE_READ)
|
|
chainedAction = __KernelCreateAction(chainedActionType);
|
|
if (!chainedAction) {
|
|
p.SetError(p.ERROR_FAILURE);
|
|
return;
|
|
}
|
|
chainedAction->DoState(p);
|
|
}
|
|
}
|
|
|
|
SceUID threadID = 0;
|
|
|
|
// Saved thread state
|
|
int status = 0;
|
|
WaitType waitType{};
|
|
int waitID = 0;
|
|
ThreadWaitInfo waitInfo{};
|
|
bool isProcessingCallbacks = false;
|
|
SceUID currentCallbackId = 0;
|
|
|
|
PSPAction *chainedAction = nullptr;
|
|
};
|
|
|
|
class ActionAfterCallback : public PSPAction {
|
|
public:
|
|
ActionAfterCallback() {}
|
|
void run(MipsCall &call) override;
|
|
|
|
static PSPAction *Create() {
|
|
return new ActionAfterCallback;
|
|
}
|
|
|
|
void setCallback(SceUID cbId_) {
|
|
cbId = cbId_;
|
|
}
|
|
|
|
void DoState(PointerWrap &p) override {
|
|
auto s = p.Section("ActionAfterCallback", 1);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, cbId);
|
|
}
|
|
|
|
SceUID cbId;
|
|
};
|
|
|
|
// Set when an exit callback dispatch is in flight, cleared by the action below when it returns.
|
|
static bool g_exitCallbackPending = false;
|
|
|
|
class ActionAfterExitCallback : public PSPAction {
|
|
public:
|
|
ActionAfterExitCallback() {}
|
|
void run(MipsCall &call) override {
|
|
INFO_LOG(Log::sceKernel, "Exit callback returned (v0 = %08x). Powering down.", currentMIPS->r[MIPS_REG_V0]);
|
|
g_exitCallbackPending = false;
|
|
// The callback may have already powered down by calling sceKernelExitGame - that's fine,
|
|
// Core_Stop is idempotent. Either way, the host will see CORE_POWERDOWN and tear the game down.
|
|
Core_Stop();
|
|
}
|
|
|
|
static PSPAction *Create() {
|
|
return new ActionAfterExitCallback;
|
|
}
|
|
|
|
void DoState(PointerWrap &p) override {
|
|
auto s = p.Section("ActionAfterExitCallback", 1);
|
|
if (!s)
|
|
return;
|
|
if (p.mode == p.MODE_READ) {
|
|
// The dispatch is still in flight in the loaded state (see __KernelThreadingDoState.)
|
|
g_exitCallbackPending = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
u32 PSPThread::GetMissingErrorCode() {
|
|
return SCE_KERNEL_ERROR_UNKNOWN_THID;
|
|
}
|
|
|
|
void PSPThread::GetQuickInfo(char *ptr, int size) {
|
|
snprintf(ptr, size, "pc= %08x sp= %08x %s %s %s %s %s %s (wt=%i wid=%i wv= %08x )",
|
|
context.pc, context.r[MIPS_REG_SP],
|
|
(nt.status & THREADSTATUS_RUNNING) ? "RUN" : "",
|
|
(nt.status & THREADSTATUS_READY) ? "READY" : "",
|
|
(nt.status & THREADSTATUS_WAIT) ? "WAIT" : "",
|
|
(nt.status & THREADSTATUS_SUSPEND) ? "SUSPEND" : "",
|
|
(nt.status & THREADSTATUS_DORMANT) ? "DORMANT" : "",
|
|
(nt.status & THREADSTATUS_DEAD) ? "DEAD" : "",
|
|
(int)nt.waitType,
|
|
nt.waitID,
|
|
waitInfo.waitValue);
|
|
}
|
|
|
|
BlockAllocator &PSPThread::StackAllocator() {
|
|
if (nt.attr & PSP_THREAD_ATTR_KERNEL) {
|
|
return kernelMemory;
|
|
}
|
|
return userMemory;
|
|
}
|
|
|
|
bool PSPThread::AllocateStack(u32 &stackSize) {
|
|
_assert_msg_(stackSize >= 0x200, "thread stack should be 256 bytes or larger");
|
|
|
|
FreeStack();
|
|
|
|
bool fromTop = (nt.attr & PSP_THREAD_ATTR_LOW_STACK) == 0;
|
|
currentStack.start = StackAllocator().Alloc(stackSize, fromTop, StringFromFormat("stack/%s", nt.name).c_str());
|
|
if (currentStack.start == (u32)-1) {
|
|
currentStack.start = 0;
|
|
nt.initialStack = 0;
|
|
ERROR_LOG(Log::sceKernel, "Failed to allocate stack for thread");
|
|
return false;
|
|
}
|
|
|
|
nt.initialStack = currentStack.start;
|
|
nt.stackSize = stackSize;
|
|
return true;
|
|
}
|
|
|
|
bool PSPThread::FillStack() {
|
|
// Fill the stack.
|
|
if ((nt.attr & PSP_THREAD_ATTR_NO_FILLSTACK) == 0) {
|
|
Memory::Memset(currentStack.start, 0xFF, nt.stackSize, "ThreadFillStack");
|
|
}
|
|
context.r[MIPS_REG_SP] = currentStack.start + nt.stackSize;
|
|
currentStack.end = context.r[MIPS_REG_SP];
|
|
// The k0 section is 256 bytes at the top of the stack.
|
|
context.r[MIPS_REG_SP] -= 256;
|
|
context.r[MIPS_REG_K0] = context.r[MIPS_REG_SP];
|
|
u32 k0 = context.r[MIPS_REG_K0];
|
|
Memory::Memset(k0, 0, 0x100, "ThreadK0");
|
|
Memory::WriteOrException_U32(GetUID(), k0 + 0xc0);
|
|
Memory::WriteOrException_U32(nt.initialStack, k0 + 0xc8);
|
|
Memory::WriteOrException_U32(0xffffffff, k0 + 0xf8);
|
|
Memory::WriteOrException_U32(0xffffffff, k0 + 0xfc);
|
|
// After k0 comes the arguments, which is done by sceKernelStartThread().
|
|
|
|
Memory::WriteOrException_U32(GetUID(), nt.initialStack);
|
|
return true;
|
|
}
|
|
|
|
void PSPThread::FreeStack() {
|
|
if (currentStack.start != 0) {
|
|
DEBUG_LOG(Log::sceKernel, "Freeing thread stack %s", nt.name);
|
|
|
|
if ((nt.attr & PSP_THREAD_ATTR_CLEAR_STACK) != 0 && nt.initialStack != 0) {
|
|
Memory::Memset(nt.initialStack, 0, nt.stackSize, "ThreadFreeStack");
|
|
}
|
|
|
|
StackAllocator().Free(currentStack.start);
|
|
currentStack.start = 0;
|
|
}
|
|
}
|
|
|
|
bool PSPThread::PushExtendedStack(u32 size) {
|
|
u32 stack = userMemory.Alloc(size, true, StringFromFormat("extended/%s", nt.name).c_str());
|
|
if (stack == (u32)-1)
|
|
return false;
|
|
|
|
pushedStacks.push_back(currentStack);
|
|
currentStack.start = stack;
|
|
currentStack.end = stack + size;
|
|
nt.initialStack = currentStack.start;
|
|
nt.stackSize = currentStack.end - currentStack.start;
|
|
|
|
// We still drop the threadID at the bottom and fill it, but there's no k0.
|
|
Memory::Memset(currentStack.start, 0xFF, nt.stackSize, "ThreadExtendStack");
|
|
Memory::WriteOrException_U32(GetUID(), nt.initialStack);
|
|
return true;
|
|
}
|
|
|
|
bool PSPThread::PopExtendedStack() {
|
|
if (pushedStacks.size() == 0)
|
|
return false;
|
|
|
|
userMemory.Free(currentStack.start);
|
|
currentStack = pushedStacks.back();
|
|
pushedStacks.pop_back();
|
|
nt.initialStack = currentStack.start;
|
|
nt.stackSize = currentStack.end - currentStack.start;
|
|
return true;
|
|
}
|
|
|
|
void PSPThread::Cleanup() {
|
|
// Callbacks are automatically deleted when their owning thread is deleted.
|
|
for (auto it = callbacks.begin(), end = callbacks.end(); it != end; ++it)
|
|
kernelObjects.Destroy<PSPCallback>(*it);
|
|
|
|
if (pushedStacks.size() != 0) {
|
|
WARN_LOG_REPORT(Log::sceKernel, "Thread ended within an extended stack");
|
|
for (size_t i = 0; i < pushedStacks.size(); ++i)
|
|
userMemory.Free(pushedStacks[i].start);
|
|
}
|
|
FreeStack();
|
|
}
|
|
|
|
void PSPThread::DoState(PointerWrap &p) {
|
|
auto s = p.Section("Thread", 1, 7);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, nt);
|
|
Do(p, waitInfo);
|
|
Do(p, moduleId);
|
|
Do(p, isProcessingCallbacks);
|
|
Do(p, currentMipscallId);
|
|
Do(p, currentCallbackId);
|
|
|
|
// TODO: If we want to "version" a DoState method here, we can just use minVer = 0.
|
|
Do(p, context);
|
|
|
|
if (s <= 3) {
|
|
// We must have been loading an old state if we're here.
|
|
// Reorder VFPU data to new order.
|
|
float temp[128];
|
|
memcpy(temp, context.v, 128 * sizeof(float));
|
|
for (int i = 0; i < 128; i++) {
|
|
context.v[voffset[i]] = temp[i];
|
|
}
|
|
}
|
|
|
|
if (s <= 2) {
|
|
context.other[4] = context.other[5];
|
|
context.other[3] = context.other[4];
|
|
}
|
|
if (s <= 4)
|
|
std::swap(context.hi, context.lo);
|
|
|
|
Do(p, callbacks);
|
|
|
|
Do(p, pendingMipsCalls);
|
|
Do(p, pushedStacks);
|
|
Do(p, currentStack);
|
|
|
|
if (s >= 2) {
|
|
Do(p, waitingThreads);
|
|
Do(p, pausedWaits);
|
|
}
|
|
if (s >= 6) {
|
|
Do(p, hasWaited);
|
|
} else {
|
|
hasWaited = true;
|
|
}
|
|
if (s >= 7) {
|
|
Do(p, waitPausedForCallback);
|
|
} else {
|
|
waitPausedForCallback = false;
|
|
}
|
|
}
|
|
|
|
|
|
struct WaitTypeFuncs
|
|
{
|
|
WaitBeginCallbackFunc beginFunc;
|
|
WaitEndCallbackFunc endFunc;
|
|
WaitTimeoutFunc timeoutFunc;
|
|
};
|
|
|
|
bool __KernelExecuteMipsCallOnCurrentThread(u32 callId, bool reschedAfter);
|
|
|
|
PSPThread *__KernelCreateThreadObject(SceUID &id, SceUID moduleID, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr);
|
|
void __KernelResetThread(PSPThread *t, int lowestPriority);
|
|
void __KernelCancelWakeup(SceUID threadID);
|
|
void __KernelCancelThreadEndTimeout(SceUID threadID);
|
|
bool __KernelCheckThreadCallbacks(PSPThread *thread, bool force);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//STATE BEGIN
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// No longer used (callback nesting is tracked per thread), only kept for savestates.
|
|
static int g_inCbCount = 0;
|
|
static SceUID currentCallbackThreadID = 0;
|
|
static int readyCallbacksCount = 0;
|
|
// Syscalls keeping the CPU busy, see __KernelBusyDelayResult().
|
|
struct BusySyscall {
|
|
SceUID threadID;
|
|
s64 remainingCycles;
|
|
bool counting;
|
|
};
|
|
static std::vector<BusySyscall> busySyscalls;
|
|
// See __KernelChance().
|
|
static int g_chanceAccumulator = 50;
|
|
static int eventBusySyscallDone = -1;
|
|
static void __KernelBusySyscallDone(u64 userdata, int cyclesLate);
|
|
static SceUID currentThread;
|
|
// When the running thread last changed, so each thread can be billed for the time it actually ran
|
|
// (nt.runForClocks). Not serialized - it's re-based on load, which only skews the very first slice.
|
|
static u64 lastContextSwitchUs = 0;
|
|
static PSPThread *currentThreadPtr;
|
|
static u32 idleThreadHackAddr;
|
|
static u32 threadReturnHackAddr;
|
|
static u32 hleReturnHackAddr;
|
|
static u32 cbReturnHackAddr;
|
|
static u32 intReturnHackAddr;
|
|
static u32 extendReturnHackAddr;
|
|
static u32 moduleReturnHackAddr;
|
|
static std::vector<ThreadCallback> threadEndListeners;
|
|
|
|
typedef std::vector<SceUID> ThreadEventHandlerList;
|
|
static std::map<SceUID, ThreadEventHandlerList> threadEventHandlers;
|
|
static std::vector<SceUID> pendingDeleteThreads;
|
|
|
|
// Lists all thread ids that aren't deleted/etc.
|
|
static std::vector<SceUID> threadqueue;
|
|
// Only for debugger, so not needed to read, just write.
|
|
static std::mutex threadqueueLock;
|
|
|
|
// Lists only ready thread ids.
|
|
static ThreadQueueList threadReadyQueue;
|
|
|
|
static SceUID threadIdleID[2];
|
|
|
|
static int eventScheduledWakeup;
|
|
static int eventWaitTimeout;
|
|
|
|
static bool dispatchEnabled = true;
|
|
|
|
static MipsCallManager mipsCalls;
|
|
static int actionAfterCallback;
|
|
static int actionAfterMipsCall;
|
|
static int actionAfterExitCallback;
|
|
|
|
// When inside a callback, delays are "paused", and rechecked after the callback returns.
|
|
static std::map<SceUID, u64> pausedDelays;
|
|
|
|
// Doesn't need state saving.
|
|
static WaitTypeFuncs waitTypeFuncs[NUM_WAITTYPES];
|
|
|
|
// Doesn't really need state saving, just for logging purposes.
|
|
static u64 lastSwitchCycles = 0;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//STATE END
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
int __KernelRegisterActionType(ActionCreator creator) {
|
|
return mipsCalls.registerActionType(creator);
|
|
}
|
|
|
|
void __KernelRestoreActionType(int actionType, ActionCreator creator) {
|
|
_assert_(actionType >= 0);
|
|
mipsCalls.restoreActionType(actionType, creator);
|
|
}
|
|
|
|
PSPAction *__KernelCreateAction(int actionType) {
|
|
return mipsCalls.createActionByType(actionType);
|
|
}
|
|
|
|
void MipsCall::DoState(PointerWrap &p)
|
|
{
|
|
auto s = p.Section("MipsCall", 1);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, entryPoint);
|
|
Do(p, cbId);
|
|
DoArray(p, args, ARRAY_SIZE(args));
|
|
Do(p, numArgs);
|
|
// No longer used.
|
|
u32 legacySavedIdRegister = 0;
|
|
Do(p, legacySavedIdRegister);
|
|
u32 legacySavedRa = 0;
|
|
Do(p, legacySavedRa);
|
|
Do(p, savedPc);
|
|
Do(p, savedV0);
|
|
Do(p, savedV1);
|
|
Do(p, tag);
|
|
Do(p, savedId);
|
|
Do(p, reschedAfter);
|
|
|
|
int actionTypeID = 0;
|
|
if (doAfter != NULL)
|
|
actionTypeID = doAfter->actionTypeID;
|
|
Do(p, actionTypeID);
|
|
if (actionTypeID != 0)
|
|
{
|
|
if (p.mode == p.MODE_READ)
|
|
doAfter = __KernelCreateAction(actionTypeID);
|
|
if (!doAfter) {
|
|
p.SetError(p.ERROR_FAILURE);
|
|
return;
|
|
}
|
|
doAfter->DoState(p);
|
|
}
|
|
}
|
|
|
|
void MipsCall::setReturnValue(u32 value) {
|
|
savedV0 = value;
|
|
}
|
|
|
|
void MipsCall::setReturnValue(u64 value) {
|
|
savedV0 = value & 0xFFFFFFFF;
|
|
savedV1 = (value >> 32) & 0xFFFFFFFF;
|
|
}
|
|
|
|
inline PSPThread *__GetCurrentThread() {
|
|
return currentThreadPtr;
|
|
}
|
|
|
|
inline void __SetCurrentThread(PSPThread *thread, SceUID threadID, const char *name) {
|
|
currentThread = threadID;
|
|
currentThreadPtr = thread;
|
|
hleCurrentThreadName = name;
|
|
}
|
|
|
|
u32 __KernelCallbackReturnAddress() {
|
|
return cbReturnHackAddr;
|
|
}
|
|
|
|
u32 __KernelInterruptReturnAddress() {
|
|
return intReturnHackAddr;
|
|
}
|
|
|
|
static void __KernelDelayBeginCallback(SceUID threadID, SceUID prevCallbackId) {
|
|
u32 error;
|
|
SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_DELAY, error);
|
|
if (waitID == threadID) {
|
|
// Most waits need to keep track of waiting threads, delays don't. Use a fake list.
|
|
std::vector<SceUID> dummy;
|
|
HLEKernel::WaitBeginCallback(threadID, prevCallbackId, eventScheduledWakeup, dummy, pausedDelays, true);
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelDelayThreadCB: Suspending delay for callback");
|
|
}
|
|
else
|
|
WARN_LOG_REPORT(Log::sceKernel, "sceKernelDelayThreadCB: beginning callback with bad wait?");
|
|
}
|
|
|
|
static void __KernelDelayEndCallback(SceUID threadID, SceUID prevCallbackId) {
|
|
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
|
|
|
|
if (pausedDelays.find(pauseKey) == pausedDelays.end())
|
|
{
|
|
// This probably should not happen.
|
|
WARN_LOG_REPORT(Log::sceKernel, "sceKernelDelayThreadCB: cannot find delay deadline");
|
|
__KernelResumeThreadFromWait(threadID, 0);
|
|
return;
|
|
}
|
|
|
|
u64 delayDeadline = pausedDelays[pauseKey];
|
|
pausedDelays.erase(pauseKey);
|
|
|
|
// TODO: Don't wake up if __KernelCurHasReadyCallbacks()?
|
|
|
|
s64 cyclesLeft = delayDeadline - CoreTiming::GetTicks(currentMIPS);
|
|
if (cyclesLeft < 0) {
|
|
__KernelResumeThreadFromWait(threadID, 0);
|
|
} else {
|
|
CoreTiming::ScheduleEvent(cyclesLeft, eventScheduledWakeup, __KernelGetCurThread());
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelDelayThreadCB: Resuming delay after callback");
|
|
}
|
|
}
|
|
|
|
static void __KernelSleepBeginCallback(SceUID threadID, SceUID prevCallbackId) {
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelSleepThreadCB: Suspending sleep for callback");
|
|
}
|
|
|
|
static void __KernelSleepEndCallback(SceUID threadID, SceUID prevCallbackId) {
|
|
u32 error;
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!thread) {
|
|
// This probably should not happen.
|
|
WARN_LOG_REPORT(Log::sceKernel, "sceKernelSleepThreadCB: thread deleted?");
|
|
return;
|
|
}
|
|
|
|
// TODO: Don't wake up if __KernelCurHasReadyCallbacks()?
|
|
|
|
if (thread->nt.wakeupCount > 0) {
|
|
thread->nt.wakeupCount--;
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelSleepThreadCB: resume from callback, wakeupCount decremented to %i", thread->nt.wakeupCount);
|
|
__KernelResumeThreadFromWait(threadID, 0);
|
|
} else {
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelSleepThreadCB: Resuming sleep after callback");
|
|
}
|
|
}
|
|
|
|
static void __KernelThreadEndBeginCallback(SceUID threadID, SceUID prevCallbackId) {
|
|
auto result = HLEKernel::WaitBeginCallback<PSPThread, WAITTYPE_THREADEND, SceUID>(threadID, prevCallbackId, eventWaitTimeout);
|
|
if (result == HLEKernel::WAIT_CB_SUCCESS)
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelWaitThreadEndCB: Suspending wait for callback");
|
|
else if (result == HLEKernel::WAIT_CB_BAD_WAIT_DATA)
|
|
ERROR_LOG_REPORT(Log::sceKernel, "sceKernelWaitThreadEndCB: wait not found to pause for callback");
|
|
else
|
|
WARN_LOG_REPORT(Log::sceKernel, "sceKernelWaitThreadEndCB: beginning callback with bad wait id?");
|
|
}
|
|
|
|
static bool __KernelCheckResumeThreadEnd(PSPThread *t, SceUID waitingThreadID, u32 &error, int result, bool &wokeThreads) {
|
|
if (!HLEKernel::VerifyWait(waitingThreadID, WAITTYPE_THREADEND, t->GetUID()))
|
|
return true;
|
|
|
|
if (t->nt.status == THREADSTATUS_DORMANT) {
|
|
u32 timeoutPtr = __KernelGetWaitTimeoutPtr(waitingThreadID, error);
|
|
// Note: unlike the helper in KernelWaitHelpers.h, we unschedule even without a timeout ptr.
|
|
s64 cyclesLeft = CoreTiming::UnscheduleEvent(eventWaitTimeout, waitingThreadID);
|
|
cyclesLeft -= usToCycles(WAIT_TIMEOUT_LATENCY_US);
|
|
if (cyclesLeft < 0)
|
|
cyclesLeft = 0;
|
|
if (timeoutPtr != 0)
|
|
Memory::WriteOrException_U32((u32) cyclesToUs(cyclesLeft), timeoutPtr);
|
|
s32 exitStatus = t->nt.exitStatus;
|
|
__KernelResumeThreadFromWait(waitingThreadID, exitStatus);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void __KernelThreadEndEndCallback(SceUID threadID, SceUID prevCallbackId)
|
|
{
|
|
auto result = HLEKernel::WaitEndCallback<PSPThread, WAITTYPE_THREADEND, SceUID>(threadID, prevCallbackId, eventWaitTimeout, __KernelCheckResumeThreadEnd);
|
|
if (result == HLEKernel::WAIT_CB_RESUMED_WAIT)
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelWaitThreadEndCB: Resuming wait from callback");
|
|
}
|
|
|
|
u32 __KernelSetThreadRA(SceUID threadID, u32 nid) {
|
|
u32 newRA;
|
|
switch (nid) {
|
|
case NID_MODULERETURN:
|
|
newRA = moduleReturnHackAddr;
|
|
break;
|
|
default:
|
|
ERROR_LOG_REPORT(Log::sceKernel, "__KernelSetThreadRA(): invalid RA address");
|
|
return -1;
|
|
}
|
|
|
|
if (threadID == currentThread) {
|
|
currentMIPS->r[MIPS_REG_RA] = newRA;
|
|
} else {
|
|
u32 error;
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!thread)
|
|
return error;
|
|
|
|
thread->context.r[MIPS_REG_RA] = newRA;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void hleScheduledWakeup(u64 userdata, int cyclesLate);
|
|
void hleThreadEndTimeout(u64 userdata, int cyclesLate);
|
|
static void __KernelWaitTimeoutFired(u64 userdata, int cyclesLate);
|
|
|
|
static void __KernelWriteFakeSysCall(u32 nid, u32 *ptr, u32 &pos) {
|
|
*ptr = pos;
|
|
pos += 8;
|
|
WriteHLESyscall("FakeSysCalls", nid, *ptr);
|
|
}
|
|
|
|
u32 HLEMipsCallReturnAddress() {
|
|
if (hleReturnHackAddr == 0) {
|
|
// From an old save state, likely... try to recover.
|
|
u32 blockSize = 2 * sizeof(u32);
|
|
u32 pos = kernelMemory.Alloc(blockSize, false, "hlerethack");
|
|
__KernelWriteFakeSysCall(NID_HLECALLRETURN, &hleReturnHackAddr, pos);
|
|
}
|
|
return hleReturnHackAddr;
|
|
}
|
|
|
|
void __KernelThreadingInit() {
|
|
g_chanceAccumulator = 50;
|
|
struct ThreadHack {
|
|
u32 nid;
|
|
u32 *addr;
|
|
};
|
|
|
|
// Yeah, this is straight out of JPCSP, I should be ashamed.
|
|
static const u32_le idleThreadCode[] = {
|
|
MIPS_MAKE_LUI(MIPS_REG_RA, 0x0800),
|
|
MIPS_MAKE_JR_RA(),
|
|
MIPS_MAKE_SYSCALL("FakeSysCalls", "_sceKernelIdle"),
|
|
MIPS_MAKE_BREAK(0),
|
|
};
|
|
|
|
// If you add another func here, don't forget __KernelThreadingDoState() below.
|
|
static const ThreadHack threadHacks[] = {
|
|
{NID_THREADRETURN, &threadReturnHackAddr},
|
|
{NID_CALLBACKRETURN, &cbReturnHackAddr},
|
|
{NID_INTERRUPTRETURN, &intReturnHackAddr},
|
|
{NID_EXTENDRETURN, &extendReturnHackAddr},
|
|
{NID_MODULERETURN, &moduleReturnHackAddr},
|
|
{NID_HLECALLRETURN, &hleReturnHackAddr},
|
|
};
|
|
u32 blockSize = sizeof(idleThreadCode) + ARRAY_SIZE(threadHacks) * 2 * 4; // The thread code above plus 8 bytes per "hack"
|
|
|
|
dispatchEnabled = true;
|
|
// Don't clear waitTypeFuncs here: __KernelMemoryInit() registers VPL and FPL before this runs.
|
|
// Every entry is set again by its module's init anyway.
|
|
|
|
__SetCurrentThread(NULL, 0, NULL);
|
|
g_inCbCount = 0;
|
|
currentCallbackThreadID = 0;
|
|
readyCallbacksCount = 0;
|
|
lastSwitchCycles = 0;
|
|
lastContextSwitchUs = 0;
|
|
idleThreadHackAddr = kernelMemory.Alloc(blockSize, false, "threadrethack");
|
|
|
|
Memory::Memcpy(idleThreadHackAddr, idleThreadCode, sizeof(idleThreadCode), "ThreadMIPS");
|
|
|
|
u32 pos = idleThreadHackAddr + sizeof(idleThreadCode);
|
|
for (size_t i = 0; i < ARRAY_SIZE(threadHacks); ++i) {
|
|
__KernelWriteFakeSysCall(threadHacks[i].nid, threadHacks[i].addr, pos);
|
|
}
|
|
|
|
eventScheduledWakeup = CoreTiming::RegisterEvent("ScheduledWakeup", &hleScheduledWakeup);
|
|
eventWaitTimeout = CoreTiming::RegisterEvent("WaitTimeout", &__KernelWaitTimeoutFired);
|
|
eventBusySyscallDone = CoreTiming::RegisterEvent("BusySyscallDone", &__KernelBusySyscallDone);
|
|
busySyscalls.clear();
|
|
actionAfterMipsCall = __KernelRegisterActionType(ActionAfterMipsCall::Create);
|
|
actionAfterCallback = __KernelRegisterActionType(ActionAfterCallback::Create);
|
|
actionAfterExitCallback = __KernelRegisterActionType(ActionAfterExitCallback::Create);
|
|
g_exitCallbackPending = false;
|
|
|
|
// Create the two idle threads, as well. With the absolute minimal possible priority.
|
|
// 4096 stack size - don't know what the right value is. Hm, if callbacks are ever to run on these threads...
|
|
__KernelResetThread(__KernelCreateThreadObject(threadIdleID[0], 0, "idle0", idleThreadHackAddr, 0x7f, 4096, PSP_THREAD_ATTR_KERNEL), 0);
|
|
__KernelResetThread(__KernelCreateThreadObject(threadIdleID[1], 0, "idle1", idleThreadHackAddr, 0x7f, 4096, PSP_THREAD_ATTR_KERNEL), 0);
|
|
// These idle threads are later started in LoadExec, which calls __KernelStartIdleThreads below.
|
|
|
|
__KernelListenThreadEnd(__KernelCancelWakeup);
|
|
__KernelListenThreadEnd(__KernelCancelThreadEndTimeout);
|
|
|
|
__KernelRegisterWaitTypeFuncs(WAITTYPE_DELAY, __KernelDelayBeginCallback, __KernelDelayEndCallback);
|
|
__KernelRegisterWaitTypeFuncs(WAITTYPE_SLEEP, __KernelSleepBeginCallback, __KernelSleepEndCallback);
|
|
__KernelRegisterWaitTypeFuncs(WAITTYPE_THREADEND, __KernelThreadEndBeginCallback, __KernelThreadEndEndCallback, &hleThreadEndTimeout);
|
|
}
|
|
|
|
void __KernelThreadingDoState(PointerWrap &p)
|
|
{
|
|
if (p.mode == p.MODE_READ) {
|
|
// Set again if an ActionAfterExitCallback is loaded (with the mips calls, later.)
|
|
g_exitCallbackPending = false;
|
|
}
|
|
|
|
auto s = p.Section("sceKernelThread", 1, 7);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, g_inCbCount);
|
|
Do(p, currentCallbackThreadID);
|
|
Do(p, readyCallbacksCount);
|
|
Do(p, idleThreadHackAddr);
|
|
Do(p, threadReturnHackAddr);
|
|
Do(p, cbReturnHackAddr);
|
|
Do(p, intReturnHackAddr);
|
|
Do(p, extendReturnHackAddr);
|
|
Do(p, moduleReturnHackAddr);
|
|
|
|
if (s >= 4) {
|
|
Do(p, hleReturnHackAddr);
|
|
} else {
|
|
hleReturnHackAddr = 0;
|
|
}
|
|
|
|
Do(p, currentThread);
|
|
SceUID dv = 0;
|
|
Do(p, threadqueue, dv);
|
|
DoArray(p, threadIdleID, ARRAY_SIZE(threadIdleID));
|
|
Do(p, dispatchEnabled);
|
|
|
|
Do(p, threadReadyQueue);
|
|
|
|
Do(p, eventScheduledWakeup);
|
|
CoreTiming::RestoreRegisterEvent(eventScheduledWakeup, "ScheduledWakeup", &hleScheduledWakeup);
|
|
Do(p, eventWaitTimeout);
|
|
CoreTiming::RestoreRegisterEvent(eventWaitTimeout, "WaitTimeout", &__KernelWaitTimeoutFired);
|
|
Do(p, actionAfterMipsCall);
|
|
__KernelRestoreActionType(actionAfterMipsCall, ActionAfterMipsCall::Create);
|
|
Do(p, actionAfterCallback);
|
|
__KernelRestoreActionType(actionAfterCallback, ActionAfterCallback::Create);
|
|
if (s >= 5) {
|
|
Do(p, actionAfterExitCallback);
|
|
__KernelRestoreActionType(actionAfterExitCallback, ActionAfterExitCallback::Create);
|
|
} else {
|
|
// Older states numbered the action types without this one, so the slot it got at boot may
|
|
// now be restored to another type (sceMpeg's). Give it a new one after all of those.
|
|
actionAfterExitCallback = __KernelRegisterActionType(ActionAfterExitCallback::Create);
|
|
}
|
|
|
|
Do(p, pausedDelays);
|
|
|
|
if (s >= 6) {
|
|
Do(p, eventBusySyscallDone);
|
|
u32 busyCount = (u32)busySyscalls.size();
|
|
Do(p, busyCount);
|
|
busySyscalls.resize(busyCount);
|
|
for (BusySyscall &busy : busySyscalls) {
|
|
Do(p, busy.threadID);
|
|
Do(p, busy.remainingCycles);
|
|
Do(p, busy.counting);
|
|
}
|
|
} else {
|
|
eventBusySyscallDone = -1;
|
|
busySyscalls.clear();
|
|
}
|
|
CoreTiming::RestoreRegisterEvent(eventBusySyscallDone, "BusySyscallDone", &__KernelBusySyscallDone);
|
|
if (s >= 7) {
|
|
Do(p, g_chanceAccumulator);
|
|
} else {
|
|
g_chanceAccumulator = 50;
|
|
}
|
|
|
|
__SetCurrentThread(kernelObjects.GetFast<PSPThread>(currentThread), currentThread, __KernelGetThreadName(currentThread));
|
|
lastSwitchCycles = CoreTiming::GetTicks(currentMIPS);
|
|
|
|
if (s >= 2)
|
|
Do(p, threadEventHandlers);
|
|
if (s >= 3)
|
|
Do(p, pendingDeleteThreads);
|
|
|
|
if (p.mode == p.MODE_READ) {
|
|
// Re-base rather than serialize, so a state saved before this existed doesn't bill one
|
|
// thread for the entire emulated time up to the save. Only on load: saving runs a measure
|
|
// pass and then a write pass, and re-basing in either would throw away the time the
|
|
// running thread had accumulated since the last switch - a save must not change what the
|
|
// game can observe.
|
|
lastContextSwitchUs = CoreTiming::GetGlobalTimeUs();
|
|
}
|
|
}
|
|
|
|
void __KernelThreadingDoStateLate(PointerWrap &p) {
|
|
// We do this late to give modules time to register actions.
|
|
mipsCalls.DoState(p);
|
|
p.DoMarker("sceKernelThread Late");
|
|
}
|
|
|
|
KernelObject *__KernelThreadObject() {
|
|
return new PSPThread();
|
|
}
|
|
|
|
KernelObject *__KernelCallbackObject() {
|
|
return new PSPCallback();
|
|
}
|
|
|
|
void __KernelListenThreadEnd(ThreadCallback callback) {
|
|
threadEndListeners.push_back(callback);
|
|
}
|
|
|
|
static void __KernelFireThreadEnd(SceUID threadID) {
|
|
for (auto iter = threadEndListeners.begin(), end = threadEndListeners.end(); iter != end; ++iter) {
|
|
ThreadCallback cb = *iter;
|
|
cb(threadID);
|
|
}
|
|
}
|
|
|
|
// TODO: Use __KernelChangeThreadState instead? It has other affects...
|
|
static void __KernelChangeReadyState(PSPThread *thread, SceUID threadID, bool ready) {
|
|
// Passing the id as a parameter is just an optimization, if it's wrong it will cause havoc.
|
|
_dbg_assert_msg_(thread->GetUID() == threadID, "Incorrect threadID");
|
|
int prio = thread->nt.currentPriority;
|
|
|
|
if (thread->isReady())
|
|
{
|
|
if (!ready)
|
|
threadReadyQueue.remove(prio, threadID);
|
|
}
|
|
else if (ready)
|
|
{
|
|
if (thread->isRunning())
|
|
threadReadyQueue.push_front(prio, threadID);
|
|
else
|
|
threadReadyQueue.push_back(prio, threadID);
|
|
thread->nt.status = THREADSTATUS_READY;
|
|
}
|
|
}
|
|
|
|
static void __KernelChangeReadyState(SceUID threadID, bool ready) {
|
|
u32 error;
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (thread)
|
|
__KernelChangeReadyState(thread, threadID, ready);
|
|
else
|
|
WARN_LOG(Log::sceKernel, "Trying to change the ready state of an unknown thread?");
|
|
}
|
|
|
|
void __KernelStartIdleThreads(SceUID moduleId) {
|
|
for (int i = 0; i < 2; i++) {
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadIdleID[i], error);
|
|
t->nt.gpreg = __KernelGetModuleGP(moduleId);
|
|
t->context.r[MIPS_REG_GP] = t->nt.gpreg;
|
|
//t->context.pc += 4; // ADJUSTPC
|
|
threadReadyQueue.prepare(t->nt.currentPriority);
|
|
__KernelChangeReadyState(t, threadIdleID[i], true);
|
|
}
|
|
}
|
|
|
|
void KernelValidateThreadTarget(uint32_t pc) {
|
|
if (!Memory::IsValidAddress(pc) || (pc & 3) != 0) {
|
|
Core_ExecException(pc, currentMIPS->pc, ExecExceptionType::THREAD);
|
|
}
|
|
}
|
|
|
|
bool __KernelSwitchOffThread(const char *reason) {
|
|
if (!reason)
|
|
reason = "switch off thread";
|
|
|
|
SceUID threadID = currentThread;
|
|
|
|
if (threadID != threadIdleID[0] && threadID != threadIdleID[1])
|
|
{
|
|
PSPThread *current = __GetCurrentThread();
|
|
if (current && current->isRunning())
|
|
__KernelChangeReadyState(current, threadID, true);
|
|
|
|
// Idle 0 chosen entirely arbitrarily.
|
|
PSPThread *t = kernelObjects.GetFast<PSPThread>(threadIdleID[0]);
|
|
if (t)
|
|
{
|
|
hleSkipDeadbeef();
|
|
__KernelSwitchContext(t, reason);
|
|
return true;
|
|
}
|
|
else
|
|
ERROR_LOG(Log::sceKernel, "Unable to switch to idle thread.");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool __KernelSwitchToThread(SceUID threadID, const char *reason)
|
|
{
|
|
if (!reason)
|
|
reason = "switch to thread";
|
|
|
|
if (currentThread != threadIdleID[0] && currentThread != threadIdleID[1])
|
|
{
|
|
ERROR_LOG_REPORT(Log::sceKernel, "__KernelSwitchToThread used when already on a thread.");
|
|
return false;
|
|
}
|
|
|
|
if (currentThread == threadID)
|
|
return false;
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t)
|
|
{
|
|
ERROR_LOG_REPORT(Log::sceKernel, "__KernelSwitchToThread: %x doesn't exist", threadID);
|
|
hleReSchedule("switch to deleted thread");
|
|
}
|
|
else if (t->isReady() || t->isRunning())
|
|
{
|
|
PSPThread *current = __GetCurrentThread();
|
|
if (current && current->isRunning())
|
|
__KernelChangeReadyState(current, currentThread, true);
|
|
|
|
KernelValidateThreadTarget(t->context.pc);
|
|
|
|
__KernelSwitchContext(t, reason);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
hleReSchedule("switch to waiting thread");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void __KernelIdle()
|
|
{
|
|
// Don't skip 0xDEADBEEF here, this is called directly bypassing CallSyscall().
|
|
// That means the hle flag would stick around until the next call.
|
|
|
|
CoreTiming::Idle(currentMIPS);
|
|
// We Advance within __KernelReSchedule(), so anything that has now happened after idle
|
|
// will be triggered properly upon reschedule.
|
|
__KernelReSchedule("idle");
|
|
}
|
|
|
|
void __KernelThreadingShutdown() {
|
|
std::lock_guard<std::mutex> guard(threadqueueLock);
|
|
|
|
kernelMemory.Free(threadReturnHackAddr);
|
|
threadqueue.clear();
|
|
threadReadyQueue.clear();
|
|
threadEndListeners.clear();
|
|
mipsCalls.clear();
|
|
threadReturnHackAddr = 0;
|
|
cbReturnHackAddr = 0;
|
|
hleReturnHackAddr = 0;
|
|
__SetCurrentThread(NULL, 0, NULL);
|
|
intReturnHackAddr = 0;
|
|
pausedDelays.clear();
|
|
threadEventHandlers.clear();
|
|
pendingDeleteThreads.clear();
|
|
g_exitCallbackPending = false;
|
|
}
|
|
|
|
std::string __KernelThreadingSummary() {
|
|
PSPThread *t = __GetCurrentThread();
|
|
return StringFromFormat("Cur thread: %s (attr %08x)", t ? t->GetName() : "(null)", t ? (u32)t->nt.attr : 0);
|
|
}
|
|
|
|
const char *__KernelGetThreadName(SceUID threadID)
|
|
{
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t)
|
|
return t->nt.name;
|
|
return "ERROR";
|
|
}
|
|
|
|
bool KernelIsThreadDormant(SceUID threadID) {
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t)
|
|
return (t->nt.status & (THREADSTATUS_DEAD | THREADSTATUS_DORMANT)) != 0;
|
|
return false;
|
|
}
|
|
|
|
bool KernelIsThreadWaiting(SceUID threadID) {
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t)
|
|
return (t->nt.status & (THREADSTATUS_WAITSUSPEND)) != 0;
|
|
return false;
|
|
}
|
|
|
|
u32 __KernelGetWaitValue(SceUID threadID, u32 &error) {
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t) {
|
|
return t->getWaitInfo().waitValue;
|
|
} else {
|
|
ERROR_LOG(Log::sceKernel, "__KernelGetWaitValue ERROR: thread %i", threadID);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
u32 __KernelGetWaitTimeoutPtr(SceUID threadID, u32 &error) {
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t) {
|
|
return t->getWaitInfo().timeoutPtr;
|
|
} else {
|
|
ERROR_LOG(Log::sceKernel, "__KernelGetWaitTimeoutPtr ERROR: thread %i", threadID);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
SceUID __KernelGetWaitID(SceUID threadID, WaitType type, u32 &error) {
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t) {
|
|
return t->getWaitID(type);
|
|
} else {
|
|
ERROR_LOG(Log::sceKernel, "__KernelGetWaitID ERROR: thread %i", threadID);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
SceUID __KernelGetCurrentCallbackID(SceUID threadID, u32 &error) {
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t) {
|
|
return t->currentCallbackId;
|
|
} else {
|
|
ERROR_LOG(Log::sceKernel, "__KernelGetCurrentCallbackID ERROR: thread %i", threadID);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
u32 sceKernelReferThreadStatus(u32 threadID, u32 statusPtr) {
|
|
static const u32 THREADINFO_SIZE = 104;
|
|
static const u32 THREADINFO_SIZE_AFTER_260 = 108;
|
|
|
|
if (threadID == 0)
|
|
threadID = __KernelGetCurThread();
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
hleEatCycles(700);
|
|
hleReSchedule("refer thread status");
|
|
return hleLogError(Log::sceKernel, error, "bad thread");
|
|
}
|
|
|
|
if (!Memory::IsValid4AlignedAddress(statusPtr)) {
|
|
Core_MemoryExceptionHLE(currentMIPS, statusPtr, 0, MemoryExceptionType::HLE_READ);
|
|
return hleNoLog(0);
|
|
}
|
|
|
|
u32 wantedSize = Memory::ReadUnchecked_U32(statusPtr);
|
|
|
|
if (sceKernelGetCompiledSdkVersion() > 0x02060010) {
|
|
if (wantedSize > THREADINFO_SIZE_AFTER_260) {
|
|
hleEatCycles(1200);
|
|
hleReSchedule("refer thread status");
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_SIZE, "bad size %d", wantedSize);
|
|
}
|
|
|
|
t->nt.nativeSize = THREADINFO_SIZE_AFTER_260;
|
|
if (wantedSize != 0)
|
|
Memory::Memcpy(statusPtr, &t->nt, std::min(wantedSize, (u32)sizeof(t->nt)), "ThreadStatus");
|
|
// TODO: What is this value? Basic tests show 0...
|
|
if (wantedSize > sizeof(t->nt))
|
|
Memory::Memset(statusPtr + sizeof(t->nt), 0, wantedSize - sizeof(t->nt), "ThreadStatus");
|
|
} else {
|
|
t->nt.nativeSize = THREADINFO_SIZE;
|
|
u32 sz = std::min(THREADINFO_SIZE, wantedSize);
|
|
if (sz != 0)
|
|
Memory::Memcpy(statusPtr, &t->nt, sz, "ThreadStatus");
|
|
}
|
|
|
|
hleEatCycles(1400);
|
|
hleReSchedule("refer thread status");
|
|
return hleLogVerbose(Log::sceKernel, 0);
|
|
}
|
|
|
|
// Thanks JPCSP
|
|
u32 sceKernelReferThreadRunStatus(u32 threadID, u32 statusPtr) {
|
|
if (threadID == 0)
|
|
threadID = __KernelGetCurThread();
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return hleLogError(Log::sceKernel, error, "bad thread");
|
|
}
|
|
|
|
if (!Memory::IsValidRange(statusPtr, sizeof(SceKernelThreadRunStatus))) {
|
|
// Raise exception?
|
|
return hleLogError(Log::sceKernel, -1);
|
|
}
|
|
|
|
auto runStatus = PSPPointer<SceKernelThreadRunStatus>::Create(statusPtr);
|
|
|
|
// TODO: Check size?
|
|
runStatus->size = sizeof(SceKernelThreadRunStatus);
|
|
runStatus->status = t->nt.status;
|
|
runStatus->currentPriority = t->nt.currentPriority;
|
|
runStatus->waitType = t->nt.waitType;
|
|
runStatus->waitID = t->nt.waitID;
|
|
runStatus->wakeupCount = t->nt.wakeupCount;
|
|
runStatus->runForClocks = t->nt.runForClocks;
|
|
runStatus->numInterruptPreempts = t->nt.numInterruptPreempts;
|
|
runStatus->numThreadPreempts = t->nt.numThreadPreempts;
|
|
runStatus->numReleases = t->nt.numReleases;
|
|
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
int __KernelGetThreadExitStatus(SceUID threadID) {
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return error;
|
|
}
|
|
|
|
// __KernelResetThread and __KernelCreateThread set exitStatus in case it's DORMANT.
|
|
if (t->nt.status == THREADSTATUS_DORMANT) {
|
|
return t->nt.exitStatus;
|
|
}
|
|
return SCE_KERNEL_ERROR_NOT_DORMANT;
|
|
}
|
|
|
|
int sceKernelGetThreadExitStatus(SceUID threadID) {
|
|
int status = __KernelGetThreadExitStatus(threadID);
|
|
// Seems this is called in a tight-ish loop, maybe awaiting an interrupt - issue #13698
|
|
hleEatCycles(330);
|
|
|
|
// Some return values don't deserve to be considered errors for logging.
|
|
switch ((PSPErrorCode)status) {
|
|
case SCE_KERNEL_ERROR_NOT_DORMANT:
|
|
case SCE_KERNEL_ERROR_DORMANT:
|
|
return hleLogDebug(Log::sceKernel, status);
|
|
default:
|
|
return hleLogDebugOrError(Log::sceKernel, status);
|
|
}
|
|
}
|
|
|
|
u32 sceKernelGetThreadmanIdType(u32 uid) {
|
|
int type;
|
|
if (kernelObjects.GetIDType(uid, &type)) {
|
|
if (type < 0x1000) {
|
|
return hleLogDebug(Log::sceKernel, type);
|
|
} else {
|
|
// This means a partition memory block or module, etc.
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid object type %i", type);
|
|
}
|
|
} else {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT);
|
|
}
|
|
}
|
|
|
|
static bool __ThreadmanIdListIsSleeping(const PSPThread *t) {
|
|
return t->isWaitingFor(WAITTYPE_SLEEP, 0);
|
|
}
|
|
|
|
static bool __ThreadmanIdListIsDelayed(const PSPThread *t) {
|
|
return t->isWaitingFor(WAITTYPE_DELAY, t->GetUID());
|
|
}
|
|
|
|
static bool __ThreadmanIdListIsSuspended(const PSPThread *t) {
|
|
return t->isSuspended();
|
|
}
|
|
|
|
static bool __ThreadmanIdListIsDormant(const PSPThread *t) {
|
|
return t->isStopped();
|
|
}
|
|
|
|
u32 sceKernelGetThreadmanIdList(u32 type, u32 readBufPtr, u32 readBufSize, u32 idCountPtr) {
|
|
if (readBufSize >= 0x8000000) {
|
|
// Not exact, it's probably if the sum ends up negative or something.
|
|
ERROR_LOG_REPORT(Log::sceKernel, "sceKernelGetThreadmanIdList(%i, %08x, %i, %08x): invalid size", type, readBufPtr, readBufSize, idCountPtr);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_ADDR;
|
|
}
|
|
if (!Memory::IsValidAddress(readBufPtr) && readBufSize > 0) {
|
|
// Crashes on a PSP.
|
|
ERROR_LOG_REPORT(Log::sceKernel, "sceKernelGetThreadmanIdList(%i, %08x, %i, %08x): invalid pointer", type, readBufPtr, readBufSize, idCountPtr);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
u32 total = 0;
|
|
auto uids = PSPPointer<SceUID_le>::Create(readBufPtr);
|
|
u32 error;
|
|
if (type > 0 && type <= SCE_KERNEL_TMID_Tlspl) {
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelGetThreadmanIdList(%i, %08x, %i, %08x)", type, readBufPtr, readBufSize, idCountPtr);
|
|
total = kernelObjects.ListIDType(type, uids, readBufSize);
|
|
} else if (type >= SCE_KERNEL_TMID_SleepThread && type <= SCE_KERNEL_TMID_DormantThread) {
|
|
bool (*checkFunc)(const PSPThread *t) = nullptr;
|
|
switch (type) {
|
|
case SCE_KERNEL_TMID_SleepThread:
|
|
checkFunc = &__ThreadmanIdListIsSleeping;
|
|
break;
|
|
|
|
case SCE_KERNEL_TMID_DelayThread:
|
|
checkFunc = &__ThreadmanIdListIsDelayed;
|
|
break;
|
|
|
|
case SCE_KERNEL_TMID_SuspendThread:
|
|
checkFunc = &__ThreadmanIdListIsSuspended;
|
|
break;
|
|
|
|
case SCE_KERNEL_TMID_DormantThread:
|
|
checkFunc = &__ThreadmanIdListIsDormant;
|
|
break;
|
|
|
|
default:
|
|
_dbg_assert_msg_(false, "Unexpected type %d", type);
|
|
}
|
|
|
|
for (size_t i = 0; i < threadqueue.size(); i++) {
|
|
const PSPThread *t = kernelObjects.Get<PSPThread>(threadqueue[i], error);
|
|
if (checkFunc(t)) {
|
|
if (total < readBufSize) {
|
|
*uids++ = threadqueue[i];
|
|
}
|
|
++total;
|
|
}
|
|
}
|
|
} else {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "sceKernelGetThreadmanIdList(%i, %08x, %i, %08x): invalid type", type, readBufPtr, readBufSize, idCountPtr);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_TYPE;
|
|
}
|
|
|
|
if (Memory::IsValidAddress(idCountPtr)) {
|
|
Memory::WriteOrException_U32(total, idCountPtr);
|
|
}
|
|
return total > readBufSize ? readBufSize : total;
|
|
}
|
|
|
|
// Saves the current CPU context
|
|
void __KernelSaveContext(PSPThreadContext *ctx, bool vfpuEnabled) {
|
|
// r and f are immediately next to each other and must be. Ignore static analyzer warnings here!
|
|
memcpy((void *)ctx->r, (void *)currentMIPS->r, sizeof(ctx->r) + sizeof(ctx->f));
|
|
|
|
if (vfpuEnabled) {
|
|
memcpy(ctx->v, currentMIPS->v, sizeof(ctx->v));
|
|
memcpy(ctx->vfpuCtrl, currentMIPS->vfpuCtrl, sizeof(ctx->vfpuCtrl));
|
|
}
|
|
|
|
memcpy(ctx->other, currentMIPS->other, sizeof(ctx->other));
|
|
}
|
|
|
|
// Loads a CPU context
|
|
void __KernelLoadContext(const PSPThreadContext *ctx, bool vfpuEnabled) {
|
|
// r and f are immediately next to each other and must be. Ignore static analyzer warnings here!
|
|
memcpy((void *)currentMIPS->r, (void *)ctx->r, sizeof(ctx->r) + sizeof(ctx->f));
|
|
|
|
if (vfpuEnabled) {
|
|
memcpy(currentMIPS->v, ctx->v, sizeof(ctx->v));
|
|
memcpy(currentMIPS->vfpuCtrl, ctx->vfpuCtrl, sizeof(ctx->vfpuCtrl));
|
|
}
|
|
|
|
KernelValidateThreadTarget(ctx->pc);
|
|
|
|
memcpy(currentMIPS->other, ctx->other, sizeof(ctx->other));
|
|
// Not locking here, we assume the jit isn't switched during execution.
|
|
if (MIPSComp::jit) {
|
|
// When thread switching, we must update the rounding mode if cached in the jit.
|
|
MIPSComp::jit->UpdateFCR31();
|
|
}
|
|
|
|
// Reset the llBit, the other thread may have touched memory.
|
|
currentMIPS->llBit = 0;
|
|
}
|
|
|
|
u32 __KernelResumeThreadFromWait(SceUID threadID, u32 retval)
|
|
{
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t)
|
|
{
|
|
t->resumeFromWait();
|
|
t->setReturnValue(retval);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
ERROR_LOG(Log::sceKernel, "__KernelResumeThreadFromWait(%d): bad thread: %08x", threadID, error);
|
|
return error;
|
|
}
|
|
}
|
|
|
|
u32 __KernelResumeThreadFromWait(SceUID threadID, u64 retval)
|
|
{
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t)
|
|
{
|
|
t->resumeFromWait();
|
|
t->setReturnValue(retval);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
ERROR_LOG(Log::sceKernel, "__KernelResumeThreadFromWait(%d): bad thread: %08x", threadID, error);
|
|
return error;
|
|
}
|
|
}
|
|
|
|
// makes the current thread wait for an event
|
|
void __KernelWaitCurThread(WaitType type, SceUID waitID, u32 waitValue, u32 timeoutPtr, bool processCallbacks, const char *reason) {
|
|
if (!dispatchEnabled) {
|
|
WARN_LOG_REPORT(Log::sceKernel, "Ignoring wait, dispatching disabled... right thing to do?");
|
|
return;
|
|
}
|
|
|
|
PSPThread *thread = __GetCurrentThread();
|
|
_assert_(thread != nullptr);
|
|
if ((thread->nt.status & THREADSTATUS_WAIT) != 0)
|
|
WARN_LOG_REPORT(Log::sceKernel, "Waiting thread for %d that was already waiting for %d", type, thread->nt.waitType);
|
|
thread->nt.waitID = waitID;
|
|
thread->nt.waitType = type;
|
|
thread->hasWaited = true;
|
|
__KernelChangeThreadState(thread, ThreadStatus(THREADSTATUS_WAIT | (thread->nt.status & THREADSTATUS_SUSPEND)));
|
|
thread->nt.numReleases++;
|
|
thread->waitInfo.waitValue = waitValue;
|
|
thread->waitInfo.timeoutPtr = timeoutPtr;
|
|
|
|
if (!reason) {
|
|
reason = "started wait";
|
|
}
|
|
|
|
hleReSchedule(processCallbacks, reason);
|
|
}
|
|
|
|
void __KernelWaitCallbacksCurThread(WaitType type, SceUID waitID, u32 waitValue, u32 timeoutPtr) {
|
|
if (!dispatchEnabled) {
|
|
WARN_LOG_REPORT(Log::sceKernel, "Ignoring wait, dispatching disabled... right thing to do?");
|
|
return;
|
|
}
|
|
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if ((thread->nt.status & THREADSTATUS_WAIT) != 0)
|
|
WARN_LOG_REPORT(Log::sceKernel, "Waiting thread for %d that was already waiting for %d", type, thread->nt.waitType);
|
|
thread->nt.waitID = waitID;
|
|
thread->nt.waitType = type;
|
|
__KernelChangeThreadState(thread, ThreadStatus(THREADSTATUS_WAIT | (thread->nt.status & THREADSTATUS_SUSPEND)));
|
|
// TODO: Probably not...?
|
|
thread->nt.numReleases++;
|
|
thread->waitInfo.waitValue = waitValue;
|
|
thread->waitInfo.timeoutPtr = timeoutPtr;
|
|
|
|
__KernelForceCallbacks();
|
|
}
|
|
|
|
void hleScheduledWakeup(u64 userdata, int cyclesLate)
|
|
{
|
|
SceUID threadID = (SceUID)userdata;
|
|
u32 error;
|
|
if (__KernelGetWaitID(threadID, WAITTYPE_DELAY, error) == threadID)
|
|
{
|
|
__KernelResumeThreadFromWait(threadID, 0);
|
|
__KernelReSchedule("thread delay finished");
|
|
}
|
|
}
|
|
|
|
void __KernelScheduleWakeup(SceUID threadID, s64 usFromNow)
|
|
{
|
|
s64 cycles = usToCycles(usFromNow);
|
|
CoreTiming::ScheduleEvent(cycles, eventScheduledWakeup, threadID);
|
|
}
|
|
|
|
void __KernelCancelWakeup(SceUID threadID)
|
|
{
|
|
CoreTiming::UnscheduleEvent(eventScheduledWakeup, threadID);
|
|
}
|
|
|
|
// On hardware these outcomes depend on the phase of the clock. We go with the likelier one, except
|
|
// that always taking a likely-but-not-certain one could lock a polling loop into never yielding.
|
|
// Those are true for the given share of calls, spread evenly: 60 is true three times in five.
|
|
static bool __KernelChance(int percent) {
|
|
if (percent < 50) {
|
|
return false;
|
|
} else if (percent >= 100) {
|
|
return true;
|
|
}
|
|
g_chanceAccumulator += percent;
|
|
if (g_chanceAccumulator >= 100) {
|
|
g_chanceAccumulator -= 100;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// How a wait's timeout plays out on hardware (pspautotests threads/scheduling/waittimeouts). The
|
|
// deadline is taken, and the alarm set up a moment later. If the deadline has passed by then, the
|
|
// wait fails with SCE_KERNEL_ERROR_WAIT_TIMEOUT at once, without giving up the CPU or writing the
|
|
// timeout back. For most calls that happens about 85% of the time for 0us, half the time for 1us,
|
|
// 15% for 2us and never from 3us on. sceKernelAllocateVpl does more first: always up to 1us, then
|
|
// about 17% less per us. Longer ones end max(timeout, 205us) plus about 35us after the call. The
|
|
// deadline is taken late in the call, so the time left written back when something else ends the
|
|
// wait counts from there.
|
|
bool __KernelWaitTimesOutAtOnce(u32 timeoutPtr, int basePercent, int stepPercent) {
|
|
if (!Memory::IsValid4AlignedAddress(timeoutPtr)) {
|
|
return false;
|
|
}
|
|
const u32 micro = std::min(Memory::ReadUnchecked_U32(timeoutPtr), 100U);
|
|
return __KernelChance(basePercent - (int)micro * stepPercent);
|
|
}
|
|
|
|
s64 __KernelWaitTimeoutUs(u32 micro) {
|
|
return (s64)std::max(micro, 205U) + WAIT_TIMEOUT_DEADLINE_US + WAIT_TIMEOUT_LATENCY_US;
|
|
}
|
|
|
|
void hleThreadEndTimeout(u64 userdata, int cyclesLate)
|
|
{
|
|
SceUID threadID = (SceUID) userdata;
|
|
HLEKernel::WaitExecTimeout<PSPThread, WAITTYPE_THREADEND>(threadID);
|
|
}
|
|
|
|
// One event times out every kernel object wait (the thread only has one wait at a time), and hands
|
|
// over to the timeout function registered for the thread's wait type.
|
|
static void __KernelWaitTimeoutFired(u64 userdata, int cyclesLate) {
|
|
u32 error;
|
|
const PSPThread *thread = kernelObjects.Get<PSPThread>((SceUID)userdata, error);
|
|
if (!thread) {
|
|
return;
|
|
}
|
|
const WaitTimeoutFunc func = waitTypeFuncs[thread->nt.waitType].timeoutFunc;
|
|
if (func) {
|
|
func(userdata, cyclesLate);
|
|
}
|
|
}
|
|
|
|
int __KernelWaitTimeoutEvent() {
|
|
return eventWaitTimeout;
|
|
}
|
|
|
|
void __KernelScheduleWaitTimeout(SceUID threadID, u32 timeoutPtr) {
|
|
if (timeoutPtr == 0) {
|
|
return;
|
|
}
|
|
const u32 micro = Memory::ReadOrException_U32(timeoutPtr);
|
|
CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), eventWaitTimeout, threadID);
|
|
}
|
|
|
|
void __KernelWaitCurThreadWithTimeout(WaitType type, SceUID waitID, u32 waitValue, u32 timeoutPtr, bool processCallbacks, const char *reason) {
|
|
__KernelScheduleWaitTimeout(__KernelGetCurThread(), timeoutPtr);
|
|
__KernelWaitCurThread(type, waitID, waitValue, timeoutPtr, processCallbacks, reason);
|
|
}
|
|
|
|
void __KernelRestoreOldWaitTimeoutEvent(int &eventType, const char *name) {
|
|
CoreTiming::RestoreRegisterEvent(eventType, name, &__KernelWaitTimeoutFired);
|
|
}
|
|
|
|
void __KernelCancelThreadEndTimeout(SceUID threadID)
|
|
{
|
|
CoreTiming::UnscheduleEvent(eventWaitTimeout, threadID);
|
|
}
|
|
|
|
static void __KernelRemoveFromThreadQueue(SceUID threadID) {
|
|
std::lock_guard<std::mutex> guard(threadqueueLock);
|
|
|
|
int prio = __KernelGetThreadPrio(threadID);
|
|
if (prio != 0)
|
|
threadReadyQueue.remove(prio, threadID);
|
|
|
|
threadqueue.erase(std::remove(threadqueue.begin(), threadqueue.end(), threadID), threadqueue.end());
|
|
}
|
|
|
|
void __KernelStopThread(SceUID threadID, int exitStatus, const char *reason)
|
|
{
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t)
|
|
{
|
|
__KernelChangeReadyState(t, threadID, false);
|
|
t->nt.exitStatus = exitStatus;
|
|
t->nt.status = THREADSTATUS_DORMANT;
|
|
__KernelFireThreadEnd(threadID);
|
|
for (size_t i = 0; i < t->waitingThreads.size(); ++i)
|
|
{
|
|
const SceUID waitingThread = t->waitingThreads[i];
|
|
u32 timeoutPtr = __KernelGetWaitTimeoutPtr(waitingThread, error);
|
|
if (HLEKernel::VerifyWait(waitingThread, WAITTYPE_THREADEND, threadID))
|
|
{
|
|
s64 cyclesLeft = CoreTiming::UnscheduleEvent(eventWaitTimeout, waitingThread) - usToCycles(WAIT_TIMEOUT_LATENCY_US);
|
|
if (cyclesLeft < 0)
|
|
cyclesLeft = 0;
|
|
if (timeoutPtr != 0)
|
|
Memory::WriteOrException_U32((u32) cyclesToUs(cyclesLeft), timeoutPtr);
|
|
|
|
HLEKernel::ResumeFromWait(waitingThread, WAITTYPE_THREADEND, threadID, exitStatus);
|
|
}
|
|
}
|
|
t->waitingThreads.clear();
|
|
|
|
// Stopped threads are never waiting.
|
|
t->nt.waitType = WAITTYPE_NONE;
|
|
t->nt.waitID = 0;
|
|
} else {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "__KernelStopThread: thread %d does not exist (%s)", threadID, reason ? reason : "?");
|
|
}
|
|
}
|
|
|
|
u32 __KernelDeleteThread(SceUID threadID, int exitStatus, const char *reason)
|
|
{
|
|
__KernelStopThread(threadID, exitStatus, reason);
|
|
__KernelRemoveFromThreadQueue(threadID);
|
|
|
|
if (currentThread == threadID)
|
|
__SetCurrentThread(NULL, 0, NULL);
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t)
|
|
{
|
|
for (auto it = t->callbacks.begin(), end = t->callbacks.end(); it != end; ++it)
|
|
{
|
|
PSPCallback *callback = kernelObjects.Get<PSPCallback>(*it, error);
|
|
if (callback && callback->nc.notifyCount != 0)
|
|
readyCallbacksCount--;
|
|
}
|
|
|
|
t->Cleanup();
|
|
|
|
// Before triggering, set v0. It'll be restored if one is called.
|
|
RETURN(error);
|
|
t->nt.status = THREADSTATUS_DEAD;
|
|
|
|
if (__KernelThreadTriggerEvent((t->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, threadID, THREADEVENT_DELETE)) {
|
|
// Don't delete it yet. We'll delete later.
|
|
pendingDeleteThreads.push_back(threadID);
|
|
return 0;
|
|
} else {
|
|
return kernelObjects.Destroy<PSPThread>(threadID);
|
|
}
|
|
} else {
|
|
RETURN(error);
|
|
return error;
|
|
}
|
|
}
|
|
|
|
static void __ReportThreadQueueEmpty() {
|
|
// We failed to find a thread to schedule.
|
|
// This means something horrible happened to the idle threads.
|
|
u32 error;
|
|
PSPThread *idleThread0 = kernelObjects.Get<PSPThread>(threadIdleID[0], error);
|
|
PSPThread *idleThread1 = kernelObjects.Get<PSPThread>(threadIdleID[1], error);
|
|
|
|
char idleDescription0[256];
|
|
int idleStatus0 = -1;
|
|
if (idleThread0) {
|
|
idleThread0->GetQuickInfo(idleDescription0, sizeof(idleDescription0));
|
|
idleStatus0 = idleThread0->nt.status;
|
|
} else {
|
|
truncate_cpy(idleDescription0, "DELETED");
|
|
}
|
|
|
|
char idleDescription1[256];
|
|
int idleStatus1 = -1;
|
|
if (idleThread1) {
|
|
idleThread1->GetQuickInfo(idleDescription1, sizeof(idleDescription1));
|
|
idleStatus1 = idleThread1->nt.status;
|
|
} else {
|
|
truncate_cpy(idleDescription1, "DELETED");
|
|
}
|
|
|
|
ERROR_LOG_REPORT_ONCE(threadqueueempty, Log::sceKernel, "Failed to reschedule: out of threads on queue (%d, %d)", idleStatus0, idleStatus1);
|
|
WARN_LOG(Log::sceKernel, "Failed to reschedule: idle0 -> %s", idleDescription0);
|
|
WARN_LOG(Log::sceKernel, "Failed to reschedule: idle1 -> %s", idleDescription1);
|
|
}
|
|
|
|
// Returns NULL if the current thread is fine.
|
|
// A syscall that keeps the CPU busy for a long time, like sceKernelCreateThread filling a big
|
|
// stack. The kernel runs it with interrupts on, so a better thread that wakes meanwhile preempts
|
|
// it, and the time that thread takes doesn't count towards the syscall. Worse threads don't get a
|
|
// look in. (tests/threads/scheduling/preemptsyscall.) Modelled as a wait that only an idle thread
|
|
// may stand in for, whose countdown only runs while one does.
|
|
// hleDelayResult waits with id 1.
|
|
static const SceUID BUSY_SYSCALL_WAIT_ID = 2;
|
|
|
|
static bool __KernelIsIdleThread(SceUID threadID) {
|
|
return threadID == threadIdleID[0] || threadID == threadIdleID[1];
|
|
}
|
|
|
|
// Also drops entries for threads that stopped waiting (terminated, say.)
|
|
static PSPThread *__KernelBestBusyThread() {
|
|
PSPThread *best = nullptr;
|
|
for (size_t i = 0; i < busySyscalls.size(); ) {
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(busySyscalls[i].threadID, error);
|
|
if (!t || !t->isWaitingFor(WAITTYPE_HLEDELAY, BUSY_SYSCALL_WAIT_ID)) {
|
|
if (busySyscalls[i].counting) {
|
|
CoreTiming::UnscheduleEvent(eventBusySyscallDone, busySyscalls[i].threadID);
|
|
}
|
|
busySyscalls.erase(busySyscalls.begin() + i);
|
|
continue;
|
|
}
|
|
if (!best || t->nt.currentPriority < best->nt.currentPriority) {
|
|
best = t;
|
|
}
|
|
++i;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// The best busy syscall makes progress only while an idle thread stands in for it.
|
|
static void __KernelUpdateBusySyscalls(PSPThread *target) {
|
|
if (busySyscalls.empty()) {
|
|
return;
|
|
}
|
|
PSPThread *best = __KernelBestBusyThread();
|
|
const bool idleStandsIn = target && __KernelIsIdleThread(target->GetUID());
|
|
for (BusySyscall &busy : busySyscalls) {
|
|
const bool shouldCount = idleStandsIn && best && busy.threadID == best->GetUID();
|
|
if (shouldCount && !busy.counting) {
|
|
CoreTiming::ScheduleEvent(busy.remainingCycles, eventBusySyscallDone, busy.threadID);
|
|
busy.counting = true;
|
|
} else if (!shouldCount && busy.counting) {
|
|
const s64 left = CoreTiming::UnscheduleEvent(eventBusySyscallDone, busy.threadID);
|
|
busy.remainingCycles = left > 0 ? left : 0;
|
|
busy.counting = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void __KernelBusySyscallDone(u64 userdata, int cyclesLate) {
|
|
const SceUID threadID = (SceUID)userdata;
|
|
for (size_t i = 0; i < busySyscalls.size(); ++i) {
|
|
if (busySyscalls[i].threadID == threadID) {
|
|
busySyscalls.erase(busySyscalls.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t && t->isWaitingFor(WAITTYPE_HLEDELAY, BUSY_SYSCALL_WAIT_ID)) {
|
|
__KernelResumeThreadFromWait(threadID, t->getWaitInfo().waitValue);
|
|
// It never gave up the CPU, so it goes back ahead of threads of the same priority.
|
|
if (t->nt.status == THREADSTATUS_READY) {
|
|
threadReadyQueue.remove(t->nt.currentPriority, threadID);
|
|
threadReadyQueue.push_front(t->nt.currentPriority, threadID);
|
|
}
|
|
__KernelReSchedule("busy syscall done");
|
|
}
|
|
}
|
|
|
|
u32 __KernelBusyDelayResult(u32 result, int cycles, const char *reason) {
|
|
if (cycles <= 0 || !__KernelIsDispatchEnabled() || __IsInInterrupt()) {
|
|
hleEatCycles(cycles);
|
|
return result;
|
|
}
|
|
busySyscalls.push_back(BusySyscall{ __KernelGetCurThread(), cycles, false });
|
|
__KernelWaitCurThread(WAITTYPE_HLEDELAY, BUSY_SYSCALL_WAIT_ID, result, 0, false, reason);
|
|
return result;
|
|
}
|
|
|
|
static PSPThread *__KernelNextThread() {
|
|
SceUID bestThread;
|
|
|
|
// If the current thread is running, it's a valid candidate.
|
|
PSPThread *cur = __GetCurrentThread();
|
|
|
|
// A busy syscall keeps the CPU unless something better wants it. An idle thread stands in.
|
|
PSPThread *busy = busySyscalls.empty() ? nullptr : __KernelBestBusyThread();
|
|
if (busy) {
|
|
const u32 busyPriority = busy->nt.currentPriority;
|
|
const SceUID bestReady = threadReadyQueue.peek_first();
|
|
PSPThread *bestReadyThread = bestReady != 0 ? kernelObjects.GetFast<PSPThread>(bestReady) : nullptr;
|
|
const bool readyOutranks = bestReadyThread && bestReadyThread->nt.currentPriority < busyPriority;
|
|
const bool curOutranks = cur && cur->isRunning() && !__KernelIsIdleThread(currentThread) && cur->nt.currentPriority < busyPriority;
|
|
if (!readyOutranks && !curOutranks) {
|
|
if (cur && cur->isRunning() && __KernelIsIdleThread(currentThread)) {
|
|
return nullptr;
|
|
}
|
|
for (SceUID idleID : threadIdleID) {
|
|
PSPThread *idle = kernelObjects.GetFast<PSPThread>(idleID);
|
|
if (idle && idle->nt.status == THREADSTATUS_READY) {
|
|
threadReadyQueue.remove(idle->nt.currentPriority, idleID);
|
|
if (cur && cur->isRunning()) {
|
|
__KernelChangeReadyState(cur, currentThread, true);
|
|
}
|
|
return idle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cur && cur->isRunning()) {
|
|
bestThread = threadReadyQueue.pop_first_better(cur->nt.currentPriority);
|
|
if (bestThread != 0)
|
|
__KernelChangeReadyState(cur, currentThread, true);
|
|
} else {
|
|
bestThread = threadReadyQueue.pop_first();
|
|
|
|
if (bestThread == 0) {
|
|
// Zoinks. No thread?
|
|
__ReportThreadQueueEmpty();
|
|
|
|
// Let's try to get back on track, if possible.
|
|
bestThread = threadIdleID[1];
|
|
}
|
|
}
|
|
|
|
// Assume threadReadyQueue has not become corrupt.
|
|
if (bestThread != 0)
|
|
return kernelObjects.GetFast<PSPThread>(bestThread);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
// Set by sceKernelStartThread when the new thread outranks the caller, for the reschedule right
|
|
// after it. The firmware hands the CPU straight to the new thread, even when a thread of better
|
|
// priority is ready but hasn't been dispatched (say, one that a sceKernelTerminateThread woke.)
|
|
// See tests/threads/threads/termsuspended.
|
|
static SceUID g_startThreadHandoff = 0;
|
|
|
|
void __KernelReSchedule(const char *reason)
|
|
{
|
|
const SceUID handoff = g_startThreadHandoff;
|
|
g_startThreadHandoff = 0;
|
|
|
|
// First, let's check if there are any pending callbacks to trigger.
|
|
// TODO: Could probably take this out of __KernelReSchedule() which is a bit hot.
|
|
__KernelCheckCallbacks();
|
|
|
|
// Execute any pending events while we're doing scheduling.
|
|
CoreTiming::Advance(currentMIPS);
|
|
if (__IsInInterrupt() || !__KernelIsDispatchEnabled()) {
|
|
// Threads don't get changed within interrupts or while dispatch is disabled.
|
|
reason = "In Interrupt Or Callback";
|
|
return;
|
|
}
|
|
|
|
if (handoff != 0) {
|
|
u32 error;
|
|
PSPThread *started = kernelObjects.Get<PSPThread>(handoff, error);
|
|
if (started && started->nt.status == THREADSTATUS_READY) {
|
|
threadReadyQueue.remove(started->nt.currentPriority, handoff);
|
|
__KernelSwitchContext(started, reason);
|
|
return;
|
|
}
|
|
}
|
|
|
|
PSPThread *nextThread = __KernelNextThread();
|
|
if (nextThread) {
|
|
__KernelSwitchContext(nextThread, reason);
|
|
}
|
|
// Otherwise, no need to switch.
|
|
}
|
|
|
|
void __KernelReSchedule(bool doCallbacks, const char *reason)
|
|
{
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (doCallbacks && thread != nullptr) {
|
|
thread->isProcessingCallbacks = doCallbacks;
|
|
}
|
|
|
|
// Note - this calls the function above, not this one. Overloading...
|
|
__KernelReSchedule(reason);
|
|
if (doCallbacks && thread != nullptr && thread->GetUID() == currentThread) {
|
|
if (thread->isRunning()) {
|
|
thread->isProcessingCallbacks = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
int sceKernelCheckThreadStack()
|
|
{
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(__KernelGetCurThread(), error);
|
|
if (t) {
|
|
u32 diff = labs((long)((s64)currentMIPS->r[MIPS_REG_SP] - (s64)t->currentStack.start));
|
|
DEBUG_LOG(Log::sceKernel, "%i=sceKernelCheckThreadStack()", diff);
|
|
return diff;
|
|
} else {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "sceKernelCheckThreadStack() - not on thread");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void PSPThreadContext::reset() {
|
|
for (int i = 0; i<32; i++) {
|
|
r[i] = 0xDEADBEEF;
|
|
fi[i] = 0x7f800001;
|
|
}
|
|
r[0] = 0;
|
|
for (int i = 0; i<128; i++) {
|
|
vi[i] = 0x7f800001;
|
|
}
|
|
for (int i = 0; i<15; i++) {
|
|
vfpuCtrl[i] = 0x00000000;
|
|
}
|
|
vfpuCtrl[VFPU_CTRL_SPREFIX] = 0xe4; // neutral
|
|
vfpuCtrl[VFPU_CTRL_TPREFIX] = 0xe4; // neutral
|
|
vfpuCtrl[VFPU_CTRL_DPREFIX] = 0x0; // neutral
|
|
vfpuCtrl[VFPU_CTRL_CC] = 0x3f;
|
|
vfpuCtrl[VFPU_CTRL_INF4] = 0;
|
|
vfpuCtrl[VFPU_CTRL_REV] = 0x7772ceab;
|
|
vfpuCtrl[VFPU_CTRL_RCX0] = 0x3f800001;
|
|
vfpuCtrl[VFPU_CTRL_RCX1] = 0x3f800002;
|
|
vfpuCtrl[VFPU_CTRL_RCX2] = 0x3f800004;
|
|
vfpuCtrl[VFPU_CTRL_RCX3] = 0x3f800008;
|
|
vfpuCtrl[VFPU_CTRL_RCX4] = 0x3f800000;
|
|
vfpuCtrl[VFPU_CTRL_RCX5] = 0x3f800000;
|
|
vfpuCtrl[VFPU_CTRL_RCX6] = 0x3f800000;
|
|
vfpuCtrl[VFPU_CTRL_RCX7] = 0x3f800000;
|
|
fpcond = 0;
|
|
fcr31 = 0x00000e00;
|
|
hi = 0xDEADBEEF;
|
|
lo = 0xDEADBEEF;
|
|
// Just for a clean state.
|
|
other[5] = 0;
|
|
}
|
|
|
|
void __KernelResetThread(PSPThread *t, int lowestPriority) {
|
|
t->context.reset();
|
|
t->context.pc = t->nt.entrypoint;
|
|
|
|
// If the thread would be better than lowestPriority, reset to its initial. Yes, kinda odd...
|
|
if (t->nt.currentPriority < lowestPriority)
|
|
t->nt.currentPriority = t->nt.initialPriority;
|
|
|
|
t->nt.waitType = WAITTYPE_NONE;
|
|
t->nt.waitID = 0;
|
|
memset(&t->waitInfo, 0, sizeof(t->waitInfo));
|
|
|
|
t->nt.exitStatus = SCE_KERNEL_ERROR_NOT_DORMANT;
|
|
t->isProcessingCallbacks = false;
|
|
t->hasWaited = false;
|
|
t->currentCallbackId = 0;
|
|
t->currentMipscallId = 0;
|
|
t->pendingMipsCalls.clear();
|
|
|
|
// This will be overwritten when starting the thread, but let's point it somewhere useful.
|
|
t->context.r[MIPS_REG_RA] = threadReturnHackAddr;
|
|
// TODO: Not sure if it's reset here, but this makes sense.
|
|
t->context.r[MIPS_REG_GP] = t->nt.gpreg;
|
|
t->FillStack();
|
|
|
|
if (!t->waitingThreads.empty())
|
|
ERROR_LOG_REPORT(Log::sceKernel, "Resetting thread with threads waiting on end?");
|
|
}
|
|
|
|
PSPThread *__KernelCreateThreadObject(SceUID &id, SceUID moduleId, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr) {
|
|
std::lock_guard<std::mutex> guard(threadqueueLock);
|
|
|
|
PSPThread *t = new PSPThread();
|
|
id = kernelObjects.Create(t);
|
|
|
|
threadqueue.push_back(id);
|
|
threadReadyQueue.prepare(priority);
|
|
|
|
memset(&t->nt, 0xCD, sizeof(t->nt));
|
|
|
|
t->nt.entrypoint = entryPoint;
|
|
t->nt.nativeSize = sizeof(t->nt);
|
|
t->nt.attr = attr;
|
|
// TODO: I have no idea what this value is but the PSP firmware seems to add it on create.
|
|
t->nt.attr |= 0xFF;
|
|
t->nt.initialPriority = t->nt.currentPriority = priority;
|
|
t->nt.stackSize = stacksize;
|
|
t->nt.status = THREADSTATUS_DORMANT;
|
|
|
|
t->nt.numInterruptPreempts = 0;
|
|
t->nt.numReleases = 0;
|
|
t->nt.numThreadPreempts = 0;
|
|
t->nt.runForClocks.lo = 0;
|
|
t->nt.runForClocks.hi = 0;
|
|
t->nt.wakeupCount = 0;
|
|
t->nt.initialStack = 0;
|
|
t->nt.waitID = 0;
|
|
t->nt.exitStatus = SCE_KERNEL_ERROR_DORMANT;
|
|
t->nt.waitType = WAITTYPE_NONE;
|
|
|
|
if (moduleId)
|
|
t->nt.gpreg = __KernelGetModuleGP(moduleId);
|
|
else
|
|
t->nt.gpreg = 0; // sceKernelStartThread will take care of this.
|
|
t->moduleId = moduleId;
|
|
|
|
strncpy(t->nt.name, name, KERNELOBJECT_MAX_NAME_LENGTH);
|
|
t->nt.name[KERNELOBJECT_MAX_NAME_LENGTH] = '\0';
|
|
|
|
u32 stackSize = t->nt.stackSize;
|
|
t->AllocateStack(stackSize); // can change the stacksize!
|
|
t->nt.stackSize = stackSize;
|
|
return t;
|
|
}
|
|
|
|
SceUID __KernelSetupRootThread(SceUID moduleID, int args, const char *argp, int prio, int stacksize, int attr)
|
|
{
|
|
//grab mips regs
|
|
SceUID id;
|
|
PSPThread *thread = __KernelCreateThreadObject(id, moduleID, "root", currentMIPS->pc, prio, stacksize, attr);
|
|
if (thread->currentStack.start == 0)
|
|
ERROR_LOG_REPORT(Log::sceKernel, "Unable to allocate stack for root thread.");
|
|
__KernelResetThread(thread, 0);
|
|
|
|
PSPThread *prevThread = __GetCurrentThread();
|
|
if (prevThread && prevThread->isRunning())
|
|
__KernelChangeReadyState(currentThread, true);
|
|
__SetCurrentThread(thread, id, "root");
|
|
thread->nt.status = THREADSTATUS_RUNNING; // do not schedule
|
|
|
|
truncate_cpy(thread->nt.name, "root");
|
|
|
|
KernelValidateThreadTarget(thread->context.pc);
|
|
|
|
__KernelLoadContext(&thread->context, (attr & PSP_THREAD_ATTR_VFPU) != 0);
|
|
currentMIPS->r[MIPS_REG_A0] = args;
|
|
currentMIPS->r[MIPS_REG_SP] -= (args + 0xf) & ~0xf;
|
|
u32 location = currentMIPS->r[MIPS_REG_SP];
|
|
currentMIPS->r[MIPS_REG_A1] = location;
|
|
if (argp)
|
|
Memory::Memcpy(location, argp, args, "ThreadParam");
|
|
// Let's assume same as starting a new thread, 64 bytes for safety/kernel.
|
|
currentMIPS->r[MIPS_REG_SP] -= 64;
|
|
|
|
return id;
|
|
}
|
|
|
|
SceUID __KernelCreateThreadInternal(const char *threadName, SceUID moduleID, u32 entry, u32 prio, int stacksize, u32 attr)
|
|
{
|
|
SceUID id;
|
|
PSPThread *newThread = __KernelCreateThreadObject(id, moduleID, threadName, entry, prio, stacksize, attr);
|
|
if (newThread->currentStack.start == 0)
|
|
return SCE_KERNEL_ERROR_NO_MEMORY;
|
|
|
|
return id;
|
|
}
|
|
|
|
// Note: Removed all the uses of hleReport* etc.
|
|
int __KernelCreateThread(const char *threadName, SceUID moduleID, u32 entry, u32 prio, int stacksize, u32 attr, u32 optionAddr, bool allowKernel, int *busyCyclesOut) {
|
|
if (!threadName) {
|
|
ERROR_LOG(Log::sceKernel, "__KernelCreateThread: NULL thread name");
|
|
return SCE_KERNEL_ERROR_ERROR;
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
if ((u8)threadName[i] >= 0xf0) {
|
|
WARN_LOG(Log::sceKernel, "Overriding bad thread name to badThreadName. first 4 bytes: %02x %02x %02x %02x", threadName[0], threadName[1], threadName[2], threadName[3]);
|
|
// Smash Court Tennis gives us an unprintable thread name, messing up logs.
|
|
threadName = "badThreadName";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((u32)stacksize < 0x200) {
|
|
WARN_LOG_REPORT(Log::sceKernel, "bogus thread stack size %08x", stacksize);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_STACK_SIZE;
|
|
}
|
|
if (prio < 0x08 || prio > 0x77) {
|
|
WARN_LOG(Log::sceKernel, "bogus thread priority %08x", prio);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_PRIORITY;
|
|
}
|
|
if (!Memory::IsValidAddress(entry)) {
|
|
// The PSP firmware seems to allow NULL...?
|
|
if (entry != 0) {
|
|
ERROR_LOG(Log::sceKernel, "invalid thread entry %08x", entry);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_ADDR;
|
|
}
|
|
}
|
|
|
|
if ((attr & ~PSP_THREAD_ATTR_USER_MASK) != 0 && !allowKernel) {
|
|
WARN_LOG(Log::sceKernel, "illegal thread attributes %08x", attr);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_ATTR;
|
|
}
|
|
|
|
if ((attr & ~PSP_THREAD_ATTR_SUPPORTED) != 0) {
|
|
WARN_LOG_REPORT(Log::sceKernel, "sceKernelCreateThread(name=%s): unsupported attributes %08x, ignoring", threadName, attr & ~PSP_THREAD_ATTR_SUPPORTED);
|
|
}
|
|
|
|
// TODO: Not sure what these values are, but they are removed from the attr silently.
|
|
// Some are USB/VSH specific, probably removes when they are from the wrong module?
|
|
attr &= ~PSP_THREAD_ATTR_USER_ERASE;
|
|
|
|
if ((attr & PSP_THREAD_ATTR_KERNEL) == 0) {
|
|
if (allowKernel && (attr & PSP_THREAD_ATTR_USER) == 0) {
|
|
attr |= PSP_THREAD_ATTR_KERNEL;
|
|
} else {
|
|
attr |= PSP_THREAD_ATTR_USER;
|
|
}
|
|
}
|
|
|
|
SceUID id = __KernelCreateThreadInternal(threadName, moduleID, entry, prio, stacksize, attr);
|
|
if ((u32)id == SCE_KERNEL_ERROR_NO_MEMORY) {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "out of memory, %08x stack requested", stacksize);
|
|
return SCE_KERNEL_ERROR_NO_MEMORY;
|
|
}
|
|
|
|
if (optionAddr != 0) {
|
|
if (Memory::IsValid4AlignedAddress(optionAddr)) {
|
|
u32 structSize = Memory::ReadUnchecked_U32(optionAddr);
|
|
WARN_LOG(Log::sceKernel, "sceKernelCreateThread(name=%s): unsupported options parameter %08x", threadName, optionAddr);
|
|
} else {
|
|
ERROR_LOG(Log::sceKernel, "sceKernelCreateThread(name=%s): bad options parameter %08x", threadName, optionAddr);
|
|
}
|
|
}
|
|
|
|
// Creating a thread resumes dispatch automatically. Probably can't create without it.
|
|
dispatchEnabled = true;
|
|
|
|
// Measured by tests/threads/scheduling/costs: about 150us, plus filling the stack with 0xFF at
|
|
// around a cycle per byte - 1.3ms for a 256KB stack.
|
|
int fillCycles = 0;
|
|
if ((attr & PSP_THREAD_ATTR_NO_FILLSTACK) == 0 && stacksize > 0) {
|
|
fillCycles = stacksize - stacksize / 64;
|
|
}
|
|
hleEatCycles(32000);
|
|
// This won't schedule to the new thread, but it may to one woken from eating cycles.
|
|
hleReSchedule("thread created");
|
|
|
|
// Before triggering, set v0, since we restore on return.
|
|
RETURN(id);
|
|
const bool handled = __KernelThreadTriggerEvent((attr & PSP_THREAD_ATTR_KERNEL) != 0, id, THREADEVENT_CREATE);
|
|
if (busyCyclesOut && !handled) {
|
|
*busyCyclesOut = fillCycles;
|
|
} else {
|
|
// A handler is about to run as a call on this thread, so no busy wait around it.
|
|
hleEatCycles(fillCycles);
|
|
if (busyCyclesOut)
|
|
*busyCyclesOut = 0;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
int sceKernelCreateThread(const char *threadName, u32 entry, u32 prio, int stacksize, u32 attr, u32 optionAddr) {
|
|
PSPThread *cur = __GetCurrentThread();
|
|
SceUID module = __KernelGetCurThreadModuleId();
|
|
bool allowKernel = KernelModuleIsKernelMode(module) || hleIsKernelMode() || (cur ? (cur->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0 : false);
|
|
int busyCycles = 0;
|
|
int retval = __KernelCreateThread(threadName, module, entry, prio, stacksize, attr, optionAddr, allowKernel, &busyCycles);
|
|
if (retval < 0) {
|
|
return hleLogError(Log::sceKernel, retval);
|
|
} else {
|
|
return __KernelBusyDelayResult(hleLogInfo(Log::sceKernel, retval), busyCycles, "thread stack filled");
|
|
}
|
|
}
|
|
|
|
int __KernelStartThread(SceUID threadToStartID, int argSize, u32 argBlockPtr, bool forceArgs) {
|
|
u32 error;
|
|
PSPThread *startThread = kernelObjects.Get<PSPThread>(threadToStartID, error);
|
|
if (startThread == 0)
|
|
return error;
|
|
|
|
PSPThread *cur = __GetCurrentThread();
|
|
__KernelResetThread(startThread, cur ? (s32)cur->nt.currentPriority : 0);
|
|
|
|
u32 &sp = startThread->context.r[MIPS_REG_SP];
|
|
// Force args means just use those as a0/a1 without any special treatment.
|
|
// This is a hack to avoid allocating memory for helper threads which take args.
|
|
if (forceArgs) {
|
|
startThread->context.r[MIPS_REG_A0] = argSize;
|
|
startThread->context.r[MIPS_REG_A1] = argBlockPtr;
|
|
} else if (argBlockPtr && argSize > 0) {
|
|
// Make room for the arguments, always 0x10 aligned.
|
|
sp -= (argSize + 0xf) & ~0xf;
|
|
startThread->context.r[MIPS_REG_A0] = argSize;
|
|
startThread->context.r[MIPS_REG_A1] = sp;
|
|
|
|
// Now copy argument to stack.
|
|
if (Memory::IsValidAddress(argBlockPtr)) {
|
|
Memory::Memcpy(sp, argBlockPtr, argSize, "ThreadStartArgs");
|
|
}
|
|
} else {
|
|
startThread->context.r[MIPS_REG_A0] = 0;
|
|
startThread->context.r[MIPS_REG_A1] = 0;
|
|
}
|
|
|
|
// On the PSP, there's an extra 64 bytes of stack eaten after the args.
|
|
// This could be stack overflow safety, or just stack eaten by the kernel entry func.
|
|
sp -= 64;
|
|
|
|
// At the bottom of those 64 bytes, the return syscall and ra is written.
|
|
// Test Drive Unlimited actually depends on it being in the correct place.
|
|
WriteHLESyscall("FakeSysCalls", NID_THREADRETURN, sp);
|
|
Memory::WriteOrException_U32(MIPS_MAKE_B(-1), sp + 8);
|
|
Memory::WriteOrException_U32(MIPS_MAKE_NOP(), sp + 12);
|
|
|
|
// Point ra at our return stub, and start fp off matching sp.
|
|
startThread->context.r[MIPS_REG_RA] = sp;
|
|
startThread->context.r[MIPS_REG_FP] = sp;
|
|
|
|
// Smaller is better for priority. Only switch if the new thread is better.
|
|
if (cur && cur->nt.currentPriority > startThread->nt.currentPriority) {
|
|
KernelValidateThreadTarget(startThread->context.pc);
|
|
__KernelChangeReadyState(cur, currentThread, true);
|
|
if (__InterruptsEnabled()) {
|
|
// Handing over costs more: about 30us from the call to the new thread's entry on
|
|
// hardware, where starting a worse one takes 10-25us.
|
|
hleEatCycles(2000);
|
|
g_startThreadHandoff = threadToStartID;
|
|
hleReSchedule("thread started");
|
|
}
|
|
}
|
|
|
|
// Starting a thread automatically resumes the dispatch thread if the new thread has worse priority.
|
|
// Seems strange but also seems reproducible.
|
|
if (cur && cur->nt.currentPriority <= startThread->nt.currentPriority) {
|
|
dispatchEnabled = true;
|
|
}
|
|
|
|
__KernelChangeReadyState(startThread, threadToStartID, true);
|
|
|
|
// Need to write out v0 before triggering event.
|
|
// TODO: Technically the wrong place. This should trigger when the thread actually starts (e.g. if suspended.)
|
|
RETURN(0);
|
|
__KernelThreadTriggerEvent((startThread->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, threadToStartID, THREADEVENT_START);
|
|
return 0;
|
|
}
|
|
|
|
// This gets called from other places, so we don't use the hleLog* here.
|
|
int __KernelStartThreadValidate(SceUID threadToStartID, int argSize, u32 argBlockPtr, bool forceArgs) {
|
|
if (threadToStartID == 0) {
|
|
ERROR_LOG(Log::sceKernel, "thread id is 0");
|
|
return SCE_KERNEL_ERROR_ILLEGAL_THID;
|
|
}
|
|
|
|
if (argSize < 0 || argBlockPtr & 0x80000000) {
|
|
ERROR_LOG(Log::sceKernel, "bad thread argument pointer/length %08x / %08x", argSize, argBlockPtr);
|
|
return SCE_KERNEL_ERROR_ILLEGAL_ADDR;
|
|
}
|
|
|
|
u32 error = 0;
|
|
PSPThread *startThread = kernelObjects.Get<PSPThread>(threadToStartID, error);
|
|
if (startThread == 0) {
|
|
ERROR_LOG(Log::sceKernel, "thread does not exist: %08x", error);
|
|
return error;
|
|
}
|
|
|
|
if (startThread->nt.status != THREADSTATUS_DORMANT) {
|
|
WARN_LOG(Log::sceKernel, "thread already running");
|
|
return SCE_KERNEL_ERROR_NOT_DORMANT;
|
|
}
|
|
|
|
hleEatCycles(3400);
|
|
return __KernelStartThread(threadToStartID, argSize, argBlockPtr, forceArgs);
|
|
}
|
|
|
|
int sceKernelStartThread(SceUID threadToStartID, int argSize, u32 argBlockPtr) {
|
|
int retval = __KernelStartThreadValidate(threadToStartID, argSize, argBlockPtr);
|
|
return hleLogDebugOrError(Log::sceKernel, retval);
|
|
}
|
|
|
|
int sceKernelGetThreadStackFreeSize(SceUID threadID) {
|
|
if (threadID == 0)
|
|
threadID = __KernelGetCurThread();
|
|
|
|
u32 error;
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!thread) {
|
|
return hleLogError(Log::sceKernel, error, "invalid thread id");
|
|
}
|
|
|
|
// Scan the stack for 0xFF, starting after 0x10 (the thread id is written there.)
|
|
// Obviously this doesn't work great if PSP_THREAD_ATTR_NO_FILLSTACK is used.
|
|
|
|
int sz = 0;
|
|
if (Memory::IsValidRange(thread->currentStack.start + 0x10, thread->nt.stackSize - 0x10)) {
|
|
for (u32 offset = 0x10; offset < thread->nt.stackSize; ++offset) {
|
|
if (Memory::ReadUnchecked_U8(thread->currentStack.start + offset) != 0xFF)
|
|
break;
|
|
sz++;
|
|
}
|
|
} else {
|
|
// Probably should do something here.
|
|
}
|
|
return hleLogDebug(Log::sceKernel, sz & ~3);
|
|
}
|
|
|
|
// What a thread ending costs, besides the switch away from it.
|
|
static const int THREAD_EXIT_CYCLES = 3300;
|
|
|
|
void __KernelReturnFromThread()
|
|
{
|
|
hleSkipDeadbeef();
|
|
|
|
int exitStatus = currentMIPS->r[MIPS_REG_V0];
|
|
PSPThread *thread = __GetCurrentThread();
|
|
_dbg_assert_msg_(thread != NULL, "Returned from a NULL thread.");
|
|
|
|
DEBUG_LOG(Log::sceKernel, "__KernelReturnFromThread: %d", exitStatus);
|
|
// About 20us from a thread ending to a thread waiting on it running, on hardware.
|
|
hleEatCycles(THREAD_EXIT_CYCLES);
|
|
__KernelStopThread(currentThread, exitStatus, "thread returned");
|
|
|
|
hleReSchedule("thread returned");
|
|
|
|
// TODO: This should trigger ON the thread when it exits.
|
|
__KernelThreadTriggerEvent((thread->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, thread->GetUID(), THREADEVENT_EXIT);
|
|
|
|
// The stack will be deallocated when the thread is deleted.
|
|
return hleNoLogVoid();
|
|
}
|
|
|
|
int sceKernelExitThread(int exitStatus) {
|
|
if (!__KernelIsDispatchEnabled() && sceKernelGetCompiledSdkVersion() >= 0x03080000)
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_CAN_NOT_WAIT);
|
|
PSPThread *thread = __GetCurrentThread();
|
|
_dbg_assert_msg_(thread != NULL, "Exited from a NULL thread.");
|
|
|
|
INFO_LOG(Log::sceKernel, "sceKernelExitThread(%d)", exitStatus);
|
|
if (exitStatus < 0) {
|
|
exitStatus = SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
hleEatCycles(THREAD_EXIT_CYCLES);
|
|
__KernelStopThread(currentThread, exitStatus, "thread exited");
|
|
|
|
hleReSchedule("thread exited");
|
|
|
|
// TODO: This should trigger ON the thread when it exits.
|
|
__KernelThreadTriggerEvent((thread->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, thread->GetUID(), THREADEVENT_EXIT);
|
|
|
|
// The stack will be deallocated when the thread is deleted.
|
|
return hleNoLog(0);
|
|
}
|
|
|
|
void _sceKernelExitThread(int exitStatus) {
|
|
PSPThread *thread = __GetCurrentThread();
|
|
_dbg_assert_msg_(thread != NULL, "_Exited from a NULL thread.");
|
|
|
|
ERROR_LOG_REPORT(Log::sceKernel, "_sceKernelExitThread(%d): should not be called directly", exitStatus);
|
|
__KernelStopThread(currentThread, exitStatus, "thread _exited");
|
|
|
|
hleReSchedule("thread _exited");
|
|
|
|
// TODO: This should trigger ON the thread when it exits.
|
|
__KernelThreadTriggerEvent((thread->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, thread->GetUID(), THREADEVENT_EXIT);
|
|
|
|
// The stack will be deallocated when the thread is deleted.
|
|
hleNoLogVoid();
|
|
}
|
|
|
|
int sceKernelExitDeleteThread(int exitStatus) {
|
|
if (!__KernelIsDispatchEnabled() && sceKernelGetCompiledSdkVersion() >= 0x03080000)
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_CAN_NOT_WAIT);
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (thread) {
|
|
INFO_LOG(Log::sceKernel,"sceKernelExitDeleteThread(%d)", exitStatus);
|
|
uint32_t thread_attr = thread->nt.attr;
|
|
uint32_t uid = thread->GetUID();
|
|
hleEatCycles(THREAD_EXIT_CYCLES);
|
|
__KernelDeleteThread(currentThread, exitStatus, "thread exited with delete");
|
|
|
|
hleReSchedule("thread exited with delete");
|
|
|
|
// TODO: This should trigger ON the thread when it exits.
|
|
__KernelThreadTriggerEvent((thread_attr & PSP_THREAD_ATTR_KERNEL) != 0, uid, THREADEVENT_EXIT);
|
|
} else {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "sceKernelExitDeleteThread(%d) ERROR - could not find myself!", exitStatus);
|
|
}
|
|
return hleNoLog(0);
|
|
}
|
|
|
|
u32 sceKernelSuspendDispatchThread() {
|
|
if (!__InterruptsEnabled()) {
|
|
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_CPUDI, "interrupts disabled");
|
|
}
|
|
|
|
u32 oldDispatchEnabled = dispatchEnabled;
|
|
dispatchEnabled = false;
|
|
hleEatCycles(940);
|
|
return hleLogDebug(Log::sceKernel, oldDispatchEnabled);
|
|
}
|
|
|
|
u32 sceKernelResumeDispatchThread(u32 enabled) {
|
|
if (!__InterruptsEnabled()) {
|
|
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_CPUDI, "interrupts disabled");
|
|
}
|
|
|
|
u32 oldDispatchEnabled = dispatchEnabled;
|
|
dispatchEnabled = enabled != 0;
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelResumeDispatchThread(%i) - from %i", enabled, oldDispatchEnabled);
|
|
hleReSchedule("dispatch resumed");
|
|
hleEatCycles(940);
|
|
return hleNoLog(0);
|
|
}
|
|
|
|
bool __KernelIsDispatchEnabled() {
|
|
// Dispatch can never be enabled when interrupts are disabled.
|
|
return dispatchEnabled && __InterruptsEnabled();
|
|
}
|
|
|
|
// PPSSPP doesn't track a real per-module "user level" (set via the owning module's
|
|
// PSP_MODULE_INFO attribute on real firmware, e.g. 0 for kernel-mode modules like the VSH's
|
|
// bridge driver, 4 for a regular user-mode game). Approximate it from the calling thread's
|
|
// kernel/user attr instead - the two levels VSH code actually seems to check for are "kernel"
|
|
// vs "not kernel", so this is enough to avoid it treating itself as an ordinary user thread.
|
|
int sceKernelGetUserLevel() {
|
|
PSPThread *t = __GetCurrentThread();
|
|
int level = (t && (t->nt.attr & PSP_THREAD_ATTR_KERNEL)) ? 0 : 4;
|
|
return hleLogDebug(Log::sceKernel, level);
|
|
}
|
|
|
|
int sceKernelIsUserModeThread() {
|
|
PSPThread *t = __GetCurrentThread();
|
|
int isUser = (t && (t->nt.attr & PSP_THREAD_ATTR_KERNEL)) ? 0 : 1;
|
|
return hleLogDebug(Log::sceKernel, isUser);
|
|
}
|
|
|
|
int KernelRotateThreadReadyQueue(int priority) {
|
|
PSPThread *cur = __GetCurrentThread();
|
|
|
|
// 0 is special, it means "my current priority."
|
|
if (priority == 0)
|
|
priority = cur->nt.currentPriority;
|
|
|
|
if (priority <= 0x07 || priority > 0x77)
|
|
return SCE_KERNEL_ERROR_ILLEGAL_PRIORITY;
|
|
|
|
if (!threadReadyQueue.empty(priority)) {
|
|
// In other words, yield to everyone else.
|
|
if (cur->nt.currentPriority == priority) {
|
|
threadReadyQueue.push_back(priority, currentThread);
|
|
cur->nt.status = (cur->nt.status & ~THREADSTATUS_RUNNING) | THREADSTATUS_READY;
|
|
}
|
|
// Yield the next thread of this priority to all other threads of same priority.
|
|
else
|
|
threadReadyQueue.rotate(priority);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sceKernelRotateThreadReadyQueue(int priority) {
|
|
int result = KernelRotateThreadReadyQueue(priority);
|
|
if (result == 0) {
|
|
hleReSchedule("rotatethreadreadyqueue");
|
|
hleEatCycles(250);
|
|
}
|
|
return hleLogVerbose(Log::sceKernel, result);
|
|
}
|
|
|
|
int sceKernelDeleteThread(int threadID) {
|
|
if (threadID == 0 || threadID == currentThread) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_NOT_DORMANT, "cannot delete current thread");
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return hleLogError(Log::sceKernel, error, "bad thread id");
|
|
} else {
|
|
if (!t->isStopped()) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_NOT_DORMANT);
|
|
}
|
|
|
|
// 50-100us whatever the stack size, per tests/threads/scheduling/costs.
|
|
hleEatCycles(15000);
|
|
return hleLogDebug(Log::sceKernel, __KernelDeleteThread(threadID, SCE_KERNEL_ERROR_THREAD_TERMINATED, "thread deleted"));
|
|
}
|
|
}
|
|
|
|
int sceKernelTerminateDeleteThread(int threadID) {
|
|
if (threadID == 0 || threadID == currentThread) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_THID, "cannot terminate current thread");
|
|
}
|
|
if (!__KernelIsDispatchEnabled() && sceKernelGetCompiledSdkVersion() >= 0x03080000) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_CAN_NOT_WAIT);
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return hleLogError(Log::sceKernel, error, "bad thread id");
|
|
} else {
|
|
bool wasStopped = t->isStopped();
|
|
uint32_t attr = t->nt.attr;
|
|
uint32_t uid = t->GetUID();
|
|
|
|
INFO_LOG(Log::sceKernel, "sceKernelTerminateDeleteThread(%i)", threadID);
|
|
error = __KernelDeleteThread(threadID, SCE_KERNEL_ERROR_THREAD_TERMINATED, "thread terminated with delete");
|
|
|
|
if (!wasStopped) {
|
|
// Set v0 before calling the handler, or it'll get lost.
|
|
RETURN(error);
|
|
__KernelThreadTriggerEvent((attr & PSP_THREAD_ATTR_KERNEL) != 0, uid, THREADEVENT_EXIT);
|
|
}
|
|
|
|
return hleNoLog(error);
|
|
}
|
|
}
|
|
|
|
int sceKernelTerminateThread(SceUID threadID) {
|
|
if (__IsInInterrupt() && sceKernelGetCompiledSdkVersion() >= 0x03080000) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "in interrupt");
|
|
}
|
|
if (!__KernelIsDispatchEnabled() && sceKernelGetCompiledSdkVersion() >= 0x03080000)
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_CAN_NOT_WAIT);
|
|
if (threadID == 0 || threadID == currentThread) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_THID, "cannot terminate current thread");
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t) {
|
|
if (t->isStopped()) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_DORMANT, "already stopped");
|
|
}
|
|
|
|
// TODO: Should this reschedule? Seems like not.
|
|
__KernelStopThread(threadID, SCE_KERNEL_ERROR_THREAD_TERMINATED, "thread terminated");
|
|
|
|
// On terminate, we reset the thread priority. On exit, we don't always (see __KernelResetThread.)
|
|
t->nt.currentPriority = t->nt.initialPriority;
|
|
|
|
// Need to set v0 since it'll be restored.
|
|
RETURN(0);
|
|
__KernelThreadTriggerEvent((t->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, t->GetUID(), THREADEVENT_EXIT);
|
|
|
|
return hleLogInfo(Log::sceKernel, 0);
|
|
} else {
|
|
return hleLogError(Log::sceKernel, error, "thread doesn't exist");
|
|
}
|
|
}
|
|
|
|
SceUID __KernelGetCurThread()
|
|
{
|
|
return currentThread;
|
|
}
|
|
|
|
int KernelCurThreadPriority() {
|
|
PSPThread *t = __GetCurrentThread();
|
|
if (t)
|
|
return t->nt.currentPriority;
|
|
return 0;
|
|
}
|
|
|
|
SceUID __KernelGetCurThreadModuleId() {
|
|
PSPThread *t = __GetCurrentThread();
|
|
if (t)
|
|
return t->moduleId;
|
|
return 0;
|
|
}
|
|
|
|
bool __KernelCurThreadIsKernelMode() {
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (thread && (thread->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0) {
|
|
return true;
|
|
}
|
|
// A kernel module's own main thread isn't necessarily flagged kernel - the attribute comes
|
|
// from PSP_MAIN_THREAD_ATTR in the module info, which needn't set it - so fall back to what
|
|
// module the thread belongs to, the same way sceKernelCreateThread decides allowKernel.
|
|
return KernelModuleIsKernelMode(__KernelGetCurThreadModuleId());
|
|
}
|
|
|
|
u32 __KernelGetCurThreadStack() {
|
|
PSPThread *t = __GetCurrentThread();
|
|
if (t)
|
|
return t->currentStack.end;
|
|
return 0;
|
|
}
|
|
|
|
u32 __KernelGetCurThreadStackStart() {
|
|
PSPThread *t = __GetCurrentThread();
|
|
if (t)
|
|
return t->currentStack.start;
|
|
return 0;
|
|
}
|
|
|
|
SceUID sceKernelGetThreadId() {
|
|
hleEatCycles(180);
|
|
return hleLogVerbose(Log::sceKernel, currentThread);
|
|
}
|
|
|
|
int sceKernelGetThreadCurrentPriority() {
|
|
u32 retVal = __GetCurrentThread()->nt.currentPriority;
|
|
return hleLogDebug(Log::sceKernel, retVal);
|
|
}
|
|
|
|
int sceKernelChangeCurrentThreadAttr(u32 clearAttr, u32 setAttr) {
|
|
// Seems like this is the only allowed attribute?
|
|
if ((clearAttr & ~PSP_THREAD_ATTR_VFPU) != 0 || (setAttr & ~PSP_THREAD_ATTR_VFPU) != 0) {
|
|
return hleReportError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr");
|
|
}
|
|
|
|
PSPThread *t = __GetCurrentThread();
|
|
if (!t)
|
|
return hleReportError(Log::sceKernel, -1, "no current thread");
|
|
|
|
t->nt.attr = (t->nt.attr & ~clearAttr) | setAttr;
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
// Assumes validated parameters.
|
|
bool KernelChangeThreadPriority(SceUID threadID, int priority) {
|
|
u32 error;
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (thread) {
|
|
int old = thread->nt.currentPriority;
|
|
threadReadyQueue.remove(old, threadID);
|
|
|
|
thread->nt.currentPriority = priority;
|
|
threadReadyQueue.prepare(thread->nt.currentPriority);
|
|
if (thread->isRunning()) {
|
|
thread->nt.status = (thread->nt.status & ~THREADSTATUS_RUNNING) | THREADSTATUS_READY;
|
|
}
|
|
if (thread->isReady()) {
|
|
threadReadyQueue.push_back(thread->nt.currentPriority, threadID);
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int sceKernelChangeThreadPriority(SceUID threadID, int priority) {
|
|
if (threadID == 0) {
|
|
threadID = __KernelGetCurThread();
|
|
}
|
|
|
|
// 0 means the current (running) thread's priority, not target's.
|
|
if (priority == 0) {
|
|
PSPThread *cur = __GetCurrentThread();
|
|
if (!cur) {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "sceKernelChangeThreadPriority(%i, %i): no current thread?", threadID, priority);
|
|
} else {
|
|
priority = cur->nt.currentPriority;
|
|
}
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (thread) {
|
|
if (thread->isStopped()) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_DORMANT, "thread is dormant");
|
|
}
|
|
|
|
if (priority < 0x08 || priority > 0x77) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_PRIORITY, "bogus priority");
|
|
}
|
|
|
|
KernelChangeThreadPriority(threadID, priority);
|
|
|
|
hleEatCycles(450);
|
|
hleReSchedule("change thread priority");
|
|
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
} else {
|
|
return hleLogError(Log::sceKernel, error, "thread not found");
|
|
}
|
|
}
|
|
|
|
static s64 __KernelDelayThreadUs(u64 usec) {
|
|
if (usec < 200) {
|
|
return 210;
|
|
}
|
|
|
|
if (usec > 0x8000000000000000ULL) {
|
|
// Wrap around (behavior seen on firmware) and potentially wake up soon.
|
|
usec -= 0x8000000000000000ULL;
|
|
}
|
|
if (usec > 0x0010000000000000ULL) {
|
|
// This will probably overflow when we convert to cycles.
|
|
// Note: converting millenia to hundreds of years. Should be safe, basically perma-delay.
|
|
usec >>= 12;
|
|
}
|
|
|
|
// It never wakes up right away. It usually takes at least 15 extra us, but let's be nicer.
|
|
return usec + 10;
|
|
}
|
|
|
|
// A delay's deadline is now + usec, and the clock is read again when the alarm is set. If the
|
|
// deadline has passed by then, the call returns 0 at once without giving up the CPU. The two reads
|
|
// are about 0.6us apart, so a delay of 0 returns at once about 60% of the time, and 1 almost never.
|
|
// On a thread's first wait after it starts, when the code is presumably out of the cache, they're
|
|
// about 1.65us apart: a delay of 1 returns at once about two times in three, and 2 never
|
|
// (pspautotests threads/scheduling/delayzero).
|
|
static bool __KernelDelayReturnsAtOnce(u32 usec) {
|
|
const PSPThread *thread = __GetCurrentThread();
|
|
const int gapPercent = thread && !thread->hasWaited ? 165 : 60;
|
|
return __KernelChance(gapPercent - (int)std::min(usec, 2U) * 100);
|
|
}
|
|
|
|
int sceKernelDelayThreadCB(u32 usec) {
|
|
hleEatCycles(2000);
|
|
if (__KernelDelayReturnsAtOnce(usec) && !__KernelCurHasReadyCallbacks()) {
|
|
return hleLogDebug(Log::sceKernel, 0, "deadline already passed");
|
|
}
|
|
SceUID curThread = __KernelGetCurThread();
|
|
s64 delayUs = __KernelDelayThreadUs(usec);
|
|
__KernelScheduleWakeup(curThread, delayUs);
|
|
__KernelWaitCurThread(WAITTYPE_DELAY, curThread, 0, 0, true, "thread delayed");
|
|
return hleLogDebug(Log::sceKernel, 0, "delaying %lld usecs", delayUs);
|
|
}
|
|
|
|
int sceKernelDelayThread(u32 usec) {
|
|
hleEatCycles(2000);
|
|
if (__KernelDelayReturnsAtOnce(usec)) {
|
|
return hleLogDebug(Log::sceKernel, 0, "deadline already passed");
|
|
}
|
|
SceUID curThread = __KernelGetCurThread();
|
|
s64 delayUs = __KernelDelayThreadUs(usec);
|
|
__KernelScheduleWakeup(curThread, delayUs);
|
|
__KernelWaitCurThread(WAITTYPE_DELAY, curThread, 0, 0, false, "thread delayed");
|
|
return hleLogDebug(Log::sceKernel, 0, "delaying %lld usecs", delayUs);
|
|
}
|
|
|
|
int sceKernelDelaySysClockThreadCB(u32 sysclockAddr) {
|
|
auto sysclock = PSPPointer<SceKernelSysClock>::Create(sysclockAddr);
|
|
if (!sysclock.IsValid()) {
|
|
// Note: crashes on real firmware.
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad pointer");
|
|
}
|
|
|
|
// This is just a u64 of usecs. All bits are respected, but overflow can happen for very large values.
|
|
u64 usec = sysclock->lo | ((u64)sysclock->hi << 32);
|
|
|
|
SceUID curThread = __KernelGetCurThread();
|
|
s64 delayUs = __KernelDelayThreadUs(usec);
|
|
__KernelScheduleWakeup(curThread, delayUs);
|
|
__KernelWaitCurThread(WAITTYPE_DELAY, curThread, 0, 0, true, "thread delayed");
|
|
return hleLogDebug(Log::sceKernel, 0, "delaying %lld usecs", delayUs);
|
|
}
|
|
|
|
int sceKernelDelaySysClockThread(u32 sysclockAddr) {
|
|
auto sysclock = PSPPointer<SceKernelSysClock>::Create(sysclockAddr);
|
|
if (!sysclock.IsValid()) {
|
|
// Note: crashes on real firmware.
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad pointer");
|
|
}
|
|
|
|
// This is just a u64 of usecs. All bits are respected, but overflow can happen for very large values.
|
|
u64 usec = sysclock->lo | ((u64)sysclock->hi << 32);
|
|
|
|
SceUID curThread = __KernelGetCurThread();
|
|
s64 delayUs = __KernelDelayThreadUs(usec);
|
|
__KernelScheduleWakeup(curThread, delayUs);
|
|
__KernelWaitCurThread(WAITTYPE_DELAY, curThread, 0, 0, false, "thread delayed");
|
|
return hleLogDebug(Log::sceKernel, 0, "delaying %lld usecs", delayUs);
|
|
}
|
|
|
|
u32 __KernelGetThreadPrio(SceUID id) {
|
|
u32 error;
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(id, error);
|
|
if (thread)
|
|
return thread->nt.currentPriority;
|
|
return 0;
|
|
}
|
|
|
|
bool __KernelThreadSortPriority(SceUID thread1, SceUID thread2)
|
|
{
|
|
return __KernelGetThreadPrio(thread1) < __KernelGetThreadPrio(thread2);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// WAIT/SLEEP ETC
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int sceKernelWakeupThread(SceUID uid) {
|
|
if (uid == currentThread) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_THID, "unable to wakeup current thread");
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(uid, error);
|
|
if (t) {
|
|
if (!t->isWaitingFor(WAITTYPE_SLEEP, 0)) {
|
|
t->nt.wakeupCount++;
|
|
return hleLogDebug(Log::sceKernel, 0, "wakeupCount incremented to %i", t->nt.wakeupCount);
|
|
} else {
|
|
__KernelResumeThreadFromWait(uid, 0);
|
|
hleReSchedule("thread woken up");
|
|
return hleLogVerbose(Log::sceKernel, 0, "woke thread at %i", t->nt.wakeupCount);
|
|
}
|
|
} else {
|
|
if (uid == (SceUID)-1) {
|
|
// Common error (for example in Power Stone). Let's log at debug level.
|
|
return hleLogDebug(Log::sceKernel, error, "bad thread id");
|
|
}
|
|
return hleLogError(Log::sceKernel, error, "bad thread id");
|
|
}
|
|
}
|
|
|
|
int sceKernelCancelWakeupThread(SceUID uid) {
|
|
if (uid == 0) {
|
|
uid = __KernelGetCurThread();
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(uid, error);
|
|
if (t) {
|
|
int wCount = t->nt.wakeupCount;
|
|
t->nt.wakeupCount = 0;
|
|
return hleLogDebug(Log::sceKernel, wCount, "wakeupCount reset to 0");
|
|
} else {
|
|
return hleLogError(Log::sceKernel, error, "bad thread id");
|
|
}
|
|
}
|
|
|
|
// Logs like a HLE func
|
|
static int __KernelSleepThread(bool doCallbacks) {
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (!thread) {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "sceKernelSleepThread*(): bad current thread");
|
|
return hleNoLog(-1);
|
|
}
|
|
|
|
if (doCallbacks && thread->nt.wakeupCount > 0 && __KernelCurHasReadyCallbacks()) {
|
|
// Pending callbacks run first, then the sleep end callback consumes the wakeup.
|
|
// Log first, the callback starts running right away.
|
|
(void)hleLogDebug(Log::sceKernel, 0, "running callbacks before wakeup");
|
|
__KernelWaitCallbacksCurThread(WAITTYPE_SLEEP, 0, 0, 0);
|
|
return 0;
|
|
} else if (thread->nt.wakeupCount > 0) {
|
|
thread->nt.wakeupCount--;
|
|
return hleLogDebug(Log::sceKernel, 0, "wakeupCount decremented to %i", thread->nt.wakeupCount);
|
|
} else {
|
|
__KernelWaitCurThread(WAITTYPE_SLEEP, 0, 0, 0, doCallbacks, "thread slept");
|
|
return hleLogVerbose(Log::sceKernel, 0, "sleeping");
|
|
}
|
|
return hleNoLog(0);
|
|
}
|
|
|
|
int sceKernelSleepThread() {
|
|
return __KernelSleepThread(false);
|
|
}
|
|
|
|
//the homebrew PollCallbacks
|
|
int sceKernelSleepThreadCB() {
|
|
return __KernelSleepThread(true);
|
|
}
|
|
|
|
int sceKernelWaitThreadEnd(SceUID threadID, u32 timeoutPtr) {
|
|
if (threadID == 0 || threadID == currentThread)
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_THID);
|
|
|
|
if (!__KernelIsDispatchEnabled())
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_CAN_NOT_WAIT);
|
|
if (__IsInInterrupt())
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT);
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return hleLogError(Log::sceKernel, error, "bad thread ID");
|
|
} else {
|
|
if (t->nt.status != THREADSTATUS_DORMANT)
|
|
{
|
|
if (__KernelWaitTimesOutAtOnce(timeoutPtr))
|
|
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once");
|
|
__KernelScheduleWaitTimeout(currentThread, Memory::IsValidAddress(timeoutPtr) ? timeoutPtr : 0);
|
|
if (std::find(t->waitingThreads.begin(), t->waitingThreads.end(), currentThread) == t->waitingThreads.end())
|
|
t->waitingThreads.push_back(currentThread);
|
|
__KernelWaitCurThread(WAITTYPE_THREADEND, threadID, 0, timeoutPtr, false, "thread wait end");
|
|
}
|
|
|
|
return hleLogDebug(Log::sceKernel, t->nt.exitStatus);
|
|
}
|
|
}
|
|
|
|
int sceKernelWaitThreadEndCB(SceUID threadID, u32 timeoutPtr) {
|
|
if (threadID == 0 || threadID == currentThread)
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_THID);
|
|
|
|
if (!__KernelIsDispatchEnabled())
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_CAN_NOT_WAIT);
|
|
if (__IsInInterrupt())
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT);
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return hleLogError(Log::sceKernel, error, "bad thread ID");
|
|
} else {
|
|
if (t->nt.status != THREADSTATUS_DORMANT)
|
|
{
|
|
if (__KernelWaitTimesOutAtOnce(timeoutPtr))
|
|
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once");
|
|
__KernelScheduleWaitTimeout(currentThread, Memory::IsValidAddress(timeoutPtr) ? timeoutPtr : 0);
|
|
if (std::find(t->waitingThreads.begin(), t->waitingThreads.end(), currentThread) == t->waitingThreads.end())
|
|
t->waitingThreads.push_back(currentThread);
|
|
__KernelWaitCurThread(WAITTYPE_THREADEND, threadID, 0, timeoutPtr, true, "thread wait end");
|
|
}
|
|
else
|
|
hleCheckCurrentCallbacks();
|
|
|
|
return hleLogDebug(Log::sceKernel, t->nt.exitStatus);
|
|
}
|
|
}
|
|
|
|
int sceKernelReleaseWaitThread(SceUID threadID) {
|
|
if (__KernelInCallback())
|
|
WARN_LOG_REPORT(Log::sceKernel, "UNTESTED sceKernelReleaseWaitThread() might not do the right thing in a callback");
|
|
|
|
if (threadID == 0 || threadID == currentThread)
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_THID);
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return hleLogError(Log::sceKernel, error, "bad thread ID");
|
|
} else {
|
|
if (!t->isWaiting()) {
|
|
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_NOT_WAIT);
|
|
}
|
|
if (t->nt.waitType == WAITTYPE_HLEDELAY) {
|
|
WARN_LOG_REPORT_ONCE(rwt_delay, Log::sceKernel, "sceKernelReleaseWaitThread(): Refusing to wake HLE-delayed thread, right thing to do?");
|
|
return hleNoLog(SCE_KERNEL_ERROR_NOT_WAIT);
|
|
}
|
|
if (t->nt.waitType == WAITTYPE_MODULE) {
|
|
WARN_LOG_REPORT_ONCE(rwt_sm, Log::sceKernel, "sceKernelReleaseWaitThread(): Refusing to wake start_module thread, right thing to do?");
|
|
return hleNoLog(SCE_KERNEL_ERROR_NOT_WAIT);
|
|
}
|
|
|
|
__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_RELEASE_WAIT);
|
|
hleReSchedule("thread released from wait");
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
}
|
|
|
|
int sceKernelSuspendThread(SceUID threadID) {
|
|
// TODO: What about interrupts/callbacks?
|
|
if (threadID == 0 || threadID == currentThread) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_THID, "cannot suspend current thread");
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return hleLogError(Log::sceKernel, error, "bad thread ID");
|
|
} else {
|
|
if (t->isStopped()) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_DORMANT, "thread not running");
|
|
}
|
|
if (t->isSuspended()) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_SUSPEND, "thread already suspended");
|
|
}
|
|
|
|
if (t->isReady())
|
|
__KernelChangeReadyState(t, threadID, false);
|
|
t->nt.status = (t->nt.status & ~THREADSTATUS_READY) | THREADSTATUS_SUSPEND;
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
}
|
|
|
|
int sceKernelResumeThread(SceUID threadID) {
|
|
// TODO: What about interrupts/callbacks?
|
|
if (threadID == 0 || threadID == currentThread) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_THID, "cannot suspend current thread");
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (!t) {
|
|
return hleLogError(Log::sceKernel, error, "bad thread ID");
|
|
} else {
|
|
if (!t->isSuspended()) {
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_NOT_SUSPEND, "sceKernelResumeThread(%d): thread not suspended", threadID);
|
|
}
|
|
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelResumeThread(%d)", threadID);
|
|
t->nt.status &= ~THREADSTATUS_SUSPEND;
|
|
|
|
// If it was dormant, waiting, etc. before we don't flip its ready state.
|
|
if (t->nt.status == 0)
|
|
__KernelChangeReadyState(t, threadID, true);
|
|
hleReSchedule("resume thread from suspend");
|
|
return hleNoLog(0);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// CALLBACKS
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
SceUID sceKernelCreateCallback(const char *name, u32 entrypoint, u32 signalArg)
|
|
{
|
|
if (!name)
|
|
return hleReportWarning(Log::sceKernel, SCE_KERNEL_ERROR_ERROR, "invalid name");
|
|
if (entrypoint & 0xF0000000)
|
|
return hleReportWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "invalid func");
|
|
|
|
PSPCallback *cb = new PSPCallback();
|
|
SceUID id = kernelObjects.Create(cb);
|
|
|
|
strncpy(cb->nc.name, name, KERNELOBJECT_MAX_NAME_LENGTH);
|
|
cb->nc.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0;
|
|
cb->nc.size = sizeof(NativeCallback);
|
|
cb->nc.entrypoint = entrypoint;
|
|
cb->nc.threadId = __KernelGetCurThread();
|
|
cb->nc.commonArgument = signalArg;
|
|
cb->nc.notifyCount = 0;
|
|
cb->nc.notifyArg = 0;
|
|
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (thread)
|
|
thread->callbacks.push_back(id);
|
|
|
|
return hleLogInfo(Log::sceKernel, id);
|
|
}
|
|
|
|
int sceKernelDeleteCallback(SceUID cbId) {
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (cb)
|
|
{
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(cb->nc.threadId, error);
|
|
if (thread)
|
|
thread->callbacks.erase(std::remove(thread->callbacks.begin(), thread->callbacks.end(), cbId), thread->callbacks.end());
|
|
if (cb->nc.notifyCount != 0)
|
|
readyCallbacksCount--;
|
|
|
|
return hleLogDebug(Log::sceKernel, kernelObjects.Destroy<PSPCallback>(cbId));
|
|
} else {
|
|
return hleLogError(Log::sceKernel, error, "bad cbId");
|
|
}
|
|
}
|
|
|
|
// Generally very rarely used, but Numblast uses it like candy.
|
|
int sceKernelNotifyCallback(SceUID cbId, int notifyArg) {
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (cb) {
|
|
__KernelNotifyCallback(cbId, notifyArg);
|
|
// A better priority thread in a CB wait runs the callback right away.
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(cb->nc.threadId, error);
|
|
if (t && t->GetUID() != currentThread && t->isWaiting() && t->isProcessingCallbacks) {
|
|
hleReSchedule("callback notified");
|
|
}
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
} else {
|
|
return hleLogError(Log::sceKernel, error, "bad cbId");
|
|
}
|
|
}
|
|
|
|
int sceKernelCancelCallback(SceUID cbId) {
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (cb) {
|
|
// This just resets the notify count.
|
|
if (cb->nc.notifyCount != 0) {
|
|
readyCallbacksCount--;
|
|
}
|
|
cb->nc.notifyCount = 0;
|
|
cb->nc.notifyArg = 0;
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
} else {
|
|
return hleLogError(Log::sceKernel, error, "bad cbId");
|
|
}
|
|
}
|
|
|
|
int sceKernelGetCallbackCount(SceUID cbId) {
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (cb) {
|
|
return hleLogVerbose(Log::sceKernel, cb->nc.notifyCount);
|
|
} else {
|
|
return hleLogError(Log::sceKernel, error, "bad cbId");
|
|
}
|
|
}
|
|
|
|
int sceKernelReferCallbackStatus(SceUID cbId, u32 statusAddr) {
|
|
u32 error;
|
|
PSPCallback *c = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (c) {
|
|
auto status = PSPPointer<NativeCallback>::Create(statusAddr);
|
|
if (status.IsValid() && status->size != 0) {
|
|
*status = c->nc;
|
|
status.NotifyWrite("CallbackStatus");
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
} else {
|
|
return hleLogDebug(Log::sceKernel, 0, "struct size was 0");
|
|
}
|
|
} else {
|
|
return hleLogError(Log::sceKernel, error, "bad cbId");
|
|
}
|
|
}
|
|
|
|
u32 sceKernelExtendThreadStack(u32 size, u32 entryAddr, u32 entryParameter) {
|
|
if (size < 512) {
|
|
return hleReportError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_STACK_SIZE, "stack size too small");
|
|
}
|
|
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (!thread)
|
|
return hleReportError(Log::sceKernel, -1, "not on a thread?");
|
|
|
|
if (!thread->PushExtendedStack(size))
|
|
return hleReportError(Log::sceKernel, SCE_KERNEL_ERROR_NO_MEMORY, "could not allocate new stack");
|
|
|
|
// The stack has been changed now, so it's do or die time.
|
|
|
|
// Push the old SP, RA, and PC onto the stack (so we can restore them later.)
|
|
Memory::WriteOrException_U32(currentMIPS->r[MIPS_REG_RA], thread->currentStack.end - 4);
|
|
Memory::WriteOrException_U32(currentMIPS->r[MIPS_REG_SP], thread->currentStack.end - 8);
|
|
Memory::WriteOrException_U32(currentMIPS->pc, thread->currentStack.end - 12);
|
|
|
|
KernelValidateThreadTarget(entryAddr);
|
|
|
|
currentMIPS->pc = entryAddr;
|
|
currentMIPS->r[MIPS_REG_A0] = entryParameter;
|
|
currentMIPS->r[MIPS_REG_RA] = extendReturnHackAddr;
|
|
// Stack should stay aligned even though we saved only 3 regs.
|
|
currentMIPS->r[MIPS_REG_SP] = thread->currentStack.end - 0x10;
|
|
|
|
hleSkipDeadbeef();
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
void __KernelReturnFromExtendStack() {
|
|
hleSkipDeadbeef();
|
|
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (!thread) {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "__KernelReturnFromExtendStack() - not on a thread?");
|
|
hleNoLogVoid();
|
|
return;
|
|
}
|
|
|
|
if (!Memory::IsValid4AlignedRange(thread->currentStack.end - 12, 12)) {
|
|
Core_MemoryExceptionHLE(currentMIPS, thread->currentStack.end, 12, MemoryExceptionType::HLE_READ);
|
|
hleNoLogVoid();
|
|
return;
|
|
}
|
|
|
|
// Grab the saved regs at the top of the stack.
|
|
u32 restoreRA = Memory::ReadUnchecked_U32(thread->currentStack.end - 4);
|
|
u32 restoreSP = Memory::ReadUnchecked_U32(thread->currentStack.end - 8);
|
|
u32 restorePC = Memory::ReadUnchecked_U32(thread->currentStack.end - 12);
|
|
|
|
if (!thread->PopExtendedStack()) {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "__KernelReturnFromExtendStack() - no stack to restore?");
|
|
return;
|
|
}
|
|
|
|
KernelValidateThreadTarget(restorePC);
|
|
|
|
DEBUG_LOG(Log::sceKernel, "__KernelReturnFromExtendStack()");
|
|
currentMIPS->r[MIPS_REG_RA] = restoreRA;
|
|
currentMIPS->r[MIPS_REG_SP] = restoreSP;
|
|
currentMIPS->pc = restorePC;
|
|
|
|
// We retain whatever is in v0/v1, it gets passed on to the caller of sceKernelExtendThreadStack().
|
|
hleNoLogVoid();
|
|
}
|
|
|
|
void ActionAfterMipsCall::run(MipsCall &call) {
|
|
u32 error;
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (thread) {
|
|
// Resume waiting after a callback, but not from terminate/delete.
|
|
if ((thread->nt.status & (THREADSTATUS_DEAD | THREADSTATUS_DORMANT)) == 0) {
|
|
__KernelChangeReadyState(thread, threadID, (status & THREADSTATUS_READY) != 0);
|
|
thread->nt.status = status;
|
|
}
|
|
thread->nt.waitType = waitType;
|
|
thread->nt.waitID = waitID;
|
|
thread->waitInfo = waitInfo;
|
|
thread->isProcessingCallbacks = isProcessingCallbacks;
|
|
thread->currentCallbackId = currentCallbackId;
|
|
}
|
|
|
|
if (chainedAction) {
|
|
chainedAction->run(call);
|
|
delete chainedAction;
|
|
}
|
|
}
|
|
|
|
void PSPThread::setReturnValue(u32 retval) {
|
|
if (GetUID() == currentThread) {
|
|
currentMIPS->r[MIPS_REG_V0] = retval;
|
|
} else {
|
|
context.r[MIPS_REG_V0] = retval;
|
|
}
|
|
}
|
|
|
|
void PSPThread::setReturnValue(u64 retval) {
|
|
if (GetUID() == currentThread) {
|
|
currentMIPS->r[MIPS_REG_V0] = retval & 0xFFFFFFFF;
|
|
currentMIPS->r[MIPS_REG_V1] = (retval >> 32) & 0xFFFFFFFF;
|
|
} else {
|
|
context.r[MIPS_REG_V0] = retval & 0xFFFFFFFF;
|
|
context.r[MIPS_REG_V1] = (retval >> 32) & 0xFFFFFFFF;
|
|
}
|
|
}
|
|
|
|
void PSPThread::resumeFromWait() {
|
|
nt.status &= ~THREADSTATUS_WAIT;
|
|
waitPausedForCallback = false;
|
|
if (!(nt.status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD)))
|
|
__KernelChangeReadyState(this, GetUID(), true);
|
|
|
|
// Non-waiting threads do not process callbacks.
|
|
isProcessingCallbacks = false;
|
|
}
|
|
|
|
bool PSPThread::isWaitingFor(WaitType type, int id) const {
|
|
if (nt.status & THREADSTATUS_WAIT)
|
|
return nt.waitType == type && nt.waitID == id;
|
|
return false;
|
|
}
|
|
|
|
int PSPThread::getWaitID(WaitType type) const {
|
|
if (nt.waitType == type)
|
|
return nt.waitID;
|
|
return 0;
|
|
}
|
|
|
|
ThreadWaitInfo PSPThread::getWaitInfo() const {
|
|
return waitInfo;
|
|
}
|
|
|
|
void __KernelSwitchContext(PSPThread *target, const char *reason) {
|
|
u32 oldPC = 0;
|
|
SceUID oldUID = 0;
|
|
const char *oldName = hleCurrentThreadName != NULL ? hleCurrentThreadName : "(none)";
|
|
|
|
// Everything since the previous switch was spent running the outgoing thread, so bill it to
|
|
// that thread's runForClocks before we change over. This is the only field that tells a game
|
|
// how much time a thread has actually had, and it used to always read back as zero: Crazy
|
|
// Taxi: Fare Wars samples its mp3 thread's run time once a second through
|
|
// sceKernelReferThreadStatus, and when it never advanced the game decided playback was wedged
|
|
// and restarted the track, roughly once a second, forever.
|
|
const u64 nowUs = CoreTiming::GetGlobalTimeUs();
|
|
PSPThread *cur = __GetCurrentThread();
|
|
if (cur && nowUs > lastContextSwitchUs) {
|
|
const u64 ranForUs = nowUs - lastContextSwitchUs;
|
|
const u64 total = ((u64)cur->nt.runForClocks.hi << 32 | cur->nt.runForClocks.lo) + ranForUs;
|
|
cur->nt.runForClocks.lo = (u32)total;
|
|
cur->nt.runForClocks.hi = (u32)(total >> 32);
|
|
}
|
|
lastContextSwitchUs = nowUs;
|
|
|
|
if (cur) // It might just have been deleted.
|
|
{
|
|
__KernelSaveContext(&cur->context, (cur->nt.attr & PSP_THREAD_ATTR_VFPU) != 0);
|
|
oldPC = currentMIPS->pc;
|
|
oldUID = cur->GetUID();
|
|
|
|
// Normally this is taken care of in __KernelNextThread().
|
|
if (cur->isRunning())
|
|
__KernelChangeReadyState(cur, oldUID, true);
|
|
}
|
|
|
|
if (target) {
|
|
__SetCurrentThread(target, target->GetUID(), target->nt.name);
|
|
__KernelChangeReadyState(target, currentThread, false);
|
|
target->nt.status = (target->nt.status | THREADSTATUS_RUNNING) & ~THREADSTATUS_READY;
|
|
|
|
__KernelLoadContext(&target->context, (target->nt.attr & PSP_THREAD_ATTR_VFPU) != 0);
|
|
} else {
|
|
__SetCurrentThread(NULL, 0, NULL);
|
|
}
|
|
|
|
const bool fromIdle = oldUID == threadIdleID[0] || oldUID == threadIdleID[1];
|
|
const bool toIdle = currentThread == threadIdleID[0] || currentThread == threadIdleID[1];
|
|
#if DEBUG_LEVEL <= MAX_LOGLEVEL || DEBUG_LOG == NOTICE_LOG
|
|
if (!(fromIdle && toIdle))
|
|
{
|
|
u64 nowCycles = CoreTiming::GetTicks(currentMIPS);
|
|
s64 consumedCycles = nowCycles - lastSwitchCycles;
|
|
lastSwitchCycles = nowCycles;
|
|
|
|
DEBUG_LOG(Log::sceKernel, "Context switch: %s -> %s (%i->%i, pc: %08x->%08x, %s) +%lldus",
|
|
oldName, hleCurrentThreadName,
|
|
oldUID, currentThread,
|
|
oldPC, currentMIPS->pc,
|
|
reason,
|
|
cyclesToUs(consumedCycles));
|
|
}
|
|
#endif
|
|
|
|
// Switching threads eats some cycles. Between two threads it's about 5us: rotating the ready
|
|
// queue to an equal thread, or waking a better one and having it block again, each take
|
|
// 7-10us on hardware including the calls themselves (pspautotests threads/scheduling/handoff).
|
|
if (fromIdle && toIdle) {
|
|
// Don't eat any cycles going between idle.
|
|
} else if (fromIdle || toIdle) {
|
|
currentMIPS->downcount -= 1200;
|
|
} else {
|
|
currentMIPS->downcount -= 1150;
|
|
}
|
|
|
|
__KernelUpdateBusySyscalls(target);
|
|
|
|
if (target)
|
|
{
|
|
// No longer waiting.
|
|
target->nt.waitType = WAITTYPE_NONE;
|
|
target->nt.waitID = 0;
|
|
|
|
__KernelExecutePendingMipsCalls(target, true);
|
|
}
|
|
}
|
|
|
|
void __KernelChangeThreadState(PSPThread *thread, ThreadStatus newStatus) {
|
|
if (!thread || thread->nt.status == newStatus)
|
|
return;
|
|
|
|
if (!dispatchEnabled && thread == __GetCurrentThread() && newStatus != THREADSTATUS_RUNNING) {
|
|
ERROR_LOG(Log::sceKernel, "Dispatching suspended, not changing thread state");
|
|
return;
|
|
}
|
|
|
|
// TODO: JPSCP has many conditions here, like removing wait timeout actions etc.
|
|
// if (thread->nt.status == THREADSTATUS_WAIT && newStatus != THREADSTATUS_WAITSUSPEND) {
|
|
|
|
__KernelChangeReadyState(thread, thread->GetUID(), (newStatus & THREADSTATUS_READY) != 0);
|
|
thread->nt.status = newStatus;
|
|
|
|
if (newStatus == THREADSTATUS_WAIT) {
|
|
if (thread->nt.waitType == WAITTYPE_NONE) {
|
|
ERROR_LOG(Log::sceKernel, "Waittype none not allowed here");
|
|
}
|
|
|
|
// Schedule deletion of stopped threads here. if (thread->isStopped())
|
|
}
|
|
}
|
|
|
|
const char *ThreadStatusToString(ThreadStatus status) {
|
|
switch (status) {
|
|
case THREADSTATUS_RUNNING: return "Running";
|
|
case THREADSTATUS_READY: return "Ready";
|
|
case THREADSTATUS_WAIT: return "Wait";
|
|
case THREADSTATUS_SUSPEND: return "Suspended";
|
|
case THREADSTATUS_DORMANT: return "Dormant";
|
|
case THREADSTATUS_DEAD: return "Dead";
|
|
case THREADSTATUS_WAITSUSPEND: return "WaitSuspended";
|
|
default:
|
|
break;
|
|
}
|
|
return "(unk)";
|
|
}
|
|
|
|
// Set while a returning mipscall's after-action runs, which may queue the next callback.
|
|
static bool g_inMipsCallReturn = false;
|
|
|
|
// How many callbacks the thread is running, nested in each other (on hardware, at most two.)
|
|
static int __KernelCallbackDepth(const PSPThread *thread) {
|
|
int depth = 0;
|
|
u32 id = thread ? thread->currentMipscallId : 0;
|
|
while (id != 0 && id != (u32)-1) {
|
|
MipsCall *call = mipsCalls.get(id);
|
|
if (!call)
|
|
break;
|
|
if (call->cbId > 0)
|
|
depth++;
|
|
id = call->savedId;
|
|
}
|
|
return depth;
|
|
}
|
|
|
|
static bool __KernelIsInCallbackOnThread(const PSPThread *thread) {
|
|
return __KernelCallbackDepth(thread) > 0;
|
|
}
|
|
|
|
static bool __CanExecuteCallbackNow(PSPThread *thread) {
|
|
if (g_inMipsCallReturn)
|
|
return false;
|
|
if (!thread)
|
|
thread = __GetCurrentThread();
|
|
if (!thread)
|
|
return true;
|
|
int depth = __KernelCallbackDepth(thread);
|
|
// Not in the middle of some other mipscall.
|
|
if (depth == 0)
|
|
return thread->currentMipscallId == 0 || thread->currentMipscallId == (u32)-1;
|
|
// A thread inside a callback runs its own pending callbacks nested, from a CB wait, but only
|
|
// one level deep: going deeper hangs the thread on hardware.
|
|
return depth == 1;
|
|
}
|
|
|
|
// Takes ownership of afterAction.
|
|
void __KernelCallAddress(PSPThread *thread, u32 entryPoint, PSPAction *afterAction, const u32 args[], int numargs, bool reschedAfter, SceUID cbId) {
|
|
if (!thread || thread->isStopped()) {
|
|
WARN_LOG_REPORT(Log::sceKernel, "Running mipscall on dormant thread");
|
|
}
|
|
|
|
_dbg_assert_msg_(numargs <= 6, "MipsCalls can only take 6 args.");
|
|
|
|
if (thread) {
|
|
ActionAfterMipsCall *after = (ActionAfterMipsCall *) __KernelCreateAction(actionAfterMipsCall);
|
|
after->chainedAction = afterAction;
|
|
after->threadID = thread->GetUID();
|
|
after->status = thread->nt.status;
|
|
after->waitType = (WaitType)(u32)thread->nt.waitType;
|
|
after->waitID = thread->nt.waitID;
|
|
after->waitInfo = thread->waitInfo;
|
|
after->isProcessingCallbacks = thread->isProcessingCallbacks;
|
|
after->currentCallbackId = thread->currentCallbackId;
|
|
|
|
afterAction = after;
|
|
|
|
if (thread->nt.waitType != WAITTYPE_NONE) {
|
|
// If it's a callback, tell the wait to stop. A thread that already returned from its
|
|
// wait keeps a stale waitType until it's switched out, so check that it's waiting.
|
|
if (cbId > 0 && (thread->nt.status & THREADSTATUS_WAIT) != 0 && thread->waitPausedForCallback) {
|
|
// Already paused when the callback was notified.
|
|
thread->waitPausedForCallback = false;
|
|
} else if (cbId > 0 && (thread->nt.status & THREADSTATUS_WAIT) != 0) {
|
|
if (waitTypeFuncs[thread->nt.waitType].beginFunc != NULL) {
|
|
waitTypeFuncs[thread->nt.waitType].beginFunc(after->threadID, thread->currentCallbackId);
|
|
} else {
|
|
ERROR_LOG_REPORT(Log::HLE, "Missing begin/restore funcs for wait type %d", thread->nt.waitType);
|
|
}
|
|
}
|
|
|
|
// Release thread from waiting
|
|
thread->nt.waitType = WAITTYPE_NONE;
|
|
}
|
|
|
|
if (cbId > 0) {
|
|
// A wait paused by a callback nested in this one is keyed by this callback's id.
|
|
thread->currentCallbackId = cbId;
|
|
// Only a CB wait inside the callback processes callbacks, not the one it interrupted.
|
|
thread->isProcessingCallbacks = false;
|
|
}
|
|
|
|
__KernelChangeThreadState(thread, THREADSTATUS_READY);
|
|
}
|
|
|
|
MipsCall *call = new MipsCall();
|
|
call->entryPoint = entryPoint;
|
|
for (int i = 0; i < numargs; i++) {
|
|
call->args[i] = args[i];
|
|
}
|
|
call->numArgs = (int) numargs;
|
|
call->doAfter = afterAction;
|
|
call->tag = "callAddress";
|
|
call->cbId = cbId;
|
|
|
|
u32 callId = mipsCalls.add(call);
|
|
|
|
bool called = false;
|
|
if ((!thread || thread == __GetCurrentThread())) {
|
|
if (__CanExecuteCallbackNow(thread)) {
|
|
thread = __GetCurrentThread();
|
|
__KernelChangeThreadState(thread, THREADSTATUS_RUNNING);
|
|
called = __KernelExecuteMipsCallOnCurrentThread(callId, reschedAfter);
|
|
}
|
|
}
|
|
|
|
if (!called) {
|
|
if (thread) {
|
|
DEBUG_LOG(Log::sceKernel, "Making mipscall pending on thread");
|
|
thread->pendingMipsCalls.push_back(callId);
|
|
} else {
|
|
WARN_LOG(Log::sceKernel, "Ignoring mispcall on NULL/deleted thread");
|
|
}
|
|
}
|
|
}
|
|
|
|
void __KernelDirectMipsCall(u32 entryPoint, PSPAction *afterAction, u32 args[], int numargs, bool reschedAfter) {
|
|
__KernelCallAddress(__GetCurrentThread(), entryPoint, afterAction, args, numargs, reschedAfter, 0);
|
|
}
|
|
|
|
bool __KernelExecuteMipsCallOnCurrentThread(u32 callId, bool reschedAfter)
|
|
{
|
|
hleSkipDeadbeef();
|
|
|
|
PSPThread *cur = __GetCurrentThread();
|
|
if (cur == nullptr) {
|
|
ERROR_LOG(Log::sceKernel, "__KernelExecuteMipsCallOnCurrentThread(): Bad current thread");
|
|
return false;
|
|
}
|
|
|
|
if (__KernelCallbackDepth(cur) > 1) {
|
|
WARN_LOG_REPORT(Log::sceKernel, "__KernelExecuteMipsCallOnCurrentThread(): Already two callbacks deep!");
|
|
}
|
|
DEBUG_LOG(Log::sceKernel, "Executing mipscall %i", callId);
|
|
MipsCall *call = mipsCalls.get(callId);
|
|
|
|
// Grab some MIPS stack space.
|
|
u32 &sp = currentMIPS->r[MIPS_REG_SP];
|
|
if (!Memory::IsValidAddress(sp - 32 * 4)) {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "__KernelExecuteMipsCallOnCurrentThread(): Not enough free stack");
|
|
return false;
|
|
}
|
|
|
|
// Let's just save regs generously. Better to be safe.
|
|
sp -= 32 * 4;
|
|
for (int i = MIPS_REG_A0; i <= MIPS_REG_T7; ++i) {
|
|
Memory::WriteOrException_U32(currentMIPS->r[i], sp + i * 4);
|
|
}
|
|
Memory::WriteOrException_U32(currentMIPS->r[MIPS_REG_T8], sp + MIPS_REG_T8 * 4);
|
|
Memory::WriteOrException_U32(currentMIPS->r[MIPS_REG_T9], sp + MIPS_REG_T9 * 4);
|
|
Memory::WriteOrException_U32(currentMIPS->r[MIPS_REG_RA], sp + MIPS_REG_RA * 4);
|
|
|
|
// Save the few regs that need saving
|
|
call->savedPc = currentMIPS->pc;
|
|
call->savedV0 = currentMIPS->r[MIPS_REG_V0];
|
|
call->savedV1 = currentMIPS->r[MIPS_REG_V1];
|
|
call->savedId = cur->currentMipscallId;
|
|
call->reschedAfter = reschedAfter;
|
|
|
|
KernelValidateThreadTarget(call->entryPoint);
|
|
|
|
// Set up the new state
|
|
currentMIPS->pc = call->entryPoint;
|
|
currentMIPS->r[MIPS_REG_RA] = __KernelCallbackReturnAddress();
|
|
cur->currentMipscallId = callId;
|
|
for (int i = 0; i < call->numArgs; i++) {
|
|
currentMIPS->r[MIPS_REG_A0 + i] = call->args[i];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void __KernelReturnFromMipsCall() {
|
|
hleSkipDeadbeef();
|
|
|
|
PSPThread *cur = __GetCurrentThread();
|
|
if (!cur) {
|
|
ERROR_LOG(Log::sceKernel, "__KernelReturnFromMipsCall(): Bad current thread");
|
|
return hleNoLogVoid();
|
|
}
|
|
|
|
u32 callId = cur->currentMipscallId;
|
|
MipsCall *call = mipsCalls.pop(callId);
|
|
|
|
// Value returned by the callback function
|
|
u32 retVal = currentMIPS->r[MIPS_REG_V0];
|
|
DEBUG_LOG(Log::sceKernel, "__KernelReturnFromMipsCall(), returned %08x", retVal);
|
|
|
|
// TODO: Should also save/restore wait state here?
|
|
if (call->doAfter) {
|
|
g_inMipsCallReturn = true;
|
|
call->doAfter->run(*call);
|
|
g_inMipsCallReturn = false;
|
|
delete call->doAfter;
|
|
call->doAfter = nullptr;
|
|
}
|
|
|
|
u32 sp = currentMIPS->r[MIPS_REG_SP];
|
|
if (!Memory::IsValid4AlignedRange(sp, 32 * 4)) {
|
|
// We're really screwed.
|
|
Core_MemoryExceptionHLE(currentMIPS, sp, 4, MemoryExceptionType::HLE_READ);
|
|
return hleNoLogVoid();
|
|
}
|
|
|
|
for (int i = MIPS_REG_A0; i <= MIPS_REG_T7; ++i) {
|
|
currentMIPS->r[i] = Memory::ReadUnchecked_U32(sp + i * 4);
|
|
}
|
|
currentMIPS->r[MIPS_REG_T8] = Memory::ReadUnchecked_U32(sp + MIPS_REG_T8 * 4);
|
|
currentMIPS->r[MIPS_REG_T9] = Memory::ReadUnchecked_U32(sp + MIPS_REG_T9 * 4);
|
|
currentMIPS->r[MIPS_REG_RA] = Memory::ReadUnchecked_U32(sp + MIPS_REG_RA * 4);
|
|
// Increment SP.
|
|
currentMIPS->r[MIPS_REG_SP] += 32 * 4;
|
|
|
|
KernelValidateThreadTarget(call->savedPc);
|
|
|
|
currentMIPS->pc = call->savedPc;
|
|
// This is how we set the return value.
|
|
currentMIPS->r[MIPS_REG_V0] = call->savedV0;
|
|
currentMIPS->r[MIPS_REG_V1] = call->savedV1;
|
|
cur->currentMipscallId = call->savedId;
|
|
|
|
if (cur->nt.waitType != WAITTYPE_NONE && (cur->nt.status & THREADSTATUS_WAIT) != 0 && call->cbId > 0) {
|
|
if (waitTypeFuncs[cur->nt.waitType].endFunc != NULL)
|
|
waitTypeFuncs[cur->nt.waitType].endFunc(cur->GetUID(), cur->currentCallbackId);
|
|
else
|
|
ERROR_LOG_REPORT(Log::HLE, "Missing begin/restore funcs for wait type %d", cur->nt.waitType);
|
|
}
|
|
|
|
// If the wait ended (or there was none), the thread never gave up the CPU, so it shouldn't
|
|
// queue behind threads of the same priority.
|
|
if (cur->isReady()) {
|
|
__KernelChangeThreadState(cur, THREADSTATUS_RUNNING);
|
|
}
|
|
|
|
// yeah! back in the real world, let's keep going. Should we process more callbacks?
|
|
if (!__KernelExecutePendingMipsCalls(cur, call->reschedAfter)) {
|
|
// Sometimes, we want to stay on the thread.
|
|
int threadReady = cur->nt.status & (THREADSTATUS_READY | THREADSTATUS_RUNNING);
|
|
if (call->reschedAfter || threadReady == 0)
|
|
__KernelReSchedule("return from callback");
|
|
|
|
// Now seems like a good time to clear out any pending deletes.
|
|
for (SceUID delThread : pendingDeleteThreads) {
|
|
kernelObjects.Destroy<PSPThread>(delThread);
|
|
}
|
|
pendingDeleteThreads.clear();
|
|
}
|
|
|
|
delete call;
|
|
hleNoLogVoid();
|
|
}
|
|
|
|
// First arg must be current thread, passed to avoid perf cost of a lookup.
|
|
bool __KernelExecutePendingMipsCalls(PSPThread *thread, bool reschedAfter) {
|
|
_dbg_assert_msg_(thread->GetUID() == __KernelGetCurThread(), "__KernelExecutePendingMipsCalls() should be called only with the current thread.");
|
|
|
|
if (thread->pendingMipsCalls.empty()) {
|
|
// Nothing to do
|
|
return false;
|
|
}
|
|
|
|
if (__CanExecuteCallbackNow(thread))
|
|
{
|
|
// Pop off the first pending mips call
|
|
u32 callId = thread->pendingMipsCalls.front();
|
|
thread->pendingMipsCalls.pop_front();
|
|
if (__KernelExecuteMipsCallOnCurrentThread(callId, reschedAfter)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Executes the callback, when it next is context switched to.
|
|
static void __KernelRunCallbackOnThread(SceUID cbId, PSPThread *thread, bool reschedAfter) {
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (!cb) {
|
|
ERROR_LOG(Log::sceKernel, "__KernelRunCallbackOnThread: Bad cbId %i", cbId);
|
|
hleNoLogVoid();
|
|
return;
|
|
}
|
|
|
|
DEBUG_LOG(Log::sceKernel, "__KernelRunCallbackOnThread: Turning callback %i into pending mipscall", cbId);
|
|
|
|
// Alright, we're on the right thread
|
|
// Should save/restore wait state?
|
|
|
|
const u32 args[] = {(u32) cb->nc.notifyCount, (u32) cb->nc.notifyArg, cb->nc.commonArgument};
|
|
|
|
// Clear the notify count / arg
|
|
cb->nc.notifyCount = 0;
|
|
cb->nc.notifyArg = 0;
|
|
|
|
// Setting up the call costs about 8us on top of the switch: 14us from a notify to a better
|
|
// thread's callback running on hardware (pspautotests threads/callbacks/combos).
|
|
currentMIPS->downcount -= 1800;
|
|
|
|
ActionAfterCallback *action = (ActionAfterCallback *) __KernelCreateAction(actionAfterCallback);
|
|
if (action != NULL)
|
|
action->setCallback(cbId);
|
|
else
|
|
ERROR_LOG(Log::sceKernel, "Something went wrong creating a restore action for a callback.");
|
|
|
|
__KernelCallAddress(thread, cb->nc.entrypoint, action, args, 3, reschedAfter, cbId);
|
|
hleNoLogVoid();
|
|
}
|
|
|
|
void ActionAfterCallback::run(MipsCall &call) {
|
|
if (cbId != -1) {
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (cb) {
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(cb->nc.threadId, error);
|
|
if (t) {
|
|
// Check for other callbacks to run (including ones this callback scheduled.)
|
|
__KernelCheckThreadCallbacks(t, true);
|
|
}
|
|
|
|
DEBUG_LOG(Log::sceKernel, "Left callback %i - %s", cbId, cb->nc.name);
|
|
// Callbacks that don't return 0 are deleted. But should this be done here?
|
|
if (currentMIPS->r[MIPS_REG_V0] != 0) {
|
|
DEBUG_LOG(Log::sceKernel, "ActionAfterCallback::run(): Callback returned non-zero, gets deleted!");
|
|
kernelObjects.Destroy<PSPCallback>(cbId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool __KernelCurHasReadyCallbacks() {
|
|
if (readyCallbacksCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
PSPThread *thread = __GetCurrentThread();
|
|
u32 error;
|
|
for (auto it = thread->callbacks.begin(), end = thread->callbacks.end(); it != end; ++it) {
|
|
PSPCallback *callback = kernelObjects.Get<PSPCallback>(*it, error);
|
|
if (callback && callback->nc.notifyCount != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check callbacks on the current thread only.
|
|
// Returns true if any callbacks were processed on the current thread.
|
|
bool __KernelCheckThreadCallbacks(PSPThread *thread, bool force) {
|
|
if (!thread || (!thread->isProcessingCallbacks && !force)) {
|
|
return false;
|
|
}
|
|
|
|
if (!thread->callbacks.empty()) {
|
|
u32 error;
|
|
for (auto it = thread->callbacks.begin(), end = thread->callbacks.end(); it != end; ++it) {
|
|
PSPCallback *callback = kernelObjects.Get<PSPCallback>(*it, error);
|
|
if (callback && callback->nc.notifyCount != 0) {
|
|
__KernelRunCallbackOnThread(callback->GetUID(), thread, !force);
|
|
readyCallbacksCount--;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Checks for callbacks on all threads
|
|
bool __KernelCheckCallbacks() {
|
|
// Let's not check every thread all the time, callbacks are fairly uncommon.
|
|
if (readyCallbacksCount == 0) {
|
|
return false;
|
|
}
|
|
if (readyCallbacksCount < 0) {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "readyCallbacksCount became negative: %i", readyCallbacksCount);
|
|
}
|
|
if (__IsInInterrupt() || !__KernelIsDispatchEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
bool processed = false;
|
|
|
|
// A waiting thread takes its callbacks when it would get to run: only if its priority beats
|
|
// the running thread and every ready one. The rest stay pending (and can still be counted or
|
|
// canceled.)
|
|
PSPThread *cur = __GetCurrentThread();
|
|
u32 error;
|
|
u32 bestPriority = 0xFFFFFFFF;
|
|
if (cur && cur->isRunning())
|
|
bestPriority = cur->nt.currentPriority;
|
|
SceUID bestReady = threadReadyQueue.peek_first();
|
|
PSPThread *bestReadyThread = bestReady != 0 ? kernelObjects.Get<PSPThread>(bestReady, error) : nullptr;
|
|
if (bestReadyThread && bestReadyThread->nt.currentPriority < bestPriority)
|
|
bestPriority = bestReadyThread->nt.currentPriority;
|
|
|
|
for (auto iter = threadqueue.begin(); iter != threadqueue.end(); ++iter) {
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(*iter, error);
|
|
if (!thread || !thread->isWaiting())
|
|
continue;
|
|
if (thread != cur && thread->nt.currentPriority >= bestPriority)
|
|
continue;
|
|
if (!__CanExecuteCallbackNow(thread))
|
|
continue;
|
|
if (__KernelCheckThreadCallbacks(thread, false)) {
|
|
processed = true;
|
|
} else if (thread->waitPausedForCallback) {
|
|
// Its turn came, but the callbacks were canceled meanwhile: back to the wait, which
|
|
// may well be satisfied by now.
|
|
thread->waitPausedForCallback = false;
|
|
if (waitTypeFuncs[thread->nt.waitType].endFunc != NULL)
|
|
waitTypeFuncs[thread->nt.waitType].endFunc(thread->GetUID(), thread->currentCallbackId);
|
|
}
|
|
}
|
|
|
|
if (processed) {
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (thread) {
|
|
return __KernelExecutePendingMipsCalls(thread, true);
|
|
} else {
|
|
ERROR_LOG(Log::sceKernel, "No current thread in __KernelCheckCallbacks!");
|
|
// stumble along?
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool __KernelForceCallbacks() {
|
|
// Let's not check every thread all the time, callbacks are fairly uncommon.
|
|
if (readyCallbacksCount == 0) {
|
|
return false;
|
|
}
|
|
if (readyCallbacksCount < 0) {
|
|
ERROR_LOG_REPORT(Log::sceKernel, "readyCallbacksCount became negative: %i", readyCallbacksCount);
|
|
}
|
|
|
|
PSPThread *curThread = __GetCurrentThread();
|
|
|
|
bool callbacksProcessed = __KernelCheckThreadCallbacks(curThread, true);
|
|
if (callbacksProcessed)
|
|
__KernelExecutePendingMipsCalls(curThread, false);
|
|
|
|
return callbacksProcessed;
|
|
}
|
|
|
|
// Not wrapped because it has special return logic.
|
|
void sceKernelCheckCallback() {
|
|
// Unlike CB waits, this refuses to run callbacks from inside one.
|
|
if (__KernelIsInCallbackOnThread(__GetCurrentThread())) {
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelCheckCallback() - in a callback, refused.");
|
|
RETURN(SCE_KERNEL_ERROR_ILLEGAL_CONTEXT);
|
|
hleEatCycles(230);
|
|
hleNoLogVoid();
|
|
return;
|
|
}
|
|
|
|
// Start with yes.
|
|
RETURN(1);
|
|
|
|
bool callbacksProcessed = __KernelForceCallbacks();
|
|
|
|
if (callbacksProcessed) {
|
|
DEBUG_LOG(Log::sceKernel, "sceKernelCheckCallback() - processed a callback.");
|
|
// The RETURN(1) above is still active here, unless __KernelForceCallbacks changed it.
|
|
} else {
|
|
RETURN(0);
|
|
}
|
|
hleEatCycles(230);
|
|
hleNoLogVoid();
|
|
}
|
|
|
|
bool __KernelInCallback()
|
|
{
|
|
return __KernelIsInCallbackOnThread(__GetCurrentThread());
|
|
}
|
|
|
|
void __KernelNotifyCallback(SceUID cbId, int notifyArg)
|
|
{
|
|
u32 error;
|
|
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (!cb) {
|
|
// Yeah, we're screwed, this shouldn't happen.
|
|
ERROR_LOG(Log::sceKernel, "__KernelNotifyCallback - invalid callback %08x", cbId);
|
|
return;
|
|
}
|
|
if (cb->nc.notifyCount == 0) {
|
|
readyCallbacksCount++;
|
|
}
|
|
cb->nc.notifyCount++;
|
|
cb->nc.notifyArg = notifyArg;
|
|
|
|
// A thread in a CB wait is taken out of the wait as soon as one of its callbacks is notified,
|
|
// even though the callback only runs when the thread would get to run. Whatever happens to the
|
|
// object in between is seen when the wait resumes after the callbacks: releasing it doesn't
|
|
// end the wait, and doesn't stop the callbacks running (pspautotests threads/callbacks/combos).
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(cb->nc.threadId, error);
|
|
if (t && t->isWaiting() && t->isProcessingCallbacks && !t->waitPausedForCallback && t->nt.waitType != WAITTYPE_NONE) {
|
|
if (waitTypeFuncs[t->nt.waitType].beginFunc != NULL) {
|
|
waitTypeFuncs[t->nt.waitType].beginFunc(t->GetUID(), t->currentCallbackId);
|
|
t->waitPausedForCallback = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void __KernelRegisterWaitTypeFuncs(WaitType type, WaitBeginCallbackFunc beginFunc, WaitEndCallbackFunc endFunc, WaitTimeoutFunc timeoutFunc)
|
|
{
|
|
waitTypeFuncs[type].beginFunc = beginFunc;
|
|
waitTypeFuncs[type].endFunc = endFunc;
|
|
waitTypeFuncs[type].timeoutFunc = timeoutFunc;
|
|
}
|
|
|
|
std::vector<DebugThreadInfo> GetThreadsInfo() {
|
|
std::lock_guard<std::mutex> guard(threadqueueLock);
|
|
std::vector<DebugThreadInfo> threadList;
|
|
|
|
u32 error;
|
|
for (const auto uid : threadqueue) {
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(uid, error);
|
|
if (!t)
|
|
continue;
|
|
|
|
DebugThreadInfo info;
|
|
info.id = uid;
|
|
strncpy(info.name,t->GetName(),KERNELOBJECT_MAX_NAME_LENGTH);
|
|
info.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0;
|
|
info.status = (ThreadStatus)t->nt.status;
|
|
info.entrypoint = t->nt.entrypoint;
|
|
info.initialStack = t->nt.initialStack;
|
|
info.stackSize = (u32)t->nt.stackSize;
|
|
info.priority = t->nt.currentPriority;
|
|
info.waitType = (WaitType)(u32)t->nt.waitType;
|
|
info.waitID = t->nt.waitID;
|
|
info.isCurrent = uid == currentThread;
|
|
if (info.isCurrent)
|
|
info.curPC = currentMIPS->pc;
|
|
else
|
|
info.curPC = t->context.pc;
|
|
threadList.push_back(info);
|
|
}
|
|
|
|
return threadList;
|
|
}
|
|
|
|
DebugInterface *KernelDebugThread(SceUID threadID) {
|
|
if (threadID == currentThread) {
|
|
return currentDebugMIPS;
|
|
}
|
|
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadID, error);
|
|
if (t) {
|
|
return &t->debug;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void __KernelChangeThreadState(SceUID threadId, ThreadStatus newStatus) {
|
|
u32 error;
|
|
PSPThread *t = kernelObjects.Get<PSPThread>(threadId, error);
|
|
if (!t)
|
|
return;
|
|
|
|
__KernelChangeThreadState(t, newStatus);
|
|
}
|
|
|
|
int sceKernelRegisterExitCallback(SceUID cbId) {
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(cbId, error);
|
|
if (!cb) {
|
|
WARN_LOG(Log::sceKernel, "sceKernelRegisterExitCallback(%i): invalid callback id", cbId);
|
|
if (sceKernelGetCompiledSdkVersion() >= 0x3090500)
|
|
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT);
|
|
return hleNoLog(0);
|
|
}
|
|
|
|
registeredExitCbId = cbId;
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
// Dispatch the exit callback registered via sceKernelRegisterExitCallback on the thread that registered
|
|
// it, so the game has a chance to run its cleanup before we shut down. Returns false if no callback can
|
|
// be dispatched (none registered, registering thread dead, etc.) - the caller should then power down
|
|
// immediately. While true is returned, __KernelIsExitCallbackPending() will report true until the
|
|
// callback returns (via the chained ActionAfterExitCallback).
|
|
bool __KernelInvokeRegisteredExitCallback() {
|
|
if (g_exitCallbackPending) {
|
|
WARN_LOG(Log::sceKernel, "__KernelInvokeRegisteredExitCallback: already in progress");
|
|
return true;
|
|
}
|
|
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(registeredExitCbId, error);
|
|
if (!cb) {
|
|
INFO_LOG(Log::sceKernel, "__KernelInvokeRegisteredExitCallback: no exit callback registered");
|
|
return false;
|
|
}
|
|
|
|
PSPThread *thread = kernelObjects.Get<PSPThread>(cb->nc.threadId, error);
|
|
if (!thread) {
|
|
WARN_LOG(Log::sceKernel, "__KernelInvokeRegisteredExitCallback: registering thread %08x is gone", cb->nc.threadId);
|
|
return false;
|
|
}
|
|
if (thread->isStopped()) {
|
|
WARN_LOG(Log::sceKernel, "__KernelInvokeRegisteredExitCallback: registering thread %s is stopped", thread->GetName());
|
|
return false;
|
|
}
|
|
|
|
ActionAfterExitCallback *action = (ActionAfterExitCallback *)__KernelCreateAction(actionAfterExitCallback);
|
|
|
|
// Exit callbacks on real PSP receive (arg1, arg2, common) where arg1/arg2 are zero for HOME-button exit.
|
|
const u32 args[] = { 0, 0, cb->nc.commonArgument };
|
|
|
|
INFO_LOG(Log::sceKernel, "__KernelInvokeRegisteredExitCallback: dispatching '%s' (cb %08x) on thread '%s'",
|
|
cb->nc.name, registeredExitCbId, thread->GetName());
|
|
|
|
g_exitCallbackPending = true;
|
|
__KernelCallAddress(thread, cb->nc.entrypoint, action, args, 3, true, registeredExitCbId);
|
|
return true;
|
|
}
|
|
|
|
bool __KernelIsExitCallbackPending() {
|
|
return g_exitCallbackPending;
|
|
}
|
|
|
|
// Update the exit callback status?
|
|
int LoadExecForUser_362A956B() {
|
|
WARN_LOG_REPORT(Log::sceKernel, "LoadExecForUser_362A956B()");
|
|
u32 error;
|
|
PSPCallback *cb = kernelObjects.Get<PSPCallback>(registeredExitCbId, error);
|
|
if (!cb) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_UNKNOWN_CBID, "registeredExitCbId not found 0x%x", registeredExitCbId);
|
|
}
|
|
int cbArg = cb->nc.commonArgument;
|
|
if (!Memory::IsValidRange(cbArg - 8, 8)) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "invalid address for cbArg (0x%08X)", cbArg);
|
|
}
|
|
const u32 unknown1 = Memory::ReadUnchecked_U32(cbArg - 8);
|
|
if (unknown1 >= 4) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid value unknown1 (0x%08X)", unknown1);
|
|
}
|
|
const u32 parameterArea = Memory::ReadUnchecked_U32(cbArg - 4);
|
|
if (!Memory::IsValid4AlignedRange(parameterArea, 12)) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "invalid address for parameterArea on userMemory (0x%08X)", parameterArea);
|
|
}
|
|
const u32 size = Memory::ReadUnchecked_U32(parameterArea);
|
|
if (size < 12) {
|
|
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_SIZE, "invalid parameterArea size %d", size);
|
|
}
|
|
Memory::WriteUnchecked_U32(0, parameterArea + 4);
|
|
Memory::WriteUnchecked_U32(-1, parameterArea + 8);
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
}
|
|
|
|
static const SceUID SCE_TE_THREADID_ALL_USER = 0xFFFFFFF0;
|
|
|
|
struct NativeThreadEventHandler {
|
|
u32 size;
|
|
char name[KERNELOBJECT_MAX_NAME_LENGTH + 1];
|
|
SceUID threadID;
|
|
u32 mask;
|
|
u32 handlerPtr;
|
|
u32 commonArg;
|
|
};
|
|
|
|
struct ThreadEventHandler : public KernelObject {
|
|
const char *GetName() override { return nteh.name; }
|
|
const char *GetTypeName() override { return GetStaticTypeName(); }
|
|
static const char *GetStaticTypeName() { return "ThreadEventHandler"; }
|
|
static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_TEID; }
|
|
static int GetStaticIDType() { return SCE_KERNEL_TMID_ThreadEventHandler; }
|
|
int GetIDType() const override { return SCE_KERNEL_TMID_ThreadEventHandler; }
|
|
|
|
void DoState(PointerWrap &p) override {
|
|
auto s = p.Section("ThreadEventHandler", 1);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, nteh);
|
|
}
|
|
|
|
NativeThreadEventHandler nteh;
|
|
};
|
|
|
|
KernelObject *__KernelThreadEventHandlerObject() {
|
|
// Default object to load from state.
|
|
return new ThreadEventHandler();
|
|
}
|
|
|
|
bool __KernelThreadTriggerEvent(const ThreadEventHandlerList &handlers, SceUID threadID, ThreadEventType type) {
|
|
PSPThread *thread = __GetCurrentThread();
|
|
if (!thread || thread->isStopped()) {
|
|
SceUID nextThreadID = threadReadyQueue.peek_first();
|
|
thread = kernelObjects.GetFast<PSPThread>(nextThreadID);
|
|
}
|
|
|
|
bool hadHandlers = false;
|
|
for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) {
|
|
u32 error;
|
|
const auto teh = kernelObjects.Get<ThreadEventHandler>(*it, error);
|
|
if (!teh || (teh->nteh.mask & type) == 0) {
|
|
continue;
|
|
}
|
|
|
|
const u32 args[] = {(u32)type, (u32)threadID, teh->nteh.commonArg};
|
|
__KernelCallAddress(thread, teh->nteh.handlerPtr, nullptr, args, ARRAY_SIZE(args), true, 0);
|
|
hadHandlers = true;
|
|
}
|
|
|
|
return hadHandlers;
|
|
}
|
|
|
|
bool __KernelThreadTriggerEvent(bool isKernel, SceUID threadID, ThreadEventType type) {
|
|
bool hadExactHandlers = false;
|
|
auto exactHandlers = threadEventHandlers.find(threadID);
|
|
if (exactHandlers != threadEventHandlers.end()) {
|
|
hadExactHandlers = __KernelThreadTriggerEvent(exactHandlers->second, threadID, type);
|
|
}
|
|
|
|
bool hadKindHandlers = false;
|
|
if (isKernel) {
|
|
auto kernelHandlers = threadEventHandlers.find(SCE_TE_THREADID_ALL_USER);
|
|
if (kernelHandlers != threadEventHandlers.end()) {
|
|
hadKindHandlers = __KernelThreadTriggerEvent(kernelHandlers->second, threadID, type);
|
|
}
|
|
} else {
|
|
auto userHandlers = threadEventHandlers.find(SCE_TE_THREADID_ALL_USER);
|
|
if (userHandlers != threadEventHandlers.end()) {
|
|
hadKindHandlers = __KernelThreadTriggerEvent(userHandlers->second, threadID, type);
|
|
}
|
|
}
|
|
|
|
return hadKindHandlers || hadExactHandlers;
|
|
}
|
|
|
|
SceUID sceKernelRegisterThreadEventHandler(const char *name, SceUID threadID, u32 mask, u32 handlerPtr, u32 commonArg) {
|
|
if (!name) {
|
|
return hleReportError(Log::sceKernel, SCE_KERNEL_ERROR_ERROR, "invalid name");
|
|
}
|
|
if (threadID == 0) {
|
|
// "atexit"?
|
|
if (mask != THREADEVENT_EXIT) {
|
|
return hleReportError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid thread id");
|
|
}
|
|
}
|
|
u32 error;
|
|
if (kernelObjects.Get<PSPThread>(threadID, error) == NULL && threadID != SCE_TE_THREADID_ALL_USER) {
|
|
return hleReportError(Log::sceKernel, error, "bad thread id");
|
|
}
|
|
if ((mask & ~THREADEVENT_SUPPORTED) != 0) {
|
|
return hleReportError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_MASK, "invalid event mask");
|
|
}
|
|
|
|
auto teh = new ThreadEventHandler;
|
|
teh->nteh.size = sizeof(teh->nteh);
|
|
strncpy(teh->nteh.name, name, KERNELOBJECT_MAX_NAME_LENGTH);
|
|
teh->nteh.name[KERNELOBJECT_MAX_NAME_LENGTH] = '\0';
|
|
teh->nteh.threadID = threadID;
|
|
teh->nteh.mask = mask;
|
|
teh->nteh.handlerPtr = handlerPtr;
|
|
teh->nteh.commonArg = commonArg;
|
|
|
|
SceUID uid = kernelObjects.Create(teh);
|
|
threadEventHandlers[threadID].push_back(uid);
|
|
|
|
return hleLogDebug(Log::sceKernel, uid);
|
|
}
|
|
|
|
int sceKernelReleaseThreadEventHandler(SceUID uid) {
|
|
u32 error;
|
|
auto teh = kernelObjects.Get<ThreadEventHandler>(uid, error);
|
|
if (!teh) {
|
|
return hleReportError(Log::sceKernel, error, "bad handler id");
|
|
}
|
|
|
|
auto &handlers = threadEventHandlers[teh->nteh.threadID];
|
|
handlers.erase(std::remove(handlers.begin(), handlers.end(), uid), handlers.end());
|
|
return hleLogDebug(Log::sceKernel, kernelObjects.Destroy<ThreadEventHandler>(uid));
|
|
}
|
|
|
|
int sceKernelReferThreadEventHandlerStatus(SceUID uid, u32 infoPtr) {
|
|
u32 error;
|
|
auto teh = kernelObjects.Get<ThreadEventHandler>(uid, error);
|
|
if (!teh) {
|
|
return hleReportError(Log::sceKernel, error, "bad handler id");
|
|
}
|
|
|
|
auto info = PSPPointer<NativeThreadEventHandler>::Create(infoPtr);
|
|
if (info.IsValid() && info->size != 0) {
|
|
*info = teh->nteh;
|
|
info.NotifyWrite("ThreadEventHandlerStatus");
|
|
return hleLogDebug(Log::sceKernel, 0);
|
|
} else {
|
|
return hleLogDebug(Log::sceKernel, 0, "struct size was 0");
|
|
}
|
|
}
|