lib: remove librte_ prefix from directory names
[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         /* Calculate deadline ASAP, unit of measure = 100ns. */
95         GetSystemTimePreciseAsFileTime(&ft);
96         deadline.LowPart = ft.dwLowDateTime;
97         deadline.HighPart = ft.dwHighDateTime;
98         deadline.QuadPart += 10 * us;
99
100         ap = calloc(1, sizeof(*ap));
101         if (ap == NULL) {
102                 RTE_LOG(ERR, EAL, "Cannot allocate alarm entry\n");
103                 ret = -ENOMEM;
104                 goto exit;
105         }
106
107         timer = CreateWaitableTimer(NULL, FALSE, NULL);
108         if (timer == NULL) {
109                 RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
110                 ret = -EINVAL;
111                 goto fail;
112         }
113
114         ap->timer = timer;
115         ap->cb_fn = cb_fn;
116         ap->cb_arg = cb_arg;
117
118         /* Waitable timer must be set in the same thread that will
119          * do an alertable wait for the alarm to trigger, that is,
120          * in the interrupt thread.
121          */
122         if (rte_thread_is_intr()) {
123                 /* Directly schedule callback execution. */
124                 ret = alarm_set(ap, deadline);
125                 if (ret < 0) {
126                         RTE_LOG(ERR, EAL, "Cannot setup alarm\n");
127                         goto fail;
128                 }
129         } else {
130                 /* Dispatch a task to set alarm into the interrupt thread.
131                  * Execute it synchronously, because it can fail.
132                  */
133                 struct alarm_task task = {
134                         .entry = ap,
135                         .deadline = deadline,
136                 };
137
138                 ret = intr_thread_exec_sync(alarm_task_exec, &task);
139                 if (ret < 0) {
140                         RTE_LOG(ERR, EAL, "Cannot setup alarm in interrupt thread\n");
141                         goto fail;
142                 }
143
144                 ret = task.ret;
145                 if (ret < 0)
146                         goto fail;
147         }
148
149         rte_spinlock_lock(&alarm_lock);
150         LIST_INSERT_HEAD(&alarm_list, ap, next);
151         rte_spinlock_unlock(&alarm_lock);
152
153         goto exit;
154
155 fail:
156         if (timer != NULL)
157                 CloseHandle(timer);
158         if (ap != NULL)
159                 free(ap);
160
161 exit:
162         rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
163         return ret;
164 }
165
166 static bool
167 alarm_matches(const struct alarm_entry *ap,
168         rte_eal_alarm_callback cb_fn, void *cb_arg)
169 {
170         bool any_arg = cb_arg == (void *)(-1);
171         return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg);
172 }
173
174 int
175 rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
176 {
177         struct alarm_entry *ap;
178         unsigned int state;
179         int removed;
180         bool executing;
181
182         removed = 0;
183         do {
184                 executing = false;
185
186                 rte_spinlock_lock(&alarm_lock);
187
188                 LIST_FOREACH(ap, &alarm_list, next) {
189                         if (!alarm_matches(ap, cb_fn, cb_arg))
190                                 continue;
191
192                         state = ALARM_ARMED;
193                         if (atomic_compare_exchange_strong(
194                                         &ap->state, &state, ALARM_CANCELLED)) {
195                                 alarm_remove_unsafe(ap);
196                                 removed++;
197                         } else if (state == ALARM_TRIGGERED)
198                                 executing = true;
199                 }
200
201                 rte_spinlock_unlock(&alarm_lock);
202         } while (executing);
203
204         rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed);
205         return removed;
206 }
207
208 struct intr_task {
209         void (*func)(void *arg);
210         void *arg;
211         rte_spinlock_t lock; /* unlocked at task completion */
212 };
213
214 static void
215 intr_thread_entry(void *arg)
216 {
217         struct intr_task *task = arg;
218         task->func(task->arg);
219         rte_spinlock_unlock(&task->lock);
220 }
221
222 static int
223 intr_thread_exec_sync(void (*func)(void *arg), void *arg)
224 {
225         struct intr_task task;
226         int ret;
227
228         task.func = func;
229         task.arg = arg;
230         rte_spinlock_init(&task.lock);
231
232         /* Make timers more precise by synchronizing in userspace. */
233         rte_spinlock_lock(&task.lock);
234         ret = eal_intr_thread_schedule(intr_thread_entry, &task);
235         if (ret < 0) {
236                 RTE_LOG(ERR, EAL, "Cannot schedule task to interrupt thread\n");
237                 return -EINVAL;
238         }
239
240         /* Wait for the task to complete. */
241         rte_spinlock_lock(&task.lock);
242         return 0;
243 }