1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright (c) 2020 Dmitry Kozlyuk
3 */
4
5 #include <stdatomic.h>
6 #include <stdbool.h>
7 #include <sys/queue.h>
8
9 #include <rte_alarm.h>
10 #include <rte_spinlock.h>
11
12 #include <rte_eal_trace.h>
13
14 #include "eal_windows.h"
15
16 enum alarm_state {
17 ALARM_ARMED,
18 ALARM_TRIGGERED,
19 ALARM_CANCELLED
20 };
21
22 struct alarm_entry {
23 LIST_ENTRY(alarm_entry) next;
24 rte_eal_alarm_callback cb_fn;
25 void *cb_arg;
26 HANDLE timer;
27 atomic_uint state;
28 };
29
30 static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
31
32 static rte_spinlock_t alarm_lock = RTE_SPINLOCK_INITIALIZER;
33
34 static int intr_thread_exec_sync(void (*func)(void *arg), void *arg);
35
36 static void
alarm_remove_unsafe(struct alarm_entry * ap)37 alarm_remove_unsafe(struct alarm_entry *ap)
38 {
39 LIST_REMOVE(ap, next);
40 CloseHandle(ap->timer);
41 free(ap);
42 }
43
44 static void
alarm_callback(void * arg,DWORD low __rte_unused,DWORD high __rte_unused)45 alarm_callback(void *arg, DWORD low __rte_unused, DWORD high __rte_unused)
46 {
47 struct alarm_entry *ap = arg;
48 unsigned int state = ALARM_ARMED;
49
50 if (!atomic_compare_exchange_strong(
51 &ap->state, &state, ALARM_TRIGGERED))
52 return;
53
54 ap->cb_fn(ap->cb_arg);
55
56 rte_spinlock_lock(&alarm_lock);
57 alarm_remove_unsafe(ap);
58 rte_spinlock_unlock(&alarm_lock);
59 }
60
61 static int
alarm_set(struct alarm_entry * entry,LARGE_INTEGER deadline)62 alarm_set(struct alarm_entry *entry, LARGE_INTEGER deadline)
63 {
64 BOOL ret = SetWaitableTimer(
65 entry->timer, &deadline, 0, alarm_callback, entry, FALSE);
66 if (!ret) {
67 RTE_LOG_WIN32_ERR("SetWaitableTimer");
68 return -1;
69 }
70 return 0;
71 }
72
73 struct alarm_task {
74 struct alarm_entry *entry;
75 LARGE_INTEGER deadline;
76 int ret;
77 };
78
79 static void
alarm_task_exec(void * arg)80 alarm_task_exec(void *arg)
81 {
82 struct alarm_task *task = arg;
83 task->ret = alarm_set(task->entry, task->deadline);
84 }
85
86 int
rte_eal_alarm_set(uint64_t us,rte_eal_alarm_callback cb_fn,void * cb_arg)87 rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
88 {
89 struct alarm_entry *ap;
90 HANDLE timer;
91 FILETIME ft;
92 LARGE_INTEGER deadline;
93 int ret;
94
95 if (cb_fn == NULL) {
96 RTE_LOG(ERR, EAL, "NULL callback\n");
97 ret = -EINVAL;
98 goto exit;
99 }
100
101 /* Calculate deadline ASAP, unit of measure = 100ns. */
102 GetSystemTimePreciseAsFileTime(&ft);
103 deadline.LowPart = ft.dwLowDateTime;
104 deadline.HighPart = ft.dwHighDateTime;
105 deadline.QuadPart += 10 * us;
106
107 ap = calloc(1, sizeof(*ap));
108 if (ap == NULL) {
109 RTE_LOG(ERR, EAL, "Cannot allocate alarm entry\n");
110 ret = -ENOMEM;
111 goto exit;
112 }
113
114 timer = CreateWaitableTimer(NULL, FALSE, NULL);
115 if (timer == NULL) {
116 RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
117 ret = -EINVAL;
118 goto fail;
119 }
120
121 ap->timer = timer;
122 ap->cb_fn = cb_fn;
123 ap->cb_arg = cb_arg;
124
125 /* Waitable timer must be set in the same thread that will
126 * do an alertable wait for the alarm to trigger, that is,
127 * in the interrupt thread.
128 */
129 if (rte_thread_is_intr()) {
130 /* Directly schedule callback execution. */
131 ret = alarm_set(ap, deadline);
132 if (ret < 0) {
133 RTE_LOG(ERR, EAL, "Cannot setup alarm\n");
134 goto fail;
135 }
136 } else {
137 /* Dispatch a task to set alarm into the interrupt thread.
138 * Execute it synchronously, because it can fail.
139 */
140 struct alarm_task task = {
141 .entry = ap,
142 .deadline = deadline,
143 };
144
145 ret = intr_thread_exec_sync(alarm_task_exec, &task);
146 if (ret < 0) {
147 RTE_LOG(ERR, EAL, "Cannot setup alarm in interrupt thread\n");
148 goto fail;
149 }
150
151 ret = task.ret;
152 if (ret < 0)
153 goto fail;
154 }
155
156 rte_spinlock_lock(&alarm_lock);
157 LIST_INSERT_HEAD(&alarm_list, ap, next);
158 rte_spinlock_unlock(&alarm_lock);
159
160 goto exit;
161
162 fail:
163 if (timer != NULL)
164 CloseHandle(timer);
165 free(ap);
166
167 exit:
168 rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
169 return ret;
170 }
171
172 static bool
alarm_matches(const struct alarm_entry * ap,rte_eal_alarm_callback cb_fn,void * cb_arg)173 alarm_matches(const struct alarm_entry *ap,
174 rte_eal_alarm_callback cb_fn, void *cb_arg)
175 {
176 bool any_arg = cb_arg == (void *)(-1);
177 return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg);
178 }
179
180 int
rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn,void * cb_arg)181 rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
182 {
183 struct alarm_entry *ap;
184 unsigned int state;
185 int removed;
186 bool executing;
187
188 removed = 0;
189
190 if (cb_fn == NULL) {
191 RTE_LOG(ERR, EAL, "NULL callback\n");
192 return -EINVAL;
193 }
194
195 do {
196 executing = false;
197
198 rte_spinlock_lock(&alarm_lock);
199
200 LIST_FOREACH(ap, &alarm_list, next) {
201 if (!alarm_matches(ap, cb_fn, cb_arg))
202 continue;
203
204 state = ALARM_ARMED;
205 if (atomic_compare_exchange_strong(
206 &ap->state, &state, ALARM_CANCELLED)) {
207 alarm_remove_unsafe(ap);
208 removed++;
209 } else if (state == ALARM_TRIGGERED)
210 executing = true;
211 }
212
213 rte_spinlock_unlock(&alarm_lock);
214 } while (executing);
215
216 rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed);
217 return removed;
218 }
219
220 struct intr_task {
221 void (*func)(void *arg);
222 void *arg;
223 rte_spinlock_t lock; /* unlocked at task completion */
224 };
225
226 static void
intr_thread_entry(void * arg)227 intr_thread_entry(void *arg)
228 {
229 struct intr_task *task = arg;
230 task->func(task->arg);
231 rte_spinlock_unlock(&task->lock);
232 }
233
234 static int
intr_thread_exec_sync(void (* func)(void * arg),void * arg)235 intr_thread_exec_sync(void (*func)(void *arg), void *arg)
236 {
237 struct intr_task task;
238 int ret;
239
240 task.func = func;
241 task.arg = arg;
242 rte_spinlock_init(&task.lock);
243
244 /* Make timers more precise by synchronizing in userspace. */
245 rte_spinlock_lock(&task.lock);
246 ret = eal_intr_thread_schedule(intr_thread_entry, &task);
247 if (ret < 0) {
248 RTE_LOG(ERR, EAL, "Cannot schedule task to interrupt thread\n");
249 return -EINVAL;
250 }
251
252 /* Wait for the task to complete. */
253 rte_spinlock_lock(&task.lock);
254 return 0;
255 }
256