diff --git a/sys/event/event.c b/sys/event/event.c index 9e6ac213fe1e28515df12133cdceae44aa7164e2..19fac6d6918241f0867116e8ff8ef96fcf05e57e 100644 --- a/sys/event/event.c +++ b/sys/event/event.c @@ -1,11 +1,25 @@ /* * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * 2018 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. */ +/** + * @ingroup sys_event + * @{ + * + * @file + * @brief Event loop implementation + * + * @author Kaspar Schleiser <kaspar@schleiser.de> + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + #include <assert.h> #include <string.h> @@ -14,6 +28,12 @@ #include "clist.h" #include "thread.h" +void event_queue_init_detached(event_queue_t *queue) +{ + assert(queue); + memset(queue, '\0', sizeof(*queue)); +} + void event_queue_init(event_queue_t *queue) { assert(queue); @@ -21,17 +41,31 @@ void event_queue_init(event_queue_t *queue) queue->waiter = (thread_t *)sched_active_thread; } +void event_queue_claim(event_queue_t *queue) +{ + assert(queue && (queue->waiter == NULL)); + queue->waiter = (thread_t *)sched_active_thread; +} + void event_post(event_queue_t *queue, event_t *event) { - assert(queue && queue->waiter && event); + assert(queue && event); unsigned state = irq_disable(); if (!event->list_node.next) { clist_rpush(&queue->event_list, &event->list_node); } + thread_t *waiter = queue->waiter; irq_restore(state); - thread_flags_set(queue->waiter, THREAD_FLAG_EVENT); + /* WARNING: there is a minimal chance, that a waiter claims a formerly + * detached queue between the end of the critical section above and + * the block below. In that case, the new waiter will not be woken + * up. This should be fixed at some point once it is safe to call + * thread_flags_set() inside a critical section on all platforms. */ + if (waiter) { + thread_flags_set(waiter, THREAD_FLAG_EVENT); + } } void event_cancel(event_queue_t *queue, event_t *event) @@ -49,8 +83,8 @@ 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; } @@ -59,13 +93,18 @@ event_t *event_get(event_queue_t *queue) 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); + assert(queue); + event_t *result; + + do { + unsigned state = irq_disable(); + result = (event_t *)clist_lpop(&queue->event_list); + irq_restore(state); + if (result == NULL) { + thread_flags_wait_any(THREAD_FLAG_EVENT); + } + } while (result == NULL); + result->list_node.next = NULL; return result; } diff --git a/sys/include/event.h b/sys/include/event.h index 0727459c912e9669aa689434573d3e3e5ddb18a4..62ddda31c95ba40c1b1cbd88234bc455bebcddf9 100644 --- a/sys/include/event.h +++ b/sys/include/event.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * 2018 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 @@ -16,8 +17,12 @@ * 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. + * 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 @@ -84,6 +89,7 @@ * @brief Event API * * @author Kaspar Schleiser <kaspar@schleiser.de> + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> */ #ifndef EVENT_H @@ -111,6 +117,11 @@ extern "C" { */ #define EVENT_QUEUE_INIT { .waiter = (thread_t *)sched_active_thread } +/** + * @brief static initializer for detached event queues + */ +#define EVENT_QUEUE_INIT_DETACHED { 0 } + /** * @brief event structure forward declaration */ @@ -146,6 +157,25 @@ typedef struct { */ void event_queue_init(event_queue_t *queue); +/** + * @brief Initialize an event queue not binding it to a thread + * + * @param[out] queue event queue object to initialize + */ +void event_queue_init_detached(event_queue_t *queue); + +/** + * @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 + */ +void event_queue_claim(event_queue_t *queue); + /** * @brief Queue an event * @@ -192,6 +222,8 @@ event_t *event_get(event_queue_t *queue); * In order to handle an event retrieved using this function, * call event->handler(event). * + * @note There can only be a single waiter on a queue! + * * @param[in] queue event queue to get event from * * @returns pointer to next event diff --git a/tests/events/Makefile b/tests/events/Makefile index c8ab5501b0ab56067966d480009548b919e1368d..28d4631f9b31ea8e27010571b0a7b643417ae8a0 100644 --- a/tests/events/Makefile +++ b/tests/events/Makefile @@ -1,5 +1,7 @@ include ../Makefile.tests_common +BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-uno + FORCE_ASSERTS = 1 USEMODULE += event_callback USEMODULE += event_timeout diff --git a/tests/events/main.c b/tests/events/main.c index 6c6e94b2a02980a665282eac72048a6454999712..bc2672a1af4a16ed801b4438dca71f3d465cbd6c 100644 --- a/tests/events/main.c +++ b/tests/events/main.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de> + * 2018 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 @@ -14,6 +15,7 @@ * @brief event test application * * @author Kaspar Schleiser <kaspar@schleiser.de> + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> * * @} */ @@ -25,22 +27,30 @@ #include "event/timeout.h" #include "event/callback.h" -static unsigned order; +#define STACKSIZE THREAD_STACKSIZE_DEFAULT +#define PRIO (THREAD_PRIORITY_MAIN - 1) + +static char stack[STACKSIZE]; + +static unsigned order = 0; static uint32_t before; static void callback(event_t *arg); static void custom_callback(event_t *event); static void timed_callback(void *arg); static void forbidden_callback(void *arg); - +static void delayed_callback1(event_t *arg); +static void delayed_callback2(event_t *arg); static event_t event = { .handler = callback }; static event_t event2 = { .handler = callback }; +static event_t delayed_event1 = { .handler = delayed_callback1 }; +static event_t delayed_event2 = { .handler = delayed_callback2 }; static void callback(event_t *arg) { order++; - assert(order == 1); + assert(order == 3); assert(arg == &event); printf("triggered 0x%08x\n", (unsigned)arg); } @@ -57,7 +67,7 @@ static event_callback_t noevent_callback = EVENT_CALLBACK_INIT(forbidden_callbac static void custom_callback(event_t *event) { order++; - assert(order == 2); + assert(order == 4); assert(event == (event_t *)&custom_event); custom_event_t *custom_event = (custom_event_t *)event; printf("triggered custom event with text: \"%s\"\n", custom_event->text); @@ -66,7 +76,7 @@ static void custom_callback(event_t *event) static void timed_callback(void *arg) { order++; - assert(order == 3); + assert(order == 5); assert(arg == event_callback.arg); uint32_t now = xtimer_now_usec(); assert((now - before >= 100000LU)); @@ -85,11 +95,54 @@ static void forbidden_callback(void *arg) } } +static void delayed_callback1(event_t *arg) +{ + order++; + assert(order == 1); + assert(arg == &delayed_event1); + printf("triggered delayed event %p\n", (void *)arg); +} + +static void delayed_callback2(event_t *arg) +{ + order++; + assert(order == 2); + assert(arg == &delayed_event2); + printf("triggered delayed event %p\n", (void *)arg); +} + +static void *claiming_thread(void *arg) +{ + event_queue_t *dq = (event_queue_t *)arg; + + printf("claiming event queue %p\n", (void *)dq); + event_queue_claim(dq); + printf("launching event queue %p\n", (void *)dq); + event_loop(dq); + + return NULL; +} + int main(void) { puts("[START] event test application.\n"); - event_queue_t queue = { .waiter = (thread_t *)sched_active_thread }; + /* test creation of delayed claiming of a detached event queue */ + event_queue_t dq; + printf("initializing detached event queue %p\n", (void *)&dq); + event_queue_init_detached(&dq); + + printf("posting %p\n", (void *)&delayed_event1); + event_post(&dq, &delayed_event1); + printf("posting %p\n", (void *)&delayed_event2); + event_post(&dq, &delayed_event2); + + printf("running thread that will claim event queue %p\n", (void *)&dq); + thread_create(stack, sizeof(stack), PRIO, 0, claiming_thread, &dq, "ct"); + + /* test posting different kind of events in order to a statically + * initialized queue */ + event_queue_t queue = EVENT_QUEUE_INIT; printf("posting 0x%08x\n", (unsigned)&event); event_post(&queue, &event);