From 188a400faf1f4d125895f60d4706a87b1421d9c8 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sat, 30 Aug 2025 22:01:43 +0800 Subject: [PATCH 1/4] 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. --- src/devices/minimal.dts | 12 ++- src/devices/virtio-blk.c | 17 +--- src/devices/virtio.h | 3 +- src/main.c | 21 ++++- src/riscv.c | 199 ++++++++++++++++++++++++++++----------- src/riscv.h | 12 ++- src/system.c | 12 ++- src/system.h | 96 ++++++++++--------- 8 files changed, 241 insertions(+), 131 deletions(-) diff --git a/src/devices/minimal.dts b/src/devices/minimal.dts index 315be2092..1b0b09572 100644 --- a/src/devices/minimal.dts +++ b/src/devices/minimal.dts @@ -66,10 +66,14 @@ clock-frequency = <5000000>; /* the baudrate divisor is ignored */ }; - blk0: virtio@4200000 { + /* + * Virtio block example subnode + * The actual subnode are generated dynamically depends on the CLI -x vblk option + */ + /*blk0: virtio@4100000 { compatible = "virtio,mmio"; - reg = <0x4200000 0x200>; - interrupts = <3>; - }; + reg = <0x4100000 0x200>; + interrupts = <2>; + };*/ }; }; diff --git a/src/devices/virtio-blk.c b/src/devices/virtio-blk.c index 5ee340857..478412097 100644 --- a/src/devices/virtio-blk.c +++ b/src/devices/virtio-blk.c @@ -32,9 +32,6 @@ #define DISK_BLK_SIZE 512 -/* TODO: Enable mutiple virtio-blk devices. */ -#define VBLK_DEV_CNT_MAX 1 - #define VBLK_FEATURES_0 0 #define VBLK_FEATURES_1 1 /* VIRTIO_F_VERSION_1 */ #define VBLK_QUEUE_NUM_MAX 1024 @@ -81,9 +78,6 @@ PACKED(struct vblk_req_header { uint8_t status; }); -static struct virtio_blk_config vblk_configs[VBLK_DEV_CNT_MAX]; -static int vblk_dev_cnt = 0; - static void virtio_blk_set_fail(virtio_blk_state_t *vblk) { vblk->status |= VIRTIO_STATUS_DEVICE_NEEDS_RESET; @@ -401,20 +395,16 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, char *disk_file, bool readonly) { - if (vblk_dev_cnt >= VBLK_DEV_CNT_MAX) { - rv_log_error( - "Exceeded the number of virtio-blk devices that can be allocated"); - exit(EXIT_FAILURE); - } - /* * For mmap_fallback, if vblk is not specified, disk_fd should remain -1 and * no fsync should be performed on exit. */ + vblk->disk_fd = -1; /* Allocate memory for the private member */ - vblk->priv = &vblk_configs[vblk_dev_cnt++]; + vblk->priv = calloc(1, sizeof(struct virtio_blk_config)); + assert(vblk->priv); /* No disk image is provided */ if (!disk_file) { @@ -549,5 +539,6 @@ void vblk_delete(virtio_blk_state_t *vblk) else munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size); #endif + free(vblk->priv); free(vblk); } diff --git a/src/devices/virtio.h b/src/devices/virtio.h index 28da79f78..a856627b5 100644 --- a/src/devices/virtio.h +++ b/src/devices/virtio.h @@ -76,8 +76,7 @@ struct virtq_desc { uint16_t next; }; -#define IRQ_VBLK_SHIFT 3 -#define IRQ_VBLK_BIT (1 << IRQ_VBLK_SHIFT) +#define IRQ_VBLK_BIT(base, i) (1 << (base + i)) typedef struct { uint32_t queue_num; diff --git a/src/main.c b/src/main.c index 08df77544..d57298539 100644 --- a/src/main.c +++ b/src/main.c @@ -60,7 +60,10 @@ static char *prof_out_file; static char *opt_kernel_img; static char *opt_rootfs_img; static char *opt_bootargs; -static char *opt_virtio_blk_img; +/* FIXME: handle overflow */ +#define VBLK_DEV_MAX 100 +static char *opt_virtio_blk_img[VBLK_DEV_MAX]; +static int opt_virtio_blk_idx = 0; #endif static void print_usage(const char *filename) @@ -78,8 +81,10 @@ static void print_usage(const char *filename) #if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) " -k : use as kernel image\n" " -i : use as rootfs\n" - " -x vblk:[,readonly] : use as virtio-blk disk image " - "(default read and write)\n" + " -x vblk:[,readonly]: use " + " as virtio-blk disk image " + "(default read and write). This option may be specified " + "multiple times for multiple block devices\n" " -b : use customized for the kernel\n" #endif " -d [filename]: dump registers as JSON to the " @@ -127,7 +132,8 @@ static bool parse_args(int argc, char **args) break; case 'x': if (!strncmp("vblk:", optarg, 5)) - opt_virtio_blk_img = optarg + 5; /* strlen("vblk:") */ + opt_virtio_blk_img[opt_virtio_blk_idx++] = + optarg + 5; /* strlen("vblk:") */ else return false; emu_argc++; @@ -273,7 +279,12 @@ int main(int argc, char **args) attr.data.system.kernel = opt_kernel_img; attr.data.system.initrd = opt_rootfs_img; attr.data.system.bootargs = opt_bootargs; - attr.data.system.vblk_device = opt_virtio_blk_img; + if (opt_virtio_blk_idx) { + attr.data.system.vblk_device = opt_virtio_blk_img; + attr.data.system.vblk_device_cnt = opt_virtio_blk_idx; + } else { + attr.data.system.vblk_device = NULL; + } #else attr.data.user.elf_program = opt_prog_name; #endif diff --git a/src/riscv.c b/src/riscv.c index 1f84f89a4..e420dea6f 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -293,17 +293,29 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr) { #include "minimal_dtb.h" char *bootargs = attr->data.system.bootargs; - char *vblk = attr->data.system.vblk_device; + char **vblk = attr->data.system.vblk_device; char *blob = *ram_loc; char *buf; size_t len; int node, err; int totalsize; - memcpy(blob, minimal, sizeof(minimal)); +#define DTB_EXPAND_SIZE 1024 /* or more if needed */ + + /* Allocate enough memory for DTB + extra room */ + size_t minimal_len = ARRAY_SIZE(minimal); + void *dtb_buf = calloc(minimal_len + DTB_EXPAND_SIZE, sizeof(uint8_t)); + assert(dtb_buf); + + /* Expand it to a usable DTB blob */ + err = fdt_open_into(minimal, dtb_buf, minimal_len + DTB_EXPAND_SIZE); + if (err < 0) { + rv_log_error("fdt_open_into fails\n"); + exit(EXIT_FAILURE); + } if (bootargs) { - node = fdt_path_offset(blob, "/chosen"); + node = fdt_path_offset(dtb_buf, "/chosen"); assert(node > 0); len = strlen(bootargs) + 1; @@ -311,27 +323,80 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr) assert(buf); memcpy(buf, bootargs, len - 1); buf[len] = 0; - err = fdt_setprop(blob, node, "bootargs", buf, len + 1); + err = fdt_setprop(dtb_buf, node, "bootargs", buf, len + 1); if (err == -FDT_ERR_NOSPACE) { - blob = realloc_property(blob, node, "bootargs", len); - err = fdt_setprop(blob, node, "bootargs", buf, len); + dtb_buf = realloc_property(dtb_buf, node, "bootargs", len); + err = fdt_setprop(dtb_buf, node, "bootargs", buf, len); } free(buf); assert(!err); } - /* remove the vblk node from soc if it is not specified */ - if (!vblk) { - int subnode; - node = fdt_path_offset(blob, "/soc@F0000000"); + if (vblk) { + int node = fdt_path_offset(dtb_buf, "/soc@F0000000"); assert(node >= 0); - subnode = fdt_subnode_offset(blob, node, "virtio@4200000"); - assert(subnode >= 0); + uint32_t base_addr = 0x4000000; + uint32_t addr_offset = 0x100000; + uint32_t size = 0x200; + + uint32_t next_addr = base_addr; + uint32_t next_irq = 1; + + /* scan existing nodes to get next addr and irq */ + int subnode; + fdt_for_each_subnode(subnode, dtb_buf, node) + { + const char *name = fdt_get_name(dtb_buf, subnode, NULL); + assert(name); + + uint32_t addr = strtoul(name + 7, NULL, 16); + if (addr == next_addr) + next_addr = addr + addr_offset; + + const fdt32_t *irq_prop = + fdt_getprop(dtb_buf, subnode, "interrupts", NULL); + if (irq_prop) { + uint32_t irq = fdt32_to_cpu(*irq_prop); + if (irq == next_irq) + next_irq = irq + 1; + } + } + /* set IRQ for virtio block, see devices/virtio.h */ + attr->vblk_irq_base = next_irq; + + /* adding new virtio block nodes */ + for (int i = 0; i < attr->vblk_cnt; i++) { + uint32_t new_addr = next_addr + i * addr_offset; + uint32_t new_irq = next_irq + i; + + char node_name[32]; + snprintf(node_name, sizeof(node_name), "virtio@%x", new_addr); + + int subnode = fdt_add_subnode(dtb_buf, node, node_name); + if (subnode == -FDT_ERR_NOSPACE) { + rv_log_warn("add subnode no space!\n"); + } + assert(subnode >= 0); - assert(fdt_del_node(blob, subnode) == 0); + /* compatible = "virtio,mmio" */ + assert(fdt_setprop_string(dtb_buf, subnode, "compatible", + "virtio,mmio") == 0); + + /* reg = */ + uint32_t reg[2] = {cpu_to_fdt32(new_addr), cpu_to_fdt32(size)}; + assert(fdt_setprop(dtb_buf, subnode, "reg", reg, sizeof(reg)) == 0); + + /* interrupts = */ + uint32_t irq = cpu_to_fdt32(new_irq); + assert(fdt_setprop(dtb_buf, subnode, "interrupts", &irq, + sizeof(irq)) == 0); + } } + memcpy(blob, dtb_buf, minimal_len + DTB_EXPAND_SIZE); + free(dtb_buf); + totalsize = fdt_totalsize(blob); *ram_loc += totalsize; return; @@ -406,28 +471,36 @@ static void rv_fsync_device() * * vblk is optional, so it could be NULL */ - if (attr->vblk) { - if (attr->vblk->disk_fd >= 3) { - if (attr->vblk->device_features & VIRTIO_BLK_F_RO) /* readonly */ - goto end; - - if (pwrite(attr->vblk->disk_fd, attr->vblk->disk, - attr->vblk->disk_size, 0) == -1) { - rv_log_error("pwrite block device failed: %s", strerror(errno)); - return; + if (attr->vblk_cnt) { + for (int i = 0; i < attr->vblk_cnt; i++) { + virtio_blk_state_t *vblk = attr->vblk[i]; + if (vblk->disk_fd >= 3) { + if (vblk->device_features & VIRTIO_BLK_F_RO) /* readonly */ + goto end; + + if (pwrite(vblk->disk_fd, vblk->disk, vblk->disk_size, 0) == + -1) { + rv_log_error("pwrite block device failed: %s", + strerror(errno)); + return; + } + + if (fsync(vblk->disk_fd) == -1) { + rv_log_error("fsync block device failed: %s", + strerror(errno)); + return; + } + rv_log_info("Sync block device OK"); + + end: + close(vblk->disk_fd); } - if (fsync(attr->vblk->disk_fd) == -1) { - rv_log_error("fsync block device failed: %s", strerror(errno)); - return; - } - rv_log_info("Sync block device OK"); - - end: - close(attr->vblk->disk_fd); + vblk_delete(vblk); } - vblk_delete(attr->vblk); + free(attr->vblk); + free(attr->disk); } } #endif /* RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) */ @@ -548,6 +621,9 @@ riscv_t *rv_create(riscv_user_t rv_attr) * *----------------*----------------*-------* */ + /* load_dtb needs the count to add the virtio block subnode dynamically */ + attr->vblk_cnt = attr->data.system.vblk_device_cnt; + char *ram_loc = (char *) attr->mem->mem_base; map_file(&ram_loc, attr->data.system.kernel); rv_log_info("Kernel loaded"); @@ -590,36 +666,47 @@ riscv_t *rv_create(riscv_user_t rv_attr) attr->uart->out_fd = attr->fd_stdout; /* setup virtio-blk */ - attr->vblk = NULL; - if (attr->data.system.vblk_device) { + attr->vblk_mmio_base_hi = 0x41; + attr->vblk_mmio_max_hi = attr->vblk_mmio_base_hi + attr->vblk_cnt; + + attr->vblk = malloc(sizeof(virtio_blk_state_t *) * attr->vblk_cnt); + assert(attr->vblk); + attr->disk = malloc(sizeof(uint32_t *) * attr->vblk_cnt); + assert(attr->disk); + + if (attr->vblk_cnt) { + for (int i = 0; i < attr->vblk_cnt; i++) { /* Currently, only used for block image path and permission */ #define MAX_OPTS 2 - char *vblk_opts[MAX_OPTS] = {NULL}; - int vblk_opt_idx = 0; - char *opt = strtok(attr->data.system.vblk_device, ","); - while (opt) { - if (vblk_opt_idx == MAX_OPTS) { - rv_log_error("Too many arguments for vblk"); - break; + char *vblk_device_str = attr->data.system.vblk_device[i]; + char *vblk_opts[MAX_OPTS] = {NULL}; + int vblk_opt_idx = 0; + char *opt = strtok(vblk_device_str, ","); + while (opt) { + if (vblk_opt_idx == MAX_OPTS) { + rv_log_error("Too many arguments for vblk"); + break; + } + vblk_opts[vblk_opt_idx++] = opt; + opt = strtok(NULL, ","); } - vblk_opts[vblk_opt_idx++] = opt; - opt = strtok(NULL, ","); - } - char *vblk_device = vblk_opts[0]; - char *vblk_readonly = vblk_opts[1]; - - bool readonly = false; - if (vblk_readonly) { - if (strcmp(vblk_readonly, "readonly") != 0) { - rv_log_error("Unknown vblk option: %s", vblk_readonly); - exit(EXIT_FAILURE); + char *vblk_device = vblk_opts[0]; + char *vblk_readonly = vblk_opts[1]; + + bool readonly = false; + if (vblk_readonly) { + if (strcmp(vblk_readonly, "readonly") != 0) { + rv_log_error("Unknown vblk option: %s", vblk_readonly); + exit(EXIT_FAILURE); + } + readonly = true; } - readonly = true; - } - attr->vblk = vblk_new(); - attr->vblk->ram = (uint32_t *) attr->mem->mem_base; - attr->disk = virtio_blk_init(attr->vblk, vblk_device, readonly); + attr->vblk[i] = vblk_new(); + attr->vblk[i]->ram = (uint32_t *) attr->mem->mem_base; + attr->disk[i] = + virtio_blk_init(attr->vblk[i], vblk_device, readonly); + } } capture_keyboard_input(); diff --git a/src/riscv.h b/src/riscv.h index 88eceef56..63f375be7 100644 --- a/src/riscv.h +++ b/src/riscv.h @@ -461,7 +461,8 @@ typedef struct { char *kernel; char *initrd; char *bootargs; - char *vblk_device; + char **vblk_device; + int vblk_device_cnt; } vm_system_t; #endif /* RV32_HAS(SYSTEM) */ @@ -483,8 +484,13 @@ typedef struct { plic_t *plic; /* virtio-blk device */ - uint32_t *disk; - virtio_blk_state_t *vblk; + uint32_t **disk; + virtio_blk_state_t **vblk; + virtio_blk_state_t *vblk_curr; + uint32_t vblk_mmio_base_hi; + uint32_t vblk_mmio_max_hi; + int vblk_irq_base; + int vblk_cnt; #endif /* RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) */ /* vm memory object */ diff --git a/src/system.c b/src/system.c index a87b55482..4dd66f224 100644 --- a/src/system.c +++ b/src/system.c @@ -22,11 +22,13 @@ void emu_update_uart_interrupts(riscv_t *rv) void emu_update_vblk_interrupts(riscv_t *rv) { vm_attr_t *attr = PRIV(rv); - if (attr->vblk->interrupt_status) - attr->plic->active |= IRQ_VBLK_BIT; - else - attr->plic->active &= ~IRQ_VBLK_BIT; - plic_update_interrupts(attr->plic); + for (int i = 0; i < attr->vblk_cnt; i++) { + if (attr->vblk[i]->interrupt_status) + attr->plic->active |= IRQ_VBLK_BIT(attr->vblk_irq_base, i); + else + attr->plic->active &= ~IRQ_VBLK_BIT(attr->vblk_irq_base, i); + plic_update_interrupts(attr->plic); + } } #endif diff --git a/src/system.h b/src/system.h index af94f612c..ace503599 100644 --- a/src/system.h +++ b/src/system.h @@ -54,11 +54,11 @@ enum SUPPORTED_MMIO { break; \ case MMIO_VIRTIOBLK: \ IIF(rw)( /* read */ \ - mmio_read_val = virtio_blk_read(PRIV(rv)->vblk, addr & 0xFFFFF); \ + mmio_read_val = virtio_blk_read(PRIV(rv)->vblk_curr, addr & 0xFFFFF); \ emu_update_vblk_interrupts(rv); \ return mmio_read_val; \ , /* write */ \ - virtio_blk_write(PRIV(rv)->vblk, addr & 0xFFFFF, val); \ + virtio_blk_write(PRIV(rv)->vblk_curr, addr & 0xFFFFF, val); \ emu_update_vblk_interrupts(rv); \ return; \ ) \ @@ -69,49 +69,59 @@ enum SUPPORTED_MMIO { } /* clang-format on */ -#define MMIO_READ() \ - do { \ - uint32_t mmio_read_val; \ - if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */ \ - /* 256 regions of 1MiB */ \ - switch ((addr >> 20) & MASK(8)) { \ - case 0x0: \ - case 0x2: /* PLIC (0 - 0x3F) */ \ - MMIO_OP(MMIO_PLIC, MMIO_R); \ - break; \ - case 0x40: /* UART */ \ - MMIO_OP(MMIO_UART, MMIO_R); \ - break; \ - case 0x42: /* Virtio-blk */ \ - MMIO_OP(MMIO_VIRTIOBLK, MMIO_R); \ - break; \ - default: \ - __UNREACHABLE; \ - break; \ - } \ - } \ +#define MMIO_READ() \ + do { \ + uint32_t mmio_read_val; \ + if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */ \ + /* 256 regions of 1MiB */ \ + uint32_t hi = (addr >> 20) & MASK(8); \ + if (hi >= PRIV(rv)->vblk_mmio_base_hi && \ + hi <= PRIV(rv)->vblk_mmio_max_hi) { \ + PRIV(rv)->vblk_curr = \ + PRIV(rv)->vblk[hi - PRIV(rv)->vblk_mmio_base_hi]; \ + MMIO_OP(MMIO_VIRTIOBLK, MMIO_R); \ + } else { \ + switch (hi) { \ + case 0x0: \ + case 0x2: /* PLIC (0 - 0x3F) */ \ + MMIO_OP(MMIO_PLIC, MMIO_R); \ + break; \ + case 0x40: /* UART */ \ + MMIO_OP(MMIO_UART, MMIO_R); \ + break; \ + default: \ + __UNREACHABLE; \ + break; \ + } \ + } \ + } \ } while (0) -#define MMIO_WRITE() \ - do { \ - if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */ \ - /* 256 regions of 1MiB */ \ - switch ((addr >> 20) & MASK(8)) { \ - case 0x0: \ - case 0x2: /* PLIC (0 - 0x3F) */ \ - MMIO_OP(MMIO_PLIC, MMIO_W); \ - break; \ - case 0x40: /* UART */ \ - MMIO_OP(MMIO_UART, MMIO_W); \ - break; \ - case 0x42: /* Virtio-blk */ \ - MMIO_OP(MMIO_VIRTIOBLK, MMIO_W); \ - break; \ - default: \ - __UNREACHABLE; \ - break; \ - } \ - } \ +#define MMIO_WRITE() \ + do { \ + if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */ \ + /* 256 regions of 1MiB */ \ + uint32_t hi = (addr >> 20) & MASK(8); \ + if (hi >= PRIV(rv)->vblk_mmio_base_hi && \ + hi <= PRIV(rv)->vblk_mmio_max_hi) { \ + PRIV(rv)->vblk_curr = \ + PRIV(rv)->vblk[hi - PRIV(rv)->vblk_mmio_base_hi]; \ + MMIO_OP(MMIO_VIRTIOBLK, MMIO_W); \ + } else { \ + switch (hi) { \ + case 0x0: \ + case 0x2: /* PLIC (0 - 0x3F) */ \ + MMIO_OP(MMIO_PLIC, MMIO_W); \ + break; \ + case 0x40: /* UART */ \ + MMIO_OP(MMIO_UART, MMIO_W); \ + break; \ + default: \ + __UNREACHABLE; \ + break; \ + } \ + } \ + } \ } while (0) void emu_update_uart_interrupts(riscv_t *rv); From 44bce03e28b67b8e29150bd07e22ed767e3a0ec6 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sun, 31 Aug 2025 05:09:22 +0800 Subject: [PATCH 2/4] CI: Cover multiple block device cases --- .ci/boot-linux.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.ci/boot-linux.sh b/.ci/boot-linux.sh index b1f5868fa..9a91abd95 100755 --- a/.ci/boot-linux.sh +++ b/.ci/boot-linux.sh @@ -61,6 +61,20 @@ if [ "${ENABLE_VBLK}" -eq "1" ]; then expect "# " { send "\x01"; send "x" } timeout { exit 3 } ') + # multiple blocks, Read-only, one disk image, one loop device (/dev/loopx(Linux) or /dev/diskx(Darwin)) + TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG},readonly -x vblk:${BLK_DEV},readonly") + EXPECT_CMDS+=(' + expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "uname -a\n" } timeout { exit 2 } + expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 } + expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 } + expect -ex "-sh: can'\''t create mnt/emu.txt: Read-only file system" {} timeout { exit 3 } + expect "# " { send "mkdir mnt2 && mount /dev/vdb mnt2\n" } timeout { exit 3 } + expect "# " { send "echo rv32emu > mnt2/emu.txt\n" } timeout { exit 3 } + expect -ex "-sh: can'\''t create mnt2/emu.txt: Read-only file system" {} timeout { exit 3 } + expect "# " { send "\x01"; send "x" } timeout { exit 3 } + ') + # Read-write using disk image TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG}") VBLK_EXPECT_CMDS=' @@ -77,6 +91,23 @@ if [ "${ENABLE_VBLK}" -eq "1" ]; then # Read-write using /dev/loopx(Linux) or /dev/diskx(Darwin) block device TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${BLK_DEV}") EXPECT_CMDS+=("${VBLK_EXPECT_CMDS}") + + # multiple blocks, Read-write, one disk image and one loop device (/dev/loopx(Linux) or /dev/diskx(Darwin)) + TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG} -x vblk:${BLK_DEV}") + VBLK_EXPECT_CMDS=' + expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "uname -a\n" } timeout { exit 2 } + expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 } + expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 } + expect "# " { send "sync\n" } timeout { exit 3 } + expect "# " { send "umount mnt\n" } timeout { exit 3 } + expect "# " { send "mkdir mnt2 && mount /dev/vdb mnt2\n" } timeout { exit 3 } + expect "# " { send "echo rv32emu > mnt2/emu.txt\n" } timeout { exit 3 } + expect "# " { send "sync\n" } timeout { exit 3 } + expect "# " { send "umount mnt2\n" } timeout { exit 3 } + expect "# " { send "\x01"; send "x" } timeout { exit 3 } + ' + EXPECT_CMDS+=("${VBLK_EXPECT_CMDS}") fi for i in "${!TEST_OPTIONS[@]}"; do From 436a2ed563572d2697a218c1ccc7e1f52acc7b12 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sun, 31 Aug 2025 05:15:50 +0800 Subject: [PATCH 3/4] Update README To reflect supporting multiple virtual block devices in guestOS. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 3efc805da..8c86a40f0 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,12 @@ Mount the virtual block device and create a test file after booting, note that r ``` Reboot and re-mount the virtual block device, the written file should remain existing. +To specify multiple virtual block devices, pass multiple `-x vblk` options when launching the emulator. Each option can point to either a disk image or a hostOS block device, with optional read-only mode. For example: +```shell +$ build/rv32emu -k -i -x vblk:disk.img -x vblk:/dev/loop22,readonly +``` +Note that the /dev/vdx device order in guestOS 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. + #### Customize bootargs Build and run with customized bootargs to boot the guestOS. Otherwise, the default bootargs defined in `src/devices/minimal.dts` will be used. ```shell From 9b4f2fe9e49abceb84ae0564358685d2186a1302 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sun, 7 Sep 2025 14:41:18 +0800 Subject: [PATCH 4/4] Fix off-by-one error in bootargs parsing This commit prevents null terminator at wrong position. --- src/riscv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/riscv.c b/src/riscv.c index e420dea6f..5b43114d4 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -318,10 +318,10 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr) node = fdt_path_offset(dtb_buf, "/chosen"); assert(node > 0); - len = strlen(bootargs) + 1; - buf = malloc(len); + len = strlen(bootargs); + buf = malloc(len + 1); assert(buf); - memcpy(buf, bootargs, len - 1); + memcpy(buf, bootargs, len); buf[len] = 0; err = fdt_setprop(dtb_buf, node, "bootargs", buf, len + 1); if (err == -FDT_ERR_NOSPACE) {