// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #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/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, }; #ifdef _WIN64 static bool TryImmediateRelease(Pthread* target_thread, const u64 target_wait_generation, BinarySemaphore* wake_sema) { if (target_thread == nullptr || wake_sema == nullptr) { return false; } if (target_thread->GetCondWaitGeneration() != target_wait_generation || target_thread->GetCondWaitArmedGeneration() != target_wait_generation) { return false; } target_thread->SetLastCondWakeGeneration(target_wait_generation); wake_sema->release(); return true; } #endif 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 int CondId = 0; cvp->name = fmt::format("Cond{}", CondId++); } 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; const u64 wait_generation = curthread->BeginCondWaitGeneration(); curthread->ArmCondWaitGeneration(wait_generation); 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); curthread->ClearCondWaitGenerationArm(); SleepqLock(this); #ifdef _WIN64 const u64 last_wake_generation = curthread->GetLastCondWakeGeneration(); if (error == 0 && curthread->wchan != nullptr && last_wake_generation != 0 && last_wake_generation != wait_generation) { curthread->ClearWake(); curthread->ArmCondWaitGeneration(wait_generation); SleepqUnlock(this); continue; } #endif 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; curthread->ClearCondWaitGenerationArm(); mp->CvLock(recurse); curthread->ClearWake(); 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; curthread->ClearCondWaitGenerationArm(); const int error2 = mp->CvLock(recurse); curthread->ClearWake(); 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; SleepqLock(this); SleepQueue* sq = SleepqLookup(this); if (sq == nullptr) { SleepqUnlock(this); return 0; } Pthread* td = thread ? thread : sq->sq_blocked.front(); const u64 td_wait_generation = td->GetCondWaitGeneration(); 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(); } auto& deferred_entry = curthread->defer_waiters[curthread->nwaiter_defer++]; deferred_entry.thread = td; deferred_entry.wait_generation = td_wait_generation; mp->m_flags |= PthreadMutexFlags::Deferred; } else { waddr = &td->wake_sema; } SleepqUnlock(this); if (waddr != nullptr) { #ifdef _WIN64 TryImmediateRelease(td, td_wait_generation, waddr); #else waddr->release(); #endif } return 0; } struct BroadcastArg { Pthread* curthread; Pthread* targets[Pthread::MaxDeferWaiters]; u64 target_wait_generations[Pthread::MaxDeferWaiters]; 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(arg); Pthread* curthread = ba2->curthread; PthreadMutex* mp = td->mutex_obj; const u64 td_wait_generation = td->GetCondWaitGeneration(); if (mp->m_owner == curthread) { if (curthread->nwaiter_defer >= Pthread::MaxDeferWaiters) { curthread->WakeAll(); } auto& deferred_entry = curthread->defer_waiters[curthread->nwaiter_defer++]; deferred_entry.thread = td; deferred_entry.wait_generation = td_wait_generation; mp->m_flags |= PthreadMutexFlags::Deferred; } else { if (ba2->count >= Pthread::MaxDeferWaiters) { for (int i = 0; i < ba2->count; i++) { #ifdef _WIN64 TryImmediateRelease(ba2->targets[i], ba2->target_wait_generations[i], ba2->waddrs[i]); #else ba2->waddrs[i]->release(); #endif } ba2->count = 0; } ba2->targets[ba2->count] = td; ba2->target_wait_generations[ba2->count] = td_wait_generation; ba2->waddrs[ba2->count] = &td->wake_sema; ba2->count++; } }; 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++) { #ifdef _WIN64 TryImmediateRelease(ba.targets[i], ba.target_wait_generations[i], ba.waddrs[i]); #else ba.waddrs[i]->release(); #endif } 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("dJcuQVn6-Iw", "libScePosix", 1, "libkernel", posix_pthread_condattr_destroy); LIB_FUNCTION("EjllaAqAPZo", "libScePosix", 1, "libkernel", posix_pthread_condattr_setclock); LIB_FUNCTION("0TyVk4MSLt0", "libScePosix", 1, "libkernel", posix_pthread_cond_init); LIB_FUNCTION("2MOy+rUfuhQ", "libScePosix", 1, "libkernel", posix_pthread_cond_signal); LIB_FUNCTION("RXXqi4CtF8w", "libScePosix", 1, "libkernel", posix_pthread_cond_destroy); LIB_FUNCTION("Op8TBGY5KHg", "libScePosix", 1, "libkernel", posix_pthread_cond_wait); LIB_FUNCTION("27bAgiJmOh0", "libScePosix", 1, "libkernel", posix_pthread_cond_timedwait); LIB_FUNCTION("mkx2fVhNMsg", "libScePosix", 1, "libkernel", posix_pthread_cond_broadcast); // Posix-Kernel LIB_FUNCTION("0TyVk4MSLt0", "libkernel", 1, "libkernel", posix_pthread_cond_init); LIB_FUNCTION("EjllaAqAPZo", "libkernel", 1, "libkernel", posix_pthread_condattr_setclock); LIB_FUNCTION("Op8TBGY5KHg", "libkernel", 1, "libkernel", posix_pthread_cond_wait); LIB_FUNCTION("mkx2fVhNMsg", "libkernel", 1, "libkernel", posix_pthread_cond_broadcast); LIB_FUNCTION("2MOy+rUfuhQ", "libkernel", 1, "libkernel", posix_pthread_cond_signal); LIB_FUNCTION("RXXqi4CtF8w", "libkernel", 1, "libkernel", posix_pthread_cond_destroy); LIB_FUNCTION("27bAgiJmOh0", "libkernel", 1, "libkernel", posix_pthread_cond_timedwait); LIB_FUNCTION("mKoTx03HRWA", "libkernel", 1, "libkernel", posix_pthread_condattr_init); LIB_FUNCTION("dJcuQVn6-Iw", "libkernel", 1, "libkernel", posix_pthread_condattr_destroy); // Orbis LIB_FUNCTION("2Tb92quprl0", "libkernel", 1, "libkernel", ORBIS(scePthreadCondInit)); LIB_FUNCTION("m5-2bsNfv7s", "libkernel", 1, "libkernel", ORBIS(posix_pthread_condattr_init)); LIB_FUNCTION("JGgj7Uvrl+A", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_broadcast)); LIB_FUNCTION("WKAXJ4XBPQ4", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_wait)); LIB_FUNCTION("waPcxYiR3WA", "libkernel", 1, "libkernel", ORBIS(posix_pthread_condattr_destroy)); LIB_FUNCTION("kDh-NfxgMtE", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_signal)); LIB_FUNCTION("BmMjYxmew1w", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_reltimedwait_np)); LIB_FUNCTION("g+PZd2hiacg", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_destroy)); LIB_FUNCTION("o69RpYO-Mu0", "libkernel", 1, "libkernel", ORBIS(posix_pthread_cond_signalto_np)); } } // namespace Libraries::Kernel