mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 20:43:40 +00:00
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>
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
//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")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
//go:build esp32c6
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/esp"
|
||||
"device/riscv"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This is the function called on startup after the flash (IROM/DROM) is
|
||||
// initialized and the stack pointer has been set.
|
||||
//
|
||||
//export main
|
||||
func main() {
|
||||
// This initialization configures the following things:
|
||||
// * It disables all watchdog timers. They might be useful at some point in
|
||||
// the future, but will need integration into the scheduler. For now,
|
||||
// they're all disabled.
|
||||
// * It sets the CPU frequency to 160MHz, which is the maximum speed allowed
|
||||
// for this CPU. Lower frequencies might be possible in the future, but
|
||||
// running fast and sleeping quickly is often also a good strategy to save
|
||||
// power.
|
||||
|
||||
// Disable Timer Group 0 watchdog (unlock first).
|
||||
esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1)
|
||||
esp.TIMG0.WDTCONFIG0.Set(0)
|
||||
|
||||
// Disable Timer Group 1 watchdog (unlock first).
|
||||
esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1)
|
||||
esp.TIMG1.WDTCONFIG0.Set(0)
|
||||
|
||||
// Disable LP watchdog (write-protect key first).
|
||||
esp.LP_WDT.WDTWPROTECT.Set(0x50D83AA1)
|
||||
esp.LP_WDT.WDTCONFIG0.Set(0)
|
||||
|
||||
// Disable super watchdog.
|
||||
esp.LP_WDT.SWD_WPROTECT.Set(0x50D83AA1)
|
||||
esp.LP_WDT.SWD_CONF.SetBits(1 << 30) // SWD_DISABLE bit
|
||||
|
||||
// Change CPU frequency to 160MHz from SPLL (480MHz).
|
||||
//
|
||||
// Clock tree: SPLL (480MHz) → HP root → CPU / AHB / APB
|
||||
//
|
||||
// Set dividers BEFORE switching the clock source so the first PLL
|
||||
// cycle already arrives divided:
|
||||
// HP root = SPLL / (HS_DIV_NUM+1) = 480 / 3 = 160 MHz
|
||||
// CPU = HP root / (CPU_HS_DIV_NUM+1) = 160 / 1 = 160 MHz
|
||||
// AHB = HP root / (AHB_HS_DIV_NUM+1) = 160 / 4 = 40 MHz
|
||||
// APB = AHB / (APB_HS_DIV_NUM+1) = 40 / 1 = 40 MHz
|
||||
esp.PCR.CPU_FREQ_CONF.Set(0 << 8) // CPU_HS_DIV_NUM = 0 (div1)
|
||||
esp.PCR.AHB_FREQ_CONF.Set(3 << 8) // AHB_HS_DIV_NUM = 3 (div4)
|
||||
esp.PCR.APB_FREQ_CONF.Set(0 << 8) // APB_HS_DIV_NUM = 0 (div1)
|
||||
|
||||
// Switch to PLL: SOC_CLK_SEL = 1 (SPLL), HS_DIV_NUM = 2 (div3).
|
||||
esp.PCR.SYSCLK_CONF.Set(1<<16 | 2<<8)
|
||||
|
||||
clearbss()
|
||||
|
||||
// Configure interrupt handler
|
||||
interruptInit()
|
||||
|
||||
// Initialize main system timer used for time.Now.
|
||||
initTimer()
|
||||
|
||||
// Initialize timer alarm interrupt for the scheduler.
|
||||
initTimerInterrupt()
|
||||
|
||||
// Initialize the heap, call main.main, etc.
|
||||
run()
|
||||
|
||||
// Fallback: if main ever returns, hang the CPU.
|
||||
exit(0)
|
||||
}
|
||||
|
||||
func init() {
|
||||
machine.InitSerial()
|
||||
}
|
||||
|
||||
func abort() {
|
||||
for {
|
||||
riscv.Asm("wfi")
|
||||
}
|
||||
}
|
||||
|
||||
// interruptInit initializes the interrupt controller.
|
||||
func interruptInit() {
|
||||
mie := riscv.DisableInterrupts()
|
||||
|
||||
// Reset all interrupt priorities to zero in the PLIC.
|
||||
for i := 1; i < 32; i++ {
|
||||
plic.MXINT_PRI[i].Set(0)
|
||||
}
|
||||
|
||||
// Default threshold for interrupts is 5.
|
||||
plic.MXINT_THRESH.Set(5)
|
||||
|
||||
// Set the interrupt address.
|
||||
// Set MODE field to 1 - a vector base address.
|
||||
// Note that this address must be aligned to 256 bytes.
|
||||
riscv.MTVEC.Set((uintptr(unsafe.Pointer(&_vector_table))) | 1)
|
||||
|
||||
// On the ESP32-C6 the CPU receives PLIC interrupts through the standard
|
||||
// RISC-V mie CSR (machine interrupt-enable, 0x304). Unlike the ESP32-C3's
|
||||
// INTC, the PLIC's per-line interrupts are gated by mie, so it must be
|
||||
// enabled for any interrupt to reach the CPU. esp-hal does the same thing
|
||||
// for the PLIC: `csrw mie, 0xffffffff`.
|
||||
riscv.MIE.Set(0xffffffff)
|
||||
|
||||
// Globally enable machine-mode interrupts (MSTATUS.MIE).
|
||||
//
|
||||
// Unlike the ESP32-C3, whose ROM bootloader hands control to the
|
||||
// application with MSTATUS.MIE already set, the ESP32-C6 ROM leaves it
|
||||
// cleared. Restoring the previous state (riscv.EnableInterrupts(mie))
|
||||
// would therefore leave interrupts globally disabled, so no interrupt is
|
||||
// ever delivered to the CPU: timer alarms silently fall back to the
|
||||
// polling loop in sleepTicks and peripheral RX interrupts (such as the
|
||||
// USB Serial/JTAG controller) never fire. Set the bit explicitly instead.
|
||||
_ = mie
|
||||
riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE)
|
||||
}
|
||||
|
||||
// CPU interrupt number used for the TIMG0 timer alarm.
|
||||
const timerAlarmCPUInterrupt = 9
|
||||
|
||||
var interruptPending volatile.Register8
|
||||
|
||||
func signalInterrupt() {
|
||||
interruptPending.Set(1)
|
||||
}
|
||||
|
||||
// initTimerInterrupt routes the TIMG0 timer 0 alarm interrupt to a CPU
|
||||
// interrupt and registers a handler.
|
||||
func initTimerInterrupt() {
|
||||
// Map the TIMG0 T0 peripheral interrupt to a CPU interrupt line.
|
||||
// On C6, INTERRUPT_CORE0 is for peripheral→CPU interrupt mapping.
|
||||
esp.INTERRUPT_CORE0.TG0_T0_INTR_MAP.Set(timerAlarmCPUInterrupt)
|
||||
|
||||
// Enable T0 interrupt at the timer group level.
|
||||
esp.TIMG0.INT_ENA_TIMERS.SetBits(1)
|
||||
|
||||
// Register the interrupt handler.
|
||||
interrupt.New(timerAlarmCPUInterrupt, func(interrupt.Interrupt) {
|
||||
esp.TIMG0.INT_CLR_TIMERS.Set(1)
|
||||
})
|
||||
|
||||
// Enable the CPU interrupt:
|
||||
mie := riscv.DisableInterrupts()
|
||||
|
||||
// Clear any stale pending bit.
|
||||
plic.MXINT_CLEAR.SetBits(1 << timerAlarmCPUInterrupt)
|
||||
plic.MXINT_CLEAR.ClearBits(1 << timerAlarmCPUInterrupt)
|
||||
|
||||
// Set edge-triggered.
|
||||
plic.MXINT_TYPE.SetBits(1 << timerAlarmCPUInterrupt)
|
||||
|
||||
// Set priority above threshold.
|
||||
plic.MXINT_PRI[timerAlarmCPUInterrupt].Set(10)
|
||||
|
||||
riscv.Asm("fence")
|
||||
|
||||
plic.MXINT_ENABLE.SetBits(1 << timerAlarmCPUInterrupt)
|
||||
|
||||
riscv.EnableInterrupts(mie)
|
||||
}
|
||||
|
||||
// sleepTicks spins until the given number of ticks have elapsed, using the
|
||||
// TIMG0 alarm interrupt to avoid busy-waiting for the entire duration.
|
||||
func sleepTicks(d timeUnit) {
|
||||
machine.FlushSerial()
|
||||
target := ticks() + d
|
||||
for ticks() < target {
|
||||
interruptPending.Set(0)
|
||||
|
||||
esp.TIMG0.T0ALARMLO.Set(uint32(target))
|
||||
esp.TIMG0.T0ALARMHI.Set(uint32(target >> 32))
|
||||
|
||||
// Enable the alarm (auto-clears when alarm fires).
|
||||
esp.TIMG0.T0CONFIG.SetBits(esp.TIMG_T0CONFIG_ALARM_EN)
|
||||
|
||||
for interruptPending.Get() == 0 {
|
||||
if ticks() >= target {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go:extern _vector_table
|
||||
var _vector_table [0]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)))
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build esp32 || esp32c3
|
||||
//go:build esp32 || esp32c3 || esp32c6
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
Reference in New Issue
Block a user