vdpa/mlx5: fix minsize build
[dpdk.git] / lib / eal / freebsd / eal_alarm.c
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
33 struct 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
43 static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
44 static rte_spinlock_t alarm_list_lk = RTE_SPINLOCK_INITIALIZER;
45
46 static struct rte_intr_handle intr_handle = {.fd = -1 };
47 static void eal_alarm_callback(void *arg);
48
49 int
50 rte_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
64 static inline int
65 timespec_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
78 static inline uint64_t
79 diff_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
92 int
93 eal_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
111 static int
112 unregister_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
129 static int
130 register_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
145 static void
146 eal_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
179 int
180 rte_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
231 int
232 rte_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 }