199a2dd95SBruce Richardson /* SPDX-License-Identifier: BSD-3-Clause 299a2dd95SBruce Richardson * Copyright(c) 2010-2014 Intel Corporation 399a2dd95SBruce Richardson */ 499a2dd95SBruce Richardson #include <stdio.h> 599a2dd95SBruce Richardson #include <stdint.h> 672b452c5SDmitry Kozlyuk #include <stdlib.h> 799a2dd95SBruce Richardson #include <errno.h> 82744cb6eSThomas Monjalon #include <pthread.h> 999a2dd95SBruce Richardson #include <sys/queue.h> 1099a2dd95SBruce Richardson #include <sys/time.h> 1199a2dd95SBruce Richardson #include <sys/timerfd.h> 1299a2dd95SBruce Richardson 1345a685caSAnkur Dwivedi #include <eal_trace_internal.h> 1499a2dd95SBruce Richardson #include <rte_interrupts.h> 1599a2dd95SBruce Richardson #include <rte_alarm.h> 1699a2dd95SBruce Richardson #include <rte_common.h> 1799a2dd95SBruce Richardson #include <rte_errno.h> 1899a2dd95SBruce Richardson #include <rte_spinlock.h> 1999a2dd95SBruce Richardson 2099a2dd95SBruce Richardson #include <eal_private.h> 2199a2dd95SBruce Richardson 2299a2dd95SBruce Richardson #ifndef TFD_NONBLOCK 2399a2dd95SBruce Richardson #include <fcntl.h> 2499a2dd95SBruce Richardson #define TFD_NONBLOCK O_NONBLOCK 2599a2dd95SBruce Richardson #endif 2699a2dd95SBruce Richardson 2799a2dd95SBruce Richardson #define NS_PER_US 1000 2899a2dd95SBruce Richardson #define US_PER_MS 1000 2999a2dd95SBruce Richardson #define MS_PER_S 1000 3099a2dd95SBruce Richardson #ifndef US_PER_S 3199a2dd95SBruce Richardson #define US_PER_S (US_PER_MS * MS_PER_S) 3299a2dd95SBruce Richardson #endif 3399a2dd95SBruce Richardson 3499a2dd95SBruce Richardson #ifdef CLOCK_MONOTONIC_RAW /* Defined in glibc bits/time.h */ 3599a2dd95SBruce Richardson #define CLOCK_TYPE_ID CLOCK_MONOTONIC_RAW 3699a2dd95SBruce Richardson #else 3799a2dd95SBruce Richardson #define CLOCK_TYPE_ID CLOCK_MONOTONIC 3899a2dd95SBruce Richardson #endif 3999a2dd95SBruce Richardson 4099a2dd95SBruce Richardson struct alarm_entry { 4199a2dd95SBruce Richardson LIST_ENTRY(alarm_entry) next; 4299a2dd95SBruce Richardson struct timeval time; 4399a2dd95SBruce Richardson rte_eal_alarm_callback cb_fn; 4499a2dd95SBruce Richardson void *cb_arg; 4599a2dd95SBruce Richardson volatile uint8_t executing; 4699a2dd95SBruce Richardson volatile pthread_t executing_id; 4799a2dd95SBruce Richardson }; 4899a2dd95SBruce Richardson 4999a2dd95SBruce Richardson static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER(); 5099a2dd95SBruce Richardson static rte_spinlock_t alarm_list_lk = RTE_SPINLOCK_INITIALIZER; 5199a2dd95SBruce Richardson 5290b13ab8SHarman Kalra static struct rte_intr_handle *intr_handle; 5399a2dd95SBruce Richardson static int handler_registered = 0; 5499a2dd95SBruce Richardson static void eal_alarm_callback(void *arg); 5599a2dd95SBruce Richardson 5690b13ab8SHarman Kalra void 5790b13ab8SHarman Kalra rte_eal_alarm_cleanup(void) 5890b13ab8SHarman Kalra { 5990b13ab8SHarman Kalra rte_intr_instance_free(intr_handle); 6090b13ab8SHarman Kalra } 6190b13ab8SHarman Kalra 6299a2dd95SBruce Richardson int 6399a2dd95SBruce Richardson rte_eal_alarm_init(void) 6499a2dd95SBruce Richardson { 6590b13ab8SHarman Kalra 6690b13ab8SHarman Kalra intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); 6790b13ab8SHarman Kalra if (intr_handle == NULL) { 68ae67895bSDavid Marchand EAL_LOG(ERR, "Fail to allocate intr_handle"); 6990b13ab8SHarman Kalra goto error; 7090b13ab8SHarman Kalra } 7190b13ab8SHarman Kalra 7290b13ab8SHarman Kalra if (rte_intr_type_set(intr_handle, RTE_INTR_HANDLE_ALARM)) 7399a2dd95SBruce Richardson goto error; 7499a2dd95SBruce Richardson 7590b13ab8SHarman Kalra /* create a timerfd file descriptor */ 7690b13ab8SHarman Kalra if (rte_intr_fd_set(intr_handle, 7790b13ab8SHarman Kalra timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK))) 7890b13ab8SHarman Kalra goto error; 7990b13ab8SHarman Kalra 8090b13ab8SHarman Kalra if (rte_intr_fd_get(intr_handle) == -1) 8190b13ab8SHarman Kalra goto error; 8299a2dd95SBruce Richardson return 0; 8399a2dd95SBruce Richardson 8499a2dd95SBruce Richardson error: 8590b13ab8SHarman Kalra rte_intr_instance_free(intr_handle); 8699a2dd95SBruce Richardson rte_errno = errno; 8799a2dd95SBruce Richardson return -1; 8899a2dd95SBruce Richardson } 8999a2dd95SBruce Richardson 9099a2dd95SBruce Richardson static void 9199a2dd95SBruce Richardson eal_alarm_callback(void *arg __rte_unused) 9299a2dd95SBruce Richardson { 9399a2dd95SBruce Richardson struct timespec now; 9499a2dd95SBruce Richardson struct alarm_entry *ap; 9599a2dd95SBruce Richardson 9699a2dd95SBruce Richardson rte_spinlock_lock(&alarm_list_lk); 9799a2dd95SBruce Richardson while ((ap = LIST_FIRST(&alarm_list)) !=NULL && 9899a2dd95SBruce Richardson clock_gettime(CLOCK_TYPE_ID, &now) == 0 && 9999a2dd95SBruce Richardson (ap->time.tv_sec < now.tv_sec || (ap->time.tv_sec == now.tv_sec && 10099a2dd95SBruce Richardson (ap->time.tv_usec * NS_PER_US) <= now.tv_nsec))) { 10199a2dd95SBruce Richardson ap->executing = 1; 10299a2dd95SBruce Richardson ap->executing_id = pthread_self(); 10399a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 10499a2dd95SBruce Richardson 10599a2dd95SBruce Richardson ap->cb_fn(ap->cb_arg); 10699a2dd95SBruce Richardson 10799a2dd95SBruce Richardson rte_spinlock_lock(&alarm_list_lk); 10899a2dd95SBruce Richardson 10999a2dd95SBruce Richardson LIST_REMOVE(ap, next); 11099a2dd95SBruce Richardson free(ap); 11199a2dd95SBruce Richardson } 11299a2dd95SBruce Richardson 11399a2dd95SBruce Richardson if (!LIST_EMPTY(&alarm_list)) { 11499a2dd95SBruce Richardson struct itimerspec atime = { .it_interval = { 0, 0 } }; 11599a2dd95SBruce Richardson 11699a2dd95SBruce Richardson ap = LIST_FIRST(&alarm_list); 11799a2dd95SBruce Richardson atime.it_value.tv_sec = ap->time.tv_sec; 11899a2dd95SBruce Richardson atime.it_value.tv_nsec = ap->time.tv_usec * NS_PER_US; 11999a2dd95SBruce Richardson /* perform borrow for subtraction if necessary */ 12099a2dd95SBruce Richardson if (now.tv_nsec > (ap->time.tv_usec * NS_PER_US)) 12199a2dd95SBruce Richardson atime.it_value.tv_sec--, atime.it_value.tv_nsec += US_PER_S * NS_PER_US; 12299a2dd95SBruce Richardson 12399a2dd95SBruce Richardson atime.it_value.tv_sec -= now.tv_sec; 12499a2dd95SBruce Richardson atime.it_value.tv_nsec -= now.tv_nsec; 12590b13ab8SHarman Kalra timerfd_settime(rte_intr_fd_get(intr_handle), 0, &atime, NULL); 12699a2dd95SBruce Richardson } 12799a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 12899a2dd95SBruce Richardson } 12999a2dd95SBruce Richardson 13099a2dd95SBruce Richardson int 13199a2dd95SBruce Richardson rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg) 13299a2dd95SBruce Richardson { 13399a2dd95SBruce Richardson struct timespec now; 13499a2dd95SBruce Richardson int ret = 0; 13599a2dd95SBruce Richardson struct alarm_entry *ap, *new_alarm; 13699a2dd95SBruce Richardson 13799a2dd95SBruce Richardson /* Check parameters, including that us won't cause a uint64_t overflow */ 13899a2dd95SBruce Richardson if (us < 1 || us > (UINT64_MAX - US_PER_S) || cb_fn == NULL) 13999a2dd95SBruce Richardson return -EINVAL; 14099a2dd95SBruce Richardson 14199a2dd95SBruce Richardson new_alarm = calloc(1, sizeof(*new_alarm)); 14299a2dd95SBruce Richardson if (new_alarm == NULL) 14399a2dd95SBruce Richardson return -ENOMEM; 14499a2dd95SBruce Richardson 14599a2dd95SBruce Richardson /* use current time to calculate absolute time of alarm */ 14699a2dd95SBruce Richardson clock_gettime(CLOCK_TYPE_ID, &now); 14799a2dd95SBruce Richardson 14899a2dd95SBruce Richardson new_alarm->cb_fn = cb_fn; 14999a2dd95SBruce Richardson new_alarm->cb_arg = cb_arg; 15099a2dd95SBruce Richardson new_alarm->time.tv_usec = ((now.tv_nsec / NS_PER_US) + us) % US_PER_S; 15199a2dd95SBruce Richardson new_alarm->time.tv_sec = now.tv_sec + (((now.tv_nsec / NS_PER_US) + us) / US_PER_S); 15299a2dd95SBruce Richardson 15399a2dd95SBruce Richardson rte_spinlock_lock(&alarm_list_lk); 15499a2dd95SBruce Richardson if (!handler_registered) { 15599a2dd95SBruce Richardson /* registration can fail, callback can be registered later */ 15690b13ab8SHarman Kalra if (rte_intr_callback_register(intr_handle, 15799a2dd95SBruce Richardson eal_alarm_callback, NULL) == 0) 15899a2dd95SBruce Richardson handler_registered = 1; 15999a2dd95SBruce Richardson } 16099a2dd95SBruce Richardson 16199a2dd95SBruce Richardson if (LIST_EMPTY(&alarm_list)) 16299a2dd95SBruce Richardson LIST_INSERT_HEAD(&alarm_list, new_alarm, next); 16399a2dd95SBruce Richardson else { 16499a2dd95SBruce Richardson LIST_FOREACH(ap, &alarm_list, next) { 16599a2dd95SBruce Richardson if (ap->time.tv_sec > new_alarm->time.tv_sec || 16699a2dd95SBruce Richardson (ap->time.tv_sec == new_alarm->time.tv_sec && 16799a2dd95SBruce Richardson ap->time.tv_usec > new_alarm->time.tv_usec)){ 16899a2dd95SBruce Richardson LIST_INSERT_BEFORE(ap, new_alarm, next); 16999a2dd95SBruce Richardson break; 17099a2dd95SBruce Richardson } 17199a2dd95SBruce Richardson if (LIST_NEXT(ap, next) == NULL) { 17299a2dd95SBruce Richardson LIST_INSERT_AFTER(ap, new_alarm, next); 17399a2dd95SBruce Richardson break; 17499a2dd95SBruce Richardson } 17599a2dd95SBruce Richardson } 17699a2dd95SBruce Richardson } 17799a2dd95SBruce Richardson 17899a2dd95SBruce Richardson if (LIST_FIRST(&alarm_list) == new_alarm) { 17999a2dd95SBruce Richardson struct itimerspec alarm_time = { 18099a2dd95SBruce Richardson .it_interval = {0, 0}, 18199a2dd95SBruce Richardson .it_value = { 18299a2dd95SBruce Richardson .tv_sec = us / US_PER_S, 18399a2dd95SBruce Richardson .tv_nsec = (us % US_PER_S) * NS_PER_US, 18499a2dd95SBruce Richardson }, 18599a2dd95SBruce Richardson }; 18690b13ab8SHarman Kalra ret |= timerfd_settime(rte_intr_fd_get(intr_handle), 0, &alarm_time, NULL); 18799a2dd95SBruce Richardson } 18899a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 18999a2dd95SBruce Richardson 19099a2dd95SBruce Richardson rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret); 19199a2dd95SBruce Richardson return ret; 19299a2dd95SBruce Richardson } 19399a2dd95SBruce Richardson 19499a2dd95SBruce Richardson int 19599a2dd95SBruce Richardson rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg) 19699a2dd95SBruce Richardson { 19799a2dd95SBruce Richardson struct alarm_entry *ap, *ap_prev; 19899a2dd95SBruce Richardson int count = 0; 19999a2dd95SBruce Richardson int err = 0; 20099a2dd95SBruce Richardson int executing; 20199a2dd95SBruce Richardson 20299a2dd95SBruce Richardson if (!cb_fn) { 20399a2dd95SBruce Richardson rte_errno = EINVAL; 20499a2dd95SBruce Richardson return -1; 20599a2dd95SBruce Richardson } 20699a2dd95SBruce Richardson 20799a2dd95SBruce Richardson do { 20899a2dd95SBruce Richardson executing = 0; 20999a2dd95SBruce Richardson rte_spinlock_lock(&alarm_list_lk); 21099a2dd95SBruce Richardson /* remove any matches at the start of the list */ 21199a2dd95SBruce Richardson while ((ap = LIST_FIRST(&alarm_list)) != NULL && 21299a2dd95SBruce Richardson cb_fn == ap->cb_fn && 21399a2dd95SBruce Richardson (cb_arg == (void *)-1 || cb_arg == ap->cb_arg)) { 21499a2dd95SBruce Richardson 21599a2dd95SBruce Richardson if (ap->executing == 0) { 21699a2dd95SBruce Richardson LIST_REMOVE(ap, next); 21799a2dd95SBruce Richardson free(ap); 21899a2dd95SBruce Richardson count++; 21999a2dd95SBruce Richardson } else { 22099a2dd95SBruce Richardson /* If calling from other context, mark that alarm is executing 22199a2dd95SBruce Richardson * so loop can spin till it finish. Otherwise we are trying to 22299a2dd95SBruce Richardson * cancel our self - mark it by EINPROGRESS */ 22399a2dd95SBruce Richardson if (pthread_equal(ap->executing_id, pthread_self()) == 0) 22499a2dd95SBruce Richardson executing++; 22599a2dd95SBruce Richardson else 22699a2dd95SBruce Richardson err = EINPROGRESS; 22799a2dd95SBruce Richardson 22899a2dd95SBruce Richardson break; 22999a2dd95SBruce Richardson } 23099a2dd95SBruce Richardson } 23199a2dd95SBruce Richardson ap_prev = ap; 23299a2dd95SBruce Richardson 23399a2dd95SBruce Richardson /* now go through list, removing entries not at start */ 23499a2dd95SBruce Richardson LIST_FOREACH(ap, &alarm_list, next) { 23599a2dd95SBruce Richardson /* this won't be true first time through */ 23699a2dd95SBruce Richardson if (cb_fn == ap->cb_fn && 23799a2dd95SBruce Richardson (cb_arg == (void *)-1 || cb_arg == ap->cb_arg)) { 23899a2dd95SBruce Richardson 23999a2dd95SBruce Richardson if (ap->executing == 0) { 24099a2dd95SBruce Richardson LIST_REMOVE(ap, next); 24199a2dd95SBruce Richardson free(ap); 24299a2dd95SBruce Richardson count++; 24399a2dd95SBruce Richardson ap = ap_prev; 24499a2dd95SBruce Richardson } else if (pthread_equal(ap->executing_id, pthread_self()) == 0) 24599a2dd95SBruce Richardson executing++; 24699a2dd95SBruce Richardson else 24799a2dd95SBruce Richardson err = EINPROGRESS; 24899a2dd95SBruce Richardson } 24999a2dd95SBruce Richardson ap_prev = ap; 25099a2dd95SBruce Richardson } 251*a4835c22SWojciech Panfil 25299a2dd95SBruce Richardson rte_spinlock_unlock(&alarm_list_lk); 253*a4835c22SWojciech Panfil 254*a4835c22SWojciech Panfil /* Yield control to a second thread executing eal_alarm_callback to avoid 255*a4835c22SWojciech Panfil * its starvation, as it is waiting for the lock we have just released. 256*a4835c22SWojciech Panfil */ 257*a4835c22SWojciech Panfil sched_yield(); 25899a2dd95SBruce Richardson } while (executing != 0); 25999a2dd95SBruce Richardson 26099a2dd95SBruce Richardson if (count == 0 && err == 0) 26199a2dd95SBruce Richardson rte_errno = ENOENT; 26299a2dd95SBruce Richardson else if (err) 26399a2dd95SBruce Richardson rte_errno = err; 26499a2dd95SBruce Richardson 26599a2dd95SBruce Richardson rte_eal_trace_alarm_cancel(cb_fn, cb_arg, count); 26699a2dd95SBruce Richardson return count; 26799a2dd95SBruce Richardson } 268