diff --git a/src/runtime/runtime_tinygoriscv_qemu.go b/src/runtime/runtime_tinygoriscv_qemu.go index e68041890..e58e75351 100644 --- a/src/runtime/runtime_tinygoriscv_qemu.go +++ b/src/runtime/runtime_tinygoriscv_qemu.go @@ -11,32 +11,98 @@ import ( // This file implements the VirtIO RISC-V interface implemented in QEMU, which // is an interface designed for emulation. +// One tick is 100ns by default in QEMU. +// (This is not a standard, just the default used by QEMU). type timeUnit int64 -var timestamp timeUnit - //export main func main() { preinit() + + // 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. + // This is currently only for timer interrupts. + riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE) + run() exit(0) } +//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 riscv.MachineTimerInterrupt: + // Signal timeout. + timerWakeup.Set(1) + // Disable the timer, to avoid triggering the interrupt right after + // this interrupt returns. + riscv.MIE.ClearBits(riscv.MIE_MTIE) + } + } else { + // Topmost bit is clear, so it is an exception of some sort. + // We could implement support for unsupported instructions here (such as + // misaligned loads). However, for now we'll just print a fatal error. + handleException(code) + } + + // Zero MCAUSE so that it can later be used to see whether we're in an + // interrupt or not. + riscv.MCAUSE.Set(0) +} + func ticksToNanoseconds(ticks timeUnit) int64 { - return int64(ticks) + return int64(ticks) * 100 // one tick is 100ns } func nanosecondsToTicks(ns int64) timeUnit { - return timeUnit(ns) + return timeUnit(ns / 100) // one tick is 100ns } +var timerWakeup volatile.Register8 + func sleepTicks(d timeUnit) { - // TODO: actually sleep here for the given time. - timestamp += d + // Enable the timer. + target := uint64(ticks() + d) + aclintMTIMECMP.Set(target) + riscv.MIE.SetBits(riscv.MIE_MTIE) + + // Wait until it fires. + for { + if timerWakeup.Get() != 0 { + timerWakeup.Set(0) + // Disable timer. + break + } + riscv.Asm("wfi") + } } func ticks() timeUnit { - return timestamp + // Combining the low bits and the high bits (at a rate of 100ns per tick) + // yields a time span of over 59930 years without counter rollover. + highBits := aclintMTIME.high.Get() + for { + lowBits := aclintMTIME.low.Get() + newHighBits := aclintMTIME.high.Get() + if newHighBits == highBits { + // High bits stayed the same. + return timeUnit(lowBits) | (timeUnit(highBits) << 32) + } + // Retry, because there was a rollover in the low bits (happening every + // 429 days). + highBits = newHighBits + } } // Memory-mapped I/O as defined by QEMU. @@ -48,6 +114,15 @@ var ( stdoutWrite = (*volatile.Register8)(unsafe.Pointer(uintptr(0x10000000))) // SiFive test finisher testFinisher = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000))) + + // RISC-V Advanced Core Local Interruptor. + // It is backwards compatible with the SiFive CLINT. + // https://github.com/riscvarchive/riscv-aclint/blob/main/riscv-aclint.adoc + aclintMTIME = (*struct { + low volatile.Register32 + high volatile.Register32 + })(unsafe.Pointer(uintptr(0x0200_bff8))) + aclintMTIMECMP = (*volatile.Register64)(unsafe.Pointer(uintptr(0x0200_4000))) ) func putchar(c byte) { @@ -82,3 +157,17 @@ func exit(code int) { riscv.Asm("wfi") } } + +// handleException is called from the interrupt handler for any exception. +// Exceptions can be things like illegal instructions, invalid memory +// read/write, and similar issues. +func handleException(code uint) { + // For a list of exception codes, see: + // https://content.riscv.org/wp-content/uploads/2019/08/riscv-privileged-20190608-1.pdf#page=49 + print("fatal error: exception with mcause=") + print(code) + print(" pc=") + print(riscv.MEPC.Get()) + println() + abort() +} diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index 90f1c312f..8a85cf9a7 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -4,5 +4,5 @@ "build-tags": ["virt", "qemu"], "default-stack-size": 8192, "linkerscript": "targets/riscv-qemu.ld", - "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -device virtio-rng-device -kernel {}" + "emulator": "qemu-system-riscv32 -machine virt,aclint=on -nographic -bios none -device virtio-rng-device -kernel {}" }