numam-dpdk/lib/eal/windows/eal_alarm.c
Jie Zhou b8617fcc51 eal/windows: check callback parameter of alarm functions
EAL functions rte_eal_alarm_set() and rte_eal_alarm_cancel()
did not for invalid parameters in Windows implementation,
which is caught by the unit test alarm_autotest.

Enforce parameter check to fail fast for invalid parameters.

Fixes: f4cbdbc7fb ("eal/windows: implement alarm API")
Cc: stable@dpdk.org

Signed-off-by: Jie Zhou <jizh@linux.microsoft.com>
Acked-by: Dmitry Kozlyuk <dmitry.kozliuk@gmail.com>
2021-07-22 22:06:27 +02:00

256 lines
5.1 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (c) 2020 Dmitry Kozlyuk
*/
#include <stdatomic.h>
#include <stdbool.h>
#include <rte_alarm.h>
#include <rte_spinlock.h>
#include <rte_eal_trace.h>
#include "eal_windows.h"
enum alarm_state {
ALARM_ARMED,
ALARM_TRIGGERED,
ALARM_CANCELLED
};
struct alarm_entry {
LIST_ENTRY(alarm_entry) next;
rte_eal_alarm_callback cb_fn;
void *cb_arg;
HANDLE timer;
atomic_uint state;
};
static LIST_HEAD(alarm_list, alarm_entry) alarm_list = LIST_HEAD_INITIALIZER();
static rte_spinlock_t alarm_lock = RTE_SPINLOCK_INITIALIZER;
static int intr_thread_exec_sync(void (*func)(void *arg), void *arg);
static void
alarm_remove_unsafe(struct alarm_entry *ap)
{
LIST_REMOVE(ap, next);
CloseHandle(ap->timer);
free(ap);
}
static void
alarm_callback(void *arg, DWORD low __rte_unused, DWORD high __rte_unused)
{
struct alarm_entry *ap = arg;
unsigned int state = ALARM_ARMED;
if (!atomic_compare_exchange_strong(
&ap->state, &state, ALARM_TRIGGERED))
return;
ap->cb_fn(ap->cb_arg);
rte_spinlock_lock(&alarm_lock);
alarm_remove_unsafe(ap);
rte_spinlock_unlock(&alarm_lock);
}
static int
alarm_set(struct alarm_entry *entry, LARGE_INTEGER deadline)
{
BOOL ret = SetWaitableTimer(
entry->timer, &deadline, 0, alarm_callback, entry, FALSE);
if (!ret) {
RTE_LOG_WIN32_ERR("SetWaitableTimer");
return -1;
}
return 0;
}
struct alarm_task {
struct alarm_entry *entry;
LARGE_INTEGER deadline;
int ret;
};
static void
alarm_task_exec(void *arg)
{
struct alarm_task *task = arg;
task->ret = alarm_set(task->entry, task->deadline);
}
int
rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg)
{
struct alarm_entry *ap;
HANDLE timer;
FILETIME ft;
LARGE_INTEGER deadline;
int ret;
if (cb_fn == NULL) {
RTE_LOG(ERR, EAL, "NULL callback\n");
ret = -EINVAL;
goto exit;
}
/* Calculate deadline ASAP, unit of measure = 100ns. */
GetSystemTimePreciseAsFileTime(&ft);
deadline.LowPart = ft.dwLowDateTime;
deadline.HighPart = ft.dwHighDateTime;
deadline.QuadPart += 10 * us;
ap = calloc(1, sizeof(*ap));
if (ap == NULL) {
RTE_LOG(ERR, EAL, "Cannot allocate alarm entry\n");
ret = -ENOMEM;
goto exit;
}
timer = CreateWaitableTimer(NULL, FALSE, NULL);
if (timer == NULL) {
RTE_LOG_WIN32_ERR("CreateWaitableTimer()");
ret = -EINVAL;
goto fail;
}
ap->timer = timer;
ap->cb_fn = cb_fn;
ap->cb_arg = cb_arg;
/* Waitable timer must be set in the same thread that will
* do an alertable wait for the alarm to trigger, that is,
* in the interrupt thread.
*/
if (rte_thread_is_intr()) {
/* Directly schedule callback execution. */
ret = alarm_set(ap, deadline);
if (ret < 0) {
RTE_LOG(ERR, EAL, "Cannot setup alarm\n");
goto fail;
}
} else {
/* Dispatch a task to set alarm into the interrupt thread.
* Execute it synchronously, because it can fail.
*/
struct alarm_task task = {
.entry = ap,
.deadline = deadline,
};
ret = intr_thread_exec_sync(alarm_task_exec, &task);
if (ret < 0) {
RTE_LOG(ERR, EAL, "Cannot setup alarm in interrupt thread\n");
goto fail;
}
ret = task.ret;
if (ret < 0)
goto fail;
}
rte_spinlock_lock(&alarm_lock);
LIST_INSERT_HEAD(&alarm_list, ap, next);
rte_spinlock_unlock(&alarm_lock);
goto exit;
fail:
if (timer != NULL)
CloseHandle(timer);
if (ap != NULL)
free(ap);
exit:
rte_eal_trace_alarm_set(us, cb_fn, cb_arg, ret);
return ret;
}
static bool
alarm_matches(const struct alarm_entry *ap,
rte_eal_alarm_callback cb_fn, void *cb_arg)
{
bool any_arg = cb_arg == (void *)(-1);
return (ap->cb_fn == cb_fn) && (any_arg || ap->cb_arg == cb_arg);
}
int
rte_eal_alarm_cancel(rte_eal_alarm_callback cb_fn, void *cb_arg)
{
struct alarm_entry *ap;
unsigned int state;
int removed;
bool executing;
removed = 0;
if (cb_fn == NULL) {
RTE_LOG(ERR, EAL, "NULL callback\n");
return -EINVAL;
}
do {
executing = false;
rte_spinlock_lock(&alarm_lock);
LIST_FOREACH(ap, &alarm_list, next) {
if (!alarm_matches(ap, cb_fn, cb_arg))
continue;
state = ALARM_ARMED;
if (atomic_compare_exchange_strong(
&ap->state, &state, ALARM_CANCELLED)) {
alarm_remove_unsafe(ap);
removed++;
} else if (state == ALARM_TRIGGERED)
executing = true;
}
rte_spinlock_unlock(&alarm_lock);
} while (executing);
rte_eal_trace_alarm_cancel(cb_fn, cb_arg, removed);
return removed;
}
struct intr_task {
void (*func)(void *arg);
void *arg;
rte_spinlock_t lock; /* unlocked at task completion */
};
static void
intr_thread_entry(void *arg)
{
struct intr_task *task = arg;
task->func(task->arg);
rte_spinlock_unlock(&task->lock);
}
static int
intr_thread_exec_sync(void (*func)(void *arg), void *arg)
{
struct intr_task task;
int ret;
task.func = func;
task.arg = arg;
rte_spinlock_init(&task.lock);
/* Make timers more precise by synchronizing in userspace. */
rte_spinlock_lock(&task.lock);
ret = eal_intr_thread_schedule(intr_thread_entry, &task);
if (ret < 0) {
RTE_LOG(ERR, EAL, "Cannot schedule task to interrupt thread\n");
return -EINVAL;
}
/* Wait for the task to complete. */
rte_spinlock_lock(&task.lock);
return 0;
}