diff --git a/esp32c3/isr.c b/esp32c3/isr.c index 9cedf9d..903c45e 100644 --- a/esp32c3/isr.c +++ b/esp32c3/isr.c @@ -31,16 +31,19 @@ void espradio_prewire_wifi_interrupts(void) { intr_matrix_set(0, ETS_WIFI_PWR_INTR_SOURCE, ESPRADIO_WIFI_CPU_INT); } +extern void espradio_mark_wifi_isr_slot(int32_t n); + /* No-op: the blob calls set_intr to route peripheral sources to CPU * interrupts, but on RISC-V (ESP32-C3) the routing is already configured * by espradio_prewire_wifi_interrupts(). Letting the blob call * intr_matrix_set at arbitrary times interferes with TinyGo's interrupt - * controller state. The Rust esp-wifi does the same (no-op set_intr). */ + * controller state. The Rust esp-wifi does the same (no-op set_intr). + * Record the blob's requested intr_num as a WiFi ISR slot. */ void espradio_set_intr(int32_t cpu_no, uint32_t intr_source, uint32_t intr_num, int32_t intr_prio) { (void)cpu_no; (void)intr_source; - (void)intr_num; (void)intr_prio; + espradio_mark_wifi_isr_slot((int32_t)intr_num); } /* No-op: the Rust esp-wifi also no-ops clear_intr. */ diff --git a/esp32s3/isr.c b/esp32s3/isr.c index 6381c4e..90d68ba 100644 --- a/esp32s3/isr.c +++ b/esp32s3/isr.c @@ -27,11 +27,14 @@ void espradio_prewire_wifi_interrupts(void) { intr_matrix_set(0, ETS_WIFI_BB_INTR_SOURCE, ESPRADIO_WIFI_CPU_INT); /* src 3 */ } -/* No-op: the blob calls set_intr to route peripheral sources to CPU - * interrupts, but routing is already configured by - * espradio_prewire_wifi_interrupts(). */ +extern void espradio_mark_wifi_isr_slot(int32_t n); + +/* Route the blob's requested peripheral source to our fixed WiFi CPU interrupt + * and record the blob's requested intr_num as a WiFi ISR slot so that + * espradio_call_wifi_isr() only calls the relevant handlers. */ void espradio_set_intr(int32_t cpu_no, uint32_t intr_source, uint32_t intr_num, int32_t intr_prio) { intr_matrix_set(0, intr_source, ESPRADIO_WIFI_CPU_INT); + espradio_mark_wifi_isr_slot((int32_t)intr_num); } /* No-op: same as set_intr. */ @@ -40,22 +43,33 @@ void espradio_clear_intr(uint32_t intr_source, uint32_t intr_num) { (void)intr_num; } -/* Enable CPU interrupts using Xtensa INTENABLE special register. - * The blob calls ints_on with its own mask (1<<0 for WiFi MAC). - * We translate: if the blob's mask includes a WiFi-related bit, - * also set our actual WiFi CPU interrupt bit. */ +/* Enable/disable CPU interrupts using Xtensa INTENABLE special register. + * + * IMPORTANT: We deliberately ignore the blob's mask and only operate on + * ESPRADIO_WIFI_CPU_INT (bit 12). The blob passes its own original CPU + * interrupt number (e.g. 0 or 1), which may coincide with CPU interrupts + * that TinyGo has allocated for other purposes (bit 10 = GPIO, bit 9 = + * timer alarm). If we forwarded the blob's mask, espradio_ints_off would + * clear those bits from INTENABLE, permanently disabling user interrupts + * such as GPIO PinFalling callbacks (issue #40). + * + * The blob calls these for its own critical-section protection. Since all + * blob ISR handlers run in goroutine context (never from real ISR context), + * the blob's critical sections are already serialised by TinyGo's + * cooperative scheduler; ignoring the mask is safe. */ void espradio_ints_on(uint32_t mask) { - uint32_t actual_mask = mask | (1u << ESPRADIO_WIFI_CPU_INT); + (void)mask; uint32_t val; __asm__ volatile ("rsr %0, intenable" : "=r"(val)); - val |= actual_mask; + val |= (1u << ESPRADIO_WIFI_CPU_INT); __asm__ volatile ("wsr %0, intenable; rsync" :: "r"(val)); } void espradio_ints_off(uint32_t mask) { + (void)mask; uint32_t val; __asm__ volatile ("rsr %0, intenable" : "=r"(val)); - val &= ~mask; + val &= ~(1u << ESPRADIO_WIFI_CPU_INT); __asm__ volatile ("wsr %0, intenable; rsync" :: "r"(val)); } diff --git a/espradio.h b/espradio.h index 03efc27..5b643f4 100644 --- a/espradio.h +++ b/espradio.h @@ -20,6 +20,7 @@ void espradio_ensure_osi_ptr(void); void espradio_coex_adapter_init(void); void espradio_call_saved_isr(int32_t n); void espradio_call_wifi_isr(void); +void espradio_mark_wifi_isr_slot(int32_t n); uint32_t espradio_get_wifi_isr_count(void); void espradio_prewire_wifi_interrupts(void); void espradio_wifi_int_to_level(void); diff --git a/isr.c b/isr.c index 6b64659..59a2167 100644 --- a/isr.c +++ b/isr.c @@ -79,6 +79,15 @@ void espradio_user_exception(uint32_t cause, uint32_t epc, uint32_t excvaddr, ui static void (*s_isr_fn[32])(void *); static void *s_isr_arg[32]; +/* Bitmask of ISR slots registered via espradio_set_intr (WiFi sources only). */ +static uint32_t s_wifi_isr_slots; + +void espradio_mark_wifi_isr_slot(int32_t n) { + if (n >= 0 && n < 32) { + s_wifi_isr_slots |= (1u << n); + } +} + void espradio_set_isr(int32_t n, void *f, void *arg) { if (n >= 0 && n < 32) { s_isr_fn[n] = (void (*)(void *))f; @@ -98,8 +107,14 @@ void espradio_call_wifi_isr(void) { s_wifi_isr_count++; s_in_isr = 1; ESPRADIO_MEMORY_BARRIER(); - // CALL ALL ISRs from 0 to 31 just in case, to see if they are set! - for (int i = 0; i < 32; i++) { + /* Only call ISR slots that were registered via espradio_set_intr for a + * WiFi peripheral source. Calling all 32 slots risks invoking blob + * handlers at slot numbers that coincide with TinyGo's GPIO or timer + * CPU interrupts, which can corrupt INTENABLE. */ + uint32_t slots = s_wifi_isr_slots; + while (slots) { + int i = __builtin_ctz(slots); + slots &= slots - 1; if (s_isr_fn[i]) { s_isr_fn[i](s_isr_arg[i]); }