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>
This commit is contained in:
committed by
Damien George
parent
dd7a950bbc
commit
6fa498cba1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user