xref: /dpdk/lib/eal/windows/eal_alarm.c (revision a4835c22ccfb5c5ba0aa5b32ebbafc0df12bf75a)
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 "eal_private.h"
13 #include <eal_trace_internal.h>
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 	/* Check parameters, including that us won't cause a uint64_t overflow */
96 	if (us < 1 || us > (UINT64_MAX - US_PER_S)) {
97 		EAL_LOG(ERR, "Invalid alarm interval");
98 		ret = -EINVAL;
99 		goto exit;
100 	}
101 
102 	if (cb_fn == NULL) {
103 		EAL_LOG(ERR, "NULL callback");
104 		ret = -EINVAL;
105 		goto exit;
106 	}
107 
108 	/* Calculate deadline ASAP, unit of measure = 100ns. */
109 	GetSystemTimePreciseAsFileTime(&ft);
110 	deadline.LowPart = ft.dwLowDateTime;
111 	deadline.HighPart = ft.dwHighDateTime;
112 	deadline.QuadPart += 10 * us;
113 
114 	ap = calloc(1, sizeof(*ap));
115 	if (ap == NULL) {
116 		EAL_LOG(ERR, "Cannot allocate alarm entry");
117 		ret = -ENOMEM;
118 		goto exit;
119 	}
120 
121 	timer = CreateWaitableTimer(NULL, FALSE, NULL);
122 	if (timer == NULL) {
123 		RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
124 		ret = -EINVAL;
125 		goto fail;
126 	}
127 
128 	ap->timer = timer;
129 	ap->cb_fn = cb_fn;
130 	ap->cb_arg = cb_arg;
131 
132 	/* Waitable timer must be set in the same thread that will
133 	 * do an alertable wait for the alarm to trigger, that is,
134 	 * in the interrupt thread.
135 	 */
136 	if (rte_thread_is_intr()) {
137 		/* Directly schedule callback execution. */
138 		ret = alarm_set(ap, deadline);
139 		if (ret < 0) {
140 			EAL_LOG(ERR, "Cannot setup alarm");
141 			goto fail;
142 		}
143 	} else {
144 		/* Dispatch a task to set alarm into the interrupt thread.
145 		 * Execute it synchronously, because it can fail.
146 		 */
147 		struct alarm_task task = {
148 			.entry = ap,
149 			.deadline = deadline,
150 		};
151 
152 		ret = intr_thread_exec_sync(alarm_task_exec, &task);
153 		if (ret < 0) {
154 			EAL_LOG(ERR, "Cannot setup alarm in interrupt thread");
155 			goto fail;
156 		}
157 
158 		ret = task.ret;
159 		if (ret < 0)
160 			goto fail;
161 	}
162 
163 	rte_spinlock_lock(&alarm_lock);
164 	LIST_INSERT_HEAD(&alarm_list, ap, next);
165 	rte_spinlock_unlock(&alarm_lock);
166 
167 	goto exit;
168 
169 fail:
170 	if (timer != NULL)
171 		CloseHandle(timer);
172 	free(ap);
173 
174 exit:
175 	rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
176 	return ret;
177 }
178 
179 static bool
180 alarm_matches(const struct alarm_entry *ap,
181 	rte_eal_alarm_callback cb_fn, void *cb_arg)
182 {
183 	bool any_arg = cb_arg == (void *)(-1);
184 	return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg);
185 }
186 
187 int
188 rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
189 {
190 	struct alarm_entry *ap;
191 	unsigned int state;
192 	int removed;
193 	bool executing;
194 
195 	removed = 0;
196 
197 	if (cb_fn == NULL) {
198 		EAL_LOG(ERR, "NULL callback");
199 		return -EINVAL;
200 	}
201 
202 	do {
203 		executing = false;
204 
205 		rte_spinlock_lock(&alarm_lock);
206 
207 		LIST_FOREACH(ap, &alarm_list, next) {
208 			if (!alarm_matches(ap, cb_fn, cb_arg))
209 				continue;
210 
211 			state = ALARM_ARMED;
212 			if (atomic_compare_exchange_strong(
213 					&ap->state, &state, ALARM_CANCELLED)) {
214 				alarm_remove_unsafe(ap);
215 				removed++;
216 			} else if (state == ALARM_TRIGGERED)
217 				executing = true;
218 		}
219 
220 		rte_spinlock_unlock(&alarm_lock);
221 
222 		/* Yield control to a second thread executing eal_alarm_callback to avoid
223 		 * its starvation, as it is waiting for the lock we have just released.
224 		 */
225 		SwitchToThread();
226 	} while (executing);
227 
228 	rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed);
229 	return removed;
230 }
231 
232 struct intr_task {
233 	void (*func)(void *arg);
234 	void *arg;
235 	rte_spinlock_t lock; /* unlocked at task completion */
236 };
237 
238 static void
239 intr_thread_entry(void *arg)
240 	__rte_no_thread_safety_analysis
241 {
242 	struct intr_task *task = arg;
243 	task->func(task->arg);
244 	rte_spinlock_unlock(&task->lock);
245 }
246 
247 static int
248 intr_thread_exec_sync(void (*func)(void *arg), void *arg)
249 	__rte_no_thread_safety_analysis
250 {
251 	struct intr_task task;
252 	int ret;
253 
254 	task.func = func;
255 	task.arg = arg;
256 	rte_spinlock_init(&task.lock);
257 
258 	/* Make timers more precise by synchronizing in userspace. */
259 	rte_spinlock_lock(&task.lock);
260 	ret = eal_intr_thread_schedule(intr_thread_entry, &task);
261 	if (ret < 0) {
262 		EAL_LOG(ERR, "Cannot schedule task to interrupt thread");
263 		return -EINVAL;
264 	}
265 
266 	/* Wait for the task to complete. */
267 	rte_spinlock_lock(&task.lock);
268 	return 0;
269 }
270