Files
tinygo/src/runtime/interrupt/interrupt_esp32c6.go
T
deadprogram 5f66260c3a machine,targets: add minimal esp32c6 implementation
This adds a minimal esp32c6 implementation, currently only
supporting the examples/serial and examples/blinky1 programs.
It does correctly output the expected "Hello, World" via the
serial port, as well as blink the onboard LED.

In addition, it adds support for the PLIC based IRQ handling
as used on the ESP32C6 processor.

Some parts of this code are loosely based on PR #5252 and #5248

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-25 19:30:24 +02:00

251 lines
6.2 KiB
Go

//go:build esp32c6
package interrupt
import (
"device/riscv"
"errors"
"runtime/volatile"
"unsafe"
)
//go:extern tinygo_saved_ra
var tinygo_saved_ra uintptr
// plicType maps the ESP32-C6 PLIC (Platform-Level Interrupt Controller)
// machine-mode registers. The C6 uses the PLIC — not the INTPRI/INTC block
// that the ESP32-C3 uses — as its CPU interrupt controller
// (SOC_INT_PLIC_SUPPORTED). The INTPRI registers at 0x600c5000 are vestigial
// backward-compatibility aliases on the C6 and are not wired to the CPU, so
// writing them never delivers an interrupt.
type plicType struct {
MXINT_ENABLE volatile.Register32 // 0x00 bit N enables CPU interrupt line N
MXINT_TYPE volatile.Register32 // 0x04 bit N: 1=edge, 0=level
MXINT_CLEAR volatile.Register32 // 0x08 edge acknowledge
MXINT_EIP_STATUS volatile.Register32 // 0x0C pending status (read-only)
MXINT_PRI [32]volatile.Register32 // 0x10..0x8C per-line priority (4 bits)
MXINT_THRESH volatile.Register32 // 0x90 priority threshold (8 bits)
}
// plic points at the PLIC machine-mode register block (DR_REG_PLIC_MX_BASE).
var plic = (*plicType)(unsafe.Pointer(uintptr(0x20001000)))
// Enable registers a CPU interrupt.
// The ESP32-C6 has 31 CPU independent interrupts (1..31).
func (i Interrupt) Enable() error {
if i.num < 1 || i.num > 31 {
return errors.New("interrupt for ESP32-C6 must be in range of 1 through 31")
}
mask := riscv.DisableInterrupts()
defer riscv.EnableInterrupts(mask)
// Enable CPU interrupt number i.num via the PLIC.
plic.MXINT_ENABLE.SetBits(1 << i.num)
// Set pulse interrupt type (rising edge detection).
plic.MXINT_TYPE.SetBits(1 << i.num)
// Set default priority (must be >= threshold to be delivered).
plic.MXINT_PRI[i.num].Set(defaultThreshold)
// Reset interrupt before re-enabling.
plic.MXINT_CLEAR.SetBits(1 << i.num)
plic.MXINT_CLEAR.ClearBits(1 << i.num)
riscv.Asm("fence")
return nil
}
// Adding pseudo function calls that is replaced by the compiler with the actual
// functions registered through interrupt.New.
//
//go:linkname callHandlers runtime/interrupt.callHandlers
func callHandlers(num int)
//go:linkname signalInterrupt runtime.signalInterrupt
func signalInterrupt()
const (
IRQNUM_1 = 1 + iota
IRQNUM_2
IRQNUM_3
IRQNUM_4
IRQNUM_5
IRQNUM_6
IRQNUM_7
IRQNUM_8
IRQNUM_9
IRQNUM_10
IRQNUM_11
IRQNUM_12
IRQNUM_13
IRQNUM_14
IRQNUM_15
IRQNUM_16
IRQNUM_17
IRQNUM_18
IRQNUM_19
IRQNUM_20
IRQNUM_21
IRQNUM_22
IRQNUM_23
IRQNUM_24
IRQNUM_25
IRQNUM_26
IRQNUM_27
IRQNUM_28
IRQNUM_29
IRQNUM_30
IRQNUM_31
)
const (
defaultThreshold = 5
// Priority 0 disables an interrupt on ESP32-C6.
disableThreshold = 0
)
//go:inline
func callHandler(n int) {
switch n {
case IRQNUM_1:
callHandlers(IRQNUM_1)
case IRQNUM_2:
callHandlers(IRQNUM_2)
case IRQNUM_3:
callHandlers(IRQNUM_3)
case IRQNUM_4:
callHandlers(IRQNUM_4)
case IRQNUM_5:
callHandlers(IRQNUM_5)
case IRQNUM_6:
callHandlers(IRQNUM_6)
case IRQNUM_7:
callHandlers(IRQNUM_7)
case IRQNUM_8:
callHandlers(IRQNUM_8)
case IRQNUM_9:
callHandlers(IRQNUM_9)
case IRQNUM_10:
callHandlers(IRQNUM_10)
case IRQNUM_11:
callHandlers(IRQNUM_11)
case IRQNUM_12:
callHandlers(IRQNUM_12)
case IRQNUM_13:
callHandlers(IRQNUM_13)
case IRQNUM_14:
callHandlers(IRQNUM_14)
case IRQNUM_15:
callHandlers(IRQNUM_15)
case IRQNUM_16:
callHandlers(IRQNUM_16)
case IRQNUM_17:
callHandlers(IRQNUM_17)
case IRQNUM_18:
callHandlers(IRQNUM_18)
case IRQNUM_19:
callHandlers(IRQNUM_19)
case IRQNUM_20:
callHandlers(IRQNUM_20)
case IRQNUM_21:
callHandlers(IRQNUM_21)
case IRQNUM_22:
callHandlers(IRQNUM_22)
case IRQNUM_23:
callHandlers(IRQNUM_23)
case IRQNUM_24:
callHandlers(IRQNUM_24)
case IRQNUM_25:
callHandlers(IRQNUM_25)
case IRQNUM_26:
callHandlers(IRQNUM_26)
case IRQNUM_27:
callHandlers(IRQNUM_27)
case IRQNUM_28:
callHandlers(IRQNUM_28)
case IRQNUM_29:
callHandlers(IRQNUM_29)
case IRQNUM_30:
callHandlers(IRQNUM_30)
case IRQNUM_31:
callHandlers(IRQNUM_31)
}
}
//export handleInterrupt
func handleInterrupt() {
mcause := riscv.MCAUSE.Get()
exception := mcause&(1<<31) == 0
interruptNumber := uint32(mcause & 0x1f)
if !exception && interruptNumber > 0 {
// Save MSTATUS & MEPC, which could be overwritten by another CPU interrupt.
mstatus := riscv.MSTATUS.Get()
mepc := riscv.MEPC.Get()
// Temporarily disable this interrupt by lowering its PLIC priority
// below the threshold.
thresholdSave := plic.MXINT_PRI[interruptNumber].Get()
plic.MXINT_PRI[interruptNumber].Set(disableThreshold)
riscv.Asm("fence")
interruptBit := uint32(1 << interruptNumber)
// Reset pending status interrupt.
if plic.MXINT_TYPE.Get()&interruptBit != 0 {
// Edge type interrupt.
plic.MXINT_CLEAR.SetBits(interruptBit)
plic.MXINT_CLEAR.ClearBits(interruptBit)
} else {
// Level type interrupt.
plic.MXINT_CLEAR.ClearBits(interruptBit)
}
// Enable CPU interrupts.
riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE)
// Call registered interrupt handler(s).
callHandler(int(interruptNumber))
// Signal to sleepTicks that an interrupt has occurred.
signalInterrupt()
// Disable CPU interrupts.
riscv.MSTATUS.ClearBits(riscv.MSTATUS_MIE)
// Restore interrupt priority to enable interrupt again.
plic.MXINT_PRI[interruptNumber].Set(thresholdSave)
riscv.Asm("fence")
// Zero MCAUSE so that interrupt.In() returns false once we
// return to normal (non-interrupt) code.
riscv.MCAUSE.Set(0)
// Restore MSTATUS & MEPC.
riscv.MSTATUS.Set(mstatus)
riscv.MEPC.Set(mepc)
} else {
handleException(mcause)
}
}
func handleException(mcause uintptr) {
println("*** Exception: pc:", riscv.MEPC.Get())
println("*** Exception: code:", uint32(mcause&0x1f))
println("*** Exception: mcause:", mcause)
println("*** Exception: ra:", tinygo_saved_ra)
switch uint32(mcause & 0x1f) {
case riscv.InstructionAccessFault:
println("*** virtual address:", riscv.MTVAL.Get())
case riscv.IllegalInstruction:
println("*** opcode:", riscv.MTVAL.Get())
case riscv.LoadAccessFault:
println("*** read address:", riscv.MTVAL.Get())
case riscv.StoreOrAMOAccessFault:
println("*** write address:", riscv.MTVAL.Get())
}
for {
riscv.Asm("wfi")
}
}