From d081e6749df4b3fa1e18b23d60fe31307a15f364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Tue, 6 Oct 2026 07:38:51 +0200 Subject: [PATCH] feat(libkernel): sceKernelAddTimerEvent and sceKernelDeleteTimerEvent Co-Authored-By: Claude --- core/libs/prx/libkernel/Equeue/Equeue.cpp | 29 +++++++- core/libs/prx/libkernel/Equeue/Equeue.hpp | 4 +- core/libs/tests/GuestEqueueEvents.cpp | 83 +++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel/Equeue/Equeue.cpp b/core/libs/prx/libkernel/Equeue/Equeue.cpp index 50b5c7faa..b328aecf5 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.cpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.cpp @@ -46,7 +46,14 @@ void KernelEqueuePrivate::Close() { void KernelEqueuePrivate::TriggerExpiredTimers(uint64_t nowNs) { for (auto& ev : m_events) { - if (!ev.triggered && ev.deadlineNs != 0 && ev.deadlineNs <= nowNs) { + if (ev.deadlineNs != 0 && ev.deadlineNs <= nowNs) { + if (ev.event.filter == EVFILT_TIMER) { + const uint64_t count = ev.intervalNs == 0 + ? (ev.triggered ? 0 : 1) + : 1 + (nowNs - ev.deadlineNs) / ev.intervalNs; + ev.event.data += static_cast(count); + ev.deadlineNs += count * ev.intervalNs; + } ev.triggered = true; } } @@ -176,7 +183,11 @@ int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) { } ); if (it != m_events.end()) { + if (event.event.filter == EVFILT_TIMER) { + TriggerExpiredTimers(MonotonicNs()); + } it->deadlineNs = event.deadlineNs; + it->intervalNs = event.intervalNs; it->event.udata = event.event.udata; for (auto& pending : it->pendingEvents) { pending.udata = event.event.udata; @@ -403,6 +414,22 @@ int APS5_VABI sceKernelDeleteHRTimerEvent(KernelEqueue eq, int id) { return EqueueDeleteEvent_nid_postfix(eq, static_cast(id), EVFILT_HRTIMER); } +int APS5_VABI sceKernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata) { + const uint64_t intervalNs = static_cast(usec) * 1000ULL; + KernelEqueueEvent event{}; + event.deadlineNs = KernelEqueuePrivate::MonotonicNs() + intervalNs; + event.intervalNs = intervalNs; + event.event.ident = static_cast(id); + event.event.filter = EVFILT_TIMER; + event.event.flags = EV_ADD | EV_CLEAR; + event.event.udata = udata; + return EqueueAddEvent_nid_postfix(eq, event); +} + +int APS5_VABI sceKernelDeleteTimerEvent(KernelEqueue eq, int id) { + return EqueueDeleteEvent_nid_postfix(eq, static_cast(id), EVFILT_TIMER); +} + int APS5_VABI sceKernelAddAmprEvent(KernelEqueue eq, int id, void* udata) { if (eq == 0) { return EQUEUE_OK; diff --git a/core/libs/prx/libkernel/Equeue/Equeue.hpp b/core/libs/prx/libkernel/Equeue/Equeue.hpp index 48085e6ac..c5d4f3e5d 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.hpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.hpp @@ -14,6 +14,7 @@ #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" +static constexpr int16_t EVFILT_TIMER = -7; static constexpr int16_t EVFILT_USER = -11; static constexpr int16_t EVFILT_VIDEO_OUT = -13; static constexpr int16_t EVFILT_HRTIMER = -15; @@ -42,6 +43,7 @@ struct KernelFilter { struct KernelEqueueEvent { bool triggered = false; uint64_t deadlineNs = 0; + uint64_t intervalNs = 0; KernelEvent event; KernelFilter filter; std::deque pendingEvents; @@ -64,9 +66,9 @@ public: int GetTriggeredEvents(KernelEvent* ev, int num); int WaitForEvents(KernelEvent* ev, int num, uint32_t micros); void Close(); + static uint64_t MonotonicNs(); private: - static uint64_t MonotonicNs(); void TriggerExpiredTimers(uint64_t nowNs); bool NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) const; diff --git a/core/libs/tests/GuestEqueueEvents.cpp b/core/libs/tests/GuestEqueueEvents.cpp index 4731605d0..e7fa6bddb 100644 --- a/core/libs/tests/GuestEqueueEvents.cpp +++ b/core/libs/tests/GuestEqueueEvents.cpp @@ -1,7 +1,9 @@ #include "SceTypes.hpp" +#include #include #include #include +#include extern "C" { int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); @@ -11,6 +13,8 @@ int APS5_VABI sceKernelAddUserEvent(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 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); intptr_t APS5_VABI sceKernelGetEventData(const KernelEvent* ev); intptr_t APS5_VABI sceKernelGetEventFflags(const KernelEvent* ev); int APS5_VABI sceKernelGetEventFilter(const KernelEvent* ev); @@ -19,6 +23,10 @@ void* APS5_VABI sceKernelGetEventUserData(const KernelEvent* ev); } static constexpr int SCE_OK = 0; +static constexpr int SCE_KERNEL_ERROR_ENOENT = static_cast(0x80020002); +static constexpr int SCE_KERNEL_ERROR_EBADF = static_cast(0x80020009); +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; @@ -34,7 +42,82 @@ static bool RejectsNull(TResult (APS5_VABI *accessor)(const KernelEvent*)) { return false; } +static void SleepAtLeast(std::chrono::steady_clock::duration duration) { + const auto start = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() - start < duration) { + std::this_thread::sleep_for(duration); + } +} + +static void VerifyPeriodicTimer() { + using namespace std::chrono_literals; + int first = 0; + int second = 0; + Require(sceKernelAddTimerEvent(0, 10, 1000, &first) == SCE_KERNEL_ERROR_EBADF); + Require(sceKernelDeleteTimerEvent(0, 10) == SCE_KERNEL_ERROR_EBADF); + + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "timer") == SCE_OK); + Require(sceKernelDeleteTimerEvent(eq, 10) == SCE_KERNEL_ERROR_ENOENT); + + KernelEvent events[2]{}; + int count = 0; + const KernelUseconds poll = 0; + const KernelUseconds wait = 1000000; + Require(sceKernelAddTimerEvent(eq, 10, 1000, &first) == SCE_OK); + SleepAtLeast(5ms); + Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_OK); + Require(count == 1); + Require(sceKernelGetEventId(&events[0]) == 10); + Require(sceKernelGetEventFilter(&events[0]) == EVFILT_TIMER); + Require(sceKernelGetEventData(&events[0]) >= 5); + Require(sceKernelGetEventUserData(&events[0]) == &first); + Require(sceKernelWaitEqueue(eq, events, 2, &count, &wait) == SCE_OK); + Require(count == 1 && sceKernelGetEventData(&events[0]) >= 1); + + SleepAtLeast(5ms); + Require(sceKernelAddTimerEvent(eq, 10, 3000000000u, &second) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_OK); + Require(count == 1); + Require(sceKernelGetEventData(&events[0]) >= 5); + Require(sceKernelGetEventUserData(&events[0]) == &second); + Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelDeleteTimerEvent(eq, 10) == SCE_OK); + Require(sceKernelDeleteTimerEvent(eq, 10) == SCE_KERNEL_ERROR_ENOENT); + + Require(sceKernelAddTimerEvent(eq, 13, 3000000000u, &first) == SCE_OK); + const auto readded = std::chrono::steady_clock::now(); + Require(sceKernelAddTimerEvent(eq, 13, 200000, &first) == SCE_OK); + SleepAtLeast(250ms); + Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_OK); + Require(count == 1 && sceKernelGetEventData(&events[0]) >= 1); + const intptr_t expirations = sceKernelGetEventData(&events[0]); + const int again = sceKernelWaitEqueue(eq, events, 2, &count, &poll); + if (std::chrono::steady_clock::now() - readded < 400ms) { + Require(expirations == 1 && again == SCE_KERNEL_ERROR_ETIMEDOUT); + } + Require(sceKernelWaitEqueue(eq, events, 2, &count, &wait) == SCE_OK); + Require(count == 1 && sceKernelGetEventId(&events[0]) == 13); + Require(sceKernelDeleteTimerEvent(eq, 13) == SCE_OK); + + Require(sceKernelAddTimerEvent(eq, 11, 0, &first) == SCE_OK); + Require(sceKernelAddTimerEvent(eq, 11, 0, &first) == SCE_OK); + for (int i = 0; i < 2; ++i) { + Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_OK); + Require(count == 1 && sceKernelGetEventData(&events[0]) == 1); + } + Require(sceKernelDeleteTimerEvent(eq, 11) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + + const KernelTimespec delay{0, 1000000}; + Require(sceKernelAddHRTimerEvent(eq, 12, &delay, &first) == SCE_OK); + Require(sceKernelDeleteTimerEvent(eq, 12) == SCE_KERNEL_ERROR_ENOENT); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + int main() { + VerifyPeriodicTimer(); + KernelEqueue eq = 0; Require(sceKernelCreateEqueue(&eq, "events") == SCE_OK); const KernelUseconds timeout = 1000000;