Browse Source

sys: xtimer: introduce new timer subsystem

dev/timer
Kaspar Schleiser 7 years ago
parent
commit
808a8bc899
  1. 8
      sys/auto_init/auto_init.c
  2. 453
      sys/include/xtimer.h
  3. 1
      sys/xtimer/Makefile
  4. 165
      sys/xtimer/xtimer.c
  5. 504
      sys/xtimer/xtimer_core.c

8
sys/auto_init/auto_init.c

@ -48,6 +48,10 @@
#include "vtimer.h"
#endif
#ifdef MODULE_XTIMER
#include "xtimer.h"
#endif
#ifdef MODULE_RTC
#include "periph/rtc.h"
#endif
@ -109,6 +113,10 @@ void auto_init(void)
board_uart0_init();
#endif
#endif
#ifdef MODULE_XTIMER
DEBUG("Auto init xtimer module.\n");
xtimer_init();
#endif
#ifdef MODULE_RTC
DEBUG("Auto init rtc module.\n");
rtc_init();

453
sys/include/xtimer.h

@ -0,0 +1,453 @@
/*
* Copyright (C) 2015 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_xtimer Timers
* @ingroup sys
* @brief Provides a high level timer module to register
* timers, get current system time, and let a thread sleep for
* a certain amount of time.
*
* The implementation takes one low-level timer that is supposed to run at 1MHz
* speed and multiplexes it.
*
* Insertion and removal of timers has O(n) complexity with (n) being the
* number of active timers. The reason for this is that multiplexing is
* realized by next-first singly linked lists.
*
* @{
* @file
* @brief xtimer interface definitions
* @author Kaspar Schleiser <kaspar@schleiser.de>
*/
#ifndef XTIMER_H
#define XTIMER_H
#include <stdint.h>
#include "msg.h"
#include "periph/timer.h"
#include "timex.h"
#include "board.h"
#include "periph_conf.h"
/**
* @brief internal define to allow using variables instead of defines
*/
#ifdef XTIMER_TRACE
#include "xtimer_trace.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief xtimer callback type
*/
typedef void (*timer_callback_t)(void*);
/**
* @brief xtimer timer structure
*/
typedef struct xtimer {
struct xtimer *next; /**< reference to next timer in timer lists */
uint32_t target; /**< lower 32bit absolute target time */
uint32_t long_target; /**< upper 32bit absolute target time */
timer_callback_t callback; /**< callback function to call when timer
expires */
void *arg; /**< argument to pass to callback function */
} xtimer_t;
/**
* @brief get the current system time as 32bit microsecond value
*
* @note Overflows every ~71minutes, thus returns xtimer_now64() % 32,
* but is more efficient.
*
* @return current time as 32bit microsecond value
*/
static inline uint32_t xtimer_now(void);
/**
* @brief get the current system time as 64bit microsecond value
*
* @return current time as 64bit microsecond value
*/
uint64_t xtimer_now64(void);
/**
* @brief get the current system time into a timex_t
*
* @param[out] out pointer to timex_t the time will be written to
*/
void xtimer_now_timex(timex_t *out);
/**
* @brief xtimer initialization function
*
* This sets up xtimer. Has to be called once at system boot.
* If @ref auto_init is enabled, it will call this for you.
*/
void xtimer_init(void);
/**
* @brief Stop execution of a thread for some time
*
* When called from an ISR, this function will spin and thus block the MCU in
* interrupt context for the specified amount in *seconds*, so don't *ever* use
* it there.
*
* @param[in] seconds the amount of seconds the thread should sleep
*/
static void xtimer_sleep(uint32_t seconds);
/**
* @brief Stop execution of a thread for some time
*
* When called from an ISR, this function will spin and thus block the MCU for
* the specified amount in microseconds, so only use it there for *very* short
* periods, e.g., less than XTIMER_BACKOFF.
*
* @param[in] microseconds the amount of microseconds the thread should sleep
*/
static void xtimer_usleep(uint32_t microseconds);
/**
* @brief Stop execution of a thread for some time, 64bit version
*
* When called from an ISR, this function will spin and thus block the MCU for
* the specified amount in microseconds, so only use it there for *very* short
* periods, e.g., less then XTIMER_BACKOFF.
*
* @param[in] microseconds the amount of microseconds the thread should sleep
*/
static inline void xtimer_usleep64(uint64_t microseconds);
/**
* @brief Stop execution of a thread for some time
*
* Don't expect nanosecond accuracy. As of now, this function just calls
* xtimer_usleep(nanoseconds/1000).
*
* When called from an ISR, this function will spin-block, so only use it there
* for *very* short periods.
*
* @param[in] nanoseconds the amount of nanoseconds the thread should sleep
*/
static void xtimer_nanosleep(uint32_t nanoseconds);
/**
* @brief Stop execution of a thread for some time, blocking
*
* This function will spin-block, so only use it *very* short periods.
*
* @param[in] microseconds the amount of microseconds the thread should spin
*/
static inline void xtimer_spin(uint32_t microseconds);
/**
* @brief will cause the calling thread to be suspended until the absolute
* time (@p last_wakeup + @p interval).
*
* When the function returns, @p last_wakeup is set to xtimer_now().
*
* This function can be used to create periodic wakeups.
* @c last_wakeup should be set to xtimer_now() before first call of the
* function.
*
* If the result of (@p last_wakeup + usecs) would be in the past, the function
* sets @p last_wakeup to xtimer_now() and returns immediately.
*
* @param[in] last_wakeup base time for the wakeup
* @param[in] usecs time in microseconds that will be added to
* last_wakeup
*/
void xtimer_usleep_until(uint32_t *last_wakeup, uint32_t usecs);
/**
* @brief Set a timer that sends a message
*
* This function sets a timer that will send a message @p offset microseconds
* from now.
*
* The mesage struct specified by msg parameter will not be copied, e.g., it
* needs to point to valid memory until the message has been delivered.
*
* @param[in] timer timer struct to work with
* @param[in] offset microseconds from now
* @param[in] msg ptr to msg that will be sent
* @param[in] target_pid pid the message will be sent to
*/
void xtimer_set_msg(xtimer_t *timer, uint32_t offset, msg_t *msg, kernel_pid_t target_pid);
/**
* @brief Set a timer that sends a message, 64bit version
*
* This function sets a timer that will send a message @p offset microseconds
* from now.
*
* The mesage struct specified by msg parameter will not be copied, e.g., it
* needs to point to valid memory until the message has been delivered.
*
* @param[in] timer timer struct to work with
* @param[in] offset microseconds from now
* @param[in] msg ptr to msg that will be sent
* @param[in] target_pid pid the message will be sent to
*/
void xtimer_set_msg64(xtimer_t *timer, uint64_t offset, msg_t *msg, kernel_pid_t target_pid);
/**
* @brief Set a timer that wakes up a thread
*
* This function sets a timer that will wake up a thread when the timer has
* expired.
*
* @param[in] timer timer struct to work with
* @param[in] offset microseconds from now
* @param[in] pid pid of the thread that will be woken up
*/
void xtimer_set_wakeup(xtimer_t *timer, uint32_t offset, kernel_pid_t pid);
/**
* @brief Set a timer to execute a callback at some time in the future
*
* Expects timer->callback to be set.
*
* The callback specified in the timer struct will be executed @p offset
* microseconds in the future.
*
* @warning BEWARE! Callbacks from xtimer_set() are being executed in interrupt
* context (unless offset < XTIMER_BACKOFF). DON'T USE THIS FUNCTION unless you
* know *exactly* what that means.
*
* @param[in] timer the timer structure to use
* @param[in] offset time in microseconds from now specifying that timer's
* callback's execution time
*/
void xtimer_set(xtimer_t *timer, uint32_t offset);
/**
* @brief remove a timer
*
* @note this function runs in O(n) with n being the number of active timers
*
* @param[in] timer ptr to timer structure that will be removed
*
* @return 1 on success
* @return 0 when timer was not active
*/
int xtimer_remove(xtimer_t *timer);
/**
* @brief receive a message blocking but with timeout
*
* @param[out] msg pointer to a msg_t which will be filled in case of
* no timeout
* @param[in] us timeout in microseconds relative
*
* @return < 0 on error, other value otherwise
*/
int xtimer_msg_receive_timeout(msg_t *msg, uint32_t us);
/**
* @brief receive a message blocking but with timeout, 64bit version
*
* @param[out] msg pointer to a msg_t which will be filled in case of no
* timeout
* @param[in] us timeout in microseconds relative
*
* @return < 0 on error, other value otherwise
*/
int xtimer_msg_receive_timeout64(msg_t *msg, uint64_t us);
/**
* @brief xtimer backoff value
*
* All timers that are less than XTIMER_BACKOFF microseconds in the future will
* just spin.
*
* This is supposed to be defined per-device in e.g., periph_conf.h.
*/
#ifndef XTIMER_BACKOFF
#define XTIMER_BACKOFF 30
#endif
/**
* @brief xtimer overhead value
*
* This value specifies the time a timer will be late if uncorrected, e.g.,
* the system-specific xtimer execution time from timer ISR to executing
* a timer's callback's first instruction.
*
* E.g., with XTIMER_OVERHEAD == 0
* start=xtimer_now();
* xtimer_set(&timer, X);
* (in callback:)
* overhead=xtimer_now()-start-X;
*
* xtimer automatically substracts XTIMER_OVERHEAD from a timer's target time,
* but when the timer triggers, xtimer will spin-lock until a timer's target
* time is reached, so timers will never trigger early.
*
* This is supposed to be defined per-device in e.g., periph_conf.h.
*/
#ifndef XTIMER_OVERHEAD
#define XTIMER_OVERHEAD 20
#endif
#ifndef XTIMER_ISR_BACKOFF
/**
* @brief xtimer isr backoff time
*
* When scheduling the next isr, if it is less than the backoff time
* in the future, just spin.
*
* This is supposed to be defined per-device in e.g., periph_conf.h.
*/
#define XTIMER_ISR_BACKOFF 20
#endif
/**
* @brief set xtimer default timer configuration
* @{
*/
#ifndef XTIMER
#define XTIMER (0)
#define XTIMER_CHAN (0)
#if TIMER_0_MAX_VALUE == 0xffffff
#define XTIMER_MASK 0xff000000
#elif TIMER_0_MAX_VALUE == 0xffff
#define XTIMER_MASK 0xffff0000
#endif
#endif
/**
* @}
*/
#ifndef XTIMER_MASK
/**
* @brief xtimer timer mask
*
* This value specifies the mask relative to 0xffffffff that the used timer
* counts to, e.g., 0xffffffff & ~TIMER_MAXVALUE.
*
* For a 16bit timer, the mask would be 0xFFFF0000, for a 24bit timer, the mask
* would be 0xFF000000. Don't set this for 32bit timers.
*
* This is supposed to be defined per-device in e.g., periph_conf.h.
*/
#define XTIMER_MASK 0
#endif
#ifndef XTIMER_USLEEP_UNTIL_OVERHEAD
/**
* @brief xtimer_usleep_until overhead value
*
* This value specifies the time a xtimer_usleep_until will be late
* if uncorrected.
*
* This is supposed to be defined per-device in e.g., periph_conf.h.
*/
#define XTIMER_USLEEP_UNTIL_OVERHEAD 10
#endif
#if XTIMER_MASK
extern volatile uint32_t _high_cnt;
#endif
/**
* @brief IPC message type for xtimer msg callback
*/
#define MSG_XTIMER 12345
/**
* @brief returns the (masked) low-level timer counter value.
*/
static inline uint32_t _xtimer_now(void)
{
#ifdef XTIMER_SHIFT
return timer_read(XTIMER) << XTIMER_SHIFT;
#else
return timer_read(XTIMER);
#endif
}
/**
* @brief drop bits of a value that don't fit into the low-level timer.
*/
static inline uint32_t _mask(uint32_t val)
{
return val & ~XTIMER_MASK;
}
/**
* @{
* @brief xtimer internal stuff
* @internal
*/
int _xtimer_set_absolute(xtimer_t *timer, uint32_t target);
void _xtimer_set64(xtimer_t *timer, uint32_t offset, uint32_t long_offset);
void _xtimer_sleep(uint32_t offset, uint32_t long_offset);
static inline void xtimer_spin_until(uint32_t value);
/** @} */
static inline uint32_t xtimer_now(void)
{
#if XTIMER_MASK
return _xtimer_now() | _high_cnt;
#else
return _xtimer_now();
#endif
}
static inline void xtimer_spin_until(uint32_t value) {
while (_xtimer_now() > value);
while (_xtimer_now() < value);
}
static inline void xtimer_spin(uint32_t offset) {
offset = _mask(offset + _xtimer_now());
xtimer_spin_until(offset);
}
static inline void xtimer_usleep(uint32_t offset)
{
_xtimer_sleep(offset, 0);
}
static inline void xtimer_usleep64(uint64_t microseconds)
{
_xtimer_sleep((uint32_t) microseconds, (uint32_t) (microseconds >> 32));
}
static inline void xtimer_sleep(uint32_t seconds)
{
xtimer_usleep64((uint64_t)seconds*SEC_IN_USEC);
}
static inline void xtimer_nanosleep(uint32_t nanoseconds)
{
_xtimer_sleep(nanoseconds/1000, 0);
}
/** @} */
#if XTIMER_OVERHEAD + XTIMER_USLEEP_UNTIL_OVERHEAD > XTIMER_BACKOFF
#warning (XTIMER_OVERHEAD + XTIMER_USLEEP_UNTIL_OVERHEAD > XTIMER_BACKOFF !!)
#warning This will lead to underruns. Check if tests/xtimer_usleep_until runs through.
#endif
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* XTIMER_H */

