blob: 3a585f6520b3d2791f69edd04808891ec25774d5 [file] [log] [blame]
Austin Schuh208337d2022-01-01 14:29:11 -08001/*
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7#include <limits.h>
8#include <inttypes.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include "pico.h"
12#include "pico/time.h"
13#include "pico/util/pheap.h"
14#include "pico/sync.h"
15
16const absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(nil_time, 0);
17const absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(at_the_end_of_time, INT64_MAX);
18
19typedef struct alarm_pool_entry {
20 absolute_time_t target;
21 alarm_callback_t callback;
22 void *user_data;
23} alarm_pool_entry_t;
24
25typedef struct alarm_pool {
26 pheap_t *heap;
27 spin_lock_t *lock;
28 alarm_pool_entry_t *entries;
29 // one byte per entry, used to provide more longevity to public IDs than heap node ids do
30 // (this is increment every time the heap node id is re-used)
31 uint8_t *entry_ids_high;
32 alarm_id_t alarm_in_progress; // this is set during a callback from the IRQ handler... it can be cleared by alarm_cancel to prevent repeats
33 uint8_t hardware_alarm_num;
34} alarm_pool_t;
35
36#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
37// To avoid bringing in calloc, we statically allocate the arrays and the heap
38PHEAP_DEFINE_STATIC(default_alarm_pool_heap, PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS);
39static alarm_pool_entry_t default_alarm_pool_entries[PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS];
40static uint8_t default_alarm_pool_entry_ids_high[PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS];
41static lock_core_t sleep_notifier;
42
43static alarm_pool_t default_alarm_pool = {
44 .heap = &default_alarm_pool_heap,
45 .entries = default_alarm_pool_entries,
46 .entry_ids_high = default_alarm_pool_entry_ids_high,
47};
48
49static inline bool default_alarm_pool_initialized(void) {
50 return default_alarm_pool.lock != NULL;
51}
52#endif
53
54static alarm_pool_t *pools[NUM_TIMERS];
55static void alarm_pool_post_alloc_init(alarm_pool_t *pool, uint hardware_alarm_num);
56
57
58static inline alarm_pool_entry_t *get_entry(alarm_pool_t *pool, pheap_node_id_t id) {
59 assert(id && id <= pool->heap->max_nodes);
60 return pool->entries + id - 1;
61}
62
63static inline uint8_t *get_entry_id_high(alarm_pool_t *pool, pheap_node_id_t id) {
64 assert(id && id <= pool->heap->max_nodes);
65 return pool->entry_ids_high + id - 1;
66}
67
68bool timer_pool_entry_comparator(void *user_data, pheap_node_id_t a, pheap_node_id_t b) {
69 alarm_pool_t *pool = (alarm_pool_t *)user_data;
70 return to_us_since_boot(get_entry(pool, a)->target) < to_us_since_boot(get_entry(pool, b)->target);
71}
72
73static inline alarm_id_t make_public_id(uint8_t id_high, pheap_node_id_t id) {
74 return (alarm_id_t)(((uint)id_high << 8u * sizeof(id)) | id);
75}
76
77void alarm_pool_init_default() {
78#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
79 // allow multiple calls for ease of use from host tests
80 if (!default_alarm_pool_initialized()) {
81 ph_post_alloc_init(default_alarm_pool.heap, PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS,
82 timer_pool_entry_comparator, &default_alarm_pool);
83 alarm_pool_post_alloc_init(&default_alarm_pool,
84 PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM);
85 }
86 lock_init(&sleep_notifier, PICO_SPINLOCK_ID_TIMER);
87#endif
88}
89
90#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
91alarm_pool_t *alarm_pool_get_default() {
92 assert(default_alarm_pool_initialized());
93 return &default_alarm_pool;
94}
95#endif
96
97static pheap_node_id_t add_alarm_under_lock(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
98 void *user_data, pheap_node_id_t reuse_id, bool create_if_past, bool *missed) {
99 pheap_node_id_t id;
100 if (reuse_id) {
101 assert(!ph_contains_node(pool->heap, reuse_id));
102 id = reuse_id;
103 } else {
104 id = ph_new_node(pool->heap);
105 }
106 if (id) {
107 alarm_pool_entry_t *entry = get_entry(pool, id);
108 entry->target = time;
109 entry->callback = callback;
110 entry->user_data = user_data;
111 if (id == ph_insert_node(pool->heap, id)) {
112 bool is_missed = hardware_alarm_set_target(pool->hardware_alarm_num, time);
113 if (is_missed && !create_if_past) {
114 ph_remove_and_free_node(pool->heap, id);
115 }
116 if (missed) *missed = is_missed;
117 }
118 }
119 return id;
120}
121
122static void alarm_pool_alarm_callback(uint alarm_num) {
123 // note this is called from timer IRQ handler
124 alarm_pool_t *pool = pools[alarm_num];
125 bool again;
126 do {
127 absolute_time_t now = get_absolute_time();
128 alarm_callback_t callback = NULL;
129 absolute_time_t target = nil_time;
130 void *user_data = NULL;
131 uint8_t id_high;
132 again = false;
133 uint32_t save = spin_lock_blocking(pool->lock);
134 pheap_node_id_t next_id = ph_peek_head(pool->heap);
135 if (next_id) {
136 alarm_pool_entry_t *entry = get_entry(pool, next_id);
137 if (absolute_time_diff_us(now, entry->target) <= 0) {
138 // we don't free the id in case we need to re-add the timer
139 pheap_node_id_t __unused removed_id = ph_remove_head(pool->heap, false);
140 assert(removed_id == next_id); // will be true under lock
141 target = entry->target;
142 callback = entry->callback;
143 user_data = entry->user_data;
144 assert(callback);
145 id_high = *get_entry_id_high(pool, next_id);
146 pool->alarm_in_progress = make_public_id(id_high, removed_id);
147 } else {
148 if (hardware_alarm_set_target(alarm_num, entry->target)) {
149 again = true;
150 }
151 }
152 }
153 spin_unlock(pool->lock, save);
154 if (callback) {
155 int64_t repeat = callback(make_public_id(id_high, next_id), user_data);
156 save = spin_lock_blocking(pool->lock);
157 // todo think more about whether we want to keep calling
158 if (repeat < 0 && pool->alarm_in_progress) {
159 assert(pool->alarm_in_progress == make_public_id(id_high, next_id));
160 add_alarm_under_lock(pool, delayed_by_us(target, (uint64_t)-repeat), callback, user_data, next_id, true, NULL);
161 } else if (repeat > 0 && pool->alarm_in_progress) {
162 assert(pool->alarm_in_progress == make_public_id(id_high, next_id));
163 add_alarm_under_lock(pool, delayed_by_us(get_absolute_time(), (uint64_t)repeat), callback, user_data, next_id,
164 true, NULL);
165 } else {
166 // need to return the id to the heap
167 ph_free_node(pool->heap, next_id);
168 (*get_entry_id_high(pool, next_id))++; // we bump it for next use of id
169 }
170 pool->alarm_in_progress = 0;
171 spin_unlock(pool->lock, save);
172 again = true;
173 }
174 } while (again);
175}
176
177// note the timer is create with IRQs on this core
178alarm_pool_t *alarm_pool_create(uint hardware_alarm_num, uint max_timers) {
179 alarm_pool_t *pool = (alarm_pool_t *) malloc(sizeof(alarm_pool_t));
180 pool->heap = ph_create(max_timers, timer_pool_entry_comparator, pool);
181 pool->entries = (alarm_pool_entry_t *)calloc(max_timers, sizeof(alarm_pool_entry_t));
182 pool->entry_ids_high = (uint8_t *)calloc(max_timers, sizeof(uint8_t));
183 alarm_pool_post_alloc_init(pool, hardware_alarm_num);
184 return pool;
185}
186
187void alarm_pool_post_alloc_init(alarm_pool_t *pool, uint hardware_alarm_num) {
188 hardware_alarm_claim(hardware_alarm_num);
189 hardware_alarm_cancel(hardware_alarm_num);
190 hardware_alarm_set_callback(hardware_alarm_num, alarm_pool_alarm_callback);
191 pool->lock = spin_lock_instance(next_striped_spin_lock_num());
192 pool->hardware_alarm_num = (uint8_t) hardware_alarm_num;
193 pools[hardware_alarm_num] = pool;
194}
195
196void alarm_pool_destroy(alarm_pool_t *pool) {
197#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
198 if (pool == &default_alarm_pool) {
199 assert(false); // attempt to delete default alarm pool
200 return;
201 }
202#endif
203 assert(pools[pool->hardware_alarm_num] == pool);
204 pools[pool->hardware_alarm_num] = NULL;
205 // todo clear out timers
206 ph_destroy(pool->heap);
207 hardware_alarm_set_callback(pool->hardware_alarm_num, NULL);
208 hardware_alarm_unclaim(pool->hardware_alarm_num);
209 free(pool->entry_ids_high);
210 free(pool->entries);
211 free(pool);
212}
213
214alarm_id_t alarm_pool_add_alarm_at(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
215 void *user_data, bool fire_if_past) {
216 bool missed = false;
217
218 alarm_id_t public_id;
219 do {
220 uint8_t id_high = 0;
221 uint32_t save = spin_lock_blocking(pool->lock);
222
223 pheap_node_id_t id = add_alarm_under_lock(pool, time, callback, user_data, 0, false, &missed);
224 if (id) id_high = *get_entry_id_high(pool, id);
225
226 spin_unlock(pool->lock, save);
227
228 if (!id) {
229 // no space in pheap to allocate an alarm
230 return -1;
231 }
232
233 // note that if missed was true, then the id was never added to the pheap (because we
234 // passed false for create_if_past arg above)
235 public_id = missed ? 0 : make_public_id(id_high, id);
236 if (missed && fire_if_past) {
237 // ... so if fire_if_past == true we call the callback
238 int64_t repeat = callback(public_id, user_data);
239 // if not repeated we have no id to return so set public_id to 0,
240 // otherwise we need to repeat, but will assign a new id next time
241 // todo arguably this does mean that the id passed to the first callback may differ from subsequent calls
242 if (!repeat) {
243 public_id = 0;
244 break;
245 } else if (repeat < 0) {
246 time = delayed_by_us(time, (uint64_t)-repeat);
247 } else {
248 time = delayed_by_us(get_absolute_time(), (uint64_t)repeat);
249 }
250 } else {
251 // either:
252 // a) missed == false && public_id is > 0
253 // b) missed == true && fire_if_past == false && public_id = 0
254 // but we are done in either case
255 break;
256 }
257 } while (true);
258 return public_id;
259}
260
261bool alarm_pool_cancel_alarm(alarm_pool_t *pool, alarm_id_t alarm_id) {
262 bool rc = false;
263 uint32_t save = spin_lock_blocking(pool->lock);
264 pheap_node_id_t id = (pheap_node_id_t) alarm_id;
265 if (ph_contains_node(pool->heap, id)) {
266 assert(alarm_id != pool->alarm_in_progress); // it shouldn't be in the heap if it is in progress
267 // check we have the right high value
268 uint8_t id_high = (uint8_t)((uint)alarm_id >> 8u * sizeof(pheap_node_id_t));
269 if (id_high == *get_entry_id_high(pool, id)) {
270 rc = ph_remove_and_free_node(pool->heap, id);
271 // note we don't bother to remove the actual hardware alarm timeout...
272 // it will either do callbacks or not depending on other alarms, and reset the next timeout itself
273 assert(rc);
274 }
275 } else {
276 if (alarm_id == pool->alarm_in_progress) {
277 // make sure the alarm doesn't repeat
278 pool->alarm_in_progress = 0;
279 }
280 }
281 spin_unlock(pool->lock, save);
282 return rc;
283}
284
285uint alarm_pool_hardware_alarm_num(alarm_pool_t *pool) {
286 return pool->hardware_alarm_num;
287}
288
289static void alarm_pool_dump_key(pheap_node_id_t id, void *user_data) {
290 alarm_pool_t *pool = (alarm_pool_t *)user_data;
291#if PICO_ON_DEVICE
292 printf("%lld (hi %02x)", to_us_since_boot(get_entry(pool, id)->target), *get_entry_id_high(pool, id));
293#else
294 printf("%"PRIu64, to_us_since_boot(get_entry(pool, id)->target));
295#endif
296}
297
298static int64_t repeating_timer_callback(__unused alarm_id_t id, void *user_data) {
299 repeating_timer_t *rt = (repeating_timer_t *)user_data;
300 assert(rt->alarm_id == id);
301 if (rt->callback(rt)) {
302 return rt->delay_us;
303 } else {
304 rt->alarm_id = 0;
305 return 0;
306 }
307}
308
309bool alarm_pool_add_repeating_timer_us(alarm_pool_t *pool, int64_t delay_us, repeating_timer_callback_t callback, void *user_data, repeating_timer_t *out) {
310 if (!delay_us) delay_us = 1;
311 out->pool = pool;
312 out->callback = callback;
313 out->delay_us = delay_us;
314 out->user_data = user_data;
315 out->alarm_id = alarm_pool_add_alarm_at(pool, make_timeout_time_us((uint64_t)(delay_us >= 0 ? delay_us : -delay_us)),
316 repeating_timer_callback, out, true);
317 // note that if out->alarm_id is 0, then the callback was called during the above call (fire_if_past == true)
318 // and then the callback removed itself.
319 return out->alarm_id >= 0;
320}
321
322bool cancel_repeating_timer(repeating_timer_t *timer) {
323 bool rc = false;
324 if (timer->alarm_id) {
325 rc = alarm_pool_cancel_alarm(timer->pool, timer->alarm_id);
326 timer->alarm_id = 0;
327 }
328 return rc;
329}
330
331void alarm_pool_dump(alarm_pool_t *pool) {
332 uint32_t save = spin_lock_blocking(pool->lock);
333 ph_dump(pool->heap, alarm_pool_dump_key, pool);
334 spin_unlock(pool->lock, save);
335}
336
337#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
338static int64_t sleep_until_callback(__unused alarm_id_t id, __unused void *user_data) {
339 uint32_t save = spin_lock_blocking(sleep_notifier.spin_lock);
340 lock_internal_spin_unlock_with_notify(&sleep_notifier, save);
341 return 0;
342}
343#endif
344
345void sleep_until(absolute_time_t t) {
346#if PICO_ON_DEVICE && !defined(NDEBUG)
347 if (__get_current_exception()) {
348 panic("Attempted to sleep inside of an exception handler; use busy_wait if you must");
349 }
350#endif
351#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
352 uint64_t t_us = to_us_since_boot(t);
353 uint64_t t_before_us = t_us - PICO_TIME_SLEEP_OVERHEAD_ADJUST_US;
354 // needs to work in the first PICO_TIME_SLEEP_OVERHEAD_ADJUST_US of boot
355 if (t_before_us > t_us) t_before_us = 0;
356 absolute_time_t t_before;
357 update_us_since_boot(&t_before, t_before_us);
358 if (absolute_time_diff_us(get_absolute_time(), t_before) > 0) {
359 if (add_alarm_at(t_before, sleep_until_callback, NULL, false) >= 0) {
360 // able to add alarm for just before the time
361 while (!time_reached(t_before)) {
362 uint32_t save = spin_lock_blocking(sleep_notifier.spin_lock);
363 lock_internal_spin_unlock_with_wait(&sleep_notifier, save);
364 }
365 }
366 }
367#else
368 // hook in case we're in RTOS; note we assume using the alarm pool is better always if available.
369 sync_internal_yield_until_before(t);
370#endif
371 // now wait until the exact time
372 busy_wait_until(t);
373}
374
375void sleep_us(uint64_t us) {
376#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
377 sleep_until(make_timeout_time_us(us));
378#else
379 if (us < PICO_TIME_SLEEP_OVERHEAD_ADJUST_US) {
380 busy_wait_us(us);
381 } else {
382 // hook in case we're in RTOS; note we assume using the alarm pool is better always if available.
383 absolute_time_t t = make_timeout_time_us(us - PICO_TIME_SLEEP_OVERHEAD_ADJUST_US);
384 sync_internal_yield_until_before(t);
385
386 // then wait the rest of thw way
387 busy_wait_until(t);
388 }
389#endif
390}
391
392void sleep_ms(uint32_t ms) {
393 sleep_us(ms * 1000ull);
394}
395
396bool best_effort_wfe_or_timeout(absolute_time_t timeout_timestamp) {
397#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
398 alarm_id_t id;
399 id = add_alarm_at(timeout_timestamp, sleep_until_callback, NULL, false);
400 if (id <= 0) {
401 tight_loop_contents();
402 return time_reached(timeout_timestamp);
403 } else {
404 __wfe();
405 // we need to clean up if it wasn't us that caused the wfe; if it was this will be a noop.
406 cancel_alarm(id);
407 return time_reached(timeout_timestamp);
408 }
409#else
410 tight_loop_contents();
411 return time_reached(timeout_timestamp);
412#endif
413}