Skip to content

Commit 610e3c2

Browse files
committed
Support multiple virtio block devices
Previously, #539 set VBLK_DEV_CNT_MAX to 1, limiting the virtual machine to a single block device. This commit introduces support for multiple block devices by switching from statically allocated storage to dynamic allocation based on CLI input. Key changes: 1. Dynamic Allocation Virtio block related fields are now dynamically allocated based on the number of -x vblk options provided. 2. Device Tree Generation The prewritten virtio@ subnode for block device in device/minimal.dts is commented and it can be an example subnode for future reference. load_dtb() dynamically appends virtio@ subnodes under the SoC node based on the number of block devices. 3. MMIO Handling Instead of hardcoding switch cases for each MMIO region of block device, the code now checks whether the accessed MMIO address falls within the valid range of any block device. Note: The /dev/vdx device order is assigned in reverse: the first -x vblk argument corresponds to the device with the highest letter, while subsequent arguments receive lower-lettered device names.
1 parent 6fd635b commit 610e3c2

File tree

8 files changed

+239
-129
lines changed

8 files changed

+239
-129
lines changed

src/devices/minimal.dts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,14 @@
6666
clock-frequency = <5000000>; /* the baudrate divisor is ignored */
6767
};
6868

69-
blk0: virtio@4200000 {
69+
/*
70+
* Virtio block example subnode
71+
* The actual subnode are generated dynamically depends on the CLI -x vblk option
72+
*/
73+
/*blk0: virtio@4100000 {
7074
compatible = "virtio,mmio";
71-
reg = <0x4200000 0x200>;
72-
interrupts = <3>;
73-
};
75+
reg = <0x4100000 0x200>;
76+
interrupts = <2>;
77+
};*/
7478
};
7579
};

src/devices/virtio-blk.c

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@
3232

3333
#define DISK_BLK_SIZE 512
3434

35-
/* TODO: Enable mutiple virtio-blk devices. */
36-
#define VBLK_DEV_CNT_MAX 1
37-
3835
#define VBLK_FEATURES_0 0
3936
#define VBLK_FEATURES_1 1 /* VIRTIO_F_VERSION_1 */
4037
#define VBLK_QUEUE_NUM_MAX 1024
@@ -81,9 +78,6 @@ PACKED(struct vblk_req_header {
8178
uint8_t status;
8279
});
8380

84-
static struct virtio_blk_config vblk_configs[VBLK_DEV_CNT_MAX];
85-
static int vblk_dev_cnt = 0;
86-
8781
static void virtio_blk_set_fail(virtio_blk_state_t *vblk)
8882
{
8983
vblk->status |= VIRTIO_STATUS_DEVICE_NEEDS_RESET;
@@ -401,20 +395,16 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
401395
char *disk_file,
402396
bool readonly)
403397
{
404-
if (vblk_dev_cnt >= VBLK_DEV_CNT_MAX) {
405-
rv_log_error(
406-
"Exceeded the number of virtio-blk devices that can be allocated");
407-
exit(EXIT_FAILURE);
408-
}
409-
410398
/*
411399
* For mmap_fallback, if vblk is not specified, disk_fd should remain -1 and
412400
* no fsync should be performed on exit.
413401
*/
402+
414403
vblk->disk_fd = -1;
415404

416405
/* Allocate memory for the private member */
417-
vblk->priv = &vblk_configs[vblk_dev_cnt++];
406+
vblk->priv = calloc(1, sizeof(struct virtio_blk_config));
407+
assert(vblk->priv);
418408

419409
/* No disk image is provided */
420410
if (!disk_file) {
@@ -549,5 +539,6 @@ void vblk_delete(virtio_blk_state_t *vblk)
549539
else
550540
munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size);
551541
#endif
542+
free(vblk->priv);
552543
free(vblk);
553544
}

src/devices/virtio.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ struct virtq_desc {
7676
uint16_t next;
7777
};
7878

79-
#define IRQ_VBLK_SHIFT 3
80-
#define IRQ_VBLK_BIT (1 << IRQ_VBLK_SHIFT)
79+
#define IRQ_VBLK_BIT(base, i) (1 << (base + i))
8180

8281
typedef struct {
8382
uint32_t queue_num;

src/main.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ static char *prof_out_file;
6060
static char *opt_kernel_img;
6161
static char *opt_rootfs_img;
6262
static char *opt_bootargs;
63-
static char *opt_virtio_blk_img;
63+
/* FIXME: handle overflow */
64+
#define VBLK_DEV_MAX 100
65+
static char *opt_virtio_blk_img[VBLK_DEV_MAX];
66+
static int opt_virtio_blk_idx = 0;
6467
#endif
6568

6669
static void print_usage(const char *filename)
@@ -78,8 +81,10 @@ static void print_usage(const char *filename)
7881
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
7982
" -k <image> : use <image> as kernel image\n"
8083
" -i <image> : use <image> as rootfs\n"
81-
" -x vblk:<image>[,readonly] : use <image> as virtio-blk disk image "
82-
"(default read and write)\n"
84+
" -x vblk:<image>[,readonly]: use "
85+
"<image> as virtio-blk disk image "
86+
"(default read and write). This option may be specified "
87+
"multiple times for multiple block devices\n"
8388
" -b <bootargs> : use customized <bootargs> for the kernel\n"
8489
#endif
8590
" -d [filename]: dump registers as JSON to the "
@@ -127,7 +132,8 @@ static bool parse_args(int argc, char **args)
127132
break;
128133
case 'x':
129134
if (!strncmp("vblk:", optarg, 5))
130-
opt_virtio_blk_img = optarg + 5; /* strlen("vblk:") */
135+
opt_virtio_blk_img[opt_virtio_blk_idx++] =
136+
optarg + 5; /* strlen("vblk:") */
131137
else
132138
return false;
133139
emu_argc++;
@@ -273,7 +279,12 @@ int main(int argc, char **args)
273279
attr.data.system.kernel = opt_kernel_img;
274280
attr.data.system.initrd = opt_rootfs_img;
275281
attr.data.system.bootargs = opt_bootargs;
276-
attr.data.system.vblk_device = opt_virtio_blk_img;
282+
if (opt_virtio_blk_idx) {
283+
attr.data.system.vblk_device = opt_virtio_blk_img;
284+
attr.data.system.vblk_device_cnt = opt_virtio_blk_idx;
285+
} else {
286+
attr.data.system.vblk_device = NULL;
287+
}
277288
#else
278289
attr.data.user.elf_program = opt_prog_name;
279290
#endif

src/riscv.c

Lines changed: 139 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,26 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr)
293293
{
294294
#include "minimal_dtb.h"
295295
char *bootargs = attr->data.system.bootargs;
296-
char *vblk = attr->data.system.vblk_device;
296+
char **vblk = attr->data.system.vblk_device;
297297
char *blob = *ram_loc;
298298
char *buf;
299299
size_t len;
300300
int node, err;
301301
int totalsize;
302302

303-
memcpy(blob, minimal, sizeof(minimal));
303+
#define DTB_EXPAND_SIZE 1024 /* or more if needed */
304+
305+
/* Allocate enough memory for DTB + extra room */
306+
int minimal_len = ARRAY_SIZE(minimal);
307+
void *dtb_buf = calloc(minimal_len + DTB_EXPAND_SIZE, sizeof(uint8_t));
308+
assert(dtb_buf);
309+
310+
/* Expand it to a usable DTB blob */
311+
err = fdt_open_into(minimal, dtb_buf, minimal_len + DTB_EXPAND_SIZE);
312+
if (err < 0) {
313+
rv_log_error("fdt_open_into fails\n");
314+
exit(EXIT_FAILURE);
315+
}
304316

305317
if (bootargs) {
306318
node = fdt_path_offset(blob, "/chosen");
@@ -320,18 +332,71 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr)
320332
assert(!err);
321333
}
322334

323-
/* remove the vblk node from soc if it is not specified */
324-
if (!vblk) {
325-
int subnode;
326-
node = fdt_path_offset(blob, "/soc@F0000000");
335+
if (vblk) {
336+
int node = fdt_path_offset(dtb_buf, "/soc@F0000000");
327337
assert(node >= 0);
328338

329-
subnode = fdt_subnode_offset(blob, node, "virtio@4200000");
330-
assert(subnode >= 0);
339+
uint32_t base_addr = 0x4000000;
340+
uint32_t addr_offset = 0x100000;
341+
uint32_t size = 0x200;
342+
343+
uint32_t next_addr = base_addr;
344+
uint32_t next_irq = 1;
345+
346+
/* scan existing nodes to get next addr and irq */
347+
int subnode;
348+
fdt_for_each_subnode(subnode, dtb_buf, node)
349+
{
350+
const char *name = fdt_get_name(dtb_buf, subnode, NULL);
351+
assert(name);
352+
353+
uint32_t addr = strtoul(name + 7, NULL, 16);
354+
if (addr == next_addr)
355+
next_addr = addr + addr_offset;
356+
357+
const fdt32_t *irq_prop =
358+
fdt_getprop(dtb_buf, subnode, "interrupts", NULL);
359+
if (irq_prop) {
360+
uint32_t irq = fdt32_to_cpu(*irq_prop);
361+
if (irq == next_irq)
362+
next_irq = irq + 1;
363+
}
364+
}
365+
/* set IRQ for virtio block, see devices/virtio.h */
366+
attr->vblk_irq_base = next_irq;
367+
368+
/* adding new virtio block nodes */
369+
for (int i = 0; i < attr->vblk_cnt; i++) {
370+
uint32_t new_addr = next_addr + i * addr_offset;
371+
uint32_t new_irq = next_irq + i;
372+
373+
char node_name[32];
374+
snprintf(node_name, sizeof(node_name), "virtio@%x", new_addr);
375+
376+
int subnode = fdt_add_subnode(dtb_buf, node, node_name);
377+
if (subnode == -FDT_ERR_NOSPACE) {
378+
rv_log_warn("add subnode no space!\n");
379+
}
380+
assert(subnode >= 0);
331381

332-
assert(fdt_del_node(blob, subnode) == 0);
382+
/* compatible = "virtio,mmio" */
383+
assert(fdt_setprop_string(dtb_buf, subnode, "compatible",
384+
"virtio,mmio") == 0);
385+
386+
/* reg = <new_addr size> */
387+
uint32_t reg[2] = {cpu_to_fdt32(new_addr), cpu_to_fdt32(size)};
388+
assert(fdt_setprop(dtb_buf, subnode, "reg", reg, sizeof(reg)) == 0);
389+
390+
/* interrupts = <new_irq> */
391+
uint32_t irq = cpu_to_fdt32(new_irq);
392+
assert(fdt_setprop(dtb_buf, subnode, "interrupts", &irq,
393+
sizeof(irq)) == 0);
394+
}
333395
}
334396

397+
memcpy(blob, dtb_buf, minimal_len + DTB_EXPAND_SIZE);
398+
free(dtb_buf);
399+
335400
totalsize = fdt_totalsize(blob);
336401
*ram_loc += totalsize;
337402
return;
@@ -406,28 +471,36 @@ static void rv_fsync_device()
406471
*
407472
* vblk is optional, so it could be NULL
408473
*/
409-
if (attr->vblk) {
410-
if (attr->vblk->disk_fd >= 3) {
411-
if (attr->vblk->device_features & VIRTIO_BLK_F_RO) /* readonly */
412-
goto end;
413-
414-
if (pwrite(attr->vblk->disk_fd, attr->vblk->disk,
415-
attr->vblk->disk_size, 0) == -1) {
416-
rv_log_error("pwrite block device failed: %s", strerror(errno));
417-
return;
474+
if (attr->vblk_cnt) {
475+
for (int i = 0; i < attr->vblk_cnt; i++) {
476+
virtio_blk_state_t *vblk = attr->vblk[i];
477+
if (vblk->disk_fd >= 3) {
478+
if (vblk->device_features & VIRTIO_BLK_F_RO) /* readonly */
479+
goto end;
480+
481+
if (pwrite(vblk->disk_fd, vblk->disk, vblk->disk_size, 0) ==
482+
-1) {
483+
rv_log_error("pwrite block device failed: %s",
484+
strerror(errno));
485+
return;
486+
}
487+
488+
if (fsync(vblk->disk_fd) == -1) {
489+
rv_log_error("fsync block device failed: %s",
490+
strerror(errno));
491+
return;
492+
}
493+
rv_log_info("Sync block device OK");
494+
495+
end:
496+
close(vblk->disk_fd);
418497
}
419498

420-
if (fsync(attr->vblk->disk_fd) == -1) {
421-
rv_log_error("fsync block device failed: %s", strerror(errno));
422-
return;
423-
}
424-
rv_log_info("Sync block device OK");
425-
426-
end:
427-
close(attr->vblk->disk_fd);
499+
vblk_delete(vblk);
428500
}
429501

430-
vblk_delete(attr->vblk);
502+
free(attr->vblk);
503+
free(attr->disk);
431504
}
432505
}
433506
#endif /* RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) */
@@ -548,6 +621,9 @@ riscv_t *rv_create(riscv_user_t rv_attr)
548621
* *----------------*----------------*-------*
549622
*/
550623

624+
/* load_dtb needs the count to add the virtio block subnode dynamically */
625+
attr->vblk_cnt = attr->data.system.vblk_device_cnt;
626+
551627
char *ram_loc = (char *) attr->mem->mem_base;
552628
map_file(&ram_loc, attr->data.system.kernel);
553629
rv_log_info("Kernel loaded");
@@ -590,36 +666,47 @@ riscv_t *rv_create(riscv_user_t rv_attr)
590666
attr->uart->out_fd = attr->fd_stdout;
591667

592668
/* setup virtio-blk */
593-
attr->vblk = NULL;
594-
if (attr->data.system.vblk_device) {
669+
attr->vblk_mmio_base_hi = 0x41;
670+
attr->vblk_mmio_max_hi = attr->vblk_mmio_base_hi + attr->vblk_cnt;
671+
672+
attr->vblk = malloc(sizeof(virtio_blk_state_t *) * attr->vblk_cnt);
673+
assert(attr->vblk);
674+
attr->disk = malloc(sizeof(uint32_t *) * attr->vblk_cnt);
675+
assert(attr->disk);
676+
677+
if (attr->vblk_cnt) {
678+
for (int i = 0; i < attr->vblk_cnt; i++) {
595679
/* Currently, only used for block image path and permission */
596680
#define MAX_OPTS 2
597-
char *vblk_opts[MAX_OPTS] = {NULL};
598-
int vblk_opt_idx = 0;
599-
char *opt = strtok(attr->data.system.vblk_device, ",");
600-
while (opt) {
601-
if (vblk_opt_idx == MAX_OPTS) {
602-
rv_log_error("Too many arguments for vblk");
603-
break;
681+
char *vblk_device_str = attr->data.system.vblk_device[i];
682+
char *vblk_opts[MAX_OPTS] = {NULL};
683+
int vblk_opt_idx = 0;
684+
char *opt = strtok(vblk_device_str, ",");
685+
while (opt) {
686+
if (vblk_opt_idx == MAX_OPTS) {
687+
rv_log_error("Too many arguments for vblk");
688+
break;
689+
}
690+
vblk_opts[vblk_opt_idx++] = opt;
691+
opt = strtok(NULL, ",");
604692
}
605-
vblk_opts[vblk_opt_idx++] = opt;
606-
opt = strtok(NULL, ",");
607-
}
608-
char *vblk_device = vblk_opts[0];
609-
char *vblk_readonly = vblk_opts[1];
610-
611-
bool readonly = false;
612-
if (vblk_readonly) {
613-
if (strcmp(vblk_readonly, "readonly") != 0) {
614-
rv_log_error("Unknown vblk option: %s", vblk_readonly);
615-
exit(EXIT_FAILURE);
693+
char *vblk_device = vblk_opts[0];
694+
char *vblk_readonly = vblk_opts[1];
695+
696+
bool readonly = false;
697+
if (vblk_readonly) {
698+
if (strcmp(vblk_readonly, "readonly") != 0) {
699+
rv_log_error("Unknown vblk option: %s", vblk_readonly);
700+
exit(EXIT_FAILURE);
701+
}
702+
readonly = true;
616703
}
617-
readonly = true;
618-
}
619704

620-
attr->vblk = vblk_new();
621-
attr->vblk->ram = (uint32_t *) attr->mem->mem_base;
622-
attr->disk = virtio_blk_init(attr->vblk, vblk_device, readonly);
705+
attr->vblk[i] = vblk_new();
706+
attr->vblk[i]->ram = (uint32_t *) attr->mem->mem_base;
707+
attr->disk[i] =
708+
virtio_blk_init(attr->vblk[i], vblk_device, readonly);
709+
}
623710
}
624711

625712
capture_keyboard_input();

0 commit comments

Comments
 (0)