mem: quiet base address hint warning if not requested
[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 #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
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
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
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
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
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         if (ap != NULL)
166                 free(ap);
167
168 exit:
169         rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
170         return ret;
171 }
172
173 static bool
174 alarm_matches(const struct alarm_entry *ap,
175         rte_eal_alarm_callback cb_fn, void *cb_arg)
176 {
177         bool any_arg = cb_arg == (void *)(-1);
178         return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg);
179 }
180
181 int
182 rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
183 {
184         struct alarm_entry *ap;
185         unsigned int state;
186         int removed;
187         bool executing;
188
189         removed = 0;
190
191         if (cb_fn == NULL) {
192                 RTE_LOG(ERR, EAL, "NULL callback\n");
193                 return -EINVAL;
194         }
195
196         do {
197                 executing = false;
198
199                 rte_spinlock_lock(&alarm_lock);
200
201                 LIST_FOREACH(ap, &alarm_list, next) {
202                         if (!alarm_matches(ap, cb_fn, cb_arg))
203                                 continue;
204
205                         state = ALARM_ARMED;
206                         if (atomic_compare_exchange_strong(
207                                         &ap->state, &state, ALARM_CANCELLED)) {
208                                 alarm_remove_unsafe(ap);
209                                 removed++;
210                         } else if (state == ALARM_TRIGGERED)
211                                 executing = true;
212                 }
213
214                 rte_spinlock_unlock(&alarm_lock);
215         } while (executing);
216
217         rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed);
218         return removed;
219 }
220
221 struct intr_task {
222         void (*func)(void *arg);
223         void *arg;
224         rte_spinlock_t lock; /* unlocked at task completion */
225 };
226
227 static void
228 intr_thread_entry(void *arg)
229 {
230         struct intr_task *task = arg;
231         task->func(task->arg);
232         rte_spinlock_unlock(&task->lock);
233 }
234
235 static int
236 intr_thread_exec_sync(void (*func)(void *arg), void *arg)
237 {
238         struct intr_task task;
239         int ret;
240
241         task.func = func;
242         task.arg = arg;
243         rte_spinlock_init(&task.lock);
244
245         /* Make timers more precise by synchronizing in userspace. */
246         rte_spinlock_lock(&task.lock);
247         ret = eal_intr_thread_schedule(intr_thread_entry, &task);
248         if (ret < 0) {
249                 RTE_LOG(ERR, EAL, "Cannot schedule task to interrupt thread\n");
250                 return -EINVAL;
251         }
252
253         /* Wait for the task to complete. */
254         rte_spinlock_lock(&task.lock);
255         return 0;
256 }