From 74bd5d9fa5fccb79719a8bc8f18016a96e93d585 Mon Sep 17 00:00:00 2001 From: Sebastian <115291+slider@users.noreply.github.com> Date: Tue, 20 Oct 2026 12:10:06 +0000 Subject: [PATCH 1/2] feat(libkernel): add mutex ownership, relative lock and spin/yield accessors Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QnkpF2oEMofseVz9Z7WEAC --- .../prx/libkernel/Pthread/Posix/Mutex.cpp | 27 +++- .../prx/libkernel/Pthread/include/Mutex.hpp | 5 + .../prx/libkernel/Pthread/include/Pthread.hpp | 9 ++ core/libs/prx/libkernel/Pthread/src/Mutex.cpp | 47 ++++++- core/libs/tests/GuestPthreadMutexErrors.cpp | 120 ++++++++++++++++++ docs/dev/debt/libkernel.md | 1 + 6 files changed, 206 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp index 3a5d38dc3f5..346b447bb87 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp @@ -29,7 +29,7 @@ void _initializeStatic(PthreadMutex* mutex, const char* funcName) { PthreadMutex current = slot.load(std::memory_order_acquire); if (!_isStaticInitializer(current)) return; auto* created = new PthreadMutexPrivate(); - created->_type = reinterpret_cast(current) == POSIX_ADAPTIVE_MUTEX_INITIALIZER ? MutexType::Adaptive : MutexType::ErrorCheck; + created->SetType(reinterpret_cast(current) == POSIX_ADAPTIVE_MUTEX_INITIALIZER ? MutexType::Adaptive : MutexType::ErrorCheck); if (!slot.compare_exchange_strong(current, created, std::memory_order_acq_rel, std::memory_order_acquire)) delete created; } @@ -79,6 +79,31 @@ int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex) { return PosixThread::ToErrno(scePthreadMutexUnlock(mutex)); } +int APS5_VABI pthread_mutex_reltimedlock_np_nid_postfix(PthreadMutex* mutex, KernelUseconds usec) { + _initializeStatic(mutex, __func__); + return PosixThread::ToErrno(scePthreadMutexTimedlock(mutex, usec)); +} + +int APS5_VABI pthread_mutex_isowned_np_nid_postfix(PthreadMutex* mutex) { + return MutexOperations::IsOwned(mutex) ? 1 : 0; +} + +int APS5_VABI pthread_mutex_getspinloops_np_nid_postfix(PthreadMutex* mutex, int* count) { + return PosixThread::ToErrno(scePthreadMutexGetspinloops(mutex, count)); +} + +int APS5_VABI pthread_mutex_setspinloops_np_nid_postfix(PthreadMutex* mutex, int count) { + return PosixThread::ToErrno(scePthreadMutexSetspinloops(mutex, count)); +} + +int APS5_VABI pthread_mutex_getyieldloops_np_nid_postfix(PthreadMutex* mutex, int* count) { + return PosixThread::ToErrno(scePthreadMutexGetyieldloops(mutex, count)); +} + +int APS5_VABI pthread_mutex_setyieldloops_np_nid_postfix(PthreadMutex* mutex, int count) { + return PosixThread::ToErrno(scePthreadMutexSetyieldloops(mutex, count)); +} + int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr) { return PosixThread::ToErrno(scePthreadMutexattrDestroy(attr)); } diff --git a/core/libs/prx/libkernel/Pthread/include/Mutex.hpp b/core/libs/prx/libkernel/Pthread/include/Mutex.hpp index ca734c603d3..a994a1b5c32 100644 --- a/core/libs/prx/libkernel/Pthread/include/Mutex.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Mutex.hpp @@ -6,6 +6,7 @@ class MutexOperations { public: static int Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime); + static bool IsOwned(const PthreadMutex* mutex); }; extern "C" { @@ -20,6 +21,10 @@ int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec); int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex); +int APS5_VABI scePthreadMutexGetspinloops(PthreadMutex* mutex, int* count); +int APS5_VABI scePthreadMutexSetspinloops(PthreadMutex* mutex, int count); +int APS5_VABI scePthreadMutexGetyieldloops(PthreadMutex* mutex, int* count); +int APS5_VABI scePthreadMutexSetyieldloops(PthreadMutex* mutex, int count); } diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 03024d9d7fc..0cef7ba4984 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -37,14 +37,23 @@ struct PthreadMutexattrPrivate { int pshared = 0; }; +constexpr int ADAPTIVE_MUTEX_SPINS = 2000; + struct PthreadMutexPrivate { std::recursive_timed_mutex _rmtx; std::timed_mutex _mtx; MutexType _type; std::atomic _owner; int _count; + int _spinloops = 0; + int _yieldloops = 0; PthreadMutexPrivate() : _type(MutexType::ErrorCheck), _owner(std::thread::id{}), _count(0) {} + + void SetType(MutexType type) { + _type = type; + _spinloops = type == MutexType::Adaptive ? ADAPTIVE_MUTEX_SPINS : 0; + } }; struct PthreadRwlockattrPrivate { diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index 876bd9ee202..dccc979442d 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -46,7 +46,7 @@ PthreadMutex resolveMutex(PthreadMutex* mutex, bool initialize) { throw std::runtime_error("Mutex is not initialized"); auto* created = new PthreadMutexPrivate(); if (current == adaptiveInitializer()) - created->_type = MutexType::Adaptive; + created->SetType(MutexType::Adaptive); slot.store(created, std::memory_order_release); return created; } @@ -80,6 +80,15 @@ int acquireMutex(PthreadMutex mutex, TAcquire acquire, int unavailable, bool try } +bool MutexOperations::IsOwned(const PthreadMutex* mutex) { + if (!mutex) + throw std::invalid_argument("Mutex pointer is null"); + const auto current = std::atomic_ref(*const_cast(mutex)).load(std::memory_order_acquire); + if (!isInitializedMutex(current)) + return false; + return current->_owner.load(std::memory_order_acquire) == std::this_thread::get_id(); +} + int MutexOperations::Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime) { if (!abstime || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) throw std::invalid_argument("Invalid absolute mutex timeout"); @@ -152,7 +161,7 @@ int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* a throw std::invalid_argument("Mutex pointer is null"); auto replacement = std::make_unique(); if (attr && *attr) - replacement->_type = (*attr)->type; + replacement->SetType((*attr)->type); std::lock_guard lock(initializationMutex); std::atomic_ref(*mutex).store(replacement.release(), std::memory_order_release); return 0; @@ -224,4 +233,38 @@ int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) { return acquireMutex(resolveMutex(mutex, true), [](auto& native) { return native.try_lock(); }, sceBusy, true); } +int APS5_VABI scePthreadMutexGetspinloops(PthreadMutex* mutex, int* count) { + auto* current = resolveMutex(mutex, true); + if (!current) + return SCE_KERNEL_ERROR_EINVAL; + if (!count) + throw std::invalid_argument("Spin loop count pointer is null"); + *count = current->_spinloops; + return 0; +} + +int APS5_VABI scePthreadMutexSetspinloops(PthreadMutex* mutex, int count) { + auto* current = resolveMutex(mutex, true); + if (!current) + return SCE_KERNEL_ERROR_EINVAL; + current->_spinloops = count; + return 0; +} + +int APS5_VABI scePthreadMutexGetyieldloops(PthreadMutex* mutex, int* count) { + auto* current = resolveMutex(mutex, true); + if (!current) + return SCE_KERNEL_ERROR_EINVAL; + if (!count) + throw std::invalid_argument("Yield loop count pointer is null"); + *count = current->_yieldloops; + return 0; +} + +int APS5_VABI scePthreadMutexSetyieldloops(PthreadMutex* mutex, int count) { + if (auto* current = resolveMutex(mutex, true)) + current->_yieldloops = count; + return 0; +} + } diff --git a/core/libs/tests/GuestPthreadMutexErrors.cpp b/core/libs/tests/GuestPthreadMutexErrors.cpp index 064469d42d8..da1083b1219 100644 --- a/core/libs/tests/GuestPthreadMutexErrors.cpp +++ b/core/libs/tests/GuestPthreadMutexErrors.cpp @@ -2,6 +2,7 @@ #include #include #include +#include extern "C" { int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); @@ -12,14 +13,131 @@ int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* a int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); +int APS5_VABI scePthreadMutexGetspinloops(PthreadMutex* mutex, int* count); +int APS5_VABI scePthreadMutexSetspinloops(PthreadMutex* mutex, int count); +int APS5_VABI scePthreadMutexGetyieldloops(PthreadMutex* mutex, int* count); +int APS5_VABI scePthreadMutexSetyieldloops(PthreadMutex* mutex, int count); +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_reltimedlock_np_nid_postfix(PthreadMutex* mutex, KernelUseconds usec); +int APS5_VABI pthread_mutex_isowned_np_nid_postfix(PthreadMutex* mutex); +int APS5_VABI pthread_mutex_getspinloops_np_nid_postfix(PthreadMutex* mutex, int* count); +int APS5_VABI pthread_mutex_setspinloops_np_nid_postfix(PthreadMutex* mutex, int count); +int APS5_VABI pthread_mutex_getyieldloops_np_nid_postfix(PthreadMutex* mutex, int* count); +int APS5_VABI pthread_mutex_setyieldloops_np_nid_postfix(PthreadMutex* mutex, int count); } static constexpr int SCE_OK = 0; static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010u); +static constexpr int POSIX_EDEADLK = 11; +static constexpr int POSIX_EINVAL = 22; +static constexpr int POSIX_ETIMEDOUT = 60; +static const PthreadMutex AdaptiveInitializer = reinterpret_cast(std::uintptr_t{1}); +static const PthreadMutex DestroyedMutex = reinterpret_cast(std::uintptr_t{2}); + static void Require(bool value) { if (!value) std::abort(); } +template +static bool Throws(Call call) { + try { + call(); + } catch (const std::exception&) { + return true; + } + return false; +} + +struct OtherThread { + PthreadMutex* mutex; + int isowned = -1; + int reltimedlock = -1; +}; + +static void* APS5_VABI CheckFromOtherThread(void* arg) { + auto& other = *static_cast(arg); + other.isowned = pthread_mutex_isowned_np_nid_postfix(other.mutex); + other.reltimedlock = pthread_mutex_reltimedlock_np_nid_postfix(other.mutex, 1000); + return nullptr; +} + +static void CheckOwnershipAndRelativeLock() { + PthreadMutex mutex = nullptr; + Require(pthread_mutex_isowned_np_nid_postfix(&mutex) == 0 && mutex == nullptr); + PthreadMutex adaptive = AdaptiveInitializer; + Require(pthread_mutex_isowned_np_nid_postfix(&adaptive) == 0 && adaptive == AdaptiveInitializer); + Require(pthread_mutex_reltimedlock_np_nid_postfix(&mutex, 1000) == 0 && mutex != nullptr); + Require(pthread_mutex_isowned_np_nid_postfix(&mutex) == 1); + Require(pthread_mutex_reltimedlock_np_nid_postfix(&mutex, 1000) == POSIX_EDEADLK); + OtherThread other{&mutex}; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, CheckFromOtherThread, &other, nullptr) == SCE_OK); + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + Require(other.isowned == 0 && other.reltimedlock == POSIX_ETIMEDOUT); + Require(pthread_mutex_unlock_nid_postfix(&mutex) == 0); + Require(pthread_mutex_isowned_np_nid_postfix(&mutex) == 0); + Require(pthread_mutex_reltimedlock_np_nid_postfix(&mutex, 0) == 0); + Require(pthread_mutex_unlock_nid_postfix(&mutex) == 0); + Require(pthread_mutex_destroy_nid_postfix(&mutex) == 0); + Require(mutex == DestroyedMutex); + Require(pthread_mutex_isowned_np_nid_postfix(&mutex) == 0); + Require(pthread_mutex_reltimedlock_np_nid_postfix(&mutex, 1000) == POSIX_EINVAL); + Require(Throws([] { pthread_mutex_isowned_np_nid_postfix(nullptr); })); +} + +static void CheckLoops(PthreadMutexattr* attr) { + PthreadMutex mutex = nullptr; + int count = -1; + Require(pthread_mutex_getspinloops_np_nid_postfix(&mutex, &count) == 0 && count == 0 && mutex != nullptr); + Require(pthread_mutex_getyieldloops_np_nid_postfix(&mutex, &count) == 0 && count == 0); + Require(pthread_mutex_destroy_nid_postfix(&mutex) == 0); + + PthreadMutex adaptive = AdaptiveInitializer; + Require(scePthreadMutexGetspinloops(&adaptive, &count) == SCE_OK && count == 2000 && adaptive != AdaptiveInitializer); + Require(scePthreadMutexGetyieldloops(&adaptive, &count) == SCE_OK && count == 0); + Require(scePthreadMutexDestroy(&adaptive) == SCE_OK); + + Require(scePthreadMutexattrSettype(attr, 4) == SCE_OK); + Require(scePthreadMutexInit(&mutex, attr, "adaptive") == SCE_OK); + Require(scePthreadMutexGetspinloops(&mutex, &count) == SCE_OK && count == 2000); + Require(scePthreadMutexSetspinloops(&mutex, 7) == SCE_OK); + Require(pthread_mutex_getspinloops_np_nid_postfix(&mutex, &count) == 0 && count == 7); + Require(pthread_mutex_setspinloops_np_nid_postfix(&mutex, -3) == 0); + Require(scePthreadMutexGetspinloops(&mutex, &count) == SCE_OK && count == -3); + Require(scePthreadMutexSetyieldloops(&mutex, 9) == SCE_OK); + Require(pthread_mutex_getyieldloops_np_nid_postfix(&mutex, &count) == 0 && count == 9); + Require(pthread_mutex_setyieldloops_np_nid_postfix(&mutex, 11) == 0); + Require(scePthreadMutexGetyieldloops(&mutex, &count) == SCE_OK && count == 11); + Require(pthread_mutex_lock_nid_postfix(&mutex) == 0); + Require(pthread_mutex_lock_nid_postfix(&mutex) == POSIX_EDEADLK); + Require(pthread_mutex_unlock_nid_postfix(&mutex) == 0); + Require(Throws([&] { scePthreadMutexGetspinloops(&mutex, nullptr); })); + Require(Throws([&] { scePthreadMutexGetyieldloops(&mutex, nullptr); })); + Require(scePthreadMutexDestroy(&mutex) == SCE_OK); + + count = -1; + Require(scePthreadMutexGetspinloops(&mutex, &count) == SCE_KERNEL_ERROR_EINVAL && count == -1); + Require(scePthreadMutexSetspinloops(&mutex, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexGetyieldloops(&mutex, &count) == SCE_KERNEL_ERROR_EINVAL && count == -1); + Require(pthread_mutex_getspinloops_np_nid_postfix(&mutex, &count) == POSIX_EINVAL); + Require(pthread_mutex_setspinloops_np_nid_postfix(&mutex, 1) == POSIX_EINVAL); + Require(pthread_mutex_getyieldloops_np_nid_postfix(&mutex, &count) == POSIX_EINVAL && count == -1); + Require(scePthreadMutexSetyieldloops(&mutex, 1) == SCE_OK); + Require(pthread_mutex_setyieldloops_np_nid_postfix(&mutex, 1) == 0); + Require(mutex == DestroyedMutex); + + for (const int type : {1, 2, 3}) { + Require(scePthreadMutexattrSettype(attr, type) == SCE_OK); + Require(scePthreadMutexInit(&mutex, attr, nullptr) == SCE_OK); + Require(scePthreadMutexGetspinloops(&mutex, &count) == SCE_OK && count == 0); + Require(scePthreadMutexDestroy(&mutex) == SCE_OK); + } +} + int main() { PthreadMutexattr attr = nullptr; Require(scePthreadMutexattrInit(&attr) == SCE_OK); @@ -46,5 +164,7 @@ int main() { Require(scePthreadMutexInit(&mutex, &attr, "attr") == SCE_OK); Require(scePthreadMutexDestroy(&mutex) == SCE_OK); + CheckLoops(&attr); + CheckOwnershipAndRelativeLock(); Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); } diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 6384b00c9f3..9db863874c6 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -219,6 +219,7 @@ - [inet_ntop, inet_pton](../../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp) (libkernel) - an `AF_INET6` result of `inet_ntop` is followed by zero bytes up to `size`. In a payload process on a PS5 (firmware 12.70), the text forms, the order of the checks and this padding were measured through both exports. The padding was sampled at sizes from 3 to 65536, above 64 only at 65, 128, 256, 1024, 4096 and 65536; the sizes that were not sampled, sizes above 65536 and a title process are assumed to behave the same - [_fstatfs](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) fills the FreeBSD 11 `struct statfs` of the PS5 payload SDK with the host volume that holds the file: block size, transfer size, total, free and available blocks, maximum name length and the read-only, `nosuid` and `noexec` flags (on Windows only read-only, and `f_files` and `f_ffree` are 0, as NTFS has no fixed number of files). The filesystem type number and name, mount paths, owner, filesystem id and I/O counters are left 0 or empty, since they would describe a host mount, not a console one. Sockets and pipes return `EINVAL`, as FreeBSD's `getvnode` does; a Windows descriptor that is neither a file nor a pipe throws. Stremio Plus (PPSA74126) reads the free space before a download and only logs the other fields - [pthread_spin_lock](../../../core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp) (libkernel) - a lock taken again by the thread that holds it throws here. On a PS5 (firmware 12.70) that call had not returned after 2 s, as FreeBSD 11.4 libthr [`thr_pspinlock.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_pspinlock.c) spins forever +- [pthread_mutex_getspinloops_np, pthread_mutex_setspinloops_np, pthread_mutex_getyieldloops_np, pthread_mutex_setyieldloops_np and their scePthreadMutex counterparts](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) store and report the counts, but host mutexes do not spin or yield by them. The defaults follow FreeBSD 11.4's [`mutex_init_body`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutex.c#L236-L272) without `LIBPTHREAD_SPINLOOPS` and `LIBPTHREAD_YIELDLOOPS`: 2000 spins for an adaptive mutex, 0 otherwise; the console's defaults are unverified. Setting the yield count of a destroyed mutex returns 0 as FreeBSD's [`__pthread_mutex_setyieldloops_np`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutex.c#L1125-L1135) does; the console's result is unverified - [pthread_rwlockattr_getpshared, pthread_rwlockattr_setpshared](../../../core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp) (libkernel) - `pthread_rwlockattr_destroy` clears the attribute here, so a later get, or a set of 0, throws. On a PS5 (firmware 12.70) destroy leaves the attribute set; a get afterwards returned 0 with a stale value and a set of 0 returned 0 - [sceKernelGetEventError](../../../core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp) (libkernel) returns the event's `data` as an `int` when `EV_ERROR` (0x4000) is set in `flags` and 0 otherwise, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/947627d10d4ef4b68ba3c0189aab6c368e70a8d2/src/kernel/eventQueue.cpp#L663) does and as FreeBSD 11's [kevent](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/kern_event.c#L1005) reports a failed change (`flags = EV_ERROR`, errno in `data`). [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_queue.pas#L643) instead returns `fflags` plus `0x80020000` when `fflags` is nonzero. Whether the console reads `data` or `fflags` and returns the raw errno or an SCE error code is unverified. No event delivered by the equeue here carries `EV_ERROR`, so the call returns 0 for every event `sceKernelWaitEqueue` reports - [pthread_cond_reltimedwait_np](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console From 31fc06b120e9f26902251d5dcb6042cddf1848ab Mon Sep 17 00:00:00 2001 From: Sebastian <115291+slider@users.noreply.github.com> Date: Wed, 21 Oct 2026 08:00:28 +0000 Subject: [PATCH 2/2] docs(debt): record the pthread_mutex_reltimedlock_np timeout width Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QnkpF2oEMofseVz9Z7WEAC --- docs/dev/debt/libkernel.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 9db863874c6..35aee71692b 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -223,6 +223,7 @@ - [pthread_rwlockattr_getpshared, pthread_rwlockattr_setpshared](../../../core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp) (libkernel) - `pthread_rwlockattr_destroy` clears the attribute here, so a later get, or a set of 0, throws. On a PS5 (firmware 12.70) destroy leaves the attribute set; a get afterwards returned 0 with a stale value and a set of 0 returned 0 - [sceKernelGetEventError](../../../core/libs/prx/libkernel/Equeue/src/EventAccessors.cpp) (libkernel) returns the event's `data` as an `int` when `EV_ERROR` (0x4000) is set in `flags` and 0 otherwise, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/947627d10d4ef4b68ba3c0189aab6c368e70a8d2/src/kernel/eventQueue.cpp#L663) does and as FreeBSD 11's [kevent](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/kern_event.c#L1005) reports a failed change (`flags = EV_ERROR`, errno in `data`). [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/kernel/ps4_queue.pas#L643) instead returns `fflags` plus `0x80020000` when `fflags` is nonzero. Whether the console reads `data` or `fflags` and returns the raw errno or an SCE error code is unverified. No event delivered by the equeue here carries `EV_ERROR`, so the call returns 0 for every event `sceKernelWaitEqueue` reports - [pthread_cond_reltimedwait_np](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console +- [pthread_mutex_reltimedlock_np](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) takes `(mutex, usec)`, a relative timeout in microseconds, and is `scePthreadMutexTimedlock` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadMutexTimedlock` ([`mutex.cpp`](https://github.com/shadps4-emu/shadPS4/blob/7c960eae136b956cf4c1d72aacd2426b650330df/src/core/libraries/kernel/threads/mutex.cpp#L284-L287)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `pthread_cond_reltimedwait_np`, so a count of 2^32 microseconds or more would be truncated if the console reads 64 bits. Unverified on the console - [sysctl kern.arandom](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) serves FreeBSD 11's MIB {1, 37} (CTL_KERN, KERN_ARANDOM) and the "kern.arandom" sysctlbyname lookup through std::random_device, returning min(*oldLength, 256) random bytes. Following FreeBSD's sysctl_kern_arnd, a null buffer reports min(*oldLength, 256) without writing, a null length returns 0 without writing, and requests with a new value return EPERM (1) as the node is read-only. Cryptographic quality depends on the host's random device implementation. - [pthread_atfork](../../../core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp) (libkernel) appends the three handlers to a process-wide list and returns 0, or `ENOMEM` when the entry cannot be allocated, as FreeBSD 11.4's [`_pthread_atfork`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_fork.c#L78-L100) does; null handlers are accepted. The handlers never run: `fork` throws and `vfork` is absent, so no child process is created. Handlers of a module that is unloaded stay in the list, where FreeBSD's `__pthread_cxa_finalize` removes them. PPSA97358 has a source-level call from the constructor `rcu_init` ([rcu.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/rcu.c#L488-L495)), but current-main runtime reachability is unverified; XPSemu currently stops first at the unresolved `mlockall` import (#4386) - [sceKernelGetPageTableStats](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) reports 8192 entries on each side and counts them as a PS5 (firmware 13.60) counted them for a payload process: one cpu entry for each 2 MiB granule that holds a committed mapping, whatever its protection (none included), and one gpu entry for each 1 GiB region that holds a GPU-accessible mapping, plus one for each 2 MiB granule of such a mapping that is not a whole granule of direct memory at a 2 MiB-aligned physical address. A granule or region shared by several mappings counts once, and reservations do not count. On the console the first GPU mapping of a process also takes three gpu entries and one cpu entry that stay after it is unmapped, which is not modelled, and only the ranges whose protection is recorded here are counted. A null pointer returns `SCE_KERNEL_ERROR_EFAULT`, where the console faults. Resonance: A Plague Tale Legacy (PPSA30601) calls it at start-up (#4067)