diff --git a/src/runtime/interrupt/interrupt_esp32s3.go b/src/runtime/interrupt/interrupt_esp32s3.go new file mode 100644 index 0000000000..4f7b8b1111 --- /dev/null +++ b/src/runtime/interrupt/interrupt_esp32s3.go @@ -0,0 +1,223 @@ +//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<IRAM AT >DRAM +/* Xtensa exception/interrupt vector table — must be 0x400-aligned. */ +.text.exception_vectors : ALIGN(0x400) +{ + *(.text.exception_vectors) +} >IRAM AT >DRAM + +/* Level-1 interrupt handler (called from the vector stub). */ +.text._handle_level1 : ALIGN(4) +{ + *(.literal._handle_level1) + *(.text._handle_level1) +} >IRAM AT >DRAM + /* All other code and literals */ .text : ALIGN(4) {