mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-05 03:27:48 +00:00
41e501aaf4
The timeUnit is now the same type everywhere. Move it to a single place and add some documentation to it.
172 lines
4.4 KiB
Go
172 lines
4.4 KiB
Go
//go:build tinygo.riscv && virt && qemu
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"device/riscv"
|
|
"runtime/volatile"
|
|
"unsafe"
|
|
)
|
|
|
|
// This file implements the VirtIO RISC-V interface implemented in QEMU, which
|
|
// is an interface designed for emulation.
|
|
|
|
//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)
|
|
}
|
|
|
|
// One tick is 100ns by default in QEMU.
|
|
// (This is not a standard, just the default used by QEMU).
|
|
func ticksToNanoseconds(ticks timeUnit) int64 {
|
|
return int64(ticks) * 100 // one tick is 100ns
|
|
}
|
|
|
|
func nanosecondsToTicks(ns int64) timeUnit {
|
|
return timeUnit(ns / 100) // one tick is 100ns
|
|
}
|
|
|
|
var timerWakeup volatile.Register8
|
|
|
|
func sleepTicks(d timeUnit) {
|
|
// 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 {
|
|
// 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.
|
|
// Source: https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c
|
|
// Technically this is an implementation detail but hopefully they won't change
|
|
// the memory-mapped I/O registers.
|
|
var (
|
|
// UART0 output register.
|
|
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) {
|
|
stdoutWrite.Set(uint8(c))
|
|
}
|
|
|
|
func getchar() byte {
|
|
// dummy, TODO
|
|
return 0
|
|
}
|
|
|
|
func buffered() int {
|
|
// dummy, TODO
|
|
return 0
|
|
}
|
|
|
|
func abort() {
|
|
exit(1)
|
|
}
|
|
|
|
func exit(code int) {
|
|
// Make sure the QEMU process exits.
|
|
if code == 0 {
|
|
testFinisher.Set(0x5555) // FINISHER_PASS
|
|
} else {
|
|
// Exit code is stored in the upper 16 bits of the 32 bit value.
|
|
testFinisher.Set(uint32(code)<<16 | 0x3333) // FINISHER_FAIL
|
|
}
|
|
|
|
// Lock up forever (as a fallback).
|
|
for {
|
|
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()
|
|
}
|