You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
534 lines
14 KiB
534 lines
14 KiB
/** |
|
* Copyright (C) 2015 Kaspar Schleiser <kaspar@schleiser.de> |
|
* Copyright (C) 2016 Eistec AB |
|
* |
|
* 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> |
|
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se> |
|
* @} |
|
*/ |
|
|
|
#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 int _in_handler = 0; |
|
|
|
static volatile uint32_t _long_cnt = 0; |
|
#if XTIMER_MASK |
|
volatile uint32_t _xtimer_high_cnt = 0; |
|
#endif |
|
|
|
static inline void xtimer_spin_until(uint32_t value); |
|
|
|
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 void _remove(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(void *arg, 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); |
|
} |
|
|
|
static inline void xtimer_spin_until(uint32_t target) { |
|
#if XTIMER_MASK |
|
target = _xtimer_lltimer_mask(target); |
|
#endif |
|
while (_xtimer_lltimer_now() > target); |
|
while (_xtimer_lltimer_now() < target); |
|
} |
|
|
|
void xtimer_init(void) |
|
{ |
|
/* initialize low-level timer */ |
|
timer_init(XTIMER_DEV, XTIMER_HZ, _periph_timer_callback, NULL); |
|
|
|
/* register initial overflow tick */ |
|
_lltimer_set(0xFFFFFFFF); |
|
} |
|
|
|
static void _xtimer_now_internal(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_now_internal(&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 { |
|
int state = irq_disable(); |
|
if (_is_set(timer)) { |
|
_remove(timer); |
|
} |
|
|
|
_xtimer_now_internal(&timer->target, &timer->long_target); |
|
timer->target += offset; |
|
timer->long_target += long_offset; |
|
if (timer->target < offset) { |
|
timer->long_target++; |
|
} |
|
|
|
_add_timer_to_long_list(&long_list_head, timer); |
|
irq_restore(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().ticks32, _xtimer_lltimer_now()); |
|
if (!timer->callback) { |
|
DEBUG("timer_set(): timer has no callback.\n"); |
|
return; |
|
} |
|
|
|
xtimer_remove(timer); |
|
|
|
if (offset < XTIMER_BACKOFF) { |
|
_xtimer_spin(offset); |
|
_shoot(timer); |
|
} |
|
else { |
|
uint32_t target = _xtimer_now() + offset; |
|
_xtimer_set_absolute(timer, target); |
|
} |
|
} |
|
|
|
static void _periph_timer_callback(void *arg, int chan) |
|
{ |
|
(void)arg; |
|
(void)chan; |
|
_timer_callback(); |
|
} |
|
|
|
static void _shoot(xtimer_t *timer) |
|
{ |
|
timer->callback(timer->arg); |
|
} |
|
|
|
static inline void _lltimer_set(uint32_t target) |
|
{ |
|
if (_in_handler) { |
|
return; |
|
} |
|
DEBUG("_lltimer_set(): setting %" PRIu32 "\n", _xtimer_lltimer_mask(target)); |
|
timer_set_absolute(XTIMER_DEV, XTIMER_CHAN, _xtimer_lltimer_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 + XTIMER_BACKOFF); |
|
_shoot(timer); |
|
return 0; |
|
} |
|
|
|
unsigned state = irq_disable(); |
|
if (_is_set(timer)) { |
|
_remove(timer); |
|
} |
|
|
|
timer->target = target; |
|
timer->long_target = _long_cnt; |
|
if (target < now) { |
|
timer->long_target++; |
|
} |
|
|
|
if ( (timer->long_target > _long_cnt) || !_this_high_period(target) ) { |
|
DEBUG("xtimer_set_absolute(): the timer doesn't fit into the low-level timer's mask.\n"); |
|
_add_timer_to_long_list(&long_list_head, timer); |
|
} |
|
else { |
|
if (_xtimer_lltimer_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); |
|
} |
|
} |
|
} |
|
|
|
irq_restore(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)->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; |
|
} |
|
|
|
static void _remove(xtimer_t *timer) |
|
{ |
|
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 = _xtimer_lltimer_mask(0xFFFFFFFF); |
|
} |
|
_lltimer_set(next); |
|
} |
|
else { |
|
if (!_remove_timer_from_list(&timer_list_head, timer)) { |
|
if (!_remove_timer_from_list(&overflow_list_head, timer)) { |
|
_remove_timer_from_list(&long_list_head, timer); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void xtimer_remove(xtimer_t *timer) |
|
{ |
|
int state = irq_disable(); |
|
if (_is_set(timer)) { |
|
_remove(timer); |
|
} |
|
irq_restore(state); |
|
} |
|
|
|
static uint32_t _time_left(uint32_t target, uint32_t reference) |
|
{ |
|
uint32_t now = _xtimer_lltimer_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) == _xtimer_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 */ |
|
_xtimer_high_cnt += ~XTIMER_MASK + 1; |
|
if (_xtimer_high_cnt == 0) { |
|
/* 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; |
|
|
|
_in_handler = 1; |
|
|
|
DEBUG("_timer_callback() now=%" PRIu32 " (%" PRIu32 ")pleft=%" PRIu32 "\n", |
|
xtimer_now().ticks32, _xtimer_lltimer_mask(xtimer_now().ticks32), |
|
_xtimer_lltimer_mask(0xffffffff - xtimer_now().ticks32)); |
|
|
|
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_lltimer_now() == _xtimer_lltimer_mask(0xFFFFFFFF)); |
|
} |
|
else { |
|
/* we ended up in _timer_callback and there is |
|
* a timer waiting. |
|
*/ |
|
/* set our period reference to the current time. */ |
|
reference = _xtimer_lltimer_now(); |
|
} |
|
|
|
overflow: |
|
/* check if next timers are close to expiring */ |
|
while (timer_list_head && (_time_left(_xtimer_lltimer_mask(timer_list_head->target), reference) < XTIMER_ISR_BACKOFF)) { |
|
/* make sure we don't fire too early */ |
|
while (_time_left(_xtimer_lltimer_mask(timer_list_head->target), reference)); |
|
|
|
/* pick first timer in list */ |
|
xtimer_t *timer = timer_list_head; |
|
|
|
/* advance list */ |
|
timer_list_head = timer->next; |
|
|
|
/* make sure timer is recognized as being already fired */ |
|
timer->target = 0; |
|
timer->long_target = 0; |
|
|
|
/* fire timer */ |
|
_shoot(timer); |
|
} |
|
|
|
/* 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_lltimer_now()) { |
|
DEBUG("_timer_callback: overflowed while executing callbacks. %i\n", |
|
timer_list_head != NULL); |
|
_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; |
|
|
|
/* make sure we're not setting a time in the past */ |
|
if (next_target < (_xtimer_lltimer_now() + XTIMER_ISR_BACKOFF)) { |
|
goto overflow; |
|
} |
|
} |
|
else { |
|
/* there's no timer planned for this timer period */ |
|
/* schedule callback on next overflow */ |
|
next_target = _xtimer_lltimer_mask(0xFFFFFFFF); |
|
uint32_t now = _xtimer_lltimer_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 (_xtimer_lltimer_mask(now + XTIMER_ISR_BACKOFF) < now) { |
|
/* spin until next period, then advance */ |
|
while (_xtimer_lltimer_now() >= now); |
|
_next_period(); |
|
reference = 0; |
|
goto overflow; |
|
} |
|
} |
|
} |
|
|
|
_in_handler = 0; |
|
|
|
/* set low level timer */ |
|
_lltimer_set(next_target); |
|
}
|
|
|