diff options
| author | Angus Gratton <angus@redyak.com.au> | 2025-03-12 16:24:55 +1100 |
|---|---|---|
| committer | Damien George <damien@micropython.org> | 2025-03-27 00:02:13 +1100 |
| commit | 6fa498cba1a622723d3dc13e15331085aea60d3c (patch) | |
| tree | 7a9160aa73e7a1d5a49c6535274c5a9c362ac848 | |
| parent | dd7a950bbc27713df8cc461958757ec793189d72 (diff) | |
rp2/mpnetworkport: Fix lost CYW43 WiFi events when using both cores.
There's a very odd but predictable sequence of events that breaks Wi-Fi
when using both cores:
1) CPU1 calls pendsv_suspend() - for example sleep() causes
a softtimer node to be inserted, which calls pendsv_suspend().
2) CYW43 sends wakeup IRQ. CPU0 GPIO IRQ handler schedules PendSV
and disables the GPIO IRQ on CPU0, to re-enable after
cyw43_poll() runs and completes.
3) CPU0 PendSV_Handler runs, sees pendsv is suspended, exits.
4) CPU1 calls pendsv_resume() and pendsv_resume() sees PendSV
is pending and triggers it on CPU1.
5) CPU1 runs PendSV_Handler, runs cyw43_poll(), and at the end
it re-enables the IRQ *but now on CPU1*.
However CPU1 has GPIO IRQs disabled, so the CYW43 interrupt never runs
again...
The fix in this commit is to always enable/disable the interrupt on CPU0.
This isn't supported by the pico-sdk, but it is supported by the hardware.
Fixes issue #16779.
This work was funded through GitHub Sponsors.
Signed-off-by: Angus Gratton <angus@redyak.com.au>
| -rw-r--r-- | ports/rp2/mpnetworkport.c | 34 |
1 files changed, 29 insertions, 5 deletions
diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index af2cabb3b..2687a5687 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -59,13 +59,36 @@ static soft_timer_entry_t mp_network_soft_timer; volatile int cyw43_has_pending = 0; +// The Pico SDK only lets us set GPIO wake on the current running CPU, but the +// hardware doesn't have this limit. We need to always enable/disable the pin +// interrupt on CPU0, regardless of which CPU runs PendSV and +// cyw43_post_poll_hook(). See feature request at https://github.com/raspberrypi/pico-sdk/issues/2354 +static void gpio_set_cpu0_host_wake_irq_enabled(bool enable) { + // This is a re-implementation of gpio_set_irq_enabled() and _gpio_set_irq_enabled() + // from the pico-sdk, but with the core, gpio, and event type hardcoded to shrink + // code size. + io_bank0_irq_ctrl_hw_t *irq_ctrl_base = &io_bank0_hw->proc0_irq_ctrl; + uint32_t gpio = CYW43_PIN_WL_HOST_WAKE; + uint32_t events = CYW43_IRQ_LEVEL; + io_rw_32 *en_reg = &irq_ctrl_base->inte[gpio / 8]; + events <<= 4 * (gpio % 8); + if (enable) { + hw_set_bits(en_reg, events); + } else { + hw_clear_bits(en_reg, events); + } +} + +// GPIO IRQ always runs on CPU0 static void gpio_irq_handler(void) { uint32_t events = gpio_get_irq_event_mask(CYW43_PIN_WL_HOST_WAKE); if (events & CYW43_IRQ_LEVEL) { - // As we use a high level interrupt, it will go off forever until it's serviced. - // So disable the interrupt until this is done. It's re-enabled again by - // CYW43_POST_POLL_HOOK which is called at the end of cyw43_poll_func. - gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, CYW43_IRQ_LEVEL, false); + // As we use a level interrupt (and can't use an edge interrupt + // as CYW43_PIN_WL_HOST_WAKE is also a SPI data pin), we need to disable + // the interrupt to stop it re-triggering until after PendSV run + // cyw43_poll(). It is re-enabled in cyw43_post_poll_hook(), implemented + // below. + gpio_set_cpu0_host_wake_irq_enabled(false); cyw43_has_pending = 1; __sev(); pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll); @@ -81,9 +104,10 @@ void cyw43_irq_init(void) { #endif } +// This hook will run on whichever CPU serviced the PendSV interrupt void cyw43_post_poll_hook(void) { cyw43_has_pending = 0; - gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, CYW43_IRQ_LEVEL, true); + gpio_set_cpu0_host_wake_irq_enabled(true); } #endif |
