From a96cc30257ab58216963b59fc8804fbd708d1f84 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:46:55 -0700 Subject: [PATCH] machine: avoid sync.Once during ESP32 startup UART initialization runs before the scheduler has a current task. Once.Do uses defer, which requires a current task when panic recovery is enabled. Use an interrupt-protected flag for this unicore initialization path instead. --- src/machine/machine_esp32.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index 8f6ddcc01..f9415fa3a 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -478,7 +478,7 @@ var ( rtsctsSignal: 199, } - onceUart = sync.Once{} + uartInterruptConfigured bool ) // CPU interrupt line used for all UART peripherals. @@ -588,11 +588,13 @@ func (uart *UART) configureInterrupt() { } // Register the ISR only once (shared across all UARTs on the same CPU int). - // interrupt.New is a compiler intrinsic and requires a plain (non-capturing) - // handler function, so we use a named package-level function. - onceUart.Do(func() { + // Avoid sync.Once here because serial is initialized before a task exists. + state := interrupt.Disable() + if !uartInterruptConfigured { _ = interrupt.New(cpuInterruptFromUART, handleUARTInterrupt).Enable() - }) + uartInterruptConfigured = true + } + interrupt.Restore(state) } // handleUARTInterrupt is the shared UART interrupt handler. It must be a plain