#include "SceTypes.hpp" #include #include #include #include #include extern "C" { 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 scePthreadSetprio(Pthread thread, int prio); int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask); int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr); int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched); int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param); int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask); int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize); int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param); int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask* mask); int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, std::size_t* stackSize); int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state); int APS5_VABI scePthreadAttrGetsolosched(const PthreadAttr* attr, int* solosched); int APS5_VABI pthread_attr_setstacksize_nid_postfix(PthreadAttr* attr, std::size_t stackSize); int APS5_VABI pthread_attr_setsolosched_np_nid_postfix(PthreadAttr* attr, int solosched); int APS5_VABI pthread_attr_getinheritsched_nid_postfix(const PthreadAttr* attr, int* inherit_sched); int APS5_VABI pthread_attr_setinheritsched_nid_postfix(PthreadAttr* attr, int inherit_sched); int APS5_VABI pthread_attr_getscope_nid_postfix(const PthreadAttr* attr, int* scope); int APS5_VABI pthread_attr_setscope_nid_postfix(PthreadAttr* attr, int scope); int APS5_VABI pthread_attr_get_np_nid_postfix(Pthread thread, PthreadAttr* attr); int APS5_VABI pthread_attr_setschedparam_nid_postfix(PthreadAttr* attr, const KernelSchedParam* param); int APS5_VABI pthread_attr_setschedpolicy_nid_postfix(PthreadAttr* attr, int policy); int APS5_VABI pthread_attr_getschedpolicy_nid_postfix(const PthreadAttr* attr, int* policy); int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int state); int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); int APS5_VABI scePthreadKeyDelete(PthreadKey key); int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); Pthread APS5_VABI scePthreadSelf(); } static constexpr int SCE_OK = 0; static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int EXPLICIT_SCHED = 0; static constexpr int INHERIT_SCHED = 4; static constexpr int SCOPE_PROCESS = 0; static constexpr int SCOPE_SYSTEM = 2; static constexpr int DETACH_JOINABLE = 0; static constexpr int CREATION_PRIORITY = 256; static constexpr int UPDATED_PRIORITY = 767; static constexpr KernelCpumask CREATION_AFFINITY = 0x3; static constexpr KernelCpumask UPDATED_AFFINITY = 0x1000; static constexpr std::size_t STACK_SIZE = 2u << 20; static constexpr std::size_t MIN_STACK_SIZE = 16384; static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_ENOTSUP = 45; static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); static constexpr int DETACH_DETACHED = 1; static constexpr int DETACHED_THREADS = 32; static void Require(bool value) { if (!value) std::abort(); } struct ReportedAttributes { int priority = 0; KernelCpumask affinity = 0; std::size_t stackSize = 0; int detachState = -1; }; extern "C" { int APS5_VABI pthread_attr_setstack_nid_postfix(PthreadAttr* attr, void* addr, std::size_t size); int APS5_VABI pthread_attr_getstack_nid_postfix(const PthreadAttr* attr, void** addr, std::size_t* size); int APS5_VABI pthread_attr_getstackaddr_nid_postfix(const PthreadAttr* attr, void** addr); } static ReportedAttributes Query(Pthread thread) { PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK); Require(scePthreadAttrGet(thread, &attr) == SCE_OK); ReportedAttributes reported; KernelSchedParam param{}; Require(scePthreadAttrGetschedparam(&attr, ¶m) == SCE_OK); reported.priority = param.sched_priority; Require(scePthreadAttrGetaffinity(&attr, &reported.affinity) == SCE_OK); Require(scePthreadAttrGetstacksize(&attr, &reported.stackSize) == SCE_OK); Require(scePthreadAttrGetdetachstate(&attr, &reported.detachState) == SCE_OK); Require(scePthreadAttrDestroy(&attr) == SCE_OK); return reported; } static void CheckStackSizeLimit() { PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK); std::size_t stackSize = 0; Require(scePthreadAttrSetstacksize(&attr, MIN_STACK_SIZE) == SCE_OK); Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK && stackSize == MIN_STACK_SIZE); Require(scePthreadAttrSetstacksize(&attr, MIN_STACK_SIZE - 1) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrSetstacksize(&attr, 0) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_attr_setstacksize_nid_postfix(&attr, MIN_STACK_SIZE - 1) == GUEST_EINVAL); Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK && stackSize == MIN_STACK_SIZE); Require(scePthreadAttrDestroy(&attr) == SCE_OK); } extern "C" int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched); static void CheckSoloSched() { PthreadAttr attr = nullptr; Require(pthread_attr_setsolosched_np_nid_postfix(nullptr, 1) == GUEST_EINVAL); Require(pthread_attr_setsolosched_np_nid_postfix(&attr, 1) == GUEST_EINVAL); Require(scePthreadAttrInit(&attr) == SCE_OK); int soloSched = -1; Require(scePthreadAttrGetsolosched(&attr, &soloSched) == SCE_OK && soloSched == 0); Require(pthread_attr_setsolosched_np_nid_postfix(&attr, 16) == 0); Require(scePthreadAttrGetsolosched(&attr, &soloSched) == SCE_OK && soloSched == 16); constexpr int GuestEopnotsupp = 45; constexpr int SceKernelErrorEopnotsupp = static_cast(0x8002002Du); for (int rejected : {-1, 1, 2, 4, 8, 15, 17, 32, 100, 0x7fffffff}) { Require(pthread_attr_setsolosched_np_nid_postfix(&attr, rejected) == GuestEopnotsupp); Require(scePthreadAttrSetsolosched(&attr, rejected) == SceKernelErrorEopnotsupp); Require(scePthreadAttrGetsolosched(&attr, &soloSched) == SCE_OK && soloSched == 16); } Require(pthread_attr_setsolosched_np_nid_postfix(&attr, 0) == 0); Require(scePthreadAttrGetsolosched(&attr, &soloSched) == SCE_OK && soloSched == 0); PthreadAttr unset = nullptr; Require(scePthreadAttrSetsolosched(&unset, 16) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrSetsolosched(nullptr, 16) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrGetsolosched(&unset, &soloSched) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrDestroy(&attr) == SCE_OK); Require(pthread_attr_setsolosched_np_nid_postfix(&attr, 1) == GUEST_EINVAL); } static void CheckSchedArguments() { constexpr int SchedFifo = 1; constexpr int SchedOther = 2; constexpr int SchedRr = 3; PthreadAttr attr = nullptr; PthreadAttr unset = nullptr; KernelSchedParam param{CREATION_PRIORITY}; Require(pthread_attr_setschedparam_nid_postfix(nullptr, ¶m) == GUEST_EINVAL); Require(pthread_attr_setschedparam_nid_postfix(&unset, ¶m) == GUEST_EINVAL); Require(pthread_attr_setschedpolicy_nid_postfix(&unset, SchedRr) == GUEST_EINVAL); Require(scePthreadAttrInit(&attr) == SCE_OK); Require(pthread_attr_setschedparam_nid_postfix(&attr, nullptr) == GUEST_ENOTSUP); for (const int policy : {SchedFifo, SchedRr}) { Require(pthread_attr_setschedpolicy_nid_postfix(&attr, policy) == 0); Require(pthread_attr_setschedparam_nid_postfix(&attr, ¶m) == 0); for (const int rejected : {0, 1, CREATION_PRIORITY - 1, UPDATED_PRIORITY + 1, 1000}) { KernelSchedParam outside{rejected}; Require(pthread_attr_setschedparam_nid_postfix(&attr, &outside) == GUEST_ENOTSUP); KernelSchedParam reported{-1}; Require(scePthreadAttrGetschedparam(&attr, &reported) == SCE_OK && reported.sched_priority == CREATION_PRIORITY); } KernelSchedParam highest{UPDATED_PRIORITY}; Require(pthread_attr_setschedparam_nid_postfix(&attr, &highest) == 0); Require(pthread_attr_setschedparam_nid_postfix(&attr, ¶m) == 0); } Require(pthread_attr_setschedpolicy_nid_postfix(&attr, SchedOther) == 0); KernelSchedParam other{1000}; Require(pthread_attr_setschedparam_nid_postfix(&attr, &other) == 0); int policy = -1; for (const int rejected : {-1, 0, 4, 100}) { Require(pthread_attr_setschedpolicy_nid_postfix(&attr, rejected) == GUEST_ENOTSUP); Require(pthread_attr_getschedpolicy_nid_postfix(&attr, &policy) == 0 && policy == SchedOther); } Require(scePthreadAttrDestroy(&attr) == SCE_OK); } static void* APS5_VABI Worker(void* arg) { static_cast*>(arg)->get(); return nullptr; } static void CheckSchedulingAttributes() { int inherit = -1; int scope = -1; Require(pthread_attr_getinheritsched_nid_postfix(nullptr, &inherit) == GUEST_EINVAL); Require(pthread_attr_setinheritsched_nid_postfix(nullptr, EXPLICIT_SCHED) == GUEST_EINVAL); Require(pthread_attr_getscope_nid_postfix(nullptr, &scope) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(nullptr, SCOPE_SYSTEM) == GUEST_EINVAL); PthreadAttr attr = nullptr; Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == GUEST_EINVAL); Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == GUEST_EINVAL); Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_SYSTEM) == GUEST_EINVAL); Require(scePthreadAttrInit(&attr) == SCE_OK); Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == 0 && inherit == INHERIT_SCHED); Require(pthread_attr_setinheritsched_nid_postfix(&attr, 1) == GUEST_ENOTSUP); Require(pthread_attr_setinheritsched_nid_postfix(&attr, -1) == GUEST_ENOTSUP); Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == 0); Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == 0 && inherit == EXPLICIT_SCHED); Require(pthread_attr_setinheritsched_nid_postfix(&attr, INHERIT_SCHED) == 0); Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == 0 && inherit == INHERIT_SCHED); Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); Require(pthread_attr_setscope_nid_postfix(&attr, 1) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(&attr, 3) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(&attr, -1) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_PROCESS) == 0); Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == 0 && scope == SCOPE_PROCESS); Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_SYSTEM) == 0); Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); Require(pthread_attr_getscope_nid_postfix(&attr, nullptr) == GUEST_EINVAL); bool threw = false; try { pthread_attr_getinheritsched_nid_postfix(&attr, nullptr); } catch (const std::exception&) { threw = true; } Require(threw); Require(scePthreadAttrDestroy(&attr) == SCE_OK); Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == GUEST_EINVAL); Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == GUEST_EINVAL); Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_SYSTEM) == GUEST_EINVAL); } static void CheckThreadScope() { PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK); Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == 0); Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_PROCESS) == 0); std::promise release; auto released = release.get_future(); Pthread thread = nullptr; Require(scePthreadCreate(&thread, &attr, Worker, &released, nullptr) == SCE_OK); Require(scePthreadAttrDestroy(&attr) == SCE_OK); PthreadAttr queryAttr = nullptr; Require(scePthreadAttrInit(&queryAttr) == SCE_OK); Require(scePthreadAttrGet(thread, &queryAttr) == SCE_OK); int scope = -1; Require(pthread_attr_getscope_nid_postfix(&queryAttr, &scope) == 0 && scope == SCOPE_PROCESS); Require(pthread_attr_get_np_nid_postfix(thread, &queryAttr) == 0); Require(pthread_attr_getscope_nid_postfix(&queryAttr, &scope) == 0 && scope == SCOPE_PROCESS); Require(scePthreadAttrDestroy(&queryAttr) == SCE_OK); release.set_value(); Require(scePthreadJoin(thread, nullptr) == SCE_OK); Require(scePthreadAttrInit(&attr) == SCE_OK); Require(pthread_attr_setinheritsched_nid_postfix(&attr, INHERIT_SCHED) == 0); Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_PROCESS) == 0); std::promise releaseInherited; auto releasedInherited = releaseInherited.get_future(); Pthread inheritedThread = nullptr; Require(scePthreadCreate(&inheritedThread, &attr, Worker, &releasedInherited, nullptr) == SCE_OK); Require(scePthreadAttrDestroy(&attr) == SCE_OK); Require(scePthreadAttrInit(&queryAttr) == SCE_OK); Require(scePthreadAttrGet(inheritedThread, &queryAttr) == SCE_OK); scope = -1; Require(pthread_attr_getscope_nid_postfix(&queryAttr, &scope) == 0 && scope == SCOPE_SYSTEM); Require(pthread_attr_get_np_nid_postfix(inheritedThread, &queryAttr) == 0); Require(pthread_attr_getscope_nid_postfix(&queryAttr, &scope) == 0 && scope == SCOPE_SYSTEM); Require(scePthreadAttrDestroy(&queryAttr) == SCE_OK); releaseInherited.set_value(); Require(scePthreadJoin(inheritedThread, nullptr) == SCE_OK); } static int QueryResult(Pthread thread) { PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK); Require(scePthreadAttrSetstacksize(&attr, STACK_SIZE) == SCE_OK); const int result = scePthreadAttrGet(thread, &attr); std::size_t stackSize = 0; Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK); Require(result == SCE_OK || stackSize == STACK_SIZE); Require(scePthreadAttrDestroy(&attr) == SCE_OK); return result; } static bool ReportedEnded(Pthread thread) { const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); while (QueryResult(thread) == SCE_OK) { if (std::chrono::steady_clock::now() > deadline) return false; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } return QueryResult(thread) == SCE_KERNEL_ERROR_ESRCH; } struct ExitQuery { PthreadKey key = -1; int result = -1; }; static void APS5_VABI QuerySelfAtExit(void* value) { auto* query = static_cast(value); query->result = QueryResult(scePthreadSelf()); } static void* APS5_VABI SetExitQuery(void* arg) { auto* query = static_cast(arg); Require(scePthreadSetspecific(query->key, query) == SCE_OK); return nullptr; } static void* APS5_VABI Finish(void*) { return nullptr; } static void CheckEndedThreads() { Require(QueryResult(scePthreadSelf()) == SCE_OK); Pthread joined = nullptr; Require(scePthreadCreate(&joined, nullptr, Finish, nullptr, nullptr) == SCE_OK); Require(scePthreadJoin(joined, nullptr) == SCE_OK); Require(QueryResult(joined) == SCE_KERNEL_ERROR_ESRCH); PthreadAttr unset = nullptr; Require(scePthreadAttrGet(joined, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrGet(joined, &unset) == SCE_KERNEL_ERROR_EINVAL); ExitQuery exitQuery; Require(scePthreadKeyCreate(&exitQuery.key, reinterpret_cast(QuerySelfAtExit)) == SCE_OK); Pthread finished = nullptr; Require(scePthreadCreate(&finished, nullptr, SetExitQuery, &exitQuery, nullptr) == SCE_OK); Require(ReportedEnded(finished)); Require(scePthreadJoin(finished, nullptr) == SCE_OK); Require(exitQuery.result == SCE_OK); Require(QueryResult(finished) == SCE_KERNEL_ERROR_ESRCH); Require(scePthreadKeyDelete(exitQuery.key) == SCE_OK); PthreadAttr detachedAttr = nullptr; Require(scePthreadAttrInit(&detachedAttr) == SCE_OK); Require(scePthreadAttrSetdetachstate(&detachedAttr, DETACH_DETACHED) == SCE_OK); for (int index = 0; index < DETACHED_THREADS; ++index) { Pthread detached = nullptr; Require(scePthreadCreate(&detached, &detachedAttr, Finish, nullptr, nullptr) == SCE_OK); Require(ReportedEnded(detached)); } Require(scePthreadAttrDestroy(&detachedAttr) == SCE_OK); } int main() { CheckStackSizeLimit(); CheckSoloSched(); CheckSchedArguments(); CheckSchedulingAttributes(); CheckThreadScope(); CheckEndedThreads(); PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK); Require(scePthreadAttrSetinheritsched(&attr, EXPLICIT_SCHED) == SCE_OK); const KernelSchedParam requested{CREATION_PRIORITY}; Require(scePthreadAttrSetschedparam(&attr, &requested) == SCE_OK); Require(scePthreadAttrSetaffinity(&attr, CREATION_AFFINITY) == SCE_OK); Require(scePthreadAttrSetstacksize(&attr, STACK_SIZE) == SCE_OK); std::promise release; auto released = release.get_future(); Pthread thread = nullptr; Require(scePthreadCreate(&thread, &attr, Worker, &released, nullptr) == SCE_OK); Require(scePthreadAttrDestroy(&attr) == SCE_OK); auto reported = Query(thread); Require(reported.priority == CREATION_PRIORITY); Require(reported.affinity == CREATION_AFFINITY); Require(reported.stackSize == STACK_SIZE); Require(reported.detachState == DETACH_JOINABLE); Require(scePthreadSetprio(thread, UPDATED_PRIORITY) == SCE_OK); Require(scePthreadSetaffinity(thread, UPDATED_AFFINITY) == SCE_OK); reported = Query(thread); Require(reported.priority == UPDATED_PRIORITY); Require(reported.affinity == UPDATED_AFFINITY); Require(reported.stackSize == STACK_SIZE); Require(reported.detachState == DETACH_JOINABLE); PthreadAttr destroyed = nullptr; Require(scePthreadAttrGet(thread, &destroyed) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrGet(thread, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrInit(&attr) == SCE_OK); Require(scePthreadAttrGet(nullptr, &attr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrDestroy(&attr) == SCE_OK); release.set_value(); Require(scePthreadJoin(thread, nullptr) == SCE_OK); PthreadAttr stackAttr = nullptr; char stackMarker; void* reportedAddress = nullptr; std::size_t reportedSize = 0; Require(pthread_attr_setstack_nid_postfix(nullptr, &stackMarker, STACK_SIZE) == 22); Require(pthread_attr_setstack_nid_postfix(&stackAttr, &stackMarker, STACK_SIZE) == 22); Require(pthread_attr_getstackaddr_nid_postfix(nullptr, &reportedAddress) == 22); Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, &reportedAddress) == 22); Require(scePthreadAttrInit(&stackAttr) == SCE_OK); Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, nullptr) == 22); Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, &reportedAddress) == 0 && reportedAddress == nullptr); Require(pthread_attr_setstack_nid_postfix(&stackAttr, &stackMarker, STACK_SIZE) == 0); Require(pthread_attr_setstack_nid_postfix(&stackAttr, nullptr, STACK_SIZE) == 22); Require(pthread_attr_setstack_nid_postfix(&stackAttr, &stackMarker, 16383) == 22); Require(pthread_attr_getstack_nid_postfix(&stackAttr, &reportedAddress, &reportedSize) == 0); Require(reportedAddress == &stackMarker && reportedSize == STACK_SIZE); reportedAddress = nullptr; Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, &reportedAddress) == 0 && reportedAddress == &stackMarker); Require(scePthreadAttrDestroy(&stackAttr) == SCE_OK); Require(pthread_attr_setstack_nid_postfix(&stackAttr, &stackMarker, STACK_SIZE) == 22); Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, &reportedAddress) == 22); }