dpdk/lib/eal/freebsd/eal_alarm.c
<<
>>
Prefs
   1/* SPDX-License-Identifier: BSD-3-Clause
   2 * Copyright(c) 2010-2018 Intel Corporation
   3 */
   4
   5#include <sys/types.h>
   6#include <sys/stat.h>
   7#include <fcntl.h>
   8#include <stdio.h>
   9#include <stdlib.h>
  10#include <string.h>
  11#include <time.h>
  12#include <errno.h>
  13
  14#include <rte_alarm.h>
  15#include <rte_cycles.h>
  16#include <rte_common.h>
  17#include <rte_errno.h>
  18#include <rte_interrupts.h>
  19#include <rte_spinlock.h>
  20#include <rte_eal_trace.h>
  21
  22#include "eal_private.h"
  23#include "eal_alarm_private.h"
  24
  25#define NS_PER_US 1000
  26
  27#ifdef CLOCK_MONOTONIC_RAW /* Defined in glibc bits/time.h */
  28#define CLOCK_TYPE_ID CLOCK_MONOTONIC_RAW
  29#else
  30#define CLOCK_TYPE_ID CLOCK_MONOTONIC
  31#endif
  32
  33struct alarm_entry {
  34        LIST_ENTRY(alarm_entry) next;
  35        struct rte_intr_handle handle;
  36        struct timespec time;
  37        rte_eal_alarm_callback cb_fn;
  38        void *cb_arg;
  39        volatile uint8_t executing;
  40        volatile pthread_t executing_id;
  41};
  42
  43static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
  44static rte_spinlock_t alarm_list_lk = RTE_SPINLOCK_INITIALIZER;
  45
  46static struct rte_intr_handle intr_handle = {.fd = -1 };
  47static void eal_alarm_callback(void *arg);
  48
  49int
  50rte_eal_alarm_init(void)
  51{
  52        intr_handle.type = RTE_INTR_HANDLE_ALARM;
  53
  54        /* on FreeBSD, timers don't use fd's, and their identifiers are stored
  55         * in separate namespace from fd's, so using any value is OK. however,
  56         * EAL interrupts handler expects fd's to be unique, so use an actual fd
  57         * to guarantee unique timer identifier.
  58         */
  59        intr_handle.fd = open("/dev/zero", O_RDONLY);
  60
  61        return 0;
  62}
  63
  64static inline int
  65timespec_cmp(const struct timespec *now, const struct timespec *at)
  66{
  67        if (now->tv_sec < at->tv_sec)
  68                return -1;
  69        if (now->tv_sec > at->tv_sec)
  70                return 1;
  71        if (now->tv_nsec < at->tv_nsec)
  72                return -1;
  73        if (now->tv_nsec > at->tv_nsec)
  74                return 1;
  75        return 0;
  76}
  77
  78static inline uint64_t
  79diff_ns(struct timespec *now, struct timespec *at)
  80{
  81        uint64_t now_ns, at_ns;
  82
  83        if (timespec_cmp(now, at) >= 0)
  84                return 0;
  85
  86        now_ns = now->tv_sec * NS_PER_S + now->tv_nsec;
  87        at_ns = at->tv_sec * NS_PER_S + at->tv_nsec;
  88
  89        return at_ns - now_ns;
  90}
  91
  92int
  93eal_alarm_get_timeout_ns(uint64_t *val)
  94{
  95        struct alarm_entry *ap;
  96        struct timespec now;
  97
  98        if (clock_gettime(CLOCK_TYPE_ID, &now) < 0)
  99                return -1;
 100
 101        if (LIST_EMPTY(&alarm_list))
 102                return -1;
 103
 104        ap = LIST_FIRST(&alarm_list);
 105
 106        *val = diff_ns(&now, &ap->time);
 107
 108        return 0;
 109}
 110
 111static int
 112unregister_current_callback(void)
 113{
 114        struct alarm_entry *ap;
 115        int ret = 0;
 116
 117        if (!LIST_EMPTY(&alarm_list)) {
 118                ap = LIST_FIRST(&alarm_list);
 119
 120                do {
 121                        ret = rte_intr_callback_unregister(&intr_handle,
 122                                eal_alarm_callback, &ap->time);
 123                } while (ret == -EAGAIN);
 124        }
 125
 126        return ret;
 127}
 128
 129static int
 130register_first_callback(void)
 131{
 132        struct alarm_entry *ap;
 133        int ret = 0;
 134
 135        if (!LIST_EMPTY(&alarm_list)) {
 136                ap = LIST_FIRST(&alarm_list);
 137
 138                /* register a new callback */
 139                ret = rte_intr_callback_register(&intr_handle,
 140                                eal_alarm_callback, &ap->time);
 141        }
 142        return ret;
 143}
 144
 145static void
 146eal_alarm_callback(void *arg __rte_unused)
 147{
 148        struct timespec now;
 149        struct alarm_entry *ap;
 150
 151        rte_spinlock_lock(&alarm_list_lk);
 152        ap = LIST_FIRST(&alarm_list);
 153
 154        if (clock_gettime(CLOCK_TYPE_ID, &now) < 0)
 155                return;
 156
 157        while (ap != NULL && timespec_cmp(&now, &ap->time) >= 0) {
 158                ap->executing = 1;
 159                ap->executing_id = pthread_self();
 160                rte_spinlock_unlock(&alarm_list_lk);
 161
 162                ap->cb_fn(ap->cb_arg);
 163
 164                rte_spinlock_lock(&alarm_list_lk);
 165
 166                LIST_REMOVE(ap, next);
 167                free(ap);
 168
 169                ap = LIST_FIRST(&alarm_list);
 170        }
 171
 172        /* timer has been deleted from the kqueue, so recreate it if needed */
 173        register_first_callback();
 174
 175        rte_spinlock_unlock(&alarm_list_lk);
 176}
 177
 178
 179int
 180rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
 181{
 182        struct alarm_entry *ap, *new_alarm;
 183        struct timespec now;
 184        uint64_t ns;
 185        int ret = 0;
 186
 187        /* check parameters, also ensure us won't cause a uint64_t overflow */
 188        if (us < 1 || us > (UINT64_MAX - US_PER_S) || cb_fn == NULL)
 189                return -EINVAL;
 190
 191        new_alarm = calloc(1, sizeof(*new_alarm));
 192        if (new_alarm == NULL)
 193                return -ENOMEM;
 194
 195        /* use current time to calculate absolute time of alarm */
 196        clock_gettime(CLOCK_TYPE_ID, &now);
 197
 198        ns = us * NS_PER_US;
 199
 200        new_alarm->cb_fn = cb_fn;
 201        new_alarm->cb_arg = cb_arg;
 202        new_alarm->time.tv_nsec = (now.tv_nsec + ns) % NS_PER_S;
 203        new_alarm->time.tv_sec = now.tv_sec + ((now.tv_nsec + ns) / NS_PER_S);
 204
 205        rte_spinlock_lock(&alarm_list_lk);
 206
 207        if (LIST_EMPTY(&alarm_list))
 208                LIST_INSERT_HEAD(&alarm_list, new_alarm, next);
 209        else {
 210                LIST_FOREACH(ap, &alarm_list, next) {
 211                        if (timespec_cmp(&new_alarm->time, &ap->time) < 0) {
 212                                LIST_INSERT_BEFORE(ap, new_alarm, next);
 213                                break;
 214                        }
 215                        if (LIST_NEXT(ap, next) == NULL) {
 216                                LIST_INSERT_AFTER(ap, new_alarm, next);
 217                                break;
 218                        }
 219                }
 220        }
 221
 222        /* re-register first callback just in case */
 223        register_first_callback();
 224
 225        rte_spinlock_unlock(&alarm_list_lk);
 226
 227        rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
 228        return ret;
 229}
 230
 231int
 232rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
 233{
 234        struct alarm_entry *ap, *ap_prev;
 235        int count = 0;
 236        int err = 0;
 237        int executing;
 238
 239        if (!cb_fn) {
 240                rte_errno = EINVAL;
 241                return -1;
 242        }
 243
 244        do {
 245                executing = 0;
 246                rte_spinlock_lock(&alarm_list_lk);
 247                /* remove any matches at the start of the list */
 248                while (1) {
 249                        ap = LIST_FIRST(&alarm_list);
 250                        if (ap == NULL)
 251                                break;
 252                        if (cb_fn != ap->cb_fn)
 253                                break;
 254                        if (cb_arg != ap->cb_arg && cb_arg != (void *) -1)
 255                                break;
 256                        if (ap->executing == 0) {
 257                                LIST_REMOVE(ap, next);
 258                                free(ap);
 259                                count++;
 260                        } else {
 261                                /* If calling from other context, mark that
 262                                 * alarm is executing so loop can spin till it
 263                                 * finish. Otherwise we are trying to cancel
 264                                 * ourselves - mark it by EINPROGRESS.
 265                                 */
 266                                if (pthread_equal(ap->executing_id,
 267                                                pthread_self()) == 0)
 268                                        executing++;
 269                                else
 270                                        err = EINPROGRESS;
 271
 272                                break;
 273                        }
 274                }
 275                ap_prev = ap;
 276
 277                /* now go through list, removing entries not at start */
 278                LIST_FOREACH(ap, &alarm_list, next) {
 279                        /* this won't be true first time through */
 280                        if (cb_fn == ap->cb_fn &&
 281                                        (cb_arg == (void *)-1 ||
 282                                         cb_arg == ap->cb_arg)) {
 283                                if (ap->executing == 0) {
 284                                        LIST_REMOVE(ap, next);
 285                                        free(ap);
 286                                        count++;
 287                                        ap = ap_prev;
 288                                } else if (pthread_equal(ap->executing_id,
 289                                                         pthread_self()) == 0) {
 290                                        executing++;
 291                                } else {
 292                                        err = EINPROGRESS;
 293                                }
 294                        }
 295                        ap_prev = ap;
 296                }
 297                rte_spinlock_unlock(&alarm_list_lk);
 298        } while (executing != 0);
 299
 300        if (count == 0 && err == 0)
 301                rte_errno = ENOENT;
 302        else if (err)
 303                rte_errno = err;
 304
 305        rte_spinlock_lock(&alarm_list_lk);
 306
 307        /* unregister if no alarms left, otherwise re-register first */
 308        if (LIST_EMPTY(&alarm_list))
 309                unregister_current_callback();
 310        else
 311                register_first_callback();
 312
 313        rte_spinlock_unlock(&alarm_list_lk);
 314
 315        rte_eal_trace_alarm_cancel(cb_fn, cb_arg, count);
 316        return count;
 317}
 318