diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 40374faff9..5fb47cc402 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -76,6 +76,9 @@ int main(int argc, char **argv) { // This is a tickless port, interrupts should always trigger SEV. SCB->SCR |= SCB_SCR_SEVONPEND_Msk; + pendsv_init(); + soft_timer_init(); + #if MICROPY_HW_ENABLE_UART_REPL bi_decl(bi_program_feature("UART REPL")) setup_default_uart(); diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index a29692d0be..128295f19f 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -154,6 +154,10 @@ #define MICROPY_SSL_MBEDTLS (1) #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) +// Hardware timer alarm index. Available range 0-3. +// Number 3 is currently used by pico-sdk (PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM) +#define MICROPY_HW_SOFT_TIMER_ALARM_NUM (2) + // fatfs configuration #define MICROPY_FATFS_ENABLE_LFN (1) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ @@ -266,6 +270,7 @@ typedef intptr_t mp_off_t; #define BINARY_INFO_ID_MP_FROZEN 0x4a99d719 #define MICROPY_FROZEN_LIST_ITEM(name, file) bi_decl(bi_string(BINARY_INFO_TAG_MICROPYTHON, BINARY_INFO_ID_MP_FROZEN, name)) + extern uint32_t rosc_random_u32(void); extern void lwip_lock_acquire(void); extern void lwip_lock_release(void); diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index 1641eadb7b..a13a0edc30 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -35,6 +35,7 @@ #include "pendsv.h" #include "tusb.h" #include "uart.h" +#include "hardware/irq.h" #include "hardware/rtc.h" #include "pico/unique_id.h" @@ -46,8 +47,6 @@ // microseconds since the Epoch. static uint64_t time_us_64_offset_from_epoch; -static alarm_id_t soft_timer_alarm_id = 0; - #if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC #ifndef MICROPY_HW_STDIN_BUFFER_LEN @@ -199,17 +198,31 @@ mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { 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) { - absolute_time_t t = make_timeout_time_ms(ms); + mp_uint_t start = mp_hal_ticks_ms(); + mp_uint_t elapsed = 0; do { - mp_event_handle_nowait(); - } while (!best_effort_wfe_or_timeout(t)); + mp_event_wait_ms(ms - elapsed); + elapsed = mp_hal_ticks_ms() - start; + } while (elapsed < ms); } 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 - // recent time settings. - sleep_us(23); + // Outstanding RTC register writes need at least two RTC clock cycles to + // update. (See RP2040 datasheet section 4.8.4 "Reference clock"). + mp_hal_delay_us(44); // Sample RTC and time_us_64() as close together as possible, so the offset // calculated for the latter can be as accurate as possible. @@ -273,21 +286,40 @@ uint32_t storage_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blo panic_unsupported(); } -static int64_t soft_timer_callback(alarm_id_t id, void *user_data) { - soft_timer_alarm_id = 0; - pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); - return 0; // don't reschedule this alarm -} - uint32_t soft_timer_get_ms(void) { return mp_hal_ticks_ms(); } void soft_timer_schedule_at_ms(uint32_t ticks_ms) { - if (soft_timer_alarm_id != 0) { - cancel_alarm(soft_timer_alarm_id); - } int32_t ms = soft_timer_ticks_diff(ticks_ms, mp_hal_ticks_ms()); ms = MAX(0, ms); - soft_timer_alarm_id = add_alarm_in_ms(ms, soft_timer_callback, NULL, true); + if (hardware_alarm_set_target(MICROPY_HW_SOFT_TIMER_ALARM_NUM, delayed_by_ms(get_absolute_time(), ms))) { + // "missed" hardware alarm target + hardware_alarm_force_irq(MICROPY_HW_SOFT_TIMER_ALARM_NUM); + } +} + +static void soft_timer_hardware_callback(unsigned int alarm_num) { + // The timer alarm ISR needs to call here and trigger PendSV dispatch via + // a second ISR, as PendSV may be currently suspended by the other CPU. + pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); +} + +void soft_timer_init(void) { + hardware_alarm_claim(MICROPY_HW_SOFT_TIMER_ALARM_NUM); + 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); } diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index d2d74d783a..bc09f8c4a5 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -62,23 +62,22 @@ if ((TIMEOUT_MS) < 0) { \ __wfe(); \ } else { \ - best_effort_wfe_or_timeout(make_timeout_time_ms(TIMEOUT_MS)); \ + mp_wfe_or_timeout(TIMEOUT_MS); \ } \ } while (0) extern int mp_interrupt_char; 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); void mp_thread_end_atomic_section(uint32_t); void mp_hal_set_interrupt_char(int c); 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) { busy_wait_us(us); } diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index 09a543c2db..4690a4e511 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -66,7 +66,7 @@ static void gpio_irq_handler(void) { void cyw43_irq_init(void) { gpio_add_raw_irq_handler_with_order_priority(CYW43_PIN_WL_HOST_WAKE, gpio_irq_handler, CYW43_SHARED_IRQ_HANDLER_PRIORITY); irq_set_enabled(IO_IRQ_BANK0, true); - NVIC_SetPriority(PendSV_IRQn, PICO_LOWEST_IRQ_PRIORITY); + NVIC_SetPriority(PendSV_IRQn, IRQ_PRI_PENDSV); } void cyw43_post_poll_hook(void) { diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index e4f662715f..d24b4cb012 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -25,6 +25,7 @@ */ #include +#include "pico/mutex.h" #include "py/mpconfig.h" #include "pendsv.h" #include "RP2040.h" @@ -34,15 +35,21 @@ #endif static pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS]; -static int pendsv_lock; +static recursive_mutex_t pendsv_mutex; + +void pendsv_init(void) { + recursive_mutex_init(&pendsv_mutex); +} void pendsv_suspend(void) { - pendsv_lock++; + // Recursive Mutex here as either core may call pendsv_suspend() and expect + // both mutual exclusion (other core can't enter pendsv_suspend() at the + // same time), and that no PendSV handler will run. + recursive_mutex_enter_blocking(&pendsv_mutex); } void pendsv_resume(void) { - pendsv_lock--; - assert(pendsv_lock >= 0); + recursive_mutex_exit(&pendsv_mutex); // Run pendsv if needed. Find an entry with a dispatch and call pendsv dispatch // with it. If pendsv runs it will service all slots. int count = PENDSV_DISPATCH_NUM_SLOTS; @@ -55,9 +62,11 @@ void pendsv_resume(void) { } void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { - assert(pendsv_lock >= 0); pendsv_dispatch_table[slot] = f; - if (pendsv_lock == 0) { + if (pendsv_mutex.enter_count == 0) { + // There is a race here where other core calls pendsv_suspend() before + // ISR can execute, but dispatch will happen later when other core + // calls pendsv_resume(). SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; } else { #if MICROPY_PY_NETWORK_CYW43 @@ -68,7 +77,14 @@ void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { // PendSV interrupt handler to perform background processing. void PendSV_Handler(void) { - assert(pendsv_lock == 0); + + if (!recursive_mutex_try_enter(&pendsv_mutex, NULL)) { + // Failure here means core 1 holds pendsv_mutex. ISR will + // run again after core 1 calls pendsv_resume(). + return; + } + // Core 0 should not already have locked pendsv_mutex + assert(pensv_mutex.enter_count == 1); #if MICROPY_PY_NETWORK_CYW43 CYW43_STAT_INC(PENDSV_RUN_COUNT); @@ -81,4 +97,6 @@ void PendSV_Handler(void) { f(); } } + + recursive_mutex_exit(&pendsv_mutex); } diff --git a/ports/rp2/pendsv.h b/ports/rp2/pendsv.h index bc8e8d61c8..a5d9440211 100644 --- a/ports/rp2/pendsv.h +++ b/ports/rp2/pendsv.h @@ -42,8 +42,12 @@ enum { #define PENDSV_DISPATCH_NUM_SLOTS PENDSV_DISPATCH_MAX +// PendSV IRQ priority, to run system-level tasks that preempt the main thread. +#define IRQ_PRI_PENDSV PICO_LOWEST_IRQ_PRIORITY + typedef void (*pendsv_dispatch_t)(void); +void pendsv_init(void); void pendsv_suspend(void); void pendsv_resume(void); void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f); diff --git a/shared/runtime/softtimer.c b/shared/runtime/softtimer.c index 13a6e78777..92a0d6d511 100644 --- a/shared/runtime/softtimer.c +++ b/shared/runtime/softtimer.c @@ -89,7 +89,7 @@ void soft_timer_handler(void) { heap = (soft_timer_entry_t *)mp_pairheap_pop(soft_timer_lt, &heap->pairheap); if (entry->flags & SOFT_TIMER_FLAG_PY_CALLBACK) { mp_sched_schedule(entry->py_callback, MP_OBJ_FROM_PTR(entry)); - } else { + } else if (entry->c_callback) { entry->c_callback(entry); } if (entry->mode == SOFT_TIMER_MODE_PERIODIC) { diff --git a/shared/runtime/softtimer.h b/shared/runtime/softtimer.h index fe5d02b907..c7b374d755 100644 --- a/shared/runtime/softtimer.h +++ b/shared/runtime/softtimer.h @@ -81,6 +81,10 @@ static inline void soft_timer_reinsert(soft_timer_entry_t *entry, uint32_t initi // pend-SV IRQ level, or equivalent. uint32_t soft_timer_get_ms(void); void soft_timer_schedule_at_ms(uint32_t ticks_ms); + +// Optional port-specific initialisation function (provided by the port and called +// by the port if needed.) +void soft_timer_init(void); #endif #endif // MICROPY_INCLUDED_SHARED_RUNTIME_SOFTTIMER_H