Austin Schuh | 208337d | 2022-01-01 14:29:11 -0800 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. |
| 3 | * |
| 4 | * SPDX-License-Identifier: BSD-3-Clause |
| 5 | */ |
| 6 | |
| 7 | #include "hardware/timer.h" |
| 8 | #include "hardware/irq.h" |
| 9 | #include "hardware/sync.h" |
| 10 | #include "hardware/claim.h" |
| 11 | |
| 12 | check_hw_layout(timer_hw_t, ints, TIMER_INTS_OFFSET); |
| 13 | |
| 14 | static hardware_alarm_callback_t alarm_callbacks[NUM_TIMERS]; |
| 15 | static uint32_t target_hi[NUM_TIMERS]; |
| 16 | static uint8_t timer_callbacks_pending; |
| 17 | |
| 18 | static_assert(NUM_TIMERS <= 4, ""); |
| 19 | static uint8_t claimed; |
| 20 | |
| 21 | void hardware_alarm_claim(uint alarm_num) { |
| 22 | check_hardware_alarm_num_param(alarm_num); |
| 23 | hw_claim_or_assert(&claimed, alarm_num, "Hardware alarm %d already claimed"); |
| 24 | } |
| 25 | |
| 26 | void hardware_alarm_unclaim(uint alarm_num) { |
| 27 | check_hardware_alarm_num_param(alarm_num); |
| 28 | hw_claim_clear(&claimed, alarm_num); |
| 29 | } |
| 30 | |
| 31 | bool hardware_alarm_is_claimed(uint alarm_num) { |
| 32 | check_hardware_alarm_num_param(alarm_num); |
| 33 | return hw_is_claimed(&claimed, alarm_num); |
| 34 | } |
| 35 | |
| 36 | /// tag::time_us_64[] |
| 37 | uint64_t time_us_64() { |
| 38 | // Need to make sure that the upper 32 bits of the timer |
| 39 | // don't change, so read that first |
| 40 | uint32_t hi = timer_hw->timerawh; |
| 41 | uint32_t lo; |
| 42 | do { |
| 43 | // Read the lower 32 bits |
| 44 | lo = timer_hw->timerawl; |
| 45 | // Now read the upper 32 bits again and |
| 46 | // check that it hasn't incremented. If it has loop around |
| 47 | // and read the lower 32 bits again to get an accurate value |
| 48 | uint32_t next_hi = timer_hw->timerawh; |
| 49 | if (hi == next_hi) break; |
| 50 | hi = next_hi; |
| 51 | } while (true); |
| 52 | return ((uint64_t) hi << 32u) | lo; |
| 53 | } |
| 54 | /// end::time_us_64[] |
| 55 | |
| 56 | /// \tag::busy_wait[] |
| 57 | void busy_wait_us_32(uint32_t delay_us) { |
| 58 | if (0 <= (int32_t)delay_us) { |
| 59 | // we only allow 31 bits, otherwise we could have a race in the loop below with |
| 60 | // values very close to 2^32 |
| 61 | uint32_t start = timer_hw->timerawl; |
| 62 | while (timer_hw->timerawl - start < delay_us) { |
| 63 | tight_loop_contents(); |
| 64 | } |
| 65 | } else { |
| 66 | busy_wait_us(delay_us); |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | void busy_wait_us(uint64_t delay_us) { |
| 71 | uint64_t base = time_us_64(); |
| 72 | uint64_t target = base + delay_us; |
| 73 | if (target < base) { |
| 74 | target = (uint64_t)-1; |
| 75 | } |
| 76 | absolute_time_t t; |
| 77 | update_us_since_boot(&t, target); |
| 78 | busy_wait_until(t); |
| 79 | } |
| 80 | |
| 81 | void busy_wait_ms(uint32_t delay_ms) |
| 82 | { |
| 83 | if (delay_ms <= 0x7fffffffu / 1000) { |
| 84 | busy_wait_us_32(delay_ms * 1000); |
| 85 | } else { |
| 86 | busy_wait_us(delay_ms * 1000ull); |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | void busy_wait_until(absolute_time_t t) { |
| 91 | uint64_t target = to_us_since_boot(t); |
| 92 | uint32_t hi_target = (uint32_t)(target >> 32u); |
| 93 | uint32_t hi = timer_hw->timerawh; |
| 94 | while (hi < hi_target) { |
| 95 | hi = timer_hw->timerawh; |
| 96 | tight_loop_contents(); |
| 97 | } |
| 98 | while (hi == hi_target && timer_hw->timerawl < (uint32_t) target) { |
| 99 | hi = timer_hw->timerawh; |
| 100 | tight_loop_contents(); |
| 101 | } |
| 102 | } |
| 103 | /// \end::busy_wait[] |
| 104 | |
| 105 | static inline uint harware_alarm_irq_number(uint alarm_num) { |
| 106 | return TIMER_IRQ_0 + alarm_num; |
| 107 | } |
| 108 | |
| 109 | static void hardware_alarm_irq_handler(void) { |
| 110 | // Determine which timer this IRQ is for |
| 111 | uint32_t ipsr; |
| 112 | __asm volatile ("mrs %0, ipsr" : "=r" (ipsr)::); |
| 113 | uint alarm_num = (ipsr & 0x3fu) - 16 - TIMER_IRQ_0; |
| 114 | check_hardware_alarm_num_param(alarm_num); |
| 115 | |
| 116 | hardware_alarm_callback_t callback = NULL; |
| 117 | |
| 118 | spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER); |
| 119 | uint32_t save = spin_lock_blocking(lock); |
| 120 | // Clear the timer IRQ (inside lock, because we check whether we have handled the IRQ yet in alarm_set by looking at the interrupt status |
| 121 | timer_hw->intr = 1u << alarm_num; |
| 122 | |
| 123 | // make sure the IRQ is still valid |
| 124 | if (timer_callbacks_pending & (1u << alarm_num)) { |
| 125 | // Now check whether we have a timer event to handle that isn't already obsolete (this could happen if we |
| 126 | // were already in the IRQ handler before someone else changed the timer setup |
| 127 | if (timer_hw->timerawh >= target_hi[alarm_num]) { |
| 128 | // we have reached the right high word as well as low word value |
| 129 | callback = alarm_callbacks[alarm_num]; |
| 130 | timer_callbacks_pending &= (uint8_t)~(1u << alarm_num); |
| 131 | } else { |
| 132 | // try again in 2^32 us |
| 133 | timer_hw->alarm[alarm_num] = timer_hw->alarm[alarm_num]; // re-arm the timer |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | spin_unlock(lock, save); |
| 138 | |
| 139 | if (callback) { |
| 140 | callback(alarm_num); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | void hardware_alarm_set_callback(uint alarm_num, hardware_alarm_callback_t callback) { |
| 145 | // todo check current core owner |
| 146 | // note this should probably be subsumed by irq_set_exclusive_handler anyway, since that |
| 147 | // should disallow IRQ handlers on both cores |
| 148 | check_hardware_alarm_num_param(alarm_num); |
| 149 | uint irq_num = harware_alarm_irq_number(alarm_num); |
| 150 | spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER); |
| 151 | uint32_t save = spin_lock_blocking(lock); |
| 152 | if (callback) { |
| 153 | if (hardware_alarm_irq_handler != irq_get_vtable_handler(irq_num)) { |
| 154 | // note that set_exclusive will silently allow you to set the handler to the same thing |
| 155 | // since it is idempotent, which means we don't need to worry about locking ourselves |
| 156 | irq_set_exclusive_handler(irq_num, hardware_alarm_irq_handler); |
| 157 | irq_set_enabled(irq_num, true); |
| 158 | // Enable interrupt in block and at processor |
| 159 | hw_set_bits(&timer_hw->inte, 1u << alarm_num); |
| 160 | } |
| 161 | alarm_callbacks[alarm_num] = callback; |
| 162 | } else { |
| 163 | alarm_callbacks[alarm_num] = NULL; |
| 164 | timer_callbacks_pending &= (uint8_t)~(1u << alarm_num); |
| 165 | irq_remove_handler(irq_num, hardware_alarm_irq_handler); |
| 166 | irq_set_enabled(irq_num, false); |
| 167 | } |
| 168 | spin_unlock(lock, save); |
| 169 | } |
| 170 | |
| 171 | bool hardware_alarm_set_target(uint alarm_num, absolute_time_t target) { |
| 172 | bool missed; |
| 173 | uint64_t now = time_us_64(); |
| 174 | uint64_t t = to_us_since_boot(target); |
| 175 | if (now >= t) { |
| 176 | missed = true; |
| 177 | } else { |
| 178 | missed = false; |
| 179 | |
| 180 | // 1) actually set the hardware timer |
| 181 | spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER); |
| 182 | uint32_t save = spin_lock_blocking(lock); |
| 183 | uint8_t old_timer_callbacks_pending = timer_callbacks_pending; |
| 184 | timer_callbacks_pending |= (uint8_t)(1u << alarm_num); |
| 185 | timer_hw->intr = 1u << alarm_num; // clear any IRQ |
| 186 | timer_hw->alarm[alarm_num] = (uint32_t) t; |
| 187 | // Set the alarm. Writing time should arm it |
| 188 | target_hi[alarm_num] = (uint32_t)(t >> 32u); |
| 189 | |
| 190 | // 2) check for races |
| 191 | if (!(timer_hw->armed & 1u << alarm_num)) { |
| 192 | // not armed, so has already fired .. IRQ must be pending (we are still under lock) |
| 193 | assert(timer_hw->ints & 1u << alarm_num); |
| 194 | } else { |
| 195 | if (time_us_64() >= t) { |
| 196 | // we are already at or past the right time; there is no point in us racing against the IRQ |
| 197 | // we are about to generate. note however that, if there was already a timer pending before, |
| 198 | // then we still let the IRQ fire, as whatever it was, is not handled by our setting missed=true here |
| 199 | missed = true; |
| 200 | if (timer_callbacks_pending != old_timer_callbacks_pending) { |
| 201 | // disarm the timer |
| 202 | timer_hw->armed = 1u << alarm_num; |
| 203 | // clear the IRQ... |
| 204 | timer_hw->intr = 1u << alarm_num; |
| 205 | // ... including anything pending on the processor - perhaps unnecessary, but |
| 206 | // our timer flag says we aren't expecting anything. |
| 207 | irq_clear(harware_alarm_irq_number(alarm_num)); |
| 208 | // and clear our flag so that if the IRQ handler is already active (because it is on |
| 209 | // the other core) it will also skip doing anything |
| 210 | timer_callbacks_pending = old_timer_callbacks_pending; |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | spin_unlock(lock, save); |
| 215 | // note at this point any pending timer IRQ can likely run |
| 216 | } |
| 217 | return missed; |
| 218 | } |
| 219 | |
| 220 | void hardware_alarm_cancel(uint alarm_num) { |
| 221 | check_hardware_alarm_num_param(alarm_num); |
| 222 | |
| 223 | spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER); |
| 224 | uint32_t save = spin_lock_blocking(lock); |
| 225 | timer_hw->armed = 1u << alarm_num; |
| 226 | timer_callbacks_pending &= (uint8_t)~(1u << alarm_num); |
| 227 | spin_unlock(lock, save); |
| 228 | } |
| 229 | |
| 230 | |