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