diff --git a/src/device/riscv/csr.go b/src/device/riscv/csr.go index 510a7b8916..52e97a262d 100644 --- a/src/device/riscv/csr.go +++ b/src/device/riscv/csr.go @@ -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 +) diff --git a/src/runtime/interrupt/interrupt_esp32c3.go b/src/runtime/interrupt/interrupt_esp32c3.go index 4e3e3dccfc..5725cd3524 100644 --- a/src/runtime/interrupt/interrupt_esp32c3.go +++ b/src/runtime/interrupt/interrupt_esp32c3.go @@ -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) @@ -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 @@ -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 { diff --git a/src/runtime/runtime_fe310.go b/src/runtime/runtime_fe310.go index f65c39a4df..218962edb5 100644 --- a/src/runtime/runtime_fe310.go +++ b/src/runtime/runtime_fe310.go @@ -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() @@ -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. @@ -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) diff --git a/src/runtime/runtime_k210.go b/src/runtime/runtime_k210.go index 96cdd63408..8ee79b938d 100644 --- a/src/runtime/runtime_k210.go +++ b/src/runtime/runtime_k210.go @@ -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() @@ -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. @@ -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) diff --git a/src/runtime/runtime_tinygoriscv_qemu.go b/src/runtime/runtime_tinygoriscv_qemu.go index e68041890d..e58e753516 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 90f1c312fc..8a85cf9a72 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 {}" }