summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngus Gratton <angus@redyak.com.au>2025-03-12 16:24:55 +1100
committerDamien George <damien@micropython.org>2025-03-27 00:02:13 +1100
commit6fa498cba1a622723d3dc13e15331085aea60d3c (patch)
tree7a9160aa73e7a1d5a49c6535274c5a9c362ac848
parentdd7a950bbc27713df8cc461958757ec793189d72 (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.c34
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