1 /*
2  * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #include <stdlib.h>
8 #include "pico.h"
9 #include "pico/time.h"
10 #include "pico/sync.h"
11 #include "pico/runtime_init.h"
12 
13 const absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(nil_time, 0);
14 const absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(at_the_end_of_time, INT64_MAX);
15 
16 typedef struct alarm_pool_entry {
17     // next entry link or -1
18     int16_t next;
19     // low 15 bits are a sequence number used in the low word of the alarm_id so that
20     // the alarm_id for this entry only repeats every 32767 adds (note this value is never zero)
21     // the top bit is a cancellation flag.
22     volatile uint16_t sequence;
23     int64_t target;
24     alarm_callback_t callback;
25     void *user_data;
26 } alarm_pool_entry_t;
27 
28 struct alarm_pool {
29     uint8_t timer_alarm_num;
30     uint8_t core_num;
31     // this is protected by the lock (threads allocate from it, and the IRQ handler adds back to it)
32     int16_t free_head;
33     // this is protected by the lock (threads add to it, the IRQ handler removes from it)
34     volatile int16_t new_head;
35     volatile bool has_pending_cancellations;
36 
37     // this is owned by the IRQ handler so doesn't need additional locking
38     int16_t ordered_head;
39     uint16_t num_entries;
40     alarm_pool_timer_t *timer;
41     spin_lock_t *lock;
42     alarm_pool_entry_t *entries;
43 };
44 
45 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
46 // To avoid bringing in calloc, we statically allocate the arrays and the heap
47 static alarm_pool_entry_t default_alarm_pool_entries[PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS];
48 
49 static alarm_pool_t default_alarm_pool = {
50         .entries = default_alarm_pool_entries,
51 };
52 
default_alarm_pool_initialized(void)53 static inline bool default_alarm_pool_initialized(void) {
54     return default_alarm_pool.lock != NULL;
55 }
56 
57 static lock_core_t sleep_notifier;
58 #endif
59 
60 #include "pico/time_adapter.h"
61 
62 static alarm_pool_t *pools[TA_NUM_TIMERS][TA_NUM_TIMER_ALARMS];
63 
64 static void alarm_pool_post_alloc_init(alarm_pool_t *pool, alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers);
65 
alarm_index(alarm_id_t id)66 static inline int16_t alarm_index(alarm_id_t id) {
67     return (int16_t)(id >> 16);
68 }
69 
alarm_sequence(alarm_id_t id)70 static inline uint16_t alarm_sequence(alarm_id_t id) {
71     return (uint16_t)id;
72 }
73 
make_alarm_id(int index,uint16_t counter)74 static alarm_id_t make_alarm_id(int index, uint16_t counter) {
75     return index << 16 | counter;
76 }
77 
78 #if !PICO_RUNTIME_NO_INIT_DEFAULT_ALARM_POOL
runtime_init_default_alarm_pool(void)79 void __weak runtime_init_default_alarm_pool(void) {
80 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
81     // allow multiple calls for ease of use from host tests
82     if (!default_alarm_pool_initialized()) {
83         alarm_pool_timer_t *timer = alarm_pool_get_default_timer();
84         ta_hardware_alarm_claim(timer, PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM);
85         alarm_pool_post_alloc_init(&default_alarm_pool,
86                                    timer,
87                                    PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM,
88                                    PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS);
89     }
90     lock_init(&sleep_notifier, PICO_SPINLOCK_ID_TIMER);
91 #endif
92 }
93 #endif
94 
alarm_pool_init_default(void)95 void alarm_pool_init_default(void) {
96     runtime_init_default_alarm_pool();
97 }
98 
99 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
alarm_pool_get_default(void)100 alarm_pool_t *alarm_pool_get_default(void) {
101     assert(default_alarm_pool_initialized());
102     return &default_alarm_pool;
103 }
104 
105 #if defined(PICO_RUNTIME_INIT_DEFAULT_ALARM_POOL) && !PICO_RUNTIME_SKIP_INIT_DEFAULT_ALARM_POOL
106 PICO_RUNTIME_INIT_FUNC_RUNTIME(runtime_init_default_alarm_pool, PICO_RUNTIME_INIT_DEFAULT_ALARM_POOL);
107 #endif
108 #endif
109 
110 // note the timer is created with IRQs on this core
alarm_pool_create_on_timer(alarm_pool_timer_t * timer,uint hardware_alarm_num,uint max_timers)111 alarm_pool_t *alarm_pool_create_on_timer(alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers) {
112     alarm_pool_t *pool = (alarm_pool_t *) malloc(sizeof(alarm_pool_t));
113     if (pool) {
114         pool->entries = (alarm_pool_entry_t *) calloc(max_timers, sizeof(alarm_pool_entry_t));
115         ta_hardware_alarm_claim(timer, hardware_alarm_num);
116         alarm_pool_post_alloc_init(pool, timer, hardware_alarm_num, max_timers);
117     }
118     return pool;
119 }
120 
alarm_pool_create_on_timer_with_unused_hardware_alarm(alarm_pool_timer_t * timer,uint max_timers)121 alarm_pool_t *alarm_pool_create_on_timer_with_unused_hardware_alarm(alarm_pool_timer_t *timer, uint max_timers) {
122     alarm_pool_t *pool = (alarm_pool_t *) malloc(sizeof(alarm_pool_t));
123     if (pool) {
124         pool->entries = (alarm_pool_entry_t *) calloc(max_timers, sizeof(alarm_pool_entry_t));
125         alarm_pool_post_alloc_init(pool, timer, (uint) ta_hardware_alarm_claim_unused(timer, true), max_timers);
126     }
127     return pool;
128 }
129 
130 static void alarm_pool_irq_handler(void);
131 
132 // marker which we can use in place of handler function to indicate we are a repeating timer
133 
134 #define repeating_timer_marker ((alarm_callback_t)alarm_pool_irq_handler)
135 #include "hardware/gpio.h"
alarm_pool_irq_handler(void)136 static void alarm_pool_irq_handler(void) {
137     // This IRQ handler does the main work, as it always (assuming the IRQ hasn't been enabled on both cores
138     // which is unsupported) run on the alarm pool's core, and can't be preempted by itself, meaning
139     // that it doesn't need locks except to protect against linked list, or other state access.
140     // This simplifies the code considerably, and makes it much faster in general, even though we are forced to take
141     // two IRQs per alarm.
142     uint timer_alarm_num;
143     alarm_pool_timer_t *timer = ta_from_current_irq(&timer_alarm_num);
144     uint timer_num = ta_timer_num(timer);
145     alarm_pool_t *pool = pools[timer_num][timer_alarm_num];
146     assert(pool->timer_alarm_num == timer_alarm_num);
147     int64_t earliest_target;
148     // 1. clear force bits if we were forced (do this outside the loop, as forcing is hopefully rare)
149     ta_clear_force_irq(timer, timer_alarm_num);
150     do {
151         // 2. clear the IRQ if it was fired
152         ta_clear_irq(timer, timer_alarm_num);
153         // 3. we look at the earliest existing alarm first; the reasoning here is that we
154         //    don't want to delay an existing callback because a later one is added, and
155         //    if both are due now, then we have a race anyway (but we prefer to fire existing
156         //    timers before new ones anyway.
157         int16_t earliest_index = pool->ordered_head;
158         // by default, we loop if there was any event pending (we will mark it false
159         // later if there is no work to do)
160         if (earliest_index >= 0) {
161             alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index];
162             earliest_target = earliest_entry->target;
163             if (((int64_t)ta_time_us_64(timer) - earliest_target) >= 0) {
164                 // time to call the callback now (or in the past)
165                 // note that an entry->target of < 0 means the entry has been canceled (not this is set
166                 // by this function, in response to the entry having been queued by the cancel_alarm API
167                 // meaning that we don't need to worry about tearing of the 64 bit value)
168                 int64_t delta;
169                 if (earliest_target >= 0) {
170                     // special case repeating timer without making another function call which adds overhead
171                     if (earliest_entry->callback == repeating_timer_marker) {
172                         repeating_timer_t *rpt = (repeating_timer_t *)earliest_entry->user_data;
173                         delta = rpt->callback(rpt) ? rpt->delay_us : 0;
174                     } else {
175                         alarm_id_t id = make_alarm_id(pool->ordered_head, earliest_entry->sequence);
176                         delta = earliest_entry->callback(id, earliest_entry->user_data);
177                     }
178                 } else {
179                     // negative target means cancel alarm
180                     delta = 0;
181                 }
182                 if (delta) {
183                     int64_t next_time;
184                     if (delta < 0) {
185                         // delta is (positive) delta from last fire time
186                         next_time = earliest_target - delta;
187                     } else {
188                         // delta is relative to now
189                         next_time = (int64_t) ta_time_us_64(timer) + delta;
190                     }
191                     earliest_entry->target = next_time;
192                     // need to re-add, unless we are the only entry or already at the front
193                     if (earliest_entry->next >= 0 && next_time - pool->entries[earliest_entry->next].target >= 0) {
194                         // unlink this item
195                         pool->ordered_head = earliest_entry->next;
196                         int16_t *prev = &pool->ordered_head;
197                         // find insertion point; note >= as if we add a new item for the same time as another, then it follows
198                         while (*prev >= 0 && (next_time - pool->entries[*prev].target) >= 0) {
199                             prev = &pool->entries[*prev].next;
200                         }
201                         earliest_entry->next = *prev;
202                         *prev = earliest_index;
203                     }
204                 } else {
205                     // need to remove the item
206                     pool->ordered_head = earliest_entry->next;
207                     // and add it back to the free list (under lock)
208                     uint32_t save = spin_lock_blocking(pool->lock);
209                     earliest_entry->next = pool->free_head;
210                     pool->free_head = earliest_index;
211                     spin_unlock(pool->lock, save);
212                 }
213             }
214         }
215         // if we have any new alarms, add them to the ordered list
216         if (pool->new_head >= 0) {
217             uint32_t save = spin_lock_blocking(pool->lock);
218             // must re-read new head under lock
219             int16_t new_index = pool->new_head;
220             // clear the list
221             pool->new_head = -1;
222             spin_unlock(pool->lock, save);
223             // insert each of the new items
224             while (new_index >= 0) {
225                 alarm_pool_entry_t *new_entry = &pool->entries[new_index];
226                 int64_t new_entry_time = new_entry->target;
227                 int16_t *prev = &pool->ordered_head;
228                 // find insertion point; note >= as if we add a new item for the same time as another, then it follows
229                 while (*prev >= 0 && (new_entry_time - pool->entries[*prev].target) >= 0) {
230                     prev = &pool->entries[*prev].next;
231                 }
232                 int16_t next = *prev;
233                 *prev = new_index;
234                 new_index = new_entry->next;
235                 new_entry->next = next;
236             }
237         }
238         // if we have any canceled alarms, then mark them for removal by setting their due time to -1 (which will
239         // cause them to be handled the next time round and removed)
240         if (pool->has_pending_cancellations) {
241             pool->has_pending_cancellations = false;
242             __compiler_memory_barrier();
243             int16_t *prev = &pool->ordered_head;
244             // set target for canceled items to -1, and move to front of the list
245             for(int16_t index = pool->ordered_head; index != -1; ) {
246                 alarm_pool_entry_t *entry = &pool->entries[index];
247                 int16_t next = entry->next;
248                 if ((int16_t)entry->sequence < 0) {
249                     // mark for deletion
250                     entry->target = -1;
251                     if (index != pool->ordered_head) {
252                         // move to start of queue
253                         *prev = entry->next;
254                         entry->next = pool->ordered_head;
255                         pool->ordered_head = index;
256                     }
257                 } else {
258                     prev = &entry->next;
259                 }
260                 index = next;
261             }
262         }
263         earliest_index = pool->ordered_head;
264         if (earliest_index < 0) break;
265         // need to wait
266         alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index];
267         earliest_target = earliest_entry->target;
268         // we are leaving a timeout every 2^32 microseconds anyway if there is no valid target, so we can choose any value.
269         // best_effort_wfe_or_timeout now relies on it being the last value set, and arguably this is the
270         // best value anyway, as it is the furthest away from the last fire.
271         if (earliest_target != -1) { // cancelled alarm has target of -1
272             ta_set_timeout(timer, timer_alarm_num, earliest_target);
273         }
274         // check we haven't now passed the target time; if not we don't want to loop again
275     } while ((earliest_target - (int64_t)ta_time_us_64(timer)) <= 0);
276     // We always want the timer IRQ to wake a WFE so that best_effort_wfe_or_timeout() will wake up. It will wake
277     // a WFE on its own core by nature of having taken an IRQ, but we do an explicit SEV so it wakes the other core
278     __sev();
279 }
280 
alarm_pool_post_alloc_init(alarm_pool_t * pool,alarm_pool_timer_t * timer,uint hardware_alarm_num,uint max_timers)281 void alarm_pool_post_alloc_init(alarm_pool_t *pool, alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers) {
282     pool->timer = timer;
283     pool->lock = spin_lock_instance(next_striped_spin_lock_num());
284     pool->timer_alarm_num = (uint8_t) hardware_alarm_num;
285     invalid_params_if(PICO_TIME, max_timers > 65536);
286     pool->num_entries = (uint16_t)max_timers;
287     pool->core_num = (uint8_t) get_core_num();
288     pool->new_head = pool->ordered_head = -1;
289     pool->free_head = (int16_t)(max_timers - 1);
290     for(uint i=0;i<max_timers;i++) {
291         pool->entries[i].next = (int16_t)(i-1);
292     }
293     pools[ta_timer_num(timer)][hardware_alarm_num] = pool;
294 
295     ta_enable_irq_handler(timer, hardware_alarm_num, alarm_pool_irq_handler);
296 }
297 
alarm_pool_destroy(alarm_pool_t * pool)298 void alarm_pool_destroy(alarm_pool_t *pool) {
299 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
300     if (pool == &default_alarm_pool) {
301         assert(false); // attempt to delete default alarm pool
302         return;
303     }
304 #endif
305     ta_disable_irq_handler(pool->timer, pool->timer_alarm_num, alarm_pool_irq_handler);
306     assert(pools[ta_timer_num(pool->timer)][pool->timer_alarm_num] == pool);
307     pools[ta_timer_num(pool->timer)][pool->timer_alarm_num] = NULL;
308     free(pool->entries);
309     free(pool);
310 }
311 
alarm_pool_add_alarm_at(alarm_pool_t * pool,absolute_time_t time,alarm_callback_t callback,void * user_data,bool fire_if_past)312 alarm_id_t alarm_pool_add_alarm_at(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
313                                    void *user_data, bool fire_if_past) {
314     if (!fire_if_past) {
315         absolute_time_t t = get_absolute_time();
316         if (absolute_time_diff_us(t, time) < 0) return 0;
317     }
318     return alarm_pool_add_alarm_at_force_in_context(pool, time, callback, user_data);
319 }
320 
alarm_pool_add_alarm_at_force_in_context(alarm_pool_t * pool,absolute_time_t time,alarm_callback_t callback,void * user_data)321 alarm_id_t alarm_pool_add_alarm_at_force_in_context(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
322                                                     void *user_data) {
323     // ---- take a free pool entry
324     uint32_t save = spin_lock_blocking(pool->lock);
325     int16_t index = pool->free_head;
326     alarm_pool_entry_t *entry = &pool->entries[index];
327     if (index >= 0) {
328         // remove from free list
329         pool->free_head = entry->next;
330     }
331     spin_unlock(pool->lock, save);
332     if (index < 0) return PICO_ERROR_GENERIC; // PICO_ERROR_INSUFFICIENT_RESOURCES - not using to preserve previous -1 return code
333 
334     // ---- initialize the pool entry
335     entry->callback = callback;
336     entry->user_data = user_data;
337     entry->target = (int64_t)to_us_since_boot(time);
338     uint16_t next_sequence = (entry->sequence + 1) & 0x7fff;
339     if (!next_sequence) next_sequence = 1; // zero is not allowed
340     entry->sequence = next_sequence;
341     alarm_id_t id = make_alarm_id(index, next_sequence);
342 
343     // ---- and add it to the new list
344     save = spin_lock_blocking(pool->lock);
345     entry->next = pool->new_head;
346     pool->new_head = index;
347     spin_unlock(pool->lock, save);
348 
349     // force the IRQ
350     ta_force_irq(pool->timer, pool->timer_alarm_num);
351     return id;
352 }
353 
alarm_pool_cancel_alarm(alarm_pool_t * pool,alarm_id_t alarm_id)354 bool alarm_pool_cancel_alarm(alarm_pool_t *pool, alarm_id_t alarm_id) {
355     int16_t index = alarm_index(alarm_id);
356     if (index >= pool->num_entries) return false;
357     uint16_t sequence = alarm_sequence(alarm_id);
358     bool canceled = false;
359     alarm_pool_entry_t *entry = &pool->entries[index];
360     uint32_t save = spin_lock_blocking(pool->lock);
361     // note this will not be true if the entry is already canceled (as the entry->sequence
362     // will have the top bit set)
363     uint current_sequence = entry->sequence;
364     if (sequence == current_sequence) {
365         entry->sequence = (uint16_t)(current_sequence | 0x8000);
366         __compiler_memory_barrier();
367         pool->has_pending_cancellations = true;
368         canceled = true;
369     }
370     spin_unlock(pool->lock, save);
371     // force the IRQ if we need to clean up an alarm id
372     if (canceled) ta_force_irq(pool->timer, pool->timer_alarm_num);
373     return canceled;
374 }
375 
alarm_pool_timer_alarm_num(alarm_pool_t * pool)376 uint alarm_pool_timer_alarm_num(alarm_pool_t *pool) {
377     return pool->timer_alarm_num;
378 }
379 
alarm_pool_core_num(alarm_pool_t * pool)380 uint alarm_pool_core_num(alarm_pool_t *pool) {
381     return pool->core_num;
382 }
383 
384 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
sleep_until_callback(__unused alarm_id_t id,__unused void * user_data)385 static int64_t sleep_until_callback(__unused alarm_id_t id, __unused void *user_data) {
386     uint32_t save = spin_lock_blocking(sleep_notifier.spin_lock);
387     lock_internal_spin_unlock_with_notify(&sleep_notifier, save);
388     return 0;
389 }
390 #endif
391 
sleep_until(absolute_time_t t)392 void sleep_until(absolute_time_t t) {
393 #if PICO_ON_DEVICE && !defined(NDEBUG)
394     if (__get_current_exception()) {
395         panic("Attempted to sleep inside of an exception handler; use busy_wait if you must");
396     }
397 #endif
398 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
399     uint64_t t_us = to_us_since_boot(t);
400     uint64_t t_before_us = t_us - PICO_TIME_SLEEP_OVERHEAD_ADJUST_US;
401     // needs to work in the first PICO_TIME_SLEEP_OVERHEAD_ADJUST_US of boot
402     if (t_before_us > t_us) t_before_us = 0;
403     absolute_time_t t_before;
404     update_us_since_boot(&t_before, t_before_us);
405     if (absolute_time_diff_us(get_absolute_time(), t_before) > 0) {
406         if (add_alarm_at(t_before, sleep_until_callback, NULL, false) >= 0) {
407             // able to add alarm for just before the time
408             while (!time_reached(t_before)) {
409                 uint32_t save = spin_lock_blocking(sleep_notifier.spin_lock);
410                 lock_internal_spin_unlock_with_wait(&sleep_notifier, save);
411             }
412         }
413     }
414 #else
415     // hook in case we're in RTOS; note we assume using the alarm pool is better always if available.
416     sync_internal_yield_until_before(t);
417 #endif
418     // now wait until the exact time
419     busy_wait_until(t);
420 }
421 
sleep_us(uint64_t us)422 void sleep_us(uint64_t us) {
423 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
424     sleep_until(make_timeout_time_us(us));
425 #else
426     if (us < PICO_TIME_SLEEP_OVERHEAD_ADJUST_US) {
427         busy_wait_us(us);
428     } else {
429         // hook in case we're in RTOS; note we assume using the alarm pool is better always if available.
430         absolute_time_t t = make_timeout_time_us(us - PICO_TIME_SLEEP_OVERHEAD_ADJUST_US);
431         sync_internal_yield_until_before(t);
432 
433         // then wait the rest of the way
434         busy_wait_until(t);
435     }
436 #endif
437 }
438 
sleep_ms(uint32_t ms)439 void sleep_ms(uint32_t ms) {
440     sleep_us(ms * 1000ull);
441 }
442 
best_effort_wfe_or_timeout(absolute_time_t timeout_timestamp)443 bool best_effort_wfe_or_timeout(absolute_time_t timeout_timestamp) {
444 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
445     if (__get_current_exception()) {
446         tight_loop_contents();
447         return time_reached(timeout_timestamp);
448     } else {
449         alarm_id_t id;
450         // note that as of SDK 2.0.0 calling add_alarm_at always causes a SEV. What we really
451         // want to do is cause an IRQ at the specified time in the future if there is not
452         // an IRQ already happening before then. The problem is that the IRQ may be happening on the
453         // other core, so taking an IRQ is the only way to get the state protection.
454         //
455         // Therefore, we make a compromise; we will set the alarm, if we won't wake up before the right time
456         // already. This means that repeated calls to this function with the same timeout will work correctly
457         // after the first one! This is fine, because we ask callers to use a polling loop on another
458         // event variable when using this function.
459         //
460         // For this to work, we require that once we have set an alarm, an SEV happens no later than that, even
461         // if we cancel the alarm as we do below. Therefore, the IRQ handler (which is always enabled) will
462         // never set its wakeup time to a later value, but instead wake up once and then wake up again.
463         //
464         // This overhead when canceling alarms is a small price to pay for the much simpler/faster/cleaner
465         // implementation that relies on the IRQ handler (on a single core) being the only state accessor.
466         //
467         // Note also, that the use of software spin locks on RP2350 to access state would always cause a SEV
468         // due to use of LDREX etc., so actually using spin locks to protect the state would be worse.
469         if (ta_wakes_up_on_or_before(alarm_pool_get_default()->timer, alarm_pool_get_default()->timer_alarm_num,
470                                      (int64_t)to_us_since_boot(timeout_timestamp))) {
471             // we already are waking up at or before when we want to (possibly due to us having been called
472             // before in a loop), so we can do an actual WFE. Note we rely on the fact that the alarm pool IRQ
473             // handler always does an explicit SEV, since it may be on the other core.
474             __wfe();
475             return time_reached(timeout_timestamp);
476         } else {
477             id = add_alarm_at(timeout_timestamp, sleep_until_callback, NULL, false);
478             if (id <= 0) {
479                 tight_loop_contents();
480                 return time_reached(timeout_timestamp);
481             } else {
482                 if (!time_reached(timeout_timestamp)) {
483                     // ^ at the point above the timer hadn't fired, so it is safe
484                     // to wait; the event will happen due to IRQ at some point between
485                     // then and the correct wakeup time
486                     __wfe();
487                 }
488                 // we need to clean up if it wasn't us that caused the wfe; if it was this will be a noop.
489                 cancel_alarm(id);
490                 return time_reached(timeout_timestamp);
491             }
492         }
493     }
494 #else
495     tight_loop_contents();
496     return time_reached(timeout_timestamp);
497 #endif
498 }
499 
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)500 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) {
501     if (!delay_us) delay_us = 1;
502     out->pool = pool;
503     out->callback = callback;
504     out->delay_us = delay_us;
505     out->user_data = user_data;
506     out->alarm_id = alarm_pool_add_alarm_at(pool, make_timeout_time_us((uint64_t)(delay_us >= 0 ? delay_us : -delay_us)),
507                                             repeating_timer_marker, out, true);
508     return out->alarm_id > 0;
509 }
510 
cancel_repeating_timer(repeating_timer_t * timer)511 bool cancel_repeating_timer(repeating_timer_t *timer) {
512     bool rc = false;
513     if (timer->alarm_id) {
514         rc = alarm_pool_cancel_alarm(timer->pool, timer->alarm_id);
515         timer->alarm_id = 0;
516     }
517     return rc;
518 }
519 
alarm_pool_timer_for_timer_num(uint timer_num)520 alarm_pool_timer_t *alarm_pool_timer_for_timer_num(uint timer_num) {
521     return ta_timer_instance(timer_num);
522 }
523 
alarm_pool_get_default_timer(void)524 alarm_pool_timer_t *alarm_pool_get_default_timer(void) {
525     return ta_default_timer_instance();
526 }
527 
alarm_pool_remaining_alarm_time_us(alarm_pool_t * pool,alarm_id_t alarm_id)528 int64_t alarm_pool_remaining_alarm_time_us(alarm_pool_t *pool, alarm_id_t alarm_id) {
529     // note there is no point distinguishing between invalid alarm_id and timer passed,
530     // since an alarm_id that has fired without being re-enabled becomes logically invalid after
531     // that point anyway
532     int64_t rc = -1;
533     int16_t index = alarm_index(alarm_id);
534     if ((uint16_t)index < pool->num_entries) {
535         uint16_t sequence = alarm_sequence(alarm_id);
536         alarm_pool_entry_t *entry = &pool->entries[index];
537         if (entry->sequence == sequence) {
538             uint32_t save = spin_lock_blocking(pool->lock);
539             int16_t search_index = pool->ordered_head;
540             while (search_index >= 0) {
541                 entry = &pool->entries[search_index];
542                 if (index == search_index) {
543                     if (entry->sequence == sequence) {
544                         rc = entry->target - (int64_t) ta_time_us_64(pool->timer);
545                     }
546                     break;
547                 }
548                 search_index = entry->next;
549             }
550             spin_unlock(pool->lock, save);
551         }
552     }
553     return rc;
554 }
555 
alarm_pool_remaining_alarm_time_ms(alarm_pool_t * pool,alarm_id_t alarm_id)556 int32_t alarm_pool_remaining_alarm_time_ms(alarm_pool_t *pool, alarm_id_t alarm_id) {
557     int64_t rc = alarm_pool_remaining_alarm_time_us(pool, alarm_id);
558     if (rc >= 0) rc /= 1000;
559     return rc >= INT32_MAX ? INT32_MAX : (int32_t) rc;
560 }
561 
562 #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
remaining_alarm_time_us(alarm_id_t alarm_id)563 int64_t remaining_alarm_time_us(alarm_id_t alarm_id) {
564     return alarm_pool_remaining_alarm_time_us(alarm_pool_get_default(), alarm_id);
565 }
566 
remaining_alarm_time_ms(alarm_id_t alarm_id)567 int32_t remaining_alarm_time_ms(alarm_id_t alarm_id) {
568     return alarm_pool_remaining_alarm_time_ms(alarm_pool_get_default(), alarm_id);
569 }
570 #endif