diff --git a/Core/HLE/ThreadQueueList.h b/Core/HLE/ThreadQueueList.h index 9c1b1e5da2..9111fff41d 100644 --- a/Core/HLE/ThreadQueueList.h +++ b/Core/HLE/ThreadQueueList.h @@ -99,6 +99,17 @@ struct ThreadQueueList { return 0; } + inline SceUID peek_first() { + Queue *cur = first; + while (cur != invalid()) { + if (cur->size() > 0) + return cur->data[cur->first]; + cur = cur->next; + } + + return 0; + } + inline void push_front(u32 priority, const SceUID threadID) { Queue *cur = &queues[priority]; cur->data[--cur->first] = threadID; diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index c5b7b85e71..8e9bea0b5b 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -596,6 +596,8 @@ KernelObject *KernelObjectPool::CreateByIDType(int type) { return __KernelFileNodeObject(); case PPSSPP_KERNEL_TMID_DirList: return __KernelDirListingObject(); + case SCE_KERNEL_TMID_ThreadEventHandler: + return __KernelThreadEventHandlerObject(); default: ERROR_LOG(COMMON, "Unable to load state: could not find object type %d.", type); @@ -786,10 +788,6 @@ const HLEFunction ThreadManForUser[] = {0X278C0DF5, &WrapI_IU, "sceKernelWaitThreadEnd", 'i', "ix" }, {0XD59EAD2F, &WrapI_I, "sceKernelWakeupThread", 'i', "i" }, //AI Go, audio? - {0X0C106E53, nullptr, "sceKernelRegisterThreadEventHandler", '?', "" }, - {0X72F3C145, nullptr, "sceKernelReleaseThreadEventHandler", '?', "" }, - {0X369EEB6B, nullptr, "sceKernelReferThreadEventHandlerStatus", '?', "" }, - {0x349d6d6c, &sceKernelCheckCallback, "sceKernelCheckCallback", 'i', "" }, {0XE81CAF8F, &WrapI_CUU, "sceKernelCreateCallback", 'i', "sxx" }, {0XEDBA5844, &WrapI_I, "sceKernelDeleteCallback", 'i', "i" }, @@ -864,6 +862,9 @@ const HLEFunction ThreadManForUser[] = {0x0E927AED, &_sceKernelReturnFromTimerHandler, "_sceKernelReturnFromTimerHandler", 'v', "" }, {0X532A522E, &WrapV_I<_sceKernelExitThread>, "_sceKernelExitThread", 'v', "i" }, + {0x0C106E53, &WrapI_CIUUU, "sceKernelRegisterThreadEventHandler", 'i', "sixxx", }, + {0x72F3C145, &WrapI_I, "sceKernelReleaseThreadEventHandler", 'i', "i" }, + {0x369EEB6B, &WrapI_IU, "sceKernelReferThreadEventHandlerStatus", 'i', "ip" }, // Shouldn't hook this up. No games should import this function manually and call it. // {0x6E9EA350, _sceKernelReturnFromCallback,"_sceKernelReturnFromCallback"}, diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index d125ec2254..978defe8a3 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -91,7 +91,17 @@ const char *getWaitTypeName(WaitType type) return "Unknown"; } - enum { +enum ThreadEventType { + THREADEVENT_CREATE = 1, + THREADEVENT_START = 2, + THREADEVENT_EXIT = 4, + THREADEVENT_DELETE = 8, + THREADEVENT_SUPPORTED = THREADEVENT_CREATE | THREADEVENT_START | THREADEVENT_EXIT | THREADEVENT_DELETE, +}; + +void __KernelThreadTriggerEvent(bool isKernel, SceUID threadID, ThreadEventType type); + +enum { PSP_THREAD_ATTR_KERNEL = 0x00001000, PSP_THREAD_ATTR_VFPU = 0x00004000, PSP_THREAD_ATTR_SCRATCH_SRAM = 0x00008000, // Save/restore scratch as part of context??? @@ -149,21 +159,16 @@ public: return; p.Do(nc); - p.Do(savedPC); - p.Do(savedRA); - p.Do(savedV0); - p.Do(savedV1); - // No longer used. - u32 legacySavedIdRegister = 0; - p.Do(legacySavedIdRegister); + // Saved values were moved to mips call, ignoring here. + u32 legacySaved = 0; + p.Do(legacySaved); + p.Do(legacySaved); + p.Do(legacySaved); + p.Do(legacySaved); + p.Do(legacySaved); } NativeCallback nc; - - u32 savedPC; - u32 savedRA; - u32 savedV0; - u32 savedV1; }; #if COMMON_LITTLE_ENDIAN @@ -607,7 +612,7 @@ struct WaitTypeFuncs WaitEndCallbackFunc endFunc; }; -void __KernelExecuteMipsCallOnCurrentThread(u32 callId, bool reschedAfter); +bool __KernelExecuteMipsCallOnCurrentThread(u32 callId, bool reschedAfter); Thread *__KernelCreateThread(SceUID &id, SceUID moduleID, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr); void __KernelResetThread(Thread *t, int lowestPriority); @@ -632,6 +637,9 @@ u32 extendReturnHackAddr; u32 moduleReturnHackAddr; std::vector threadEndListeners; +typedef std::vector ThreadEventHandlerList; +static std::map threadEventHandlers; + // Lists all thread ids that aren't deleted/etc. std::vector threadqueue; @@ -690,7 +698,8 @@ void MipsCall::DoState(PointerWrap &p) // No longer used. u32 legacySavedIdRegister = 0; p.Do(legacySavedIdRegister); - p.Do(savedRa); + u32 legacySavedRa = 0; + p.Do(legacySavedRa); p.Do(savedPc); p.Do(savedV0); p.Do(savedV1); @@ -943,7 +952,7 @@ void __KernelThreadingInit() void __KernelThreadingDoState(PointerWrap &p) { - auto s = p.Section("sceKernelThread", 1); + auto s = p.Section("sceKernelThread", 1, 2); if (!s) return; @@ -978,6 +987,9 @@ void __KernelThreadingDoState(PointerWrap &p) __SetCurrentThread(kernelObjects.GetFast(currentThread), currentThread, __KernelGetThreadName(currentThread)); lastSwitchCycles = CoreTiming::GetTicks(); + + if (s >= 2) + p.Do(threadEventHandlers); } void __KernelThreadingDoStateLate(PointerWrap &p) @@ -1146,6 +1158,7 @@ void __KernelThreadingShutdown() __SetCurrentThread(NULL, 0, NULL); intReturnHackAddr = 0; pausedDelays.clear(); + threadEventHandlers.clear(); } const char *__KernelGetThreadName(SceUID threadID) @@ -1633,6 +1646,11 @@ u32 __KernelDeleteThread(SceUID threadID, int exitStatus, const char *reason) t->Cleanup(); } + // TODO: Thread should not be deleted yet... + // Before triggering, set v0. It'll be restored if one is called. + RETURN(error); + __KernelThreadTriggerEvent((t->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, threadID, THREADEVENT_DELETE); + return kernelObjects.Destroy(threadID); } @@ -1940,6 +1958,10 @@ int __KernelCreateThread(const char *threadName, SceUID moduleID, u32 entry, u32 // This won't schedule to the new thread, but it may to one woken from eating cycles. // Technically, this should not eat all at once, and reschedule in the middle, but that's hard. hleReSchedule("thread created"); + + // Before triggering, set v0, since we restore on return. + RETURN(id); + __KernelThreadTriggerEvent((attr & PSP_THREAD_ATTR_KERNEL) != 0, id, THREADEVENT_CREATE); return hleLogSuccessInfoI(SCEKERNEL, id); } @@ -1988,6 +2010,11 @@ int __KernelStartThread(SceUID threadToStartID, int argSize, u32 argBlockPtr, bo 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; } @@ -2055,6 +2082,9 @@ void __KernelReturnFromThread() 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. } @@ -2068,6 +2098,9 @@ void sceKernelExitThread(int exitStatus) 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. } @@ -2081,6 +2114,9 @@ void _sceKernelExitThread(int exitStatus) 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. } @@ -2095,6 +2131,9 @@ void sceKernelExitDeleteThread(int exitStatus) g_inCbCount = 0; hleReSchedule("thread exited with delete"); + + // TODO: This should trigger ON the thread when it exits. + __KernelThreadTriggerEvent((thread->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, thread->GetUID(), THREADEVENT_EXIT); } else ERROR_LOG_REPORT(SCEKERNEL, "sceKernelExitDeleteThread(%d) ERROR - could not find myself!", exitStatus); @@ -2208,9 +2247,17 @@ int sceKernelTerminateDeleteThread(int threadID) Thread *t = kernelObjects.Get(threadID, error); if (t) { + bool wasStopped = t->isStopped(); + INFO_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((t->nt.attr & PSP_THREAD_ATTR_KERNEL) != 0, t->GetUID(), THREADEVENT_EXIT); + } + return error; } else @@ -2241,6 +2288,10 @@ int sceKernelTerminateThread(SceUID threadID) { // 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 hleLogSuccessInfoI(SCEKERNEL, 0); } else { return hleLogError(SCEKERNEL, error, "thread doesn't exist"); @@ -2716,7 +2767,7 @@ int sceKernelDeleteCallback(SceUID cbId) { Thread *thread = kernelObjects.Get(cb->nc.threadId, error); if (thread) - thread->callbacks.erase(std::find(thread->callbacks.begin(), thread->callbacks.end(), cbId), thread->callbacks.end()); + thread->callbacks.erase(std::remove(thread->callbacks.begin(), thread->callbacks.end(), cbId), thread->callbacks.end()); if (cb->nc.notifyCount != 0) readyCallbacksCount--; @@ -2842,8 +2893,11 @@ void ActionAfterMipsCall::run(MipsCall &call) { u32 error; Thread *thread = kernelObjects.Get(threadID, error); if (thread) { - __KernelChangeReadyState(thread, threadID, (status & THREADSTATUS_READY) != 0); - thread->nt.status = status; + // 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; @@ -3003,7 +3057,10 @@ static bool __CanExecuteCallbackNow(Thread *thread) { void __KernelCallAddress(Thread *thread, u32 entryPoint, Action *afterAction, const u32 args[], int numargs, bool reschedAfter, SceUID cbId) { - hleSkipDeadbeef(); + if (!thread || thread->isStopped()) { + WARN_LOG_REPORT(SCEKERNEL, "Running mipscall on dormant thread"); + } + _dbg_assert_msg_(SCEKERNEL, numargs <= 6, "MipsCalls can only take 6 args."); if (thread) { @@ -3053,8 +3110,7 @@ void __KernelCallAddress(Thread *thread, u32 entryPoint, Action *afterAction, co if (__CanExecuteCallbackNow(thread)) { thread = __GetCurrentThread(); __KernelChangeThreadState(thread, THREADSTATUS_RUNNING); - __KernelExecuteMipsCallOnCurrentThread(callId, reschedAfter); - called = true; + called = __KernelExecuteMipsCallOnCurrentThread(callId, reschedAfter); } } @@ -3073,13 +3129,14 @@ void __KernelDirectMipsCall(u32 entryPoint, Action *afterAction, u32 args[], int __KernelCallAddress(__GetCurrentThread(), entryPoint, afterAction, args, numargs, reschedAfter, 0); } -void __KernelExecuteMipsCallOnCurrentThread(u32 callId, bool reschedAfter) +bool __KernelExecuteMipsCallOnCurrentThread(u32 callId, bool reschedAfter) { + hleSkipDeadbeef(); + Thread *cur = __GetCurrentThread(); - if (cur == NULL) - { + if (cur == nullptr) { ERROR_LOG(SCEKERNEL, "__KernelExecuteMipsCallOnCurrentThread(): Bad current thread"); - return; + return false; } if (g_inCbCount > 0) { @@ -3088,9 +3145,24 @@ void __KernelExecuteMipsCallOnCurrentThread(u32 callId, bool reschedAfter) DEBUG_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(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::Write_U32(currentMIPS->r[i], sp + i * 4); + } + Memory::Write_U32(currentMIPS->r[MIPS_REG_T8], sp + MIPS_REG_T8 * 4); + Memory::Write_U32(currentMIPS->r[MIPS_REG_T9], sp + MIPS_REG_T9 * 4); + Memory::Write_U32(currentMIPS->r[MIPS_REG_RA], sp + MIPS_REG_RA * 4); + // Save the few regs that need saving call->savedPc = currentMIPS->pc; - call->savedRa = currentMIPS->r[MIPS_REG_RA]; call->savedV0 = currentMIPS->r[MIPS_REG_V0]; call->savedV1 = currentMIPS->r[MIPS_REG_V1]; call->savedId = cur->currentMipscallId; @@ -3107,6 +3179,8 @@ void __KernelExecuteMipsCallOnCurrentThread(u32 callId, bool reschedAfter) if (call->cbId != 0) g_inCbCount++; currentCallbackThreadID = currentThread; + + return true; } void __KernelReturnFromMipsCall() @@ -3134,8 +3208,17 @@ void __KernelReturnFromMipsCall() delete call->doAfter; } + u32 &sp = currentMIPS->r[MIPS_REG_SP]; + for (int i = MIPS_REG_A0; i <= MIPS_REG_T7; ++i) { + currentMIPS->r[i] = Memory::Read_U32(sp + i * 4); + } + currentMIPS->r[MIPS_REG_T8] = Memory::Read_U32(sp + MIPS_REG_T8 * 4); + currentMIPS->r[MIPS_REG_T9] = Memory::Read_U32(sp + MIPS_REG_T9 * 4); + currentMIPS->r[MIPS_REG_RA] = Memory::Read_U32(sp + MIPS_REG_RA * 4); + sp += 32 * 4; + currentMIPS->pc = call->savedPc; - currentMIPS->r[MIPS_REG_RA] = call->savedRa; + // 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; @@ -3184,8 +3267,9 @@ bool __KernelExecutePendingMipsCalls(Thread *thread, bool reschedAfter) // Pop off the first pending mips call u32 callId = thread->pendingMipsCalls.front(); thread->pendingMipsCalls.pop_front(); - __KernelExecuteMipsCallOnCurrentThread(callId, reschedAfter); - return true; + if (__KernelExecuteMipsCallOnCurrentThread(callId, reschedAfter)) { + return true; + } } return false; } @@ -3470,3 +3554,134 @@ int LoadExecForUser_362A956B() Memory::Write_U32(-1, parameterArea + 8); return 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() { return nteh.name; } + const char *GetTypeName() { return "ThreadEventHandler"; } + static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_TEID; } + static int GetStaticIDType() { return SCE_KERNEL_TMID_ThreadEventHandler; } + int GetIDType() const { return SCE_KERNEL_TMID_ThreadEventHandler; } + + virtual void DoState(PointerWrap &p) { + auto s = p.Section("ThreadEventHandler", 1); + if (!s) + return; + + p.Do(nteh); + } + + NativeThreadEventHandler nteh; +}; + +KernelObject *__KernelThreadEventHandlerObject() { + // Default object to load from state. + return new ThreadEventHandler; +} + +void __KernelThreadTriggerEvent(const ThreadEventHandlerList &handlers, SceUID threadID, ThreadEventType type) { + Thread *thread = __GetCurrentThread(); + if (!thread || thread->isStopped()) { + SceUID nextThreadID = threadReadyQueue.peek_first(); + thread = kernelObjects.GetFast(nextThreadID); + } + + for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) { + u32 error; + const auto teh = kernelObjects.Get(*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); + } +} + +void __KernelThreadTriggerEvent(bool isKernel, SceUID threadID, ThreadEventType type) { + auto exactHandlers = threadEventHandlers.find(threadID); + if (exactHandlers != threadEventHandlers.end()) { + __KernelThreadTriggerEvent(exactHandlers->second, threadID, type); + } + if (isKernel) { + auto kernelHandlers = threadEventHandlers.find(SCE_TE_THREADID_ALL_USER); + if (kernelHandlers != threadEventHandlers.end()) { + __KernelThreadTriggerEvent(kernelHandlers->second, threadID, type); + } + } else { + auto userHandlers = threadEventHandlers.find(SCE_TE_THREADID_ALL_USER); + if (userHandlers != threadEventHandlers.end()) { + __KernelThreadTriggerEvent(userHandlers->second, threadID, type); + } + } +} + +SceUID sceKernelRegisterThreadEventHandler(const char *name, SceUID threadID, u32 mask, u32 handlerPtr, u32 commonArg) { + if (!name) { + return hleReportError(SCEKERNEL, SCE_KERNEL_ERROR_ERROR, "invalid name"); + } + if (threadID == 0) { + // "atexit"? + if (mask != THREADEVENT_EXIT) { + return hleReportError(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid thread id"); + } + } + u32 error; + if (kernelObjects.Get(threadID, error) == NULL && threadID != SCE_TE_THREADID_ALL_USER) { + return hleReportError(SCEKERNEL, error, "bad thread id"); + } + if ((mask & ~THREADEVENT_SUPPORTED) != 0) { + return hleReportError(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 hleLogSuccessI(SCEKERNEL, uid); +} + +int sceKernelReleaseThreadEventHandler(SceUID uid) { + u32 error; + auto teh = kernelObjects.Get(uid, error); + if (!teh) { + return hleReportError(SCEKERNEL, error, "bad handler id"); + } + + auto &handlers = threadEventHandlers[teh->nteh.threadID]; + handlers.erase(std::remove(handlers.begin(), handlers.end(), uid), handlers.end()); + return hleLogSuccessI(SCEKERNEL, kernelObjects.Destroy(uid)); +} + +int sceKernelReferThreadEventHandlerStatus(SceUID uid, u32 infoPtr) { + u32 error; + auto teh = kernelObjects.Get(uid, error); + if (!teh) { + return hleReportError(SCEKERNEL, error, "bad handler id"); + } + + if (Memory::IsValidAddress(infoPtr) && Memory::Read_U32(infoPtr) != 0) { + Memory::WriteStruct(infoPtr, &teh->nteh); + return hleLogSuccessI(SCEKERNEL, 0); + } else { + return hleLogDebug(SCEKERNEL, 0, "struct size was 0"); + } +} diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index 74ec6cd19e..53fa5e4367 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -44,6 +44,7 @@ void sceKernelExitThread(int exitStatus); void _sceKernelExitThread(int exitStatus); SceUID sceKernelGetThreadId(); int sceKernelGetThreadCurrentPriority(); +// Warning: will alter v0 in current MIPS state. int __KernelStartThread(SceUID threadToStartID, int argSize, u32 argBlockPtr, bool forceArgs = false); int __KernelStartThreadValidate(SceUID threadToStartID, int argSize, u32 argBlockPtr, bool forceArgs = false); int sceKernelStartThread(SceUID threadToStartID, int argSize, u32 argBlockPtr); @@ -254,7 +255,6 @@ struct MipsCall { u32 args[6]; int numArgs; Action *doAfter; - u32 savedRa; u32 savedPc; u32 savedV0; u32 savedV1; @@ -320,3 +320,8 @@ void __KernelChangeThreadState(SceUID threadId, ThreadStatus newStatus); int LoadExecForUser_362A956B(); int sceKernelRegisterExitCallback(SceUID cbId); + +KernelObject *__KernelThreadEventHandlerObject(); +SceUID sceKernelRegisterThreadEventHandler(const char *name, SceUID threadID, u32 mask, u32 handlerPtr, u32 commonArg); +int sceKernelReleaseThreadEventHandler(SceUID uid); +int sceKernelReferThreadEventHandlerStatus(SceUID uid, u32 infoPtr);