@@ -11,32 +11,98 @@ import (
11
11
// This file implements the VirtIO RISC-V interface implemented in QEMU, which
12
12
// is an interface designed for emulation.
13
13
14
+ // One tick is 100ns by default in QEMU.
15
+ // (This is not a standard, just the default used by QEMU).
14
16
type timeUnit int64
15
17
16
- var timestamp timeUnit
17
-
18
18
//export main
19
19
func main () {
20
20
preinit ()
21
+
22
+ // Set the interrupt address.
23
+ // Note that this address must be aligned specially, otherwise the MODE bits
24
+ // of MTVEC won't be zero.
25
+ riscv .MTVEC .Set (uintptr (unsafe .Pointer (& handleInterruptASM )))
26
+
27
+ // Enable global interrupts now that they've been set up.
28
+ // This is currently only for timer interrupts.
29
+ riscv .MSTATUS .SetBits (riscv .MSTATUS_MIE )
30
+
21
31
run ()
22
32
exit (0 )
23
33
}
24
34
35
+ //go:extern handleInterruptASM
36
+ var handleInterruptASM [0 ]uintptr
37
+
38
+ //export handleInterrupt
39
+ func handleInterrupt () {
40
+ cause := riscv .MCAUSE .Get ()
41
+ code := uint (cause &^ (1 << 31 ))
42
+ if cause & (1 << 31 ) != 0 {
43
+ // Topmost bit is set, which means that it is an interrupt.
44
+ switch code {
45
+ case riscv .MachineTimerInterrupt :
46
+ // Signal timeout.
47
+ timerWakeup .Set (1 )
48
+ // Disable the timer, to avoid triggering the interrupt right after
49
+ // this interrupt returns.
50
+ riscv .MIE .ClearBits (riscv .MIE_MTIE )
51
+ }
52
+ } else {
53
+ // Topmost bit is clear, so it is an exception of some sort.
54
+ // We could implement support for unsupported instructions here (such as
55
+ // misaligned loads). However, for now we'll just print a fatal error.
56
+ handleException (code )
57
+ }
58
+
59
+ // Zero MCAUSE so that it can later be used to see whether we're in an
60
+ // interrupt or not.
61
+ riscv .MCAUSE .Set (0 )
62
+ }
63
+
25
64
func ticksToNanoseconds (ticks timeUnit ) int64 {
26
- return int64 (ticks )
65
+ return int64 (ticks ) * 100 // one tick is 100ns
27
66
}
28
67
29
68
func nanosecondsToTicks (ns int64 ) timeUnit {
30
- return timeUnit (ns )
69
+ return timeUnit (ns / 100 ) // one tick is 100ns
31
70
}
32
71
72
+ var timerWakeup volatile.Register8
73
+
33
74
func sleepTicks (d timeUnit ) {
34
- // TODO: actually sleep here for the given time.
35
- timestamp += d
75
+ // Enable the timer.
76
+ target := uint64 (ticks () + d )
77
+ aclintMTIMECMP .Set (target )
78
+ riscv .MIE .SetBits (riscv .MIE_MTIE )
79
+
80
+ // Wait until it fires.
81
+ for {
82
+ if timerWakeup .Get () != 0 {
83
+ timerWakeup .Set (0 )
84
+ // Disable timer.
85
+ break
86
+ }
87
+ riscv .Asm ("wfi" )
88
+ }
36
89
}
37
90
38
91
func ticks () timeUnit {
39
- return timestamp
92
+ // Combining the low bits and the high bits (at a rate of 100ns per tick)
93
+ // yields a time span of over 59930 years without counter rollover.
94
+ highBits := aclintMTIME .high .Get ()
95
+ for {
96
+ lowBits := aclintMTIME .low .Get ()
97
+ newHighBits := aclintMTIME .high .Get ()
98
+ if newHighBits == highBits {
99
+ // High bits stayed the same.
100
+ return timeUnit (lowBits ) | (timeUnit (highBits ) << 32 )
101
+ }
102
+ // Retry, because there was a rollover in the low bits (happening every
103
+ // 429 days).
104
+ highBits = newHighBits
105
+ }
40
106
}
41
107
42
108
// Memory-mapped I/O as defined by QEMU.
@@ -48,6 +114,15 @@ var (
48
114
stdoutWrite = (* volatile .Register8 )(unsafe .Pointer (uintptr (0x10000000 )))
49
115
// SiFive test finisher
50
116
testFinisher = (* volatile .Register32 )(unsafe .Pointer (uintptr (0x100000 )))
117
+
118
+ // RISC-V Advanced Core Local Interruptor.
119
+ // It is backwards compatible with the SiFive CLINT.
120
+ // https://github.com/riscvarchive/riscv-aclint/blob/main/riscv-aclint.adoc
121
+ aclintMTIME = (* struct {
122
+ low volatile.Register32
123
+ high volatile.Register32
124
+ })(unsafe .Pointer (uintptr (0x0200_bff8 )))
125
+ aclintMTIMECMP = (* volatile .Register64 )(unsafe .Pointer (uintptr (0x0200_4000 )))
51
126
)
52
127
53
128
func putchar (c byte ) {
@@ -82,3 +157,17 @@ func exit(code int) {
82
157
riscv .Asm ("wfi" )
83
158
}
84
159
}
160
+
161
+ // handleException is called from the interrupt handler for any exception.
162
+ // Exceptions can be things like illegal instructions, invalid memory
163
+ // read/write, and similar issues.
164
+ func handleException (code uint ) {
165
+ // For a list of exception codes, see:
166
+ // https://content.riscv.org/wp-content/uploads/2019/08/riscv-privileged-20190608-1.pdf#page=49
167
+ print ("fatal error: exception with mcause=" )
168
+ print (code )
169
+ print (" pc=" )
170
+ print (riscv .MEPC .Get ())
171
+ println ()
172
+ abort ()
173
+ }
0 commit comments