From 2124cfba5f1bf0b60100f50dc6c4245cde5c1936 Mon Sep 17 00:00:00 2001 From: Semir Kabir <66262518+semirkabir@users.noreply.github.com> Date: Sun, 11 Oct 2026 16:43:36 +0000 Subject: [PATCH] feat(libkernel): report the process affinity from cpuset_getaffinity --- .../prx/libkernel/Pthread/Posix/Thread.cpp | 12 ++++++--- .../prx/libkernel/Pthread/include/Pthread.hpp | 1 + .../libs/prx/libkernel/Pthread/src/Thread.cpp | 10 ++++++++ core/libs/tests/GuestAvailableCpumask.cpp | 25 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index 11e577c3c..37beb1141 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -40,20 +40,26 @@ KernelCpumask APS5_VABI sceKernelGetAvailableCpumask(void); int APS5_VABI getpid_nid_postfix(void); int APS5_VABI cpuset_getaffinity_nid_postfix(int level, int which, std::int64_t id, std::size_t size, void* mask) { - constexpr int LevelWhich = 3, WhichThread = 1; + constexpr int LevelWhich = 3, WhichThread = 1, WhichProcess = 2; constexpr int GuestEfault = 14, GuestErange = 34; constexpr std::size_t MaximumSize = 256 / 8; if (size < sizeof(KernelCpumask) || size > MaximumSize) { *__error_nid_postfix() = GuestErange; return -1; } - if (level != LevelWhich || which != WhichThread || id != -1) NotImplemented_nid_no_patch("cpuset_getaffinity other than the calling thread"); + const bool thread = level == LevelWhich && which == WhichThread && id == -1; + const bool process = level == LevelWhich && which == WhichProcess && (id == -1 || id == getpid_nid_postfix()); + if (!thread && !process) NotImplemented_nid_no_patch("cpuset_getaffinity other than the calling thread or process"); if (!mask) { *__error_nid_postfix() = GuestEfault; return -1; } KernelCpumask affinity = 0; - scePthreadGetaffinity(scePthreadSelf(), &affinity); + if (thread) scePthreadGetaffinity(scePthreadSelf(), &affinity); + else { + scePthreadSelf(); + affinity = LiveGuestThreadsAffinity(); + } std::memset(mask, 0, size); std::memcpy(mask, &affinity, sizeof(affinity)); return 0; diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index f2df5c9b2..05177ac38 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -151,5 +151,6 @@ void NoteGuestStackProtection(std::uintptr_t start, std::size_t bytes, bool read int GuestThreadCpuClockId(const PthreadPrivate* thread); bool GuestThreadCpuNanos(int cpuClockThread, std::uint64_t* nanos); void SetLiveGuestThreadsAffinity(KernelCpumask mask); +KernelCpumask LiveGuestThreadsAffinity(); #endif diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 11400bdd2..e82077db5 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -240,6 +240,16 @@ void SetLiveGuestThreadsAffinity(KernelCpumask mask) { } } +KernelCpumask LiveGuestThreadsAffinity() { + auto& registry = CpuClocks(); + std::lock_guard lock(registry.lock); + KernelCpumask mask = 0; + for (const auto& [id, thread] : registry.threads) { + if (!thread->_finished.load(std::memory_order_acquire)) mask |= thread->affinity.load(std::memory_order_relaxed); + } + return mask; +} + bool GuestThreadCpuNanos(int cpuClockThread, std::uint64_t* nanos) { auto& registry = CpuClocks(); std::lock_guard lock(registry.lock); diff --git a/core/libs/tests/GuestAvailableCpumask.cpp b/core/libs/tests/GuestAvailableCpumask.cpp index 6485fcffe..cf40f562b 100644 --- a/core/libs/tests/GuestAvailableCpumask.cpp +++ b/core/libs/tests/GuestAvailableCpumask.cpp @@ -198,6 +198,31 @@ int main() { Require(changed && restored && created && joined && inherited[0] == 0 && inherited[1] == last); Require(explicitSet && explicitCreated && explicitJoined && explicitAffinity[0] == 0 && explicitAffinity[1] == available); } + + std::uint64_t processSet[4] = {~0ull, ~0ull, ~0ull, ~0ull}; + Require(cpuset_getaffinity_nid_postfix(3, 2, -1, sizeof(processSet), processSet) == 0); + Require(processSet[0] == available && processSet[1] == 0 && processSet[2] == 0 && processSet[3] == 0); + processSet[0] = 0; + Require(cpuset_getaffinity_nid_postfix(3, 2, getpid_nid_postfix(), 8, processSet) == 0 && processSet[0] == available); + Require(cpuset_getaffinity_nid_postfix(3, 2, -1, 4, processSet) == -1 && *__error_nid_postfix() == GUEST_ERANGE); + Require(cpuset_getaffinity_nid_postfix(3, 2, -1, 8, nullptr) == -1 && *__error_nid_postfix() == GUEST_EFAULT); + if (first != last) { + std::promise park; + auto parked = park.get_future(); + Pthread pinned = nullptr; + Require(scePthreadCreate(&pinned, &attr, Worker, &parked, nullptr) == SCE_OK); + Require(scePthreadSetaffinity(pinned, first) == SCE_OK); + Require(scePthreadSetaffinity(scePthreadSelf(), last) == SCE_OK); + std::uint64_t unionSet[1] = {0}; + const int unionResult = cpuset_getaffinity_nid_postfix(3, 2, -1, 8, unionSet); + std::uint64_t ownSet[1] = {0}; + const int ownResult = cpuset_getaffinity_nid_postfix(3, 1, -1, 8, ownSet); + Require(scePthreadSetaffinity(scePthreadSelf(), available) == SCE_OK); + park.set_value(); + Require(scePthreadJoin(pinned, nullptr) == SCE_OK); + Require(unionResult == 0 && unionSet[0] == (first | last)); + Require(ownResult == 0 && ownSet[0] == last); + } Require(scePthreadAttrDestroy(&attr) == SCE_OK); Require(ReadCpuset(pthread_self_nid_postfix()) == available); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..838cbdc14 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -406,6 +406,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. - [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. 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. 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 - [dup and dup2](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` between a guest socket and a host descriptor in either direction, and on Windows duplicating a directory descriptor. Guest sockets share one socket object between the duplicates and the descriptor numbers come from the guest socket counter, not the lowest free number as in FreeBSD `kern_dup`. The errno values for descriptor exhaustion are not verified against the console. - [Cancellation coverage](ARCHITECTURE.md#thread-cancellation) checks deferred request handling and recovery, not full asynchronous support or console ABI compatibility. Cancellation during file/socket I/O, sleep interruption, cancellation versus exit/detach, condition signal conservation under cancellation, and safe rejection of non-null invalid or reclaimed thread handles remain unverified. No console title or homebrew establishes the cleanup NIDs or buffer layout. Accepted asynchronous mode currently has no diagnostic for its deferred behavior. - [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, the LOD warning (texture-streaming feedback, like the T# `MIN_LOD_WARN` itself) is never reported, and `lwe` on `image_get_lod` throws.