-
Notifications
You must be signed in to change notification settings - Fork 2k
/
xtimer_core.c
346 lines (301 loc) · 9.5 KB
/
xtimer_core.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/**
* Copyright (C) 2015 Kaspar Schleiser <kaspar@schleiser.de>
* 2016 Eistec AB
* 2018 Josua Arndt
* 2018 UC Berkeley
*
* 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_xtimer
*
* @{
* @file
* @brief xtimer core functionality
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
* @author Josua Arndt <jarndt@ias.rwth-aachen.de>
* @author Hyung-Sin Kim <hs.kim@cs.berkeley.edu>
* @}
*/
#include <assert.h>
#include <stdint.h>
#include <string.h>
#ifndef MODULE_XTIMER_ON_ZTIMER
#include "board.h"
#include "periph/timer.h"
#include "periph_conf.h"
#endif
#include "assert.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;
volatile uint64_t _xtimer_current_time = 0;
static xtimer_t *timer_list_head = NULL;
static xtimer_t *long_list_head = NULL;
static bool _lltimer_ongoing = false;
static void _add_timer_to_list(xtimer_t **list_head, xtimer_t *timer, uint32_t now);
static void _shoot(xtimer_t *timer);
static inline void _update_short_timers(uint64_t *now);
static inline void _update_long_timers(uint64_t *now);
static inline void _schedule_earliest_lltimer(uint32_t now);
static void _timer_callback(void);
#ifndef MODULE_XTIMER_ON_ZTIMER
static void _periph_timer_callback(void *arg, int chan);
#else
static void _ztimer_callback(void *arg);
static ztimer_t _ztimer = { .callback=_ztimer_callback };
#endif
void xtimer_init(void)
{
#ifndef MODULE_XTIMER_ON_ZTIMER
/* initialize low-level timer */
int ret = timer_init(XTIMER_DEV, XTIMER_HZ, _periph_timer_callback, NULL);
(void)ret;
assert(ret == 0);
#endif
/* register initial overflow tick */
_schedule_earliest_lltimer(_xtimer_now());
}
uint32_t _xtimer_now(void)
{
return (uint32_t) _xtimer_now64();
}
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 (!timer->callback) {
DEBUG("_xtimer_set64(): timer has no callback.\n");
return;
}
xtimer_remove(timer);
if (!long_offset && offset < XTIMER_BACKOFF) {
/* timer fits into the short timer */
_xtimer_spin(offset);
_shoot(timer);
return;
}
/* time sensitive */
unsigned int state = irq_disable();
uint64_t now = _xtimer_now64();
timer->offset = offset;
timer->long_offset = long_offset;
timer->start_time = (uint32_t)now;
timer->long_start_time = (uint32_t)(now >> 32);
if (!long_offset) {
_add_timer_to_list(&timer_list_head, timer, (uint32_t)now);
if (timer_list_head == timer) {
DEBUG("_xtimer_set64(): timer is new list head. updating lltimer.\n");
_schedule_earliest_lltimer((uint32_t)now);
}
}
else {
_add_timer_to_list(&long_list_head, timer, (uint32_t)now);
DEBUG("_xtimer_set64(): added longterm timer.\n");
}
irq_restore(state);
}
#ifndef MODULE_XTIMER_ON_ZTIMER
static void _periph_timer_callback(void *arg, int chan)
{
(void)arg;
(void)chan;
_timer_callback();
}
#else
static void _ztimer_callback(void *arg)
{
(void)arg;
_timer_callback();
}
#endif
static void _shoot(xtimer_t *timer)
{
timer->callback(timer->arg);
}
static inline void _schedule_earliest_lltimer(uint32_t now)
{
uint32_t target;
if (_in_handler) {
return;
}
if (timer_list_head && timer_list_head->offset <= (_xtimer_lltimer_mask(0xFFFFFFFF)>>1)) {
/* schedule lltimer on next timer target time */
target = timer_list_head->start_time + timer_list_head->offset;
}
else if (!_lltimer_ongoing) {
/* schedule lltimer after max_low_level_time/2 to detect a cycle */
target = now + (_xtimer_lltimer_mask(0xFFFFFFFF)>>1);
}
else {
/* lltimer is already running */
return;
}
DEBUG("_schedule_earliest_lltimer(): setting %" PRIu32 "\n", _xtimer_lltimer_mask(target));
#ifndef MODULE_XTIMER_ON_ZTIMER
timer_set_absolute(XTIMER_DEV, XTIMER_CHAN, _xtimer_lltimer_mask(target));
#else
ztimer_set(ZTIMER_USEC, &_ztimer, target - ztimer_now(ZTIMER_USEC));
#endif
_lltimer_ongoing = true;
}
/**
* @brief compare two timers. return true if timerA expires earlier than or
* equal to timerB and false otherwise.
*/
static bool _timer_comparison(xtimer_t* timerA, xtimer_t* timerB, uint32_t now)
{
if (timerA->long_offset < timerB->long_offset) {
return true;
}
if (timerA->long_offset == timerB->long_offset) {
uint32_t elapsedA = now - timerA->start_time;
uint32_t elapsedB = now - timerB->start_time;
/* it is necessary to compare two offsets, instead of two absolute times
* two conditions: (1) timerA was already expired
* (2) timerA will expire earlier than or equal to timerB
*/
if (timerA->offset < elapsedA || timerA->offset - elapsedA <= timerB->offset - elapsedB) {
return true;
}
}
return false;
}
/**
* @brief add a timer to an ordered list of timers
*/
static void _add_timer_to_list(xtimer_t **list_head, xtimer_t *timer, uint32_t now)
{
while (*list_head && _timer_comparison((*list_head), timer, now)) {
list_head = &((*list_head)->next);
}
timer->next = *list_head;
*list_head = timer;
}
/**
* @brief remove a timer from an ordered list of timers
*/
static void _remove_timer_from_list(xtimer_t **list_head, xtimer_t *timer)
{
while (*list_head) {
if (*list_head == timer) {
*list_head = timer->next;
timer->next = NULL;
return;
}
list_head = &((*list_head)->next);
}
}
void xtimer_remove(xtimer_t *timer)
{
/* time sensitive since the target timer can be fired */
unsigned int state = irq_disable();
timer->offset = 0;
timer->long_offset = 0;
timer->start_time = 0;
timer->long_start_time = 0;
_remove_timer_from_list(&timer_list_head, timer);
_remove_timer_from_list(&long_list_head, timer);
irq_restore(state);
}
/**
* @brief update long timers' offsets and switch those that will expire in
* one short timer period to the short timer list
*/
static inline void _update_long_timers(uint64_t *now)
{
xtimer_t *timer = long_list_head;
while (timer) {
uint32_t elapsed = (uint32_t)*now - timer->start_time;
if (timer->offset < elapsed) {
timer->long_offset--;
}
timer->offset -= elapsed;
timer->start_time = (uint32_t)*now;
timer->long_start_time = (uint32_t)(*now >> 32);
if (!timer->long_offset) {
assert(timer == long_list_head);
_remove_timer_from_list(&long_list_head, timer);
_add_timer_to_list(&timer_list_head, timer, (uint32_t)*now);
timer = long_list_head;
}
else {
timer = timer->next;
}
}
}
/**
* @brief update short timers' offsets and fire those that are close to expiry
*/
static inline void _update_short_timers(uint64_t *now)
{
xtimer_t *timer = timer_list_head;
while (timer) {
assert(!timer->long_offset);
uint32_t elapsed = (uint32_t)*now - timer->start_time;
if (timer->offset < elapsed || timer->offset - elapsed < XTIMER_ISR_BACKOFF) {
assert(timer == timer_list_head);
/* make sure we don't fire too early */
if (timer->offset > elapsed) {
while (_xtimer_now() - timer->start_time < timer->offset) {}
}
/* advance list */
timer_list_head = timer->next;
/* make sure timer is recognized as being already fired */
timer->offset = 0;
timer->start_time = 0;
timer->long_start_time = 0;
timer->next = NULL;
/* fire timer */
_shoot(timer);
/* assign new head */
timer = timer_list_head;
/* update current_time */
*now = _xtimer_now64();
}
else {
timer->offset -= elapsed;
timer->start_time = (uint32_t)*now;
timer->long_start_time = (uint32_t)(*now >> 32);
timer = timer->next;
}
}
}
/**
* @brief main xtimer callback function (called in an interrupt context)
*/
static void _timer_callback(void)
{
uint64_t now;
_in_handler = 1;
_lltimer_ongoing = false;
now = _xtimer_now64();
update:
/* update short timer offset and fire */
_update_short_timers(&now);
/* update long timer offset */
_update_long_timers(&now);
/* update current time */
now = _xtimer_now64();
if (timer_list_head) {
/* make sure we're not setting a time in the past */
uint32_t elapsed = (uint32_t)now - timer_list_head->start_time;
if (timer_list_head->offset < elapsed ||
timer_list_head->offset - elapsed < XTIMER_ISR_BACKOFF) {
goto update;
}
else {
timer_list_head->offset -= elapsed;
timer_list_head->start_time = (uint32_t)now;
timer_list_head->long_start_time = (uint32_t)(now >> 32);
}
}
_in_handler = 0;
/* set low level timer */
_schedule_earliest_lltimer((uint32_t)now);
}