Skip to content

Commit cab3834

Browse files
aykevldeadprogram
authored andcommitted
riscv-qemu: add VirtIO RNG device
This implements machine.GetRNG() using VirtIO. This gets the tests to pass for crypto/md5 and crypto/sha1 that use crypto/rand in their tests.
1 parent 7f970a4 commit cab3834

File tree

6 files changed

+193
-7
lines changed

6 files changed

+193
-7
lines changed

GNUmakefile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,6 @@ TEST_PACKAGES_NONWASM = \
429429
#
430430
# * No filesystem is available, so packages like compress/zlib can't be tested
431431
# (just like wasm).
432-
# * There is no RNG implemented (TODO, I think this is fixable).
433432
# * picolibc math functions apparently are less precise, the math package
434433
# fails on baremetal.
435434
# * Some packages fail or hang for an unknown reason, this should be
@@ -438,8 +437,6 @@ TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PAC
438437
TEST_PACKAGES_NONBAREMETAL = \
439438
$(TEST_PACKAGES_NONWASM) \
440439
crypto/elliptic \
441-
crypto/md5 \
442-
crypto/sha1 \
443440
math \
444441
reflect \
445442
encoding/asn1 \

src/crypto/rand/rand_baremetal.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey
1+
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)
22

33
// If you update the above build constraint, you'll probably also need to update
44
// src/runtime/rand_hwrng.go.

