diff --git a/Makefile.dep b/Makefile.dep index 63e48af89a76e219163f2893986ceb2e918c724f..949924ec7281fd9acc0baf8e628b458eb2588d96 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -562,6 +562,10 @@ ifneq (,$(filter phydat,$(USEMODULE))) USEMODULE += fmt endif +ifneq (,$(filter evtimer,$(USEMODULE))) + USEMODULE += xtimer +endif + ifneq (,$(filter random,$(USEMODULE))) # select default prng ifeq (,$(filter prng_%,$(USEMODULE))) diff --git a/sys/evtimer/Makefile b/sys/evtimer/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2 --- /dev/null +++ b/sys/evtimer/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/evtimer/evtimer.c b/sys/evtimer/evtimer.c new file mode 100644 index 0000000000000000000000000000000000000000..4adfd426370326dca83bc67ebe42a97a472126fb --- /dev/null +++ b/sys/evtimer/evtimer.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de> + * 2017 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_evtimer + * @{ + * + * @file + * @brief event timer implementation + * + * @author Kaspar Schleiser <kaspar@schleiser.de> + * @author Martine Lenders <m.lenders@fu-berlin.de> + * + * @} + */ + +#include "div.h" +#include "irq.h" +#include "xtimer.h" + +#include "evtimer.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* XXX this function is intentionally non-static, since the optimizer can't + * handle the pointer hack in this function */ +void evtimer_add_event_to_list(evtimer_t *evtimer, evtimer_event_t *event) +{ + uint32_t delta_sum = 0; + + /* we want list->next to point to the first list element. thus we take the + * *address* of evtimer->events, then cast it from (evtimer_event_t **) to + * (evtimer_event_t*). After that, list->next actually equals + * evtimer->events. */ + evtimer_event_t *list = (evtimer_event_t *)&evtimer->events; + + while (list->next) { + evtimer_event_t *list_entry = list->next; + if ((list_entry->offset + delta_sum) > event->offset) { + break; + } + delta_sum += list_entry->offset; + list = list->next; + } + + event->next = list->next; + if (list->next) { + evtimer_event_t *next_entry = list->next; + next_entry->offset += delta_sum; + next_entry->offset -= event->offset; + } + event->offset -= delta_sum; + + list->next = event; +} + +static void _del_event_from_list(evtimer_t *evtimer, evtimer_event_t *event) +{ + evtimer_event_t *list = (evtimer_event_t *) &evtimer->events; + + while (list->next) { + evtimer_event_t *list_entry = list->next; + if (list_entry == event) { + list->next = event->next; + if (list->next) { + list_entry = list->next; + list_entry->offset += event->offset; + } + break; + } + list = list->next; + } +} + +static void _set_timer(xtimer_t *timer, uint32_t offset) +{ + uint64_t offset_in_us = (uint64_t)offset * 1000; + + DEBUG("evtimer: now=%" PRIu32 " setting xtimer to %" PRIu32 ":%" PRIu32 "\n", + xtimer_now_usec(), (uint32_t)(offset_in_us >> 32), + (uint32_t)(offset_in_us)); + _xtimer_set64(timer, offset_in_us, offset_in_us >> 32); +} + +static void _update_timer(evtimer_t *evtimer) +{ + if (evtimer->events) { + evtimer_event_t *event = evtimer->events; + _set_timer(&evtimer->timer, event->offset); + } + else { + xtimer_remove(&evtimer->timer); + } +} + +static uint32_t _get_offset(xtimer_t *timer) +{ + uint64_t now = xtimer_now_usec64(); + uint64_t target = ((uint64_t)timer->long_target) << 32 | timer->target; + + if (target <= now) { + return 0; + } + else { + target -= now; + /* add half of 125 so integer division rounds to nearest */ + return div_u64_by_125((target >> 3) + 62); + } +} + +static void _update_head_offset(evtimer_t *evtimer) +{ + if (evtimer->events) { + evtimer_event_t *event = evtimer->events; + event->offset = _get_offset(&evtimer->timer); + DEBUG("evtimer: _update_head_offset(): new head offset %" PRIu32 "\n", event->offset); + } +} + +void evtimer_add(evtimer_t *evtimer, evtimer_event_t *event) +{ + unsigned state = irq_disable(); + + DEBUG("evtimer_add(): adding event with offset %" PRIu32 "\n", event->offset); + + _update_head_offset(evtimer); + evtimer_add_event_to_list(evtimer, event); + if (evtimer->events == event) { + _set_timer(&evtimer->timer, event->offset); + } + irq_restore(state); + if (sched_context_switch_request) { + thread_yield_higher(); + } +} + +void evtimer_del(evtimer_t *evtimer, evtimer_event_t *event) +{ + unsigned state = irq_disable(); + + DEBUG("evtimer_del(): removing event with offset %" PRIu32 "\n", event->offset); + + _update_head_offset(evtimer); + _del_event_from_list(evtimer, event); + _update_timer(evtimer); + irq_restore(state); +} + +static evtimer_event_t *_get_next(evtimer_t *evtimer) +{ + evtimer_event_t *event = evtimer->events; + + if (event && (event->offset == 0)) { + evtimer->events = event->next; + return event; + } + else { + return NULL; + } +} + +static void _evtimer_handler(void *arg) +{ + DEBUG("_evtimer_handler()\n"); + + evtimer_t *evtimer = (evtimer_t *)arg; + + /* this function gets called directly by xtimer if the set xtimer expired. + * Thus the offset of the first event is down to zero. */ + evtimer_event_t *event = evtimer->events; + event->offset = 0; + + /* iterate the event list */ + while ((event = _get_next(evtimer))) { + evtimer->callback(event); + } + + _update_timer(evtimer); +} + +void evtimer_init(evtimer_t *evtimer, evtimer_callback_t handler) +{ + evtimer->callback = handler; + evtimer->timer.callback = _evtimer_handler; + evtimer->timer.arg = (void *)evtimer; + evtimer->events = NULL; +} + +void evtimer_print(const evtimer_t *evtimer) +{ + evtimer_event_t *list = evtimer->events; + + while (list->next) { + evtimer_event_t *list_entry = list->next; + printf("ev offset=%u\n", (unsigned)list_entry->offset); + list = list->next; + } +} diff --git a/sys/include/div.h b/sys/include/div.h index 1870e3e2c928e8e851229c0ed719e778394b1e42..456835ee0cae71cf81cfa3de10fd7fdb62e03271 100644 --- a/sys/include/div.h +++ b/sys/include/div.h @@ -74,6 +74,33 @@ static inline uint64_t div_u64_by_15625(uint64_t val) return (val * DIV_H_INV_15625_32) >> (DIV_H_INV_15625_SHIFT + 32); } +/** + * @brief Integer divide val by 125 + * + * This function can be used to convert uint64_t microsecond times (or + * intervals) to miliseconds and store them in uint32_t variables, with up to + * ~50 days worth of miliseconds ((2**32*1000) -1). + * Use e.g., ms = div_u64_by_125(microseconds >> 3) + * + * @pre val <= 536870911999 ((2**32 * 125) -1) + * + * @param[in] val dividend + * @return (val / 125) + */ +static inline uint32_t div_u64_by_125(uint64_t val) +{ + /* a higher value would overflow the result type */ + assert(val <= 536870911999LLU); + + uint32_t hi = val >> 32; + uint32_t lo = val; + uint32_t r = (lo >> 16) + (hi << 16); + uint32_t res = r / 125; + r = ((r % 125) << 16) + (lo & 0xFFFF); + res = (res << 16) + r / 125; + return res; +} + /** * @brief Integer divide val by 1000000 * diff --git a/sys/include/evtimer.h b/sys/include/evtimer.h new file mode 100644 index 0000000000000000000000000000000000000000..da8dc9a1efca7b461bfd6aecf3e8d07fa1a19253 --- /dev/null +++ b/sys/include/evtimer.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de> + * 2017 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_evtimer Millisecond interval event timers + * @ingroup sys + * @brief Provides timers for events up to @$2^{32}@$ milliseconds in the + * future + * + * @note Experimental and likely to replaced with unified timer API + * + * RIOT's main timer subsystem is @ref sys_xtimer "xtimer", but for many + * applications @ref sys_xtimer "xtimer's" 64-bit absolute time values are + * wasteful or clumsy to use. + * + * Compared to @ref sys_xtimer "xtimer", evtimer offers: + * + * - only relative 32-bit millisecond timer values + * Events can be scheduled with a relative offset of up to ~49.7 days in the + * future. + * **For time-critical stuff, use @ref sys_xtimer "xtimer"!** + * - more flexible, "intrusive" timer type @ref evtimer_event_t only contains + * the necessary fields, which can be extended as needed, and handlers define + * actions taken on timer triggers. Check out @ref evtimer_msg_event_t as + * example. + * - uses @ref sys_xtimer "xtimer" as backend + * + * @{ + * + * @file + * @brief evtimer API definitions + * + * @author Kaspar Schleiser <kaspar@schleiser.de> + * @author Martine Lenders <m.lenders@fu-berlin.de> + */ + +#ifndef EVTIMER_H +#define EVTIMER_H + +#include <stdint.h> + +#include "xtimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Generic event + */ +typedef struct evtimer_event { + struct evtimer_event *next; /**< the next event in the queue */ + uint32_t offset; /**< offset in milliseconds from previous event */ +} evtimer_event_t; + +/** + * @brief Event timer callback type + */ +typedef void(*evtimer_callback_t)(evtimer_event_t* event); + +/** + * @brief Event timer + */ +typedef struct { + xtimer_t timer; /**< Timer */ + evtimer_callback_t callback; /**< Handler function for this evtimer's + event type */ + evtimer_event_t *events; /**< Event queue */ +} evtimer_t; + +/** + * @brief Initializes an event timer + * + * @param[in] evtimer An event timer + * @param[in] handler An event handler function + */ +void evtimer_init(evtimer_t *evtimer, evtimer_callback_t handler); + +/** + * @brief Adds event to an event timer + * + * @param[in] evtimer An event timer + * @param[in] event An event + */ +void evtimer_add(evtimer_t *evtimer, evtimer_event_t *event); + +/** + * @brief Removes an event from an event timer + * + * @param[in] evtimer An event timer + * @param[in] event An event + */ +void evtimer_del(evtimer_t *evtimer, evtimer_event_t *event); + +/** + * @brief Print overview of current state of an event timer + * + * @param[in] evtimer An event timer + */ +void evtimer_print(const evtimer_t *evtimer); + +#ifdef __cplusplus +} +#endif + +#endif /* EVTIMER_H */ +/** @} */ diff --git a/sys/include/evtimer_msg.h b/sys/include/evtimer_msg.h new file mode 100644 index 0000000000000000000000000000000000000000..ce15f6c3c65a0a0ab2f79a14bb2e87203ce5f7b9 --- /dev/null +++ b/sys/include/evtimer_msg.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de> + * 2017 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. + */ + +/** + * @addtogroup sys_evtimer + * @{ + * + * @file + * @brief IPC-based evtimer definitions + * + * @author Kaspar Schleiser <kaspar@schleiser.de> + * @author Martine Lenders <m.lenders@fu-berlin.de> + */ +#ifndef EVTIMER_MSG_H +#define EVTIMER_MSG_H + +#include "msg.h" +#include "evtimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief IPC-message event timer + * @extends evtimer_t + */ +typedef evtimer_t evtimer_msg_t; + +/** + * @brief IPC-message event + * @extends evtimer_event_t + */ +typedef struct { + evtimer_event_t event; /**< base class */ + msg_t msg; /**< the IPC message to generate on event */ +} evtimer_msg_event_t; + +/** + * @brief Adds event to an event timer that handles events via IPC + * + * @param[in] evtimer An event timer + * @param[in] event An event + * @param[in] target_pid The PID of the thread that should receive the IPC + * message + */ +static inline void evtimer_add_msg(evtimer_msg_t *evtimer, + evtimer_msg_event_t *event, + kernel_pid_t target_pid) +{ + /* use sender_pid field to get target_pid into callback function */ + event->msg.sender_pid = target_pid; + evtimer_add(evtimer, &event->event); +} + +/** + * @brief Event handler for IPC messages + * + * @param[in] event The event to handle + */ +static inline void _evtimer_msg_handler(evtimer_event_t *event) +{ + evtimer_msg_event_t *mevent = (evtimer_msg_event_t *)event; + msg_send_int(&mevent->msg, mevent->msg.sender_pid); +} + +/** + * @brief Initializes event timer to handle events via IPC + * + * @param[in] evtimer An event timer + */ +static inline void evtimer_init_msg(evtimer_t *evtimer) +{ + evtimer_init(evtimer, _evtimer_msg_handler); +} + +#ifdef __cplusplus +} +#endif + +#endif /* EVTIMER_MSG_H */ +/** @} */ diff --git a/tests/evtimer_msg/Makefile b/tests/evtimer_msg/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..fff087ba1d0444a8cfc27c0eb4ce5d281906cac3 --- /dev/null +++ b/tests/evtimer_msg/Makefile @@ -0,0 +1,13 @@ +APPLICATION = evtimer_msg +include ../Makefile.tests_common + +BOARD_INSUFFICIENT_MEMORY := nucleo32-f031 nucleo32-f042 + +USEMODULE += evtimer + +include $(RIOTBASE)/Makefile.include + +test: +# `testrunner` calls `make term` recursively, results in duplicated `TERMFLAGS`. +# So clears `TERMFLAGS` before run. + TERMFLAGS= tests/01-run.py diff --git a/tests/evtimer_msg/main.c b/tests/evtimer_msg/main.c new file mode 100644 index 0000000000000000000000000000000000000000..febc60de28a335b6b8932a028cbe61576a10975b --- /dev/null +++ b/tests/evtimer_msg/main.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017 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 tests + * @{ + * + * @file + * @brief evtimer_msg test application + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + * + * @} + */ + +#include <stdio.h> + +#include "evtimer_msg.h" +#include "thread.h" +#include "msg.h" +#include "xtimer.h" + +static char worker_stack[THREAD_STACKSIZE_MAIN]; +static evtimer_t evtimer; +static evtimer_msg_event_t events[] = { + { .event = { .offset = 1000 }, .msg = { .content = { .ptr = "supposed to be 1000" } } }, + { .event = { .offset = 1500 }, .msg = { .content = { .ptr = "supposed to be 1500" } } }, + { .event = { .offset = 659 }, .msg = { .content = { .ptr = "supposed to be 659" } } }, + { .event = { .offset = 3954 }, .msg = { .content = { .ptr = "supposed to be 3954" } } }, +}; + +#define NEVENTS ((unsigned)(sizeof(events) / sizeof(evtimer_msg_event_t))) + +/* This thread will print the drift to stdout once per second */ +void *worker_thread(void *arg) +{ + int count = 0; + (void) arg; + + while (1) { + char *ctx; + msg_t m; + uint32_t now; + + msg_receive(&m); + now = xtimer_now_usec() / US_PER_MS; + ctx = m.content.ptr; + printf("At %6" PRIu32 " ms received msg %i: \"%s\"\n", now, count++, ctx); + } +} + +int main(void) +{ + uint32_t now = xtimer_now_usec() / US_PER_MS; + + evtimer_init_msg(&evtimer); + + /* create worker thread */ + kernel_pid_t pid = thread_create(worker_stack, sizeof(worker_stack), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + worker_thread, NULL, "worker"); + printf("Testing generic evtimer (start time = %" PRIu32 " ms)\n", now); + for (unsigned i = 0; i < NEVENTS; i++) { + evtimer_add_msg(&evtimer, &events[i], pid); + } + printf("Are the reception times of all %u msgs close to the supposed values?\n", + NEVENTS); + puts("If yes, the tests were successful"); +} diff --git a/tests/evtimer_msg/tests/01-run.py b/tests/evtimer_msg/tests/01-run.py new file mode 100755 index 0000000000000000000000000000000000000000..721ea7615593d6d02725bc943f65d4a64a0b39a4 --- /dev/null +++ b/tests/evtimer_msg/tests/01-run.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2016 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. + +from __future__ import print_function +import os +import sys +import time + +sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner')) +import testrunner + +ACCEPTED_ERROR = 20 + +def testfunc(child): + child.expect(r"Testing generic evtimer \(start time = (\d+) ms\)") + timer_offset = int(child.match.group(1)) + child.expect(r"Are the reception times of all (\d+) msgs close to the supposed values?") + numof = int(child.match.group(1)) + + for i in range(numof): + child.expect(r'At \s*(\d+) ms received msg %i: "supposed to be (\d+)"' % i) + stop = int(time.time() * 1000) + # check if output is correct + exp = int(child.match.group(2)) + timer_offset + assert(int(child.match.group(1)) in range(exp - ACCEPTED_ERROR, exp + ACCEPTED_ERROR + 1)) + print(".", end="", flush=True) + print("") + print("All tests successful") + +if __name__ == "__main__": + sys.exit(testrunner.run(testfunc, echo=False)) diff --git a/tests/evtimer_underflow/Makefile b/tests/evtimer_underflow/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..fff087ba1d0444a8cfc27c0eb4ce5d281906cac3 --- /dev/null +++ b/tests/evtimer_underflow/Makefile @@ -0,0 +1,13 @@ +APPLICATION = evtimer_msg +include ../Makefile.tests_common + +BOARD_INSUFFICIENT_MEMORY := nucleo32-f031 nucleo32-f042 + +USEMODULE += evtimer + +include $(RIOTBASE)/Makefile.include + +test: +# `testrunner` calls `make term` recursively, results in duplicated `TERMFLAGS`. +# So clears `TERMFLAGS` before run. + TERMFLAGS= tests/01-run.py diff --git a/tests/evtimer_underflow/main.c b/tests/evtimer_underflow/main.c new file mode 100644 index 0000000000000000000000000000000000000000..4c8e008fa413fe6cdb14fdb7892440cd55494fef --- /dev/null +++ b/tests/evtimer_underflow/main.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017 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 tests + * @{ + * + * @file + * @brief evtimer_msg test application + * + * @author Martine Lenders <m.lenders@fu-berlin.de> + * + * @} + */ + +#include <stdio.h> + +#include "evtimer_msg.h" +#include "thread.h" +#include "msg.h" + +#define WORKER_MSG_QUEUE_SIZE (8) + +msg_t worker_msg_queue[WORKER_MSG_QUEUE_SIZE]; +static char worker_stack[THREAD_STACKSIZE_MAIN]; +static evtimer_t evtimer; +static evtimer_msg_event_t events[] = { + { .event = { .offset = 0 }, .msg = { .content = { .ptr = "1" } } }, + { .event = { .offset = 0 }, .msg = { .content = { .ptr = "2" } } }, + { .event = { .offset = 0 }, .msg = { .content = { .ptr = "3" } } }, + { .event = { .offset = 0 }, .msg = { .content = { .ptr = "4" } } }, + { .event = { .offset = 0 }, .msg = { .content = { .ptr = "5" } } }, + { .event = { .offset = 0 }, .msg = { .content = { .ptr = "6" } } }, + { .event = { .offset = 0 }, .msg = { .content = { .ptr = "7" } } }, + { .event = { .offset = 0 }, .msg = { .content = { .ptr = "8" } } }, +}; + +#define NEVENTS (sizeof(events) / sizeof(evtimer_msg_event_t)) + +/* This thread will print the drift to stdout once per second */ +void *worker_thread(void *arg) +{ + (void) arg; + + msg_init_queue(worker_msg_queue, WORKER_MSG_QUEUE_SIZE); + while (1) { + char *ctx; + msg_t m; + + msg_receive(&m); + ctx = m.content.ptr; + printf("received msg \"%s\"\n", ctx); + } +} + +int main(void) +{ + evtimer_init_msg(&evtimer); + + /* create worker thread */ + kernel_pid_t pid = thread_create(worker_stack, sizeof(worker_stack), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + worker_thread, NULL, "worker"); + while (1) { + for (unsigned i = 0; i < NEVENTS; i++) { + evtimer_add_msg(&evtimer, &events[i], pid); + } + } +} diff --git a/tests/evtimer_underflow/tests/01-run.py b/tests/evtimer_underflow/tests/01-run.py new file mode 100755 index 0000000000000000000000000000000000000000..66c324239b0e0675e7059df30167926217aeff25 --- /dev/null +++ b/tests/evtimer_underflow/tests/01-run.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2016 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. + +from __future__ import print_function +import os +import sys +import time + +sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner')) +import testrunner + +how_many = 100 + +def testfunc(child): + for i in range(how_many): + for j in range(8): + child.expect(r'received msg "%i"' % (j + 1)) + print(".", end="", flush=True) + print("") + print("Stopped after %i iterations, but should run forever." % how_many) + print("=> All tests successful") + +if __name__ == "__main__": + sys.exit(testrunner.run(testfunc, echo=False))