mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-06 03:53:42 +00:00
a6a4f85e61
Replace the minimal inline ISR (which only disabled INTENABLE) with a full level-1 interrupt handler that saves/restores the interrupted context and dispatches to Go's handleInterrupt. The handler uses callx4 (not callx0) to call into Go code because: - callx0 does not set PS.CALLINC, so the Go function's entry instruction uses stale CALLINC from the interrupted code, causing wrong window rotation and a garbage stack pointer. - callx4 explicitly sets CALLINC=1, and our frame pointer (a1) is outside the callee's register window so it is preserved. Also updates the USB Serial/JTAG ISR to disable INT_ENA (peripheral level) instead of relying on INTENABLE, and adds signalInterrupt to the dispatcher so sleepTicks can be woken by any interrupt.
230 lines
6.0 KiB
Go
230 lines
6.0 KiB
Go
//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<<uint(i)) != 0 {
|
|
// callHandlers requires a compile-time constant, so we
|
|
// dispatch through a switch.
|
|
callHandler(i)
|
|
}
|
|
}
|
|
|
|
// Signal to sleepTicks that an interrupt has occurred.
|
|
signalInterrupt()
|
|
|
|
inInterrupt = false
|
|
}
|
|
|
|
//go:inline
|
|
func callHandler(n int) {
|
|
switch n {
|
|
case 6:
|
|
callHandlers(6)
|
|
case 7:
|
|
callHandlers(7)
|
|
case 8:
|
|
callHandlers(8)
|
|
case 9:
|
|
callHandlers(9)
|
|
case 10:
|
|
callHandlers(10)
|
|
case 11:
|
|
callHandlers(11)
|
|
case 12:
|
|
callHandlers(12)
|
|
case 13:
|
|
callHandlers(13)
|
|
case 14:
|
|
callHandlers(14)
|
|
case 15:
|
|
callHandlers(15)
|
|
case 16:
|
|
callHandlers(16)
|
|
case 17:
|
|
callHandlers(17)
|
|
case 18:
|
|
callHandlers(18)
|
|
case 19:
|
|
callHandlers(19)
|
|
case 20:
|
|
callHandlers(20)
|
|
case 21:
|
|
callHandlers(21)
|
|
case 22:
|
|
callHandlers(22)
|
|
case 23:
|
|
callHandlers(23)
|
|
case 24:
|
|
callHandlers(24)
|
|
case 25:
|
|
callHandlers(25)
|
|
case 26:
|
|
callHandlers(26)
|
|
case 27:
|
|
callHandlers(27)
|
|
case 28:
|
|
callHandlers(28)
|
|
case 29:
|
|
callHandlers(29)
|
|
case 30:
|
|
callHandlers(30)
|
|
}
|
|
}
|
|
|
|
// callHandlers dispatches to registered interrupt handlers for a given
|
|
// interrupt number.
|
|
//
|
|
//go:linkname callHandlers runtime/interrupt.callHandlers
|
|
func callHandlers(num int)
|
|
|
|
//go:linkname signalInterrupt runtime.signalInterrupt
|
|
func signalInterrupt()
|
|
|
|
var errInterruptRange = constError("interrupt for ESP32-S3 must be in range 6 through 30")
|
|
|
|
type constError string
|
|
|
|
func (e constError) Error() string {
|
|
return string(e)
|
|
}
|
|
|
|
// readINTENABLE reads the INTENABLE special register (SR 228).
|
|
func readINTENABLE() uint32 {
|
|
return uint32(device.AsmFull("rsr {}, INTENABLE", nil))
|
|
}
|
|
|
|
// writeINTENABLE writes the INTENABLE special register (SR 228).
|
|
func writeINTENABLE(val uint32) {
|
|
device.AsmFull("wsr {val}, INTENABLE", map[string]interface{}{
|
|
"val": val,
|
|
})
|
|
}
|
|
|
|
// readINTERRUPT reads the INTERRUPT special register (SR 226), which
|
|
// reflects the currently pending CPU interrupts.
|
|
func readINTERRUPT() uint32 {
|
|
return uint32(device.AsmFull("rsr {}, INTERRUPT", nil))
|
|
}
|
|
|
|
// -- Interrupt matrix helpers -----------------------------------------------
|
|
// The ESP32-S3 interrupt matrix has one mapping register per peripheral
|
|
// source. These are memory-mapped in the INTERRUPT_CORE0 peripheral.
|
|
// The mapping register for peripheral source N is at:
|
|
// base + N*4 (where base = &INTERRUPT_CORE0.PRO_MAC_INTR_MAP)
|
|
//
|
|
// We provide helpers to set/get the mapping for any source number.
|
|
|
|
// mapPeripheralToInt routes peripheral IRQ source `src` to CPU interrupt
|
|
// `cpuInt` via the interrupt matrix.
|
|
func mapPeripheralToInt(src int, cpuInt int) {
|
|
base := unsafe.Pointer(&esp.INTERRUPT_CORE0.PRO_MAC_INTR_MAP)
|
|
reg := (*volatile.Register32)(unsafe.Add(base, uintptr(src)*4))
|
|
reg.Set(uint32(cpuInt))
|
|
}
|