1
sys/xtimer/Makefile

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

165
sys/xtimer/xtimer.c

@ -0,0 +1,165 @@
/**
* Copyright (C) 2015 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 xtimer
* @{
* @file
* @brief xtimer convenience functionality
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @}
*/
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include "xtimer.h"
#include "mutex.h"
#include "thread.h"
#include "irq.h"
#include "timex.h"
#define ENABLE_DEBUG 0
#include "debug.h"
static void _callback_unlock_mutex(void* arg)
{
mutex_t *mutex = (mutex_t *) arg;
mutex_unlock(mutex);
}
void _xtimer_sleep(uint32_t offset, uint32_t long_offset)
{
if (inISR()) {
assert(!long_offset);
xtimer_spin(offset);
}
xtimer_t timer;
mutex_t mutex = MUTEX_INIT;
timer.callback = _callback_unlock_mutex;
timer.arg = (void*) &mutex;
mutex_lock(&mutex);
_xtimer_set64(&timer, offset, long_offset);
mutex_lock(&mutex);
}
void xtimer_usleep_until(uint32_t *last_wakeup, uint32_t interval) {
xtimer_t timer;
mutex_t mutex = MUTEX_INIT;
timer.callback = _callback_unlock_mutex;
timer.arg = (void*) &mutex;
uint32_t target = *last_wakeup + interval;
uint32_t now = xtimer_now();
/* make sure we're not setting a value in the past */
if (now < *last_wakeup) {
/* base timer overflowed */
if (!((target < *last_wakeup) && (target > now))) {
goto out;
}
}
else if (! ((target < *last_wakeup) || (target > now))) {
goto out;
}
uint32_t offset = target - now;
if (offset > XTIMER_BACKOFF+XTIMER_USLEEP_UNTIL_OVERHEAD+1) {
mutex_lock(&mutex);
_xtimer_set_absolute(&timer, target - XTIMER_USLEEP_UNTIL_OVERHEAD);
mutex_lock(&mutex);
}
else {
xtimer_spin_until(target);
}
out:
*last_wakeup = target;
}
static void _callback_msg(void* arg)
{
msg_t *msg = (msg_t*)arg;
msg_send_int(msg, msg->sender_pid);
}
static inline void _setup_msg(xtimer_t *timer, msg_t *msg, kernel_pid_t target_pid)
{
timer->callback = _callback_msg;
timer->arg = (void*) msg;
/* use sender_pid field to get target_pid into callback function */
msg->sender_pid = target_pid;
}
void xtimer_set_msg(xtimer_t *timer, uint32_t offset, msg_t *msg, kernel_pid_t target_pid)
{
_setup_msg(timer, msg, target_pid);
xtimer_set(timer, offset);
}
void xtimer_set_msg64(xtimer_t *timer, uint64_t offset, msg_t *msg, kernel_pid_t target_pid)
{
_setup_msg(timer, msg, target_pid);
_xtimer_set64(timer, offset, offset >> 32);
}
static void _callback_wakeup(void* arg)
{
thread_wakeup((kernel_pid_t)((intptr_t)arg));
}
void xtimer_set_wakeup(xtimer_t *timer, uint32_t offset, kernel_pid_t pid)
{
timer->callback = _callback_wakeup;
timer->arg = (void*) ((intptr_t)pid);
xtimer_set(timer, offset);
}
/**
* see http://www.hackersdelight.org/magic.htm.
* This is to avoid using long integer division functions
* the compiler otherwise links in.
*/
static inline uint64_t _ms_to_sec(uint64_t ms)
{
return (unsigned long long)(ms * 0x431bde83) >> (0x12 + 32);
}
void xtimer_now_timex(timex_t *out)
{
uint64_t now = xtimer_now64();
out->seconds = _ms_to_sec(now);
out->microseconds = now - (out->seconds * SEC_IN_USEC);
}
int xtimer_msg_receive_timeout64(msg_t *m, uint64_t timeout) {
msg_t tmsg;
tmsg.type = MSG_XTIMER;
tmsg.content.ptr = (char *) &tmsg;
xtimer_t t;
xtimer_set_msg64(&t, timeout, &tmsg, sched_active_pid);
msg_receive(m);
if (m->type == MSG_XTIMER && m->content.ptr == (char *) &tmsg) {
/* we hit the timeout */
return -1;
}
else {
xtimer_remove(&t);
return 1;
}
}