src/machine/virt.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
//go:build tinygo.riscv32 && virt
2+
3+
// Machine implementation for VirtIO targets.
4+
// At the moment only QEMU RISC-V is supported, but support for ARM for example
5+
// should not be difficult to add with a change to virtioFindDevice.
6+
7+
package machine
8+
9+
import (
10+
"errors"
11+
"runtime/volatile"
12+
"sync"
13+
"unsafe"
14+
)
15+
16+
const deviceName = "riscv-qemu"
17+
18+
func (p Pin) Set(high bool) {
19+
// no pins defined
20+
}
21+
22+
var rngLock sync.Mutex
23+
var rngDevice *virtioDevice1
24+
var rngBuf volatile.Register32
25+
26+
var errNoRNG = errors.New("machine: no entropy source found")
27+
var errNoRNGData = errors.New("machine: entropy source didn't return enough data")
28+
29+
// GetRNG returns random numbers from a VirtIO entropy source.
30+
// When running in QEMU, it requires adding the RNG device:
31+
//
32+
// -device virtio-rng-device
33+
func GetRNG() (uint32, error) {
34+
rngLock.Lock()
35+
36+
// Initialize the device on first use.
37+
if rngDevice == nil {
38+
// Search for an available RNG.
39+
rngDevice = virtioFindDevice(virtioDeviceEntropySource)
40+
if rngDevice == nil {
41+
rngLock.Unlock()
42+
return 0, errNoRNG
43+
}
44+
45+
// Initialize the device.
46+
rngDevice.status.Set(0) // reset device
47+
rngDevice.status.Set(virtioDeviceStatusAcknowledge)
48+
rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver)
49+
rngDevice.hostFeaturesSel.Set(0)
50+
rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver | virtioDeviceStatusDriverOk)
51+
rngDevice.guestPageSize.Set(4096)
52+
53+
// Configure queue, according to section 4.2.4 "Legacy interface".
54+
// Note: we're skipping checks for queuePFM and queueNumMax.
55+
rngDevice.queueSel.Set(0) // use queue 0 (the only queue)
56+
rngDevice.queueNum.Set(1) // use a single buffer in the queue
57+
rngDevice.queueAlign.Set(4096) // default alignment appears to be 4096
58+
rngDevice.queuePFN.Set(uint32(uintptr(unsafe.Pointer(&rngQueue))) / 4096)
59+
60+
// Configure the only buffer in the queue (but don't increment
61+
// rngQueue.available yet).
62+
rngQueue.buffers[0].address = uint64(uintptr(unsafe.Pointer(&rngBuf)))
63+
rngQueue.buffers[0].length = uint32(unsafe.Sizeof(rngBuf))
64+
rngQueue.buffers[0].flags = 2 // 2 means write-only buffer
65+
}
66+
67+
// Increment the available ring buffer. This doesn't actually change the
68+
// buffer index (it's a ring with a single entry), but the number needs to
69+
// be incremented otherwise the device won't recognize a new buffer.
70+
index := rngQueue.available.index
71+
rngQueue.available.index = index + 1
72+
rngDevice.queueNotify.Set(0) // notify the device of the 'new' (reused) buffer
73+
for rngQueue.used.index.Get() != index+1 {
74+
// Busy wait until the RNG buffer is filled.
75+
// A better way would be to wait for an interrupt, but since this driver
76+
// implementation is mostly used for testing it's good enough for now.
77+
}
78+
79+
// Check that we indeed got 4 bytes back.
80+
if rngQueue.used.ring[0].length != 4 {
81+
rngLock.Unlock()
82+
return 0, errNoRNGData
83+
}
84+
85+
// Read the resulting random numbers.
86+
result := rngBuf.Get()
87+
88+
rngLock.Unlock()
89+
90+
return result, nil
91+
}
92+
93+
// Implement a driver for the VirtIO entropy device.
94+
// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html
95+
// http://wiki.osdev.org/Virtio
96+
// http://www.dumais.io/index.php?article=aca38a9a2b065b24dfa1dee728062a12
97+
98+
const (
99+
virtioDeviceStatusAcknowledge = 1
100+
virtioDeviceStatusDriver = 2
101+
virtioDeviceStatusDriverOk = 4
102+
virtioDeviceStatusFeaturesOk = 8
103+
virtioDeviceStatusFailed = 128
104+
)
105+
106+
const (
107+
virtioDeviceReserved = iota
108+
virtioDeviceNetworkCard
109+
virtioDeviceBlockDevice
110+
virtioDeviceConsole
111+
virtioDeviceEntropySource
112+
// there are more device types
113+
)
114+
115+
// VirtIO device version 1
116+
type virtioDevice1 struct {
117+
magic volatile.Register32 // always 0x74726976
118+
version volatile.Register32
119+
deviceID volatile.Register32
120+
vendorID volatile.Register32
121+
hostFeatures volatile.Register32
122+
hostFeaturesSel volatile.Register32
123+
_ [2]uint32
124+
guestFeatures volatile.Register32
125+
guestFeaturesSel volatile.Register32
126+
guestPageSize volatile.Register32
127+
_ uint32
128+
queueSel volatile.Register32
129+
queueNumMax volatile.Register32
130+
queueNum volatile.Register32
131+
queueAlign volatile.Register32
132+
queuePFN volatile.Register32
133+
_ [3]uint32
134+
queueNotify volatile.Register32
135+
_ [3]uint32
136+
interruptStatus volatile.Register32
137+
interruptAck volatile.Register32
138+
_ [2]uint32
139+
status volatile.Register32
140+
}
141+
142+
// VirtIO queue, with a single buffer.
143+
type virtioQueue struct {
144+
buffers [1]struct {
145+
address uint64
146+
length uint32
147+
flags uint16
148+
next uint16
149+
} // 16 bytes
150+
151+
available struct {
152+
flags uint16
153+
index uint16
154+
ring [1]uint16
155+
eventIndex uint16
156+
} // 8 bytes
157+
158+
_ [4096 - 16*1 - 8*1]byte // padding (to align on a 4096 byte boundary)
159+
160+
used struct {
161+
flags uint16
162+
index volatile.Register16
163+
ring [1]struct {
164+
index uint32
165+
length uint32
166+
}
167+
availEvent uint16
168+
}
169+
}
170+
171+
func virtioFindDevice(deviceID uint32) *virtioDevice1 {
172+
// On RISC-V, QEMU defines 8 VirtIO devices starting at 0x10001000 and
173+
// repeating every 0x1000 bytes.
174+
// The memory map can be seen in the QEMU source code:
175+
// https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c
176+
for i := 0; i < 8; i++ {
177+
dev := (*virtioDevice1)(unsafe.Pointer(uintptr(0x10001000 + i*0x1000)))
178+
if dev.magic.Get() != 0x74726976 || dev.version.Get() != 1 || dev.deviceID.Get() != deviceID {
179+
continue
180+
}
181+
return dev
182+
}
183+
return nil
184+
}
185+
186+
// A VirtIO queue needs to be page-aligned.
187+
//
188+
//go:align 4096
189+
var rngQueue virtioQueue

src/runtime/rand_hwrng.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey)
1+
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt))
22

33
// If you update the above build constraint, you'll probably also need to update
44
// src/crypto/rand/rand_baremetal.go.

src/runtime/rand_norng.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey)
1+
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt))
22

33
package runtime
44

targets/riscv-qemu.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"build-tags": ["virt", "qemu"],
55
"default-stack-size": 4096,
66
"linkerscript": "targets/riscv-qemu.ld",
7-
"emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -kernel {}"
7+
"emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -device virtio-rng-device -kernel {}"
88
}

0 commit comments

Comments
 (0)