--- /dev/null
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2020 Dmitry Kozlyuk
+ */
+
+#include <stdatomic.h>
+#include <stdbool.h>
+
+#include <rte_alarm.h>
+#include <rte_spinlock.h>
+
+#include <rte_eal_trace.h>
+
+#include "eal_windows.h"
+
+enum alarm_state {
+ ALARM_ARMED,
+ ALARM_TRIGGERED,
+ ALARM_CANCELLED
+};
+
+struct alarm_entry {
+ LIST_ENTRY(alarm_entry) next;
+ rte_eal_alarm_callback cb_fn;
+ void *cb_arg;
+ HANDLE timer;
+ atomic_uint state;
+};
+
+static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
+
+static rte_spinlock_t alarm_lock = RTE_SPINLOCK_INITIALIZER;
+
+static int intr_thread_exec(void (*func)(void *arg), void *arg);
+
+static void
+alarm_remove_unsafe(struct alarm_entry *ap)
+{
+ LIST_REMOVE(ap, next);
+ CloseHandle(ap->timer);
+ free(ap);
+}
+
+static void
+alarm_callback(void *arg, DWORD low __rte_unused, DWORD high __rte_unused)
+{
+ struct alarm_entry *ap = arg;
+ unsigned int state = ALARM_ARMED;
+
+ if (!atomic_compare_exchange_strong(
+ &ap->state, &state, ALARM_TRIGGERED))
+ return;
+
+ ap->cb_fn(ap->cb_arg);
+
+ rte_spinlock_lock(&alarm_lock);
+ alarm_remove_unsafe(ap);
+ rte_spinlock_unlock(&alarm_lock);
+}
+
+struct alarm_task {
+ struct alarm_entry *entry;
+ LARGE_INTEGER deadline;
+ int ret;
+};
+
+static void
+alarm_set(void *arg)
+{
+ struct alarm_task *task = arg;
+
+ BOOL ret = SetWaitableTimer(
+ task->entry->timer, &task->deadline,
+ 0, alarm_callback, task->entry, FALSE);
+ task->ret = ret ? 0 : (-1);
+}
+
+int
+rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
+{
+ struct alarm_entry *ap;
+ HANDLE timer;
+ FILETIME ft;
+ struct alarm_task task;
+ int ret;
+
+ /* Calculate deadline ASAP, unit of measure = 100ns. */
+ GetSystemTimePreciseAsFileTime(&ft);
+ task.deadline.LowPart = ft.dwLowDateTime;
+ task.deadline.HighPart = ft.dwHighDateTime;
+ task.deadline.QuadPart += 10 * us;
+
+ ap = calloc(1, sizeof(*ap));
+ if (ap == NULL) {
+ RTE_LOG(ERR, EAL, "Cannot allocate alarm entry\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ timer = CreateWaitableTimer(NULL, FALSE, NULL);
+ if (timer == NULL) {
+ RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ap->timer = timer;
+ ap->cb_fn = cb_fn;
+ ap->cb_arg = cb_arg;
+ task.entry = ap;
+
+ /* Waitable timer must be set in the same thread that will
+ * do an alertable wait for the alarm to trigger, that is,
+ * in the interrupt thread. Setting can fail, so do it synchronously.
+ */
+ ret = intr_thread_exec(alarm_set, &task);
+ if (ret < 0) {
+ RTE_LOG(ERR, EAL, "Cannot setup alarm in interrupt thread\n");
+ goto fail;
+ }
+
+ ret = task.ret;
+ if (ret < 0)
+ goto fail;
+
+ rte_spinlock_lock(&alarm_lock);
+ LIST_INSERT_HEAD(&alarm_list, ap, next);
+ rte_spinlock_unlock(&alarm_lock);
+
+ goto exit;
+
+fail:
+ if (timer != NULL)
+ CloseHandle(timer);
+ if (ap != NULL)
+ free(ap);
+
+exit:
+ rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
+ return ret;
+}
+
+static bool
+alarm_matches(const struct alarm_entry *ap,
+ rte_eal_alarm_callback cb_fn, void *cb_arg)
+{
+ bool any_arg = cb_arg == (void *)(-1);
+ return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg);
+}
+
+int
+rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
+{
+ struct alarm_entry *ap;
+ unsigned int state;
+ int removed;
+ bool executing;
+
+ removed = 0;
+ do {
+ executing = false;
+
+ rte_spinlock_lock(&alarm_lock);
+
+ LIST_FOREACH(ap, &alarm_list, next) {
+ if (!alarm_matches(ap, cb_fn, cb_arg))
+ continue;
+
+ state = ALARM_ARMED;
+ if (atomic_compare_exchange_strong(
+ &ap->state, &state, ALARM_CANCELLED)) {
+ alarm_remove_unsafe(ap);
+ removed++;
+ } else if (state == ALARM_TRIGGERED)
+ executing = true;
+ }
+
+ rte_spinlock_unlock(&alarm_lock);
+ } while (executing);
+
+ rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed);
+ return removed;
+}
+
+struct intr_task {
+ void (*func)(void *arg);
+ void *arg;
+ rte_spinlock_t lock; /* unlocked at task completion */
+};
+
+static void
+intr_thread_entry(void *arg)
+{
+ struct intr_task *task = arg;
+ task->func(task->arg);
+ rte_spinlock_unlock(&task->lock);
+}
+
+static int
+intr_thread_exec(void (*func)(void *arg), void *arg)
+{
+ struct intr_task task;
+ int ret;
+
+ task.func = func;
+ task.arg = arg;
+ rte_spinlock_init(&task.lock);
+
+ /* Make timers more precise by synchronizing in userspace. */
+ rte_spinlock_lock(&task.lock);
+ ret = eal_intr_thread_schedule(intr_thread_entry, &task);
+ if (ret < 0) {
+ RTE_LOG(ERR, EAL, "Cannot schedule task to interrupt thread\n");
+ return -EINVAL;
+ }
+
+ /* Wait for the task to complete. */
+ rte_spinlock_lock(&task.lock);
+ return 0;
+}