xref: /dpdk/lib/eal/windows/eal_alarm.c (revision e9fd1ebf981f361844aea9ec94e17f4bda5e1479)
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 	if (cb_fn == NULL) {
96 		EAL_LOG(ERR, "NULL callback");
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 		EAL_LOG(ERR, "Cannot allocate alarm entry");
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 			EAL_LOG(ERR, "Cannot setup alarm");
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 			EAL_LOG(ERR, "Cannot setup alarm in interrupt thread");
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 		EAL_LOG(ERR, "NULL callback");
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 	__rte_no_thread_safety_analysis
229 {
230 	struct intr_task *task = arg;
231 	task->func(task->arg);
232 	rte_spinlock_unlock(&task->lock);
233 }
234 
235 static int
236 intr_thread_exec_sync(void (*func)(void *arg), void *arg)
237 	__rte_no_thread_safety_analysis
238 {
239 	struct intr_task task;
240 	int ret;
241 
242 	task.func = func;
243 	task.arg = arg;
244 	rte_spinlock_init(&task.lock);
245 
246 	/* Make timers more precise by synchronizing in userspace. */
247 	rte_spinlock_lock(&task.lock);
248 	ret = eal_intr_thread_schedule(intr_thread_entry, &task);
249 	if (ret < 0) {
250 		EAL_LOG(ERR, "Cannot schedule task to interrupt thread");
251 		return -EINVAL;
252 	}
253 
254 	/* Wait for the task to complete. */
255 	rte_spinlock_lock(&task.lock);
256 	return 0;
257 }
258