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