diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index e3f305a913..8c7428da2f 100644 --- a/sys/auto_init/auto_init.c +++ b/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(); diff --git a/sys/include/xtimer.h b/sys/include/xtimer.h new file mode 100644 index 0000000000..f772685b64 --- /dev/null +++ b/sys/include/xtimer.h @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2015 Kaspar Schleiser + * + * 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 + */ +#ifndef XTIMER_H +#define XTIMER_H + +#include +#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 */ diff --git a/sys/xtimer/Makefile b/sys/xtimer/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/xtimer/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/xtimer/xtimer.c b/sys/xtimer/xtimer.c new file mode 100644 index 0000000000..29ed50b3f7 --- /dev/null +++ b/sys/xtimer/xtimer.c @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2015 Kaspar Schleiser + * + * 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 + * @} + */ + +#include +#include +#include + +#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; + } +} diff --git a/sys/xtimer/xtimer_core.c b/sys/xtimer/xtimer_core.c new file mode 100644 index 0000000000..d7a57117fe --- /dev/null +++ b/sys/xtimer/xtimer_core.c @@ -0,0 +1,504 @@ +/** + * Copyright (C) 2015 Kaspar Schleiser + * + * 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 + * @} + */ + +#include +#include +#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); +}