eal: rename lcore master and slave
[dpdk.git] / lib / librte_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(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 struct alarm_task {
61         struct alarm_entry *entry;
62         LARGE_INTEGER deadline;
63         int ret;
64 };
65
66 static void
67 alarm_set(void *arg)
68 {
69         struct alarm_task *task = arg;
70
71         BOOL ret = SetWaitableTimer(
72                 task->entry->timer, &task->deadline,
73                 0, alarm_callback, task->entry, FALSE);
74         task->ret = ret ? 0 : (-1);
75 }
76
77 int
78 rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
79 {
80         struct alarm_entry *ap;
81         HANDLE timer;
82         FILETIME ft;
83         struct alarm_task task;
84         int ret;
85
86         /* Calculate deadline ASAP, unit of measure = 100ns. */
87         GetSystemTimePreciseAsFileTime(&ft);
88         task.deadline.LowPart = ft.dwLowDateTime;
89         task.deadline.HighPart = ft.dwHighDateTime;
90         task.deadline.QuadPart += 10 * us;
91
92         ap = calloc(1, sizeof(*ap));
93         if (ap == NULL) {
94                 RTE_LOG(ERR, EAL, "Cannot allocate alarm entry\n");
95                 ret = -ENOMEM;
96                 goto exit;
97         }
98
99         timer = CreateWaitableTimer(NULL, FALSE, NULL);
100         if (timer == NULL) {
101                 RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
102                 ret = -EINVAL;
103                 goto fail;
104         }
105
106         ap->timer = timer;
107         ap->cb_fn = cb_fn;
108         ap->cb_arg = cb_arg;
109         task.entry = ap;
110
111         /* Waitable timer must be set in the same thread that will
112          * do an alertable wait for the alarm to trigger, that is,
113          * in the interrupt thread. Setting can fail, so do it synchronously.
114          */
115         ret = intr_thread_exec(alarm_set, &task);
116         if (ret < 0) {
117                 RTE_LOG(ERR, EAL, "Cannot setup alarm in interrupt thread\n");
118                 goto fail;
119         }
120
121         ret = task.ret;
122         if (ret < 0)
123                 goto fail;
124
125         rte_spinlock_lock(&alarm_lock);
126         LIST_INSERT_HEAD(&alarm_list, ap, next);
127         rte_spinlock_unlock(&alarm_lock);
128
129         goto exit;
130
131 fail:
132         if (timer != NULL)
133                 CloseHandle(timer);
134         if (ap != NULL)
135                 free(ap);
136
137 exit:
138         rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
139         return ret;
140 }
141
142 static bool
143 alarm_matches(const struct alarm_entry *ap,
144         rte_eal_alarm_callback cb_fn, void *cb_arg)
145 {
146         bool any_arg = cb_arg == (void *)(-1);
147         return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg);
148 }
149
150 int
151 rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
152 {
153         struct alarm_entry *ap;
154         unsigned int state;
155         int removed;
156         bool executing;
157
158         removed = 0;
159         do {
160                 executing = false;
161
162                 rte_spinlock_lock(&alarm_lock);
163
164                 LIST_FOREACH(ap, &alarm_list, next) {
165                         if (!alarm_matches(ap, cb_fn, cb_arg))
166                                 continue;
167
168                         state = ALARM_ARMED;
169                         if (atomic_compare_exchange_strong(
170                                         &ap->state, &state, ALARM_CANCELLED)) {
171                                 alarm_remove_unsafe(ap);
172                                 removed++;
173                         } else if (state == ALARM_TRIGGERED)
174                                 executing = true;
175                 }
176
177                 rte_spinlock_unlock(&alarm_lock);
178         } while (executing);
179
180         rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed);
181         return removed;
182 }
183
184 struct intr_task {
185         void (*func)(void *arg);
186         void *arg;
187         rte_spinlock_t lock; /* unlocked at task completion */
188 };
189
190 static void
191 intr_thread_entry(void *arg)
192 {
193         struct intr_task *task = arg;
194         task->func(task->arg);
195         rte_spinlock_unlock(&task->lock);
196 }
197
198 static int
199 intr_thread_exec(void (*func)(void *arg), void *arg)
200 {
201         struct intr_task task;
202         int ret;
203
204         task.func = func;
205         task.arg = arg;
206         rte_spinlock_init(&task.lock);
207
208         /* Make timers more precise by synchronizing in userspace. */
209         rte_spinlock_lock(&task.lock);
210         ret = eal_intr_thread_schedule(intr_thread_entry, &task);
211         if (ret < 0) {
212                 RTE_LOG(ERR, EAL, "Cannot schedule task to interrupt thread\n");
213                 return -EINVAL;
214         }
215
216         /* Wait for the task to complete. */
217         rte_spinlock_lock(&task.lock);
218         return 0;
219 }