mem: quiet base address hint warning if not requested
[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 timespec time;
36         rte_eal_alarm_callback cb_fn;
37         void *cb_arg;
38         volatile uint8_t executing;
39         volatile pthread_t executing_id;
40 };
41
42 static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
43 static rte_spinlock_t alarm_list_lk = RTE_SPINLOCK_INITIALIZER;
44
45 static struct rte_intr_handle *intr_handle;
46 static void eal_alarm_callback(void *arg);
47
48 void
49 rte_eal_alarm_cleanup(void)
50 {
51         rte_intr_instance_free(intr_handle);
52 }
53
54 int
55 rte_eal_alarm_init(void)
56 {
57         int fd;
58
59         intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE);
60         if (intr_handle == NULL) {
61                 RTE_LOG(ERR, EAL, "Fail to allocate intr_handle\n");
62                 goto error;
63         }
64
65         if (rte_intr_type_set(intr_handle, RTE_INTR_HANDLE_ALARM))
66                 goto error;
67
68         if (rte_intr_fd_set(intr_handle, -1))
69                 goto error;
70
71         /* on FreeBSD, timers don't use fd's, and their identifiers are stored
72          * in separate namespace from fd's, so using any value is OK. however,
73          * EAL interrupts handler expects fd's to be unique, so use an actual fd
74          * to guarantee unique timer identifier.
75          */
76         fd = open("/dev/zero", O_RDONLY);
77
78         if (rte_intr_fd_set(intr_handle, fd))
79                 goto error;
80
81         return 0;
82 error:
83         rte_intr_instance_free(intr_handle);
84         return -1;
85 }
86
87 static inline int
88 timespec_cmp(const struct timespec *now, const struct timespec *at)
89 {
90         if (now->tv_sec < at->tv_sec)
91                 return -1;
92         if (now->tv_sec > at->tv_sec)
93                 return 1;
94         if (now->tv_nsec < at->tv_nsec)
95                 return -1;
96         if (now->tv_nsec > at->tv_nsec)
97                 return 1;
98         return 0;
99 }
100
101 static inline uint64_t
102 diff_ns(struct timespec *now, struct timespec *at)
103 {
104         uint64_t now_ns, at_ns;
105
106         if (timespec_cmp(now, at) >= 0)
107                 return 0;
108
109         now_ns = now->tv_sec * NS_PER_S + now->tv_nsec;
110         at_ns = at->tv_sec * NS_PER_S + at->tv_nsec;
111
112         return at_ns - now_ns;
113 }
114
115 int
116 eal_alarm_get_timeout_ns(uint64_t *val)
117 {
118         struct alarm_entry *ap;
119         struct timespec now;
120
121         if (clock_gettime(CLOCK_TYPE_ID, &now) < 0)
122                 return -1;
123
124         if (LIST_EMPTY(&alarm_list))
125                 return -1;
126
127         ap = LIST_FIRST(&alarm_list);
128
129         *val = diff_ns(&now, &ap->time);
130
131         return 0;
132 }
133
134 static int
135 unregister_current_callback(void)
136 {
137         struct alarm_entry *ap;
138         int ret = 0;
139
140         if (!LIST_EMPTY(&alarm_list)) {
141                 ap = LIST_FIRST(&alarm_list);
142
143                 do {
144                         ret = rte_intr_callback_unregister(intr_handle,
145                                 eal_alarm_callback, &ap->time);
146                 } while (ret == -EAGAIN);
147         }
148
149         return ret;
150 }
151
152 static int
153 register_first_callback(void)
154 {
155         struct alarm_entry *ap;
156         int ret = 0;
157
158         if (!LIST_EMPTY(&alarm_list)) {
159                 ap = LIST_FIRST(&alarm_list);
160
161                 /* register a new callback */
162                 ret = rte_intr_callback_register(intr_handle,
163                                 eal_alarm_callback, &ap->time);
164         }
165         return ret;
166 }
167
168 static void
169 eal_alarm_callback(void *arg __rte_unused)
170 {
171         struct timespec now;
172         struct alarm_entry *ap;
173
174         rte_spinlock_lock(&alarm_list_lk);
175         ap = LIST_FIRST(&alarm_list);
176
177         if (clock_gettime(CLOCK_TYPE_ID, &now) < 0)
178                 return;
179
180         while (ap != NULL && timespec_cmp(&now, &ap->time) >= 0) {
181                 ap->executing = 1;
182                 ap->executing_id = pthread_self();
183                 rte_spinlock_unlock(&alarm_list_lk);
184
185                 ap->cb_fn(ap->cb_arg);
186
187                 rte_spinlock_lock(&alarm_list_lk);
188
189                 LIST_REMOVE(ap, next);
190                 free(ap);
191
192                 ap = LIST_FIRST(&alarm_list);
193         }
194
195         /* timer has been deleted from the kqueue, so recreate it if needed */
196         register_first_callback();
197
198         rte_spinlock_unlock(&alarm_list_lk);
199 }
200
201
202 int
203 rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
204 {
205         struct alarm_entry *ap, *new_alarm;
206         struct timespec now;
207         uint64_t ns;
208         int ret = 0;
209
210         /* check parameters, also ensure us won't cause a uint64_t overflow */
211         if (us < 1 || us > (UINT64_MAX - US_PER_S) || cb_fn == NULL)
212                 return -EINVAL;
213
214         new_alarm = calloc(1, sizeof(*new_alarm));
215         if (new_alarm == NULL)
216                 return -ENOMEM;
217
218         /* use current time to calculate absolute time of alarm */
219         clock_gettime(CLOCK_TYPE_ID, &now);
220
221         ns = us * NS_PER_US;
222
223         new_alarm->cb_fn = cb_fn;
224         new_alarm->cb_arg = cb_arg;
225         new_alarm->time.tv_nsec = (now.tv_nsec + ns) % NS_PER_S;
226         new_alarm->time.tv_sec = now.tv_sec + ((now.tv_nsec + ns) / NS_PER_S);
227
228         rte_spinlock_lock(&alarm_list_lk);
229
230         if (LIST_EMPTY(&alarm_list))
231                 LIST_INSERT_HEAD(&alarm_list, new_alarm, next);
232         else {
233                 LIST_FOREACH(ap, &alarm_list, next) {
234                         if (timespec_cmp(&new_alarm->time, &ap->time) < 0) {
235                                 LIST_INSERT_BEFORE(ap, new_alarm, next);
236                                 break;
237                         }
238                         if (LIST_NEXT(ap, next) == NULL) {
239                                 LIST_INSERT_AFTER(ap, new_alarm, next);
240                                 break;
241                         }
242                 }
243         }
244
245         /* re-register first callback just in case */
246         register_first_callback();
247
248         rte_spinlock_unlock(&alarm_list_lk);
249
250         rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
251         return ret;
252 }
253
254 int
255 rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
256 {
257         struct alarm_entry *ap, *ap_prev;
258         int count = 0;
259         int err = 0;
260         int executing;
261
262         if (!cb_fn) {
263                 rte_errno = EINVAL;
264                 return -1;
265         }
266
267         do {
268                 executing = 0;
269                 rte_spinlock_lock(&alarm_list_lk);
270                 /* remove any matches at the start of the list */
271                 while (1) {
272                         ap = LIST_FIRST(&alarm_list);
273                         if (ap == NULL)
274                                 break;
275                         if (cb_fn != ap->cb_fn)
276                                 break;
277                         if (cb_arg != ap->cb_arg && cb_arg != (void *) -1)
278                                 break;
279                         if (ap->executing == 0) {
280                                 LIST_REMOVE(ap, next);
281                                 free(ap);
282                                 count++;
283                         } else {
284                                 /* If calling from other context, mark that
285                                  * alarm is executing so loop can spin till it
286                                  * finish. Otherwise we are trying to cancel
287                                  * ourselves - mark it by EINPROGRESS.
288                                  */
289                                 if (pthread_equal(ap->executing_id,
290                                                 pthread_self()) == 0)
291                                         executing++;
292                                 else
293                                         err = EINPROGRESS;
294
295                                 break;
296                         }
297                 }
298                 ap_prev = ap;
299
300                 /* now go through list, removing entries not at start */
301                 LIST_FOREACH(ap, &alarm_list, next) {
302                         /* this won't be true first time through */
303                         if (cb_fn == ap->cb_fn &&
304                                         (cb_arg == (void *)-1 ||
305                                          cb_arg == ap->cb_arg)) {
306                                 if (ap->executing == 0) {
307                                         LIST_REMOVE(ap, next);
308                                         free(ap);
309                                         count++;
310                                         ap = ap_prev;
311                                 } else if (pthread_equal(ap->executing_id,
312                                                          pthread_self()) == 0) {
313                                         executing++;
314                                 } else {
315                                         err = EINPROGRESS;
316                                 }
317                         }
318                         ap_prev = ap;
319                 }
320                 rte_spinlock_unlock(&alarm_list_lk);
321         } while (executing != 0);
322
323         if (count == 0 && err == 0)
324                 rte_errno = ENOENT;
325         else if (err)
326                 rte_errno = err;
327
328         rte_spinlock_lock(&alarm_list_lk);
329
330         /* unregister if no alarms left, otherwise re-register first */
331         if (LIST_EMPTY(&alarm_list))
332                 unregister_current_callback();
333         else
334                 register_first_callback();
335
336         rte_spinlock_unlock(&alarm_list_lk);
337
338         rte_eal_trace_alarm_cancel(cb_fn, cb_arg, count);
339         return count;
340 }