| /* |
| * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <stdlib.h> |
| #include "pico.h" |
| #include "pico/time.h" |
| #include "pico/sync.h" |
| #include "pico/runtime_init.h" |
| |
| const absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(nil_time, 0); |
| const absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(at_the_end_of_time, INT64_MAX); |
| |
| // If cooperative lock/sleep routines are overridden (e.g. for RTOS integration), use them. This |
| // lets an RTOS schedule out sleeping tasks for active ones. Otherwise use SEV/WFE directly, to |
| // allow a core to sleep until a timer IRQ fires (including when the IRQ is on a different core). |
| #ifndef PICO_TIME_USE_SLEEP_NOTIFIER |
| #if LOCK_INTERNAL_SPIN_UNLOCK_WITH_WAIT_OVERRIDDEN | LOCK_INTERNAL_SPIN_UNLOCK_WITH_NOTIFY_OVERRIDDEN | LOCK_INTERNAL_SPIN_UNLOCK_WITH_BEST_EFFORT_WAIT_OR_TIMEOUT_OVERRIDDEN |
| #define PICO_TIME_USE_SLEEP_NOTIFIER 1 |
| #endif |
| #endif |
| |
| typedef struct alarm_pool_entry { |
| // next entry link or -1 |
| int16_t next; |
| // low 15 bits are a sequence number used in the low word of the alarm_id so that |
| // the alarm_id for this entry only repeats every 32767 adds (note this value is never zero) |
| // the top bit is a cancellation flag. |
| volatile uint16_t sequence; |
| int64_t target; |
| alarm_callback_t callback; |
| void *user_data; |
| } alarm_pool_entry_t; |
| |
| struct alarm_pool { |
| uint8_t timer_alarm_num; |
| uint8_t core_num; |
| // this is protected by the lock (threads allocate from it, and the IRQ handler adds back to it) |
| int16_t free_head; |
| // this is protected by the lock (threads add to it, the IRQ handler removes from it) |
| volatile int16_t new_head; |
| volatile bool has_pending_cancellations; |
| |
| // this is owned by the IRQ handler so doesn't need additional locking |
| int16_t ordered_head; |
| uint16_t num_entries; |
| alarm_pool_timer_t *timer; |
| spin_lock_t *lock; |
| alarm_pool_entry_t *entries; |
| }; |
| |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| // To avoid bringing in calloc, we statically allocate the arrays and the heap |
| static alarm_pool_entry_t default_alarm_pool_entries[PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS]; |
| |
| static alarm_pool_t default_alarm_pool = { |
| .entries = default_alarm_pool_entries, |
| }; |
| |
| static inline bool default_alarm_pool_initialized(void) { |
| return default_alarm_pool.lock != NULL; |
| } |
| |
| #if PICO_TIME_USE_SLEEP_NOTIFIER |
| static lock_core_t sleep_notifier; |
| #endif |
| #endif |
| |
| #include "pico/time_adapter.h" |
| |
| static alarm_pool_t *pools[TA_NUM_TIMERS][TA_NUM_TIMER_ALARMS]; |
| |
| static void alarm_pool_post_alloc_init(alarm_pool_t *pool, alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers); |
| |
| static inline int16_t alarm_index(alarm_id_t id) { |
| return (int16_t)(id >> 16); |
| } |
| |
| static inline uint16_t alarm_sequence(alarm_id_t id) { |
| return (uint16_t)id; |
| } |
| |
| static alarm_id_t make_alarm_id(int index, uint16_t counter) { |
| return index << 16 | counter; |
| } |
| |
| #if !PICO_RUNTIME_NO_INIT_DEFAULT_ALARM_POOL |
| void __weak runtime_init_default_alarm_pool(void) { |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| // allow multiple calls for ease of use from host tests |
| if (!default_alarm_pool_initialized()) { |
| alarm_pool_timer_t *timer = alarm_pool_get_default_timer(); |
| ta_hardware_alarm_claim(timer, PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM); |
| alarm_pool_post_alloc_init(&default_alarm_pool, |
| timer, |
| PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM, |
| PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS); |
| } |
| #if PICO_TIME_USE_SLEEP_NOTIFIER |
| lock_init(&sleep_notifier, PICO_SPINLOCK_ID_TIMER); |
| #endif |
| #endif |
| } |
| #endif |
| |
| void alarm_pool_init_default(void) { |
| runtime_init_default_alarm_pool(); |
| } |
| |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| alarm_pool_t *alarm_pool_get_default(void) { |
| assert(default_alarm_pool_initialized()); |
| return &default_alarm_pool; |
| } |
| |
| #if defined(PICO_RUNTIME_INIT_DEFAULT_ALARM_POOL) && !PICO_RUNTIME_SKIP_INIT_DEFAULT_ALARM_POOL |
| PICO_RUNTIME_INIT_FUNC_RUNTIME(runtime_init_default_alarm_pool, PICO_RUNTIME_INIT_DEFAULT_ALARM_POOL); |
| #endif |
| #endif |
| |
| // note the timer is created with IRQs on this core |
| alarm_pool_t *alarm_pool_create_on_timer(alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers) { |
| alarm_pool_t *pool = (alarm_pool_t *) malloc(sizeof(alarm_pool_t)); |
| if (pool) { |
| pool->entries = (alarm_pool_entry_t *) calloc(max_timers, sizeof(alarm_pool_entry_t)); |
| if (pool->entries) { |
| ta_hardware_alarm_claim(timer, hardware_alarm_num); |
| alarm_pool_post_alloc_init(pool, timer, hardware_alarm_num, max_timers); |
| } else { |
| free(pool); |
| pool = NULL; |
| } |
| } |
| return pool; |
| } |
| |
| alarm_pool_t *alarm_pool_create_on_timer_with_unused_hardware_alarm(alarm_pool_timer_t *timer, uint max_timers) { |
| alarm_pool_t *pool = (alarm_pool_t *) malloc(sizeof(alarm_pool_t)); |
| if (pool) { |
| pool->entries = (alarm_pool_entry_t *) calloc(max_timers, sizeof(alarm_pool_entry_t)); |
| if (pool->entries) { |
| alarm_pool_post_alloc_init(pool, timer, (uint) ta_hardware_alarm_claim_unused(timer, true), max_timers); |
| } else { |
| free(pool); |
| pool = NULL; |
| } |
| } |
| return pool; |
| } |
| |
| static void alarm_pool_irq_handler(void); |
| |
| // marker which we can use in place of handler function to indicate we are a repeating timer |
| |
| #define repeating_timer_marker ((alarm_callback_t)(uintptr_t)2) |
| |
| static void alarm_pool_irq_handler(void) { |
| // This IRQ handler is the main engine for the timer code. It runs on the core that created the alarm pool. |
| // |
| // It is fired: |
| // |
| // i. by the timer alarm h/w |
| // ii. forced by the timer code when an alarm is added or removed. |
| // |
| // The IRQ handler is responsible for maintaining an ordered list of alarms, and configuring the next |
| // hardware alarm via the time_adapter (ta_ functions). Because this IRQ handler runs on a single core |
| // and isn't re-entrant, we don't need locks within the handler. Locks _are_ however needed to walk or modify |
| // the "free" or "new" alarm lists which are also touched by clients. |
| // |
| // This layout has proved to be (somewhat) easier to reason about. |
| // |
| // As a further wrinkle, this IRQ handler maintains an invariant that: |
| // |
| // once an alarm is added, an event (SEV) will be fired either on or before that time, even if the |
| // alarm is subsequently canceled. This invariant is relied on by best_effort_wfe_or_timeout() (for |
| // details see comment there) |
| |
| // Figure out correct alarm_pool instance |
| uint timer_alarm_num; |
| alarm_pool_timer_t *timer = ta_from_current_irq(&timer_alarm_num); |
| uint timer_num = ta_timer_num(timer); |
| alarm_pool_t *pool = pools[timer_num][timer_alarm_num]; |
| assert(pool->timer_alarm_num == timer_alarm_num); |
| |
| // Clear any forced irq from a client before processing any add/cancel requests from the client code, which |
| // adds the requests before forcing the IRQ to make sure we don't race |
| ta_clear_force_irq(timer, timer_alarm_num); |
| |
| int64_t earliest_target; |
| // This loop (more than one pass) really just serves the purpose of avoiding having to leave and re-enter the IRQ |
| // if the next alarm is already due. We might call it a "logical" IRQ, or "due" wakeup |
| // |
| // Note that cancellations are applied at the end of a pass, after the hardware has been armed, |
| // so a canceled alarm still contributes the timeout it was added with (see the invariant above) |
| do { |
| // 1. Clear the hardware alarm if any, as we're servicing it, and will re-configure the alarm later |
| ta_clear_irq(timer, timer_alarm_num); |
| |
| // 2. Look at the earliest existing alarm before dealing with anything else. If two alarms are due, we have |
| // a race anyway, so the ordering is somewhat arbitrary, and this way we do as little work as possible |
| // before firing an alarm |
| |
| int16_t earliest_index = pool->ordered_head; |
| if (earliest_index >= 0) { |
| // We have an existing alarm ... |
| alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index]; |
| earliest_target = earliest_entry->target; |
| |
| if (((int64_t)ta_time_us_64(timer) - earliest_target) >= 0) { |
| // ... which is due ... |
| int64_t delta; |
| // ... so fire it and figure the delta to the next timeout |
| if (earliest_entry->callback == repeating_timer_marker) { |
| // Special case repeating timer without making another function call which adds overhead |
| repeating_timer_t *rpt = (repeating_timer_t *)earliest_entry->user_data; |
| delta = rpt->callback(rpt) ? rpt->delay_us : 0; |
| } else { |
| alarm_id_t id = make_alarm_id(pool->ordered_head, earliest_entry->sequence); |
| delta = earliest_entry->callback(id, earliest_entry->user_data); |
| } |
| if (delta) { |
| // Non-zero delta means the entry should be kept to fire again |
| int64_t next_time; |
| if (delta < 0) { |
| // delta is (positive) delta from last fire time |
| next_time = earliest_target - delta; |
| } else { |
| // delta is relative to now |
| next_time = (int64_t) ta_time_us_64(timer) + delta; |
| } |
| // Record the new timeout |
| earliest_entry->target = next_time; |
| |
| // Move to the right point in the ordered list, which is a no-op if we're the only item, |
| // or belong first anyway. |
| if (earliest_entry->next >= 0 && next_time - pool->entries[earliest_entry->next].target >= 0) { |
| // Unlink this item |
| pool->ordered_head = earliest_entry->next; |
| int16_t *prev = &pool->ordered_head; |
| // Find insertion point; note >= as if we add a new item for the same time as another, then it follows |
| while (*prev >= 0 && (next_time - pool->entries[*prev].target) >= 0) { |
| prev = &pool->entries[*prev].next; |
| } |
| earliest_entry->next = *prev; |
| *prev = earliest_index; |
| } |
| } else { |
| // delta == 0, means we need to remove the item (now rather than with the cancellations below |
| // as, having fired, its existence has no bearing on the future timeout) |
| pool->ordered_head = earliest_entry->next; |
| // We must take the lock to modify the free list |
| uint32_t save = spin_lock_blocking(pool->lock); |
| earliest_entry->next = pool->free_head; |
| pool->free_head = earliest_index; |
| spin_unlock(pool->lock, save); |
| } |
| } |
| } |
| // 3. If we have any new alarms, add them to the ordered list in the correct place |
| if (pool->new_head >= 0) { |
| uint32_t save = spin_lock_blocking(pool->lock); |
| // must re-read new head under lock |
| int16_t new_index = pool->new_head; |
| // clear the list |
| pool->new_head = -1; |
| spin_unlock(pool->lock, save); |
| // insert each of the new items |
| while (new_index >= 0) { |
| alarm_pool_entry_t *new_entry = &pool->entries[new_index]; |
| int64_t new_entry_time = new_entry->target; |
| int16_t *prev = &pool->ordered_head; |
| // find insertion point; note >= as if we add a new item for the same time as another, then it follows |
| while (*prev >= 0 && (new_entry_time - pool->entries[*prev].target) >= 0) { |
| prev = &pool->entries[*prev].next; |
| } |
| int16_t next = *prev; |
| *prev = new_index; |
| new_index = new_entry->next; |
| new_entry->next = next; |
| } |
| } |
| |
| // Now the ordered list is correctly sorted and includes all the alarms including any recently cancelled ones |
| |
| // 4. Look at the next alarm |
| earliest_index = pool->ordered_head; |
| if (earliest_index < 0) { |
| // If there is none, set a dummy timeout. This maintains the invariant that there is always |
| // a pending underlying ta_ alarm, such that you can ask the hardware (ta_) "when is the next alarm?", |
| // and get a sensible answer (useful in best_effort_wfe_or_timeout()). |
| // |
| // Since at this point we have nothing to schedule, uphold this invariant by setting a dummy alarm |
| // in the far future. We use half of 32-bit range, to minimise risk of wrapping. Note also |
| // that the default ta_wakes_up_on_or_before using the 32 bit hardware timer alarms requires that |
| // the next timeout be within the next 2^32 us anyway. |
| ta_set_timeout(timer, timer_alarm_num, (int64_t)ta_time_us_64(timer) + (1u << 31)); |
| break; |
| } |
| |
| // 5. We actually have an item at the head of the ordered list |
| alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index]; |
| earliest_target = earliest_entry->target; |
| |
| // 6. Reconfigure the time adapter (hardware) for the next up time |
| ta_set_timeout(timer, timer_alarm_num, earliest_target); |
| |
| // 7. Finally remove any pending cancellations now that they (can have) contributed |
| // to the timeout configured above. |
| if (pool->has_pending_cancellations) { |
| pool->has_pending_cancellations = false; |
| __compiler_memory_barrier(); |
| int16_t *prev = &pool->ordered_head; |
| for(int16_t index = pool->ordered_head; index != -1; ) { |
| alarm_pool_entry_t *entry = &pool->entries[index]; |
| int16_t next = entry->next; |
| // A pending cancellation is marked by a sequence number with bit 15 set |
| if ((int16_t)entry->sequence < 0) { |
| // Unlink... |
| *prev = entry->next; |
| // ... and add it back to the free list (under lock) |
| uint32_t save = spin_lock_blocking(pool->lock); |
| entry->next = pool->free_head; |
| pool->free_head = index; |
| spin_unlock(pool->lock, save); |
| } else { |
| prev = &entry->next; |
| } |
| index = next; |
| } |
| } |
| // We will loop if there is another alarm due already |
| } while ((earliest_target - (int64_t)ta_time_us_64(timer)) <= 0); |
| // We always want the timer IRQ to wake a WFE so that best_effort_wfe_or_timeout() will wake up. It will wake |
| // 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 |
| // |
| // Note that it seems tempting to add a __sev() inside the loop, but its not yet clear it has any provable |
| // benefit (and it could only help the other core since our core is pre-empted by thie IRQ), and it defintitely |
| // would cause more wakes to actual waiters on the other core. |
| __sev(); |
| } |
| |
| void alarm_pool_post_alloc_init(alarm_pool_t *pool, alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers) { |
| pool->timer = timer; |
| pool->lock = spin_lock_instance(next_striped_spin_lock_num()); |
| pool->timer_alarm_num = (uint8_t) hardware_alarm_num; |
| invalid_params_if(PICO_TIME, max_timers > 65536); |
| pool->num_entries = (uint16_t)max_timers; |
| pool->core_num = (uint8_t) get_core_num(); |
| pool->new_head = pool->ordered_head = -1; |
| pool->free_head = (int16_t)(max_timers - 1); |
| for(uint i=0;i<max_timers;i++) { |
| pool->entries[i].next = (int16_t)(i-1); |
| } |
| pools[ta_timer_num(timer)][hardware_alarm_num] = pool; |
| |
| ta_enable_irq_handler(timer, hardware_alarm_num, alarm_pool_irq_handler); |
| } |
| |
| void alarm_pool_destroy(alarm_pool_t *pool) { |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| if (pool == &default_alarm_pool) { |
| assert(false); // attempt to delete default alarm pool |
| return; |
| } |
| #endif |
| assert(pool->core_num == get_core_num()); |
| ta_disable_irq_handler(pool->timer, pool->timer_alarm_num, alarm_pool_irq_handler); |
| ta_hardware_alarm_unclaim(pool->timer, pool->timer_alarm_num); |
| assert(pools[ta_timer_num(pool->timer)][pool->timer_alarm_num] == pool); |
| pools[ta_timer_num(pool->timer)][pool->timer_alarm_num] = NULL; |
| free(pool->entries); |
| free(pool); |
| } |
| |
| alarm_id_t alarm_pool_add_alarm_at(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback, |
| void *user_data, bool fire_if_past) { |
| if (!fire_if_past) { |
| absolute_time_t t = get_absolute_time(); |
| if (absolute_time_diff_us(t, time) < 0) return 0; |
| } |
| return alarm_pool_add_alarm_at_force_in_context(pool, time, callback, user_data); |
| } |
| |
| alarm_id_t alarm_pool_add_alarm_at_force_in_context(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback, |
| void *user_data) { |
| // ---- take a free pool entry |
| uint32_t save = spin_lock_blocking(pool->lock); |
| int16_t index = pool->free_head; |
| alarm_pool_entry_t *entry = &pool->entries[index]; |
| if (index >= 0) { |
| // remove from free list |
| pool->free_head = entry->next; |
| } |
| spin_unlock(pool->lock, save); |
| if (index < 0) return PICO_ERROR_GENERIC; // PICO_ERROR_INSUFFICIENT_RESOURCES - not using to preserve previous -1 return code |
| |
| // ---- initialize the pool entry |
| entry->callback = callback; |
| entry->user_data = user_data; |
| entry->target = (int64_t)to_us_since_boot(time); |
| uint16_t next_sequence = (entry->sequence + 1) & 0x7fff; |
| if (!next_sequence) next_sequence = 1; // zero is not allowed |
| entry->sequence = next_sequence; |
| alarm_id_t id = make_alarm_id(index, next_sequence); |
| |
| // ---- and add it to the new list |
| save = spin_lock_blocking(pool->lock); |
| entry->next = pool->new_head; |
| pool->new_head = index; |
| spin_unlock(pool->lock, save); |
| |
| // force the IRQ |
| ta_force_irq(pool->timer, pool->timer_alarm_num); |
| return id; |
| } |
| |
| bool alarm_pool_cancel_alarm(alarm_pool_t *pool, alarm_id_t alarm_id) { |
| int16_t index = alarm_index(alarm_id); |
| if (index >= pool->num_entries) return false; |
| uint16_t sequence = alarm_sequence(alarm_id); |
| bool canceled = false; |
| alarm_pool_entry_t *entry = &pool->entries[index]; |
| uint32_t save = spin_lock_blocking(pool->lock); |
| // note this will not be true if the entry is already canceled (as the entry->sequence |
| // will have the top bit set) |
| uint current_sequence = entry->sequence; |
| if (sequence == current_sequence) { |
| entry->sequence = (uint16_t)(current_sequence | 0x8000); |
| __compiler_memory_barrier(); |
| pool->has_pending_cancellations = true; |
| canceled = true; |
| } |
| spin_unlock(pool->lock, save); |
| // force the IRQ if we need to clean up an alarm id |
| if (canceled) ta_force_irq(pool->timer, pool->timer_alarm_num); |
| return canceled; |
| } |
| |
| uint alarm_pool_timer_alarm_num(alarm_pool_t *pool) { |
| return pool->timer_alarm_num; |
| } |
| |
| uint alarm_pool_core_num(alarm_pool_t *pool) { |
| return pool->core_num; |
| } |
| |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| static int64_t sleep_until_callback(__unused alarm_id_t id, __unused void *user_data) { |
| #if PICO_TIME_USE_SLEEP_NOTIFIER |
| uint32_t save = spin_lock_blocking(sleep_notifier.spin_lock); |
| lock_internal_spin_unlock_with_notify(&sleep_notifier, save); |
| #else |
| // note this implementation is copied in pico_sync_test.c and should be updated if this code is |
| __sev(); // signal event in case the waiter is on the other core |
| #endif |
| return 0; |
| } |
| #endif |
| |
| void sleep_until(absolute_time_t t) { |
| #if PICO_ON_DEVICE && !defined(NDEBUG) |
| if (__get_current_exception()) { |
| panic("Attempted to sleep inside of an exception handler; use busy_wait if you must"); |
| } |
| #endif |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| uint64_t t_us = to_us_since_boot(t); |
| uint64_t t_before_us = t_us - PICO_TIME_SLEEP_OVERHEAD_ADJUST_US; |
| // needs to work in the first PICO_TIME_SLEEP_OVERHEAD_ADJUST_US of boot |
| if (t_before_us > t_us) t_before_us = 0; |
| absolute_time_t t_before; |
| update_us_since_boot(&t_before, t_before_us); |
| if (absolute_time_diff_us(get_absolute_time(), t_before) > 0) { |
| if (add_alarm_at(t_before, sleep_until_callback, NULL, false) >= 0) { |
| // able to add alarm for just before the time |
| while (!time_reached(t_before)) { |
| #if PICO_TIME_USE_SLEEP_NOTIFIER |
| uint32_t save = spin_lock_blocking(sleep_notifier.spin_lock); |
| lock_internal_spin_unlock_with_wait(&sleep_notifier, save); |
| #else |
| // note __wfe() is sufficient here because the add_alarm always causes an IRQ which calls |
| // sleep_until_callback() which also does a __sev() - the irq itself will wake us up if on the same core |
| __wfe(); |
| #endif |
| blocked_waiter_wakeup(false); |
| } |
| } |
| } |
| #else |
| // hook in case we're in RTOS; note we assume using the alarm pool is better always if available. |
| sync_internal_yield_until_before(t); |
| #endif |
| // now wait until the exact time |
| busy_wait_until(t); |
| } |
| |
| void sleep_us(uint64_t us) { |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| sleep_until(make_timeout_time_us(us)); |
| #else |
| if (us < PICO_TIME_SLEEP_OVERHEAD_ADJUST_US) { |
| busy_wait_us(us); |
| } else { |
| // hook in case we're in RTOS; note we assume using the alarm pool is better always if available. |
| absolute_time_t t = make_timeout_time_us(us - PICO_TIME_SLEEP_OVERHEAD_ADJUST_US); |
| sync_internal_yield_until_before(t); |
| |
| // then wait the rest of the way |
| busy_wait_until(t); |
| } |
| #endif |
| } |
| |
| void sleep_ms(uint32_t ms) { |
| sleep_us(ms * 1000ull); |
| } |
| |
| bool best_effort_wfe_or_timeout(absolute_time_t timeout_timestamp) { |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| if (__get_current_exception()) { |
| tight_loop_contents(); |
| return time_reached(timeout_timestamp); |
| } else { |
| alarm_id_t id; |
| // note that as of SDK 2.0.0 calling add_alarm_at always causes a SEV. What we really |
| // want to do is cause an IRQ at the specified time in the future if there is not |
| // an IRQ already happening before then. The problem is that the IRQ may be happening on the |
| // other core, so taking an IRQ is the only way to get the state protection. |
| // |
| // Therefore, we make a compromise; we will set the alarm, if we won't wake up before the right time |
| // already. This means that repeated calls to this function with the same timeout will work correctly |
| // after the first one! This is fine, because we ask callers to use a polling loop on another |
| // event variable when using this function. |
| // |
| // For this to work, we require that once we have set an alarm, an SEV happens no later than that, even |
| // if we cancel the alarm as we do below. Therefore, the IRQ handler (which is always enabled) will |
| // never set its wakeup time to a later value, but instead wake up once and then wake up again. |
| // |
| // This overhead when canceling alarms is a small price to pay for the much simpler/faster/cleaner |
| // implementation that relies on the IRQ handler (on a single core) being the only state accessor. |
| // |
| // Note also, that the use of software spin locks on RP2350 to access state would always cause a SEV |
| // due to use of LDREX etc., so actually using spin locks to protect the state would be worse. |
| // |
| // alarm_pool_irq_handler() maintains the invariant that there is *always* a pending alarm |
| // (if there are no scheduled callbacks then it adds a dummy alarm with a long interval), |
| // so we can directly query the hardware to determine the next wakeup: |
| if (ta_wakes_up_on_or_before(alarm_pool_get_default()->timer, alarm_pool_get_default()->timer_alarm_num, |
| (int64_t)to_us_since_boot(timeout_timestamp))) { |
| // if we are called repeatedly for a timeout in the past, we won't have an event - but in any case, it has already past! |
| if (time_reached(timeout_timestamp)) return true; |
| |
| // we already are waking up at or before when we want to (possibly due to us having been called |
| // before in a loop), so we can do an actual WFE. Note we rely on the fact that the alarm pool IRQ |
| // handler always does an explicit SEV, since it may be on the other core. |
| __wfe(); |
| return time_reached(timeout_timestamp); |
| } else { |
| id = add_alarm_at(timeout_timestamp, sleep_until_callback, NULL, false); |
| if (id <= 0) { |
| tight_loop_contents(); |
| return time_reached(timeout_timestamp); |
| } else { |
| if (!time_reached(timeout_timestamp)) { |
| // ^ at the point above the timer hadn't fired, so it is safe |
| // to wait; the event will happen due to IRQ at some point between |
| // then and the correct wakeup time |
| __wfe(); |
| } |
| // we need to clean up if it wasn't us that caused the wfe; if it was this will be a noop. |
| cancel_alarm(id); |
| return time_reached(timeout_timestamp); |
| } |
| } |
| } |
| #else |
| tight_loop_contents(); |
| return time_reached(timeout_timestamp); |
| #endif |
| } |
| |
| 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) { |
| if (!delay_us) delay_us = 1; |
| out->pool = pool; |
| out->callback = callback; |
| out->delay_us = delay_us; |
| out->user_data = user_data; |
| out->alarm_id = alarm_pool_add_alarm_at(pool, make_timeout_time_us((uint64_t)(delay_us >= 0 ? delay_us : -delay_us)), |
| repeating_timer_marker, out, true); |
| return out->alarm_id > 0; |
| } |
| |
| bool cancel_repeating_timer(repeating_timer_t *timer) { |
| bool rc = false; |
| if (timer->alarm_id) { |
| rc = alarm_pool_cancel_alarm(timer->pool, timer->alarm_id); |
| timer->alarm_id = 0; |
| } |
| return rc; |
| } |
| |
| alarm_pool_timer_t *alarm_pool_timer_for_timer_num(uint timer_num) { |
| return ta_timer_instance(timer_num); |
| } |
| |
| alarm_pool_timer_t *alarm_pool_get_default_timer(void) { |
| return ta_default_timer_instance(); |
| } |
| |
| int64_t alarm_pool_remaining_alarm_time_us(alarm_pool_t *pool, alarm_id_t alarm_id) { |
| // note there is no point distinguishing between invalid alarm_id and timer passed, |
| // since an alarm_id that has fired without being re-enabled becomes logically invalid after |
| // that point anyway |
| int64_t rc = -1; |
| int16_t index = alarm_index(alarm_id); |
| if ((uint16_t)index < pool->num_entries) { |
| uint16_t sequence = alarm_sequence(alarm_id); |
| alarm_pool_entry_t *entry = &pool->entries[index]; |
| if (entry->sequence == sequence) { |
| uint32_t save = spin_lock_blocking(pool->lock); |
| int16_t search_index = pool->ordered_head; |
| while (search_index >= 0) { |
| entry = &pool->entries[search_index]; |
| if (index == search_index) { |
| if (entry->sequence == sequence) { |
| rc = entry->target - (int64_t) ta_time_us_64(pool->timer); |
| } |
| break; |
| } |
| search_index = entry->next; |
| } |
| spin_unlock(pool->lock, save); |
| } |
| } |
| return rc; |
| } |
| |
| int32_t alarm_pool_remaining_alarm_time_ms(alarm_pool_t *pool, alarm_id_t alarm_id) { |
| int64_t rc = alarm_pool_remaining_alarm_time_us(pool, alarm_id); |
| if (rc >= 0) rc /= 1000; |
| return rc >= INT32_MAX ? INT32_MAX : (int32_t) rc; |
| } |
| |
| #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED |
| int64_t remaining_alarm_time_us(alarm_id_t alarm_id) { |
| return alarm_pool_remaining_alarm_time_us(alarm_pool_get_default(), alarm_id); |
| } |
| |
| int32_t remaining_alarm_time_ms(alarm_id_t alarm_id) { |
| return alarm_pool_remaining_alarm_time_ms(alarm_pool_get_default(), alarm_id); |
| } |
| #endif |