]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
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 | } |