199a2dd95SBruce Richardson /* SPDX-License-Identifier: BSD-3-Clause 299a2dd95SBruce Richardson * Copyright(c) 2010-2018 Intel Corporation 399a2dd95SBruce Richardson */ 499a2dd95SBruce Richardson 599a2dd95SBruce Richardson #include <sys/types.h> 699a2dd95SBruce Richardson #include <sys/stat.h> 72744cb6eSThomas Monjalon #include <pthread.h> 899a2dd95SBruce Richardson #include <fcntl.h> 999a2dd95SBruce Richardson #include <stdio.h> 1099a2dd95SBruce Richardson #include <stdlib.h> 1199a2dd95SBruce Richardson #include <string.h> 1299a2dd95SBruce Richardson #include <time.h> 1399a2dd95SBruce Richardson #include <errno.h> 1499a2dd95SBruce Richardson 1545a685caSAnkur Dwivedi #include <eal_trace_internal.h> 1699a2dd95SBruce Richardson #include <rte_alarm.h> 1799a2dd95SBruce Richardson #include <rte_cycles.h> 1899a2dd95SBruce Richardson #include <rte_common.h> 1999a2dd95SBruce Richardson #include <rte_errno.h> 2099a2dd95SBruce Richardson #include <rte_interrupts.h> 2199a2dd95SBruce Richardson #include <rte_spinlock.h> 2299a2dd95SBruce Richardson 2399a2dd95SBruce Richardson #include "eal_private.h" 2499a2dd95SBruce Richardson #include "eal_alarm_private.h" 2599a2dd95SBruce Richardson 2699a2dd95SBruce Richardson #define NS_PER_US 1000 2799a2dd95SBruce Richardson 2899a2dd95SBruce Richardson #ifdef CLOCK_MONOTONIC_RAW /* Defined in glibc bits/time.h */ 2999a2dd95SBruce Richardson #define CLOCK_TYPE_ID CLOCK_MONOTONIC_RAW 3099a2dd95SBruce Richardson #else 3199a2dd95SBruce Richardson #define CLOCK_TYPE_ID CLOCK_MONOTONIC 3299a2dd95SBruce Richardson #endif 3399a2dd95SBruce Richardson 3499a2dd95SBruce Richardson struct alarm_entry { 3599a2dd95SBruce Richardson LIST_ENTRY(alarm_entry) next; 3699a2dd95SBruce Richardson struct timespec time; 3799a2dd95SBruce Richardson rte_eal_alarm_callback cb_fn; 3899a2dd95SBruce Richardson void *cb_arg; 3999a2dd95SBruce Richardson volatile uint8_t executing; 4099a2dd95SBruce Richardson volatile pthread_t executing_id; 4199a2dd95SBruce Richardson }; 4299a2dd95SBruce Richardson 4399a2dd95SBruce Richardson static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER(); 4499a2dd95SBruce Richardson static rte_spinlock_t alarm_list_lk = RTE_SPINLOCK_INITIALIZER; 4599a2dd95SBruce Richardson 4690b13ab8SHarman Kalra static struct rte_intr_handle *intr_handle; 4799a2dd95SBruce Richardson static void eal_alarm_callback(void *arg); 4899a2dd95SBruce Richardson 4990b13ab8SHarman Kalra void 5090b13ab8SHarman Kalra rte_eal_alarm_cleanup(void) 5190b13ab8SHarman Kalra { 5290b13ab8SHarman Kalra rte_intr_instance_free(intr_handle); 5390b13ab8SHarman Kalra } 5490b13ab8SHarman Kalra 5599a2dd95SBruce Richardson int 5699a2dd95SBruce Richardson rte_eal_alarm_init(void) 5799a2dd95SBruce Richardson { 5890b13ab8SHarman Kalra int fd; 5990b13ab8SHarman Kalra 6090b13ab8SHarman Kalra intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); 6190b13ab8SHarman Kalra if (intr_handle == NULL) { 62ae67895bSDavid Marchand EAL_LOG(ERR, "Fail to allocate intr_handle"); 6390b13ab8SHarman Kalra goto error; 6490b13ab8SHarman Kalra } 6590b13ab8SHarman Kalra 6690b13ab8SHarman Kalra if (rte_intr_type_set(intr_handle, RTE_INTR_HANDLE_ALARM)) 6790b13ab8SHarman Kalra goto error; 6890b13ab8SHarman Kalra 6990b13ab8SHarman Kalra if (rte_intr_fd_set(intr_handle, -1)) 7090b13ab8SHarman Kalra goto error; 7199a2dd95SBruce Richardson 7299a2dd95SBruce Richardson /* on FreeBSD, timers don't use fd's, and their identifiers are stored 7399a2dd95SBruce Richardson * in separate namespace from fd's, so using any value is OK. however, 7499a2dd95SBruce Richardson * EAL interrupts handler expects fd's to be unique, so use an actual fd 7599a2dd95SBruce Richardson * to guarantee unique timer identifier. 7699a2dd95SBruce Richardson */ 7790b13ab8SHarman Kalra fd = open("/dev/zero", O_RDONLY); 7890b13ab8SHarman Kalra 7990b13ab8SHarman Kalra if (rte_intr_fd_set(intr_handle, fd)) 8090b13ab8SHarman Kalra goto error; 8199a2dd95SBruce Richardson 8299a2dd95SBruce Richardson return 0; 8390b13ab8SHarman Kalra error: 8490b13ab8SHarman Kalra rte_intr_instance_free(intr_handle); 8590b13ab8SHarman Kalra return -1; 8699a2dd95SBruce Richardson } 8799a2dd95SBruce Richardson 8899a2dd95SBruce Richardson static inline int 8999a2dd95SBruce Richardson timespec_cmp(const struct timespec *now, const struct timespec *at) 9099a2dd95SBruce Richardson { 9199a2dd95SBruce Richardson if (now->tv_sec < at->tv_sec) 9299a2dd95SBruce Richardson return -1; 9399a2dd95SBruce Richardson if (now->tv_sec > at->tv_sec) 9499a2dd95SBruce Richardson return 1; 9599a2dd95SBruce Richardson if (now->tv_nsec < at->tv_nsec) 9699a2dd95SBruce Richardson return -1; 9799a2dd95SBruce Richardson if (now->tv_nsec > at->tv_nsec) 9899a2dd95SBruce Richardson return 1; 9999a2dd95SBruce Richardson return 0; 10099a2dd95SBruce Richardson } 10199a2dd95SBruce Richardson 10299a2dd95SBruce Richardson static inline uint64_t 10399a2dd95SBruce Richardson diff_ns(struct timespec *now, struct timespec *at) 10499a2dd95SBruce Richardson { 10599a2dd95SBruce Richardson uint64_t now_ns, at_ns; 10699a2dd95SBruce Richardson 10799a2dd95SBruce Richardson if (timespec_cmp(now, at) >= 0) 10899a2dd95SBruce Richardson return 0; 10999a2dd95SBruce Richardson 11099a2dd95SBruce Richardson now_ns = now->tv_sec * NS_PER_S + now->tv_nsec; 11199a2dd95SBruce Richardson at_ns = at->tv_sec * NS_PER_S + at->tv_nsec; 11299a2dd95SBruce Richardson 11399a2dd95SBruce Richardson return at_ns - now_ns; 11499a2dd95SBruce Richardson } 11599a2dd95SBruce Richardson 11699a2dd95SBruce Richardson int 11799a2dd95SBruce Richardson eal_alarm_get_timeout_ns(uint64_t *val) 11899a2dd95SBruce Richardson { 11999a2dd95SBruce Richardson struct alarm_entry *ap; 12099a2dd95SBruce Richardson struct timespec now; 12199a2dd95SBruce Richardson 12299a2dd95SBruce Richardson if (clock_gettime(CLOCK_TYPE_ID, &now) < 0) 12399a2dd95SBruce Richardson return -1; 12499a2dd95SBruce Richardson 12599a2dd95SBruce Richardson if (LIST_EMPTY(&alarm_list)) 12699a2dd95SBruce Richardson return -1; 12799a2dd95SBruce Richardson 12899a2dd95SBruce Richardson ap = LIST_FIRST(&alarm_list); 12999a2dd95SBruce Richardson 13099a2dd95SBruce Richardson *val = diff_ns(&now, &ap->time); 13199a2dd95SBruce Richardson 13299a2dd95SBruce Richardson return 0; 13399a2dd95SBruce Richardson } 13499a2dd95SBruce Richardson 13599a2dd95SBruce Richardson static int 13699a2dd95SBruce Richardson unregister_current_callback(void) 13799a2dd95SBruce Richardson { 13899a2dd95SBruce Richardson struct alarm_entry *ap; 13999a2dd95SBruce Richardson int ret = 0; 14099a2dd95SBruce Richardson 14199a2dd95SBruce Richardson if (!LIST_EMPTY(&alarm_list)) { 14299a2dd95SBruce Richardson ap = LIST_FIRST(&alarm_list); 14399a2dd95SBruce Richardson 14499a2dd95SBruce Richardson do { 14590b13ab8SHarman Kalra ret = rte_intr_callback_unregister(intr_handle, 14699a2dd95SBruce Richardson eal_alarm_callback, &ap->time); 14799a2dd95SBruce Richardson } while (ret == -EAGAIN); 14899a2dd95SBruce Richardson } 14999a2dd95SBruce Richardson 15099a2dd95SBruce Richardson return ret; 15199a2dd95SBruce Richardson } 15299a2dd95SBruce Richardson 15399a2dd95SBruce Richardson static int 15499a2dd95SBruce Richardson register_first_callback(void) 15599a2dd95SBruce Richardson { 15699a2dd95SBruce Richardson struct alarm_entry *ap; 15799a2dd95SBruce Richardson int ret = 0; 15899a2dd95SBruce Richardson 15999a2dd95SBruce Richardson if (!LIST_EMPTY(&alarm_list)) { 16099a2dd95SBruce Richardson ap = LIST_FIRST(&alarm_list); 16199a2dd95SBruce Richardson 16299a2dd95SBruce Richardson /* register a new callback */ 16390b13ab8SHarman Kalra ret = rte_intr_callback_register(intr_handle, 16499a2dd95SBruce Richardson eal_alarm_callback, &ap->time); 16599a2dd95SBruce Richardson } 16699a2dd95SBruce Richardson return ret; 16799a2dd95SBruce Richardson } 16899a2dd95SBruce Richardson 16999a2dd95SBruce Richardson static void 17099a2dd95SBruce Richardson eal_alarm_callback(void *arg __rte_unused) 17199a2dd95SBruce Richardson { 17299a2dd95SBruce Richardson struct timespec now; 17399a2dd95SBruce Richardson struct alarm_entry *ap; 17499a2dd95SBruce Richardson 17599a2dd95SBruce Richardson if (clock_gettime(CLOCK_TYPE_ID, &now) < 0) 17699a2dd95SBruce Richardson return; 17799a2dd95SBruce Richardson 178fc6bdd52SChengwen Feng rte_spinlock_lock(&alarm_list_lk); 179fc6bdd52SChengwen Feng ap = LIST_FIRST(&alarm_list); 180fc6bdd52SChengwen Feng 18199a2dd95SBruce Richardson while (ap != NULL && timespec_cmp(&now, &ap->time) >= 0) { 18299a2dd95SBruce Richardson ap->executing = 1; 18399a2dd95SBruce Richardson ap->executing_id = pthread_self(); 18499a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 18599a2dd95SBruce Richardson 18699a2dd95SBruce Richardson ap->cb_fn(ap->cb_arg); 18799a2dd95SBruce Richardson 18899a2dd95SBruce Richardson rte_spinlock_lock(&alarm_list_lk); 18999a2dd95SBruce Richardson 19099a2dd95SBruce Richardson LIST_REMOVE(ap, next); 19199a2dd95SBruce Richardson free(ap); 19299a2dd95SBruce Richardson 19399a2dd95SBruce Richardson ap = LIST_FIRST(&alarm_list); 19499a2dd95SBruce Richardson } 19599a2dd95SBruce Richardson 19699a2dd95SBruce Richardson /* timer has been deleted from the kqueue, so recreate it if needed */ 19799a2dd95SBruce Richardson register_first_callback(); 19899a2dd95SBruce Richardson 19999a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 20099a2dd95SBruce Richardson } 20199a2dd95SBruce Richardson 20299a2dd95SBruce Richardson 20399a2dd95SBruce Richardson int 20499a2dd95SBruce Richardson rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg) 20599a2dd95SBruce Richardson { 20699a2dd95SBruce Richardson struct alarm_entry *ap, *new_alarm; 20799a2dd95SBruce Richardson struct timespec now; 20899a2dd95SBruce Richardson uint64_t ns; 20999a2dd95SBruce Richardson int ret = 0; 21099a2dd95SBruce Richardson 21199a2dd95SBruce Richardson /* check parameters, also ensure us won't cause a uint64_t overflow */ 21299a2dd95SBruce Richardson if (us < 1 || us > (UINT64_MAX - US_PER_S) || cb_fn == NULL) 21399a2dd95SBruce Richardson return -EINVAL; 21499a2dd95SBruce Richardson 21599a2dd95SBruce Richardson new_alarm = calloc(1, sizeof(*new_alarm)); 21699a2dd95SBruce Richardson if (new_alarm == NULL) 21799a2dd95SBruce Richardson return -ENOMEM; 21899a2dd95SBruce Richardson 21999a2dd95SBruce Richardson /* use current time to calculate absolute time of alarm */ 22099a2dd95SBruce Richardson clock_gettime(CLOCK_TYPE_ID, &now); 22199a2dd95SBruce Richardson 22299a2dd95SBruce Richardson ns = us * NS_PER_US; 22399a2dd95SBruce Richardson 22499a2dd95SBruce Richardson new_alarm->cb_fn = cb_fn; 22599a2dd95SBruce Richardson new_alarm->cb_arg = cb_arg; 22699a2dd95SBruce Richardson new_alarm->time.tv_nsec = (now.tv_nsec + ns) % NS_PER_S; 22799a2dd95SBruce Richardson new_alarm->time.tv_sec = now.tv_sec + ((now.tv_nsec + ns) / NS_PER_S); 22899a2dd95SBruce Richardson 22999a2dd95SBruce Richardson rte_spinlock_lock(&alarm_list_lk); 23099a2dd95SBruce Richardson 23199a2dd95SBruce Richardson if (LIST_EMPTY(&alarm_list)) 23299a2dd95SBruce Richardson LIST_INSERT_HEAD(&alarm_list, new_alarm, next); 23399a2dd95SBruce Richardson else { 23499a2dd95SBruce Richardson LIST_FOREACH(ap, &alarm_list, next) { 23599a2dd95SBruce Richardson if (timespec_cmp(&new_alarm->time, &ap->time) < 0) { 23699a2dd95SBruce Richardson LIST_INSERT_BEFORE(ap, new_alarm, next); 23799a2dd95SBruce Richardson break; 23899a2dd95SBruce Richardson } 23999a2dd95SBruce Richardson if (LIST_NEXT(ap, next) == NULL) { 24099a2dd95SBruce Richardson LIST_INSERT_AFTER(ap, new_alarm, next); 24199a2dd95SBruce Richardson break; 24299a2dd95SBruce Richardson } 24399a2dd95SBruce Richardson } 24499a2dd95SBruce Richardson } 24599a2dd95SBruce Richardson 24699a2dd95SBruce Richardson /* re-register first callback just in case */ 24799a2dd95SBruce Richardson register_first_callback(); 24899a2dd95SBruce Richardson 24999a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 25099a2dd95SBruce Richardson 25199a2dd95SBruce Richardson rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret); 25299a2dd95SBruce Richardson return ret; 25399a2dd95SBruce Richardson } 25499a2dd95SBruce Richardson 25599a2dd95SBruce Richardson int 25699a2dd95SBruce Richardson rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg) 25799a2dd95SBruce Richardson { 25899a2dd95SBruce Richardson struct alarm_entry *ap, *ap_prev; 25999a2dd95SBruce Richardson int count = 0; 26099a2dd95SBruce Richardson int err = 0; 26199a2dd95SBruce Richardson int executing; 26299a2dd95SBruce Richardson 26399a2dd95SBruce Richardson if (!cb_fn) { 26499a2dd95SBruce Richardson rte_errno = EINVAL; 26599a2dd95SBruce Richardson return -1; 26699a2dd95SBruce Richardson } 26799a2dd95SBruce Richardson 26899a2dd95SBruce Richardson do { 26999a2dd95SBruce Richardson executing = 0; 27099a2dd95SBruce Richardson rte_spinlock_lock(&alarm_list_lk); 27199a2dd95SBruce Richardson /* remove any matches at the start of the list */ 27299a2dd95SBruce Richardson while (1) { 27399a2dd95SBruce Richardson ap = LIST_FIRST(&alarm_list); 27499a2dd95SBruce Richardson if (ap == NULL) 27599a2dd95SBruce Richardson break; 27699a2dd95SBruce Richardson if (cb_fn != ap->cb_fn) 27799a2dd95SBruce Richardson break; 27899a2dd95SBruce Richardson if (cb_arg != ap->cb_arg && cb_arg != (void *) -1) 27999a2dd95SBruce Richardson break; 28099a2dd95SBruce Richardson if (ap->executing == 0) { 28199a2dd95SBruce Richardson LIST_REMOVE(ap, next); 28299a2dd95SBruce Richardson free(ap); 28399a2dd95SBruce Richardson count++; 28499a2dd95SBruce Richardson } else { 28599a2dd95SBruce Richardson /* If calling from other context, mark that 28699a2dd95SBruce Richardson * alarm is executing so loop can spin till it 28799a2dd95SBruce Richardson * finish. Otherwise we are trying to cancel 28899a2dd95SBruce Richardson * ourselves - mark it by EINPROGRESS. 28999a2dd95SBruce Richardson */ 29099a2dd95SBruce Richardson if (pthread_equal(ap->executing_id, 29199a2dd95SBruce Richardson pthread_self()) == 0) 29299a2dd95SBruce Richardson executing++; 29399a2dd95SBruce Richardson else 29499a2dd95SBruce Richardson err = EINPROGRESS; 29599a2dd95SBruce Richardson 29699a2dd95SBruce Richardson break; 29799a2dd95SBruce Richardson } 29899a2dd95SBruce Richardson } 29999a2dd95SBruce Richardson ap_prev = ap; 30099a2dd95SBruce Richardson 30199a2dd95SBruce Richardson /* now go through list, removing entries not at start */ 30299a2dd95SBruce Richardson LIST_FOREACH(ap, &alarm_list, next) { 30399a2dd95SBruce Richardson /* this won't be true first time through */ 30499a2dd95SBruce Richardson if (cb_fn == ap->cb_fn && 30599a2dd95SBruce Richardson (cb_arg == (void *)-1 || 30699a2dd95SBruce Richardson cb_arg == ap->cb_arg)) { 30799a2dd95SBruce Richardson if (ap->executing == 0) { 30899a2dd95SBruce Richardson LIST_REMOVE(ap, next); 30999a2dd95SBruce Richardson free(ap); 31099a2dd95SBruce Richardson count++; 31199a2dd95SBruce Richardson ap = ap_prev; 31299a2dd95SBruce Richardson } else if (pthread_equal(ap->executing_id, 31399a2dd95SBruce Richardson pthread_self()) == 0) { 31499a2dd95SBruce Richardson executing++; 31599a2dd95SBruce Richardson } else { 31699a2dd95SBruce Richardson err = EINPROGRESS; 31799a2dd95SBruce Richardson } 31899a2dd95SBruce Richardson } 31999a2dd95SBruce Richardson ap_prev = ap; 32099a2dd95SBruce Richardson } 321*a4835c22SWojciech Panfil 32299a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 323*a4835c22SWojciech Panfil 324*a4835c22SWojciech Panfil /* Yield control to a second thread executing eal_alarm_callback to avoid 325*a4835c22SWojciech Panfil * its starvation, as it is waiting for the lock we have just released. 326*a4835c22SWojciech Panfil */ 327*a4835c22SWojciech Panfil sched_yield(); 32899a2dd95SBruce Richardson } while (executing != 0); 32999a2dd95SBruce Richardson 33099a2dd95SBruce Richardson if (count == 0 && err == 0) 33199a2dd95SBruce Richardson rte_errno = ENOENT; 33299a2dd95SBruce Richardson else if (err) 33399a2dd95SBruce Richardson rte_errno = err; 33499a2dd95SBruce Richardson 33599a2dd95SBruce Richardson rte_spinlock_lock(&alarm_list_lk); 33699a2dd95SBruce Richardson 33799a2dd95SBruce Richardson /* unregister if no alarms left, otherwise re-register first */ 33899a2dd95SBruce Richardson if (LIST_EMPTY(&alarm_list)) 33999a2dd95SBruce Richardson unregister_current_callback(); 34099a2dd95SBruce Richardson else 34199a2dd95SBruce Richardson register_first_callback(); 34299a2dd95SBruce Richardson 34399a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 34499a2dd95SBruce Richardson 34599a2dd95SBruce Richardson rte_eal_trace_alarm_cancel(cb_fn, cb_arg, count); 34699a2dd95SBruce Richardson return count; 34799a2dd95SBruce Richardson } 348