summaryrefslogtreecommitdiff
path: root/libhw_cr/host_alarmclock.c
diff options
context:
space:
mode:
Diffstat (limited to 'libhw_cr/host_alarmclock.c')
-rw-r--r--libhw_cr/host_alarmclock.c133
1 files changed, 133 insertions, 0 deletions
diff --git a/libhw_cr/host_alarmclock.c b/libhw_cr/host_alarmclock.c
new file mode 100644
index 0000000..2f255e0
--- /dev/null
+++ b/libhw_cr/host_alarmclock.c
@@ -0,0 +1,133 @@
+/* libhw_cr/host_alarmclock.c - <libhw/generic/alarmclock.h> implementation for POSIX hosts
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <errno.h>
+#include <error.h>
+#include <signal.h>
+
+#include <libcr/coroutine.h>
+#include <libmisc/assert.h>
+
+#define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES
+#include <libhw/generic/alarmclock.h>
+
+#define IMPLEMENTATION_FOR_LIBHW_HOST_ALARMCLOCK_H YES
+#include <libhw/host_alarmclock.h>
+
+#include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_ns_time() */
+
+LO_IMPLEMENTATION_C(alarmclock, struct hostclock, hostclock, static)
+
+static uint64_t hostclock_get_time_ns(struct hostclock *alarmclock) {
+ assert(alarmclock);
+
+ struct timespec ts;
+
+ if (clock_gettime(alarmclock->clock_id, &ts) != 0)
+ error(1, errno, "clock_gettime(%d)", (int)alarmclock->clock_id);
+
+ return ns_from_host_ns_time(ts);
+}
+
+static void hostclock_handle_sig_alarm(int LM_UNUSED(sig), siginfo_t *info, void *LM_UNUSED(ucontext)) {
+ struct hostclock *alarmclock = info->si_value.sival_ptr;
+ assert(alarmclock);
+
+ while (alarmclock->queue &&
+ alarmclock->queue->fire_at_ns <= hostclock_get_time_ns(alarmclock)) {
+ struct alarmclock_trigger *trigger = alarmclock->queue;
+ trigger->cb(trigger->cb_arg);
+ alarmclock->queue = trigger->next;
+ trigger->alarmclock = NULL;
+ trigger->next = NULL;
+ trigger->prev = NULL;
+ }
+
+ if (alarmclock->queue) {
+ struct itimerspec alarmspec = {
+ .it_value = ns_to_host_ns_time(alarmclock->queue->fire_at_ns),
+ .it_interval = {0},
+ };
+ if (timer_settime(alarmclock->timer_id, TIMER_ABSTIME, &alarmspec, NULL) != 0)
+ error(1, errno, "timer_settime");
+ }
+}
+
+static bool hostclock_add_trigger(struct hostclock *alarmclock,
+ struct alarmclock_trigger *trigger,
+ uint64_t fire_at_ns,
+ void (*cb)(void *),
+ void *cb_arg) {
+ assert(alarmclock);
+ assert(trigger);
+ assert(fire_at_ns);
+ assert(cb);
+
+ trigger->alarmclock = alarmclock;
+ trigger->fire_at_ns = fire_at_ns;
+ trigger->cb = cb;
+ trigger->cb_arg = cb_arg;
+
+ bool saved = cr_save_and_disable_interrupts();
+ struct alarmclock_trigger **dst = &alarmclock->queue;
+ while (*dst && fire_at_ns >= (*dst)->fire_at_ns)
+ dst = &(*dst)->next;
+ trigger->next = *dst;
+ trigger->prev = *dst ? (*dst)->prev : NULL;
+ if (*dst)
+ (*dst)->prev = trigger;
+ *dst = trigger;
+ if (!alarmclock->initialized) {
+ struct sigevent how_to_notify = {
+ .sigev_notify = SIGEV_SIGNAL,
+ .sigev_signo = host_sigrt_alloc(),
+ .sigev_value = {
+ .sival_ptr = alarmclock,
+ },
+ };
+ struct sigaction action = {
+ .sa_flags = SA_SIGINFO,
+ .sa_sigaction = hostclock_handle_sig_alarm,
+ };
+ if (sigaction(how_to_notify.sigev_signo, &action, NULL) != 0)
+ error(1, errno, "sigaction");
+ if (timer_create(alarmclock->clock_id, &how_to_notify, &alarmclock->timer_id) != 0)
+ error(1, errno, "timer_create(%d)", (int)alarmclock->clock_id);
+ alarmclock->initialized = true;
+ }
+ if (alarmclock->queue == trigger) {
+ struct itimerspec alarmspec = {
+ .it_value = ns_to_host_ns_time(trigger->fire_at_ns),
+ .it_interval = {0},
+ };
+ if (timer_settime(alarmclock->timer_id, TIMER_ABSTIME, &alarmspec, NULL) != 0)
+ error(1, errno, "timer_settime");
+ }
+ cr_restore_interrupts(saved);
+
+ return false;
+}
+
+static void hostclock_del_trigger(struct hostclock *alarmclock,
+ struct alarmclock_trigger *trigger) {
+ assert(alarmclock);
+ assert(trigger);
+
+ bool saved = cr_save_and_disable_interrupts();
+ if (trigger->alarmclock == alarmclock) {
+ if (!trigger->prev)
+ alarmclock->queue = trigger->next;
+ else
+ trigger->prev->next = trigger->next;
+ if (trigger->next)
+ trigger->next->prev = trigger->prev;
+ trigger->alarmclock = NULL;
+ trigger->prev = NULL;
+ trigger->next = NULL;
+ } else
+ assert(!trigger->alarmclock);
+ cr_restore_interrupts(saved);
+}