Files
ppsspp/Core/HLE/sceKernelMutex.cpp
T
Henrik RydgårdandClaude Opus 5.5 fb9ac8397e Threads: Wait timeouts work like hardware's, one rule for all of them
Every wait with a timeout behaves the same 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
WAIT_TIMEOUT at once, without yielding or writing the timeout back. That's
usual for 0us, half the time for 1us, and rare after; AllocateVpl does more
first. Otherwise it ends max(t, 205us) + ~35us after the call. Each object
had its own guess (24/245, 25/250, 20/250 and so on), and only MsgPipe had
the immediate case.

__KernelWaitTimesOutAtOnce() and __KernelWaitTimeoutUs() now do it for
semaphores, event flags, mutexes, lwmutexes, mbx, msgpipes, fpl, vpl and
WaitThreadEnd. The latency past the deadline isn't counted in the time
left written back.

Outcomes that hardware decides by the clock's phase (these, and
sceKernelDelayThread returning at once) go with the likelier one. Ones
between 50% and certain are instead spread evenly over calls, so a polling
loop can't lock into never yielding (sceKernelThread section version 7).
This replaces the pseudo-random choice for delays.

Also adds threads/scheduling/readyqueue, which already passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-30 09:06:30 -06:00

1017 lines
33 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 <map>
#include <unordered_map>
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/Serialize/SerializeMap.h"
#include "Core/MemMapHelpers.h"
#include "Core/HLE/HLE.h"
#include "Core/Core.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/MIPS/MIPS.h"
#include "Core/CoreTiming.h"
#include "Core/Reporting.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelMutex.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/KernelWaitHelpers.h"
#define PSP_MUTEX_ATTR_FIFO 0
#define PSP_MUTEX_ATTR_PRIORITY 0x100
#define PSP_MUTEX_ATTR_ALLOW_RECURSIVE 0x200
#define PSP_MUTEX_ATTR_KNOWN (PSP_MUTEX_ATTR_PRIORITY | PSP_MUTEX_ATTR_ALLOW_RECURSIVE)
// NativeMutex/PSPMutex itself now live in sceKernelMutex.h - see the comment on the class there
// for why.
void PSPMutex::DoState(PointerWrap &p) {
auto s = p.Section("Mutex", 1);
if (!s)
return;
Do(p, nm);
SceUID dv = 0;
Do(p, waitingThreads, dv);
Do(p, pausedWaits);
}
struct NativeLwMutexWorkarea
{
s32_le lockLevel;
SceUID_le lockThread;
u32_le attr;
s32_le numWaitThreads;
SceUID_le uid;
s32_le pad[3];
void init()
{
memset(this, 0, sizeof(NativeLwMutexWorkarea));
}
void clear()
{
lockLevel = 0;
lockThread = -1;
uid = -1;
}
};
struct NativeLwMutex
{
SceSize_le size;
char name[KERNELOBJECT_MAX_NAME_LENGTH + 1];
SceUInt_le attr;
SceUID_le uid;
PSPPointer<NativeLwMutexWorkarea> workarea;
s32_le initialCount;
// Not kept up to date.
s32_le currentCount;
// Not kept up to date.
SceUID_le lockThread;
// Not kept up to date.
s32_le numWaitThreads;
};
struct LwMutex : public KernelObject
{
const char *GetName() override { return nm.name; }
const char *GetTypeName() override { return GetStaticTypeName(); }
static const char *GetStaticTypeName() { return "LwMutex"; }
static u32 GetMissingErrorCode() { return SCE_LWMUTEX_ERROR_NO_SUCH_LWMUTEX; }
static int GetStaticIDType() { return SCE_KERNEL_TMID_LwMutex; }
int GetIDType() const override { return SCE_KERNEL_TMID_LwMutex; }
void DoState(PointerWrap &p) override
{
auto s = p.Section("LwMutex", 1);
if (!s)
return;
Do(p, nm);
SceUID dv = 0;
Do(p, waitingThreads, dv);
Do(p, pausedWaits);
}
NativeLwMutex nm;
std::vector<SceUID> waitingThreads;
// Key is the callback id it was for, or if no callback, the thread id.
std::map<SceUID, u64> pausedWaits;
};
static int mutexWaitTimer = -1;
static int lwMutexWaitTimer = -1;
// Thread -> Mutex locks for thread end.
typedef std::unordered_multimap<SceUID, SceUID> MutexMap;
static MutexMap mutexHeldLocks;
void __KernelMutexBeginCallback(SceUID threadID, SceUID prevCallbackId);
void __KernelMutexEndCallback(SceUID threadID, SceUID prevCallbackId);
void __KernelLwMutexBeginCallback(SceUID threadID, SceUID prevCallbackId);
void __KernelLwMutexEndCallback(SceUID threadID, SceUID prevCallbackId);
void __KernelMutexInit()
{
mutexWaitTimer = CoreTiming::RegisterEvent("MutexTimeout", __KernelMutexTimeout);
lwMutexWaitTimer = CoreTiming::RegisterEvent("LwMutexTimeout", __KernelLwMutexTimeout);
__KernelListenThreadEnd(&__KernelMutexThreadEnd);
__KernelRegisterWaitTypeFuncs(WAITTYPE_MUTEX, __KernelMutexBeginCallback, __KernelMutexEndCallback);
__KernelRegisterWaitTypeFuncs(WAITTYPE_LWMUTEX, __KernelLwMutexBeginCallback, __KernelLwMutexEndCallback);
}
void __KernelMutexDoState(PointerWrap &p)
{
auto s = p.Section("sceKernelMutex", 1);
if (!s)
return;
Do(p, mutexWaitTimer);
CoreTiming::RestoreRegisterEvent(mutexWaitTimer, "MutexTimeout", __KernelMutexTimeout);
Do(p, lwMutexWaitTimer);
CoreTiming::RestoreRegisterEvent(lwMutexWaitTimer, "LwMutexTimeout", __KernelLwMutexTimeout);
Do(p, mutexHeldLocks);
}
KernelObject *__KernelMutexObject()
{
return new PSPMutex;
}
KernelObject *__KernelLwMutexObject()
{
return new LwMutex;
}
void __KernelMutexShutdown()
{
mutexHeldLocks.clear();
}
static void __KernelMutexAcquireLock(PSPMutex *mutex, int count, SceUID thread) {
#if defined(_DEBUG)
auto locked = mutexHeldLocks.equal_range(thread);
for (MutexMap::iterator iter = locked.first; iter != locked.second; ++iter)
_dbg_assert_msg_((*iter).second != mutex->GetUID(), "Thread %d / mutex %d wasn't removed from mutexHeldLocks properly.", thread, mutex->GetUID());
#endif
mutexHeldLocks.emplace(thread, mutex->GetUID());
mutex->nm.lockLevel = count;
mutex->nm.lockThread = thread;
}
static void __KernelMutexAcquireLock(PSPMutex *mutex, int count) {
__KernelMutexAcquireLock(mutex, count, __KernelGetCurThread());
}
static void __KernelMutexEraseLock(PSPMutex *mutex) {
if (mutex->nm.lockThread != -1)
{
SceUID id = mutex->GetUID();
std::pair<MutexMap::iterator, MutexMap::iterator> locked = mutexHeldLocks.equal_range(mutex->nm.lockThread);
for (MutexMap::iterator iter = locked.first; iter != locked.second; ++iter)
{
if ((*iter).second == id)
{
mutexHeldLocks.erase(iter);
break;
}
}
}
mutex->nm.lockThread = -1;
}
static std::vector<SceUID>::iterator __KernelMutexFindPriority(std::vector<SceUID> &waiting)
{
_dbg_assert_msg_(!waiting.empty(), "__KernelMutexFindPriority: Trying to find best of no threads.");
std::vector<SceUID>::iterator iter, end, best = waiting.end();
u32 best_prio = 0xFFFFFFFF;
for (iter = waiting.begin(), end = waiting.end(); iter != end; ++iter)
{
u32 iter_prio = __KernelGetThreadPrio(*iter);
if (iter_prio < best_prio)
{
best = iter;
best_prio = iter_prio;
}
}
_dbg_assert_msg_(best != waiting.end(), "__KernelMutexFindPriority: Returning invalid best thread.");
return best;
}
static bool __KernelUnlockMutexForThread(PSPMutex *mutex, SceUID threadID, u32 &error, int result) {
if (!HLEKernel::VerifyWait(threadID, WAITTYPE_MUTEX, 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);
}
u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error);
HLEKernel::WriteRemainingTimeout(mutexWaitTimer, threadID, timeoutPtr);
__KernelResumeThreadFromWait(threadID, result);
return true;
}
static bool __KernelUnlockMutexForThreadCheck(PSPMutex *mutex, SceUID threadID, u32 &error, int result, bool &wokeThreads) {
if (mutex->nm.lockThread == -1 && __KernelUnlockMutexForThread(mutex, threadID, error, 0))
return true;
return false;
}
void __KernelMutexBeginCallback(SceUID threadID, SceUID prevCallbackId)
{
auto result = HLEKernel::WaitBeginCallback<PSPMutex, WAITTYPE_MUTEX, SceUID>(threadID, prevCallbackId, mutexWaitTimer);
if (result == HLEKernel::WAIT_CB_SUCCESS)
DEBUG_LOG(Log::sceKernel, "sceKernelLockMutexCB: Suspending lock wait for callback");
else
WARN_LOG_REPORT(Log::sceKernel, "sceKernelLockMutexCB: beginning callback with bad wait id?");
}
void __KernelMutexEndCallback(SceUID threadID, SceUID prevCallbackId)
{
auto result = HLEKernel::WaitEndCallback<PSPMutex, WAITTYPE_MUTEX, SceUID>(threadID, prevCallbackId, mutexWaitTimer, __KernelUnlockMutexForThreadCheck);
if (result == HLEKernel::WAIT_CB_RESUMED_WAIT)
DEBUG_LOG(Log::sceKernel, "sceKernelLockMutexCB: Resuming lock wait for callback");
}
int sceKernelCreateMutex(const char *name, u32 attr, int initialCount, u32 optionsPtr) {
if (!name)
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ERROR, "invalid name");
if (attr & ~0xBFF)
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter %08x", attr);
if (initialCount < 0)
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_COUNT, "illegal initial count");
if ((attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) == 0 && initialCount > 1)
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_COUNT, "illegal non-recursive count");
PSPMutex *mutex = new PSPMutex();
SceUID id = kernelObjects.Create(mutex);
mutex->nm.size = sizeof(mutex->nm);
strncpy(mutex->nm.name, name, KERNELOBJECT_MAX_NAME_LENGTH);
mutex->nm.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0;
mutex->nm.attr = attr;
mutex->nm.initialCount = initialCount;
if (initialCount == 0) {
mutex->nm.lockLevel = 0;
mutex->nm.lockThread = -1;
} else {
__KernelMutexAcquireLock(mutex, initialCount);
}
if (optionsPtr != 0) {
if (Memory::IsValid4AlignedAddress(optionsPtr)) {
u32 size = Memory::ReadUnchecked_U32(optionsPtr);
if (size > 4)
WARN_LOG_REPORT(Log::sceKernel, "sceKernelCreateMutex(%s) unsupported options parameter, size = %d", name, size);
} else {
Core_MemoryExceptionHLE(currentMIPS, optionsPtr, 4, MemoryExceptionType::HLE_READ);
}
}
if ((attr & ~PSP_MUTEX_ATTR_KNOWN) != 0)
WARN_LOG_REPORT(Log::sceKernel, "sceKernelCreateMutex(%s) unsupported attr parameter: %08x", name, attr);
return hleLogDebug(Log::sceKernel, id);
}
int sceKernelDeleteMutex(SceUID id) {
u32 error;
PSPMutex *mutex = kernelObjects.Get<PSPMutex>(id, error);
if (!mutex) {
return hleLogError(Log::sceKernel, error);
} else {
DEBUG_LOG(Log::sceKernel, "sceKernelDeleteMutex(%i)", id);
bool wokeThreads = false;
std::vector<SceUID>::iterator iter, end;
for (iter = mutex->waitingThreads.begin(), end = mutex->waitingThreads.end(); iter != end; ++iter)
wokeThreads |= __KernelUnlockMutexForThread(mutex, *iter, error, SCE_KERNEL_ERROR_WAIT_DELETE);
if (mutex->nm.lockThread != -1)
__KernelMutexEraseLock(mutex);
mutex->waitingThreads.clear();
if (wokeThreads)
hleReSchedule("mutex deleted");
return hleLogDebug(Log::sceKernel, kernelObjects.Destroy<PSPMutex>(id));
}
}
static bool __KernelLockMutexCheck(PSPMutex *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 = SCE_MUTEX_ERROR_LOCK_OVERFLOW;
// Only a recursive mutex can re-lock.
else if (mutex->nm.lockThread == __KernelGetCurThread()) {
if (mutexIsRecursive)
return true;
error = SCE_MUTEX_ERROR_ALREADY_LOCKED;
}
// Otherwise it would lock or wait.
else if (mutex->nm.lockLevel == 0)
return true;
return false;
}
static bool __KernelLockMutex(PSPMutex *mutex, int count, u32 &error) {
if (!__KernelLockMutexCheck(mutex, count, error))
return false;
if (mutex->nm.lockLevel == 0)
{
__KernelMutexAcquireLock(mutex, count);
// Nobody had it locked - no need to block
return true;
}
if (mutex->nm.lockThread == __KernelGetCurThread())
{
// __KernelLockMutexCheck() would've returned an error, so this must be recursive.
mutex->nm.lockLevel += count;
return true;
}
return false;
}
static bool __KernelUnlockMutex(PSPMutex *mutex, u32 &error) {
__KernelMutexEraseLock(mutex);
bool wokeThreads = false;
std::vector<SceUID>::iterator iter;
while (!wokeThreads && !mutex->waitingThreads.empty())
{
if ((mutex->nm.attr & PSP_MUTEX_ATTR_PRIORITY) != 0)
iter = __KernelMutexFindPriority(mutex->waitingThreads);
else
iter = mutex->waitingThreads.begin();
wokeThreads |= __KernelUnlockMutexForThread(mutex, *iter, error, 0);
mutex->waitingThreads.erase(iter);
}
if (!wokeThreads)
mutex->nm.lockThread = -1;
return wokeThreads;
}
void __KernelMutexTimeout(u64 userdata, int cyclesLate) {
SceUID threadID = (SceUID)userdata;
HLEKernel::WaitExecTimeout<PSPMutex, WAITTYPE_MUTEX>(threadID);
}
void __KernelMutexThreadEnd(SceUID threadID) {
u32 error;
// If it was waiting on the mutex, it should finish now.
SceUID waitingMutexID = __KernelGetWaitID(threadID, WAITTYPE_MUTEX, error);
if (waitingMutexID)
{
PSPMutex *mutex = kernelObjects.Get<PSPMutex>(waitingMutexID, error);
if (mutex)
HLEKernel::RemoveWaitingThread(mutex->waitingThreads, threadID);
}
// Unlock all mutexes the thread had locked.
std::pair<MutexMap::iterator, MutexMap::iterator> locked = mutexHeldLocks.equal_range(threadID);
for (MutexMap::iterator iter = locked.first; iter != locked.second; )
{
// Need to increment early so erase() doesn't invalidate.
SceUID mutexID = (*iter++).second;
PSPMutex *mutex = kernelObjects.Get<PSPMutex>(mutexID, error);
if (mutex)
{
mutex->nm.lockLevel = 0;
__KernelUnlockMutex(mutex, error);
}
}
}
// The timeoutPtr is assumed to be checked by the caller to either be 0 or valid.
static void __KernelWaitMutex(PSPMutex *mutex, u32 timeoutPtr) {
_dbg_assert_(mutexWaitTimer != -1); // this could only come from extremely old savestates.
if (timeoutPtr == 0 || mutexWaitTimer == -1)
return;
u32 micro = Memory::ReadUnchecked_U32(timeoutPtr);
// This should call __KernelMutexTimeout() later, unless we cancel it.
CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), mutexWaitTimer, __KernelGetCurThread());
}
int sceKernelCancelMutex(SceUID uid, int count, u32 numWaitThreadsPtr) {
u32 error;
PSPMutex *mutex = kernelObjects.Get<PSPMutex>(uid, error);
if (!mutex) {
return hleLogError(Log::sceKernel, error);
} else {
bool lockable = count <= 0 || __KernelLockMutexCheck(mutex, count, error);
if (!lockable)
{
// May still be okay. As long as the count/etc. are valid.
if (error != 0 && error != SCE_MUTEX_ERROR_LOCK_OVERFLOW && error != SCE_MUTEX_ERROR_ALREADY_LOCKED) {
return hleLogWarning(Log::sceKernel, error, "invalid count");
}
}
DEBUG_LOG(Log::sceKernel, "sceKernelCancelMutex(%i, %d, %08x)", uid, count, numWaitThreadsPtr);
// Remove threads no longer waiting on this first (so the numWaitThreads value is correct.)
HLEKernel::CleanupWaitingThreads(WAITTYPE_MUTEX, uid, mutex->waitingThreads);
if (Memory::IsValid4AlignedAddress(numWaitThreadsPtr))
Memory::WriteUnchecked_U32((u32)mutex->waitingThreads.size(), numWaitThreadsPtr);
bool wokeThreads = false;
for (auto iter = mutex->waitingThreads.begin(), end = mutex->waitingThreads.end(); iter != end; ++iter)
wokeThreads |= __KernelUnlockMutexForThread(mutex, *iter, error, SCE_KERNEL_ERROR_WAIT_CANCEL);
if (mutex->nm.lockThread != -1)
__KernelMutexEraseLock(mutex);
mutex->waitingThreads.clear();
if (count <= 0)
{
mutex->nm.lockLevel = 0;
mutex->nm.lockThread = -1;
}
else
__KernelMutexAcquireLock(mutex, count);
if (wokeThreads)
hleReSchedule("mutex canceled");
return hleNoLog(0);
}
}
int sceKernelLockMutex(SceUID id, int count, u32 timeoutPtr) {
// Tekken 6 hack: Let's avoid the unnecessary logspam. It does this on hardware too.
// This ID is always invalid.
if (id == 0x80020001 && timeoutPtr == 0) {
return hleNoLog(0);
}
u32 error;
PSPMutex *mutex = kernelObjects.Get<PSPMutex>(id, error);
if (__KernelLockMutex(mutex, count, error))
return hleLogDebug(Log::sceKernel, 0);
else if (error) {
if (error == SCE_MUTEX_ERROR_ALREADY_LOCKED) {
// Benign it seems.
return hleLogDebug(Log::sceKernel, error);
} else {
return hleLogError(Log::sceKernel, error);
}
}
if (__KernelWaitTimesOutAtOnce(timeoutPtr))
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once");
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())
mutex->waitingThreads.push_back(threadID);
__KernelWaitMutex(mutex, timeoutPtr);
__KernelWaitCurThread(WAITTYPE_MUTEX, id, count, timeoutPtr, false, "mutex waited");
// Return value will be overwritten by wait.
return hleLogDebug(Log::sceKernel, 0);
}
int sceKernelLockMutexCB(SceUID id, int count, u32 timeoutPtr) {
u32 error;
PSPMutex *mutex = kernelObjects.Get<PSPMutex>(id, error);
if (!__KernelLockMutexCheck(mutex, count, error))
{
if (error)
return hleLogError(Log::sceKernel, error);
if (__KernelWaitTimesOutAtOnce(timeoutPtr))
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once");
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())
mutex->waitingThreads.push_back(threadID);
__KernelWaitMutex(mutex, timeoutPtr);
__KernelWaitCurThread(WAITTYPE_MUTEX, id, count, timeoutPtr, true, "mutex waited");
// Return value will be overwritten by wait.
return hleLogDebug(Log::sceKernel, 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 hleLogDebug(Log::sceKernel, 0);
}
}
int sceKernelTryLockMutex(SceUID id, int count) {
u32 error;
PSPMutex *mutex = kernelObjects.Get<PSPMutex>(id, error);
if (__KernelLockMutex(mutex, count, error))
return hleLogDebug(Log::sceKernel, 0);
else if (error)
return hleLogError(Log::sceKernel, error);
else
return hleLogDebug(Log::sceKernel, SCE_MUTEX_ERROR_TRYLOCK_FAILED);
}
int sceKernelUnlockMutex(SceUID id, int count) {
// Tekken 6 hack: Let's avoid the unnecessary logspam. It does this on hardware too.
// This ID is always invalid.
if (id == 0x80020001) {
return hleNoLog(0);
}
u32 error;
PSPMutex *mutex = kernelObjects.Get<PSPMutex>(id, error);
if (error)
return hleLogError(Log::sceKernel, error);
if (count <= 0)
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_COUNT);
if ((mutex->nm.attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) == 0 && count > 1)
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_COUNT);
if (mutex->nm.lockLevel == 0 || mutex->nm.lockThread != __KernelGetCurThread())
return hleLogDebug(Log::sceKernel, SCE_MUTEX_ERROR_NOT_LOCKED);
if (mutex->nm.lockLevel < count)
return hleLogWarning(Log::sceKernel, SCE_MUTEX_ERROR_UNLOCK_UNDERFLOW);
// To log before the reschedule.
DEBUG_LOG(Log::sceKernel, "0=sceKernelUnlockMutex(%i, %i)", id, count);
mutex->nm.lockLevel -= count;
if (mutex->nm.lockLevel == 0)
{
if (__KernelUnlockMutex(mutex, error))
hleReSchedule("mutex unlocked");
}
return hleNoLog(0);
}
int sceKernelReferMutexStatus(SceUID id, u32 infoAddr) {
u32 error;
PSPMutex *m = kernelObjects.Get<PSPMutex>(id, error);
if (!m) {
return hleLogError(Log::sceKernel, error, "invalid mutex id");
}
// Should we crash the thread somehow?
auto info = PSPPointer<NativeMutex>::Create(infoAddr);
if (!info.IsValid())
return hleLogError(Log::sceKernel, -1, "invalid pointer");
// Don't write if the size is 0. Anything else is A-OK, though, apparently.
if (info->size != 0) {
HLEKernel::CleanupWaitingThreads(WAITTYPE_MUTEX, id, m->waitingThreads);
m->nm.numWaitThreads = (int) m->waitingThreads.size();
*info = m->nm;
info.NotifyWrite("MutexStatus");
}
return hleLogDebug(Log::sceKernel, 0);
}
int sceKernelCreateLwMutex(u32 workareaPtr, const char *name, u32 attr, int initialCount, u32 optionsPtr) {
if (!name) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ERROR, "invalid name");
}
if (attr >= 0x400) {
return hleReportError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr);
}
if (initialCount < 0) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_COUNT);
}
if ((attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) == 0 && initialCount > 1) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_COUNT);
}
LwMutex *mutex = new LwMutex();
SceUID id = kernelObjects.Create(mutex);
mutex->nm.size = sizeof(mutex->nm);
strncpy(mutex->nm.name, name, KERNELOBJECT_MAX_NAME_LENGTH);
mutex->nm.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0;
mutex->nm.attr = attr;
mutex->nm.uid = id;
mutex->nm.workarea = workareaPtr;
mutex->nm.initialCount = initialCount;
auto workarea = PSPPointer<NativeLwMutexWorkarea>::Create(workareaPtr);
workarea->init();
workarea->lockLevel = initialCount;
if (initialCount == 0)
workarea->lockThread = 0;
else
workarea->lockThread = __KernelGetCurThread();
workarea->attr = attr;
workarea->uid = id;
if (optionsPtr != 0) {
if (Memory::IsValid4AlignedAddress(optionsPtr)) {
u32 size = Memory::ReadUnchecked_U32(optionsPtr);
if (size > 4)
WARN_LOG_REPORT(Log::sceKernel, "sceKernelCreateLwMutex(%s) unsupported options parameter, size = %d", name, size);
} else {
Core_MemoryExceptionHLE(currentMIPS, optionsPtr, 4, MemoryExceptionType::HLE_READ);
}
}
if ((attr & ~PSP_MUTEX_ATTR_KNOWN) != 0)
WARN_LOG_REPORT(Log::sceKernel, "sceKernelCreateLwMutex(%s) unsupported attr parameter: %08x", name, attr);
return hleLogDebug(Log::sceKernel, 0);
}
template <typename T>
bool __KernelUnlockLwMutexForThread(LwMutex *mutex, T workarea, SceUID threadID, u32 &error, int result) {
if (!HLEKernel::VerifyWait(threadID, WAITTYPE_LWMUTEX, mutex->GetUID()))
return false;
// If result is an error code, we're just letting it go.
if (result == 0) {
workarea->lockLevel = (int) __KernelGetWaitValue(threadID, error);
workarea->lockThread = threadID;
}
const u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error);
HLEKernel::WriteRemainingTimeout(lwMutexWaitTimer, threadID, timeoutPtr);
__KernelResumeThreadFromWait(threadID, result);
return true;
}
int sceKernelDeleteLwMutex(u32 workareaPtr) {
DEBUG_LOG(Log::sceKernel, "sceKernelDeleteLwMutex(%08x)", workareaPtr);
if (!workareaPtr || !Memory::IsValidAddress(workareaPtr))
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDR);
auto workarea = PSPPointer<NativeLwMutexWorkarea>::Create(workareaPtr);
u32 error;
LwMutex *mutex = kernelObjects.Get<LwMutex>(workarea->uid, error);
if (!mutex) {
return hleLogError(Log::sceKernel, error);
} else {
bool wokeThreads = false;
std::vector<SceUID>::iterator iter, end;
for (iter = mutex->waitingThreads.begin(), end = mutex->waitingThreads.end(); iter != end; ++iter)
wokeThreads |= __KernelUnlockLwMutexForThread(mutex, workarea, *iter, error, SCE_KERNEL_ERROR_WAIT_DELETE);
mutex->waitingThreads.clear();
workarea->clear();
if (wokeThreads)
hleReSchedule("lwmutex deleted");
return hleLogDebugOrError(Log::sceKernel, kernelObjects.Destroy<LwMutex>(mutex->GetUID()));
}
}
static bool __KernelLockLwMutex(NativeLwMutexWorkarea *workarea, int count, u32 &error) {
if (!error)
{
if (count <= 0)
error = SCE_KERNEL_ERROR_ILLEGAL_COUNT;
else if (count > 1 && !(workarea->attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE))
error = SCE_KERNEL_ERROR_ILLEGAL_COUNT;
// Two positive ints will always overflow to negative.
else if (count + workarea->lockLevel < 0)
error = SCE_LWMUTEX_ERROR_LOCK_OVERFLOW;
else if (workarea->uid == -1)
error = SCE_LWMUTEX_ERROR_NO_SUCH_LWMUTEX;
}
if (error)
return false;
if (workarea->lockLevel == 0)
{
if (workarea->lockThread != 0)
{
// Validate that it actually exists so we can return an error if not.
kernelObjects.Get<LwMutex>(workarea->uid, error);
if (error)
return false;
}
workarea->lockLevel = count;
workarea->lockThread = __KernelGetCurThread();
return true;
}
if (workarea->lockThread == __KernelGetCurThread())
{
// Recursive mutex, let's just increase the lock count and keep going
if (workarea->attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE)
{
workarea->lockLevel += count;
return true;
}
else
{
error = SCE_LWMUTEX_ERROR_ALREADY_LOCKED;
return false;
}
}
return false;
}
template <typename T>
bool __KernelUnlockLwMutex(T workarea, u32 &error) {
LwMutex *mutex = kernelObjects.Get<LwMutex>(workarea->uid, error);
if (error) {
workarea->lockThread = 0;
return false;
}
bool wokeThreads = false;
std::vector<SceUID>::iterator iter;
while (!wokeThreads && !mutex->waitingThreads.empty())
{
if ((mutex->nm.attr & PSP_MUTEX_ATTR_PRIORITY) != 0)
iter = __KernelMutexFindPriority(mutex->waitingThreads);
else
iter = mutex->waitingThreads.begin();
wokeThreads |= __KernelUnlockLwMutexForThread(mutex, workarea, *iter, error, 0);
mutex->waitingThreads.erase(iter);
}
if (!wokeThreads)
workarea->lockThread = 0;
return wokeThreads;
}
void __KernelLwMutexTimeout(u64 userdata, int cyclesLate)
{
SceUID threadID = (SceUID)userdata;
HLEKernel::WaitExecTimeout<LwMutex, WAITTYPE_LWMUTEX>(threadID);
}
// timeoutPtr is assumed to be checked by the caller to either be 0 or valid.
static void __KernelWaitLwMutex(LwMutex *mutex, u32 timeoutPtr) {
if (timeoutPtr == 0 || lwMutexWaitTimer == -1)
return;
u32 micro = Memory::ReadUnchecked_U32(timeoutPtr);
// This should call __KernelLwMutexTimeout() later, unless we cancel it.
CoreTiming::ScheduleEvent(usToCycles(__KernelWaitTimeoutUs(micro)), lwMutexWaitTimer, __KernelGetCurThread());
}
static bool __KernelUnlockLwMutexForThreadCheck(LwMutex *mutex, SceUID threadID, u32 &error, int result, bool &wokeThreads)
{
// The lock state lives in the workarea, nm.lockThread is only refreshed when referred.
if (mutex->nm.workarea->lockLevel == 0 && __KernelUnlockLwMutexForThread(mutex, mutex->nm.workarea, threadID, error, 0))
return true;
return false;
}
void __KernelLwMutexBeginCallback(SceUID threadID, SceUID prevCallbackId)
{
auto result = HLEKernel::WaitBeginCallback<LwMutex, WAITTYPE_LWMUTEX, SceUID>(threadID, prevCallbackId, lwMutexWaitTimer);
if (result == HLEKernel::WAIT_CB_SUCCESS)
DEBUG_LOG(Log::sceKernel, "sceKernelLockLwMutexCB: Suspending lock wait for callback");
else
WARN_LOG_REPORT(Log::sceKernel, "sceKernelLockLwMutexCB: beginning callback with bad wait id?");
}
void __KernelLwMutexEndCallback(SceUID threadID, SceUID prevCallbackId)
{
auto result = HLEKernel::WaitEndCallback<LwMutex, WAITTYPE_LWMUTEX, SceUID>(threadID, prevCallbackId, lwMutexWaitTimer, __KernelUnlockLwMutexForThreadCheck);
if (result == HLEKernel::WAIT_CB_RESUMED_WAIT)
DEBUG_LOG(Log::sceKernel, "sceKernelLockLwMutexCB: Resuming lock wait for callback");
}
int sceKernelTryLockLwMutex(u32 workareaPtr, int count) {
if (!Memory::IsValidAddress(workareaPtr)) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ACCESS_ERROR, "Bad workarea pointer for LwMutex");
}
auto workarea = PSPPointer<NativeLwMutexWorkarea>::Create(workareaPtr);
hleEatCycles(24);
u32 error = 0;
if (__KernelLockLwMutex(workarea, count, error))
return hleLogDebug(Log::sceKernel, 0);
else if (error)
// Unlike sceKernelTryLockLwMutex_600, this always returns the same error.
return hleLogDebug(Log::sceKernel, SCE_MUTEX_ERROR_TRYLOCK_FAILED);
else
return hleLogDebug(Log::sceKernel, SCE_MUTEX_ERROR_TRYLOCK_FAILED);
}
int sceKernelTryLockLwMutex_600(u32 workareaPtr, int count) {
if (!Memory::IsValidAddress(workareaPtr)) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ACCESS_ERROR, "Bad workarea pointer for LwMutex");
}
auto workarea = PSPPointer<NativeLwMutexWorkarea>::Create(workareaPtr);
hleEatCycles(24);
u32 error = 0;
if (__KernelLockLwMutex(workarea, count, error))
return hleLogDebug(Log::sceKernel, 0);
else if (error)
return hleLogDebug(Log::sceKernel, error);
else
return hleLogDebug(Log::sceKernel, SCE_LWMUTEX_ERROR_TRYLOCK_FAILED);
}
int sceKernelLockLwMutex(u32 workareaPtr, int count, u32 timeoutPtr) {
if (!Memory::IsValidAddress(workareaPtr)) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ACCESS_ERROR, "Bad workarea pointer for LwMutex");
}
auto workarea = PSPPointer<NativeLwMutexWorkarea>::Create(workareaPtr);
hleEatCycles(48);
u32 error = 0;
if (__KernelLockLwMutex(workarea, count, error)) {
return hleLogVerbose(Log::sceKernel, 0);
} else if (error) {
return hleLogVerbose(Log::sceKernel, error);
} else {
LwMutex *mutex = kernelObjects.Get<LwMutex>(workarea->uid, error);
if (!mutex) {
return hleLogError(Log::sceKernel, error);
} else {
if (__KernelWaitTimesOutAtOnce(timeoutPtr))
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once");
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())
mutex->waitingThreads.push_back(threadID);
__KernelWaitLwMutex(mutex, timeoutPtr);
__KernelWaitCurThread(WAITTYPE_LWMUTEX, workarea->uid, count, timeoutPtr, false, "lwmutex waited");
// Return value will be overwritten by wait.
return hleLogVerbose(Log::sceKernel, 0);
}
}
}
int sceKernelLockLwMutexCB(u32 workareaPtr, int count, u32 timeoutPtr) {
if (!Memory::IsValidAddress(workareaPtr)) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ACCESS_ERROR, "Bad workarea pointer for LwMutex");
}
auto workarea = PSPPointer<NativeLwMutexWorkarea>::Create(workareaPtr);
hleEatCycles(48);
u32 error = 0;
if (__KernelLockLwMutex(workarea, count, error)) {
return hleLogVerbose(Log::sceKernel, 0);
} else if (error) {
return hleLogVerbose(Log::sceKernel, error);
} else {
LwMutex *mutex = kernelObjects.Get<LwMutex>(workarea->uid, error);
if (!mutex) {
return hleLogError(Log::sceKernel, error);
} else {
if (__KernelWaitTimesOutAtOnce(timeoutPtr))
return hleLogDebug(Log::sceKernel, SCE_KERNEL_ERROR_WAIT_TIMEOUT, "timed out at once");
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())
mutex->waitingThreads.push_back(threadID);
__KernelWaitLwMutex(mutex, timeoutPtr);
__KernelWaitCurThread(WAITTYPE_LWMUTEX, workarea->uid, count, timeoutPtr, true, "lwmutex cb waited");
// Return value will be overwritten by wait.
return hleLogVerbose(Log::sceKernel, 0);
}
}
}
int sceKernelUnlockLwMutex(u32 workareaPtr, int count) {
if (!Memory::IsValidAddress(workareaPtr)) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ACCESS_ERROR, "Bad workarea pointer for LwMutex");
}
auto workarea = PSPPointer<NativeLwMutexWorkarea>::Create(workareaPtr);
hleEatCycles(28);
if (workarea->uid == -1)
return hleLogError(Log::sceKernel, SCE_LWMUTEX_ERROR_NO_SUCH_LWMUTEX);
else if (count <= 0)
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_COUNT);
else if ((workarea->attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) == 0 && count > 1)
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_COUNT);
else if (workarea->lockLevel == 0 || workarea->lockThread != __KernelGetCurThread())
return hleLogDebug(Log::sceKernel, SCE_LWMUTEX_ERROR_NOT_LOCKED);
else if (workarea->lockLevel < count)
return hleLogDebug(Log::sceKernel, SCE_LWMUTEX_ERROR_UNLOCK_UNDERFLOW);
VERBOSE_LOG(Log::sceKernel, "sceKernelUnlockLwMutex(%08x, %i)", workareaPtr, count);
workarea->lockLevel -= count;
if (workarea->lockLevel == 0) {
u32 error;
if (__KernelUnlockLwMutex(workarea, error))
hleReSchedule("lwmutex unlocked");
}
return hleNoLog(0);
}
// Logs like a HLE function
static int __KernelReferLwMutexStatus(SceUID uid, u32 infoPtr) {
u32 error;
LwMutex *m = kernelObjects.Get<LwMutex>(uid, error);
if (!m) {
return hleLogError(Log::sceKernel, error, "invalid id");
}
// Should we crash the thread somehow?
auto info = PSPPointer<NativeLwMutex>::Create(infoPtr);
if (!info.IsValid()) {
return hleLogError(Log::sceKernel, -1, "invalid pointer");
}
if (info->size != 0) {
auto workarea = m->nm.workarea;
HLEKernel::CleanupWaitingThreads(WAITTYPE_LWMUTEX, uid, m->waitingThreads);
// Refresh and write
m->nm.currentCount = workarea->lockLevel;
m->nm.lockThread = workarea->lockThread == 0 ? SceUID_le(-1) : workarea->lockThread;
m->nm.numWaitThreads = (int) m->waitingThreads.size();
*info = m->nm;
info.NotifyWrite("LwMutexStatus");
}
return hleLogDebug(Log::sceKernel, 0);
}
int sceKernelReferLwMutexStatusByID(SceUID uid, u32 infoPtr) {
return __KernelReferLwMutexStatus(uid, infoPtr);
}
int sceKernelReferLwMutexStatus(u32 workareaPtr, u32 infoPtr) {
auto workarea = PSPPointer<NativeLwMutexWorkarea>::Create(workareaPtr);
if (!workarea.IsValid()) {
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ACCESS_ERROR, "Bad workarea pointer for LwMutex");
}
return __KernelReferLwMutexStatus(workarea->uid, infoPtr);
}