504
sys/xtimer/xtimer_core.c

@ -0,0 +1,504 @@
/**
* Copyright (C) 2015 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 xtimer
* @{
* @file
* @brief xtimer core functionality
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @}
*/
#include <stdint.h>
#include <string.h>
#include "board.h"
#include "periph/timer.h"
#include "periph_conf.h"
#include "xtimer.h"
#include "irq.h"
/* WARNING! enabling this will have side effects and can lead to timer underflows. */
#define ENABLE_DEBUG 0
#include "debug.h"
static volatile uint32_t _long_cnt = 0;
#if XTIMER_MASK
volatile uint32_t _high_cnt = 0;
#endif
static xtimer_t *timer_list_head = NULL;
static xtimer_t *overflow_list_head = NULL;
static xtimer_t *long_list_head = NULL;
static void _add_timer_to_list(xtimer_t **list_head, xtimer_t *timer);
static void _add_timer_to_long_list(xtimer_t **list_head, xtimer_t *timer);
static void _shoot(xtimer_t *timer);
static inline void _lltimer_set(uint32_t target);
static uint32_t _time_left(uint32_t target, uint32_t reference);
static void _timer_callback(void);
static void _periph_timer_callback(int chan);
static inline int _this_high_period(uint32_t target);
static inline int _is_set(xtimer_t *timer)
{
return (timer->target || timer->long_target);
}
void xtimer_init(void)
{
/* initialize low-level timer */
timer_init(XTIMER, 1 /* us_per_tick */, _periph_timer_callback);
/* register initial overflow tick */
_lltimer_set(0xFFFFFFFF);
}
static void _xtimer_now64(uint32_t *short_term, uint32_t *long_term)
{
uint32_t before, after, long_value;
/* loop to cope with possible overflow of xtimer_now() */
do {
before = xtimer_now();
long_value = _long_cnt;
after = xtimer_now();
} while(before > after);
*short_term = after;
*long_term = long_value;
}
uint64_t xtimer_now64(void)
{
uint32_t short_term, long_term;
_xtimer_now64(&short_term, &long_term);
return ((uint64_t)long_term<<32) + short_term;
}
void _xtimer_set64(xtimer_t *timer, uint32_t offset, uint32_t long_offset)
{
DEBUG(" _xtimer_set64() offset=%" PRIu32 " long_offset=%" PRIu32 "\n", offset, long_offset);
if (!long_offset) {
/* timer fits into the short timer */
xtimer_set(timer, (uint32_t) offset);
}
else {
xtimer_remove(timer);
_xtimer_now64(&timer->target, &timer->long_target);
timer->target += offset;
timer->long_target += long_offset;
if (timer->target < offset) {
timer->long_target++;
}
int state = disableIRQ();
_add_timer_to_long_list(&long_list_head, timer);
restoreIRQ(state);
DEBUG("xtimer_set64(): added longterm timer (long_target=%" PRIu32 " target=%" PRIu32 ")\n",
timer->long_target, timer->target);
}
}
void xtimer_set(xtimer_t *timer, uint32_t offset)
{
DEBUG("timer_set(): offset=%" PRIu32 " now=%" PRIu32 " (%" PRIu32 ")\n", offset, xtimer_now(), _xtimer_now());
if (!timer->callback) {
DEBUG("timer_set(): timer has no callback.\n");
return;
}
xtimer_remove(timer);
uint32_t target = xtimer_now() + offset;
if (offset < XTIMER_BACKOFF) {
/* spin until timer should be run */
xtimer_spin_until(target);
_shoot(timer);
}
else {
_xtimer_set_absolute(timer, target);
}
}
static void _periph_timer_callback(int chan)
{
(void)chan;
_timer_callback();
}
static void _shoot(xtimer_t *timer)
{
timer->callback(timer->arg);
}
static inline void _lltimer_set(uint32_t target)
{
DEBUG("__lltimer_set(): setting %" PRIu32 "\n", _mask(target));
#ifdef XTIMER_SHIFT
target >>= XTIMER_SHIFT;
if (!target) {
target++;
}
#endif
timer_set_absolute(XTIMER, XTIMER_CHAN, _mask(target));
}
int _xtimer_set_absolute(xtimer_t *timer, uint32_t target)
{
uint32_t now = xtimer_now();
int res = 0;
DEBUG("timer_set_absolute(): now=%" PRIu32 " target=%" PRIu32 "\n", now, target);
timer->next = NULL;
if ((target >= now) && ((target - XTIMER_BACKOFF) < now)) {
/* backoff */
xtimer_spin_until(target);
_shoot(timer);
return 0;
}
timer->target = target;
unsigned state = disableIRQ();
if ( !_this_high_period(target) ) {
DEBUG("xtimer_set_absolute(): the timer doesn't fit into the low-level timer's mask.\n");
timer->long_target = _long_cnt;
_add_timer_to_long_list(&long_list_head, timer);
}
else {
if (!target) {
/* set long_target != 0 so _is_set() can work */
timer->long_target = 1;
}
if (_mask(now) >= target) {
DEBUG("xtimer_set_absolute(): the timer will expire in the next timer period\n");
_add_timer_to_list(&overflow_list_head, timer);
}
else {
DEBUG("timer_set_absolute(): timer will expire in this timer period.\n");
_add_timer_to_list(&timer_list_head, timer);
if (timer_list_head == timer) {
DEBUG("timer_set_absolute(): timer is new list head. updating lltimer.\n");
_lltimer_set(target - XTIMER_OVERHEAD);
}
}
}
restoreIRQ(state);
return res;
}
static void _add_timer_to_list(xtimer_t **list_head, xtimer_t *timer)
{
while (*list_head && (*list_head)->target <= timer->target) {
list_head = &((*list_head)->next);
}
timer->next = *list_head;
*list_head = timer;
}
static void _add_timer_to_long_list(xtimer_t **list_head, xtimer_t *timer)
{
while (*list_head
&& (*list_head)->long_target <= timer->long_target
&& (*list_head)->target <= timer->target) {
list_head = &((*list_head)->next);
}
timer->next = *list_head;
*list_head = timer;
}
static int _remove_timer_from_list(xtimer_t **list_head, xtimer_t *timer)
{
while (*list_head) {
if (*list_head == timer) {
*list_head = timer->next;
return 1;
}
list_head = &((*list_head)->next);
}
return 0;
}
int xtimer_remove(xtimer_t *timer)
{
if (!_is_set(timer)) {
return 0;
}
unsigned state = disableIRQ();
int res = 0;
if (timer_list_head == timer) {
uint32_t next;
timer_list_head = timer->next;
if (timer_list_head) {
/* schedule callback on next timer target time */
next = timer_list_head->target - XTIMER_OVERHEAD;
}
else {
next = _mask(0xFFFFFFFF);
}
_lltimer_set(next);
}
else {
res = _remove_timer_from_list(&timer_list_head, timer) ||
_remove_timer_from_list(&overflow_list_head, timer) ||
_remove_timer_from_list(&long_list_head, timer);
}
timer->target = 0;
timer->long_target = 0;
restoreIRQ(state);
return res;
}
static uint32_t _time_left(uint32_t target, uint32_t reference)
{
uint32_t now = _xtimer_now();
if (now < reference) {
return 0;
}
if (target > now) {
return target - now;
}
else {
return 0;
}
}
static inline int _this_high_period(uint32_t target) {
#if XTIMER_MASK
return (target & XTIMER_MASK) == _high_cnt;
#else
(void)target;
return 1;
#endif
}
/**
* @brief compare two timers' target values, return the one with lower value.
*
* if either is NULL, return the other.
* if both are NULL, return NULL.
*/
static inline xtimer_t *_compare(xtimer_t *a, xtimer_t *b)
{
if (a && b) {
return a->target <= b->target ? a : b;
}
else {
return a ? a : b;
}
}
/**
* @brief merge two timer lists, return head of new list
*/
static xtimer_t *_merge_lists(xtimer_t *head_a, xtimer_t *head_b)
{
xtimer_t *result_head = _compare(head_a, head_b);
xtimer_t *pos = result_head;
while(1) {
head_a = head_a->next;
head_b = head_b->next;
if (!head_a) {
pos->next = head_b;
break;
}
if (!head_b) {
pos->next = head_a;
break;
}
pos->next = _compare(head_a, head_b);
pos = pos->next;
}
return result_head;
}
/**
* @brief parse long timers list and copy those that will expire in the current
* short timer period
*/
static void _select_long_timers(void)
{
xtimer_t *select_list_start = long_list_head;
xtimer_t *select_list_last = NULL;
/* advance long_list head so it points to the first timer of the next (not
* just started) "long timer period" */
while (long_list_head) {
if ((long_list_head->long_target <= _long_cnt) && _this_high_period(long_list_head->target)) {
select_list_last = long_list_head;
long_list_head = long_list_head->next;
}
else {
/* remaining long_list timers belong to later long periods */
break;
}
}
/* cut the "selected long timer list" at the end */
if (select_list_last) {
select_list_last->next = NULL;
}
/* merge "current timer list" and "selected long timer list" */
if (timer_list_head) {
if (select_list_last) {
/* both lists are non-empty. merge. */
timer_list_head = _merge_lists(timer_list_head, select_list_start);
}
else {
/* "selected long timer list" is empty, nothing to do */
}
}
else { /* current timer list is empty */
if (select_list_last) {
/* there's no current timer list, but a non-empty "selected long
* timer list". So just use that list as the new current timer
* list.*/
timer_list_head = select_list_start;
}
}
}
/**
* @brief handle low-level timer overflow, advance to next short timer period
*/
static void _next_period(void)
{
#if XTIMER_MASK
/* advance <32bit mask register */
_high_cnt += ~XTIMER_MASK + 1;
if (! _high_cnt) {
/* high_cnt overflowed, so advance >32bit counter */
_long_cnt++;
}
#else
/* advance >32bit counter */
_long_cnt++;
#endif
/* swap overflow list to current timer list */
timer_list_head = overflow_list_head;
overflow_list_head = NULL;
_select_long_timers();
}
/**
* @brief main xtimer callback function
*/
static void _timer_callback(void)
{
uint32_t next_target;
uint32_t reference;
DEBUG("_timer_callback() now=%" PRIu32 " (%" PRIu32 ")pleft=%" PRIu32 "\n", xtimer_now(),
_mask(xtimer_now()), _mask(0xffffffff-xtimer_now()));
if (!timer_list_head) {
DEBUG("_timer_callback(): tick\n");
/* there's no timer for this timer period,
* so this was a timer overflow callback.
*
* In this case, we advance to the next timer period.
*/
_next_period();
reference = 0;
/* make sure the timer counter also arrived
* in the next timer period */
while (_xtimer_now() == _mask(0xFFFFFFFF));
}
else {
/* we ended up in _timer_callback and there is
* a timer waiting.
*/
/* set our period reference to that timer's target time. */
reference = _mask(timer_list_head->target);
}
overflow:
/* check if next timers are close to expiring */
while (timer_list_head && (_time_left(_mask(timer_list_head->target), reference) < XTIMER_ISR_BACKOFF)) {
/* make sure we don't fire too early */
while (_time_left(_mask(timer_list_head->target), 0));
xtimer_t *next = timer_list_head->next;
_shoot(timer_list_head);
/* advance to next timer in list */
timer_list_head = next;
}
/* possibly executing all callbacks took enough
* time to overflow. In that case we advance to
* next timer period and check again for expired
* timers.*/
if (reference > _xtimer_now()) {
DEBUG("_timer_callback: overflowed while executing callbacks. %i\n", timer_list_head != 0);
_next_period();
reference = 0;
goto overflow;
}
if (timer_list_head) {
/* schedule callback on next timer target time */
next_target = timer_list_head->target - XTIMER_OVERHEAD;
}
else {
/* there's no timer planned for this timer period */
/* schedule callback on next overflow */
next_target = _mask(0xFFFFFFFF);
uint32_t now = _xtimer_now();
/* check for overflow again */
if (now < reference) {
_next_period();
reference = 0;
goto overflow;
}
else {
/* check if the end of this period is very soon */
if (_mask(now + XTIMER_ISR_BACKOFF) < now) {
/* spin until next period, then advance */
while (_xtimer_now() > now);
_next_period();
reference = 0;
goto overflow;
}
}
}
/* set low level timer */
_lltimer_set(next_target);
}
Loading…
Cancel
Save