diff options
Diffstat (limited to 'libhw_cr/host_alarmclock.c')
-rw-r--r-- | libhw_cr/host_alarmclock.c | 133 |
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); +} |