Files
shadps4/src/core/libraries/kernel/threads/condvar.cpp
Stephen Miller 65986d8a8a Kernel.Pthread: Various fixes (#4406)
* Fix for SignalTo behavior

Should run FindThread to make sure thread input is valid.

* Missing mutex-related exports

* Oops

* All missing cond exports

We have these implemented, so we might as well export them.
I also organized them a bit.

* Implement pthread_attr_getscope, pthread_attr_setcreatesuspend_np, pthread_attr_setscope

Also export all of our pthread_attr functions.

* Oops

* Fix SchedPolicy::Fifo definition

Some tricks FreeBSD source performs breaks with this incorrectly set.
Specifically, stuff like setting priorities breaks because we do array accesses with priority - 1 like FreeBSD, but Fifo being 0 makes that an oob read.

* Fix error checks in pthread_attr_setschedparam

* Fix and run thread destructors

_sceKernelSetThreadDtors receives the function itself, not a pointer to the function.

* Oops

* Rewrite pthread_rename_np for accuracy

There's a couple details the initial implementation missed that decomp shows. We weren't locking, we weren't doing the reference add or delete ops, and we were erroneously skipping null names when real hardware actually seems to allow it (the only thing real hardware skips for null name is naming the thread stack).

* Avoid changing common code by using the converted thread name instead

Slightly cleaner code

* Oops

* Flip priority defines

This better matches with FreeBSD's defines, and fixes the returns of sched_get_priority_min and sched_get_priority_max

* Fix args and return for sched_get_priority_max and sched_get_priority_min

Behavior is based on my own kernel decomp (though what I'm seeing matches what red_prig's done for fpPS4 too).

* Better comment

why not

* Fix error behavior of scePthreadGetPrio

Can error from invalid thread input, which the function hardcodes as an ORBIS_KERNEL_ERROR_ESRCH.

* Fix pthread_setprio

This should use the RefAdd and RefDelete functions on non-curthread threads, not FindThread.

* Remove pthread_set_name_np

Decomp shows it's identical to pthread_rename_np.

* Bring back pthread_set_name_np

What I didn't realize was that it was a void method. Behavior is the same as pthread_rename_np though, so call it instead of doing the direct common setname call.

* Move pthread_set_name_np

Needs to be under pthread_rename_np so I can actually call it.

* Better implementation of pthread_getname_np

* rwlock types

Some Sony extension to rwlocks, paired with a proper SDK check specific to rwlock init. I've implemented both pthread_rwlockattr_gettype_np and pthread_rwlockattr_settype_np, and added the relevant SDK check for rwlockattr type.

* Missing thread unlock in SignalTo

SignalTo specifically runs FindThread, then unlocks before running pretty much the same code as Signal.

* Fix comment
2026-05-13 20:50:37 -07:00

403 lines
14 KiB
C++

// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/assert.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/posix_error.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/kernel/threads/sleepq.h"
#include "core/libraries/kernel/threads/thread_state.h"
#include "core/libraries/libs.h"
namespace Libraries::Kernel {
static std::mutex CondStaticLock;
#define THR_COND_INITIALIZER ((PthreadCond*)nullptr)
#define THR_COND_DESTROYED ((PthreadCond*)1)
static constexpr PthreadCondAttr PthreadCondattrDefault = {
.c_pshared = 0,
.c_clockid = ClockId::Realtime,
};
static int CondInit(PthreadCondT* cond, const PthreadCondAttrT* cond_attr, const char* name) {
auto* cvp = new (std::nothrow) PthreadCond{};
if (cvp == nullptr) {
return POSIX_ENOMEM;
}
if (name) {
cvp->name = name;
} else {
static std::atomic<int> CondId{0};
cvp->name = fmt::format("Cond{}", CondId.fetch_add(1));
}
if (cond_attr == nullptr || *cond_attr == nullptr) {
cvp->clock_id = ClockId::Realtime;
} else {
// if ((*cond_attr)->c_pshared) {
// cvp->flags |= USYNC_PROCESS_SHARED;
// }
cvp->clock_id = (*cond_attr)->c_clockid;
}
*cond = cvp;
return 0;
}
static int InitStatic(Pthread* thread, PthreadCondT* cond) {
std::scoped_lock lk{CondStaticLock};
if (*cond == nullptr) {
return CondInit(cond, nullptr, nullptr);
}
return 0;
}
#define CHECK_AND_INIT_COND \
if (cvp = *cond; cvp <= THR_COND_DESTROYED) [[unlikely]] { \
if (cvp == THR_COND_INITIALIZER) { \
int ret; \
ret = InitStatic(g_curthread, cond); \
if (ret) \
return (ret); \
} else if (cvp == THR_COND_DESTROYED) { \
return POSIX_EINVAL; \
} \
cvp = *cond; \
}
int PS4_SYSV_ABI posix_pthread_cond_init(PthreadCondT* cond, const PthreadCondAttrT* cond_attr) {
*cond = nullptr;
return CondInit(cond, cond_attr, nullptr);
}
int PS4_SYSV_ABI scePthreadCondInit(PthreadCondT* cond, const PthreadCondAttrT* cond_attr,
const char* name) {
*cond = nullptr;
return CondInit(cond, cond_attr, name);
}
int PS4_SYSV_ABI posix_pthread_cond_destroy(PthreadCondT* cond) {
PthreadCond* cvp = *cond;
if (cvp == THR_COND_INITIALIZER) {
return 0;
}
if (cvp == THR_COND_DESTROYED) {
return POSIX_EINVAL;
}
cvp = *cond;
*cond = THR_COND_DESTROYED;
delete cvp;
return 0;
}
int PthreadCond::Wait(PthreadMutexT* mutex, const OrbisKernelTimespec* abstime, u64 usec) {
PthreadMutex* mp = *mutex;
if (const int error = mp->IsOwned(g_curthread); error != 0) {
return error;
}
Pthread* curthread = g_curthread;
ASSERT_MSG(curthread->wchan == nullptr, "Thread was already on queue.");
// _thr_testcancel(curthread);
SleepqLock(this);
/*
* set __has_user_waiters before unlocking mutex, this allows
* us to check it without locking in pthread_cond_signal().
*/
has_user_waiters = true;
curthread->will_sleep = true;
int recurse;
mp->CvUnlock(&recurse);
curthread->mutex_obj = mp;
SleepqAdd(this, curthread);
int error = 0;
for (;;) {
curthread->ClearWake();
SleepqUnlock(this);
//_thr_cancel_enter2(curthread, 0);
error = curthread->Sleep(abstime, usec) ? 0 : POSIX_ETIMEDOUT;
//_thr_cancel_leave(curthread, 0);
SleepqLock(this);
if (curthread->wchan == nullptr) {
error = 0;
break;
} else if (curthread->ShouldCancel()) {
SleepQueue* sq = SleepqLookup(this);
has_user_waiters = SleepqRemove(sq, curthread);
SleepqUnlock(this);
curthread->mutex_obj = nullptr;
mp->CvLock(recurse);
return 0;
} else if (error == POSIX_ETIMEDOUT) {
SleepQueue* sq = SleepqLookup(this);
has_user_waiters = SleepqRemove(sq, curthread);
break;
}
UNREACHABLE();
}
SleepqUnlock(this);
curthread->mutex_obj = nullptr;
const int error2 = mp->CvLock(recurse);
if (error == 0) {
error = error2;
}
return error;
}
int PS4_SYSV_ABI posix_pthread_cond_wait(PthreadCondT* cond, PthreadMutexT* mutex) {
PthreadCond* cvp{};
CHECK_AND_INIT_COND
return cvp->Wait(mutex, nullptr);
}
int PS4_SYSV_ABI posix_pthread_cond_timedwait(PthreadCondT* cond, PthreadMutexT* mutex,
const OrbisKernelTimespec* abstime) {
if (abstime == nullptr || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
abstime->tv_nsec >= 1000000000) {
return POSIX_EINVAL;
}
PthreadCond* cvp{};
CHECK_AND_INIT_COND
return cvp->Wait(mutex, abstime);
}
int PS4_SYSV_ABI posix_pthread_cond_reltimedwait_np(PthreadCondT* cond, PthreadMutexT* mutex,
u64 usec) {
PthreadCond* cvp{};
CHECK_AND_INIT_COND
return cvp->Wait(mutex, THR_RELTIME, usec);
}
int PthreadCond::Signal(Pthread* thread) {
Pthread* curthread = g_curthread;
if (thread) {
auto* thread_state = ThrState::Instance();
int ret = thread_state->FindThread(thread, false);
if (ret != ORBIS_OK) {
return ret;
}
thread->lock.unlock();
}
SleepqLock(this);
SleepQueue* sq = SleepqLookup(this);
if (sq == nullptr) {
SleepqUnlock(this);
return 0;
}
Pthread* td = thread ? thread : sq->sq_blocked.front();
PthreadMutex* mp = td->mutex_obj;
has_user_waiters = SleepqRemove(sq, td);
BinarySemaphore* waddr = nullptr;
if (mp->m_owner == curthread) {
if (curthread->nwaiter_defer >= Pthread::MaxDeferWaiters) {
curthread->WakeAll();
}
curthread->defer_waiters[curthread->nwaiter_defer++] = &td->wake_sema;
mp->m_flags |= PthreadMutexFlags::Deferred;
} else {
waddr = &td->wake_sema;
}
SleepqUnlock(this);
if (waddr != nullptr) {
waddr->release();
}
return 0;
}
struct BroadcastArg {
Pthread* curthread;
BinarySemaphore* waddrs[Pthread::MaxDeferWaiters];
int count;
};
int PthreadCond::Broadcast() {
BroadcastArg ba;
ba.curthread = g_curthread;
ba.count = 0;
const auto drop_cb = [](Pthread* td, void* arg) {
auto* ba2 = static_cast<BroadcastArg*>(arg);
Pthread* curthread = ba2->curthread;
PthreadMutex* mp = td->mutex_obj;
if (mp->m_owner == curthread) {
if (curthread->nwaiter_defer >= Pthread::MaxDeferWaiters) {
curthread->WakeAll();
}
curthread->defer_waiters[curthread->nwaiter_defer++] = &td->wake_sema;
mp->m_flags |= PthreadMutexFlags::Deferred;
} else {
if (ba2->count >= Pthread::MaxDeferWaiters) {
for (int i = 0; i < ba2->count; i++) {
ba2->waddrs[i]->release();
}
ba2->count = 0;
}
ba2->waddrs[ba2->count++] = &td->wake_sema;
}
};
SleepqLock(this);
SleepQueue* sq = SleepqLookup(this);
if (sq == nullptr) {
SleepqUnlock(this);
return 0;
}
SleepqDrop(sq, drop_cb, &ba);
has_user_waiters = false;
SleepqUnlock(this);
for (int i = 0; i < ba.count; i++) {
ba.waddrs[i]->release();
}
return 0;
}
int PS4_SYSV_ABI posix_pthread_cond_signal(PthreadCondT* cond) {
PthreadCond* cvp{};
CHECK_AND_INIT_COND
return cvp->Signal(nullptr);
}
int PS4_SYSV_ABI posix_pthread_cond_signalto_np(PthreadCondT* cond, Pthread* thread) {
PthreadCond* cvp{};
CHECK_AND_INIT_COND
return cvp->Signal(thread);
}
int PS4_SYSV_ABI posix_pthread_cond_broadcast(PthreadCondT* cond) {
PthreadCond* cvp{};
CHECK_AND_INIT_COND
cvp->Broadcast();
return 0;
}
int PS4_SYSV_ABI posix_pthread_condattr_init(PthreadCondAttrT* attr) {
auto* pattr = new (std::nothrow) PthreadCondAttr{};
if (pattr == nullptr) {
return POSIX_ENOMEM;
}
memcpy(pattr, &PthreadCondattrDefault, sizeof(PthreadCondAttr));
*attr = pattr;
return 0;
}
int PS4_SYSV_ABI posix_pthread_condattr_destroy(PthreadCondAttrT* attr) {
if (attr == nullptr || *attr == nullptr) {
return POSIX_EINVAL;
}
delete *attr;
*attr = nullptr;
return 0;
}
int PS4_SYSV_ABI posix_pthread_condattr_getclock(const PthreadCondAttrT* attr, ClockId* clock_id) {
if (attr == nullptr || *attr == nullptr) {
return POSIX_EINVAL;
}
*clock_id = (*attr)->c_clockid;
return 0;
}
int PS4_SYSV_ABI posix_pthread_condattr_setclock(PthreadCondAttrT* attr, ClockId clock_id) {
if (attr == nullptr || *attr == nullptr) {
return POSIX_EINVAL;
}
if (clock_id != ClockId::Realtime && clock_id != ClockId::Virtual &&
clock_id != ClockId::Prof && clock_id != ClockId::Monotonic) {
return POSIX_EINVAL;
}
(*attr)->c_clockid = clock_id;
return 0;
}
int PS4_SYSV_ABI posix_pthread_condattr_getpshared(const PthreadCondAttrT* attr, int* pshared) {
if (attr == nullptr || *attr == nullptr) {
return POSIX_EINVAL;
}
*pshared = 0;
return 0;
}
int PS4_SYSV_ABI posix_pthread_condattr_setpshared(PthreadCondAttrT* attr, int pshared) {
if (attr == nullptr || *attr == nullptr) {
return POSIX_EINVAL;
}
if (pshared != 0) {
return POSIX_EINVAL;
}
return 0;
}
void RegisterCond(Core::Loader::SymbolsResolver* sym) {
// Posix
LIB_FUNCTION("mKoTx03HRWA", "libScePosix", 1, "libkernel", posix_pthread_condattr_init);
LIB_FUNCTION("3BpP850hBT4", "libScePosix", 1, "libkernel", posix_pthread_condattr_setpshared);
LIB_FUNCTION("EjllaAqAPZo", "libScePosix", 1, "libkernel", posix_pthread_condattr_setclock);
LIB_FUNCTION("h0qUqSuOmC8", "libScePosix", 1, "libkernel", posix_pthread_condattr_getpshared);
LIB_FUNCTION("cTDYxTUNPhM", "libScePosix", 1, "libkernel", posix_pthread_condattr_getclock);
LIB_FUNCTION("dJcuQVn6-Iw", "libScePosix", 1, "libkernel", posix_pthread_condattr_destroy);
LIB_FUNCTION("0TyVk4MSLt0", "libScePosix", 1, "libkernel", posix_pthread_cond_init);
LIB_FUNCTION("K953PF5u6Pc", "libScePosix", 1, "libkernel", posix_pthread_cond_reltimedwait_np);
LIB_FUNCTION("27bAgiJmOh0", "libScePosix", 1, "libkernel", posix_pthread_cond_timedwait);
LIB_FUNCTION("Op8TBGY5KHg", "libScePosix", 1, "libkernel", posix_pthread_cond_wait);
LIB_FUNCTION("2MOy+rUfuhQ", "libScePosix", 1, "libkernel", posix_pthread_cond_signal);
LIB_FUNCTION("CI6Qy73ae10", "libScePosix", 1, "libkernel", posix_pthread_cond_signalto_np);
LIB_FUNCTION("mkx2fVhNMsg", "libScePosix", 1, "libkernel", posix_pthread_cond_broadcast);
LIB_FUNCTION("RXXqi4CtF8w", "libScePosix", 1, "libkernel", posix_pthread_cond_destroy);
// Posix-Kernel
LIB_FUNCTION("mKoTx03HRWA", "libkernel", 1, "libkernel", posix_pthread_condattr_init);
LIB_FUNCTION("3BpP850hBT4", "libkernel", 1, "libkernel", posix_pthread_condattr_setpshared);
LIB_FUNCTION("EjllaAqAPZo", "libkernel", 1, "libkernel", posix_pthread_condattr_setclock);
LIB_FUNCTION("h0qUqSuOmC8", "libkernel", 1, "libkernel", posix_pthread_condattr_getpshared);
LIB_FUNCTION("cTDYxTUNPhM", "libkernel", 1, "libkernel", posix_pthread_condattr_getclock);
LIB_FUNCTION("dJcuQVn6-Iw", "libkernel", 1, "libkernel", posix_pthread_condattr_destroy);
LIB_FUNCTION("0TyVk4MSLt0", "libkernel", 1, "libkernel", posix_pthread_cond_init);
LIB_FUNCTION("K953PF5u6Pc", "libkernel", 1, "libkernel", posix_pthread_cond_reltimedwait_np);
LIB_FUNCTION("27bAgiJmOh0", "libkernel", 1, "libkernel", posix_pthread_cond_timedwait);
LIB_FUNCTION("Op8TBGY5KHg", "libkernel", 1, "libkernel", posix_pthread_cond_wait);
LIB_FUNCTION("2MOy+rUfuhQ", "libkernel", 1, "libkernel", posix_pthread_cond_signal);
LIB_FUNCTION("CI6Qy73ae10", "libkernel", 1, "libkernel", posix_pthread_cond_signalto_np);
LIB_FUNCTION("mkx2fVhNMsg", "libkernel", 1, "libkernel", posix_pthread_cond_broadcast);
LIB_FUNCTION("RXXqi4CtF8w", "libkernel", 1, "libkernel", posix_pthread_cond_destroy);
// Orbis
LIB_FUNCTION("m5-2bsNfv7s", "libkernel", 1, "libkernel", ORBIS(posix_pthread_condattr_init));
LIB_FUNCTION("6xMew9+rZwI", "libkernel", 1, "libkernel",
ORBIS(posix_pthread_condattr_setpshared));
LIB_FUNCTION("c-bxj027czs", "libkernel", 1, "libkernel",
ORBIS(posix_pthread_condattr_setclock));
LIB_FUNCTION("Dn-DRWi9t54", "libkernel", 1, "libkernel",
ORBIS(posix_pthread_condattr_getpshared));
LIB_FUNCTION("6qM3kO5S3Oo", "libkernel", 1, "libkernel",
ORBIS(posix_pthread_condattr_getclock));
LIB_FUNCTION("waPcxYiR3WA", "libkernel", 1, "libkernel", ORBIS(posix_pthread_condattr_destroy));
LIB_FUNCTION("2Tb92quprl0", "libkernel", 1, "libkernel", ORBIS(scePthreadCondInit));
LIB_FUNCTION("BmMjYxmew1w", "libkernel", 1, "libkernel",
ORBIS(posix_pthread_cond_reltimedwait_np));
LIB_FUNCTION("WKAXJ4XBPQ4", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_wait));
LIB_FUNCTION("kDh-NfxgMtE", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_signal));
LIB_FUNCTION("o69RpYO-Mu0", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_signalto_np));
LIB_FUNCTION("JGgj7Uvrl+A", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_broadcast));
LIB_FUNCTION("g+PZd2hiacg", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_destroy));
}
} // namespace Libraries::Kernel