zephyr/kernel/events.c
Aastha Grover a2dccf1283 kernel: events: fix walking the waitq race condition
Fixes race condition for k_event_post_internal() in an
SMP environment while walking the waitq. Uses z_sched_waitq_walk()
to safely walk the waitq by using a sched_spinlock.
It should be noted that since walking the wait queue is an
operation of indeterminant length, there exists the possibility
that the sched_spinlock (which is a highly used and contended-for
lock) may be locked for an indeterminant amount of time. However,
it is expected that few threads will be waiting on any given kernel
event object, which should ameliorate this risk.

Fixes #54317

Signed-off-by: Aastha Grover <aastha.grover@intel.com>
2023-03-09 09:22:21 +01:00

325 lines
8.5 KiB
C

/*
* Copyright (c) 2021 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file event objects library
*
* Event objects are used to signal one or more threads that a custom set of
* events has occurred. Threads wait on event objects until another thread or
* ISR posts the desired set of events to the event object. Each time events
* are posted to an event object, all threads waiting on that event object are
* processed to determine if there is a match. All threads that whose wait
* conditions match the current set of events now belonging to the event object
* are awakened.
*
* Threads waiting on an event object have the option of either waking once
* any or all of the events it desires have been posted to the event object.
*
* @brief Kernel event object
*/
#include <zephyr/kernel.h>
#include <zephyr/kernel_structs.h>
#include <zephyr/toolchain.h>
#include <zephyr/wait_q.h>
#include <zephyr/sys/dlist.h>
#include <ksched.h>
#include <zephyr/init.h>
#include <zephyr/syscall_handler.h>
#include <zephyr/tracing/tracing.h>
#include <zephyr/sys/check.h>
#define K_EVENT_WAIT_ANY 0x00 /* Wait for any events */
#define K_EVENT_WAIT_ALL 0x01 /* Wait for all events */
#define K_EVENT_WAIT_MASK 0x01
#define K_EVENT_WAIT_RESET 0x02 /* Reset events prior to waiting */
struct event_walk_data {
struct k_thread *head;
uint32_t events;
};
void z_impl_k_event_init(struct k_event *event)
{
event->events = 0;
event->lock = (struct k_spinlock) {};
SYS_PORT_TRACING_OBJ_INIT(k_event, event);
z_waitq_init(&event->wait_q);
z_object_init(event);
}
#ifdef CONFIG_USERSPACE
void z_vrfy_k_event_init(struct k_event *event)
{
Z_OOPS(Z_SYSCALL_OBJ_NEVER_INIT(event, K_OBJ_EVENT));
z_impl_k_event_init(event);
}
#include <syscalls/k_event_init_mrsh.c>
#endif
/**
* @brief determine if desired set of events been satisfied
*
* This routine determines if the current set of events satisfies the desired
* set of events. If @a wait_condition is K_EVENT_WAIT_ALL, then at least
* all the desired events must be present to satisfy the request. If @a
* wait_condition is not K_EVENT_WAIT_ALL, it is assumed to be K_EVENT_WAIT_ANY.
* In the K_EVENT_WAIT_ANY case, the request is satisfied when any of the
* current set of events are present in the desired set of events.
*/
static bool are_wait_conditions_met(uint32_t desired, uint32_t current,
unsigned int wait_condition)
{
uint32_t match = current & desired;
if (wait_condition == K_EVENT_WAIT_ALL) {
return match == desired;
}
/* wait_condition assumed to be K_EVENT_WAIT_ANY */
return match != 0;
}
static int event_walk_op(struct k_thread *thread, void *data)
{
unsigned int wait_condition;
struct event_walk_data *event_data = data;
wait_condition = thread->event_options & K_EVENT_WAIT_MASK;
if (are_wait_conditions_met(thread->events, event_data->events,
wait_condition)) {
/*
* The wait conditions have been satisfied. Add this
* thread to the list of threads to unpend.
*/
thread->next_event_link = event_data->head;
event_data->head = thread;
}
return 0;
}
static void k_event_post_internal(struct k_event *event, uint32_t events,
uint32_t events_mask)
{
k_spinlock_key_t key;
struct k_thread *thread;
struct event_walk_data data;
data.head = NULL;
key = k_spin_lock(&event->lock);
SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_event, post, event, events,
events_mask);
events = (event->events & ~events_mask) |
(events & events_mask);
event->events = events;
data.events = events;
/*
* Posting an event has the potential to wake multiple pended threads.
* It is desirable to unpend all affected threads simultaneously. To
* do so, this must be done in three steps as it is unsafe to unpend
* threads from within the _WAIT_Q_FOR_EACH() loop.
*
* 1. Create a linked list of threads to unpend.
* 2. Unpend each of the threads in the linked list
* 3. Ready each of the threads in the linked list
*/
z_sched_waitq_walk(&event->wait_q, event_walk_op, &data);
if (data.head != NULL) {
thread = data.head;
do {
z_unpend_thread(thread);
arch_thread_return_value_set(thread, 0);
thread->events = events;
z_ready_thread(thread);
thread = thread->next_event_link;
} while (thread != NULL);
}
z_reschedule(&event->lock, key);
SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_event, post, event, events,
events_mask);
}
void z_impl_k_event_post(struct k_event *event, uint32_t events)
{
k_event_post_internal(event, events, events);
}
#ifdef CONFIG_USERSPACE
void z_vrfy_k_event_post(struct k_event *event, uint32_t events)
{
Z_OOPS(Z_SYSCALL_OBJ(event, K_OBJ_EVENT));
z_impl_k_event_post(event, events);
}
#include <syscalls/k_event_post_mrsh.c>
#endif
void z_impl_k_event_set(struct k_event *event, uint32_t events)
{
k_event_post_internal(event, events, ~0);
}
#ifdef CONFIG_USERSPACE
void z_vrfy_k_event_set(struct k_event *event, uint32_t events)
{
Z_OOPS(Z_SYSCALL_OBJ(event, K_OBJ_EVENT));
z_impl_k_event_set(event, events);
}
#include <syscalls/k_event_set_mrsh.c>
#endif
void z_impl_k_event_set_masked(struct k_event *event, uint32_t events,
uint32_t events_mask)
{
k_event_post_internal(event, events, events_mask);
}
#ifdef CONFIG_USERSPACE
void z_vrfy_k_event_set_masked(struct k_event *event, uint32_t events,
uint32_t events_mask)
{
Z_OOPS(Z_SYSCALL_OBJ(event, K_OBJ_EVENT));
z_impl_k_event_set_masked(event, events, events_mask);
}
#include <syscalls/k_event_set_masked_mrsh.c>
#endif
void z_impl_k_event_clear(struct k_event *event, uint32_t events)
{
k_event_post_internal(event, 0, events);
}
#ifdef CONFIG_USERSPACE
void z_vrfy_k_event_clear(struct k_event *event, uint32_t events)
{
Z_OOPS(Z_SYSCALL_OBJ(event, K_OBJ_EVENT));
z_impl_k_event_clear(event, events);
}
#include <syscalls/k_event_clear_mrsh.c>
#endif
static uint32_t k_event_wait_internal(struct k_event *event, uint32_t events,
unsigned int options, k_timeout_t timeout)
{
uint32_t rv = 0;
unsigned int wait_condition;
struct k_thread *thread;
__ASSERT(((arch_is_in_isr() == false) ||
K_TIMEOUT_EQ(timeout, K_NO_WAIT)), "");
SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_event, wait, event, events,
options, timeout);
if (events == 0) {
SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_event, wait, event, events, 0);
return 0;
}
wait_condition = options & K_EVENT_WAIT_MASK;
thread = z_current_get();
k_spinlock_key_t key = k_spin_lock(&event->lock);
if (options & K_EVENT_WAIT_RESET) {
event->events = 0;
}
/* Test if the wait conditions have already been met. */
if (are_wait_conditions_met(events, event->events, wait_condition)) {
rv = event->events;
k_spin_unlock(&event->lock, key);
goto out;
}
/* Match conditions have not been met. */
if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
k_spin_unlock(&event->lock, key);
goto out;
}
/*
* The caller must pend to wait for the match. Save the desired
* set of events in the k_thread structure.
*/
thread->events = events;
thread->event_options = options;
SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_event, wait, event, events,
options, timeout);
if (z_pend_curr(&event->lock, key, &event->wait_q, timeout) == 0) {
/* Retrieve the set of events that woke the thread */
rv = thread->events;
}
out:
SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_event, wait, event,
events, rv & events);
return rv & events;
}
/**
* Wait for any of the specified events
*/
uint32_t z_impl_k_event_wait(struct k_event *event, uint32_t events,
bool reset, k_timeout_t timeout)
{
uint32_t options = reset ? K_EVENT_WAIT_RESET : 0;
return k_event_wait_internal(event, events, options, timeout);
}
#ifdef CONFIG_USERSPACE
uint32_t z_vrfy_k_event_wait(struct k_event *event, uint32_t events,
bool reset, k_timeout_t timeout)
{
Z_OOPS(Z_SYSCALL_OBJ(event, K_OBJ_EVENT));
return z_impl_k_event_wait(event, events, reset, timeout);
}
#include <syscalls/k_event_wait_mrsh.c>
#endif
/**
* Wait for all of the specified events
*/
uint32_t z_impl_k_event_wait_all(struct k_event *event, uint32_t events,
bool reset, k_timeout_t timeout)
{
uint32_t options = reset ? (K_EVENT_WAIT_RESET | K_EVENT_WAIT_ALL)
: K_EVENT_WAIT_ALL;
return k_event_wait_internal(event, events, options, timeout);
}
#ifdef CONFIG_USERSPACE
uint32_t z_vrfy_k_event_wait_all(struct k_event *event, uint32_t events,
bool reset, k_timeout_t timeout)
{
Z_OOPS(Z_SYSCALL_OBJ(event, K_OBJ_EVENT));
return z_impl_k_event_wait_all(event, events, reset, timeout);
}
#include <syscalls/k_event_wait_all_mrsh.c>
#endif