diff --git a/src/machine/machine_esp32c3_usb.go b/src/machine/machine_esp32c3_usb.go index 06fdca913..648034824 100644 --- a/src/machine/machine_esp32c3_usb.go +++ b/src/machine/machine_esp32c3_usb.go @@ -17,7 +17,7 @@ import ( // CDC-ACM serial port. The USB protocol and enumeration are handled entirely // in hardware; software only reads/writes the EP1 FIFO. -const cpuInterruptFromUSB = 8 +const cpuInterruptFromUSB = 10 // flushTimeout is the maximum number of busy-wait iterations in flush(). // Prevents hanging when no USB host is connected. diff --git a/src/runtime/interrupt/interrupt_esp32c3.go b/src/runtime/interrupt/interrupt_esp32c3.go index 82e3d13ea..86a4db4cf 100644 --- a/src/runtime/interrupt/interrupt_esp32c3.go +++ b/src/runtime/interrupt/interrupt_esp32c3.go @@ -55,6 +55,9 @@ func (i Interrupt) Enable() error { //go:linkname callHandlers runtime/interrupt.callHandlers func callHandlers(num int) +//go:linkname signalInterrupt runtime.signalInterrupt +func signalInterrupt() + const ( IRQNUM_1 = 1 + iota IRQNUM_2 @@ -203,6 +206,9 @@ func handleInterrupt() { // 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) diff --git a/src/runtime/runtime_esp32.go b/src/runtime/runtime_esp32.go index 39219bb03..2e734c5e5 100644 --- a/src/runtime/runtime_esp32.go +++ b/src/runtime/runtime_esp32.go @@ -65,6 +65,14 @@ var _sbss [0]byte //go:extern _ebss var _ebss [0]byte +// sleepTicks busy-waits until the given number of ticks have passed. +func sleepTicks(d timeUnit) { + sleepUntil := ticks() + d + for ticks() < sleepUntil { + // TODO: suspend the CPU to not burn power here unnecessarily. + } +} + func abort() { for { device.Asm("waiti 0") diff --git a/src/runtime/runtime_esp32c3.go b/src/runtime/runtime_esp32c3.go index f85f7dec7..abae59533 100644 --- a/src/runtime/runtime_esp32c3.go +++ b/src/runtime/runtime_esp32c3.go @@ -6,6 +6,7 @@ import ( "device/esp" "device/riscv" "machine" + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -57,6 +58,9 @@ func main() { // 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() @@ -98,5 +102,75 @@ func interruptInit() { riscv.EnableInterrupts(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 that signals timerWakeup. +func initTimerInterrupt() { + // Map the TIMG0 T0 peripheral interrupt to a CPU interrupt line. + esp.INTERRUPT_CORE0.TG_T0_INT_MAP.Set(timerAlarmCPUInterrupt) + + // Enable T0 interrupt at the timer group level. + esp.TIMG0.INT_ENA_TIMERS.SetBits(1) + + // Register the interrupt handler (compile-time wiring). + interrupt.New(timerAlarmCPUInterrupt, func(interrupt.Interrupt) { + // Clear the timer interrupt at the peripheral level. + esp.TIMG0.INT_CLR_TIMERS.Set(1) + }) + + // Manually enable the CPU interrupt with correct ordering: + // 1) clear any stale pending bit first + // 2) set edge-triggered + // 3) set priority above threshold + // 4) enable the interrupt last + mie := riscv.DisableInterrupts() + + esp.INTERRUPT_CORE0.CPU_INT_CLEAR.SetBits(1 << timerAlarmCPUInterrupt) + esp.INTERRUPT_CORE0.CPU_INT_CLEAR.ClearBits(1 << timerAlarmCPUInterrupt) + + esp.INTERRUPT_CORE0.CPU_INT_TYPE.SetBits(1 << timerAlarmCPUInterrupt) + + priReg := (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.INTERRUPT_CORE0.CPU_INT_PRI_0), timerAlarmCPUInterrupt*4)) + priReg.Set(10) + + riscv.Asm("fence") + + esp.INTERRUPT_CORE0.CPU_INT_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) { + target := ticks() + d + for ticks() < target { + // Set the alarm to fire at the target tick count (or as close + // as the 54-bit counter allows). + 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) + + // Wait for any interrupt (timer alarm or other) or a timeout. + for interruptPending.Get() == 0 { + if ticks() >= target { + return + } + } + } +} + //go:extern _vector_table var _vector_table [0]uintptr diff --git a/src/runtime/runtime_esp32xx.go b/src/runtime/runtime_esp32xx.go index f1c62243f..cde65b8cb 100644 --- a/src/runtime/runtime_esp32xx.go +++ b/src/runtime/runtime_esp32xx.go @@ -55,14 +55,6 @@ func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * 25 } -// sleepTicks busy-waits until the given number of ticks have passed. -func sleepTicks(d timeUnit) { - sleepUntil := ticks() + d - for ticks() < sleepUntil { - // TODO: suspend the CPU to not burn power here unnecessarily. - } -} - func exit(code int) { abort() }