/* * 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 #include #include #include #include #include #include #include #include #include #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 #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 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. 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); } 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 #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 #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 #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 #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 #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 #endif