From 261dcdcbadb974464ff7ac823668c6959f1e10ad Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Sun, 26 Jan 2025 16:33:03 +0800 Subject: [PATCH] [Workaround] Implement virtio-rng device --- Makefile | 7 ++ device.h | 49 ++++++++++ main.c | 28 ++++++ minimal.dts | 8 ++ virtio-rng.c | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 360 insertions(+) create mode 100644 virtio-rng.c diff --git a/Makefile b/Makefile index 161d4af..a3fd89e 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,13 @@ ifeq ($(call has, VIRTIOBLK), 1) endif endif +# virtio-rng +ENABLE_VIRTIORNG ?= 1 +$(call set-feature, VIRTIORNG) +ifeq ($(call has, VIRTIORNG), 1) + OBJS_EXTRA += virtio-rng.o +endif + NETDEV ?= tap # virtio-net ENABLE_VIRTIONET ?= 1 diff --git a/device.h b/device.h index 8c21cff..bd9aa49 100644 --- a/device.h +++ b/device.h @@ -286,6 +286,52 @@ void virtio_input_update_mouse_button_state(uint32_t button, bool pressed); void virtio_input_update_cursor(uint32_t x, uint32_t y); #endif /* SEMU_HAS(VIRTIOINPUT) */ +/* VirtIO-RNG */ + +#if SEMU_HAS(VIRTIORNG) + +#define IRQ_VRNG 7 +#define IRQ_VRNG_BIT (1 << IRQ_VRNG) + +typedef struct { + uint32_t QueueNum; + uint32_t QueueDesc; + uint32_t QueueAvail; + uint32_t QueueUsed; + uint16_t last_avail; + bool ready; +} virtio_rng_queue_t; + +typedef struct { + /* feature negotiation */ + uint32_t DeviceFeaturesSel; + uint32_t DriverFeatures; + uint32_t DriverFeaturesSel; + /* queue config */ + uint32_t QueueSel; + virtio_rng_queue_t queues[1]; + /* status */ + uint32_t Status; + uint32_t InterruptStatus; + /* supplied by environment */ + uint32_t *ram; +} virtio_rng_state_t; + +void virtio_rng_read(hart_t *vm, + virtio_rng_state_t *rng, + uint32_t addr, + uint8_t width, + uint32_t *value); + +void virtio_rng_write(hart_t *vm, + virtio_rng_state_t *vrng, + uint32_t addr, + uint8_t width, + uint32_t value); + +void virtio_rng_init(virtio_rng_state_t *vrng); +#endif /* SEMU_HAS(VIRTIORNG) */ + /* ACLINT MTIMER */ typedef struct { /* A MTIMER device has two separate base addresses: one for the MTIME @@ -393,6 +439,9 @@ typedef struct { #if SEMU_HAS(VIRTIOINPUT) virtio_input_state_t vkeyboard; virtio_input_state_t vmouse; +#endif +#if SEMU_HAS(VIRTIORNG) + virtio_rng_state_t vrng; #endif /* ACLINT */ mtimer_state_t mtimer; diff --git a/main.c b/main.c index 6426618..3e96f72 100644 --- a/main.c +++ b/main.c @@ -107,6 +107,18 @@ static void emu_update_vinput_mouse_interrupts(vm_t *vm) } #endif +#if SEMU_HAS(VIRTIORNG) +static void emu_update_vrng_interrupts(vm_t *vm) +{ + emu_state_t *data = PRIV(vm->hart[0]); + if (data->vrng.InterruptStatus) + data->plic.active |= IRQ_VRNG_BIT; + else + data->plic.active &= ~IRQ_VRNG_BIT; + plic_update_interrupts(vm, &data->plic); +} +#endif + static void emu_update_timer_interrupt(hart_t *hart) { emu_state_t *data = PRIV(hart); @@ -189,6 +201,12 @@ static void mem_load(hart_t *hart, value); emu_update_vinput_mouse_interrupts(hart->vm); return; +#endif +#if SEMU_HAS(VIRTIORNG) + case 0x49: /* virtio-rng */ + virtio_rng_read(hart, &data->vrng, addr & 0xFFFFF, width, value); + emu_update_vrng_interrupts(hart->vm); + return; #endif } } @@ -261,6 +279,12 @@ static void mem_store(hart_t *hart, value); emu_update_vinput_mouse_interrupts(hart->vm); return; +#endif +#if SEMU_HAS(VIRTIORNG) + case 0x49: /* virtio-rng */ + virtio_rng_write(hart, &data->vrng, addr & 0xFFFFF, width, value); + emu_update_vrng_interrupts(hart->vm); + return; #endif } } @@ -709,6 +733,10 @@ static int semu_start(int argc, char **argv) emu.vmouse.ram = emu.ram; virtio_input_init(&(emu.vmouse)); #endif +#if SEMU_HAS(VIRTIORNG) + emu.vrng.ram = emu.ram; + virtio_rng_init(&(emu.vrng)); +#endif /* Emulate */ uint32_t peripheral_update_ctr = 0; diff --git a/minimal.dts b/minimal.dts index 570e608..6f30dc5 100644 --- a/minimal.dts +++ b/minimal.dts @@ -86,5 +86,13 @@ interrupts = <6>; }; #endif + +#if SEMU_FEATURE_VIRTIORNG + rng0: virtio@4900000 { + compatible = "virtio,mmio"; + reg = <0x4900000 0x200>; + interrupts = <7>; + }; +#endif }; }; diff --git a/virtio-rng.c b/virtio-rng.c new file mode 100644 index 0000000..109ddad --- /dev/null +++ b/virtio-rng.c @@ -0,0 +1,268 @@ +#include +#include +#include +#include +#include + +#include "common.h" +#include "device.h" +#include "riscv.h" +#include "riscv_private.h" +#include "virtio.h" + +#define VIRTIO_F_VERSION_1 1 + +#define VRNG_FEATURES_0 0 +#define VRNG_FEATURES_1 1 /* VIRTIO_F_VERSION_1 */ + +#define VRNG_QUEUE_NUM_MAX 1024 +#define VRNG_QUEUE (vrng->queues[vrng->QueueSel]) + +static int rng_fd = -1; + +static void virtio_rng_set_fail(virtio_rng_state_t *vrng) +{ + vrng->Status |= VIRTIO_STATUS__DEVICE_NEEDS_RESET; + if (vrng->Status & VIRTIO_STATUS__DRIVER_OK) + vrng->InterruptStatus |= VIRTIO_INT__CONF_CHANGE; +} + +static inline uint32_t vrng_preprocess(virtio_rng_state_t *vrng, uint32_t addr) +{ + if ((addr >= RAM_SIZE) || (addr & 0b11)) + return virtio_rng_set_fail(vrng), 0; + + return addr >> 2; +} + +static void virtio_rng_update_status(virtio_rng_state_t *vrng, uint32_t status) +{ + vrng->Status |= status; + if (status) + return; + + /* Reset */ + uint32_t *ram = vrng->ram; + memset(vrng, 0, sizeof(*vrng)); + vrng->ram = ram; +} + +static void virtio_queue_notify_handler(virtio_rng_state_t *vrng, + virtio_rng_queue_t *queue) +{ + uint32_t *ram = vrng->ram; + + /* Calculate available ring index */ + uint16_t queue_idx = queue->last_avail % queue->QueueNum; + uint16_t buffer_idx = + ram[queue->QueueAvail + 1 + queue_idx / 2] >> (16 * (queue_idx % 2)); + + /* Update available ring pointer */ + VRNG_QUEUE.last_avail++; + + /* Read descriptor */ + uint32_t *desc = &vrng->ram[queue->QueueDesc + buffer_idx * 4]; + struct virtq_desc vq_desc = { + .addr = desc[0], + .len = desc[2], + .flags = desc[3], + }; + + /* Write entropy buffer */ + void *entropy_buf = + (void *) ((uintptr_t) vrng->ram + (uintptr_t) vq_desc.addr); + ssize_t total = read(rng_fd, entropy_buf, vq_desc.len); + + /* Clear write flag */ + desc[3] = 0; + + /* Get virtq_used.idx (le16) */ + uint16_t used = ram[queue->QueueUsed] >> 16; + + /* Update used ring information */ + uint32_t vq_used_addr = + VRNG_QUEUE.QueueUsed + 1 + (used % queue->QueueNum) * 2; + ram[vq_used_addr] = buffer_idx; + ram[vq_used_addr + 1] = total; + used++; + + /* Reset used ring flag to zero (virtq_used.flags) */ + vrng->ram[VRNG_QUEUE.QueueUsed] &= MASK(16); + + /* Update the used ring pointer (virtq_used.idx) */ + vrng->ram[VRNG_QUEUE.QueueUsed] |= ((uint32_t) used) << 16; + + /* Send interrupt, unless VIRTQ_AVAIL_F_NO_INTERRUPT is set */ + if (!(ram[VRNG_QUEUE.QueueAvail] & 1)) + vrng->InterruptStatus |= VIRTIO_INT__USED_RING; +} + +static bool virtio_rng_reg_read(virtio_rng_state_t *vrng, + uint32_t addr, + uint32_t *value) +{ +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(MagicValue): + *value = 0x74726976; + return true; + case _(Version): + *value = 2; + return true; + case _(DeviceID): + *value = 4; + return true; + case _(VendorID): + *value = VIRTIO_VENDOR_ID; + return true; + case _(DeviceFeatures): + *value = vrng->DeviceFeaturesSel == 0 + ? VRNG_FEATURES_0 + : (vrng->DeviceFeaturesSel == 1 ? VRNG_FEATURES_1 : 0); + return true; + case _(QueueNumMax): + *value = VRNG_QUEUE_NUM_MAX; + return true; + case _(QueueReady): + *value = VRNG_QUEUE.ready ? 1 : 0; + return true; + case _(InterruptStatus): + *value = vrng->InterruptStatus; + return true; + case _(Status): + *value = vrng->Status; + return true; + case _(ConfigGeneration): + *value = 0; + return true; + default: + /* No other readable registers */ + return false; + } +#undef _ +} + +static bool virtio_rng_reg_write(virtio_rng_state_t *vrng, + uint32_t addr, + uint32_t value) +{ +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(DeviceFeaturesSel): + vrng->DeviceFeaturesSel = value; + return true; + case _(DriverFeatures): + vrng->DriverFeaturesSel == 0 ? (vrng->DriverFeatures = value) : 0; + return true; + case _(DriverFeaturesSel): + vrng->DriverFeaturesSel = value; + return true; + case _(QueueSel): + if (value < ARRAY_SIZE(vrng->queues)) + vrng->QueueSel = value; + else + virtio_rng_set_fail(vrng); + return true; + case _(QueueNum): + if (value > 0 && value <= VRNG_QUEUE_NUM_MAX) + VRNG_QUEUE.QueueNum = value; + else + virtio_rng_set_fail(vrng); + return true; + case _(QueueReady): + VRNG_QUEUE.ready = value & 1; + if (value & 1) + VRNG_QUEUE.last_avail = vrng->ram[VRNG_QUEUE.QueueAvail] >> 16; + return true; + case _(QueueDescLow): + VRNG_QUEUE.QueueDesc = vrng_preprocess(vrng, value); + return true; + case _(QueueDescHigh): + if (value) + virtio_rng_set_fail(vrng); + return true; + case _(QueueDriverLow): + VRNG_QUEUE.QueueAvail = vrng_preprocess(vrng, value); + return true; + case _(QueueDriverHigh): + if (value) + virtio_rng_set_fail(vrng); + return true; + case _(QueueDeviceLow): + VRNG_QUEUE.QueueUsed = vrng_preprocess(vrng, value); + return true; + case _(QueueDeviceHigh): + if (value) + virtio_rng_set_fail(vrng); + return true; + case _(QueueNotify): + if (value < ARRAY_SIZE(vrng->queues)) + virtio_queue_notify_handler(vrng, &VRNG_QUEUE); + else + virtio_rng_set_fail(vrng); + return true; + case _(InterruptACK): + vrng->InterruptStatus &= ~value; + return true; + case _(Status): + virtio_rng_update_status(vrng, value); + return true; + default: + /* No other writable registers */ + return false; + } +#undef _ +} + +void virtio_rng_read(hart_t *vm, + virtio_rng_state_t *vrng, + uint32_t addr, + uint8_t width, + uint32_t *value) +{ + switch (width) { + case RV_MEM_LW: + if (!virtio_rng_reg_read(vrng, 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_INSN, 0); + return; + } +} + +void virtio_rng_write(hart_t *vm, + virtio_rng_state_t *vrng, + uint32_t addr, + uint8_t width, + uint32_t value) +{ + switch (width) { + case RV_MEM_SW: + if (!virtio_rng_reg_write(vrng, 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_INSN, 0); + return; + } +} + +void virtio_rng_init(virtio_rng_state_t *vrng) +{ + rng_fd = open("/dev/urandom", O_RDONLY); + if (rng_fd < 0) { + fprintf(stderr, "Could not open /dev/urandom\n"); + exit(2); + } +}