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