diff --git a/core/libs/prx/libkernel/Equeue/Equeue.cpp b/core/libs/prx/libkernel/Equeue/Equeue.cpp index ee1d24fd0..da6601e52 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.cpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.cpp @@ -120,11 +120,15 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) { } TriggerExpiredTimers(MonotonicNs()); PollEvents(); + return CollectTriggered(ev, num); +} + +int KernelEqueuePrivate::CollectTriggered(KernelEvent* ev, int num) { int ret = 0; - for (auto it = m_events.begin(); it != m_events.end();) { + for (auto it = m_events.begin(); it != m_events.end() && ret < num;) { auto& e = *it; bool erase = false; - while (e.triggered) { + while (e.triggered && ret < num) { ev[ret++] = e.event; if ((e.event.flags & EV_ONESHOT) != 0) { erase = true; @@ -137,19 +141,14 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) { e.event.fflags = 0; e.event.data = 0; } - if (!e.pendingEvents.empty()) { - e.event = e.pendingEvents.front(); - e.pendingEvents.pop_front(); - e.triggered = true; - } - if (ret >= num) { + if (e.pendingEvents.empty()) { break; } + e.event = e.pendingEvents.front(); + e.pendingEvents.pop_front(); + e.triggered = true; } it = erase ? m_events.erase(it) : std::next(it); - if (ret >= num) { - break; - } } return ret; } @@ -163,34 +162,7 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros for (;;) { TriggerExpiredTimers(MonotonicNs()); const bool polling = PollEvents(); - int ret = 0; - for (auto it = m_events.begin(); it != m_events.end() && ret < num;) { - auto& e = *it; - bool erase = false; - while (e.triggered) { - ev[ret++] = e.event; - if ((e.event.flags & EV_ONESHOT) != 0) { - erase = true; - break; - } - if (e.filter.resetFunc != nullptr) { - e.filter.resetFunc(&e); - } else if ((e.event.flags & EV_CLEAR) != 0) { - e.triggered = false; - e.event.fflags = 0; - e.event.data = 0; - } - if (!e.pendingEvents.empty()) { - e.event = e.pendingEvents.front(); - e.pendingEvents.pop_front(); - e.triggered = true; - } - if (ret >= num) { - break; - } - } - it = erase ? m_events.erase(it) : std::next(it); - } + const int ret = CollectTriggered(ev, num); if (ret != 0) { return ret; } diff --git a/core/libs/prx/libkernel/Equeue/Equeue.hpp b/core/libs/prx/libkernel/Equeue/Equeue.hpp index c63f505fb..f5b291dbd 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.hpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.hpp @@ -78,6 +78,7 @@ public: private: void TriggerExpiredTimers(uint64_t nowNs); + int CollectTriggered(KernelEvent* ev, int num); bool NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) const; bool PollEvents(); void WakePollers(); diff --git a/core/libs/tests/GuestEqueueEvents.cpp b/core/libs/tests/GuestEqueueEvents.cpp index b0f74d8cc..56819452f 100644 --- a/core/libs/tests/GuestEqueueEvents.cpp +++ b/core/libs/tests/GuestEqueueEvents.cpp @@ -10,8 +10,12 @@ int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo); int APS5_VABI sceKernelAddUserEvent(KernelEqueue eq, int id); +int APS5_VABI sceKernelAddUserEventEdge(KernelEqueue eq, int id); int APS5_VABI sceKernelTriggerUserEvent(KernelEqueue eq, int id, void* udata); int APS5_VABI sceKernelDeleteUserEvent(KernelEqueue eq, int id); +int APS5_VABI sceKernelAddAmprEvent(KernelEqueue eq, int id, void* udata); +int APS5_VABI sceKernelDeleteAmprEvent(KernelEqueue eq, int id); +int APS5_VABI EqueueTriggerEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter, void* triggerData); int APS5_VABI sceKernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts, void* udata); int APS5_VABI sceKernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata); int APS5_VABI sceKernelDeleteTimerEvent(KernelEqueue eq, int id); @@ -29,6 +33,7 @@ static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003c); static constexpr int EVFILT_TIMER = -7; static constexpr int EVFILT_USER = -11; static constexpr int EVFILT_HRTIMER = -15; +static constexpr int EVFILT_AMPR = -25; static void Require(bool value) { if (!value) std::abort(); } @@ -124,8 +129,126 @@ static void VerifyPeriodicTimer() { Require(sceKernelDeleteEqueue(eq) == SCE_OK); } +static void VerifyUserEventReportedOncePerWait() { + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "user") == SCE_OK); + KernelEvent events[4]{}; + int count = 0; + const KernelUseconds poll = 0; + int payload = 0; + + Require(sceKernelAddUserEvent(eq, 5) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelTriggerUserEvent(eq, 5, &payload) == SCE_OK); + for (int i = 0; i < 2; ++i) { + count = 0; + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK); + Require(count == 1 && sceKernelGetEventId(&events[0]) == 5); + } + Require(sceKernelDeleteUserEvent(eq, 5) == SCE_OK); + + Require(sceKernelAddUserEventEdge(eq, 6) == SCE_OK); + Require(sceKernelTriggerUserEvent(eq, 6, &payload) == SCE_OK); + Require(sceKernelAddUserEvent(eq, 8) == SCE_OK); + Require(sceKernelTriggerUserEvent(eq, 8, &payload) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK); + Require(count == 2); + Require(sceKernelGetEventId(&events[0]) + sceKernelGetEventId(&events[1]) == 14); + Require(sceKernelGetEventId(&events[0]) != sceKernelGetEventId(&events[1])); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK); + Require(count == 1 && sceKernelGetEventId(&events[0]) == 8); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + +static void VerifyBlockingWaitReportsLevelEventOnce() { + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "blocking") == SCE_OK); + KernelEvent events[4]{}; + int count = 0; + const KernelUseconds wait = 1000000; + int payload = 0; + + Require(sceKernelAddUserEvent(eq, 5) == SCE_OK); + Require(sceKernelTriggerUserEvent(eq, 5, &payload) == SCE_OK); + for (int i = 0; i < 2; ++i) { + count = 0; + Require(sceKernelWaitEqueue(eq, events, 4, &count, &wait) == SCE_OK); + Require(count == 1 && sceKernelGetEventId(&events[0]) == 5); + Require(sceKernelGetEventUserData(&events[0]) == &payload); + } + Require(sceKernelDeleteUserEvent(eq, 5) == SCE_OK); + + Require(sceKernelAddUserEvent(eq, 6) == SCE_OK); + std::thread trigger([eq, &payload] { + SleepAtLeast(std::chrono::milliseconds(10)); + Require(sceKernelTriggerUserEvent(eq, 6, &payload) == SCE_OK); + }); + count = 0; + Require(sceKernelWaitEqueue(eq, events, 4, &count, &wait) == SCE_OK); + trigger.join(); + Require(count == 1 && sceKernelGetEventId(&events[0]) == 6); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + +static void VerifyEdgeUserEventReportedOnce() { + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "edge") == SCE_OK); + KernelEvent events[4]{}; + int count = 0; + const KernelUseconds poll = 0; + const KernelUseconds wait = 1000000; + int payload = 0; + + Require(sceKernelAddUserEventEdge(eq, 6) == SCE_OK); + Require(sceKernelTriggerUserEvent(eq, 6, &payload) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &wait) == SCE_OK); + Require(count == 1 && sceKernelGetEventId(&events[0]) == 6); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelTriggerUserEvent(eq, 6, &payload) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK); + Require(count == 1 && sceKernelGetEventId(&events[0]) == 6); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + +static void VerifyRepeatedAmprCompletionsAreNotLost() { + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "ampr") == SCE_OK); + KernelEvent events[4]{}; + int count = 0; + const KernelUseconds poll = 0; + int udata = 0; + auto data = [](std::uintptr_t value) { return reinterpret_cast(value); }; + + Require(sceKernelAddAmprEvent(eq, 3, &udata) == SCE_OK); + Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x11)) == SCE_OK); + Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x22)) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_OK); + Require(count == 2); + Require(sceKernelGetEventFilter(&events[0]) == EVFILT_AMPR && sceKernelGetEventFilter(&events[1]) == EVFILT_AMPR); + Require(sceKernelGetEventId(&events[0]) == 3 && sceKernelGetEventId(&events[1]) == 3); + Require(sceKernelGetEventData(&events[0]) == 0x11 && sceKernelGetEventData(&events[1]) == 0x22); + Require(sceKernelGetEventUserData(&events[0]) == &udata && sceKernelGetEventUserData(&events[1]) == &udata); + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + + Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x33)) == SCE_OK); + Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x44)) == SCE_OK); + Require(EqueueTriggerEvent_nid_postfix(eq, 3, EVFILT_AMPR, data(0x55)) == SCE_OK); + for (const intptr_t expected : {0x33, 0x44, 0x55}) { + Require(sceKernelWaitEqueue(eq, events, 1, &count, &poll) == SCE_OK); + Require(count == 1 && sceKernelGetEventData(&events[0]) == expected); + } + Require(sceKernelWaitEqueue(eq, events, 4, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelDeleteAmprEvent(eq, 3) == SCE_OK); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + int main() { VerifyPeriodicTimer(); + VerifyUserEventReportedOncePerWait(); + VerifyBlockingWaitReportsLevelEventOnce(); + VerifyEdgeUserEventReportedOnce(); + VerifyRepeatedAmprCompletionsAreNotLost(); KernelEqueue eq = 0; Require(sceKernelCreateEqueue(&eq, "events") == SCE_OK); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 36035caf7..e28dddb40 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -288,6 +288,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceCoredumpAttachUserMemoryFile](../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - unknown signature - [sceCoredumpAttachMemoryRegion](../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - unknown signature - [sceKernelAddTimerEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - `usec` is taken as the period in microseconds; re-adding an existing timer keeps its pending expiration count and restarts the period from the time of the call, where FreeBSD 12+ `filt_timertouch` clears pending expirations on `EV_ADD`; `usec == 0` fires on every wait. None of this is confirmed on a PS5 title +- [sceKernelWaitEqueue](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - a level-triggered user event (`sceKernelAddUserEvent`) is reported at most once per call, as in FreeBSD `kqueue_scan` (`sys/kern/kern_event.c`), which re-queues a reported knote without `EV_CLEAR` or `EV_DISPATCH` behind the scan marker, and kevent(2), where several triggers do not produce several kevents. Not measured on a console. Events are scanned in list order and the list is not rotated, so when `num` is smaller than the number of ready level events the later ones can starve, where FreeBSD moves a reported level knote to the tail. Several completions of one AMPR or VideoOut event within one call are returned as separate entries, where FreeBSD would aggregate them; the Orbis behaviour is unconfirmed - [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset) - [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` - [vsnprintf_s and vsscanf_s](../../core/libs/prx/libc/src/Formatting.cpp) (libc) - `vsnprintf_s` forwards to `vsnprintf` semantics: the C11 K.3.7.1.5 runtime constraints (null buffer, `size == 0`, `%n`, null `%s`) are not checked, so `size == 0` returns the would-be length instead of a negative value, and no constraint handler is invoked. `vsscanf_s` returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Neither is confirmed on a PS5 title