rp2: Refactor to not use pico-sdk alarm pool functions for sleeping.

The best_effort_wfe_or_timeout() and sleep_us() pico-sdk functions use the
pico-sdk alarm pool internally, and that has a bug.

Some usages inside pico-sdk (notably multicore_lockout_start_blocking())
will still end up calling best_effort_wfe_or_timeout(), although usually
with "end_of_time" as the timeout value so it should avoid any alarm pool
race conditions.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
This commit is contained in:
Angus Gratton
2024-01-02 16:35:27 +11:00
committed by Damien George
parent 74fb42aa82
commit 83e82c5ad3
3 changed files with 39 additions and 12 deletions

View File

@@ -198,17 +198,31 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
return did_write ? ret : 0; return did_write ? ret : 0;
} }
void mp_hal_delay_us(mp_uint_t us) {
// Avoid calling sleep_us() and invoking the alarm pool by splitting long
// sleeps into an optional longer sleep and a shorter busy-wait
uint64_t end = time_us_64() + us;
if (us > 1000) {
mp_hal_delay_ms(us / 1000);
}
while (time_us_64() < end) {
// Tight loop busy-wait for accurate timing
}
}
void mp_hal_delay_ms(mp_uint_t ms) { void mp_hal_delay_ms(mp_uint_t ms) {
absolute_time_t t = make_timeout_time_ms(ms); mp_uint_t start = mp_hal_ticks_ms();
mp_uint_t elapsed = 0;
do { do {
mp_event_handle_nowait(); mp_event_wait_ms(ms - elapsed);
} while (!best_effort_wfe_or_timeout(t)); elapsed = mp_hal_ticks_ms() - start;
} while (elapsed < ms);
} }
void mp_hal_time_ns_set_from_rtc(void) { void mp_hal_time_ns_set_from_rtc(void) {
// Delay at least one RTC clock cycle so it's registers have updated with the most // Outstanding RTC register writes need at least two RTC clock cycles to
// recent time settings. // update. (See RP2040 datasheet section 4.8.4 "Reference clock").
sleep_us(23); mp_hal_delay_us(44);
// Sample RTC and time_us_64() as close together as possible, so the offset // Sample RTC and time_us_64() as close together as possible, so the offset
// calculated for the latter can be as accurate as possible. // calculated for the latter can be as accurate as possible.
@@ -295,3 +309,17 @@ void soft_timer_init(void) {
hardware_alarm_claim(MICROPY_HW_SOFT_TIMER_ALARM_NUM); hardware_alarm_claim(MICROPY_HW_SOFT_TIMER_ALARM_NUM);
hardware_alarm_set_callback(MICROPY_HW_SOFT_TIMER_ALARM_NUM, soft_timer_hardware_callback); hardware_alarm_set_callback(MICROPY_HW_SOFT_TIMER_ALARM_NUM, soft_timer_hardware_callback);
} }
void mp_wfe_or_timeout(uint32_t timeout_ms) {
soft_timer_entry_t timer;
// Note the timer doesn't have an associated callback, it just exists to create a
// hardware interrupt to wake the CPU
soft_timer_static_init(&timer, SOFT_TIMER_MODE_ONE_SHOT, 0, NULL);
soft_timer_insert(&timer, timeout_ms);
__wfe();
// Clean up the timer node if it's not already
soft_timer_remove(&timer);
}

View File

@@ -62,23 +62,22 @@
if ((TIMEOUT_MS) < 0) { \ if ((TIMEOUT_MS) < 0) { \
__wfe(); \ __wfe(); \
} else { \ } else { \
best_effort_wfe_or_timeout(make_timeout_time_ms(TIMEOUT_MS)); \ mp_wfe_or_timeout(TIMEOUT_MS); \
} \ } \
} while (0) } while (0)
extern int mp_interrupt_char; extern int mp_interrupt_char;
extern ringbuf_t stdin_ringbuf; extern ringbuf_t stdin_ringbuf;
// Port-specific function to create a wakeup interrupt after timeout_ms and enter WFE
void mp_wfe_or_timeout(uint32_t timeout_ms);
uint32_t mp_thread_begin_atomic_section(void); uint32_t mp_thread_begin_atomic_section(void);
void mp_thread_end_atomic_section(uint32_t); void mp_thread_end_atomic_section(uint32_t);
void mp_hal_set_interrupt_char(int c); void mp_hal_set_interrupt_char(int c);
void mp_hal_time_ns_set_from_rtc(void); void mp_hal_time_ns_set_from_rtc(void);
static inline void mp_hal_delay_us(mp_uint_t us) {
sleep_us(us);
}
static inline void mp_hal_delay_us_fast(mp_uint_t us) { static inline void mp_hal_delay_us_fast(mp_uint_t us) {
busy_wait_us(us); busy_wait_us(us);
} }

View File

@@ -89,7 +89,7 @@ void soft_timer_handler(void) {
heap = (soft_timer_entry_t *)mp_pairheap_pop(soft_timer_lt, &heap->pairheap); heap = (soft_timer_entry_t *)mp_pairheap_pop(soft_timer_lt, &heap->pairheap);
if (entry->flags & SOFT_TIMER_FLAG_PY_CALLBACK) { if (entry->flags & SOFT_TIMER_FLAG_PY_CALLBACK) {
mp_sched_schedule(entry->py_callback, MP_OBJ_FROM_PTR(entry)); mp_sched_schedule(entry->py_callback, MP_OBJ_FROM_PTR(entry));
} else { } else if (entry->c_callback) {
entry->c_callback(entry); entry->c_callback(entry);
} }
if (entry->mode == SOFT_TIMER_MODE_PERIODIC) { if (entry->mode == SOFT_TIMER_MODE_PERIODIC) {