e5dc54efb89b076b99377ebad00d0d7eb38ce146
[dpdk.git] / lib / eal / windows / eal_alarm.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright (c) 2020 Dmitry Kozlyuk
3  */
4
5 #include <stdatomic.h>
6 #include <stdbool.h>
7
8 #include <rte_alarm.h>
9 #include <rte_spinlock.h>
10
11 #include <rte_eal_trace.h>
12
13 #include "eal_windows.h"
14
15 enum alarm_state {
16         ALARM_ARMED,
17         ALARM_TRIGGERED,
18         ALARM_CANCELLED
19 };
20
21 struct alarm_entry {
22         LIST_ENTRY(alarm_entry) next;
23         rte_eal_alarm_callback cb_fn;
24         void *cb_arg;
25         HANDLE timer;
26         atomic_uint state;
27 };
28
29 static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
30
31 static rte_spinlock_t alarm_lock = RTE_SPINLOCK_INITIALIZER;
32
33 static int intr_thread_exec_sync(void (*func)(void *arg), void *arg);
34
35 static void
36 alarm_remove_unsafe(struct alarm_entry *ap)
37 {
38         LIST_REMOVE(ap, next);
39         CloseHandle(ap->timer);
40         free(ap);
41 }
42
43 static void
44 alarm_callback(void *arg, DWORD low __rte_unused, DWORD high __rte_unused)
45 {
46         struct alarm_entry *ap = arg;
47         unsigned int state = ALARM_ARMED;
48
49         if (!atomic_compare_exchange_strong(
50                         &ap->state, &state, ALARM_TRIGGERED))
51                 return;
52
53         ap->cb_fn(ap->cb_arg);
54
55         rte_spinlock_lock(&alarm_lock);
56         alarm_remove_unsafe(ap);
57         rte_spinlock_unlock(&alarm_lock);
58 }
59
60 static int
61 alarm_set(struct alarm_entry *entry, LARGE_INTEGER deadline)
62 {
63         BOOL ret = SetWaitableTimer(
64                 entry->timer, &deadline, 0, alarm_callback, entry, FALSE);
65         if (!ret) {
66                 RTE_LOG_WIN32_ERR("SetWaitableTimer");
67                 return -1;
68         }
69         return 0;
70 }
71
72 struct alarm_task {
73         struct alarm_entry *entry;
74         LARGE_INTEGER deadline;
75         int ret;
76 };
77
78 static void
79 alarm_task_exec(void *arg)
80 {
81         struct alarm_task *task = arg;
82         task->ret = alarm_set(task->entry, task->deadline);
83 }
84
85 int
86 rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
87 {
88         struct alarm_entry *ap;
89         HANDLE timer;
90         FILETIME ft;
91         LARGE_INTEGER deadline;
92         int ret;
93
94         if (cb_fn == NULL) {
95                 RTE_LOG(ERR, EAL, "NULL callback\n");
96                 ret = -EINVAL;
97                 goto exit;
98         }
99
100         /* Calculate deadline ASAP, unit of measure = 100ns. */
101         GetSystemTimePreciseAsFileTime(&ft);
102         deadline.LowPart = ft.dwLowDateTime;
103         deadline.HighPart = ft.dwHighDateTime;
104         deadline.QuadPart += 10 * us;
105
106         ap = calloc(1, sizeof(*ap));
107         if (ap == NULL) {
108                 RTE_LOG(ERR, EAL, "Cannot allocate alarm entry\n");
109                 ret = -ENOMEM;
110                 goto exit;
111         }
112
113         timer = CreateWaitableTimer(NULL, FALSE, NULL);
114         if (timer == NULL) {
115                 RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
116                 ret = -EINVAL;
117                 goto fail;
118         }
119
120         ap->timer = timer;
121         ap->cb_fn = cb_fn;
122         ap->cb_arg = cb_arg;
123
124         /* Waitable timer must be set in the same thread that will
125          * do an alertable wait for the alarm to trigger, that is,
126          * in the interrupt thread.
127          */
128         if (rte_thread_is_intr()) {
129                 /* Directly schedule callback execution. */
130                 ret = alarm_set(ap, deadline);
131                 if (ret < 0) {
132                         RTE_LOG(ERR, EAL, "Cannot setup alarm\n");
133                         goto fail;
134                 }
135         } else {
136                 /* Dispatch a task to set alarm into the interrupt thread.
137                  * Execute it synchronously, because it can fail.
138                  */
139                 struct alarm_task task = {
140                         .entry = ap,
141                         .deadline = deadline,
142                 };
143
144                 ret = intr_thread_exec_sync(alarm_task_exec, &task);
145                 if (ret < 0) {
146                         RTE_LOG(ERR, EAL, "Cannot setup alarm in interrupt thread\n");
147                         goto fail;
148                 }
149
150                 ret = task.ret;
151                 if (ret < 0)
152                         goto fail;
153         }
154
155         rte_spinlock_lock(&alarm_lock);
156         LIST_INSERT_HEAD(&alarm_list, ap, next);
157         rte_spinlock_unlock(&alarm_lock);
158
159         goto exit;
160
161 fail:
162         if (timer != NULL)
163                 CloseHandle(timer);
164         if (ap != NULL)
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
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
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
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
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 }