diff --git a/src/device/riscv/start.S b/src/device/riscv/start.S index dd6ce327..06c9c4a2 100644 --- a/src/device/riscv/start.S +++ b/src/device/riscv/start.S @@ -5,8 +5,56 @@ _start: // Load the stack pointer. la sp, _stack_top + // Load the globals pointer. The program will load pointers relative to this // register, so it must be set to the right value on startup. // See: https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register la gp, __global_pointer$ + + // Jump to runtime.main call main + +.section .text.handleInterruptASM +.global handleInterruptASM +.type handleInterruptASM,@function +handleInterruptASM: + // Save and restore all registers, because the hardware only saves/restores + // the pc. + // Note: we have to do this in assembly because the "interrupt"="machine" + // attribute is broken in LLVM: https://bugs.llvm.org/show_bug.cgi?id=42984 + addi sp, sp, -64 + sw ra, 60(sp) + sw t0, 56(sp) + sw t1, 52(sp) + sw t2, 48(sp) + sw a0, 44(sp) + sw a1, 40(sp) + sw a2, 36(sp) + sw a3, 32(sp) + sw a4, 28(sp) + sw a5, 24(sp) + sw a6, 20(sp) + sw a7, 16(sp) + sw t3, 12(sp) + sw t4, 8(sp) + sw t5, 4(sp) + sw t6, 0(sp) + call handleInterrupt + lw t6, 0(sp) + lw t5, 4(sp) + lw t4, 8(sp) + lw t3, 12(sp) + lw a7, 16(sp) + lw a6, 20(sp) + lw a5, 24(sp) + lw a4, 28(sp) + lw a3, 32(sp) + lw a2, 36(sp) + lw a1, 40(sp) + lw a0, 44(sp) + lw t2, 48(sp) + lw t1, 52(sp) + lw t0, 56(sp) + lw ra, 60(sp) + addi sp, sp, 64 + mret diff --git a/src/runtime/runtime_fe310.go b/src/runtime/runtime_fe310.go index b7a3774b..c3a10bf3 100644 --- a/src/runtime/runtime_fe310.go +++ b/src/runtime/runtime_fe310.go @@ -9,7 +9,9 @@ import ( "machine" "unsafe" + "device/riscv" "device/sifive" + "runtime/volatile" ) type timeUnit int64 @@ -31,12 +33,47 @@ var _edata unsafe.Pointer //go:export main func main() { + // Zero the PLIC enable bits on startup: they are not zeroed at reset. + sifive.PLIC.ENABLE[0].Set(0) + sifive.PLIC.ENABLE[1].Set(0) + + // Set the interrupt address. + // Note that this address must be aligned specially, otherwise the MODE bits + // of MTVEC won't be zero. + riscv.MTVEC.Set(uintptr(unsafe.Pointer(&handleInterruptASM))) + + // Enable global interrupts now that they've been set up. + riscv.MSTATUS.SetBits(1 << 3) // MIE + preinit() initAll() callMain() abort() } +//go:extern handleInterruptASM +var handleInterruptASM [0]uintptr + +//export handleInterrupt +func handleInterrupt() { + cause := riscv.MCAUSE.Get() + code := uint(cause &^ (1 << 31)) + if cause&(1<<31) != 0 { + // Topmost bit is set, which means that it is an interrupt. + switch code { + case 7: // Machine timer interrupt + // Signal timeout. + timerWakeup.Set(1) + // Disable the timer, to avoid triggering the interrupt right after + // this interrupt returns. + riscv.MIE.ClearBits(1 << 7) // MTIE bit + } + } else { + // TODO: handle exceptions in a similar was as HardFault is handled on + // Cortex-M (by printing an error message with instruction address). + } +} + func init() { pric_init() machine.UART0.Configure(machine.UARTConfig{}) @@ -81,6 +118,8 @@ func putchar(c byte) { const asyncScheduler = false +var timerWakeup volatile.Register8 + func ticks() timeUnit { // Combining the low bits and the high bits yields a time span of over 270 // years without counter rollover. @@ -99,7 +138,16 @@ func ticks() timeUnit { } func sleepTicks(d timeUnit) { - target := ticks() + d - for ticks() < target { + target := uint64(ticks() + d) + sifive.CLINT.MTIMECMPH.Set(uint32(target >> 32)) + sifive.CLINT.MTIMECMP.Set(uint32(target)) + riscv.MIE.SetBits(1 << 7) // MTIE + for { + if timerWakeup.Get() != 0 { + timerWakeup.Set(0) + // Disable timer. + break + } + riscv.Asm("wfi") } -} \ No newline at end of file +} diff --git a/targets/riscv.ld b/targets/riscv.ld index 86bcfaf0..7f4427a8 100644 --- a/targets/riscv.ld +++ b/targets/riscv.ld @@ -2,7 +2,10 @@ SECTIONS { .text : { + . = ALIGN(4); KEEP(*(.init)) + . = ALIGN(4); + *(.text.handleInterruptASM) *(.text) *(.text.*) *(.rodata)