Browse Source

Merge pull request #6767 from miri64/evtimer/feat/initial

evtimer: initial import
master
Kaspar Schleiser 5 years ago committed by GitHub
parent
commit
031870038b
  1. 4
      Makefile.dep
  2. 1
      sys/evtimer/Makefile
  3. 205
      sys/evtimer/evtimer.c
  4. 27
      sys/include/div.h
  5. 113
      sys/include/evtimer.h
  6. 88
      sys/include/evtimer_msg.h
  7. 13
      tests/evtimer_msg/Makefile
  8. 75
      tests/evtimer_msg/main.c
  9. 36
      tests/evtimer_msg/tests/01-run.py
  10. 13
      tests/evtimer_underflow/Makefile
  11. 75
      tests/evtimer_underflow/main.c
  12. 29
      tests/evtimer_underflow/tests/01-run.py

4
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)))

1
sys/evtimer/Makefile

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

205
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;
}
}

27
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
*

113
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 */
/** @} */

88
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 */
/** @} */

13
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

75
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");
}

36
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))

13
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

75
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);
}
}
}

29
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))
Loading…
Cancel
Save