esp32s3: replace inline ISR with full interrupt vector handler

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.
This commit is contained in:
deadprogram
2026-04-02 14:09:31 +02:00
parent 3386316b9f
commit a6a4f85e61
3 changed files with 139 additions and 15 deletions
+6 -7
View File
@@ -112,10 +112,11 @@ func (usbdev *USB_DEVICE) ensureConfigured() {
}
// handleInterrupt is called from the CPU interrupt vector when the USB
// peripheral raises an interrupt. For now, just clear the interrupt flag.
// The actual data drain happens in Buffered() via polling — once the ISR
// mechanism is proven, we can move the drain here.
// peripheral raises an interrupt. Disable INT_ENA to prevent the
// level-triggered interrupt from re-asserting immediately (data may
// still be in the FIFO). Buffered() re-enables after draining.
func (usbdev *USB_DEVICE) handleInterrupt() {
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(0)
usbdev.Bus.SetINT_CLR_SERIAL_OUT_RECV_PKT_INT_CLR(1)
}
@@ -162,7 +163,7 @@ func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) {
// Buffered returns the number of bytes waiting in the receive ring buffer.
// It drains any data sitting in the hardware FIFO and re-enables the
// USB interrupt (which the ISR disables via INTENABLE to prevent a
// peripheral-level USB interrupt (which the ISR disables to prevent a
// level-triggered interrupt storm).
func (usbdev *USB_DEVICE) Buffered() int {
usbdev.ensureConfigured()
@@ -171,11 +172,9 @@ func (usbdev *USB_DEVICE) Buffered() int {
b := byte(usbdev.Bus.EP1.Get())
usbdev.Buffer.Put(b)
}
// Clear pending flags and re-enable the RX interrupt.
// Clear pending flags and re-enable the RX interrupt at the peripheral level.
usbdev.Bus.INT_CLR.Set(0xFFFFFFFF)
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
// Re-enable CPU interrupt 8 in INTENABLE (the ISR clears all bits).
interrupt.New(cpuInterruptFromUSB, usbHandleInterrupt).Enable()
return int(usbdev.Buffer.Used())
}
@@ -115,6 +115,9 @@ func handleInterrupt() {
}
}
// Signal to sleepTicks that an interrupt has occurred.
signalInterrupt()
inInterrupt = false
}
@@ -180,6 +183,9 @@ func callHandler(n int) {
//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