diff --git a/.ci/boot-linux.sh b/.ci/boot-linux.sh index df619ad0..f439fadb 100755 --- a/.ci/boot-linux.sh +++ b/.ci/boot-linux.sh @@ -16,12 +16,37 @@ function ASSERT { cleanup +ENABLE_VBLK=1 +VBLK_IMG=build/disk.img +which dd >/dev/null 2>&1 || ENABLE_VBLK=0 +which mkfs.ext4 >/dev/null 2>&1 || which $(brew --prefix e2fsprogs)/sbin/mkfs.ext4 >/dev/null 2>&1 || ENABLE_VBLK=0 +which 7z >/dev/null 2>&1 || ENABLE_VBLK=0 + TIMEOUT=50 OPTS=" -k build/linux-image/Image " OPTS+=" -i build/linux-image/rootfs.cpio " -OPTS+=" -b build/minimal.dtb " +if [ "$ENABLE_VBLK" -eq "1" ]; then + dd if=/dev/zero of=$VBLK_IMG bs=4M count=32 + mkfs.ext4 $VBLK_IMG || $(brew --prefix e2fsprogs)/sbin/mkfs.ext4 $VBLK_IMG + OPTS+=" -x vblk:$VBLK_IMG " +else + printf "Virtio-blk Test...Passed\n" +fi RUN_LINUX="build/rv32emu ${OPTS}" +if [ "$ENABLE_VBLK" -eq "1" ]; then +ASSERT expect < mnt/emu.txt\n" } timeout { exit 3 } +expect "# " { send "sync\n" } timeout { exit 3 } +expect "# " { send "umount mnt\n" } timeout { exit 3 } +expect "# " { send "\x01"; send "x" } timeout { exit 3 } +DONE +else ASSERT expect </dev/null 2>&1 || ret=4 + printf "Virtio-blk Test: [ ${MESSAGES[$ret]}${COLOR_N} ]\n" +fi exit ${ret} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 272a2aff..6366c82e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,7 +46,7 @@ jobs: - name: install-dependencies run: | sudo apt-get update -q -y - sudo apt-get install -q -y libsdl2-dev libsdl2-mixer-dev device-tree-compiler expect bc + sudo apt-get install -q -y libsdl2-dev libsdl2-mixer-dev device-tree-compiler expect bc p7zip-full .ci/riscv-toolchain-install.sh echo "${{ github.workspace }}/toolchain/bin" >> $GITHUB_PATH wget https://apt.llvm.org/llvm.sh diff --git a/Makefile b/Makefile index 0f695258..f896206a 100644 --- a/Makefile +++ b/Makefile @@ -195,7 +195,7 @@ endif # during emulator initialization. $(call set-feature, FULL4G) ifeq ($(call has, FULL4G), 1) -$(OUT)/main.o: CFLAGS += -DMEM_SIZE=0xFFFFFFFFULL # 2^{32} - 1 +CFLAGS += -DMEM_SIZE=0xFFFFFFFFULL # 2^{32} - 1 endif ENABLE_GDBSTUB ?= 0 diff --git a/README.md b/README.md index 1e72285f..4b7ee4e5 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ $ make ENABLE_SYSTEM=1 system Build using run using specified images: ```shell $ make ENABLE_SYSTEM=1 -$ build/rv32emu -k -i +$ build/rv32emu -k -i [-x vblk:] ``` Build with a larger INITRD_SIZE (e.g., 64 MiB) to run SDL-oriented application because the default 8 MiB is insufficient for SDL-oriented application artifacts: @@ -86,6 +86,21 @@ $ make system ENABLE_SYSTEM=1 ENABLE_SDL=1 INITRD_SIZE=64 ``` Once login the guestOS, run `doom-riscv` or `quake` or `smolnes`. To terminate SDL-oriented applications, use the built-in exit utility, ctrl-c or the SDL window close button(X). +#### Virtio Block Device (optional) +Generate ext4 image file for virtio block device in Unix-like system: +```shell +$ dd if=/dev/zero of=disk.img bs=4M count=32 +$ mkfs.ext4 disk.img +``` +Mount the virtual block device and create a test file after booting, note that root privilege is required to mount and unmount a disk: +```shell +# mkdir mnt +# mount /dev/vda mnt +# echo "rv32emu" > mnt/emu.txt +# umount mnt +``` +Reboot and re-mount the virtual block device, the written file should remain existing. + #### Build Linux image An automated build script is provided to compile the RISC-V cross-compiler, Busybox, and Linux kernel from source. Please note that it only supports the Linux host environment. It can be found at tools/build-linux-image.sh. ``` diff --git a/src/devices/minimal.dts b/src/devices/minimal.dts index cb4951de..315be209 100644 --- a/src/devices/minimal.dts +++ b/src/devices/minimal.dts @@ -65,5 +65,11 @@ no-loopback-test; clock-frequency = <5000000>; /* the baudrate divisor is ignored */ }; + + blk0: virtio@4200000 { + compatible = "virtio,mmio"; + reg = <0x4200000 0x200>; + interrupts = <3>; + }; }; }; diff --git a/src/devices/virtio-blk.c b/src/devices/virtio-blk.c new file mode 100644 index 00000000..94af5ea1 --- /dev/null +++ b/src/devices/virtio-blk.c @@ -0,0 +1,447 @@ +/* + * rv32emu is freely redistributable under the MIT License. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virtio.h" + +#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 +#define VBLK_QUEUE (vblk->queues[vblk->queue_sel]) + +#define VBLK_PRIV(x) ((struct virtio_blk_config *) x->priv) + +PACKED(struct virtio_blk_config { + uint64_t capacity; + uint32_t size_max; + uint32_t seg_max; + + struct virtio_blk_geometry { + uint16_t cylinders; + uint8_t heads; + uint8_t sectors; + } geometry; + + uint32_t blk_size; + + struct virtio_blk_topology { + uint8_t physical_block_exp; + uint8_t alignment_offset; + uint16_t min_io_size; + uint32_t opt_io_size; + } topology; + + uint8_t writeback; + uint8_t unused0[3]; + uint32_t max_discard_sectors; + uint32_t max_discard_seg; + uint32_t discard_sector_alignment; + uint32_t max_write_zeroes_sectors; + uint32_t max_write_zeroes_seg; + uint8_t write_zeroes_may_unmap; + uint8_t unused1[3]; + uint64_t disk_size; +}); + +PACKED(struct vblk_req_header { + uint32_t type; + uint32_t reserved; + uint64_t sector; + 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; + if (vblk->status & VIRTIO_STATUS_DRIVER_OK) + vblk->interrupt_status |= VIRTIO_INT_CONF_CHANGE; +} + +static inline uint32_t vblk_preprocess(virtio_blk_state_t *vblk UNUSED, + uint32_t addr) +{ + if ((addr >= MEM_SIZE) || (addr & 0b11)) { + virtio_blk_set_fail(vblk); + return 0; + } + + return addr >> 2; +} + +static void virtio_blk_update_status(virtio_blk_state_t *vblk, uint32_t status) +{ + vblk->status |= status; + if (status) + return; + + /* Reset */ + uint32_t *ram = vblk->ram; + uint32_t *disk = vblk->disk; + void *priv = vblk->priv; + uint32_t capacity = VBLK_PRIV(vblk)->capacity; + memset(vblk, 0, sizeof(*vblk)); + vblk->ram = ram; + vblk->disk = disk; + vblk->priv = priv; + VBLK_PRIV(vblk)->capacity = capacity; +} + +static void virtio_blk_write_handler(virtio_blk_state_t *vblk, + uint64_t sector, + uint64_t desc_addr, + uint32_t len) +{ + void *dest = (void *) ((uintptr_t) vblk->disk + sector * DISK_BLK_SIZE); + const void *src = (void *) ((uintptr_t) vblk->ram + desc_addr); + memcpy(dest, src, len); +} + +static void virtio_blk_read_handler(virtio_blk_state_t *vblk, + uint64_t sector, + uint64_t desc_addr, + uint32_t len) +{ + void *dest = (void *) ((uintptr_t) vblk->ram + desc_addr); + const void *src = + (void *) ((uintptr_t) vblk->disk + sector * DISK_BLK_SIZE); + memcpy(dest, src, len); +} + +static int virtio_blk_desc_handler(virtio_blk_state_t *vblk, + const virtio_blk_queue_t *queue, + uint16_t desc_idx, + uint32_t *plen) +{ + /* A full virtio_blk_req is represented by 3 descriptors, where + * the first descriptor contains: + * le32 type + * le32 reserved + * le64 sector + * the second descriptor contains: + * u8 data[][512] + * the third descriptor contains: + * u8 status + */ + struct virtq_desc vq_desc[3]; + + /* Collect the descriptors */ + for (int i = 0; i < 3; i++) { + /* The size of the `struct virtq_desc` is 4 words */ + const struct virtq_desc *desc = + (struct virtq_desc *) &vblk->ram[queue->queue_desc + desc_idx * 4]; + + /* Retrieve the fields of current descriptor */ + vq_desc[i].addr = desc->addr; + vq_desc[i].len = desc->len; + vq_desc[i].flags = desc->flags; + desc_idx = desc->next; + } + + /* The next flag for the first and second descriptors should be set, + * whereas for the third descriptor is should not be set + */ + if (!(vq_desc[0].flags & VIRTIO_DESC_F_NEXT) || + !(vq_desc[1].flags & VIRTIO_DESC_F_NEXT) || + (vq_desc[2].flags & VIRTIO_DESC_F_NEXT)) { + /* since the descriptor list is abnormal, we don't write the status + * back here */ + virtio_blk_set_fail(vblk); + return -1; + } + + /* Process the header */ + const struct vblk_req_header *header = + (struct vblk_req_header *) ((uintptr_t) vblk->ram + vq_desc[0].addr); + uint32_t type = header->type; + uint64_t sector = header->sector; + uint8_t *status = (uint8_t *) ((uintptr_t) vblk->ram + vq_desc[2].addr); + + /* Check sector index is valid */ + if (sector > (VBLK_PRIV(vblk)->capacity - 1)) { + *status = VIRTIO_BLK_S_IOERR; + return -1; + } + + /* Process the data */ + switch (type) { + case VIRTIO_BLK_T_IN: + virtio_blk_read_handler(vblk, sector, vq_desc[1].addr, vq_desc[1].len); + break; + case VIRTIO_BLK_T_OUT: + virtio_blk_write_handler(vblk, sector, vq_desc[1].addr, vq_desc[1].len); + break; + default: + fprintf(stderr, "unsupported virtio-blk operation!\n"); + *status = VIRTIO_BLK_S_UNSUPP; + return -1; + } + + /* Return the device status */ + *status = VIRTIO_BLK_S_OK; + *plen = vq_desc[1].len; + + return 0; +} + +static void virtio_queue_notify_handler(virtio_blk_state_t *vblk, int index) +{ + uint32_t *ram = vblk->ram; + virtio_blk_queue_t *queue = &vblk->queues[index]; + if (vblk->status & VIRTIO_STATUS_DEVICE_NEEDS_RESET) + return; + + if (!((vblk->status & VIRTIO_STATUS_DRIVER_OK) && queue->ready)) + return virtio_blk_set_fail(vblk); + + /* Check for new buffers */ + uint16_t new_avail = ram[queue->queue_avail] >> 16; + if (new_avail - queue->last_avail > (uint16_t) queue->queue_num) { + fprintf(stderr, "size check fail\n"); + return virtio_blk_set_fail(vblk); + } + + if (queue->last_avail == new_avail) + return; + + /* Process them */ + uint16_t new_used = + ram[queue->queue_used] >> 16; /* virtq_used.idx (le16) */ + while (queue->last_avail != new_avail) { + /* Obtain the index in the ring buffer */ + uint16_t queue_idx = queue->last_avail % queue->queue_num; + + /* Since each buffer index occupies 2 bytes but the memory is aligned + * with 4 bytes, and the first element of the available queue is stored + * at ram[queue->queue_avail + 1], to acquire the buffer index, it + * requires the following array index calculation and bit shifting. + * Check also the `struct virtq_avail` on the spec. + */ + uint16_t buffer_idx = ram[queue->queue_avail + 1 + queue_idx / 2] >> + (16 * (queue_idx % 2)); + + /* Consume request from the available queue and process the data in the + * descriptor list. + */ + uint32_t len = 0; + int result = virtio_blk_desc_handler(vblk, queue, buffer_idx, &len); + if (result != 0) + return virtio_blk_set_fail(vblk); + + /* Write used element information (`struct virtq_used_elem`) to the used + * queue */ + uint32_t vq_used_addr = + queue->queue_used + 1 + (new_used % queue->queue_num) * 2; + ram[vq_used_addr] = buffer_idx; /* virtq_used_elem.id (le32) */ + ram[vq_used_addr + 1] = len; /* virtq_used_elem.len (le32) */ + queue->last_avail++; + new_used++; + } + + /* Check le32 len field of `struct virtq_used_elem` on the spec */ + vblk->ram[queue->queue_used] &= MASK(16); /* Reset low 16 bits to zero */ + vblk->ram[queue->queue_used] |= ((uint32_t) new_used) << 16; /* len */ + + /* Send interrupt, unless VIRTQ_AVAIL_F_NO_INTERRUPT is set */ + if (!(ram[queue->queue_avail] & 1)) + vblk->interrupt_status |= VIRTIO_INT_USED_RING; +} + +uint32_t virtio_blk_read(virtio_blk_state_t *vblk, uint32_t addr) +{ + addr = addr >> 2; +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(MagicValue): + return VIRTIO_MAGIC_NUMBER; + case _(Version): + return VIRTIO_VERSION; + case _(DeviceID): + return VIRTIO_BLK_DEV_ID; + case _(VendorID): + return VIRTIO_VENDOR_ID; + case _(DeviceFeatures): + return vblk->driver_features_sel == 0 + ? VBLK_FEATURES_0 + : (vblk->driver_features_sel == 1 ? VBLK_FEATURES_1 : 0); + case _(QueueNumMax): + return VBLK_QUEUE_NUM_MAX; + case _(QueueReady): + return (uint32_t) VBLK_QUEUE.ready; + case _(InterruptStatus): + return vblk->interrupt_status; + case _(Status): + return vblk->status; + case _(ConfigGeneration): + return VIRTIO_CONFIG_GENERATE; + default: + /* Read configuration from the corresponding register */ + return ((uint32_t *) VBLK_PRIV(vblk))[addr - _(Config)]; + } +#undef _ +} + +void virtio_blk_write(virtio_blk_state_t *vblk, uint32_t addr, uint32_t value) +{ + addr = addr >> 2; +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(DeviceFeaturesSel): + vblk->driver_features_sel = value; + break; + case _(DriverFeatures): + vblk->driver_features_sel == 0 ? (vblk->driver_features = value) : 0; + break; + case _(DriverFeaturesSel): + vblk->driver_features_sel = value; + break; + case _(QueueSel): + if (value < ARRAY_SIZE(vblk->queues)) + vblk->queue_sel = value; + else + virtio_blk_set_fail(vblk); + break; + case _(QueueNum): + if (value > 0 && value <= VBLK_QUEUE_NUM_MAX) + VBLK_QUEUE.queue_num = value; + else + virtio_blk_set_fail(vblk); + break; + case _(QueueReady): + VBLK_QUEUE.ready = value & 1; + if (value & 1) + VBLK_QUEUE.last_avail = vblk->ram[VBLK_QUEUE.queue_avail] >> 16; + break; + case _(QueueDescLow): + VBLK_QUEUE.queue_desc = vblk_preprocess(vblk, value); + break; + case _(QueueDescHigh): + if (value) + virtio_blk_set_fail(vblk); + break; + case _(QueueDriverLow): + VBLK_QUEUE.queue_avail = vblk_preprocess(vblk, value); + break; + case _(QueueDriverHigh): + if (value) + virtio_blk_set_fail(vblk); + break; + case _(QueueDeviceLow): + VBLK_QUEUE.queue_used = vblk_preprocess(vblk, value); + break; + case _(QueueDeviceHigh): + if (value) + virtio_blk_set_fail(vblk); + break; + case _(QueueNotify): + if (value < ARRAY_SIZE(vblk->queues)) + virtio_queue_notify_handler(vblk, value); + else + virtio_blk_set_fail(vblk); + break; + case _(InterruptACK): + vblk->interrupt_status &= ~value; + break; + case _(Status): + virtio_blk_update_status(vblk, value); + break; + default: + /* Write configuration to the corresponding register */ + ((uint32_t *) VBLK_PRIV(vblk))[addr - _(Config)] = value; + break; + } +#undef _ +} + +uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, char *disk_file) +{ + if (vblk_dev_cnt >= VBLK_DEV_CNT_MAX) { + fprintf(stderr, + "Exceeded the number of virtio-blk devices that can be " + "allocated.\n"); + exit(EXIT_FAILURE); + } + + /* Allocate memory for the private member */ + vblk->priv = &vblk_configs[vblk_dev_cnt++]; + + /* No disk image is provided */ + if (!disk_file) { + /* By setting the block capacity to zero, the kernel will + * then not to touch the device after booting */ + VBLK_PRIV(vblk)->capacity = 0; + return NULL; + } + + /* Open disk file */ + int disk_fd = open(disk_file, O_RDWR); + if (disk_fd < 0) { + fprintf(stderr, "could not open %s\n", disk_file); + exit(EXIT_FAILURE); + } + + /* Get the disk image size */ + struct stat st; + fstat(disk_fd, &st); + VBLK_PRIV(vblk)->disk_size = st.st_size; + + /* Set up the disk memory */ + uint32_t *disk_mem; +#if HAVE_MMAP + disk_mem = mmap(NULL, VBLK_PRIV(vblk)->disk_size, PROT_READ | PROT_WRITE, + MAP_SHARED, disk_fd, 0); + if (disk_mem == MAP_FAILED) { + fprintf(stderr, "Could not map disk\n"); + return NULL; + } +#else + disk_mem = malloc(VBLK_PRIV(vblk)->disk_size); + if (!disk_mem) { + fprintf(stderr, "Could not map disk\n"); + return NULL; + } +#endif + assert(!(((uintptr_t) disk_mem) & 0b11)); + close(disk_fd); + + vblk->disk = disk_mem; + VBLK_PRIV(vblk)->capacity = + (VBLK_PRIV(vblk)->disk_size - 1) / DISK_BLK_SIZE + 1; + + return disk_mem; +} + +virtio_blk_state_t *vblk_new() +{ + virtio_blk_state_t *vblk = calloc(1, sizeof(virtio_blk_state_t)); + assert(vblk); + return vblk; +} + +void vblk_delete(virtio_blk_state_t *vblk) +{ +#if HAVE_MMAP + munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size); +#else + free(vblk->disk); +#endif + free(vblk); +} diff --git a/src/devices/virtio.h b/src/devices/virtio.h new file mode 100644 index 00000000..27d54442 --- /dev/null +++ b/src/devices/virtio.h @@ -0,0 +1,114 @@ +/* + * rv32emu is freely redistributable under the MIT License. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +#pragma once + +#define VIRTIO_VENDOR_ID 0x12345678 +#define VIRTIO_MAGIC_NUMBER 0x74726976 +#define VIRTIO_VERSION 2 +#define VIRTIO_CONFIG_GENERATE 0 + +#define VIRTIO_STATUS_DRIVER_OK 4 +#define VIRTIO_STATUS_DEVICE_NEEDS_RESET 64 + +#define VIRTIO_INT_USED_RING 1 +#define VIRTIO_INT_CONF_CHANGE 2 + +#define VIRTIO_DESC_F_NEXT 1 +#define VIRTIO_DESC_F_WRITE 2 + +#define VIRTIO_BLK_DEV_ID 2 +#define VIRTIO_BLK_T_IN 0 +#define VIRTIO_BLK_T_OUT 1 +#define VIRTIO_BLK_T_FLUSH 4 +#define VIRTIO_BLK_T_GET_ID 8 +#define VIRTIO_BLK_T_GET_LIFETIME 10 +#define VIRTIO_BLK_T_DISCARD 11 +#define VIRTIO_BLK_T_WRITE_ZEROES 13 +#define VIRTIO_BLK_T_SECURE_ERASE 14 + +#define VIRTIO_BLK_S_OK 0 +#define VIRTIO_BLK_S_IOERR 1 +#define VIRTIO_BLK_S_UNSUPP 2 + +/* VirtIO MMIO registers */ +#define VIRTIO_REG_LIST \ + _(MagicValue, 0x000) /* R */ \ + _(Version, 0x004) /* R */ \ + _(DeviceID, 0x008) /* R */ \ + _(VendorID, 0x00c) /* R */ \ + _(DeviceFeatures, 0x010) /* R */ \ + _(DeviceFeaturesSel, 0x014) /* W */ \ + _(DriverFeatures, 0x020) /* W */ \ + _(DriverFeaturesSel, 0x024) /* W */ \ + _(QueueSel, 0x030) /* W */ \ + _(QueueNumMax, 0x034) /* R */ \ + _(QueueNum, 0x038) /* W */ \ + _(QueueReady, 0x044) /* RW */ \ + _(QueueNotify, 0x050) /* W */ \ + _(InterruptStatus, 0x60) /* R */ \ + _(InterruptACK, 0x064) /* W */ \ + _(Status, 0x070) /* RW */ \ + _(QueueDescLow, 0x080) /* W */ \ + _(QueueDescHigh, 0x084) /* W */ \ + _(QueueDriverLow, 0x090) /* W */ \ + _(QueueDriverHigh, 0x094) /* W */ \ + _(QueueDeviceLow, 0x0a0) /* W */ \ + _(QueueDeviceHigh, 0x0a4) /* W */ \ + _(ConfigGeneration, 0x0fc) /* R */ \ + _(Config, 0x100) /* RW */ + +enum { +#define _(reg, addr) VIRTIO_##reg = addr >> 2, + VIRTIO_REG_LIST +#undef _ +}; + +struct virtq_desc { + uint64_t addr; + uint32_t len; + uint16_t flags; + uint16_t next; +}; + +#define IRQ_VBLK_SHIFT 3 +#define IRQ_VBLK_BIT (1 << IRQ_VBLK_SHIFT) + +typedef struct { + uint32_t queue_num; + uint32_t queue_desc; + uint32_t queue_avail; + uint32_t queue_used; + uint16_t last_avail; + bool ready; +} virtio_blk_queue_t; + +typedef struct { + /* feature negotiation */ + uint32_t device_features_sel; + uint32_t driver_features; + uint32_t driver_features_sel; + /* queue config */ + uint32_t queue_sel; + virtio_blk_queue_t queues[2]; + /* status */ + uint32_t status; + uint32_t interrupt_status; + /* supplied by environment */ + uint32_t *ram; + uint32_t *disk; + /* implementation-specific */ + void *priv; +} virtio_blk_state_t; + +uint32_t virtio_blk_read(virtio_blk_state_t *vblk, uint32_t addr); + +void virtio_blk_write(virtio_blk_state_t *vblk, uint32_t addr, uint32_t value); + +uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, char *disk_file); + +virtio_blk_state_t *vblk_new(); + +void vblk_delete(virtio_blk_state_t *vblk); diff --git a/src/main.c b/src/main.c index 861a3bb1..fc68bbcc 100644 --- a/src/main.c +++ b/src/main.c @@ -42,7 +42,7 @@ static char *opt_prog_name; /* target argc and argv */ static int prog_argc; static char **prog_args; -static const char *optstr = "tgqmhpd:a:k:i:b:"; +static const char *optstr = "tgqmhpd:a:k:i:b:x:"; /* enable misaligned memory access */ static bool opt_misaligned = false; @@ -56,6 +56,7 @@ 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; #endif static void print_usage(const char *filename) @@ -73,6 +74,7 @@ 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: : use as virtio-blk disk image\n" " -b : use customized for the kernel\n" #endif " -d [filename]: dump registers as JSON to the " @@ -118,6 +120,13 @@ static bool parse_args(int argc, char **args) opt_bootargs = optarg; emu_argc++; break; + case 'x': + if (!strncmp("vblk:", optarg, 5)) + opt_virtio_blk_img = optarg + 5; /* strlen("vblk:") */ + else + return false; + emu_argc++; + break; #endif case 'q': opt_quiet_outputs = true; @@ -258,6 +267,7 @@ 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; #else attr.data.user.elf_program = opt_prog_name; #endif diff --git a/src/riscv.c b/src/riscv.c index 9a4c7c75..ec14c0e1 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -502,6 +502,11 @@ riscv_t *rv_create(riscv_user_t rv_attr) attr->uart->in_fd = attr->fd_stdin; attr->uart->out_fd = attr->fd_stdout; + /* setup virtio-blk */ + attr->vblk = vblk_new(); + attr->vblk->ram = (uint32_t *) attr->mem->mem_base; + attr->disk = virtio_blk_init(attr->vblk, attr->data.system.vblk_device); + capture_keyboard_input(); #endif /* !RV32_HAS(SYSTEM) || (RV32_HAS(SYSTEM) && RV32_HAS(ELF_LOADER)) */ @@ -642,6 +647,7 @@ void rv_delete(riscv_t *rv) #if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) u8250_delete(attr->uart); plic_delete(attr->plic); + vblk_delete(attr->vblk); #endif free(rv); } diff --git a/src/riscv.h b/src/riscv.h index e333241e..0eccba00 100644 --- a/src/riscv.h +++ b/src/riscv.h @@ -15,6 +15,7 @@ #if RV32_HAS(SYSTEM) #include "devices/plic.h" #include "devices/uart.h" +#include "devices/virtio.h" #endif /* RV32_HAS(SYSTEM) */ #if RV32_HAS(EXT_F) @@ -459,6 +460,7 @@ typedef struct { char *kernel; char *initrd; char *bootargs; + char *vblk_device; } vm_system_t; #endif /* RV32_HAS(SYSTEM) */ @@ -478,6 +480,10 @@ typedef struct { /* plic object */ plic_t *plic; + + /* virtio-blk device */ + uint32_t *disk; + virtio_blk_state_t *vblk; #endif /* RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) */ /* vm memory object */ diff --git a/src/system.c b/src/system.c index d454a25e..82d381b0 100644 --- a/src/system.c +++ b/src/system.c @@ -11,6 +11,7 @@ #include "devices/plic.h" #include "devices/uart.h" +#include "devices/virtio.h" #include "riscv_private.h" #define R 1 @@ -28,6 +29,15 @@ void emu_update_uart_interrupts(riscv_t *rv) plic_update_interrupts(attr->plic); } +static 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); +} /* * Linux kernel might create signal frame when returning from trap * handling, which modifies the SEPC CSR. Thus, the fault instruction @@ -45,6 +55,7 @@ extern bool need_handle_signal; enum SUPPORTED_MMIO { MMIO_PLIC, MMIO_UART, + MMIO_VIRTIOBLK, }; /* clang-format off */ @@ -72,6 +83,17 @@ enum SUPPORTED_MMIO { return; \ ) \ break; \ + case MMIO_VIRTIOBLK: \ + IIF(rw)( /* read */ \ + mmio_read_val = virtio_blk_read(PRIV(rv)->vblk, addr & 0xFFFFF); \ + emu_update_vblk_interrupts(rv); \ + return mmio_read_val; \ + , /* write */ \ + virtio_blk_write(PRIV(rv)->vblk, addr & 0xFFFFF, val); \ + emu_update_vblk_interrupts(rv); \ + return; \ + ) \ + break; \ default: \ fprintf(stderr, "unknown MMIO type %d\n", io); \ break; \ @@ -91,6 +113,9 @@ enum SUPPORTED_MMIO { case 0x40: /* UART */ \ MMIO_OP(MMIO_UART, MMIO_R); \ break; \ + case 0x42: /* Virtio-blk */ \ + MMIO_OP(MMIO_VIRTIOBLK, MMIO_R); \ + break; \ default: \ __UNREACHABLE; \ break; \ @@ -110,6 +135,9 @@ enum SUPPORTED_MMIO { case 0x40: /* UART */ \ MMIO_OP(MMIO_UART, MMIO_W); \ break; \ + case 0x42: /* Virtio-blk */ \ + MMIO_OP(MMIO_VIRTIOBLK, MMIO_W); \ + break; \ default: \ __UNREACHABLE; \ break; \