From ae3090ed98d41454053fe932f808b0012dbfa28e Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Thu, 20 Jul 2023 16:38:06 +0800 Subject: [PATCH 1/6] Implement virtio-blk device --- Makefile | 2 + device.h | 51 ++++++ main.c | 38 +++- minimal-virtio.dts | 6 + virtio-blk.c | 428 +++++++++++++++++++++++++++++++++++++++++++++ virtio-net.c | 12 +- virtio.h | 32 ++++ 7 files changed, 556 insertions(+), 13 deletions(-) create mode 100644 virtio-blk.c create mode 100644 virtio.h diff --git a/Makefile b/Makefile index 15bcd4c..7d38d2d 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,9 @@ OBJS_EXTRA := ifeq ($(UNAME_S),Linux) CFLAGS += -D ENABLE_VIRTIONET +CFLAGS += -D ENABLE_VIRTIOBLK OBJS_EXTRA += virtio-net.o +OBJS_EXTRA += virtio-blk.o MINIMAL_DTS = minimal-virtio.dts else MINIMAL_DTS = minimal.dts diff --git a/device.h b/device.h index 039c8c6..a5fefea 100644 --- a/device.h +++ b/device.h @@ -117,15 +117,66 @@ void virtio_net_refresh_queue(virtio_net_state_t *vnet); bool virtio_net_init(virtio_net_state_t *vnet); #endif /* ENABLE_VIRTIONET */ +/* VirtIO-Block */ +#if defined(ENABLE_VIRTIOBLK) + +#define IRQ_VBLK 3 +#define IRQ_VBLK_BIT (1 << IRQ_VBLK) + +typedef struct { + uint32_t QueueNum; + uint32_t QueueDesc; + uint32_t QueueAvail; + uint32_t QueueUsed; + uint16_t last_avail; + bool ready; +} virtio_blk_queue_t; + +typedef struct { + /* feature negotiation */ + uint32_t DeviceFeaturesSel; + uint32_t DriverFeatures; + uint32_t DriverFeaturesSel; + /* queue config */ + uint32_t QueueSel; + virtio_blk_queue_t queues[2]; + /* status */ + uint32_t Status; + uint32_t InterruptStatus; + /* supplied by environment */ + uint32_t *ram; + uint32_t *disk; + uint64_t capacity; +} virtio_blk_state_t; + +void virtio_blk_read(vm_t *vm, + virtio_blk_state_t *vblk, + uint32_t addr, + uint8_t width, + uint32_t *value); + +void virtio_blk_write(vm_t *vm, + virtio_blk_state_t *vblk, + uint32_t addr, + uint8_t width, + uint32_t value); + +uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, char *disk_file); +#endif /* ENABLE_VIRTIOBLK */ + /* memory mapping */ typedef struct { bool stopped; uint32_t *ram; + uint32_t *disk; plic_state_t plic; u8250_state_t uart; #if defined(ENABLE_VIRTIONET) virtio_net_state_t vnet; +#endif +#if defined(ENABLE_VIRTIOBLK) + virtio_blk_state_t vblk; #endif uint32_t timer_lo, timer_hi; } emu_state_t; diff --git a/main.c b/main.c index 356feba..76cc9ef 100644 --- a/main.c +++ b/main.c @@ -57,6 +57,18 @@ static void emu_update_vnet_interrupts(vm_t *vm) } #endif +#if defined(ENABLE_VIRTIOBLK) +static void emu_update_vblk_interrupts(vm_t *vm) +{ + emu_state_t *data = (emu_state_t *) vm->priv; + if (data->vblk.InterruptStatus) + data->plic.active |= IRQ_VBLK_BIT; + else + data->plic.active &= ~IRQ_VBLK_BIT; + plic_update_interrupts(vm, &data->plic); +} +#endif + static void mem_load(vm_t *vm, uint32_t addr, uint8_t width, uint32_t *value) { emu_state_t *data = (emu_state_t *) vm->priv; @@ -85,8 +97,14 @@ static void mem_load(vm_t *vm, uint32_t addr, uint8_t width, uint32_t *value) emu_update_vnet_interrupts(vm); return; #endif +#if defined(ENABLE_VIRTIOBLK) + case 0x42: /* virtio-blk */ + virtio_blk_read(vm, &data->vblk, addr & 0xFFFFF, width, value); + emu_update_vblk_interrupts(vm); + return; } } +#endif vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); } @@ -118,7 +136,13 @@ static void mem_store(vm_t *vm, uint32_t addr, uint8_t width, uint32_t value) emu_update_vnet_interrupts(vm); return; #endif +#if defined(ENABLE_VIRTIOBLK) + case 0x42: /* virtio-blk */ + virtio_blk_write(vm, &data->vblk, addr & 0xFFFFF, width, value); + emu_update_vblk_interrupts(vm); + return; } +#endif } vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); } @@ -293,7 +317,7 @@ static int semu_start(int argc, char **argv) /* Load at last 1 MiB to prevent kernel / initrd from overwriting it */ uint32_t dtb_addr = RAM_SIZE - 1024 * 1024; /* Device tree */ ram_loc = ((char *) emu.ram) + dtb_addr; - map_file(&ram_loc, (argc == 3) ? argv[2] : "minimal.dtb"); + map_file(&ram_loc, (argc >= 3) ? argv[2] : "minimal.dtb"); /* TODO: load disk image via virtio_blk */ /* Hook for unmapping files */ atexit(unmap_files); @@ -312,6 +336,11 @@ static int semu_start(int argc, char **argv) fprintf(stderr, "No virtio-net functioned\n"); emu.vnet.ram = emu.ram; #endif +#if defined(ENABLE_VIRTIOBLK) + char *disk_file = (argc >= 4) ? argv[3] : NULL; + emu.vblk.ram = emu.ram; + emu.disk = virtio_blk_init(&(emu.vblk), disk_file); +#endif /* Emulate */ uint32_t peripheral_update_ctr = 0; @@ -328,6 +357,11 @@ static int semu_start(int argc, char **argv) if (emu.vnet.InterruptStatus) emu_update_vnet_interrupts(&vm); #endif + +#if defined(ENABLE_VIRTIOBLK) + if (emu.vblk.InterruptStatus) + emu_update_vblk_interrupts(&vm); +#endif } if (vm.insn_count_hi > emu.timer_hi || @@ -361,7 +395,7 @@ static int semu_start(int argc, char **argv) int main(int argc, char **argv) { if (argc < 2) { - printf("Usage: %s []\n", argv[0]); + printf("Usage: %s [] []\n", argv[0]); return 2; } return semu_start(argc, argv); diff --git a/minimal-virtio.dts b/minimal-virtio.dts index b351119..d5331f4 100644 --- a/minimal-virtio.dts +++ b/minimal-virtio.dts @@ -68,5 +68,11 @@ reg = <0x4100000 0x100000>; interrupts = <2>; }; + + blk0: virtio@4200000 { + compatible = "virtio,mmio"; + reg = <0x4200000 0x200>; + interrupts = <3>; + }; }; }; diff --git a/virtio-blk.c b/virtio-blk.c new file mode 100644 index 0000000..44d16e0 --- /dev/null +++ b/virtio-blk.c @@ -0,0 +1,428 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "riscv.h" +#include "riscv_private.h" +#include "virtio.h" + +#define DISK_BLK_SIZE 512 + +#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->QueueSel]) + +struct vblk_req_header { + uint32_t type; + uint32_t reserved; + uint64_t sector; + uint8_t status; +} __attribute__((packed)); + +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->InterruptStatus |= VIRTIO_INT__CONF_CHANGE; +} + +static inline uint32_t vblk_preprocess(virtio_blk_state_t *vblk, uint32_t addr) +{ + if ((addr >= RAM_SIZE) || (addr & 0b11)) + return virtio_blk_set_fail(vblk), 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; + uint32_t capacity = vblk->capacity; + memset(vblk, 0, sizeof(*vblk)); + vblk->ram = ram; + vblk->disk = disk; + vblk->capacity = capacity; +} + +static void virtio_blk_write_handler(virtio_blk_state_t *vblk, + uint64_t sector, + uint32_t desc_addr, + uint32_t len) +{ + char *dest = (char *) ((uintptr_t) vblk->disk + sector * DISK_BLK_SIZE); + char *src = (char *) ((uintptr_t) vblk->ram + desc_addr); + memcpy(dest, src, len); +} + +static void virtio_blk_read_handler(virtio_blk_state_t *vblk, + uint64_t sector, + uint32_t desc_addr, + uint32_t len) +{ + char *dest = (char *) ((uintptr_t) vblk->ram + desc_addr); + char *src = (char *) ((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, + uint32_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 */ + uint32_t *desc = &vblk->ram[queue->QueueDesc + desc_idx * 4]; + + /* Retrieve the fields of current descriptor */ + vq_desc[i].addr = desc[0]; + vq_desc[i].len = desc[2]; + vq_desc[i].flags = desc[3]; + desc_idx = desc[3] >> 16; /* vq_desc[desc_cnt].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 */ + struct vblk_req_header *hdr = + (struct vblk_req_header *) ((uintptr_t) vblk->ram + vq_desc[0].addr); + uint32_t type = hdr->type; + uint64_t sector = hdr->sector; + uint8_t *status = (uint8_t *) ((uintptr_t) vblk->ram + vq_desc[2].addr); + + /* Check sector index is valid */ + if (sector > (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->QueueAvail] >> 16; + if (new_avail - queue->last_avail > (uint16_t) queue->QueueNum) + return (fprintf(stderr, "size check fail\n"), + virtio_blk_set_fail(vblk)); + + if (queue->last_avail == new_avail) + return; + + /* Process them */ + uint16_t new_used = ram[queue->QueueUsed] >> 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->QueueNum; + + /* + * 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->QueueAvail + 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->QueueAvail + 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->QueueUsed + 1 + (new_used % queue->QueueNum) * 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->QueueUsed] &= MASK(16); /* Reset low 16 bits to zero */ + vblk->ram[queue->QueueUsed] |= ((uint32_t) new_used) << 16; /* len */ + + /* Send interrupt, unless VIRTQ_AVAIL_F_NO_INTERRUPT is set */ + if (!(ram[queue->QueueAvail] & 1)) + vblk->InterruptStatus |= VIRTIO_INT__USED_RING; +} + +static bool virtio_blk_reg_read(virtio_blk_state_t *vblk, + uint32_t addr, + uint32_t *value) +{ + /* + * TODO: replace the register address with enum. + * For the address after the configuration space, it should be + * handled by structure pointer depend on the device. + */ + + switch (addr) { + case 0: /* MagicValue (R) */ + *value = 0x74726976; + return true; + case 1: /* Version (R) */ + *value = 2; + return true; + case 2: /* DeviceID (R) */ + *value = 2; + return true; + case 3: /* VendorID (R) */ + *value = VIRTIO_VENDOR_ID; + return true; + case 4: /* DeviceFeatures (R) */ + *value = vblk->DeviceFeaturesSel == 0 + ? VBLK_FEATURES_0 + : (vblk->DeviceFeaturesSel == 1 ? VBLK_FEATURES_1 : 0); + return true; + case 13: /* QueueNumMax (R) */ + *value = VBLK_QUEUE_NUM_MAX; + return true; + case 17: /* QueueReady (RW) */ + *value = VBLK_QUEUE.ready ? 1 : 0; + return true; + case 24: /* InterruptStatus (R) */ + *value = vblk->InterruptStatus; + return true; + case 28: /* Status (RW) */ + *value = vblk->Status; + return true; + case 63: /* ConfigGeneration (R) */ + *value = 0; + return true; + case 64: /* CapacityLow (R) */ + *value = 0x00000000ffffffff & vblk->capacity; + return true; + case 65: /* CapacityHigh (R) */ + *value = vblk->capacity >> 32; + return true; + default: + return false; + } +} + +static bool virtio_blk_reg_write(virtio_blk_state_t *vblk, + uint32_t addr, + uint32_t value) +{ + /* + * TODO: replace the register address with enum. + * For the address after the configuration space, it should be + * handled by structure pointer depend on the device. + */ + + switch (addr) { + case 5: /* DeviceFeaturesSel (W) */ + vblk->DeviceFeaturesSel = value; + return true; + case 8: /* DriverFeatures (W) */ + vblk->DriverFeaturesSel == 0 ? (vblk->DriverFeatures = value) : 0; + return true; + case 9: /* DriverFeaturesSel (W) */ + vblk->DriverFeaturesSel = value; + return true; + case 12: /* QueueSel (W) */ + if (value < ARRAY_SIZE(vblk->queues)) + vblk->QueueSel = value; + else + virtio_blk_set_fail(vblk); + return true; + case 14: /* QueueNum (W) */ + if (value > 0 && value <= VBLK_QUEUE_NUM_MAX) + VBLK_QUEUE.QueueNum = value; + else + virtio_blk_set_fail(vblk); + return true; + case 17: /* QueueReady (RW) */ + VBLK_QUEUE.ready = value & 1; + if (value & 1) + VBLK_QUEUE.last_avail = vblk->ram[VBLK_QUEUE.QueueAvail] >> 16; + return true; + case 32: /* QueueDescLow (W) */ + VBLK_QUEUE.QueueDesc = vblk_preprocess(vblk, value); + return true; + case 33: /* QueueDescHigh (W) */ + if (value) + virtio_blk_set_fail(vblk); + return true; + case 36: /* QueueAvailLow (W) */ + VBLK_QUEUE.QueueAvail = vblk_preprocess(vblk, value); + return true; + case 37: /* QueueAvailHigh (W) */ + if (value) + virtio_blk_set_fail(vblk); + return true; + case 40: /* QueueUsedLow (W) */ + VBLK_QUEUE.QueueUsed = vblk_preprocess(vblk, value); + return true; + case 41: /* QueueUsedHigh (W) */ + if (value) + virtio_blk_set_fail(vblk); + return true; + case 20: /* QueueNotify (W) */ + if (value < ARRAY_SIZE(vblk->queues)) + virtio_queue_notify_handler(vblk, value); + else + virtio_blk_set_fail(vblk); + return true; + case 25: /* InterruptACK (W) */ + vblk->InterruptStatus &= ~value; + return true; + case 28: /* Status (RW) */ + virtio_blk_update_status(vblk, value); + return true; + default: + return false; + } +} + +void virtio_blk_read(vm_t *vm, + virtio_blk_state_t *vblk, + uint32_t addr, + uint8_t width, + uint32_t *value) +{ + switch (width) { + case RV_MEM_LW: + if (!virtio_blk_reg_read(vblk, addr >> 2, value)) + vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); + break; + case RV_MEM_LBU: + case RV_MEM_LB: + case RV_MEM_LHU: + case RV_MEM_LH: + vm_set_exception(vm, RV_EXC_LOAD_MISALIGN, vm->exc_val); + return; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSTR, 0); + return; + } +} + +void virtio_blk_write(vm_t *vm, + virtio_blk_state_t *vblk, + uint32_t addr, + uint8_t width, + uint32_t value) +{ + switch (width) { + case RV_MEM_SW: + if (!virtio_blk_reg_write(vblk, addr >> 2, value)) + vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); + break; + case RV_MEM_SB: + case RV_MEM_SH: + vm_set_exception(vm, RV_EXC_STORE_MISALIGN, vm->exc_val); + return; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSTR, 0); + return; + } +} + +uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, char *disk_file) +{ + /* 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->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(2); + } + + /* Get the disk image size */ + struct stat st; + fstat(disk_fd, &st); + size_t disk_size = st.st_size; + + /* Set up the disk memory */ + uint32_t *disk_mem = + mmap(NULL, 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; + } + assert(!(((uintptr_t) disk_mem) & 0b11)); + + vblk->disk = disk_mem; + vblk->capacity = (disk_size - 1) / DISK_BLK_SIZE + 1; + + return disk_mem; +} diff --git a/virtio-net.c b/virtio-net.c index cfb0a1f..6e677bd 100644 --- a/virtio-net.c +++ b/virtio-net.c @@ -14,20 +14,10 @@ #include "device.h" #include "riscv.h" #include "riscv_private.h" +#include "virtio.h" #define TAP_INTERFACE "tap%d" -#define VIRTIO_VENDOR_ID 0x12345678 - -#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 VNET_FEATURES_0 0 #define VNET_FEATURES_1 1 /* VIRTIO_F_VERSION_1 */ #define VNET_QUEUE_NUM_MAX 1024 diff --git a/virtio.h b/virtio.h new file mode 100644 index 0000000..f7151fa --- /dev/null +++ b/virtio.h @@ -0,0 +1,32 @@ +#pragma once + +#define VIRTIO_VENDOR_ID 0x12345678 + +#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_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 + +struct virtq_desc { + uint32_t addr; + uint32_t len; + uint16_t flags; + uint16_t next; +}; From 1ed1ed8147c75a5f0bf97c666b3205e8ef5b37b4 Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Sun, 23 Jul 2023 20:42:32 +0800 Subject: [PATCH 2/6] linux.config: Enable virtio-blk driver --- configs/linux.config | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configs/linux.config b/configs/linux.config index fc97c66..844ed9c 100644 --- a/configs/linux.config +++ b/configs/linux.config @@ -644,7 +644,7 @@ CONFIG_BLK_DEV=y # CONFIG_BLK_DEV_NBD is not set # CONFIG_BLK_DEV_RAM is not set # CONFIG_ATA_OVER_ETH is not set -# CONFIG_VIRTIO_BLK is not set +CONFIG_VIRTIO_BLK=y # CONFIG_BLK_DEV_RBD is not set # CONFIG_BLK_DEV_UBLK is not set @@ -1594,6 +1594,7 @@ CONFIG_NLATTR=y CONFIG_GENERIC_ATOMIC64=y # CONFIG_IRQ_POLL is not set CONFIG_LIBFDT=y +CONFIG_SG_POOL=y CONFIG_ARCH_STACKWALK=y CONFIG_SBITMAP=y # end of Library routines From a3b7a6a8afc669382ebfc2786ff8d25add8fc4ee Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Sun, 23 Jul 2023 20:49:17 +0800 Subject: [PATCH 3/6] Makefile: Let the check target auto-generate the test disk image --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7d38d2d..382a944 100644 --- a/Makefile +++ b/Makefile @@ -46,9 +46,13 @@ minimal.dtb: $(MINIMAL_DTS) # Rules for downloading prebuilt Linux kernel image include mk/external.mk -check: $(BIN) minimal.dtb $(KERNEL_DATA) +ext4.img: + $(Q)dd if=/dev/zero of=$@ bs=4k count=600 + $(Q)mkfs.ext4 -F $@ + +check: $(BIN) minimal.dtb $(KERNEL_DATA) ext4.img @$(call notice, Ready to launch Linux kernel. Please be patient.) - $(Q)./$(BIN) $(KERNEL_DATA) + $(Q)./$(BIN) $(KERNEL_DATA) minimal.dtb ext4.img build-image: scripts/build-image.sh @@ -59,5 +63,6 @@ clean: distclean: clean $(Q)$(RM) minimal.dtb $(Q)$(RM) Image + $(Q)$(RM) ext4.img -include $(deps) From 0071bd8abdca08627089790f33a3e9583a1a38ad Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Sun, 23 Jul 2023 21:45:27 +0800 Subject: [PATCH 4/6] Use getopt_long() to handle input arguments --- Makefile | 2 +- main.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 382a944..c3c976c 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ ext4.img: check: $(BIN) minimal.dtb $(KERNEL_DATA) ext4.img @$(call notice, Ready to launch Linux kernel. Please be patient.) - $(Q)./$(BIN) $(KERNEL_DATA) minimal.dtb ext4.img + $(Q)./$(BIN) -k $(KERNEL_DATA) -b minimal.dtb -d ext4.img build-image: scripts/build-image.sh diff --git a/main.c b/main.c index 76cc9ef..5e03693 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -288,8 +289,67 @@ static void map_file(char **ram_loc, const char *name) close(fd); } +static void usage(const char *execpath) +{ + fprintf(stderr, "Usage: %s -k linux-image [-b dtb] [-d disk-image]\n", + execpath); +} + +static void handle_options(int argc, + char **argv, + char **kernel_file, + char **dtb_file, + char **disk_file) +{ + *kernel_file = *dtb_file = *disk_file = NULL; + + int optidx = 0; + struct option opts[] = { + {"kernel", 1, NULL, 'k'}, + {"dtb", 1, NULL, 'b'}, + {"disk", 1, NULL, 'd'}, + {"help", 0, NULL, 'h'}, + }; + + int c; + while ((c = getopt_long(argc, argv, "k:b:d:h", opts, &optidx)) != -1) { + switch (c) { + case 'k': + *kernel_file = optarg; + break; + case 'b': + *dtb_file = optarg; + break; + case 'd': + *disk_file = optarg; + break; + case 'h': + usage(argv[0]); + exit(0); + default: + break; + } + } + + if (!*kernel_file) { + fprintf(stderr, + "Linux kernel image file must " + "be provided via -k option.\n"); + usage(argv[0]); + exit(2); + } + + if (!*dtb_file) + *dtb_file = "minimal.dtb"; +} + static int semu_start(int argc, char **argv) { + char *kernel_file; + char *dtb_file; + char *disk_file; + handle_options(argc, argv, &kernel_file, &dtb_file, &disk_file); + /* Initialize the emulator */ emu_state_t emu; memset(&emu, 0, sizeof(emu)); @@ -313,11 +373,11 @@ static int semu_start(int argc, char **argv) char *ram_loc = (char *) emu.ram; /* Load Linux kernel image */ - map_file(&ram_loc, argv[1]); + map_file(&ram_loc, kernel_file); /* Load at last 1 MiB to prevent kernel / initrd from overwriting it */ uint32_t dtb_addr = RAM_SIZE - 1024 * 1024; /* Device tree */ ram_loc = ((char *) emu.ram) + dtb_addr; - map_file(&ram_loc, (argc >= 3) ? argv[2] : "minimal.dtb"); + map_file(&ram_loc, dtb_file); /* TODO: load disk image via virtio_blk */ /* Hook for unmapping files */ atexit(unmap_files); @@ -337,7 +397,6 @@ static int semu_start(int argc, char **argv) emu.vnet.ram = emu.ram; #endif #if defined(ENABLE_VIRTIOBLK) - char *disk_file = (argc >= 4) ? argv[3] : NULL; emu.vblk.ram = emu.ram; emu.disk = virtio_blk_init(&(emu.vblk), disk_file); #endif @@ -394,9 +453,5 @@ static int semu_start(int argc, char **argv) int main(int argc, char **argv) { - if (argc < 2) { - printf("Usage: %s [] []\n", argv[0]); - return 2; - } return semu_start(argc, argv); } From e6c7cac1aacee27aacd2a221728589aa8e1d15bf Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Sun, 23 Jul 2023 22:16:46 +0800 Subject: [PATCH 5/6] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3d1a0ba..30f20ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ *.o *.o.d +*.sw* semu # Linux-specific *.dtb Image +ext4.img From efda0c1ba3c6306c1e81ba6cc646d2c9509affcd Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Sun, 23 Jul 2023 21:56:30 +0800 Subject: [PATCH 6/6] Update README.md --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b0c4c35..2a872cc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A minimalist RISC-V system emulator capable of running Linux the kernel and corr - UART: 8250/16550 - PLIC (platform-level interrupt controller): 32 interrupts, no priority - Standard SBI, with the timer extension -- VirtIO: virtio-net, mapped as TAP interface +- VirtIO: virtio-blk acquires disk image from the host, and virtio-net is mapped as TAP interface ## Prerequisites @@ -62,6 +62,16 @@ Please note that it only supports the Linux host environment. $ make build-image ``` +## Usage + +``` +./semu -k linux-image [-b dtb-file] [-d disk-image] +``` + +* `linux-image` is the path to the Linux kernel `Image`. +* `dtb-file` is optional, as it specifies the user-specified device tree blob. +* `disk-image` is optional, as it specifies the path of a disk image in ext4 file system for the virtio-blk device. + ## License `semu` is released under the MIT License.