xref: /dpdk/lib/eal/windows/eal_alarm.c (revision a4835c22ccfb5c5ba0aa5b32ebbafc0df12bf75a)
199a2dd95SBruce Richardson /* SPDX-License-Identifier: BSD-3-Clause
299a2dd95SBruce Richardson  * Copyright (c) 2020 Dmitry Kozlyuk
399a2dd95SBruce Richardson  */
499a2dd95SBruce Richardson 
599a2dd95SBruce Richardson #include <stdatomic.h>
699a2dd95SBruce Richardson #include <stdbool.h>
7f1f6ebc0SWilliam Tu #include <sys/queue.h>
899a2dd95SBruce Richardson 
999a2dd95SBruce Richardson #include <rte_alarm.h>
1099a2dd95SBruce Richardson #include <rte_spinlock.h>
1199a2dd95SBruce Richardson 
12ae67895bSDavid Marchand #include "eal_private.h"
1345a685caSAnkur Dwivedi #include <eal_trace_internal.h>
1499a2dd95SBruce Richardson #include "eal_windows.h"
1599a2dd95SBruce Richardson 
1699a2dd95SBruce Richardson enum alarm_state {
1799a2dd95SBruce Richardson 	ALARM_ARMED,
1899a2dd95SBruce Richardson 	ALARM_TRIGGERED,
1999a2dd95SBruce Richardson 	ALARM_CANCELLED
2099a2dd95SBruce Richardson };
2199a2dd95SBruce Richardson 
2299a2dd95SBruce Richardson struct alarm_entry {
2399a2dd95SBruce Richardson 	LIST_ENTRY(alarm_entry) next;
2499a2dd95SBruce Richardson 	rte_eal_alarm_callback cb_fn;
2599a2dd95SBruce Richardson 	void *cb_arg;
2699a2dd95SBruce Richardson 	HANDLE timer;
2799a2dd95SBruce Richardson 	atomic_uint state;
2899a2dd95SBruce Richardson };
2999a2dd95SBruce Richardson 
3099a2dd95SBruce Richardson static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
3199a2dd95SBruce Richardson 
3299a2dd95SBruce Richardson static rte_spinlock_t alarm_lock = RTE_SPINLOCK_INITIALIZER;
3399a2dd95SBruce Richardson 
3499a2dd95SBruce Richardson static int intr_thread_exec_sync(void (*func)(void *arg), void *arg);
3599a2dd95SBruce Richardson 
3699a2dd95SBruce Richardson static void
3799a2dd95SBruce Richardson alarm_remove_unsafe(struct alarm_entry *ap)
3899a2dd95SBruce Richardson {
3999a2dd95SBruce Richardson 	LIST_REMOVE(ap, next);
4099a2dd95SBruce Richardson 	CloseHandle(ap->timer);
4199a2dd95SBruce Richardson 	free(ap);
4299a2dd95SBruce Richardson }
4399a2dd95SBruce Richardson 
4499a2dd95SBruce Richardson static void
4599a2dd95SBruce Richardson alarm_callback(void *arg, DWORD low __rte_unused, DWORD high __rte_unused)
4699a2dd95SBruce Richardson {
4799a2dd95SBruce Richardson 	struct alarm_entry *ap = arg;
4899a2dd95SBruce Richardson 	unsigned int state = ALARM_ARMED;
4999a2dd95SBruce Richardson 
5099a2dd95SBruce Richardson 	if (!atomic_compare_exchange_strong(
5199a2dd95SBruce Richardson 			&ap->state, &state, ALARM_TRIGGERED))
5299a2dd95SBruce Richardson 		return;
5399a2dd95SBruce Richardson 
5499a2dd95SBruce Richardson 	ap->cb_fn(ap->cb_arg);
5599a2dd95SBruce Richardson 
5699a2dd95SBruce Richardson 	rte_spinlock_lock(&alarm_lock);
5799a2dd95SBruce Richardson 	alarm_remove_unsafe(ap);
5899a2dd95SBruce Richardson 	rte_spinlock_unlock(&alarm_lock);
5999a2dd95SBruce Richardson }
6099a2dd95SBruce Richardson 
6199a2dd95SBruce Richardson static int
6299a2dd95SBruce Richardson alarm_set(struct alarm_entry *entry, LARGE_INTEGER deadline)
6399a2dd95SBruce Richardson {
6499a2dd95SBruce Richardson 	BOOL ret = SetWaitableTimer(
6599a2dd95SBruce Richardson 		entry->timer, &deadline, 0, alarm_callback, entry, FALSE);
6699a2dd95SBruce Richardson 	if (!ret) {
6799a2dd95SBruce Richardson 		RTE_LOG_WIN32_ERR("SetWaitableTimer");
6899a2dd95SBruce Richardson 		return -1;
6999a2dd95SBruce Richardson 	}
7099a2dd95SBruce Richardson 	return 0;
7199a2dd95SBruce Richardson }
7299a2dd95SBruce Richardson 
7399a2dd95SBruce Richardson struct alarm_task {
7499a2dd95SBruce Richardson 	struct alarm_entry *entry;
7599a2dd95SBruce Richardson 	LARGE_INTEGER deadline;
7699a2dd95SBruce Richardson 	int ret;
7799a2dd95SBruce Richardson };
7899a2dd95SBruce Richardson 
7999a2dd95SBruce Richardson static void
8099a2dd95SBruce Richardson alarm_task_exec(void *arg)
8199a2dd95SBruce Richardson {
8299a2dd95SBruce Richardson 	struct alarm_task *task = arg;
8399a2dd95SBruce Richardson 	task->ret = alarm_set(task->entry, task->deadline);
8499a2dd95SBruce Richardson }
8599a2dd95SBruce Richardson 
8699a2dd95SBruce Richardson int
8799a2dd95SBruce Richardson rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
8899a2dd95SBruce Richardson {
8999a2dd95SBruce Richardson 	struct alarm_entry *ap;
9099a2dd95SBruce Richardson 	HANDLE timer;
9199a2dd95SBruce Richardson 	FILETIME ft;
9299a2dd95SBruce Richardson 	LARGE_INTEGER deadline;
9399a2dd95SBruce Richardson 	int ret;
9499a2dd95SBruce Richardson 
957f34ecb6SStephen Hemminger 	/* Check parameters, including that us won't cause a uint64_t overflow */
967f34ecb6SStephen Hemminger 	if (us < 1 || us > (UINT64_MAX - US_PER_S)) {
977f34ecb6SStephen Hemminger 		EAL_LOG(ERR, "Invalid alarm interval");
987f34ecb6SStephen Hemminger 		ret = -EINVAL;
997f34ecb6SStephen Hemminger 		goto exit;
1007f34ecb6SStephen Hemminger 	}
1017f34ecb6SStephen Hemminger 
102b8617fccSJie Zhou 	if (cb_fn == NULL) {
103ae67895bSDavid Marchand 		EAL_LOG(ERR, "NULL callback");
104b8617fccSJie Zhou 		ret = -EINVAL;
105b8617fccSJie Zhou 		goto exit;
106b8617fccSJie Zhou 	}
107b8617fccSJie Zhou 
10899a2dd95SBruce Richardson 	/* Calculate deadline ASAP, unit of measure = 100ns. */
10999a2dd95SBruce Richardson 	GetSystemTimePreciseAsFileTime(&ft);
11099a2dd95SBruce Richardson 	deadline.LowPart = ft.dwLowDateTime;
11199a2dd95SBruce Richardson 	deadline.HighPart = ft.dwHighDateTime;
11299a2dd95SBruce Richardson 	deadline.QuadPart += 10 * us;
11399a2dd95SBruce Richardson 
11499a2dd95SBruce Richardson 	ap = calloc(1, sizeof(*ap));
11599a2dd95SBruce Richardson 	if (ap == NULL) {
116ae67895bSDavid Marchand 		EAL_LOG(ERR, "Cannot allocate alarm entry");
11799a2dd95SBruce Richardson 		ret = -ENOMEM;
11899a2dd95SBruce Richardson 		goto exit;
11999a2dd95SBruce Richardson 	}
12099a2dd95SBruce Richardson 
12199a2dd95SBruce Richardson 	timer = CreateWaitableTimer(NULL, FALSE, NULL);
12299a2dd95SBruce Richardson 	if (timer == NULL) {
12399a2dd95SBruce Richardson 		RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
12499a2dd95SBruce Richardson 		ret = -EINVAL;
12599a2dd95SBruce Richardson 		goto fail;
12699a2dd95SBruce Richardson 	}
12799a2dd95SBruce Richardson 
12899a2dd95SBruce Richardson 	ap->timer = timer;
12999a2dd95SBruce Richardson 	ap->cb_fn = cb_fn;
13099a2dd95SBruce Richardson 	ap->cb_arg = cb_arg;
13199a2dd95SBruce Richardson 
13299a2dd95SBruce Richardson 	/* Waitable timer must be set in the same thread that will
13399a2dd95SBruce Richardson 	 * do an alertable wait for the alarm to trigger, that is,
13499a2dd95SBruce Richardson 	 * in the interrupt thread.
13599a2dd95SBruce Richardson 	 */
13699a2dd95SBruce Richardson 	if (rte_thread_is_intr()) {
13799a2dd95SBruce Richardson 		/* Directly schedule callback execution. */
13899a2dd95SBruce Richardson 		ret = alarm_set(ap, deadline);
13999a2dd95SBruce Richardson 		if (ret < 0) {
140ae67895bSDavid Marchand 			EAL_LOG(ERR, "Cannot setup alarm");
14199a2dd95SBruce Richardson 			goto fail;
14299a2dd95SBruce Richardson 		}
14399a2dd95SBruce Richardson 	} else {
14499a2dd95SBruce Richardson 		/* Dispatch a task to set alarm into the interrupt thread.
14599a2dd95SBruce Richardson 		 * Execute it synchronously, because it can fail.
14699a2dd95SBruce Richardson 		 */
14799a2dd95SBruce Richardson 		struct alarm_task task = {
14899a2dd95SBruce Richardson 			.entry = ap,
14999a2dd95SBruce Richardson 			.deadline = deadline,
15099a2dd95SBruce Richardson 		};
15199a2dd95SBruce Richardson 
15299a2dd95SBruce Richardson 		ret = intr_thread_exec_sync(alarm_task_exec, &task);
15399a2dd95SBruce Richardson 		if (ret < 0) {
154ae67895bSDavid Marchand 			EAL_LOG(ERR, "Cannot setup alarm in interrupt thread");
15599a2dd95SBruce Richardson 			goto fail;
15699a2dd95SBruce Richardson 		}
15799a2dd95SBruce Richardson 
15899a2dd95SBruce Richardson 		ret = task.ret;
15999a2dd95SBruce Richardson 		if (ret < 0)
16099a2dd95SBruce Richardson 			goto fail;
16199a2dd95SBruce Richardson 	}
16299a2dd95SBruce Richardson 
16399a2dd95SBruce Richardson 	rte_spinlock_lock(&alarm_lock);
16499a2dd95SBruce Richardson 	LIST_INSERT_HEAD(&alarm_list, ap, next);
16599a2dd95SBruce Richardson 	rte_spinlock_unlock(&alarm_lock);
16699a2dd95SBruce Richardson 
16799a2dd95SBruce Richardson 	goto exit;
16899a2dd95SBruce Richardson 
16999a2dd95SBruce Richardson fail:
17099a2dd95SBruce Richardson 	if (timer != NULL)
17199a2dd95SBruce Richardson 		CloseHandle(timer);
17299a2dd95SBruce Richardson 	free(ap);
17399a2dd95SBruce Richardson 
17499a2dd95SBruce Richardson exit:
17599a2dd95SBruce Richardson 	rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
17699a2dd95SBruce Richardson 	return ret;
17799a2dd95SBruce Richardson }
17899a2dd95SBruce Richardson 
17999a2dd95SBruce Richardson static bool
18099a2dd95SBruce Richardson alarm_matches(const struct alarm_entry *ap,
18199a2dd95SBruce Richardson 	rte_eal_alarm_callback cb_fn, void *cb_arg)
18299a2dd95SBruce Richardson {
18399a2dd95SBruce Richardson 	bool any_arg = cb_arg == (void *)(-1);
18499a2dd95SBruce Richardson 	return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg);
18599a2dd95SBruce Richardson }
18699a2dd95SBruce Richardson 
18799a2dd95SBruce Richardson int
18899a2dd95SBruce Richardson rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
18999a2dd95SBruce Richardson {
19099a2dd95SBruce Richardson 	struct alarm_entry *ap;
19199a2dd95SBruce Richardson 	unsigned int state;
19299a2dd95SBruce Richardson 	int removed;
19399a2dd95SBruce Richardson 	bool executing;
19499a2dd95SBruce Richardson 
19599a2dd95SBruce Richardson 	removed = 0;
196b8617fccSJie Zhou 
197b8617fccSJie Zhou 	if (cb_fn == NULL) {
198ae67895bSDavid Marchand 		EAL_LOG(ERR, "NULL callback");
199b8617fccSJie Zhou 		return -EINVAL;
200b8617fccSJie Zhou 	}
201b8617fccSJie Zhou 
20299a2dd95SBruce Richardson 	do {
20399a2dd95SBruce Richardson 		executing = false;
20499a2dd95SBruce Richardson 
20599a2dd95SBruce Richardson 		rte_spinlock_lock(&alarm_lock);
20699a2dd95SBruce Richardson 
20799a2dd95SBruce Richardson 		LIST_FOREACH(ap, &alarm_list, next) {
20899a2dd95SBruce Richardson 			if (!alarm_matches(ap, cb_fn, cb_arg))
20999a2dd95SBruce Richardson 				continue;
21099a2dd95SBruce Richardson 
21199a2dd95SBruce Richardson 			state = ALARM_ARMED;
21299a2dd95SBruce Richardson 			if (atomic_compare_exchange_strong(
21399a2dd95SBruce Richardson 					&ap->state, &state, ALARM_CANCELLED)) {
21499a2dd95SBruce Richardson 				alarm_remove_unsafe(ap);
21599a2dd95SBruce Richardson 				removed++;
21699a2dd95SBruce Richardson 			} else if (state == ALARM_TRIGGERED)
21799a2dd95SBruce Richardson 				executing = true;
21899a2dd95SBruce Richardson 		}
21999a2dd95SBruce Richardson 
22099a2dd95SBruce Richardson 		rte_spinlock_unlock(&alarm_lock);
221*a4835c22SWojciech Panfil 
222*a4835c22SWojciech Panfil 		/* Yield control to a second thread executing eal_alarm_callback to avoid
223*a4835c22SWojciech Panfil 		 * its starvation, as it is waiting for the lock we have just released.
224*a4835c22SWojciech Panfil 		 */
225*a4835c22SWojciech Panfil 		SwitchToThread();
22699a2dd95SBruce Richardson 	} while (executing);
22799a2dd95SBruce Richardson 
22899a2dd95SBruce Richardson 	rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed);
22999a2dd95SBruce Richardson 	return removed;
23099a2dd95SBruce Richardson }
23199a2dd95SBruce Richardson 
23299a2dd95SBruce Richardson struct intr_task {
23399a2dd95SBruce Richardson 	void (*func)(void *arg);
23499a2dd95SBruce Richardson 	void *arg;
23599a2dd95SBruce Richardson 	rte_spinlock_t lock; /* unlocked at task completion */
23699a2dd95SBruce Richardson };
23799a2dd95SBruce Richardson 
23899a2dd95SBruce Richardson static void
23999a2dd95SBruce Richardson intr_thread_entry(void *arg)
2406673c47dSDavid Marchand 	__rte_no_thread_safety_analysis
24199a2dd95SBruce Richardson {
24299a2dd95SBruce Richardson 	struct intr_task *task = arg;
24399a2dd95SBruce Richardson 	task->func(task->arg);
24499a2dd95SBruce Richardson 	rte_spinlock_unlock(&task->lock);
24599a2dd95SBruce Richardson }
24699a2dd95SBruce Richardson 
24799a2dd95SBruce Richardson static int
24899a2dd95SBruce Richardson intr_thread_exec_sync(void (*func)(void *arg), void *arg)
2496673c47dSDavid Marchand 	__rte_no_thread_safety_analysis
25099a2dd95SBruce Richardson {
25199a2dd95SBruce Richardson 	struct intr_task task;
25299a2dd95SBruce Richardson 	int ret;
25399a2dd95SBruce Richardson 
25499a2dd95SBruce Richardson 	task.func = func;
25599a2dd95SBruce Richardson 	task.arg = arg;
25699a2dd95SBruce Richardson 	rte_spinlock_init(&task.lock);
25799a2dd95SBruce Richardson 
25899a2dd95SBruce Richardson 	/* Make timers more precise by synchronizing in userspace. */
25999a2dd95SBruce Richardson 	rte_spinlock_lock(&task.lock);
26099a2dd95SBruce Richardson 	ret = eal_intr_thread_schedule(intr_thread_entry, &task);
26199a2dd95SBruce Richardson 	if (ret < 0) {
262ae67895bSDavid Marchand 		EAL_LOG(ERR, "Cannot schedule task to interrupt thread");
26399a2dd95SBruce Richardson 		return -EINVAL;
26499a2dd95SBruce Richardson 	}
26599a2dd95SBruce Richardson 
26699a2dd95SBruce Richardson 	/* Wait for the task to complete. */
26799a2dd95SBruce Richardson 	rte_spinlock_lock(&task.lock);
26899a2dd95SBruce Richardson 	return 0;
26999a2dd95SBruce Richardson }
270