From 0e09213e53a43412699603059d45f98202877a9b Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser <kaspar@schleiser.de> Date: Sun, 22 Oct 2017 13:54:43 +0200 Subject: [PATCH] sys/event: initial commit of handler-based event system --- Makefile.dep | 12 ++ makefiles/pseudomodules.inc.mk | 1 + sys/event/Makefile | 5 + sys/event/callback.c | 22 ++++ sys/event/event.c | 79 ++++++++++++ sys/event/timeout.c | 28 +++++ sys/include/event.h | 218 +++++++++++++++++++++++++++++++++ sys/include/event/callback.h | 87 +++++++++++++ sys/include/event/timeout.h | 81 ++++++++++++ 9 files changed, 533 insertions(+) create mode 100644 sys/event/Makefile create mode 100644 sys/event/callback.c create mode 100644 sys/event/event.c create mode 100644 sys/event/timeout.c create mode 100644 sys/include/event.h create mode 100644 sys/include/event/callback.h create mode 100644 sys/include/event/timeout.h diff --git a/Makefile.dep b/Makefile.dep index 86dbc62bd9..6fed87fa97 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -660,6 +660,18 @@ ifneq (,$(filter sock_dns,$(USEMODULE))) USEMODULE += sock_util endif +ifneq (,$(filter event_%,$(USEMODULE))) + USEMODULE += event +endif + +ifneq (,$(filter event_timeout,$(USEMODULE))) + USEMODULE += xtimer +endif + +ifneq (,$(filter event,$(USEMODULE))) + USEMODULE += core_thread_flags +endif + ifneq (,$(filter spiffs,$(USEMODULE))) USEPKG += spiffs USEMODULE += vfs diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 458ea356e9..1c08ac3109 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -8,6 +8,7 @@ PSEUDOMODULES += cbor_semantic_tagging PSEUDOMODULES += conn_can_isotp_multi PSEUDOMODULES += core_% PSEUDOMODULES += emb6_router +PSEUDOMODULES += event_% PSEUDOMODULES += gnrc_ipv6_default PSEUDOMODULES += gnrc_ipv6_router PSEUDOMODULES += gnrc_ipv6_router_default diff --git a/sys/event/Makefile b/sys/event/Makefile new file mode 100644 index 0000000000..6af7702a9e --- /dev/null +++ b/sys/event/Makefile @@ -0,0 +1,5 @@ +SRC := event.c + +SUBMODULES = 1 + +include $(RIOTBASE)/Makefile.base diff --git a/sys/event/callback.c b/sys/event/callback.c new file mode 100644 index 0000000000..ec6da559be --- /dev/null +++ b/sys/event/callback.c @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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. + */ + +#include "event/callback.h" + +void _event_callback_handler(event_t *event) +{ + event_callback_t *event_callback = (event_callback_t *) event; + event_callback->callback(event_callback->arg); +} + +void event_callback_init(event_callback_t *event_callback, void (callback)(void *), void *arg) +{ + event_callback->super.handler = _event_callback_handler; + event_callback->callback = callback; + event_callback->arg = arg; +} diff --git a/sys/event/event.c b/sys/event/event.c new file mode 100644 index 0000000000..570d7e232f --- /dev/null +++ b/sys/event/event.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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. + */ + +#include <assert.h> + +#include <string.h> + +#include "event.h" +#include "clist.h" +#include "thread.h" + +void event_queue_init(event_queue_t *queue) +{ + assert(queue); + memset(queue, '\0', sizeof(*queue)); + queue->waiter = (thread_t *)sched_active_thread; +} + +void event_post(event_queue_t *queue, event_t *event) +{ + assert(!event->list_node.next); + assert(queue->waiter); + + unsigned state = irq_disable(); + clist_rpush(&queue->event_list, &event->list_node); + irq_restore(state); + + thread_flags_set(queue->waiter, THREAD_FLAG_EVENT); +} + +void event_cancel(event_queue_t *queue, event_t *event) +{ + assert(queue); + assert(event); + + unsigned state = irq_disable(); + clist_remove(&queue->event_list, &event->list_node); + event->list_node.next = NULL; + irq_restore(state); +} + +event_t *event_get(event_queue_t *queue) +{ + unsigned state = irq_disable(); + event_t *result = (event_t *) clist_lpop(&queue->event_list); + + irq_restore(state); + if (result) { + result->list_node.next = NULL; + } + return result; +} + +event_t *event_wait(event_queue_t *queue) +{ + thread_flags_wait_any(THREAD_FLAG_EVENT); + unsigned state = irq_disable(); + event_t *result = (event_t *) clist_lpop(&queue->event_list); + if (clist_rpeek(&queue->event_list)) { + queue->waiter->flags |= THREAD_FLAG_EVENT; + } + irq_restore(state); + result->list_node.next = NULL; + return result; +} + +void event_loop(event_queue_t *queue) +{ + event_t *event; + + while ((event = event_wait(queue))) { + event->handler(event); + } +} diff --git a/sys/event/timeout.c b/sys/event/timeout.c new file mode 100644 index 0000000000..c26e63f091 --- /dev/null +++ b/sys/event/timeout.c @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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. + */ + +#include "event/timeout.h" + +static void _event_timeout_callback(void *arg) +{ + event_timeout_t *event_timeout = (event_timeout_t *)arg; + event_post(event_timeout->queue, event_timeout->event); +} + +void event_timeout_init(event_timeout_t *event_timeout, event_queue_t *queue, event_t *event) +{ + event_timeout->timer.callback = _event_timeout_callback; + event_timeout->timer.arg = event_timeout; + event_timeout->queue = queue; + event_timeout->event = event; +} + +void event_timeout_set(event_timeout_t *event_timeout, uint32_t timeout) +{ + xtimer_set(&event_timeout->timer, timeout); +} diff --git a/sys/include/event.h b/sys/include/event.h new file mode 100644 index 0000000000..02e3542950 --- /dev/null +++ b/sys/include/event.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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. + * + * 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 imlementation. + * + * 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.callback = custom_handler, .text = "CUSTOM EVENT" }; + * + * [...] event_post(&queue, &custom_event) + * ~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @{ + * + * @file + * @brief Event API + * + * @author Kaspar Schleiser <kaspar@schleiser.de> + */ + +#ifndef EVENT_H +#define EVENT_H + +#include <stdint.h> + +#include "irq.h" +#include "thread_flags.h" +#include "clist.h" + +#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_t *)sched_active_thread } + +/** + * @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 { + clist_node_t event_list; /**< list of queued events */ + thread_t *waiter; /**< thread ownning event queue */ +} event_queue_t; + +/** + * @brief Initialize an event queue + * + * This will set the calling thread as owner of @p queue. + * + * @param[out] queue event queue object to initialize + */ +void event_queue_init(event_queue_t *queue); + +/** + * @brief Queue an event + * + * @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 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 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). + * + * @param[in] queue event queue to get event from + * + * @returns pointer to next event + */ +event_t *event_wait(event_queue_t *queue); + +/** + * @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); + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @param[in] queue event queue to process + */ +void event_loop(event_queue_t *queue); + +#ifdef __cplusplus +} +#endif +#endif /* EVENT_H */ +/** @} */ diff --git a/sys/include/event/callback.h b/sys/include/event/callback.h new file mode 100644 index 0000000000..c92de38b49 --- /dev/null +++ b/sys/include/event/callback.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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. + */ + +/** + * @ingroup sys_event + * @brief Provides a callback-with-argument event type + * + * Example: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~ {.c} + * void callback(void *arg) + * { + * printf("%s called with arg %p\n", __func__, arg); + * } + * + * [...] + * event_callback_t event_callback = EVENT_CALLBACK_INIT(callback, 0x12345678); + * event_post(&queue, &event_callback); + * ~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @{ + * + * @file + * @brief Event Callback API + * + * @author Kaspar Schleiser <kaspar@schleiser.de> + */ + +#ifndef EVENT_CALLBACK_H +#define EVENT_CALLBACK_H + +#include "event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Callback Event structure definition + */ +typedef struct { + event_t super; /**< event_t structure that gets extended */ + void (*callback)(void*); /**< callback function */ + void *arg; /**< callback function argument */ +} event_callback_t; + +/** + * @brief event callback initialization function + * + * @param[out] event_callback object to initialize + * @param[in] callback callback to set up + * @param[in] arg callback argument to set up + */ +void event_callback_init(event_callback_t *event_callback, void (*callback)(void *), void *arg); + +/** + * @brief event callback handler function (used internally) + * + * @internal + * + * @param[in] event callback event to process + */ +void _event_callback_handler(event_t *event); + +/** + * @brief Callback Event static initializer + * + * @param[in] _cb callback function to set + * @param[in] _arg arguments to set + */ +#define EVENT_CALLBACK_INIT(_cb, _arg) \ + { \ + .super.handler = _event_callback_handler, \ + .callback = _cb, \ + .arg = (void *)_arg \ + } + +#ifdef __cplusplus +} +#endif +#endif /* EVENT_CALLBACK_H */ +/** @} */ diff --git a/sys/include/event/timeout.h b/sys/include/event/timeout.h new file mode 100644 index 0000000000..0d13e65ea7 --- /dev/null +++ b/sys/include/event/timeout.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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. + */ + +/** + * @ingroup sys_event + * @brief Provides functionality to trigger events after timeout + * + * event_timeout intentionally does't extend event structures in order to + * support events that are integrated in larger structs intrusively. + * + * Example: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~ {.c} + * event_timeout_t event_timeout; + * + * printf("posting timed callback with timeout 1sec\n"); + * event_timeout_init(&event_timeout, &queue, (event_t*)&event); + * event_timeout_set(&event_timeout, 1000000); + * [...] + * ~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @{ + * + * @file + * @brief Event Timeout API + * + * @author Kaspar Schleiser <kaspar@schleiser.de> + */ + +#ifndef EVENT_TIMEOUT_H +#define EVENT_TIMEOUT_H + +#include "event.h" +#include "xtimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Timeout Event structure + */ +typedef struct { + xtimer_t timer; /**< xtimer object used for timeout */ + event_queue_t *queue; /**< event queue to post event to */ + event_t *event; /**< event to post after timeout */ +} event_timeout_t; + +/** + * @brief Initialize timeout event object + * + * @param[in] event_timeout event_timeout object to initilalize + * @param[in] queue queue that the timed-out event will be added to + * @param[in] event event to add to queue after timeout + */ +void event_timeout_init(event_timeout_t *event_timeout, event_queue_t *queue, event_t *event); + +/** + * @brief Set a timeout + * + * This will make the event as configured in @p event_timeout be triggered + * after @p timeout miliseconds. + * + * @note: the used event_timeout struct must stay valid until after the timeout + * event has been processed! + * + * @param[in] event_timeout event_timout context onject to use + * @param[in] timeout timeout in miliseconds + */ +void event_timeout_set(event_timeout_t *event_timeout, uint32_t timeout); + +#ifdef __cplusplus +} +#endif +#endif /* EVENT_TIMEOUT_H */ +/** @} */ -- GitLab