mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
473 lines
14 KiB
C
473 lines
14 KiB
C
/*
|
|
* Copyright (C) 2017 Inria
|
|
* 2017 Kaspar Schleiser <kaspar@schleiser.de>
|
|
* 2018-2019 Freie Universität Berlin
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @defgroup sys_event Event Queue
|
|
* @ingroup sys
|
|
* @brief Provides an Event loop
|
|
*
|
|
* This module offers an event queue framework like libevent or libuev.
|
|
*
|
|
* An event queue is basically a FIFO queue of events, with some functions to
|
|
* efficiently and safely handle adding and getting events to / from such a
|
|
* queue.
|
|
*
|
|
* An event queue is bound to a thread, but any thread or ISR can put events
|
|
* into a queue. In most cases, the owning thread of a queue is set during the
|
|
* queue's initialization. But it is also possible to initialize a queue in a
|
|
* detached state from a different context and to set the owning thread
|
|
* at a later point of time using the event_queue_claim() function.
|
|
*
|
|
* An event is a structure containing a pointer to an event handler. It can be
|
|
* extended to provide context or arguments to the handler. It can also be
|
|
* embedded into existing structures (see examples).
|
|
*
|
|
* Compared to msg or mbox, this some fundamental differences:
|
|
*
|
|
* 1. events are "sender allocated". Unlike msg_send(), event_post() never
|
|
* blocks or fails.
|
|
* 2. events contain everything necessary to handle them, thus a thread
|
|
* processing the events of an event queue doesn't need to be changed in
|
|
* order to support new event types.
|
|
* 3. events can be safely used (and actually perform best) when used within
|
|
* one thread, e.g., in order to create a state-machine like process flow.
|
|
* This is not (easily) possible using msg queues, as they might fill up.
|
|
* 4. an event can only be queued in one event queue at the same time.
|
|
* Notifying many queues using only one event object is not possible with
|
|
* this implementation.
|
|
*
|
|
* At the core, event_wait() uses thread flags to implement waiting for events
|
|
* to be queued. Thus event queues can be used safely and efficiently in combination
|
|
* with thread flags and msg queues.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
|
|
* // simple event handler
|
|
* static void handler(event_t *event)
|
|
* {
|
|
* printf("triggered 0x%08x\n", (unsigned)event);
|
|
* }
|
|
*
|
|
* static event_t event = { .handler = handler };
|
|
* static event_queue_t queue;
|
|
*
|
|
* int main(void)
|
|
* {
|
|
* event_queue_init(&queue);
|
|
* event_loop(&queue);
|
|
* }
|
|
*
|
|
* [...] event_post(&queue, &event);
|
|
*
|
|
* // example for event extended event struct
|
|
* typedef struct {
|
|
* event_t super;
|
|
* const char *text;
|
|
* } custom_event_t;
|
|
*
|
|
* static void custom_handler(event_t *event)
|
|
* {
|
|
* custom_event_t *custom_event = (custom_event_t *)event;
|
|
* printf("triggered custom event with text: \"%s\"\n", custom_event->text);
|
|
* }
|
|
*
|
|
* static custom_event_t custom_event = { .super.handler = custom_handler, .text = "CUSTOM EVENT" };
|
|
*
|
|
* [...] event_post(&queue, &custom_event)
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Event API
|
|
*
|
|
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
|
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
|
*/
|
|
|
|
#ifndef EVENT_H
|
|
#define EVENT_H
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include "assert.h"
|
|
#include "clist.h"
|
|
#include "irq.h"
|
|
#include "thread.h"
|
|
#include "thread_flags.h"
|
|
#include "ptrtag.h"
|
|
|
|
#if IS_USED(MODULE_ZTIMER)
|
|
#include "ztimer.h"
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#ifndef THREAD_FLAG_EVENT
|
|
/**
|
|
* @brief Thread flag use to notify available events in an event queue
|
|
*/
|
|
#define THREAD_FLAG_EVENT (0x1)
|
|
#endif
|
|
|
|
/**
|
|
* @brief event_queue_t static initializer
|
|
*/
|
|
#define EVENT_QUEUE_INIT { .waiter = thread_get_active() }
|
|
|
|
/**
|
|
* @brief static initializer for detached event queues
|
|
*/
|
|
#define EVENT_QUEUE_INIT_DETACHED { .waiter = NULL }
|
|
|
|
/**
|
|
* @brief event structure forward declaration
|
|
*/
|
|
typedef struct event event_t;
|
|
|
|
/**
|
|
* @brief event handler type definition
|
|
*/
|
|
typedef void (*event_handler_t)(event_t *);
|
|
|
|
/**
|
|
* @brief event structure
|
|
*/
|
|
struct event {
|
|
clist_node_t list_node; /**< event queue list entry */
|
|
event_handler_t handler; /**< pointer to event handler function */
|
|
};
|
|
|
|
/**
|
|
* @brief event queue structure
|
|
*/
|
|
typedef struct PTRTAG {
|
|
clist_node_t event_list; /**< list of queued events */
|
|
thread_t *waiter; /**< thread owning event queue */
|
|
} event_queue_t;
|
|
|
|
/**
|
|
* @brief Initialize an array of event queues
|
|
*
|
|
* This will set the calling thread as owner of each queue in @p queues.
|
|
*
|
|
* @param[out] queues event queue objects to initialize
|
|
* @param[in] n_queues number of queues in @p queues
|
|
*/
|
|
static inline void event_queues_init(event_queue_t *queues,
|
|
size_t n_queues)
|
|
{
|
|
assert(queues && n_queues);
|
|
thread_t *me = thread_get_active();
|
|
for (size_t i = 0; i < n_queues; i++) {
|
|
memset(&queues[i], '\0', sizeof(queues[0]));
|
|
queues[i].waiter = me;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize an event queue
|
|
*
|
|
* This will set the calling thread as owner of @p queue.
|
|
*
|
|
* @param[out] queue event queue object to initialize
|
|
*/
|
|
static inline void event_queue_init(event_queue_t *queue)
|
|
{
|
|
event_queues_init(queue, 1);
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize an array of event queues not binding it to a thread
|
|
*
|
|
* @param[out] queues event queue objects to initialize
|
|
* @param[in] n_queues number of queues in @p queues
|
|
*/
|
|
static inline void event_queues_init_detached(event_queue_t *queues,
|
|
size_t n_queues)
|
|
{
|
|
assert(queues);
|
|
for (size_t i = 0; i < n_queues; i++) {
|
|
memset(&queues[i], '\0', sizeof(queues[0]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize an event queue not binding it to a thread
|
|
*
|
|
* @param[out] queue event queue object to initialize
|
|
*/
|
|
static inline void event_queue_init_detached(event_queue_t *queue)
|
|
{
|
|
event_queues_init_detached(queue, 1);
|
|
}
|
|
|
|
/**
|
|
* @brief Bind an array of event queues to the calling thread
|
|
*
|
|
* This function must only be called once and only if the given queue is not
|
|
* yet bound to a thread.
|
|
*
|
|
* @pre (queues[i].waiter == NULL for i in {0, ..., n_queues - 1})
|
|
*
|
|
* @param[out] queues event queue objects to bind to a thread
|
|
* @param[in] n_queues number of queues in @p queues
|
|
*/
|
|
static inline void event_queues_claim(event_queue_t *queues, size_t n_queues)
|
|
{
|
|
assert(queues);
|
|
thread_t *me = thread_get_active();
|
|
for (size_t i = 0; i < n_queues; i++) {
|
|
assert(queues[i].waiter == NULL);
|
|
queues[i].waiter = me;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Bind an event queue to the calling thread
|
|
*
|
|
* This function must only be called once and only if the given queue is not
|
|
* yet bound to a thread.
|
|
*
|
|
* @pre (queue->waiter == NULL)
|
|
*
|
|
* @param[out] queue event queue object to bind to a thread
|
|
*/
|
|
static inline void event_queue_claim(event_queue_t *queue)
|
|
{
|
|
event_queues_claim(queue, 1);
|
|
}
|
|
|
|
/**
|
|
* @brief Queue an event
|
|
*
|
|
* The given event will be posted on the given @p queue. If the event is already
|
|
* queued when calling this function, the event will not be touched and remain
|
|
* in the previous position on the queue. So reposting an event while it is
|
|
* already on the queue will have no effect.
|
|
*
|
|
* @pre queue should be initialized
|
|
*
|
|
* @param[in] queue event queue to queue event in
|
|
* @param[in] event event to queue in event queue
|
|
*/
|
|
void event_post(event_queue_t *queue, event_t *event);
|
|
|
|
/**
|
|
* @brief Cancel a queued event
|
|
*
|
|
* This will remove a queued event from an event queue.
|
|
*
|
|
* @note Due to the underlying list implementation, this will run in O(n).
|
|
*
|
|
* @param[in] queue event queue to remove event from
|
|
* @param[in] event event to remove from queue
|
|
*/
|
|
void event_cancel(event_queue_t *queue, event_t *event);
|
|
|
|
/**
|
|
* @brief Check if an event is already queued
|
|
*
|
|
* @param[in] queue event queue to check
|
|
* @param[in] event event to check
|
|
*
|
|
* @returns true if @p event is in @p queue
|
|
* @returns false otherwise
|
|
*/
|
|
bool event_is_queued(const event_queue_t *queue, const event_t *event);
|
|
|
|
/**
|
|
* @brief Get next event from event queue, non-blocking
|
|
*
|
|
* In order to handle an event retrieved using this function,
|
|
* call event->handler(event).
|
|
*
|
|
* @param[in] queue event queue to get event from
|
|
*
|
|
* @returns pointer to next event
|
|
* @returns NULL if no event available
|
|
*/
|
|
event_t *event_get(event_queue_t *queue);
|
|
|
|
/**
|
|
* @brief Get next event from the given event queues, blocking
|
|
*
|
|
* This function will block until an event becomes available. If more than one
|
|
* queue contains an event, the queue with the lowest index is chosen. Thus,
|
|
* a lower index in the @p queues array translates into a higher priority of
|
|
* the queue.
|
|
*
|
|
* In order to handle an event retrieved using this function,
|
|
* call event->handler(event).
|
|
*
|
|
* @warning There can only be a single waiter on a queue!
|
|
*
|
|
* @note This function *can* be suitable for having a single thread
|
|
* handling both real-time and non-real-time events. However, a real
|
|
* time event can be delayed for the whole duration a single
|
|
* non-real-time event takes (in addition to all other sources of
|
|
* latency). Thus, the slowest to handle non-real-time event must still
|
|
* execute fast enough to add an amount of latency (on top of other
|
|
* sources of latency) that is acceptable to the real-time event with
|
|
* the strictest requirements.
|
|
*
|
|
* @pre 0 < @p n_queues (expect blowing `assert()` otherwise)
|
|
* @pre The queue must have a waiter (i.e. it should have been claimed, or
|
|
* initialized using @ref event_queue_init, @ref event_queues_init)
|
|
*
|
|
* @param[in] queues Array of event queues to get event from
|
|
* @param[in] n_queues Number of event queues passed in @p queues
|
|
*
|
|
* @returns pointer to next event
|
|
*/
|
|
event_t *event_wait_multi(event_queue_t *queues, size_t n_queues);
|
|
|
|
/**
|
|
* @brief Get next event from event queue, blocking
|
|
*
|
|
* This function will block until an event becomes available.
|
|
*
|
|
* In order to handle an event retrieved using this function,
|
|
* call event->handler(event).
|
|
*
|
|
* @warning There can only be a single waiter on a queue!
|
|
*
|
|
* @pre The queue must have a waiter (i.e. it should have been claimed, or
|
|
* initialized using @ref event_queue_init, @ref event_queues_init)
|
|
*
|
|
* @param[in] queue event queue to get event from
|
|
*
|
|
* @returns pointer to next event
|
|
*/
|
|
static inline event_t *event_wait(event_queue_t *queue)
|
|
{
|
|
return event_wait_multi(queue, 1);
|
|
}
|
|
|
|
#if IS_USED(MODULE_XTIMER) || defined(DOXYGEN)
|
|
/**
|
|
* @brief Get next event from event queue, blocking until timeout expires
|
|
*
|
|
* @pre The queue must have a waiter (i.e. it should have been claimed, or
|
|
* initialized using @ref event_queue_init, @ref event_queues_init)
|
|
*
|
|
* @param[in] queue queue to query for an event
|
|
* @param[in] timeout maximum time to wait for an event to be posted in us
|
|
*
|
|
* @return pointer to next event if event was taken from the queue
|
|
* @return NULL if timeout expired before an event was posted
|
|
*/
|
|
event_t *event_wait_timeout(event_queue_t *queue, uint32_t timeout);
|
|
|
|
/**
|
|
* @brief Get next event from event queue, blocking until timeout expires
|
|
*
|
|
* @pre The queue must have a waiter (i.e. it should have been claimed, or
|
|
* initialized using @ref event_queue_init, @ref event_queues_init)
|
|
*
|
|
* @param[in] queue queue to query for an event
|
|
* @param[in] timeout maximum time to wait for an event to be posted in us
|
|
*
|
|
* @return pointer to next event if event was taken from the queue
|
|
* @return NULL if timeout expired before an event was posted
|
|
*/
|
|
event_t *event_wait_timeout64(event_queue_t *queue, uint64_t timeout);
|
|
#endif
|
|
|
|
#if IS_USED(MODULE_ZTIMER) || defined(DOXYGEN)
|
|
/**
|
|
* @brief Get next event from event queue, blocking until timeout expires
|
|
*
|
|
* This function is the same as event_wait_timeout() with the difference that it
|
|
* uses ztimer instead of xtimer as timer backend.
|
|
*
|
|
* @pre The queue must have a waiter (i.e. it should have been claimed, or
|
|
* initialized using @ref event_queue_init, @ref event_queues_init)
|
|
*
|
|
* @param[in] queue queue to query for an event
|
|
* @param[in] clock ztimer clock to use
|
|
* @param[in] timeout maximum time to wait for an event, time unit depends
|
|
* on the used ztimer clock
|
|
*
|
|
* @return pointer to next event if event was taken from the queue
|
|
* @return NULL if timeout expired before an event was posted
|
|
*/
|
|
event_t *event_wait_timeout_ztimer(event_queue_t *queue,
|
|
ztimer_clock_t *clock, uint32_t timeout);
|
|
#endif
|
|
|
|
/**
|
|
* @brief Simple event loop with multiple queues
|
|
*
|
|
* This function will forever sit in a loop, waiting for events to be queued
|
|
* and executing their handlers. If more than one queue contains an event, the
|
|
* queue with the lowest index is chosen. Thus, a lower index in the @p queues
|
|
* array translates into a higher priority of the queue.
|
|
*
|
|
* It is pretty much defined as:
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
|
|
* while ((event = event_wait_multi(queues, n_queues))) {
|
|
* event->handler(event);
|
|
* }
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*
|
|
* @see event_wait_multi
|
|
*
|
|
* @pre The queue must have a waiter (i.e. it should have been claimed, or
|
|
* initialized using @ref event_queue_init, @ref event_queues_init)
|
|
*
|
|
* @param[in] queues Event queues to process
|
|
* @param[in] n_queues Number of queues passed with @p queues
|
|
*/
|
|
static inline void event_loop_multi(event_queue_t *queues, size_t n_queues)
|
|
{
|
|
event_t *event;
|
|
|
|
while ((event = event_wait_multi(queues, n_queues))) {
|
|
event->handler(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Simple event loop
|
|
*
|
|
* This function will forever sit in a loop, waiting for events to be queued
|
|
* and executing their handlers.
|
|
*
|
|
* It is pretty much defined as:
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
|
|
* while ((event = event_wait(queue))) {
|
|
* event->handler(event);
|
|
* }
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*
|
|
* @pre The queue must have a waiter (i.e. it should have been claimed, or
|
|
* initialized using @ref event_queue_init, @ref event_queues_init)
|
|
*
|
|
* @param[in] queue event queue to process
|
|
*/
|
|
static inline void event_loop(event_queue_t *queue)
|
|
{
|
|
event_loop_multi(queue, 1);
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif /* EVENT_H */
|
|
/** @} */
|