From 74c3fb657699176081bbbbf96823f3179a8bea11 Mon Sep 17 00:00:00 2001 From: screamm Date: Mon, 12 Oct 2026 14:14:40 +0200 Subject: [PATCH] feat(libkernel): implement pthread_setaffinity_np PS5CEMU-HAR (PPSA99360, ps5-payload-dev build, relinks with --trap-sdk-syscall-stubs) imports pthread_setaffinity_np (5KWrg7-ZqvE) from libkernel.prx. babyhuey's run with #3787 merged onto main e7c70a2d (PR #3787, 2026-10-12T10:54:44Z) stops at it: Windows: FAIL: unresolved ELF import 5KWrg7-ZqvE (pthread_setaffinity_np) from libkernel.prx Linux: undefined symbol: 5KWrg7-ZqvE (pthread_setaffinity_np) It follows FreeBSD 11.4 libthr thr_affinity.c (8c639945) over kern_cpuset_setaffinity: another thread is looked up first (EINVAL for null, ESRCH when unknown or finished, as _thr_find_thread without dead threads), a size other than the 32-byte cpuset_t returns ERANGE, the mask is clipped to sceKernelGetAvailableCpumask and an empty result returns EDEADLK, as cpuset_setaffinity does. The affinity is stored on the guest thread through scePthreadSetaffinity, for the caller and for any live guest thread. Co-Authored-By: Claude Opus 5.5 --- .../prx/libkernel/Pthread/Posix/Thread.cpp | 15 ++++++++ core/libs/tests/GuestAvailableCpumask.cpp | 35 +++++++++++++++++++ docs/dev/debt/libkernel.md | 1 + 3 files changed, 51 insertions(+) diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index 38eef15ff..90ad038f8 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -163,6 +163,21 @@ int APS5_VABI pthread_getaffinity_np_nid_postfix(Pthread thread, std::size_t cpu return 0; } +int APS5_VABI pthread_setaffinity_np_nid_postfix(Pthread thread, std::size_t cpusetSize, const void* cpuset) { + constexpr int GuestEdeadlk = 11; + if (thread != scePthreadSelf()) { + if (!thread) return PosixThread::GUEST_EINVAL; + if (!GuestThreadIsLive(thread)) return PosixThread::GUEST_ESRCH; + } + if (cpusetSize != GUEST_CPUSET_BYTES) return GUEST_ERANGE; + if (!cpuset) return PosixThread::GUEST_EFAULT; + KernelCpumask requested = 0; + std::memcpy(&requested, cpuset, sizeof(requested)); + const KernelCpumask affinity = requested & sceKernelGetAvailableCpumask(); + if (affinity == 0) return GuestEdeadlk; + return PosixThread::ToErrno(scePthreadSetaffinity(thread, affinity)); +} + int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** value) { return PosixThread::ToErrno(scePthreadJoin(thread, value)); } diff --git a/core/libs/tests/GuestAvailableCpumask.cpp b/core/libs/tests/GuestAvailableCpumask.cpp index f15d13e2e..76b09054a 100644 --- a/core/libs/tests/GuestAvailableCpumask.cpp +++ b/core/libs/tests/GuestAvailableCpumask.cpp @@ -26,6 +26,7 @@ int APS5_VABI getpid_nid_postfix(void); int* APS5_VABI __error_nid_postfix(); Pthread APS5_VABI pthread_self_nid_postfix(void); int APS5_VABI pthread_getaffinity_np_nid_postfix(Pthread thread, std::size_t cpusetSize, void* cpuset); +int APS5_VABI pthread_setaffinity_np_nid_postfix(Pthread thread, std::size_t cpusetSize, const void* cpuset); int APS5_VABI scePthreadGetthreadid(void); } @@ -46,6 +47,7 @@ struct RestoreProcessMask { static constexpr std::size_t GUEST_CPUSET_BYTES = 32; static constexpr int GUEST_ESRCH = 3; +static constexpr int GUEST_EDEADLK = 11; static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_EFAULT = 14; static constexpr int GUEST_ERANGE = 34; @@ -236,6 +238,39 @@ int main() { Require(unionResult == 0 && unionSet[0] == (first | last)); Require(ownResult == 0 && ownSet[0] == last); } + std::uint64_t request[GUEST_CPUSET_BYTES / sizeof(std::uint64_t)] = {first, 0, 0, 0}; + Require(pthread_setaffinity_np_nid_postfix(pthread_self_nid_postfix(), GUEST_CPUSET_BYTES, request) == 0); + Require(ReadCpuset(pthread_self_nid_postfix()) == first); + std::promise settle; + auto settled = settle.get_future(); + Pthread target = nullptr; + Require(scePthreadCreate(&target, &attr, Worker, &settled, nullptr) == SCE_OK); + request[0] = last; + Require(pthread_setaffinity_np_nid_postfix(target, GUEST_CPUSET_BYTES, request) == 0); + Require(ReadCpuset(target) == last && ReadCpuset(pthread_self_nid_postfix()) == first); + request[0] = available | (1ull << 40); + request[3] = 1ull << 63; + Require(pthread_setaffinity_np_nid_postfix(target, GUEST_CPUSET_BYTES, request) == 0); + Require(ReadCpuset(target) == available); + const std::uint64_t outside[GUEST_CPUSET_BYTES / sizeof(std::uint64_t)] = {1ull << 40, 1, 0, 0}; + Require(pthread_setaffinity_np_nid_postfix(target, GUEST_CPUSET_BYTES, outside) == GUEST_EDEADLK); + Require(pthread_setaffinity_np_nid_postfix(pthread_self_nid_postfix(), GUEST_CPUSET_BYTES, outside) == GUEST_EDEADLK); + Require(ReadCpuset(target) == available && ReadCpuset(pthread_self_nid_postfix()) == first); + for (const std::size_t size : {std::size_t{0}, std::size_t{8}, std::size_t{31}, std::size_t{33}, std::size_t{64}}) { + Require(pthread_setaffinity_np_nid_postfix(pthread_self_nid_postfix(), size, request) == GUEST_ERANGE); + Require(pthread_setaffinity_np_nid_postfix(target, size, request) == GUEST_ERANGE); + } + Require(pthread_setaffinity_np_nid_postfix(target, GUEST_CPUSET_BYTES, nullptr) == GUEST_EFAULT); + Require(pthread_setaffinity_np_nid_postfix(nullptr, GUEST_CPUSET_BYTES, request) == GUEST_EINVAL); + std::uint64_t unknownThread[8]{}; + Require(pthread_setaffinity_np_nid_postfix(reinterpret_cast(unknownThread), GUEST_CPUSET_BYTES, request) == GUEST_ESRCH); + Require(pthread_setaffinity_np_nid_postfix(reinterpret_cast(unknownThread), 8, request) == GUEST_ESRCH); + settle.set_value(); + Require(scePthreadJoin(target, nullptr) == SCE_OK); + Require(pthread_setaffinity_np_nid_postfix(target, GUEST_CPUSET_BYTES, request) == GUEST_ESRCH); + request[0] = available; + request[3] = 0; + Require(pthread_setaffinity_np_nid_postfix(pthread_self_nid_postfix(), GUEST_CPUSET_BYTES, request) == 0); Require(scePthreadAttrDestroy(&attr) == SCE_OK); Require(ReadCpuset(pthread_self_nid_postfix()) == available); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index b54b27aba..5f2eb9796 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -129,6 +129,7 @@ - [Thread cancellation](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) follows FreeBSD libthr (states and types 0/1 and 0/2, `PTHREAD_CANCELED` 1, the `_pthread_cleanup_info` node layout), not measured on the console. A request is acted on only in `scePthreadTestcancel`, the condition variable, `sem` and `scePthreadSem` waits, `scePthreadJoin` and the sleeps, and their POSIX names; file and socket calls are not cancellation points. A sleep is not interrupted: the thread exits when it ends. The asynchronous type acts like the deferred one, except at the two set calls. - [cpuset_setaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled; other levels (ROOT/CPUSET), other scopes (CPUSET/JAIL) and other threads throw as unmodelled, invalid levels or scopes return `EINVAL` and unknown thread or process IDs return `ESRCH`, following FreeBSD's `cpuset_setaffinity`. Bits outside `sceKernelGetAvailableCpumask` are dropped, and a mask with none of those bits fails with `EDEADLK`, as FreeBSD does when the new set has no CPU in common with the parent set - [cpuset_getaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled; other levels (ROOT/CPUSET), other scopes (CPUSET/JAIL) and other threads throw as unmodelled, invalid levels or scopes return `EINVAL` and unknown thread or process IDs return `ESRCH`, following FreeBSD's `cpuset_getaffinity`. For the process the mask is the union of the affinities of the live guest threads, as [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/kern_cpuset.c#L1150-L1156) ORs the mask of every thread in the process; threads the guest did not create through libkernel are not included, and the console result is unverified +- [pthread_setaffinity_np](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - follows [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_affinity.c#L41-L62) libthr over `kern_cpuset_setaffinity` for the calling thread and any live guest thread: a size other than the 32-byte `cpuset_t` returns `ERANGE`, an unknown or finished thread `ESRCH`. Bits outside `sceKernelGetAvailableCpumask` are dropped and a mask with none of those bits returns `EDEADLK`, as `cpuset_setaffinity` does; the affinity is recorded on the guest thread and not applied to the host thread, and the console result is unverified - [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` of a guest socket onto descriptors 0 to 2, whose host streams libc writes without the socket table, and on Windows duplicating a directory descriptor. Guest sockets share one socket object between the duplicates; the numbers come from the host descriptor table like those of files. The errno values for descriptor exhaustion are not verified against the console. - [waitpid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns -1 with `ECHILD` (`EINVAL` for unknown options): nothing here creates a child process; child processes would need a real `waitpid`. - [execv](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported, the same as `execvp`. PPSA97358 imports it and fails to load without it.