//go:build esp32s3 package interrupt import ( "device" "device/esp" "runtime/volatile" "unsafe" ) // State represents the previous global interrupt state. type State uintptr // Disable disables all interrupts and returns the previous interrupt state. It // can be used in a critical section like this: // // state := interrupt.Disable() // // critical section // interrupt.Restore(state) // // Critical sections can be nested. Make sure to call Restore in the same order // as you called Disable (this happens naturally with the pattern above). func Disable() (state State) { return State(device.AsmFull("rsil {}, 15", nil)) } // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested // critical sections. func Restore(state State) { device.AsmFull("wsr {state}, PS", map[string]interface{}{ "state": state, }) } // The ESP32-S3 (Xtensa LX7) interrupt model: // // 1. The **interrupt matrix** (INTERRUPT_CORE0) maps each peripheral source // (0-98) to one of 32 CPU interrupt lines via a 5-bit mapping register. // 2. The CPU's INTENABLE special register (SR 228) enables/disables each of // the 32 CPU interrupt lines independently. // 3. When an enabled CPU interrupt fires, the processor vectors to the // level-1 exception vector (offset 0x180 from VECBASE). // 4. The INTERRUPT special register (SR 226) shows which CPU interrupts are // currently pending. // // We allocate CPU interrupt lines 6..30 for use by peripherals via // interrupt.New(). Lines 0-5 are reserved (timer, software, etc.) and // line 31 is avoided because some hardware treats it specially. const ( // First / last allocatable CPU interrupt for peripherals. firstCPUInt = 6 lastCPUInt = 30 ) // cpuIntUsed tracks which CPU interrupt lines have been allocated. var cpuIntUsed [32]bool // cpuIntToPeripheral maps CPU interrupt number → peripheral IRQ source, // so that handleInterrupt can dispatch to the correct Go handler. var cpuIntToPeripheral [32]int // inInterrupt is set while we're inside the interrupt handler so that // interrupt.In() returns the correct value. var inInterrupt bool // Enable enables a CPU interrupt for the ESP32-S3. The caller must first // map the peripheral to a CPU interrupt line using the interrupt matrix, // e.g.: // // esp.INTERRUPT_CORE0.SetGPIO_INTERRUPT_PRO_MAP(cpuInt) // interrupt.New(cpuInt, handler).Enable() func (i Interrupt) Enable() error { if i.num < firstCPUInt || i.num > lastCPUInt { return errInterruptRange } // Mark as used. cpuIntUsed[i.num] = true // Read current INTENABLE, set the bit for this CPU interrupt. cur := readINTENABLE() cur |= 1 << uint(i.num) writeINTENABLE(cur) return nil } // In returns whether the CPU is currently inside an interrupt handler. func In() bool { return inInterrupt } // handleInterrupt is called from the assembly vector code in esp32s3.S. // It determines which CPU interrupt(s) fired and dispatches to the // registered Go handlers. // //export handleInterrupt func handleInterrupt() { inInterrupt = true // INTERRUPT register shows pending + enabled CPU interrupts. pending := readINTERRUPT() enabled := readINTENABLE() active := pending & enabled for i := firstCPUInt; i <= lastCPUInt; i++ { if active&(1<