shadPS4/src/core/libraries/kernel/threads/mutex.cpp

465 lines
15 KiB
C++

// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/assert.h"
#include "common/scope_exit.h"
#include "common/types.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/libs.h"
namespace Libraries::Kernel {
static constexpr u32 MUTEX_ADAPTIVE_SPINS = 2000;
static std::mutex MutxStaticLock;
#define THR_MUTEX_INITIALIZER ((PthreadMutex*)NULL)
#define THR_ADAPTIVE_MUTEX_INITIALIZER ((PthreadMutex*)1)
#define THR_MUTEX_DESTROYED ((PthreadMutex*)2)
#define THR_MUTEX_RELTIME (const OrbisKernelTimespec*)-1
#define CPU_SPINWAIT __asm__ volatile("pause")
#define CHECK_AND_INIT_MUTEX \
if (PthreadMutex* m = *mutex; m <= THR_MUTEX_DESTROYED) [[unlikely]] { \
if (m == THR_MUTEX_DESTROYED) { \
return POSIX_EINVAL; \
} \
if (s32 ret = InitStatic(g_curthread, mutex); ret) { \
return ret; \
} \
m = *mutex; \
}
static constexpr PthreadMutexAttr PthreadMutexattrDefault = {
.m_type = PthreadMutexType::ErrorCheck, .m_protocol = PthreadMutexProt::None, .m_ceiling = 0};
static constexpr PthreadMutexAttr PthreadMutexattrAdaptiveDefault = {
.m_type = PthreadMutexType::AdaptiveNp, .m_protocol = PthreadMutexProt::None, .m_ceiling = 0};
using CallocFun = void* (*)(size_t, size_t);
static int MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, const char* name) {
const PthreadMutexAttr* attr;
if (mutex_attr == NULL) {
attr = &PthreadMutexattrDefault;
} else {
attr = mutex_attr;
if (attr->m_type < PthreadMutexType::ErrorCheck || attr->m_type >= PthreadMutexType::Max) {
return POSIX_EINVAL;
}
if (attr->m_protocol > PthreadMutexProt::Protect) {
return POSIX_EINVAL;
}
}
auto* pmutex = new PthreadMutex{};
if (pmutex == nullptr) {
return POSIX_ENOMEM;
}
if (name) {
pmutex->name = name;
} else {
static int MutexId = 0;
pmutex->name = fmt::format("Mutex{}", MutexId++);
}
pmutex->m_flags = PthreadMutexFlags(attr->m_type);
pmutex->m_owner = nullptr;
pmutex->m_count = 0;
pmutex->m_spinloops = 0;
pmutex->m_yieldloops = 0;
pmutex->m_protocol = attr->m_protocol;
if (attr->m_type == PthreadMutexType::AdaptiveNp) {
pmutex->m_spinloops = MUTEX_ADAPTIVE_SPINS;
// pmutex->m_yieldloops = _thr_yieldloops;
}
*mutex = pmutex;
return 0;
}
static int InitStatic(Pthread* thread, PthreadMutexT* mutex) {
std::scoped_lock lk{MutxStaticLock};
if (*mutex == THR_MUTEX_INITIALIZER) {
return MutexInit(mutex, &PthreadMutexattrDefault, nullptr);
} else if (*mutex == THR_ADAPTIVE_MUTEX_INITIALIZER) {
return MutexInit(mutex, &PthreadMutexattrAdaptiveDefault, nullptr);
}
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutex_init(PthreadMutexT* mutex,
const PthreadMutexAttrT* mutex_attr) {
return MutexInit(mutex, mutex_attr ? *mutex_attr : nullptr, nullptr);
}
int PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr,
const char* name) {
return MutexInit(mutex, mutex_attr ? *mutex_attr : nullptr, name);
}
int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex) {
PthreadMutexT m = *mutex;
if (m < THR_MUTEX_DESTROYED) {
return 0;
}
if (m == THR_MUTEX_DESTROYED) {
return POSIX_EINVAL;
}
if (m->m_owner != nullptr) {
return POSIX_EBUSY;
}
*mutex = THR_MUTEX_DESTROYED;
delete m;
return 0;
}
int PthreadMutex::SelfTryLock() {
switch (Type()) {
case PthreadMutexType::ErrorCheck:
case PthreadMutexType::Normal:
return POSIX_EBUSY;
case PthreadMutexType::Recursive: {
/* Increment the lock count: */
if (m_count + 1 > 0) {
m_count++;
return 0;
}
return POSIX_EAGAIN;
}
default:
return POSIX_EINVAL;
}
}
int PthreadMutex::SelfLock(const OrbisKernelTimespec* abstime, u64 usec) {
const auto DoSleep = [&] {
if (abstime == THR_MUTEX_RELTIME) {
std::this_thread::sleep_for(std::chrono::microseconds(usec));
return POSIX_ETIMEDOUT;
} else {
if (abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) {
return POSIX_EINVAL;
} else {
std::this_thread::sleep_until(abstime->TimePoint());
return POSIX_ETIMEDOUT;
}
}
};
switch (Type()) {
case PthreadMutexType::ErrorCheck:
case PthreadMutexType::AdaptiveNp: {
if (abstime) {
return DoSleep();
}
/*
* POSIX specifies that mutexes should return
* EDEADLK if a recursive lock is detected.
*/
UNREACHABLE_MSG("Mutex deadlock occured");
return POSIX_EDEADLK;
}
case PthreadMutexType::Normal: {
/*
* What SS2 define as a 'normal' mutex. Intentionally
* deadlock on attempts to get a lock you already own.
*/
if (abstime) {
return DoSleep();
}
UNREACHABLE_MSG("Mutex deadlock occured");
return 0;
}
case PthreadMutexType::Recursive: {
/* Increment the lock count: */
if (m_count + 1 > 0) {
m_count++;
return 0;
}
return POSIX_EAGAIN;
}
default:
return POSIX_EINVAL;
}
}
int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) {
Pthread* curthread = g_curthread;
if (m_owner == curthread) {
return SelfLock(abstime, usec);
}
int ret = 0;
SCOPE_EXIT {
if (ret == 0) {
curthread->Enqueue(this);
}
};
/*
* For adaptive mutexes, spin for a bit in the expectation
* that if the application requests this mutex type then
* the lock is likely to be released quickly and it is
* faster than entering the kernel
*/
if (m_protocol == PthreadMutexProt::None) [[likely]] {
int count = m_spinloops;
while (count--) {
if (m_lock.try_lock()) {
return 0;
}
CPU_SPINWAIT;
}
count = m_yieldloops;
while (count--) {
std::this_thread::yield();
if (m_lock.try_lock()) {
return 0;
}
}
}
if (abstime == nullptr) {
m_lock.lock();
} else if (abstime != THR_MUTEX_RELTIME &&
(abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)) [[unlikely]] {
ret = POSIX_EINVAL;
} else {
if (THR_MUTEX_RELTIME) {
ret = m_lock.try_lock_for(std::chrono::microseconds(usec)) ? 0 : POSIX_ETIMEDOUT;
} else {
ret = m_lock.try_lock_until(abstime->TimePoint()) ? 0 : POSIX_ETIMEDOUT;
}
}
return ret;
}
int PthreadMutex::TryLock() {
Pthread* curthread = g_curthread;
if (m_owner == curthread) {
return SelfTryLock();
}
const int ret = m_lock.try_lock() ? 0 : POSIX_EBUSY;
if (ret == 0) {
curthread->Enqueue(this);
}
return ret;
}
int PS4_SYSV_ABI posix_pthread_mutex_trylock(PthreadMutexT* mutex) {
CHECK_AND_INIT_MUTEX
return (*mutex)->TryLock();
}
int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) {
CHECK_AND_INIT_MUTEX
return (*mutex)->Lock(nullptr);
}
int PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex,
const OrbisKernelTimespec* abstime) {
CHECK_AND_INIT_MUTEX
UNREACHABLE();
return (*mutex)->Lock(abstime);
}
int PS4_SYSV_ABI posix_pthread_mutex_reltimedlock_np(PthreadMutexT* mutex, u64 usec) {
CHECK_AND_INIT_MUTEX
return (*mutex)->Lock(THR_MUTEX_RELTIME, usec);
}
int PthreadMutex::Unlock() {
Pthread* curthread = g_curthread;
/*
* Check if the running thread is not the owner of the mutex.
*/
if (m_owner != curthread) [[unlikely]] {
return POSIX_EPERM;
}
if (Type() == PthreadMutexType::Recursive && m_count > 0) [[unlikely]] {
m_count--;
} else {
curthread->Dequeue(this);
m_lock.unlock();
}
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex) {
PthreadMutex* mp = *mutex;
if (mp <= THR_MUTEX_DESTROYED) [[unlikely]] {
if (mp == THR_MUTEX_DESTROYED) {
return POSIX_EINVAL;
}
return POSIX_EPERM;
}
return mp->Unlock();
}
int PS4_SYSV_ABI posix_pthread_mutex_getspinloops_np(PthreadMutexT* mutex, int* count) {
CHECK_AND_INIT_MUTEX
*count = (*mutex)->m_spinloops;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutex_setspinloops_np(PthreadMutexT* mutex, int count) {
CHECK_AND_INIT_MUTEX(*mutex)->m_spinloops = count;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutex_getyieldloops_np(PthreadMutexT* mutex, int* count) {
CHECK_AND_INIT_MUTEX
*count = (*mutex)->m_yieldloops;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutex_setyieldloops_np(PthreadMutexT* mutex, int count) {
CHECK_AND_INIT_MUTEX(*mutex)->m_yieldloops = count;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutex_isowned_np(PthreadMutexT* mutex) {
PthreadMutex* m = *mutex;
if (m <= THR_MUTEX_DESTROYED) {
return 0;
}
return m->m_owner == g_curthread;
}
bool PthreadMutex::IsOwned(Pthread* curthread) const {
if (this <= THR_MUTEX_DESTROYED) [[unlikely]] {
if (this == THR_MUTEX_DESTROYED) {
return POSIX_EINVAL;
}
return POSIX_EPERM;
}
if (m_owner != curthread) {
return POSIX_EPERM;
}
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) {
PthreadMutexAttrT pattr = new PthreadMutexAttr{};
if (pattr == nullptr) {
return POSIX_ENOMEM;
}
memcpy(pattr, &PthreadMutexattrDefault, sizeof(PthreadMutexAttr));
*attr = pattr;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutexattr_setkind_np(PthreadMutexAttrT* attr,
PthreadMutexType kind) {
if (attr == nullptr || *attr == nullptr) {
*__Error() = POSIX_EINVAL;
return -1;
}
(*attr)->m_type = kind;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) {
if (attr == nullptr) {
*__Error() = POSIX_EINVAL;
return -1;
}
return static_cast<int>(attr->m_type);
}
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) {
if (attr == nullptr || *attr == nullptr || type >= PthreadMutexType::Max) {
return POSIX_EINVAL;
}
(*attr)->m_type = type;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutexattr_gettype(PthreadMutexAttrT* attr, PthreadMutexType* type) {
if (attr == nullptr || *attr == nullptr || (*attr)->m_type >= PthreadMutexType::Max) {
return POSIX_EINVAL;
}
*type = (*attr)->m_type;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) {
if (attr == nullptr || *attr == nullptr) {
return POSIX_EINVAL;
}
delete *attr;
*attr = nullptr;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutexattr_getprotocol(PthreadMutexAttrT* mattr,
PthreadMutexProt* protocol) {
if (mattr == nullptr || *mattr == nullptr) {
return POSIX_EINVAL;
}
*protocol = (*mattr)->m_protocol;
return 0;
}
int PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(PthreadMutexAttrT* mattr,
PthreadMutexProt protocol) {
if (mattr == nullptr || *mattr == nullptr || (protocol < PthreadMutexProt::None) ||
(protocol > PthreadMutexProt::Protect)) {
return POSIX_EINVAL;
}
(*mattr)->m_protocol = protocol;
//(*mattr)->m_ceiling = THR_MAX_RR_PRIORITY;
return 0;
}
void RegisterMutex(Core::Loader::SymbolsResolver* sym) {
// Posix
LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_init);
LIB_FUNCTION("7H0iTOciTLo", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_lock);
LIB_FUNCTION("2Z+PpY6CaJg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_unlock);
LIB_FUNCTION("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy);
LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init);
LIB_FUNCTION("mDmgMOGVUqg", "libScePosix", 1, "libkernel", 1, 1,
posix_pthread_mutexattr_settype);
LIB_FUNCTION("5txKfcMUAok", "libScePosix", 1, "libkernel", 1, 1,
posix_pthread_mutexattr_setprotocol);
LIB_FUNCTION("HF7lK46xzjY", "libScePosix", 1, "libkernel", 1, 1,
posix_pthread_mutexattr_destroy);
LIB_FUNCTION("K-jXhbt2gn4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_trylock);
// Posix-Kernel
LIB_FUNCTION("7H0iTOciTLo", "libkernel", 1, "libkernel", 1, 1, posix_pthread_mutex_lock);
LIB_FUNCTION("2Z+PpY6CaJg", "libkernel", 1, "libkernel", 1, 1, posix_pthread_mutex_unlock);
// Orbis
LIB_FUNCTION("cmo1RIYva9o", "libkernel", 1, "libkernel", 1, 1, ORBIS(scePthreadMutexInit));
LIB_FUNCTION("2Of0f+3mhhE", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutex_destroy));
LIB_FUNCTION("F8bUHwAG284", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutexattr_init));
LIB_FUNCTION("smWEktiyyG0", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutexattr_destroy));
LIB_FUNCTION("iMp8QpE+XO4", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutexattr_settype));
LIB_FUNCTION("1FGvU0i9saQ", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutexattr_setprotocol));
LIB_FUNCTION("9UK1vLZQft4", "libkernel", 1, "libkernel", 1, 1, ORBIS(posix_pthread_mutex_lock));
LIB_FUNCTION("tn3VlD0hG60", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutex_unlock));
LIB_FUNCTION("upoVrzMHFeE", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutex_trylock));
LIB_FUNCTION("IafI2PxcPnQ", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutex_reltimedlock_np));
LIB_FUNCTION("qH1gXoq71RY", "libkernel", 1, "libkernel", 1, 1, ORBIS(posix_pthread_mutex_init));
LIB_FUNCTION("n2MMpvU8igI", "libkernel", 1, "libkernel", 1, 1,
ORBIS(posix_pthread_mutexattr_init));
}
} // namespace Libraries::Kernel