Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

riscv-qemu: actually sleep in time.Sleep() #4841

Merged
merged 2 commits into from
Apr 5, 2025
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
58 changes: 58 additions & 0 deletions src/device/riscv/csr.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,61 @@ const (
DPC CSR = 0x7B1 // Debug PC.
DSCRATCH CSR = 0x7B2 // Debug scratch register.
)

// Bitfields for the CSR registers above.
const (
// MSTATUS (common bits between RV32 and RV64)
MSTATUS_SIE = 1 << 1
MSTATUS_MIE = 1 << 3
MSTATUS_SPIE = 1 << 5
MSTATUS_UBE = 1 << 6
MSTATUS_MPIE = 1 << 7
MSTATUS_SPP = 1 << 8
MSTATUS_MPRV = 1 << 17
MSTATUS_SUM = 1 << 18
MSTATUS_MXR = 1 << 19
MSTATUS_TVM = 1 << 20
MSTATUS_TW = 1 << 21
MSTATUS_TSR = 1 << 22

MIE_SSIE = 1 << 1
MIE_MSIE = 1 << 3
MIE_STIE = 1 << 5
MIE_MTIE = 1 << 7
MIE_SEIE = 1 << 9
MIE_MEIE = 1 << 11

MIP_SSIP = 1 << 1
MIP_MSIP = 1 << 3
MIP_STIP = 1 << 5
MIP_MTIP = 1 << 7
MIP_SEIP = 1 << 9
MIP_MEIP = 1 << 11
)

// Interrupt constants
const (
// MCAUSE values with the topmost bit (interrupt bit) set.
SupervisorSoftwareInterrupt = 1
MachineSoftwareInterrupt = 3
SupervisorTimerInterrupt = 5
MachineTimerInterrupt = 7
SupervisorExternalInterrupt = 9
MachineExternalInterrupt = 11

// MCAUSE values with the topmost bit (interrupt bit) clear.
InstructionAddressMisaligned = 0
InstructionAccessFault = 1
IllegalInstruction = 2
Breakpoint = 3
LoadAddressMisaligned = 4
LoadAccessFault = 5
StoreOrAMOAddressMisaligned = 6
StoreOrAMOAccessFault = 7
EnvironmentCallFromUMode = 8
EnvironmentCallFromSMode = 9
EnvironmentCallFromMMode = 11
InstructionPageFault = 12
LoadPageFault = 13
StoreOrAMOPageFault = 15
)
14 changes: 7 additions & 7 deletions src/runtime/interrupt/interrupt_esp32c3.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,13 @@ func handleInterrupt() {
}

// enable CPU interrupts
riscv.MSTATUS.SetBits(1 << 3)
riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE)

// Call registered interrupt handler(s)
callHandler(int(interruptNumber))

// disable CPU interrupts
riscv.MSTATUS.ClearBits(1 << 3)
riscv.MSTATUS.ClearBits(riscv.MSTATUS_MIE)

// restore interrupt threshold to enable interrupt again
reg.Set(thresholdSave)
Expand All @@ -207,7 +207,7 @@ func handleInterrupt() {

// do not enable CPU interrupts now
// the 'MRET' in src/device/riscv/handleinterrupt.S will copies the state of MPIE back into MIE, and subsequently clears MPIE.
// riscv.MSTATUS.SetBits(0x8)
// riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE)
} else {
// Topmost bit is clear, so it is an exception of some sort.
// We could implement support for unsupported instructions here (such as
Expand All @@ -221,13 +221,13 @@ func handleException(mcause uintptr) {
println("*** Exception: code:", uint32(mcause&0x1f))
println("*** Exception: mcause:", mcause)
switch uint32(mcause & 0x1f) {
case 1:
case riscv.InstructionAccessFault:
println("*** virtual address:", riscv.MTVAL.Get())
case 2:
case riscv.IllegalInstruction:
println("*** opcode:", riscv.MTVAL.Get())
case 5:
case riscv.LoadAccessFault:
println("*** read address:", riscv.MTVAL.Get())
case 7:
case riscv.StoreOrAMOAccessFault:
println("*** write address:", riscv.MTVAL.Get())
}
for {
Expand Down
12 changes: 6 additions & 6 deletions src/runtime/runtime_fe310.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ func main() {

// Reset the MIE register and enable external interrupts.
// It must be reset here because it not zeroed at startup.
riscv.MIE.Set(1 << 11) // bit 11 is for machine external interrupts
riscv.MIE.Set(riscv.MIE_MEIE)

// Enable global interrupts now that they've been set up.
riscv.MSTATUS.SetBits(1 << 3) // MIE
riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE) // MIE: machine external interrupts

preinit()
initPeripherals()
Expand All @@ -59,13 +59,13 @@ func handleInterrupt() {
if cause&(1<<31) != 0 {
// Topmost bit is set, which means that it is an interrupt.
switch code {
case 7: // Machine timer interrupt
case riscv.MachineTimerInterrupt:
// 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
case 11: // Machine external interrupt
riscv.MIE.ClearBits(riscv.MIE_MTIE)
case riscv.MachineExternalInterrupt:
// Claim this interrupt.
id := sifive.PLIC.CLAIM.Get()
// Call the interrupt handler, if any is registered for this ID.
Expand Down Expand Up @@ -143,7 +143,7 @@ func sleepTicks(d timeUnit) {
target := uint64(ticks() + d)
sifive.CLINT.MTIMECMPH.Set(uint32(target >> 32))
sifive.CLINT.MTIMECMP.Set(uint32(target))
riscv.MIE.SetBits(1 << 7) // MTIE
riscv.MIE.SetBits(riscv.MIE_MTIE)
for {
if timerWakeup.Get() != 0 {
timerWakeup.Set(0)
Expand Down
12 changes: 6 additions & 6 deletions src/runtime/runtime_k210.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ func main() {

// Reset the MIE register and enable external interrupts.
// It must be reset here because it not zeroed at startup.
riscv.MIE.Set(1 << 11) // bit 11 is for machine external interrupts
riscv.MIE.Set(riscv.MIE_MEIE) // MEIE is for machine external interrupts

// Enable global interrupts now that they've been set up.
riscv.MSTATUS.SetBits(1 << 3) // MIE
riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE)

preinit()
initPeripherals()
Expand Down Expand Up @@ -77,13 +77,13 @@ func handleInterrupt() {
if cause&(1<<63) != 0 {
// Topmost bit is set, which means that it is an interrupt.
switch code {
case 7: // Machine timer interrupt
case riscv.MachineTimerInterrupt:
// 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
case 11: // Machine external interrupt
riscv.MIE.ClearBits(riscv.MIE_MTIE)
case riscv.MachineExternalInterrupt:
hartId := riscv.MHARTID.Get()

// Claim this interrupt.
Expand Down Expand Up @@ -149,7 +149,7 @@ func ticks() timeUnit {
func sleepTicks(d timeUnit) {
target := uint64(ticks() + d)
kendryte.CLINT.MTIMECMP[0].Set(target)
riscv.MIE.SetBits(1 << 7) // MTIE
riscv.MIE.SetBits(riscv.MIE_MTIE)
for {
if timerWakeup.Get() != 0 {
timerWakeup.Set(0)
Expand Down
103 changes: 96 additions & 7 deletions src/runtime/runtime_tinygoriscv_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -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()
}
2 changes: 1 addition & 1 deletion targets/riscv-qemu.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}"
}