Skip to content

feat(libkernel): sceKernelAddTimerEvent and sceKernelDeleteTimerEvent - #807

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
adriadam10:feat/kernel-timer-event
Oct 7, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
adriadam10:feat/kernel-timer-event

Conversation

@adriadam10

@adriadam10 adriadam10 commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

What

Adds sceKernelAddTimerEvent (57ZK+ODEXWY) and sceKernelDeleteTimerEvent (YWQFUyXIVdU), which main doesn't declare at all.

  • AddTimerEvent(eq, id, usec, udata) registers a periodic EVFILT_TIMER (-7) event with EV_CLEAR, reusing the equeue's existing timer scheduling (TriggerExpiredTimers/NextTimerWaitMicros used by the HR timer). data is the number of expirations since the last delivery, and the timer stays registered after delivery. Calling it again on the same id keeps the expirations already counted and replaces the interval and userdata. With usec == 0 the event is ready again after every read, with data == 1. This follows KytyPS5's KernelAddTimerEvent and TriggerExpiredTimers, and its EventQueueLifetimeTests. prosper's post_after agrees on periodic firing and expirations-since-last-read in data (FreeBSD kqueue EVFILT_TIMER).
  • DeleteTimerEvent removes the EVFILT_TIMER event, returning ENOENT if there isn't one. An unknown queue returns EBADF for both calls (shadPS4 and KytyPS5).
  • Where the references disagree: shadPS4 stores usec / 1000 in data and doesn't model periodic expirations, which looks like a placeholder. prosper's Delete always returns 0. I followed KytyPS5 (expirations in data) and shadPS4 (ENOENT on Delete).
  • KernelEqueuePrivate::MonotonicNs is now public so the export uses the same clock as the queue. Re-adding only refreshes expired timers first for EVFILT_TIMER, so the HR timer behaves as before.

Tested

Linux, GCC (Release and a fast dev build). I extended guest_equeue_events (core/libs/tests/GuestEqueueEvents.cpp) with these cases:

  • invalid queue → EBADF
  • delete of a missing timer → ENOENT, including when only an HR timer has that id
  • expirations accumulate (data >= 5 after 5 ms at 1 ms), and the timer is still delivered afterwards
  • an update keeps the counted expirations, replaces userdata and replaces the interval (long → no event; long → 200 ms fires once, then not immediately, then again)
  • usec == 0 gives data == 1 on every read
  • delete

It aborts on main (the exports don't exist, so it doesn't link). Negative checks: each of these breaks the test: data = 1 instead of accumulating, one-shot flag, no refresh before update, interval not updated on re-add, no deadline advance, zero interval counting while already triggered, and Delete using the HR filter. The full guest_|libkernel|equeue ctest set passes (119/119).

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Progress report (2b8f0b2 → 9ab637a)

📈 System libraries: 86.0% (+0.01%, +2 implemented functions)

✅ 2 implemented
Library Name
libkernel sceKernelAddTimerEvent
libkernel sceKernelDeleteTimerEvent

@adriadam10

Copy link
Copy Markdown
Collaborator Author

Windows CI: guest_equeue_events aborted because the test assumed sleep_for never wakes early. The test now sleeps until steady_clock has advanced by at least the requested time, and only checks the exact expiration count and the empty follow-up poll when the measured elapsed time makes them deterministic (< 400 ms). The implementation is unchanged.

@adriadam10
adriadam10 force-pushed the feat/kernel-timer-event branch from d41ab00 to 7cb154d Compare October 6, 2026 05:51
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, the expiration counting and the reworked test look right to me. The test is deterministic now: every lower bound holds whatever the scheduler does, and the exact count is only checked when the elapsed time guarantees it.

One functional problem and one docs point:

  • A blocking wait with a null timeout never wakes up for a timer. sceKernelWaitEqueue(eq, ev, n, &out, nullptr) goes to WaitForEvents(ev, num, 0), which calls m_cond.Wait(lock) with no deadline (Equeue.cpp:161). Nothing signals the condition when a timer expires, so a thread waiting on a queue that only holds a timer hangs for good. Periodic timer plus infinite wait is the usual way this API is used. HRTimer has the same gap on main, but this PR makes it much easier to hit. Could the micros == 0 branch use NextTimerWaitMicros and WaitUntil the nearest timer deadline when there is one? A test that adds a 1000 us timer and waits with a null timeout would cover it.
  • Please add a TechnicalDebt line for the unverified parts: usec taken as microseconds, re-adding an existing timer keeps the pending expiration count and restarts the period from now, and usec == 0 fires on every wait. For comparison, FreeBSD 12+ filt_timertouch clears pending expirations on EV_ADD. I don't know what the PS5 kernel does here, so documenting the choice seems enough.

@adriadam10
adriadam10 force-pushed the feat/kernel-timer-event branch from 7cb154d to 00a8ec8 Compare October 6, 2026 09:51
@adriadam10

Copy link
Copy Markdown
Collaborator Author

Rebased on main and fixed:

  • A wait with a null timeout now sleeps until the nearest timer deadline (NextTimerWaitMicros + WaitUntil) when the queue has one, and blocks as before otherwise. This also covers HRTimer. New test cases: a 1000 us timer and a 1 ms HRTimer, each waited on with a null timeout. Without the fix the test hangs until the 20 s ctest timeout.
  • Added a TechnicalDebt line under "Unknown function info": usec taken as microseconds, re-add keeps pending expirations and restarts the period (FreeBSD 12+ filt_timertouch clears them), and usec == 0 fires on every wait.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks. After the last batch this branch conflicts with main in docs/dev/TechnicalDebt.md. Could you rebase onto main? The code doesn't need to change.

Co-Authored-By: Claude <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (8e281fa). The only conflict was in docs/dev/TechnicalDebt.md. The sceKernelAddTimerEvent line stays under "Unknown function info", now next to the other libkernel entries instead of at the end of the section. Build and full ctest pass, including guest_equeue_events.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#1081 core/libs/prx/libkernel/Equeue/Equeue.cpp L154
#1091 core/libs/prx/libkernel/Equeue/Equeue.cpp L154
core/libs/prx/libkernel/Equeue/Equeue.hpp L16, L65-66

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 7, 2026
… choices

Co-Authored-By: Claude <noreply@anthropic.com>
@adriadam10
adriadam10 force-pushed the feat/kernel-timer-event branch from 1ab6e74 to 9ab637a Compare October 7, 2026 16:14
@adriadam10
adriadam10 merged commit 4b16995 into boykopovar:main Oct 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants