Skip to content

Commit 60f74c0

Browse files
Migrate virtio-rng from semu
This commit migrates virtio-rng support from semu and follows the existing virtio-blk implementation, with the following modifications: 1. Implement virtio-rng device model The virtio-rng implementation follows the VirtIO-MMIO flow used by virtio-blk, including feature negotiation, queue setup, QueueNotify handling, used ring update, interrupt status, and device status reset. 2. Rename virtio_rng_reg_read/write to virtio_rng_read/write In semu, virtio_rng_read/write first checks the memory access width and then calls virtio_rng_reg_read/write for register handling. In rv32emu, MMIO accesses are already routed to each device by system.h, following the existing virtio-blk model. Therefore, the migrated virtio-rng device exposes virtio_rng_read/write as the register handlers directly. 3. Implement MMIO_VIRTIORNG Add MMIO routing for virtio-rng and connect the device interrupt status to the PLIC, following the existing virtio-blk interrupt update model. 4. Implement vrng_new() and vrng_delete() These functions align virtio-rng with the allocation and cleanup. 5. Introduce new argument '-x vrng' for virtio-rng The new option enables virtio-rng in system emulation mode. When it is enabled, rv32emu dynamically creates a virtio-mmio node in the generated device tree and assigns an MMIO base address and IRQ for the device. 6. Use virtio-rng state Unlike semu's global rng_fd, rv32emu stores the virtio-rng state in vm_attr_t so MMIO routing, interrupt routing, and device cleanup can access the same device instance. 7. Enable virtio-rng in the guest Linux configuration Enable the Linux hardware random framework and virtio-rng driver so the guest can bind the device and expose /dev/hwrng. Co-authored-by: Shengwen Cheng <shengwen1997.tw@gmail.com>
1 parent c5e8819 commit 60f74c0

8 files changed

Lines changed: 393 additions & 10 deletions

File tree

assets/system/configs/linux.config

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,8 @@ CONFIG_SERIAL_CORE_CONSOLE=y
831831
# CONFIG_TTY_PRINTK is not set
832832
# CONFIG_VIRTIO_CONSOLE is not set
833833
# CONFIG_IPMI_HANDLER is not set
834-
# CONFIG_HW_RANDOM is not set
834+
CONFIG_HW_RANDOM=y
835+
CONFIG_HW_RANDOM_VIRTIO=y
835836
CONFIG_DEVMEM=y
836837
# CONFIG_TCG_TPM is not set
837838
# CONFIG_XILLYBUS is not set

src/devices/virtio-rng.c

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#include <assert.h>
7+
#include <errno.h>
8+
#include <fcntl.h>
9+
#include <stdbool.h>
10+
#include <stdint.h>
11+
#include <stdio.h>
12+
#include <stdlib.h>
13+
#include <string.h>
14+
#include <unistd.h>
15+
16+
#include "virtio.h"
17+
18+
#define VRNG_FEATURES_0 0
19+
#define VRNG_FEATURES_1 1 /* VIRTIO_F_VERSION_1 */
20+
#define VRNG_QUEUE_NUM_MAX 1024
21+
#define VRNG_QUEUE (vrng->queues[vrng->queue_sel])
22+
23+
static void virtio_rng_set_fail(virtio_rng_state_t *vrng)
24+
{
25+
vrng->status |= VIRTIO_STATUS_DEVICE_NEEDS_RESET;
26+
if (vrng->status & VIRTIO_STATUS_DRIVER_OK)
27+
vrng->interrupt_status |= VIRTIO_INT_CONF_CHANGE;
28+
}
29+
30+
static inline uint32_t vrng_preprocess(virtio_rng_state_t *vrng, uint32_t addr)
31+
{
32+
#if MEM_SIZE < 0x100000000ULL
33+
if ((addr >= MEM_SIZE) || (addr & 0b11)) {
34+
#else
35+
if (addr & 0b11) {
36+
#endif
37+
virtio_rng_set_fail(vrng);
38+
return 0;
39+
}
40+
41+
return addr >> 2;
42+
}
43+
44+
static void virtio_rng_update_status(virtio_rng_state_t *vrng, uint32_t status)
45+
{
46+
vrng->status |= status;
47+
if (status)
48+
return;
49+
50+
/* Reset while preserving environment-owned fields. */
51+
uint32_t *ram = vrng->ram;
52+
int rng_fd = vrng->rng_fd;
53+
memset(vrng, 0, sizeof(*vrng));
54+
vrng->ram = ram;
55+
vrng->rng_fd = rng_fd;
56+
}
57+
58+
static void virtio_queue_notify_handler(virtio_rng_state_t *vrng, int index)
59+
{
60+
uint32_t *ram = vrng->ram;
61+
virtio_rng_queue_t *queue = &vrng->queues[index];
62+
63+
if (vrng->status & VIRTIO_STATUS_DEVICE_NEEDS_RESET)
64+
return;
65+
66+
if (!((vrng->status & VIRTIO_STATUS_DRIVER_OK) && queue->ready))
67+
return virtio_rng_set_fail(vrng);
68+
69+
/* Check for new buffers */
70+
uint16_t new_avail = ram[queue->queue_avail] >> 16;
71+
if (new_avail - queue->last_avail > (uint16_t) queue->queue_num) {
72+
rv_log_error("Size check fail");
73+
return virtio_rng_set_fail(vrng);
74+
}
75+
76+
uint16_t new_used = ram[queue->queue_used] >> 16;
77+
while (queue->last_avail != new_avail) {
78+
uint16_t queue_idx = queue->last_avail % queue->queue_num;
79+
80+
/* Since each buffer index occupies 2 bytes but the memory is aligned
81+
* with 4 bytes, and the first element of the available queue is stored
82+
* at ram[queue->queue_avail + 1], to acquire the buffer index, it
83+
* requires the following array index calculation and bit shifting.
84+
* Check also the `struct virtq_avail` on the spec.
85+
*/
86+
uint16_t buffer_idx = ram[queue->queue_avail + 1 + queue_idx / 2] >>
87+
(16 * (queue_idx % 2));
88+
89+
struct virtq_desc *desc =
90+
(struct virtq_desc *) &vrng->ram[queue->queue_desc + buffer_idx * 4];
91+
92+
if (!(desc->flags & VIRTIO_DESC_F_WRITE))
93+
return virtio_rng_set_fail(vrng);
94+
95+
void *entropy_buf = (void *) ((uintptr_t) vrng->ram + desc->addr);
96+
ssize_t total = read(vrng->rng_fd, entropy_buf, desc->len);
97+
if (total < 0)
98+
total = 0;
99+
100+
/* Write used element information (`struct virtq_used_elem`) to the used
101+
* queue */
102+
uint32_t vq_used_addr =
103+
queue->queue_used + 1 + (new_used % queue->queue_num) * 2;
104+
ram[vq_used_addr] = buffer_idx;
105+
ram[vq_used_addr + 1] = (uint32_t) total;
106+
107+
queue->last_avail++;
108+
new_used++;
109+
}
110+
111+
ram[queue->queue_used] &= MASK(16);
112+
ram[queue->queue_used] |= ((uint32_t) new_used) << 16;
113+
114+
if (!(ram[queue->queue_avail] & 1))
115+
vrng->interrupt_status |= VIRTIO_INT_USED_RING;
116+
}
117+
118+
uint32_t virtio_rng_read(virtio_rng_state_t *vrng, uint32_t addr)
119+
{
120+
addr = addr >> 2;
121+
#define _(reg) VIRTIO_##reg
122+
switch (addr) {
123+
case _(MagicValue):
124+
return VIRTIO_MAGIC_NUMBER;
125+
case _(Version):
126+
return VIRTIO_VERSION;
127+
case _(DeviceID):
128+
return VIRTIO_RNG_DEV_ID;
129+
case _(VendorID):
130+
return VIRTIO_VENDOR_ID;
131+
case _(DeviceFeatures):
132+
return vrng->device_features_sel == 0
133+
? VRNG_FEATURES_0 | vrng->device_features
134+
: (vrng->device_features_sel == 1 ? VRNG_FEATURES_1 : 0);
135+
case _(QueueNumMax):
136+
return VRNG_QUEUE_NUM_MAX;
137+
case _(QueueReady):
138+
return (uint32_t) VRNG_QUEUE.ready;
139+
case _(InterruptStatus):
140+
return vrng->interrupt_status;
141+
case _(Status):
142+
return vrng->status;
143+
case _(ConfigGeneration):
144+
return VIRTIO_CONFIG_GENERATE;
145+
default:
146+
return 0;
147+
}
148+
#undef _
149+
}
150+
151+
void virtio_rng_write(virtio_rng_state_t *vrng, uint32_t addr, uint32_t value)
152+
{
153+
addr = addr >> 2;
154+
#define _(reg) VIRTIO_##reg
155+
switch (addr) {
156+
case _(DeviceFeaturesSel):
157+
vrng->device_features_sel = value;
158+
break;
159+
case _(DriverFeatures):
160+
if (vrng->driver_features_sel == 0)
161+
vrng->driver_features = value;
162+
break;
163+
case _(DriverFeaturesSel):
164+
vrng->driver_features_sel = value;
165+
break;
166+
case _(QueueSel):
167+
if (value < ARRAY_SIZE(vrng->queues))
168+
vrng->queue_sel = value;
169+
else
170+
virtio_rng_set_fail(vrng);
171+
break;
172+
case _(QueueNum):
173+
if (value > 0 && value <= VRNG_QUEUE_NUM_MAX)
174+
VRNG_QUEUE.queue_num = value;
175+
else
176+
virtio_rng_set_fail(vrng);
177+
break;
178+
case _(QueueReady):
179+
VRNG_QUEUE.ready = value & 1;
180+
if (value & 1)
181+
VRNG_QUEUE.last_avail = vrng->ram[VRNG_QUEUE.queue_avail] >> 16;
182+
break;
183+
case _(QueueDescLow):
184+
VRNG_QUEUE.queue_desc = vrng_preprocess(vrng, value);
185+
break;
186+
case _(QueueDescHigh):
187+
if (value)
188+
virtio_rng_set_fail(vrng);
189+
break;
190+
case _(QueueDriverLow):
191+
VRNG_QUEUE.queue_avail = vrng_preprocess(vrng, value);
192+
break;
193+
case _(QueueDriverHigh):
194+
if (value)
195+
virtio_rng_set_fail(vrng);
196+
break;
197+
case _(QueueDeviceLow):
198+
VRNG_QUEUE.queue_used = vrng_preprocess(vrng, value);
199+
break;
200+
case _(QueueDeviceHigh):
201+
if (value)
202+
virtio_rng_set_fail(vrng);
203+
break;
204+
case _(QueueNotify):
205+
if (value < ARRAY_SIZE(vrng->queues))
206+
virtio_queue_notify_handler(vrng, value);
207+
else
208+
virtio_rng_set_fail(vrng);
209+
break;
210+
case _(InterruptACK):
211+
vrng->interrupt_status &= ~value;
212+
break;
213+
case _(Status):
214+
virtio_rng_update_status(vrng, value);
215+
break;
216+
default:
217+
break;
218+
}
219+
#undef _
220+
}
221+
222+
bool virtio_rng_init(virtio_rng_state_t *vrng)
223+
{
224+
vrng->rng_fd = open("/dev/random", O_RDONLY);
225+
if (vrng->rng_fd < 0) {
226+
rv_log_error("Could not open /dev/random: %s", strerror(errno));
227+
return false;
228+
}
229+
230+
return true;
231+
}
232+
233+
virtio_rng_state_t *vrng_new(void)
234+
{
235+
virtio_rng_state_t *vrng = calloc(1, sizeof(*vrng));
236+
assert(vrng);
237+
vrng->rng_fd = -1;
238+
return vrng;
239+
}
240+
241+
void vrng_delete(virtio_rng_state_t *vrng)
242+
{
243+
if (!vrng)
244+
return;
245+
if (vrng->rng_fd >= 0)
246+
close(vrng->rng_fd);
247+
free(vrng);
248+
}

src/devices/virtio.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
/* TODO: support more features */
3737
#define VIRTIO_BLK_F_RO (1 << 5)
3838

39+
#define VIRTIO_RNG_DEV_ID 4
40+
3941
/* VirtIO MMIO registers */
4042
#define VIRTIO_REG_LIST \
4143
_(MagicValue, 0x000) /* R */ \
@@ -77,6 +79,7 @@ struct virtq_desc {
7779
};
7880

7981
#define IRQ_VBLK_BIT(base, i) (1 << (base + i))
82+
#define IRQ_VRNG_BIT(irq) (1 << (irq))
8083

8184
typedef struct {
8285
uint32_t queue_num;
@@ -119,3 +122,38 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
119122
virtio_blk_state_t *vblk_new();
120123

121124
void vblk_delete(virtio_blk_state_t *vblk);
125+
126+
typedef struct {
127+
uint32_t queue_num;
128+
uint32_t queue_desc;
129+
uint32_t queue_avail;
130+
uint32_t queue_used;
131+
uint16_t last_avail;
132+
bool ready;
133+
} virtio_rng_queue_t;
134+
135+
typedef struct {
136+
uint32_t device_features;
137+
uint32_t device_features_sel;
138+
uint32_t driver_features;
139+
uint32_t driver_features_sel;
140+
141+
uint32_t queue_sel;
142+
virtio_rng_queue_t queues[1];
143+
144+
uint32_t status;
145+
uint32_t interrupt_status;
146+
147+
uint32_t *ram;
148+
int rng_fd;
149+
} virtio_rng_state_t;
150+
151+
uint32_t virtio_rng_read(virtio_rng_state_t *vrng, uint32_t addr);
152+
153+
void virtio_rng_write(virtio_rng_state_t *vrng, uint32_t addr, uint32_t value);
154+
155+
bool virtio_rng_init(virtio_rng_state_t *vrng);
156+
157+
virtio_rng_state_t *vrng_new(void);
158+
159+
void vrng_delete(virtio_rng_state_t *vrng);

src/main.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ static char *opt_bootargs;
6666
#define VBLK_DEV_MAX 100
6767
static char *opt_virtio_blk_img[VBLK_DEV_MAX];
6868
static int opt_virtio_blk_idx = 0;
69+
70+
/* enable virtio-rng device */
71+
static bool opt_virtio_rng = false;
6972
#endif
7073

7174
static void reset_getopt_state(void)
@@ -107,6 +110,7 @@ static void reset_runtime_options(void)
107110
opt_bootargs = NULL;
108111
memset(opt_virtio_blk_img, 0, sizeof(opt_virtio_blk_img));
109112
opt_virtio_blk_idx = 0;
113+
opt_virtio_rng = false;
110114
#endif
111115

112116
reset_getopt_state();
@@ -132,6 +136,7 @@ static void print_usage(const char *filename)
132136
"<image> as virtio-blk disk image "
133137
"(default read and write). This option may be specified "
134138
"multiple times for multiple block devices\n"
139+
" -x vrng : enable virtio-rng device\n"
135140
" -b <bootargs> : use customized <bootargs> for the kernel\n"
136141
#endif
137142
" -d [filename]: dump registers as JSON to the "
@@ -178,16 +183,20 @@ static bool parse_args(int argc, char **args)
178183
emu_argc++;
179184
break;
180185
case 'x':
181-
if (opt_virtio_blk_idx >= VBLK_DEV_MAX) {
182-
rv_log_error("Too many virtio-blk devices. Maximum is %d.\n",
183-
VBLK_DEV_MAX);
184-
return false;
185-
}
186-
if (!strncmp("vblk:", optarg, 5))
186+
if (!strncmp("vblk:", optarg, 5)) {
187+
if (opt_virtio_blk_idx >= VBLK_DEV_MAX) {
188+
rv_log_error(
189+
"Too many virtio-blk devices. Maximum is %d.\n",
190+
VBLK_DEV_MAX);
191+
return false;
192+
}
187193
opt_virtio_blk_img[opt_virtio_blk_idx++] =
188194
optarg + 5; /* strlen("vblk:") */
189-
else
195+
} else if (!strcmp("vrng", optarg)) {
196+
opt_virtio_rng = true;
197+
} else {
190198
return false;
199+
}
191200
emu_argc++;
192201
break;
193202
#endif
@@ -379,6 +388,7 @@ int main(int argc, char **args)
379388
attr.data.system.kernel = opt_kernel_img;
380389
attr.data.system.initrd = opt_rootfs_img;
381390
attr.data.system.bootargs = opt_bootargs;
391+
attr.data.system.vrng_enabled = opt_virtio_rng;
382392
if (opt_virtio_blk_idx) {
383393
attr.data.system.vblk_device = opt_virtio_blk_img;
384394
attr.data.system.vblk_device_cnt = opt_virtio_blk_idx;

0 commit comments

Comments
 (0)