mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
Support periodic kernel timers through shared event queue deadlines
This commit is contained in:
+26
-12
@@ -138,7 +138,13 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) {
|
||||
|
||||
void KernelEqueuePrivate::TriggerExpiredTimers(uint64_t now_ns) {
|
||||
for (auto& event: m_events) {
|
||||
if (!event.triggered && event.deadline_ns != 0 && event.deadline_ns <= now_ns) {
|
||||
if (event.deadline_ns != 0 && event.deadline_ns <= now_ns) {
|
||||
if (event.event.filter == KERNEL_EVFILT_TIMER) {
|
||||
const auto count = event.interval_ns == 0 ? !event.triggered :
|
||||
1 + (now_ns - event.deadline_ns) / event.interval_ns;
|
||||
event.event.data += static_cast<intptr_t>(count);
|
||||
event.deadline_ns += count * event.interval_ns;
|
||||
}
|
||||
event.triggered = true;
|
||||
}
|
||||
}
|
||||
@@ -209,7 +215,9 @@ int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) {
|
||||
return e.event.ident == ident && e.event.filter == filter;
|
||||
});
|
||||
if (it != m_events.end()) {
|
||||
TriggerExpiredTimers(MonotonicTimeNs());
|
||||
it->deadline_ns = event.deadline_ns;
|
||||
it->interval_ns = event.interval_ns;
|
||||
it->event.udata = event.event.udata;
|
||||
for (auto& pending: it->pending_events) {
|
||||
pending.udata = event.event.udata;
|
||||
@@ -511,6 +519,22 @@ int KYTY_SYSV_ABI KernelDeleteUserEvent(KernelEqueue eq, int id) {
|
||||
return KernelDeleteEvent(eq, static_cast<uintptr_t>(id), KERNEL_EVFILT_USER);
|
||||
}
|
||||
|
||||
static int AddTimerEvent(KernelEqueue eq, int id, uint64_t delay_ns, bool periodic, void* udata) {
|
||||
const auto now_ns = MonotonicTimeNs();
|
||||
KernelEqueueEvent event {};
|
||||
event.deadline_ns = delay_ns <= UINT64_MAX - now_ns ? now_ns + delay_ns : UINT64_MAX;
|
||||
event.interval_ns = periodic ? delay_ns : 0;
|
||||
event.event.ident = static_cast<uintptr_t>(id);
|
||||
event.event.filter = periodic ? KERNEL_EVFILT_TIMER : KERNEL_EVFILT_HRTIMER;
|
||||
event.event.flags = EV_ADD | (periodic ? EV_CLEAR : EV_ONESHOT);
|
||||
event.event.udata = udata;
|
||||
return KernelAddEvent(eq, event);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata) {
|
||||
return AddTimerEvent(eq, id, static_cast<uint64_t>(usec) * 1000, true, udata);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts,
|
||||
void* udata) {
|
||||
if (ts == nullptr) {
|
||||
@@ -524,17 +548,7 @@ int KYTY_SYSV_ABI KernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTim
|
||||
|
||||
const auto delay_ns =
|
||||
static_cast<uint64_t>(ts->tv_sec) * 1000000000ull + static_cast<uint64_t>(ts->tv_nsec);
|
||||
const auto now_ns = MonotonicTimeNs();
|
||||
|
||||
KernelEqueueEvent event {};
|
||||
event.deadline_ns = delay_ns <= UINT64_MAX - now_ns ? now_ns + delay_ns : UINT64_MAX;
|
||||
event.event.ident = static_cast<uintptr_t>(id);
|
||||
event.event.filter = KERNEL_EVFILT_HRTIMER;
|
||||
event.event.flags = EV_ADD | EV_ONESHOT;
|
||||
event.event.fflags = 0;
|
||||
event.event.data = 0;
|
||||
event.event.udata = udata;
|
||||
return KernelAddEvent(eq, event);
|
||||
return AddTimerEvent(eq, id, delay_ns, false, udata);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelDeleteHRTimerEvent(KernelEqueue eq, int id) {
|
||||
|
||||
@@ -51,6 +51,7 @@ struct KernelFilter {
|
||||
struct KernelEqueueEvent {
|
||||
bool triggered = false;
|
||||
uint64_t deadline_ns = 0;
|
||||
uint64_t interval_ns = 0;
|
||||
KernelEvent event;
|
||||
KernelFilter filter;
|
||||
std::deque<KernelEvent> pending_events;
|
||||
@@ -72,6 +73,8 @@ int KYTY_SYSV_ABI KernelAddUserEventEdge(KernelEqueue eq, int id);
|
||||
int KYTY_SYSV_ABI KernelTriggerUserEvent(KernelEqueue eq, int id, void* udata);
|
||||
int KYTY_SYSV_ABI KernelTriggerUserEventForAll(int id, void* udata);
|
||||
int KYTY_SYSV_ABI KernelDeleteUserEvent(KernelEqueue eq, int id);
|
||||
int KYTY_SYSV_ABI KernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec,
|
||||
void* udata);
|
||||
int KYTY_SYSV_ABI KernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts,
|
||||
void* udata);
|
||||
int KYTY_SYSV_ABI KernelDeleteHRTimerEvent(KernelEqueue eq, int id);
|
||||
|
||||
@@ -3148,6 +3148,7 @@ LIB_DEFINE(InitLibKernel_1_Equeue) {
|
||||
LIB_FUNC("WDszmSbWuDk", EventQueue::KernelAddUserEventEdge);
|
||||
LIB_FUNC("F6e0kwo4cnk", EventQueue::KernelTriggerUserEvent);
|
||||
LIB_FUNC("LJDwdSNTnDg", EventQueue::KernelDeleteUserEvent);
|
||||
LIB_FUNC("57ZK+ODEXWY", EventQueue::KernelAddTimerEvent);
|
||||
LIB_FUNC("R74tt43xP6k", EventQueue::KernelAddHRTimerEvent);
|
||||
LIB_FUNC("J+LF6LwObXU", EventQueue::KernelDeleteHRTimerEvent);
|
||||
LIB_FUNC("bBfz7kMF2Ho", EventQueue::KernelAddAmprEvent);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
@@ -27,6 +28,69 @@ void CheckConcurrentResult(int result, const char* text) {
|
||||
Check(result == OK || result == KERNEL_ERROR_EBADF || result == KERNEL_ERROR_ENOENT, text);
|
||||
}
|
||||
|
||||
void TestPeriodicTimerEvents() {
|
||||
using namespace std::chrono_literals;
|
||||
using Libs::LibKernel::KERNEL_ERROR_ETIMEDOUT;
|
||||
Check(EventQueue::KernelAddTimerEvent(EventQueue::KERNEL_EQUEUE_INVALID, 10, 1000, nullptr) ==
|
||||
KERNEL_ERROR_EBADF,
|
||||
"timer rejects invalid queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "periodic-timer") == OK, "create timer queue");
|
||||
auto* user_data = reinterpret_cast<void*>(0x1234);
|
||||
Check(EventQueue::KernelAddTimerEvent(queue, 10, 1000, user_data) == OK, "add periodic timer");
|
||||
std::this_thread::sleep_for(5ms);
|
||||
EventQueue::KernelEvent events[2] {};
|
||||
int out = 0;
|
||||
Libs::LibKernel::KernelUseconds timeout = 0;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1,
|
||||
"periodic occurrences are aggregated into one event");
|
||||
Check(events[0].ident == 10 && events[0].filter == EventQueue::KERNEL_EVFILT_TIMER &&
|
||||
events[0].data >= 5 && events[0].udata == user_data,
|
||||
"timer reports identity, userdata and elapsed occurrence count");
|
||||
timeout = 100000;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1 &&
|
||||
events[0].data >= 1,
|
||||
"periodic timer remains registered after delivery");
|
||||
|
||||
std::this_thread::sleep_for(5ms);
|
||||
user_data = reinterpret_cast<void*>(0x5678);
|
||||
Check(EventQueue::KernelAddTimerEvent(queue, 10, 3000000000u, user_data) == OK,
|
||||
"update periodic timer interval");
|
||||
timeout = 0;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1 &&
|
||||
events[0].data >= 5 && events[0].udata == user_data,
|
||||
"timer update preserves accumulated occurrences and updates userdata");
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == KERNEL_ERROR_ETIMEDOUT,
|
||||
"timer update replaces the repeating interval");
|
||||
Check(EventQueue::KernelDeleteEvent(queue, 10, EventQueue::KERNEL_EVFILT_TIMER) == OK,
|
||||
"delete periodic timer");
|
||||
|
||||
Check(EventQueue::KernelAddTimerEvent(queue, 10, 0, user_data) == OK,
|
||||
"add immediate periodic timer");
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Check(EventQueue::KernelAddTimerEvent(queue, 10, 0, user_data) == OK,
|
||||
"update pending immediate timer");
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1 &&
|
||||
events[0].data == 1,
|
||||
"zero interval remains immediately ready after delivery");
|
||||
}
|
||||
Check(EventQueue::KernelDeleteEvent(queue, 10, EventQueue::KERNEL_EVFILT_TIMER) == OK,
|
||||
"delete immediate timer");
|
||||
|
||||
const Libs::LibKernel::KernelTimespec delay {0, 1000000};
|
||||
Check(EventQueue::KernelAddHRTimerEvent(queue, 10, &delay, user_data) == OK,
|
||||
"add high resolution timer");
|
||||
timeout = 100000;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1 &&
|
||||
events[0].filter == EventQueue::KERNEL_EVFILT_HRTIMER && events[0].udata == user_data,
|
||||
"shared timer scheduling delivers high resolution event");
|
||||
Check(EventQueue::KernelDeleteHRTimerEvent(queue, 10) == KERNEL_ERROR_ENOENT,
|
||||
"high resolution timer is removed after one delivery");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete timer queue");
|
||||
}
|
||||
|
||||
void CountDeletedEvent(EventQueue::KernelEqueue, EventQueue::KernelEqueueEvent* event) {
|
||||
auto* count = static_cast<std::atomic_uint32_t*>(event->filter.data);
|
||||
count->fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -431,6 +495,7 @@ void TestConcurrentDelete() {
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestPeriodicTimerEvents();
|
||||
TestDuplicateAddPreservesEventState();
|
||||
TestCallbackStateOutlivesPort();
|
||||
TestCallbackOwnsPayload();
|
||||
|
||||
Reference in New Issue
Block a user