blob: 8cb90dc61601d482f4dea39eb7f9279131603acf [file] [log] [blame]
/*
* 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/sys/dlist.h>
#include <zephyr/init.h>
#include <zephyr/internal/syscall_handler.h>
#include <zephyr/tracing/tracing.h>
#include <zephyr/sys/check.h>
/* private kernel APIs */
#include <wait_q.h>
#include <ksched.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;
};
#ifdef CONFIG_OBJ_CORE_EVENT
static struct k_obj_type obj_type_event;
#endif
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);
k_object_init(event);
#ifdef CONFIG_OBJ_CORE_EVENT
k_obj_core_init_and_link(K_OBJ_CORE(event), &obj_type_event);
#endif
}
#ifdef CONFIG_USERSPACE
void z_vrfy_k_event_init(struct k_event *event)
{
K_OOPS(K_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)) {
/*
* Events create a list of threads to wake up. We do
* not want z_thread_timeout to wake these threads; they
* will be woken up by k_event_post_internal once they
* have been processed.
*/
thread->no_wake_on_timeout = true;
/*
* 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;
z_abort_timeout(&thread->base.timeout);
}
return 0;
}
static uint32_t 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;
uint32_t previous_events;
data.head = NULL;
key = k_spin_lock(&event->lock);
SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_event, post, event, events,
events_mask);
previous_events = 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. This
* is done in three steps:
*
* 1. Walk the waitq and 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;
struct k_thread *next;
do {
arch_thread_return_value_set(thread, 0);
thread->events = events;
next = thread->next_event_link;
z_sched_wake_thread(thread, false);
thread = next;
} while (thread != NULL);
}
z_reschedule(&event->lock, key);
SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_event, post, event, events,
events_mask);
return previous_events;
}
uint32_t z_impl_k_event_post(struct k_event *event, uint32_t events)
{
return k_event_post_internal(event, events, events);
}
#ifdef CONFIG_USERSPACE
uint32_t z_vrfy_k_event_post(struct k_event *event, uint32_t events)
{
K_OOPS(K_SYSCALL_OBJ(event, K_OBJ_EVENT));
return z_impl_k_event_post(event, events);
}
#include <syscalls/k_event_post_mrsh.c>
#endif
uint32_t z_impl_k_event_set(struct k_event *event, uint32_t events)
{
return k_event_post_internal(event, events, ~0);
}
#ifdef CONFIG_USERSPACE
uint32_t z_vrfy_k_event_set(struct k_event *event, uint32_t events)
{
K_OOPS(K_SYSCALL_OBJ(event, K_OBJ_EVENT));
return z_impl_k_event_set(event, events);
}
#include <syscalls/k_event_set_mrsh.c>
#endif
uint32_t z_impl_k_event_set_masked(struct k_event *event, uint32_t events,
uint32_t events_mask)
{
return k_event_post_internal(event, events, events_mask);
}
#ifdef CONFIG_USERSPACE
uint32_t z_vrfy_k_event_set_masked(struct k_event *event, uint32_t events,
uint32_t events_mask)
{
K_OOPS(K_SYSCALL_OBJ(event, K_OBJ_EVENT));
return z_impl_k_event_set_masked(event, events, events_mask);
}
#include <syscalls/k_event_set_masked_mrsh.c>
#endif
uint32_t z_impl_k_event_clear(struct k_event *event, uint32_t events)
{
return k_event_post_internal(event, 0, events);
}
#ifdef CONFIG_USERSPACE
uint32_t z_vrfy_k_event_clear(struct k_event *event, uint32_t events)
{
K_OOPS(K_SYSCALL_OBJ(event, K_OBJ_EVENT));
return 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 = k_sched_current_thread_query();
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)
{
K_OOPS(K_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)
{
K_OOPS(K_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
#ifdef CONFIG_OBJ_CORE_EVENT
static int init_event_obj_core_list(void)
{
/* Initialize condvar object type */
z_obj_type_init(&obj_type_event, K_OBJ_TYPE_EVENT_ID,
offsetof(struct k_event, obj_core));
/* Initialize and link statically defined condvars */
STRUCT_SECTION_FOREACH(k_event, event) {
k_obj_core_init_and_link(K_OBJ_CORE(event), &obj_type_event);
}
return 0;
}
SYS_INIT(init_event_obj_core_list, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
#endif