eal/riscv: support RISC-V architecture
[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         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 }