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