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