xref: /dpdk/lib/eal/windows/eal_alarm.c (revision d38febb08d57fec29fed27a2d12a507fc6fcdfa1)
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 	if (cb_fn == NULL) {
95 		RTE_LOG(ERR, EAL, "NULL callback\n");
96 		ret = -EINVAL;
97 		goto exit;
98 	}
99 
100 	/* Calculate deadline ASAP, unit of measure = 100ns. */
101 	GetSystemTimePreciseAsFileTime(&ft);
102 	deadline.LowPart = ft.dwLowDateTime;
103 	deadline.HighPart = ft.dwHighDateTime;
104 	deadline.QuadPart += 10 * us;
105 
106 	ap = calloc(1, sizeof(*ap));
107 	if (ap == NULL) {
108 		RTE_LOG(ERR, EAL, "Cannot allocate alarm entry\n");
109 		ret = -ENOMEM;
110 		goto exit;
111 	}
112 
113 	timer = CreateWaitableTimer(NULL, FALSE, NULL);
114 	if (timer == NULL) {
115 		RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
116 		ret = -EINVAL;
117 		goto fail;
118 	}
119 
120 	ap->timer = timer;
121 	ap->cb_fn = cb_fn;
122 	ap->cb_arg = cb_arg;
123 
124 	/* Waitable timer must be set in the same thread that will
125 	 * do an alertable wait for the alarm to trigger, that is,
126 	 * in the interrupt thread.
127 	 */
128 	if (rte_thread_is_intr()) {
129 		/* Directly schedule callback execution. */
130 		ret = alarm_set(ap, deadline);
131 		if (ret < 0) {
132 			RTE_LOG(ERR, EAL, "Cannot setup alarm\n");
133 			goto fail;
134 		}
135 	} else {
136 		/* Dispatch a task to set alarm into the interrupt thread.
137 		 * Execute it synchronously, because it can fail.
138 		 */
139 		struct alarm_task task = {
140 			.entry = ap,
141 			.deadline = deadline,
142 		};
143 
144 		ret = intr_thread_exec_sync(alarm_task_exec, &task);
145 		if (ret < 0) {
146 			RTE_LOG(ERR, EAL, "Cannot setup alarm in interrupt thread\n");
147 			goto fail;
148 		}
149 
150 		ret = task.ret;
151 		if (ret < 0)
152 			goto fail;
153 	}
154 
155 	rte_spinlock_lock(&alarm_lock);
156 	LIST_INSERT_HEAD(&alarm_list, ap, next);
157 	rte_spinlock_unlock(&alarm_lock);
158 
159 	goto exit;
160 
161 fail:
162 	if (timer != NULL)
163 		CloseHandle(timer);
164 	if (ap != NULL)
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 }
256