199a2dd95SBruce Richardson /* SPDX-License-Identifier: BSD-3-Clause 299a2dd95SBruce Richardson * Copyright (c) 2020 Dmitry Kozlyuk 399a2dd95SBruce Richardson */ 499a2dd95SBruce Richardson 599a2dd95SBruce Richardson #include <stdatomic.h> 699a2dd95SBruce Richardson #include <stdbool.h> 7f1f6ebc0SWilliam Tu #include <sys/queue.h> 899a2dd95SBruce Richardson 999a2dd95SBruce Richardson #include <rte_alarm.h> 1099a2dd95SBruce Richardson #include <rte_spinlock.h> 1199a2dd95SBruce Richardson 12ae67895bSDavid Marchand #include "eal_private.h" 1345a685caSAnkur Dwivedi #include <eal_trace_internal.h> 1499a2dd95SBruce Richardson #include "eal_windows.h" 1599a2dd95SBruce Richardson 1699a2dd95SBruce Richardson enum alarm_state { 1799a2dd95SBruce Richardson ALARM_ARMED, 1899a2dd95SBruce Richardson ALARM_TRIGGERED, 1999a2dd95SBruce Richardson ALARM_CANCELLED 2099a2dd95SBruce Richardson }; 2199a2dd95SBruce Richardson 2299a2dd95SBruce Richardson struct alarm_entry { 2399a2dd95SBruce Richardson LIST_ENTRY(alarm_entry) next; 2499a2dd95SBruce Richardson rte_eal_alarm_callback cb_fn; 2599a2dd95SBruce Richardson void *cb_arg; 2699a2dd95SBruce Richardson HANDLE timer; 2799a2dd95SBruce Richardson atomic_uint state; 2899a2dd95SBruce Richardson }; 2999a2dd95SBruce Richardson 3099a2dd95SBruce Richardson static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER(); 3199a2dd95SBruce Richardson 3299a2dd95SBruce Richardson static rte_spinlock_t alarm_lock = RTE_SPINLOCK_INITIALIZER; 3399a2dd95SBruce Richardson 3499a2dd95SBruce Richardson static int intr_thread_exec_sync(void (*func)(void *arg), void *arg); 3599a2dd95SBruce Richardson 3699a2dd95SBruce Richardson static void 3799a2dd95SBruce Richardson alarm_remove_unsafe(struct alarm_entry *ap) 3899a2dd95SBruce Richardson { 3999a2dd95SBruce Richardson LIST_REMOVE(ap, next); 4099a2dd95SBruce Richardson CloseHandle(ap->timer); 4199a2dd95SBruce Richardson free(ap); 4299a2dd95SBruce Richardson } 4399a2dd95SBruce Richardson 4499a2dd95SBruce Richardson static void 4599a2dd95SBruce Richardson alarm_callback(void *arg, DWORD low __rte_unused, DWORD high __rte_unused) 4699a2dd95SBruce Richardson { 4799a2dd95SBruce Richardson struct alarm_entry *ap = arg; 4899a2dd95SBruce Richardson unsigned int state = ALARM_ARMED; 4999a2dd95SBruce Richardson 5099a2dd95SBruce Richardson if (!atomic_compare_exchange_strong( 5199a2dd95SBruce Richardson &ap->state, &state, ALARM_TRIGGERED)) 5299a2dd95SBruce Richardson return; 5399a2dd95SBruce Richardson 5499a2dd95SBruce Richardson ap->cb_fn(ap->cb_arg); 5599a2dd95SBruce Richardson 5699a2dd95SBruce Richardson rte_spinlock_lock(&alarm_lock); 5799a2dd95SBruce Richardson alarm_remove_unsafe(ap); 5899a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_lock); 5999a2dd95SBruce Richardson } 6099a2dd95SBruce Richardson 6199a2dd95SBruce Richardson static int 6299a2dd95SBruce Richardson alarm_set(struct alarm_entry *entry, LARGE_INTEGER deadline) 6399a2dd95SBruce Richardson { 6499a2dd95SBruce Richardson BOOL ret = SetWaitableTimer( 6599a2dd95SBruce Richardson entry->timer, &deadline, 0, alarm_callback, entry, FALSE); 6699a2dd95SBruce Richardson if (!ret) { 6799a2dd95SBruce Richardson RTE_LOG_WIN32_ERR("SetWaitableTimer"); 6899a2dd95SBruce Richardson return -1; 6999a2dd95SBruce Richardson } 7099a2dd95SBruce Richardson return 0; 7199a2dd95SBruce Richardson } 7299a2dd95SBruce Richardson 7399a2dd95SBruce Richardson struct alarm_task { 7499a2dd95SBruce Richardson struct alarm_entry *entry; 7599a2dd95SBruce Richardson LARGE_INTEGER deadline; 7699a2dd95SBruce Richardson int ret; 7799a2dd95SBruce Richardson }; 7899a2dd95SBruce Richardson 7999a2dd95SBruce Richardson static void 8099a2dd95SBruce Richardson alarm_task_exec(void *arg) 8199a2dd95SBruce Richardson { 8299a2dd95SBruce Richardson struct alarm_task *task = arg; 8399a2dd95SBruce Richardson task->ret = alarm_set(task->entry, task->deadline); 8499a2dd95SBruce Richardson } 8599a2dd95SBruce Richardson 8699a2dd95SBruce Richardson int 8799a2dd95SBruce Richardson rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg) 8899a2dd95SBruce Richardson { 8999a2dd95SBruce Richardson struct alarm_entry *ap; 9099a2dd95SBruce Richardson HANDLE timer; 9199a2dd95SBruce Richardson FILETIME ft; 9299a2dd95SBruce Richardson LARGE_INTEGER deadline; 9399a2dd95SBruce Richardson int ret; 9499a2dd95SBruce Richardson 957f34ecb6SStephen Hemminger /* Check parameters, including that us won't cause a uint64_t overflow */ 967f34ecb6SStephen Hemminger if (us < 1 || us > (UINT64_MAX - US_PER_S)) { 977f34ecb6SStephen Hemminger EAL_LOG(ERR, "Invalid alarm interval"); 987f34ecb6SStephen Hemminger ret = -EINVAL; 997f34ecb6SStephen Hemminger goto exit; 1007f34ecb6SStephen Hemminger } 1017f34ecb6SStephen Hemminger 102b8617fccSJie Zhou if (cb_fn == NULL) { 103ae67895bSDavid Marchand EAL_LOG(ERR, "NULL callback"); 104b8617fccSJie Zhou ret = -EINVAL; 105b8617fccSJie Zhou goto exit; 106b8617fccSJie Zhou } 107b8617fccSJie Zhou 10899a2dd95SBruce Richardson /* Calculate deadline ASAP, unit of measure = 100ns. */ 10999a2dd95SBruce Richardson GetSystemTimePreciseAsFileTime(&ft); 11099a2dd95SBruce Richardson deadline.LowPart = ft.dwLowDateTime; 11199a2dd95SBruce Richardson deadline.HighPart = ft.dwHighDateTime; 11299a2dd95SBruce Richardson deadline.QuadPart += 10 * us; 11399a2dd95SBruce Richardson 11499a2dd95SBruce Richardson ap = calloc(1, sizeof(*ap)); 11599a2dd95SBruce Richardson if (ap == NULL) { 116ae67895bSDavid Marchand EAL_LOG(ERR, "Cannot allocate alarm entry"); 11799a2dd95SBruce Richardson ret = -ENOMEM; 11899a2dd95SBruce Richardson goto exit; 11999a2dd95SBruce Richardson } 12099a2dd95SBruce Richardson 12199a2dd95SBruce Richardson timer = CreateWaitableTimer(NULL, FALSE, NULL); 12299a2dd95SBruce Richardson if (timer == NULL) { 12399a2dd95SBruce Richardson RTE_LOG_WIN32_ERR("CreateWaitableTimer()"); 12499a2dd95SBruce Richardson ret = -EINVAL; 12599a2dd95SBruce Richardson goto fail; 12699a2dd95SBruce Richardson } 12799a2dd95SBruce Richardson 12899a2dd95SBruce Richardson ap->timer = timer; 12999a2dd95SBruce Richardson ap->cb_fn = cb_fn; 13099a2dd95SBruce Richardson ap->cb_arg = cb_arg; 13199a2dd95SBruce Richardson 13299a2dd95SBruce Richardson /* Waitable timer must be set in the same thread that will 13399a2dd95SBruce Richardson * do an alertable wait for the alarm to trigger, that is, 13499a2dd95SBruce Richardson * in the interrupt thread. 13599a2dd95SBruce Richardson */ 13699a2dd95SBruce Richardson if (rte_thread_is_intr()) { 13799a2dd95SBruce Richardson /* Directly schedule callback execution. */ 13899a2dd95SBruce Richardson ret = alarm_set(ap, deadline); 13999a2dd95SBruce Richardson if (ret < 0) { 140ae67895bSDavid Marchand EAL_LOG(ERR, "Cannot setup alarm"); 14199a2dd95SBruce Richardson goto fail; 14299a2dd95SBruce Richardson } 14399a2dd95SBruce Richardson } else { 14499a2dd95SBruce Richardson /* Dispatch a task to set alarm into the interrupt thread. 14599a2dd95SBruce Richardson * Execute it synchronously, because it can fail. 14699a2dd95SBruce Richardson */ 14799a2dd95SBruce Richardson struct alarm_task task = { 14899a2dd95SBruce Richardson .entry = ap, 14999a2dd95SBruce Richardson .deadline = deadline, 15099a2dd95SBruce Richardson }; 15199a2dd95SBruce Richardson 15299a2dd95SBruce Richardson ret = intr_thread_exec_sync(alarm_task_exec, &task); 15399a2dd95SBruce Richardson if (ret < 0) { 154ae67895bSDavid Marchand EAL_LOG(ERR, "Cannot setup alarm in interrupt thread"); 15599a2dd95SBruce Richardson goto fail; 15699a2dd95SBruce Richardson } 15799a2dd95SBruce Richardson 15899a2dd95SBruce Richardson ret = task.ret; 15999a2dd95SBruce Richardson if (ret < 0) 16099a2dd95SBruce Richardson goto fail; 16199a2dd95SBruce Richardson } 16299a2dd95SBruce Richardson 16399a2dd95SBruce Richardson rte_spinlock_lock(&alarm_lock); 16499a2dd95SBruce Richardson LIST_INSERT_HEAD(&alarm_list, ap, next); 16599a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_lock); 16699a2dd95SBruce Richardson 16799a2dd95SBruce Richardson goto exit; 16899a2dd95SBruce Richardson 16999a2dd95SBruce Richardson fail: 17099a2dd95SBruce Richardson if (timer != NULL) 17199a2dd95SBruce Richardson CloseHandle(timer); 17299a2dd95SBruce Richardson free(ap); 17399a2dd95SBruce Richardson 17499a2dd95SBruce Richardson exit: 17599a2dd95SBruce Richardson rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret); 17699a2dd95SBruce Richardson return ret; 17799a2dd95SBruce Richardson } 17899a2dd95SBruce Richardson 17999a2dd95SBruce Richardson static bool 18099a2dd95SBruce Richardson alarm_matches(const struct alarm_entry *ap, 18199a2dd95SBruce Richardson rte_eal_alarm_callback cb_fn, void *cb_arg) 18299a2dd95SBruce Richardson { 18399a2dd95SBruce Richardson bool any_arg = cb_arg == (void *)(-1); 18499a2dd95SBruce Richardson return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg); 18599a2dd95SBruce Richardson } 18699a2dd95SBruce Richardson 18799a2dd95SBruce Richardson int 18899a2dd95SBruce Richardson rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg) 18999a2dd95SBruce Richardson { 19099a2dd95SBruce Richardson struct alarm_entry *ap; 19199a2dd95SBruce Richardson unsigned int state; 19299a2dd95SBruce Richardson int removed; 19399a2dd95SBruce Richardson bool executing; 19499a2dd95SBruce Richardson 19599a2dd95SBruce Richardson removed = 0; 196b8617fccSJie Zhou 197b8617fccSJie Zhou if (cb_fn == NULL) { 198ae67895bSDavid Marchand EAL_LOG(ERR, "NULL callback"); 199b8617fccSJie Zhou return -EINVAL; 200b8617fccSJie Zhou } 201b8617fccSJie Zhou 20299a2dd95SBruce Richardson do { 20399a2dd95SBruce Richardson executing = false; 20499a2dd95SBruce Richardson 20599a2dd95SBruce Richardson rte_spinlock_lock(&alarm_lock); 20699a2dd95SBruce Richardson 20799a2dd95SBruce Richardson LIST_FOREACH(ap, &alarm_list, next) { 20899a2dd95SBruce Richardson if (!alarm_matches(ap, cb_fn, cb_arg)) 20999a2dd95SBruce Richardson continue; 21099a2dd95SBruce Richardson 21199a2dd95SBruce Richardson state = ALARM_ARMED; 21299a2dd95SBruce Richardson if (atomic_compare_exchange_strong( 21399a2dd95SBruce Richardson &ap->state, &state, ALARM_CANCELLED)) { 21499a2dd95SBruce Richardson alarm_remove_unsafe(ap); 21599a2dd95SBruce Richardson removed++; 21699a2dd95SBruce Richardson } else if (state == ALARM_TRIGGERED) 21799a2dd95SBruce Richardson executing = true; 21899a2dd95SBruce Richardson } 21999a2dd95SBruce Richardson 22099a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_lock); 221*a4835c22SWojciech Panfil 222*a4835c22SWojciech Panfil /* Yield control to a second thread executing eal_alarm_callback to avoid 223*a4835c22SWojciech Panfil * its starvation, as it is waiting for the lock we have just released. 224*a4835c22SWojciech Panfil */ 225*a4835c22SWojciech Panfil SwitchToThread(); 22699a2dd95SBruce Richardson } while (executing); 22799a2dd95SBruce Richardson 22899a2dd95SBruce Richardson rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed); 22999a2dd95SBruce Richardson return removed; 23099a2dd95SBruce Richardson } 23199a2dd95SBruce Richardson 23299a2dd95SBruce Richardson struct intr_task { 23399a2dd95SBruce Richardson void (*func)(void *arg); 23499a2dd95SBruce Richardson void *arg; 23599a2dd95SBruce Richardson rte_spinlock_t lock; /* unlocked at task completion */ 23699a2dd95SBruce Richardson }; 23799a2dd95SBruce Richardson 23899a2dd95SBruce Richardson static void 23999a2dd95SBruce Richardson intr_thread_entry(void *arg) 2406673c47dSDavid Marchand __rte_no_thread_safety_analysis 24199a2dd95SBruce Richardson { 24299a2dd95SBruce Richardson struct intr_task *task = arg; 24399a2dd95SBruce Richardson task->func(task->arg); 24499a2dd95SBruce Richardson rte_spinlock_unlock(&task->lock); 24599a2dd95SBruce Richardson } 24699a2dd95SBruce Richardson 24799a2dd95SBruce Richardson static int 24899a2dd95SBruce Richardson intr_thread_exec_sync(void (*func)(void *arg), void *arg) 2496673c47dSDavid Marchand __rte_no_thread_safety_analysis 25099a2dd95SBruce Richardson { 25199a2dd95SBruce Richardson struct intr_task task; 25299a2dd95SBruce Richardson int ret; 25399a2dd95SBruce Richardson 25499a2dd95SBruce Richardson task.func = func; 25599a2dd95SBruce Richardson task.arg = arg; 25699a2dd95SBruce Richardson rte_spinlock_init(&task.lock); 25799a2dd95SBruce Richardson 25899a2dd95SBruce Richardson /* Make timers more precise by synchronizing in userspace. */ 25999a2dd95SBruce Richardson rte_spinlock_lock(&task.lock); 26099a2dd95SBruce Richardson ret = eal_intr_thread_schedule(intr_thread_entry, &task); 26199a2dd95SBruce Richardson if (ret < 0) { 262ae67895bSDavid Marchand EAL_LOG(ERR, "Cannot schedule task to interrupt thread"); 26399a2dd95SBruce Richardson return -EINVAL; 26499a2dd95SBruce Richardson } 26599a2dd95SBruce Richardson 26699a2dd95SBruce Richardson /* Wait for the task to complete. */ 26799a2dd95SBruce Richardson rte_spinlock_lock(&task.lock); 26899a2dd95SBruce Richardson return 0; 26999a2dd95SBruce Richardson } 270