mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 19:17:47 +00:00
riscv-qemu: actually sleep in time.Sleep()
Instead of just incrementing the timestamp, this causes the system to
actually sleep when calling time.Sleep. The direct effect is that this
works as expected:
$ tinygo run -target=riscv-qemu examples/serial
hello world!
hello world!
hello world!
[..etc]
This commit also adds a bare bones handler for exceptions (such as
invalid memory writes), since we're adding an interrupt handler anyway.
While this patch doesn't add that much functionality, having interrupt
support is going to be needed for multicore support on riscv-qemu. My
plan is to first add this support to riscv-qemu (based on the earlier
work I did for the RP2040 and demoed at FOSDEM 2025) and once the basics
are in place and fully tested we can extend this support to the RP2040.
Writing for QEMU first makes it much easier to debug any issues that
will come up.
This commit is contained in:
committed by
Ron Evans
parent
4bce85dc09
commit
dcf609defb
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {}"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user