From e25b10826cc7a4b885bfbb81cc628dfddbea364f Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Sun, 26 Jan 2025 16:35:54 +0800 Subject: [PATCH 1/2] linux.config: Enable hardware random number generator with VirtIO --- configs/linux.config | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configs/linux.config b/configs/linux.config index 99078d48..55cd1a1d 100644 --- a/configs/linux.config +++ b/configs/linux.config @@ -831,7 +831,8 @@ CONFIG_SERIAL_CORE_CONSOLE=y # CONFIG_TTY_PRINTK is not set # CONFIG_VIRTIO_CONSOLE is not set # CONFIG_IPMI_HANDLER is not set -# CONFIG_HW_RANDOM is not set +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_VIRTIO=y CONFIG_DEVMEM=y # CONFIG_TCG_TPM is not set # CONFIG_XILLYBUS is not set From 9b3b4aa090a969a217416c9de5cc7af6c0c1c678 Mon Sep 17 00:00:00 2001 From: Shengwen Cheng Date: Sun, 26 Jan 2025 16:33:03 +0800 Subject: [PATCH 2/2] Implement virtio-rng device This commit introduces the VirtIO entropy device (also know as virtio-rng in QEMU and the Linux kernel) to resolve the blocking issue of arc4random_buf() [1] caused by insufficient entropy of /dev/random. According to the man page (`man 7 random`): The kernel random-number generator relies on entropy gathered from device drivers and other sources of environmental noise to seed a cryptographically secure pseudorandom number generator (CSPRNG). Interface Pool: /dev/random Pool: Blocking pool Blocking behavior: If entropy too low, blocks until there is enough entropy Behavior when pool is not yet ready: Blocks until enough entropy gathered Quaoted from https://en.wikipedia.org/wiki//dev/random With Linux kernel 3.16 and newer, the kernel itself mixes data from hardware random number generators into /dev/random on a sliding scale based on the definable entropy estimation quality of the HWRNG. This means that no userspace daemon, such as rngd from rng-tools, is needed to do that job. With Linux kernel 3.17+, the VirtIO RNG was modified to have a default quality defined above 0, and as such, is currently the only HWRNG mixed into /dev/random by default. [1] https://elixir.bootlin.com/glibc/glibc-2.36/source/stdlib/arc4random.c Close #68. --- 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 24980d4c..83cd0c52 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,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 4610f45b..23f08dda 100644 --- a/device.h +++ b/device.h @@ -172,6 +172,52 @@ void virtio_blk_write(hart_t *vm, uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, char *disk_file); #endif /* SEMU_HAS(VIRTIOBLK) */ +/* VirtIO-RNG */ + +#if SEMU_HAS(VIRTIORNG) + +#define IRQ_VRNG 4 +#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(void); +#endif /* SEMU_HAS(VIRTIORNG) */ + /* ACLINT MTIMER */ typedef struct { /* A MTIMER device has two separate base addresses: one for the MTIME @@ -272,6 +318,9 @@ typedef struct { #endif #if SEMU_HAS(VIRTIOBLK) virtio_blk_state_t vblk; +#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 c6e81cb9..7ceb1d57 100644 --- a/main.c +++ b/main.c @@ -72,6 +72,18 @@ static void emu_update_vblk_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); @@ -137,6 +149,12 @@ static void mem_load(hart_t *hart, aclint_sswi_read(hart, &data->sswi, addr & 0xFFFFF, width, value); aclint_sswi_update_interrupts(hart, &data->sswi); return; +#if SEMU_HAS(VIRTIORNG) + case 0x46: /* virtio-rng */ + virtio_rng_read(hart, &data->vrng, addr & 0xFFFFF, width, value); + emu_update_vrng_interrupts(hart->vm); + return; +#endif } } vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val); @@ -191,6 +209,12 @@ static void mem_store(hart_t *hart, aclint_sswi_write(hart, &data->sswi, addr & 0xFFFFF, width, value); aclint_sswi_update_interrupts(hart, &data->sswi); return; +#if SEMU_HAS(VIRTIORNG) + case 0x46: /* virtio-rng */ + virtio_rng_write(hart, &data->vrng, addr & 0xFFFFF, width, value); + emu_update_vrng_interrupts(hart->vm); + return; +#endif } } vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val); @@ -617,6 +641,10 @@ static int semu_start(int argc, char **argv) #if SEMU_HAS(VIRTIOBLK) emu.vblk.ram = emu.ram; emu.disk = virtio_blk_init(&(emu.vblk), disk_file); +#endif +#if SEMU_HAS(VIRTIORNG) + emu.vrng.ram = emu.ram; + virtio_rng_init(); #endif /* Set up ACLINT */ semu_timer_init(&emu.mtimer.mtime, CLOCK_FREQ); diff --git a/minimal.dts b/minimal.dts index d83bcfca..3f8ca820 100644 --- a/minimal.dts +++ b/minimal.dts @@ -63,5 +63,13 @@ interrupts = <3>; }; #endif + +#if SEMU_FEATURE_VIRTIORNG + rng0: virtio@4600000 { + compatible = "virtio,mmio"; + reg = <0x4600000 0x200>; + interrupts = <4>; + }; +#endif }; }; diff --git a/virtio-rng.c b/virtio-rng.c new file mode 100644 index 00000000..21a62ae3 --- /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(void) +{ + rng_fd = open("/dev/random", O_RDONLY); + if (rng_fd < 0) { + fprintf(stderr, "Could not open /dev/random\n"); + exit(2); + } +}