feat(libkernel): implement pthread_cond_reltimedwait_np
Add pthread_cond_reltimedwait_np (K953PF5u6Pc; computed with tools/nid_names.py and matching aerolib.csv) as scePthreadCondTimedwait with POSIX error numbers: ETIMEDOUT when the relative timeout expires, EPERM when the caller does not hold the mutex, and the mutex held again on return in every waiting case. Titles: Porpoise PPSA99764 and RetroArch PPSA99169 import it. Predicted from relink registry order, not observed: Porpoise stops with "FAIL: unresolved ELF import K953PF5u6Pc from libkernel.prx" (registry index 384; RetroArch index 490). No call site is resolved in the registry. The third argument is a relative timeout in microseconds, not a struct timespec. FreeBSD 11.4 libthr (8c639945a44588f6cfdddfb057bb5aad88735da0) has no such function. shadPS4 (3a9f57265d28975658f1ca941daa5c879c7a74bd, src/core/libraries/kernel/threads/condvar.cpp:229-234, 445, 467-468) registers posix_pthread_cond_reltimedwait_np(cond, mutex, u64 usec) for this NID and the same function for scePthreadCondTimedwait (BmMjYxmew1w), whose microsecond argument this repository already implements. The width follows the SCE twin's 32-bit SceKernelUseconds, as sem_reltimedwait_np does, not shadPS4's u64. There is no timespec to validate, so no EINVAL case. Recorded in TechnicalDebt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit c86ec8417d12f5b12136b0f06eae06493b0e7a62)
This commit is contained in:
parent
092f7ef00a
commit
774f21845d
|
|
@ -41,6 +41,10 @@ int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex
|
|||
return toPosix(CondOperations::AbsoluteTimedwait(cond, mutex, abstime));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_cond_reltimedwait_np_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec) {
|
||||
return toPosix(scePthreadCondTimedwait(cond, mutex, usec));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex) {
|
||||
return toPosix(scePthreadCondWait(cond, mutex));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
|
|
@ -10,9 +11,12 @@ 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 scePthreadMutexTrylock(PthreadMutex* mutex);
|
||||
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
|
||||
int APS5_VABI scePthreadCondDestroy(PthreadCond* cond);
|
||||
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec);
|
||||
int APS5_VABI scePthreadCondSignal(PthreadCond* cond);
|
||||
int APS5_VABI pthread_cond_reltimedwait_np_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec);
|
||||
int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *)(void*), void*, void*);
|
||||
void APS5_VABI __pthread_cxa_finalize_nid_postfix(void*);
|
||||
unsigned int APS5_VABI sceKernelSleep(unsigned int seconds);
|
||||
|
|
@ -23,6 +27,9 @@ static void APS5_VABI Cleanup(void*) { cleanupAt = 0; }
|
|||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast<int>(0x8002003C);
|
||||
static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast<int>(0x80020010);
|
||||
static constexpr int POSIX_EPERM = 1;
|
||||
static constexpr int POSIX_ETIMEDOUT = 60;
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
|
|
@ -39,7 +46,67 @@ static void* APS5_VABI Contender(void* arg) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
struct Signalled {
|
||||
PthreadMutex mutex = nullptr;
|
||||
PthreadCond cond = nullptr;
|
||||
bool ready = false;
|
||||
int trylock = SCE_OK;
|
||||
};
|
||||
|
||||
static void* APS5_VABI Trylocker(void* arg) {
|
||||
auto& signalled = *static_cast<Signalled*>(arg);
|
||||
signalled.trylock = scePthreadMutexTrylock(&signalled.mutex);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void* APS5_VABI Signaller(void* arg) {
|
||||
auto& signalled = *static_cast<Signalled*>(arg);
|
||||
Require(scePthreadMutexLock(&signalled.mutex) == SCE_OK);
|
||||
signalled.ready = true;
|
||||
Require(scePthreadCondSignal(&signalled.cond) == SCE_OK);
|
||||
Require(scePthreadMutexUnlock(&signalled.mutex) == SCE_OK);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void RequireHeldByCaller(Signalled& signalled) {
|
||||
Pthread thread = nullptr;
|
||||
Require(scePthreadCreate(&thread, nullptr, Trylocker, &signalled, nullptr) == SCE_OK);
|
||||
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
|
||||
Require(signalled.trylock == SCE_KERNEL_ERROR_EBUSY);
|
||||
}
|
||||
|
||||
static void RelativeWait() {
|
||||
Signalled signalled;
|
||||
Require(scePthreadMutexInit(&signalled.mutex, nullptr, nullptr) == SCE_OK);
|
||||
Require(scePthreadCondInit(&signalled.cond, nullptr, nullptr) == SCE_OK);
|
||||
|
||||
Require(pthread_cond_reltimedwait_np_nid_postfix(&signalled.cond, &signalled.mutex, 1000) == POSIX_EPERM);
|
||||
|
||||
Require(scePthreadMutexLock(&signalled.mutex) == SCE_OK);
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
Require(pthread_cond_reltimedwait_np_nid_postfix(&signalled.cond, &signalled.mutex, 50000) == POSIX_ETIMEDOUT);
|
||||
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40));
|
||||
RequireHeldByCaller(signalled);
|
||||
Require(pthread_cond_reltimedwait_np_nid_postfix(&signalled.cond, &signalled.mutex, 0) == POSIX_ETIMEDOUT);
|
||||
RequireHeldByCaller(signalled);
|
||||
|
||||
Pthread thread = nullptr;
|
||||
Require(scePthreadCreate(&thread, nullptr, Signaller, &signalled, nullptr) == SCE_OK);
|
||||
const auto waitStart = std::chrono::steady_clock::now();
|
||||
while (!signalled.ready)
|
||||
Require(pthread_cond_reltimedwait_np_nid_postfix(&signalled.cond, &signalled.mutex, 10000000) == 0);
|
||||
Require(std::chrono::steady_clock::now() - waitStart < std::chrono::seconds(5));
|
||||
RequireHeldByCaller(signalled);
|
||||
Require(scePthreadMutexUnlock(&signalled.mutex) == SCE_OK);
|
||||
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
|
||||
|
||||
Require(scePthreadCondDestroy(&signalled.cond) == SCE_OK);
|
||||
Require(scePthreadMutexDestroy(&signalled.mutex) == SCE_OK);
|
||||
}
|
||||
|
||||
int main() {
|
||||
RelativeWait();
|
||||
|
||||
Context context;
|
||||
PthreadCond cond = nullptr;
|
||||
Require(scePthreadMutexInit(&context.mutex, nullptr, nullptr) == SCE_OK);
|
||||
|
|
|
|||
|
|
@ -626,3 +626,4 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceSharePlayGetCurrentConnectionInfoA](../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - reports no Share Play session, as the library keeps none: status 0 (DORMANT), mode 0, empty online IDs, account IDs 0 and both user IDs -1 (`SCE_USER_SERVICE_USER_ID_INVALID`). A null pointer returns `SCE_SHARE_PLAY_ERROR_INVALID_ARGS` (0x810E0001). The 72-byte `SceSharePlayConnectionInfoA` layout and the error code follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libNet.cpp) and [fpPS4](https://github.com/red-prig/fpPS4/blob/trunk/src/ps4_libsceshareplay.pas), which agree on both; KytyPS5 sets the user IDs to -1 and fpPS4 leaves them 0, and which one the console returns is not verified. KytyPS5 also returns `SCE_SHARE_PLAY_ERROR_NOT_INITIALIZED` before `sceSharePlayInitialize`; this library keeps no initialization state and does not.
|
||||
- [sceAgcCreatePrimState](../../core/libs/prx/libSceAgc/Shader/src/PrimState.cpp) (libSceAgc) - the `ge_user_vgpr_en` special is passed to the user configuration registers at whatever offset the shader header stores, as the SPRX does according to [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L1181-L1252), which copies all four specials verbatim (KytyPS5 stops on any other offset). PPSA99039, PPSA99505, PPSA99515 and PPSA99997 build their headers by hand with it at 0x25c (`GE_USER_VGPR1`) instead of 0x262 (`GE_USER_VGPR_EN`), value 0, and run on the console; it is not moved to 0x262, and the driver reads neither register. `ge_cntl`, `vgt_shader_stages_en` and a used `vgt_gs_out_prim_type` must still be at their own offsets, since the driver reads those registers there, so a header that stores one elsewhere throws naming the offset, although by OpenAGC the SPRX would copy it as well
|
||||
- [pthread_spin_init, pthread_spin_destroy, pthread_spin_lock, pthread_spin_trylock, pthread_spin_unlock](../../core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp) (libkernel) follow FreeBSD 11.4 libthr [`thr_pspinlock.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_pspinlock.c) and the owner checks of its [`thr_umtx.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_umtx.h#L80-L175), unverified on the console: `EINVAL` for a null or destroyed lock and a `pshared` other than 0 or 1, `EBUSY` from `trylock` while any thread holds it, `EPERM` from `unlock` by a thread that does not hold it. `PTHREAD_PROCESS_SHARED` allocates a private lock, as guest code runs in one process. libthr spins forever when the holder locks again; here that call throws
|
||||
- [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
|
||||
|
|
|
|||
Loading…
Reference in New Issue