diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index bb02ef4ce0..16510fee42 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -61,6 +61,23 @@ void SetState(std::int32_t id, std::int32_t state) { g_states[id] = state; } +std::int32_t StateLocked(std::int32_t id) { + return ValidId(id) && g_states[id] != 0 ? g_states[id] : SCE_KERNEL_ERROR_ESRCH; +} + +std::int32_t CancelLocked(std::int32_t id) { + const std::int32_t state = StateLocked(id); + if (state != AioProcessing) return state; + g_states[id] = AioAborted; + return AioAborted; +} + +std::int32_t DeleteLocked(std::int32_t id) { + if (StateLocked(id) == SCE_KERNEL_ERROR_ESRCH) return SCE_KERNEL_ERROR_ESRCH; + g_states[id] = 0; + return 0; +} + std::int64_t NativePread(std::int32_t fd, void* buf, std::size_t nbyte, std::int64_t offset) { const GuestArena::HostWrite destination(buf, nbyte); if (!destination.Open()) { @@ -151,9 +168,6 @@ int SubmitCommandsMultiple(KernelAioRwRequest* req, std::int32_t size, std::int3 int ValidateIds(const std::int32_t* id, std::int32_t num, const void* out) { if (id == nullptr || out == nullptr) return SCE_KERNEL_ERROR_EFAULT; if (num <= 0 || num > AioIdNumMax) return SCE_KERNEL_ERROR_EINVAL; - for (std::int32_t i = 0; i < num; ++i) { - if (!ValidId(id[i])) return SCE_KERNEL_ERROR_EINVAL; - } return 0; } @@ -163,48 +177,31 @@ extern "C" { int APS5_VABI sceKernelAioDeleteRequest(int32_t id, int32_t* ret) { if (ret == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL; - SetState(id, AioAborted); - *ret = 0; + std::lock_guard lock(g_mutex); + *ret = DeleteLocked(id); return 0; } int APS5_VABI sceKernelAioDeleteRequests(int32_t* id, int32_t num, int32_t* ret) { const int error = ValidateIds(id, num, ret); if (error != 0) return error; - for (int32_t i = 0; i < num; ++i) { - SetState(id[i], AioAborted); - ret[i] = 0; - } + std::lock_guard lock(g_mutex); + for (int32_t i = 0; i < num; ++i) ret[i] = DeleteLocked(id[i]); return 0; } int APS5_VABI sceKernelAioCancelRequest(int32_t id, int32_t* state) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (id == 0) { - *state = AioProcessing; - return 0; - } - if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL; - SetState(id, AioAborted); - *state = AioAborted; + std::lock_guard lock(g_mutex); + *state = CancelLocked(id); return 0; } int APS5_VABI sceKernelAioCancelRequests(int32_t* id, int32_t num, int32_t* state) { - if (id == nullptr || state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (num <= 0 || num > AioIdNumMax) return SCE_KERNEL_ERROR_EINVAL; - for (int32_t i = 0; i < num; ++i) { - if (id[i] != 0 && !ValidId(id[i])) return SCE_KERNEL_ERROR_EINVAL; - } - for (int32_t i = 0; i < num; ++i) { - if (id[i] == 0) { - state[i] = AioProcessing; - continue; - } - SetState(id[i], AioAborted); - state[i] = AioAborted; - } + const int error = ValidateIds(id, num, state); + if (error != 0) return error; + std::lock_guard lock(g_mutex); + for (int32_t i = 0; i < num; ++i) state[i] = CancelLocked(id[i]); return 0; } @@ -254,9 +251,8 @@ int APS5_VABI sceKernelAioSubmitWriteCommandsMultiple(KernelAioRwRequest* req, i int APS5_VABI sceKernelAioPollRequest(int32_t id, int32_t* state) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL; std::lock_guard lock(g_mutex); - *state = g_states[id]; + *state = StateLocked(id); return 0; } @@ -271,7 +267,7 @@ int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, { std::lock_guard lock(g_mutex); for (int32_t i = 0; i < num; ++i) { - state[i] = g_states[id[i]]; + state[i] = StateLocked(id[i]); if (state[i] == AioProcessing) allDone = false; if (state[i] == AioCompleted) anyCompleted = true; } @@ -287,13 +283,12 @@ int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, int APS5_VABI sceKernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - if (!ValidId(id)) return SCE_KERNEL_ERROR_EINVAL; const auto start = std::chrono::steady_clock::now(); for (;;) { std::int32_t current; { std::lock_guard lock(g_mutex); - current = g_states[id]; + current = StateLocked(id); } if (current != AioProcessing) { *state = current; @@ -314,7 +309,7 @@ int APS5_VABI sceKernelAioPollRequests(int32_t* id, int32_t num, int32_t* state) const int error = ValidateIds(id, num, state); if (error != 0) return error; std::lock_guard lock(g_mutex); - for (int32_t i = 0; i < num; ++i) state[i] = g_states[id[i]]; + for (int32_t i = 0; i < num; ++i) state[i] = StateLocked(id[i]); return 0; } diff --git a/core/libs/tests/GuestAio.cpp b/core/libs/tests/GuestAio.cpp index bdd2efcdaa..2025452f8b 100644 --- a/core/libs/tests/GuestAio.cpp +++ b/core/libs/tests/GuestAio.cpp @@ -239,10 +239,14 @@ int main(int argc, char** argv) { Check(sceKernelAioSubmitReadCommands(nullptr, 1, 1, &id) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioSubmitReadCommands(requests, 0, 1, &id) == SCE_KERNEL_ERROR_EINVAL); Check(sceKernelAioSubmitReadCommands(requests, 1, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioWaitRequest(9999, &state, nullptr) == SCE_KERNEL_ERROR_EINVAL); + state = 7; + Check(sceKernelAioWaitRequest(9999, &state, nullptr) == 0 && state == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioWaitRequest(id, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioPollRequest(id, nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioPollRequest(0, &polled) == SCE_KERNEL_ERROR_EINVAL); + for (const std::int32_t unknown : {0, -1, 511, 512, 100000}) { + polled = 7; + Check(sceKernelAioPollRequest(unknown, &polled) == 0 && polled == SCE_KERNEL_ERROR_ESRCH); + } Check(sceKernelAioDeleteRequest(id, nullptr) == SCE_KERNEL_ERROR_EFAULT); const int batchFd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDWR, 0); Check(batchFd >= 0); @@ -317,24 +321,31 @@ int main(int argc, char** argv) { Check(readStates[0] == 3 && readStates[1] == 3); std::int32_t cancelled = 0; Check(sceKernelAioCancelRequest(readIds[0], &cancelled) == 0); - Check(cancelled == 4); + Check(cancelled == 3); Check(sceKernelAioPollRequest(readIds[0], &polled) == 0); - Check(polled == 4); + Check(polled == 3); Check(sceKernelAioCancelRequest(0, &cancelled) == 0); - Check(cancelled == 2); + Check(cancelled == SCE_KERNEL_ERROR_ESRCH); std::int32_t cancelIds[2] = {0, readIds[1]}; std::int32_t cancelStates[2] = {0, 0}; Check(sceKernelAioCancelRequests(cancelIds, 2, cancelStates) == 0); - Check(cancelStates[0] == 2 && cancelStates[1] == 4); + Check(cancelStates[0] == SCE_KERNEL_ERROR_ESRCH && cancelStates[1] == 3); Check(sceKernelAioPollRequest(readIds[1], &polled) == 0); - Check(polled == 4); + Check(polled == 3); Check(sceKernelAioPollRequests(readIds, 2, polledStates) == 0); - Check(polledStates[0] == 4 && polledStates[1] == 4); + Check(polledStates[0] == 3 && polledStates[1] == 3); std::int32_t deletedRets[2] = {-1, -1}; Check(sceKernelAioDeleteRequests(writeIds, 2, deletedRets) == 0); Check(deletedRets[0] == 0 && deletedRets[1] == 0); Check(sceKernelAioPollRequest(writeIds[0], &polled) == 0); - Check(polled == 4); + Check(polled == SCE_KERNEL_ERROR_ESRCH); + Check(sceKernelAioDeleteRequests(writeIds, 2, deletedRets) == 0); + Check(deletedRets[0] == SCE_KERNEL_ERROR_ESRCH && deletedRets[1] == SCE_KERNEL_ERROR_ESRCH); + std::int32_t deletedRet = 7; + Check(sceKernelAioDeleteRequest(writeIds[0], &deletedRet) == 0 && deletedRet == SCE_KERNEL_ERROR_ESRCH); + std::uint32_t unknownTimeout = 1000; + Check(sceKernelAioWaitRequests(writeIds, 2, polledStates, 1, &unknownTimeout) == 0); + Check(polledStates[0] == SCE_KERNEL_ERROR_ESRCH && polledStates[1] == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioDeleteRequests(writeIds, 0, deletedRets) == SCE_KERNEL_ERROR_EINVAL); Check(sceKernelClose(batchFd) == 0); std::int32_t badIds[2] = {readIds[0], 9999}; @@ -355,13 +366,15 @@ int main(int argc, char** argv) { Check(sceKernelAioWaitRequests(readIds, 2, nullptr, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(nullptr, 2, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(readIds, -1, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); - Check(sceKernelAioWaitRequests(badIds, 2, untouched, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); - Check(untouched[0] == 7 && untouched[1] == 7); + Check(sceKernelAioWaitRequests(badIds, 2, untouched, 1, nullptr) == 0); + Check(untouched[0] == 3 && untouched[1] == SCE_KERNEL_ERROR_ESRCH); + untouched[0] = untouched[1] = 7; Check(sceKernelAioPollRequests(readIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioPollRequests(nullptr, 2, readStates) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioPollRequests(readIds, -1, readStates) == SCE_KERNEL_ERROR_EINVAL); - Check(sceKernelAioPollRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL); - Check(untouched[0] == 7 && untouched[1] == 7); + Check(sceKernelAioPollRequests(badIds, 2, untouched) == 0); + Check(untouched[0] == 3 && untouched[1] == SCE_KERNEL_ERROR_ESRCH); + untouched[0] = untouched[1] = 7; Check(sceKernelAioPollRequests(readIds, 0, untouched) == SCE_KERNEL_ERROR_EINVAL); Check(sceKernelAioWaitRequests(readIds, 0, untouched, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); Check(sceKernelAioCancelRequests(readIds, 0, untouched) == SCE_KERNEL_ERROR_EINVAL); @@ -381,13 +394,13 @@ int main(int argc, char** argv) { Check(sceKernelAioCancelRequests(manyIds.data(), 129, manyStates.data()) == SCE_KERNEL_ERROR_EINVAL); Check(manyStates[0] == 7); Check(sceKernelAioPollRequests(manyIds.data(), 128, manyStates.data()) == 0 && manyStates[127] == manyStates[0]); - Check(sceKernelAioCancelRequest(9999, &cancelled) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioCancelRequest(9999, &cancelled) == 0 && cancelled == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioCancelRequest(readIds[0], nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioCancelRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL); - Check(untouched[0] == 7 && untouched[1] == 7); + Check(sceKernelAioCancelRequests(badIds, 2, untouched) == 0); + Check(untouched[0] == 3 && untouched[1] == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioCancelRequests(cancelIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT); - Check(sceKernelAioDeleteRequests(badIds, 2, untouched) == SCE_KERNEL_ERROR_EINVAL); - Check(untouched[0] == 7 && untouched[1] == 7); + Check(sceKernelAioDeleteRequests(badIds, 2, untouched) == 0); + Check(untouched[0] == 0 && untouched[1] == SCE_KERNEL_ERROR_ESRCH); Check(sceKernelAioDeleteRequests(writeIds, 2, nullptr) == SCE_KERNEL_ERROR_EFAULT); const auto shared = root / "shared.bin"; std::string sharedContents; diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 8ff9d99cbd..e793686546 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -63,7 +63,7 @@ - [scePthreadCondWait / scePthreadCondTimedwait](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) - before waiting, FreeBSD libthr's [`cond_wait_common`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_cond.c#L1992-L2029) checks ownership through [`_mutex_owned`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_mutex.c#L1166-L1178): an unowned mutex returns `EPERM`, while its destroyed marker returns `EINVAL`. An untouched adaptive static initializer (slot value 1) therefore returns `EPERM`; the test covers this, an uninitialized slot, a mutex owned by another thread, and the destroyed marker for both timed and untimed sce and POSIX waits. A recursive mutex locked several times is released completely for the wait and relocked to the same depth afterwards, as FreeBSD libthr does (`_mutex_cv_unlock`, `_mutex_cv_lock`). These FreeBSD-derived behaviors remain unverified on console hardware. Cancellation follows the [POSIX condition wait contract](https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_cond_clockwait.html): the original mutex and recursive depth are restored before cleanup, and a canceled waiter forwards a wake to the remaining waiters. The host condition state is retained across mutex reacquisition so the guest can destroy and free its condition after notification; cancellation ordering and signal replacement are not measured on a console - [sceKernelSetEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - multi-waiter satisfaction follows fpPS4 in-order evaluation: registered waiters are evaluated in FIFO order and each satisfied waiter's clear mode is applied immediately to the pattern before evaluating subsequent waiters; this ordering is unverified on PS5 console hardware - [sceKernelOpenEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - returns the handle of an undeleted event flag this process created with the same name, and `SCE_KERNEL_ERROR_ENOENT` otherwise. Flags owned by other processes, such as the shell's `SceShellCoreUtilAppFocus`, do not exist here, and which attribute makes a flag openable on the console is not modelled. The error code for a missing name and the choice between flags sharing a name are unverified. -- [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioPollRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - behaviour from shadPS4 (one id per request for the Multiple submits, cancel turns any request aborted, wait mode 2 returns once one request completed, poll copies every state without waiting). On a PS5 (firmware 13.60) an id that is not a live request (0, -1, 1, 511, 512, 100000) makes every poll, wait, cancel and delete call return 0 and write `0x80020003` as its state; here an id outside 1 to 511 returns `EINVAL` and cancel reports id 0 as processing. The console's ids are not the small numbers used here (it returned 33, 0x2021, 0x4021). Batch sizes, id counts, priorities and wait modes follow the console +- [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioPollRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - one id per request for the Multiple submits and wait mode 2 returning once one request completed follow shadPS4. Requests run synchronously on submit, so cancel never finds one processing. On a PS5 (firmware 13.60) a state read after a wait has reported it carries bit `0x10000` (`0x10003` for completed); that bit is not set here. The console's ids are not the small numbers used here (it returned 33, 0x2021, 0x4021) - [scePthreadAttr setters and getters](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) (libkernel) - measured on a PS5 (firmware 13.60): a null or uninitialized attribute returns `EINVAL`, as does a null output of the stack size, detach state and scheduling parameter getters; a policy other than 1 and 3, an inheritance other than 0 and 4, or a priority outside 256 to 767 (or a null parameter) returns `ENOTSUP` (`0x8002002D`); a detach state other than 0 and 1 and a stack size below 16384 return `EINVAL`; any affinity mask is accepted. A null attribute pointer to `scePthreadAttrInit` and a null output of `scePthreadAttrGetaffinity` crashed the payload, so they still throw. A new attribute holds a 64 KiB stack size on the console and 1 MiB here, which the host code that runs on guest threads needs. `scePthreadAttrGetschedpolicy` and `scePthreadAttrGetinheritsched` were not measured and follow [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_attr.c#L216-L262); a null output of `scePthreadAttrGetinheritsched` throws, since libthr writes through it unchecked - [scePthreadAttrGet](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) and [pthread_attr_get_np](../../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) (libkernel) - the returned scheduling policy is unmeasured on a console. Both report the recorded guest policy, following [FreeBSD 11.4 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libthr/thread/thr_attr.c#L134-L164) - [pthread_getprio](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) is unmeasured on the console. NID `aO+WS2xWuo4` is named in the firmware 3.20 [export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libkernel.c). Its return and errno contract follows FreeBSD 11 [thr_getprio.c](https://github.com/freebsd/freebsd-src/blob/671fb7df97631020c42ce55527dc49fffe0656b7/lib/libthr/thread/thr_getprio.c): return the recorded guest priority, or -1 with `ESRCH` for an unknown or finished thread. The live-thread registry lock protects the read from a concurrent join. This reports guest scheduler state, not the host scheduling priority.