Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion builder/sizes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 3668, 280, 0, 2244},
{"hifive1b", "examples/echo", 3680, 280, 0, 2252},
{"microbit", "examples/serial", 2694, 342, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},

Expand Down
11 changes: 11 additions & 0 deletions src/device/riscv/handleinterrupt.S
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ handleInterruptASM:
SFREG f30,(30 + 16)*REGSIZE(sp)
SFREG f31,(31 + 16)*REGSIZE(sp)
#endif
// Save ra to a global so handleException can print the caller of the NULL
// function pointer.
la t0, tinygo_saved_ra
SREG ra, 0(t0)
call handleInterrupt
#ifdef __riscv_flen
LFREG f0, (31 + 16)*REGSIZE(sp)
Expand Down Expand Up @@ -128,3 +132,10 @@ handleInterruptASM:
LREG ra, 0*REGSIZE(sp)
addi sp, sp, NREG*REGSIZE
mret

.section .bss.tinygo_saved_ra
.global tinygo_saved_ra
.type tinygo_saved_ra,@object
.align 2
tinygo_saved_ra:
.space REGSIZE
2 changes: 1 addition & 1 deletion src/machine/machine_esp32c3_usb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 17 additions & 1 deletion src/runtime/interrupt/interrupt_esp32c3.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
"unsafe"
)

//go:extern tinygo_saved_ra
var tinygo_saved_ra uintptr

// Enable register CPU interrupt with interrupt.Interrupt.
// The ESP32-C3 has 31 CPU independent interrupts.
// The Interrupt.New(x, f) (x = [1..31]) attaches CPU interrupt to function f.
Expand Down Expand Up @@ -52,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
Expand Down Expand Up @@ -88,7 +94,13 @@ const (

const (
defaultThreshold = 5
disableThreshold = 10
// Priority 0 disables an interrupt on ESP32-C3 (per the TRM,
// priority 0 = masked). During handling we set the current
// interrupt's priority to 0 so it cannot re-fire while MIE is
// re-enabled for nesting. This is critical for level-triggered
// interrupts where the hardware line stays asserted until the
// peripheral source is serviced by the handler.
disableThreshold = 0
)

//go:inline
Expand Down Expand Up @@ -194,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)

Expand Down Expand Up @@ -225,6 +240,7 @@ 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())
Expand Down
8 changes: 8 additions & 0 deletions src/runtime/runtime_esp32.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
74 changes: 74 additions & 0 deletions src/runtime/runtime_esp32c3.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"device/esp"
"device/riscv"
"machine"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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
8 changes: 0 additions & 8 deletions src/runtime/runtime_esp32xx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
Loading