From fe60677f751f324e510ad3afd8b2f676a95985bd Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Mar 2013 13:48:29 -0700 Subject: [PATCH] Make mutexes handle callbacks better + before. Mutex and semaphores (but NOT lwmutexes) call callbacks before attempting the lock, but only if it would not generate errors. Have not yet tested other wait types. --- Core/HLE/sceKernelMutex.cpp | 220 +++++++++++++++++++++++--------- Core/HLE/sceKernelSemaphore.cpp | 15 ++- Core/HLE/sceKernelThread.cpp | 20 +++ Core/HLE/sceKernelThread.h | 1 + 4 files changed, 195 insertions(+), 61 deletions(-) diff --git a/Core/HLE/sceKernelMutex.cpp b/Core/HLE/sceKernelMutex.cpp index bf7c4565c5..8fc998fc89 100644 --- a/Core/HLE/sceKernelMutex.cpp +++ b/Core/HLE/sceKernelMutex.cpp @@ -71,11 +71,14 @@ struct Mutex : public KernelObject p.Do(nm); SceUID dv = 0; p.Do(waitingThreads, dv); + p.Do(pausedWaitTimeouts); p.DoMarker("Mutex"); } NativeMutex nm; std::vector waitingThreads; + // Key is the callback id it was for, or if no callback, the thread id. + std::map pausedWaitTimeouts; }; @@ -142,12 +145,16 @@ static int lwMutexWaitTimer = -1; typedef std::multimap MutexMap; static MutexMap mutexHeldLocks; +void __KernelMutexBeginCallback(SceUID threadID, SceUID prevCallbackId); +void __KernelMutexEndCallback(SceUID threadID, SceUID prevCallbackId, u32 &returnValue); + void __KernelMutexInit() { mutexWaitTimer = CoreTiming::RegisterEvent("MutexTimeout", __KernelMutexTimeout); lwMutexWaitTimer = CoreTiming::RegisterEvent("LwMutexTimeout", __KernelLwMutexTimeout); __KernelListenThreadEnd(&__KernelMutexThreadEnd); + __KernelRegisterWaitTypeFuncs(WAITTYPE_MUTEX, __KernelMutexBeginCallback, __KernelMutexEndCallback); } void __KernelMutexDoState(PointerWrap &p) @@ -232,6 +239,109 @@ std::vector::iterator __KernelMutexFindPriority(std::vector &wai return best; } +bool __KernelUnlockMutexForThread(Mutex *mutex, SceUID threadID, u32 &error, int result) +{ + SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_MUTEX, error); + u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); + + // The waitID may be different after a timeout. + if (waitID != mutex->GetUID()) + return false; + + // If result is an error code, we're just letting it go. + if (result == 0) + { + int wVal = (int)__KernelGetWaitValue(threadID, error); + __KernelMutexAcquireLock(mutex, wVal, threadID); + } + + if (timeoutPtr != 0 && mutexWaitTimer != -1) + { + // Remove any event for this thread. + u64 cyclesLeft = CoreTiming::UnscheduleEvent(mutexWaitTimer, threadID); + Memory::Write_U32((u32) cyclesToUs(cyclesLeft), timeoutPtr); + } + + __KernelResumeThreadFromWait(threadID, result); + return true; +} + +void __KernelMutexBeginCallback(SceUID threadID, SceUID prevCallbackId) +{ + SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId; + + u32 error; + SceUID mutexID = __KernelGetWaitID(threadID, WAITTYPE_MUTEX, error); + u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); + Mutex *mutex = mutexID == 0 ? NULL : kernelObjects.Get(mutexID, error); + if (mutex) + { + if (mutex->pausedWaitTimeouts.find(pauseKey) != mutex->pausedWaitTimeouts.end()) + WARN_LOG_REPORT(HLE, "sceKernelLockMutexCB: Callback %08x triggered within itself, should not happen (PSP crashes.)", prevCallbackId); + + if (timeoutPtr != 0 && mutexWaitTimer != -1) + { + u64 cyclesLeft = CoreTiming::UnscheduleEvent(mutexWaitTimer, threadID); + mutex->pausedWaitTimeouts[pauseKey] = CoreTiming::GetTicks() + cyclesLeft; + } + else + mutex->pausedWaitTimeouts[pauseKey] = 0; + + // TODO: Hmm, what about priority/fifo order? Does it lose its place in line? + mutex->waitingThreads.erase(std::remove(mutex->waitingThreads.begin(), mutex->waitingThreads.end(), threadID), mutex->waitingThreads.end()); + + DEBUG_LOG(HLE, "sceKernelLockMutexCB: Suspending lock wait for callback"); + } + else + WARN_LOG_REPORT(HLE, "sceKernelLockMutexCB: beginning callback with bad wait id?"); +} + +void __KernelMutexEndCallback(SceUID threadID, SceUID prevCallbackId, u32 &returnValue) +{ + SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId; + + u32 error; + SceUID mutexID = __KernelGetWaitID(threadID, WAITTYPE_MUTEX, error); + u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); + Mutex *mutex = mutexID == 0 ? NULL : kernelObjects.Get(mutexID, error); + if (!mutex || mutex->pausedWaitTimeouts.find(pauseKey) == mutex->pausedWaitTimeouts.end()) + { + // TODO: Since it was deleted, we don't know how long was actually left. + // For now, we just say the full time was taken. + if (timeoutPtr != 0 && mutexWaitTimer != -1) + Memory::Write_U32(0, timeoutPtr); + + __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_DELETE); + return; + } + + u64 waitDeadline = mutex->pausedWaitTimeouts[pauseKey]; + mutex->pausedWaitTimeouts.erase(pauseKey); + + // TODO: Don't wake up if __KernelCurHasReadyCallbacks()? + + // Attempt to unlock. + if (mutex->nm.lockThread == -1 && __KernelUnlockMutexForThread(mutex, threadID, error, 0)) + return; + + // We only check if it timed out if it couldn't unlock. + s64 cyclesLeft = waitDeadline - CoreTiming::GetTicks(); + if (cyclesLeft < 0 && waitDeadline != 0) + { + if (timeoutPtr != 0 && mutexWaitTimer != -1) + Memory::Write_U32(0, timeoutPtr); + + __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT); + } + else + { + // TODO: Should this not go at the end? + mutex->waitingThreads.push_back(threadID); + + DEBUG_LOG(HLE, "sceKernelLockMutexCB: Resuming lock wait for callback"); + } +} + int sceKernelCreateMutex(const char *name, u32 attr, int initialCount, u32 optionsPtr) { if (!name) @@ -276,33 +386,6 @@ int sceKernelCreateMutex(const char *name, u32 attr, int initialCount, u32 optio return id; } -bool __KernelUnlockMutexForThread(Mutex *mutex, SceUID threadID, u32 &error, int result) -{ - SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_MUTEX, error); - u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); - - // The waitID may be different after a timeout. - if (waitID != mutex->GetUID()) - return false; - - // If result is an error code, we're just letting it go. - if (result == 0) - { - int wVal = (int)__KernelGetWaitValue(threadID, error); - __KernelMutexAcquireLock(mutex, wVal, threadID); - } - - if (timeoutPtr != 0 && mutexWaitTimer != -1) - { - // Remove any event for this thread. - u64 cyclesLeft = CoreTiming::UnscheduleEvent(mutexWaitTimer, threadID); - Memory::Write_U32((u32) cyclesToUs(cyclesLeft), timeoutPtr); - } - - __KernelResumeThreadFromWait(threadID, result); - return true; -} - int sceKernelDeleteMutex(SceUID id) { DEBUG_LOG(HLE,"sceKernelDeleteMutex(%i)", id); @@ -328,20 +411,38 @@ int sceKernelDeleteMutex(SceUID id) return error; } +bool __KernelLockMutexCheck(Mutex *mutex, int count, u32 &error) +{ + if (error) + return false; + + const bool mutexIsRecursive = (mutex->nm.attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) != 0; + + if (count <= 0) + error = SCE_KERNEL_ERROR_ILLEGAL_COUNT; + else if (count > 1 && !mutexIsRecursive) + error = SCE_KERNEL_ERROR_ILLEGAL_COUNT; + // Two positive ints will always overflow to negative. + else if (count + mutex->nm.lockLevel < 0) + error = PSP_MUTEX_ERROR_LOCK_OVERFLOW; + // Only a recursive mutex can re-lock. + else if (mutex->nm.lockThread == __KernelGetCurThread()) + { + if (mutexIsRecursive) + return true; + + error = PSP_MUTEX_ERROR_ALREADY_LOCKED; + } + // Otherwise it would lock or wait. + else if (mutex->nm.lockLevel == 0) + return true; + + return false; +} + bool __KernelLockMutex(Mutex *mutex, int count, u32 &error) { - if (!error) - { - if (count <= 0) - error = SCE_KERNEL_ERROR_ILLEGAL_COUNT; - else if (count > 1 && !(mutex->nm.attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE)) - error = SCE_KERNEL_ERROR_ILLEGAL_COUNT; - // Two positive ints will always overflow to negative. - else if (count + mutex->nm.lockLevel < 0) - error = PSP_MUTEX_ERROR_LOCK_OVERFLOW; - } - - if (error) + if (!__KernelLockMutexCheck(mutex, count, error)) return false; if (mutex->nm.lockLevel == 0) @@ -353,17 +454,9 @@ bool __KernelLockMutex(Mutex *mutex, int count, u32 &error) if (mutex->nm.lockThread == __KernelGetCurThread()) { - // Recursive mutex, let's just increase the lock count and keep going - if (mutex->nm.attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) - { - mutex->nm.lockLevel += count; - return true; - } - else - { - error = PSP_MUTEX_ERROR_ALREADY_LOCKED; - return false; - } + // __KernelLockMutexCheck() would've returned an error, so this must be recursive. + mutex->nm.lockLevel += count; + return true; } return false; @@ -489,15 +582,11 @@ int sceKernelLockMutexCB(SceUID id, int count, u32 timeoutPtr) u32 error; Mutex *mutex = kernelObjects.Get(id, error); - if (__KernelLockMutex(mutex, count, error)) - { - hleCheckCurrentCallbacks(); - return 0; - } - else if (error) - return error; - else + if (!__KernelLockMutexCheck(mutex, count, error)) { + if (error) + return error; + SceUID threadID = __KernelGetCurThread(); // May be in a tight loop timing out (where we don't remove from waitingThreads yet), don't want to add duplicates. if (std::find(mutex->waitingThreads.begin(), mutex->waitingThreads.end(), threadID) == mutex->waitingThreads.end()) @@ -508,6 +597,21 @@ int sceKernelLockMutexCB(SceUID id, int count, u32 timeoutPtr) // Return value will be overwritten by wait. return 0; } + else + { + if (__KernelCurHasReadyCallbacks()) + { + // Might actually end up having to wait, so set the timeout. + __KernelWaitMutex(mutex, timeoutPtr); + __KernelWaitCallbacksCurThread(WAITTYPE_MUTEX, id, count, timeoutPtr); + + // Return value will be written to callback's v0, but... that's probably fine? + } + else + __KernelLockMutex(mutex, count, error); + + return 0; + } } // int sceKernelTryLockMutex(SceUID id, int count) diff --git a/Core/HLE/sceKernelSemaphore.cpp b/Core/HLE/sceKernelSemaphore.cpp index 09e808ee55..2b7109d9db 100644 --- a/Core/HLE/sceKernelSemaphore.cpp +++ b/Core/HLE/sceKernelSemaphore.cpp @@ -425,9 +425,18 @@ int __KernelWaitSema(SceUID id, int wantedCount, u32 timeoutPtr, const char *bad bool hasCallbacks = processCallbacks && __KernelCurHasReadyCallbacks(); if (s->ns.currentCount >= wantedCount && s->ns.numWaitThreads == 0 && !hasCallbacks) { - s->ns.currentCount -= wantedCount; - if (processCallbacks) - hleCheckCurrentCallbacks(); + if (hasCallbacks) + { + // __KernelSemaBeginCallback() will decrement this, so increment it here. + // TODO: Clean this up a bit better. + s->ns.numWaitThreads++; + + // Might actually end up having to wait, so set the timeout. + __KernelSetSemaTimeout(s, timeoutPtr); + __KernelWaitCallbacksCurThread(WAITTYPE_SEMA, id, wantedCount, timeoutPtr); + } + else + s->ns.currentCount -= wantedCount; } else { diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 72704cac2d..38c7e691e8 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -1195,6 +1195,26 @@ void __KernelWaitCurThread(WaitType type, SceUID waitID, u32 waitValue, u32 time // TODO: Remove thread from Ready queue? } +void __KernelWaitCallbacksCurThread(WaitType type, SceUID waitID, u32 waitValue, u32 timeoutPtr) +{ + if (!dispatchEnabled) + { + WARN_LOG_REPORT(HLE, "Ignoring wait, dispatching disabled... right thing to do?"); + return; + } + + Thread *thread = __GetCurrentThread(); + thread->nt.waitID = waitID; + thread->nt.waitType = type; + __KernelChangeThreadState(thread, THREADSTATUS_WAIT); + // 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; diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index ba5532a0e4..e26536c7e9 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -155,6 +155,7 @@ u32 __KernelGetWaitTimeoutPtr(SceUID threadID, u32 &error); SceUID __KernelGetWaitID(SceUID threadID, WaitType type, u32 &error); SceUID __KernelGetCurrentCallbackID(SceUID threadID, u32 &error); void __KernelWaitCurThread(WaitType type, SceUID waitId, u32 waitValue, u32 timeoutPtr, bool processCallbacks, const char *reason); +void __KernelWaitCallbacksCurThread(WaitType type, SceUID waitID, u32 waitValue, u32 timeoutPtr); void __KernelReSchedule(const char *reason = "no reason"); void __KernelReSchedule(bool doCallbacks, const char *reason);