The Linux kernel already has a virtio-rng driver, this is the device implementation.
When Linux needs more entropy, it puts a buffer in the vq. We then put entropy into that buffer, and push it back to the guest. Feeding randomness from host's /dev/urandom into the guest is sufficient, so this is a simple implementation that opens /dev/urandom and reads from it whenever required. Invocation is simple: qemu ... -device virtio-rng-pci In the guest, we see $ cat /sys/devices/virtual/misc/hw_random/rng_available virtio $ cat /sys/devices/virtual/misc/hw_random/rng_current virtio There are ways to extend the device to be more generic and collect entropy from other sources, but this is simple enough and works for now. Signed-off-by: Amit Shah <amit.s...@redhat.com> --- Makefile.objs | 1 + hw/pci.h | 1 + hw/virtio-pci.c | 50 +++++++++++++++++++++ hw/virtio-rng.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/virtio-rng.h | 18 ++++++++ hw/virtio.h | 2 + 6 files changed, 202 insertions(+), 0 deletions(-) create mode 100644 hw/virtio-rng.c create mode 100644 hw/virtio-rng.h diff --git a/Makefile.objs b/Makefile.objs index 70c5c79..5850762 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -210,6 +210,7 @@ user-obj-y += $(qom-obj-twice-y) hw-obj-y = hw-obj-y += vl.o loader.o hw-obj-$(CONFIG_VIRTIO) += virtio-console.o +hw-obj-$(CONFIG_VIRTIO) += virtio-rng.o hw-obj-y += usb/libhw.o hw-obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o hw-obj-y += fw_cfg.o diff --git a/hw/pci.h b/hw/pci.h index 8d0aa49..0a22f91 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -76,6 +76,7 @@ #define PCI_DEVICE_ID_VIRTIO_BALLOON 0x1002 #define PCI_DEVICE_ID_VIRTIO_CONSOLE 0x1003 #define PCI_DEVICE_ID_VIRTIO_SCSI 0x1004 +#define PCI_DEVICE_ID_VIRTIO_RNG 0x1005 #define FMT_PCIBUS PRIx64 diff --git a/hw/virtio-pci.c b/hw/virtio-pci.c index 4a4413d..7f2d630 100644 --- a/hw/virtio-pci.c +++ b/hw/virtio-pci.c @@ -812,6 +812,28 @@ static int virtio_balloon_exit_pci(PCIDevice *pci_dev) return virtio_exit_pci(pci_dev); } +static int virtio_rng_init_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + VirtIODevice *vdev; + + vdev = virtio_rng_init(&pci_dev->qdev); + if (!vdev) { + return -1; + } + virtio_init_pci(proxy, vdev); + return 0; +} + +static int virtio_rng_exit_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + + virtio_pci_stop_ioeventfd(proxy); + virtio_rng_exit(proxy->vdev); + return virtio_exit_pci(pci_dev); +} + static Property virtio_blk_properties[] = { DEFINE_PROP_HEX32("class", VirtIOPCIProxy, class_code, 0), DEFINE_BLOCK_PROPERTIES(VirtIOPCIProxy, block), @@ -937,6 +959,33 @@ static TypeInfo virtio_balloon_info = { .class_init = virtio_balloon_class_init, }; +static Property virtio_rng_properties[] = { + DEFINE_VIRTIO_COMMON_FEATURES(VirtIOPCIProxy, host_features), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_rng_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = virtio_rng_init_pci; + k->exit = virtio_rng_exit_pci; + k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; + k->device_id = PCI_DEVICE_ID_VIRTIO_RNG; + k->revision = VIRTIO_PCI_ABI_VERSION; + k->class_id = PCI_CLASS_OTHERS; + dc->reset = virtio_pci_reset; + dc->props = virtio_rng_properties; +} + +static TypeInfo virtio_rng_info = { + .name = "virtio-rng-pci", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(VirtIOPCIProxy), + .class_init = virtio_rng_class_init, +}; + static int virtio_scsi_init_pci(PCIDevice *pci_dev) { VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); @@ -998,6 +1047,7 @@ static void virtio_pci_register_types(void) type_register_static(&virtio_serial_info); type_register_static(&virtio_balloon_info); type_register_static(&virtio_scsi_info); + type_register_static(&virtio_rng_info); } type_init(virtio_pci_register_types) diff --git a/hw/virtio-rng.c b/hw/virtio-rng.c new file mode 100644 index 0000000..e1f3d1c --- /dev/null +++ b/hw/virtio-rng.c @@ -0,0 +1,130 @@ +/* A virtio device for feeding entropy into a guest. + * + * Copyright 2012 Red Hat, Inc. + * Copyright 2012 Amit Shah <amit.s...@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "iov.h" +#include "qdev.h" +#include "virtio.h" +#include "virtio-rng.h" + +typedef struct VirtIORNG { + VirtIODevice vdev; + + DeviceState *qdev; + + /* Only one vq - guest puts a buffer on it when it needs entropy */ + VirtQueue *vq; + + int input_fd; +} VirtIORNG; + +static void handle_input(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIORNG *vrng = DO_UPCAST(VirtIORNG, vdev, vdev); + VirtQueueElement elem; + char *buf; + ssize_t size, offset, ret; + + if (!virtqueue_pop(vq, &elem)) { + return; + } + size = iov_size(elem.in_sg, elem.in_num); + + buf = g_malloc(size); + do { + ret = read(vrng->input_fd, buf, size); + } while (ret == -1 && errno == EINTR); + if (ret < 0) { + /* We can't get randomness -- give up for now. */ + virtqueue_push(vq, &elem, 0); + goto skip; + } + + offset = 0; + size = ret; + while (offset < size) { + size_t len; + + /* We've already popped the first elem */ + if (offset && !virtqueue_pop(vq, &elem)) { + break; + } + + len = iov_from_buf(elem.in_sg, elem.in_num, + buf + offset, 0, size - offset); + offset += len; + + virtqueue_push(vq, &elem, len); + } +skip: + g_free(buf); + virtio_notify(vdev, vq); +} + +static uint32_t get_features(VirtIODevice *vdev, uint32_t f) +{ + return f; +} + +static void virtio_rng_save(QEMUFile *f, void *opaque) +{ + VirtIORNG *vrng = opaque; + + virtio_save(&vrng->vdev, f); +} + +static int virtio_rng_load(QEMUFile *f, void *opaque, int version_id) +{ + VirtIORNG *vrng = opaque; + + if (version_id != 1) { + return -EINVAL; + } + virtio_load(&vrng->vdev, f); + + return 0; +} + +VirtIODevice *virtio_rng_init(DeviceState *dev) +{ + VirtIORNG *vrng; + VirtIODevice *vdev; + int input_fd; + + input_fd = open("/dev/urandom", O_RDONLY); + if (input_fd < 0) { + error_report("error %d opening /dev/urandom", errno); + return NULL; + } + + vdev = virtio_common_init("virtio-rng", VIRTIO_ID_RNG, 0, + sizeof(VirtIORNG)); + + vrng = DO_UPCAST(VirtIORNG, vdev, vdev); + + vrng->input_fd = input_fd; + + vrng->vq = virtio_add_queue(vdev, 8, handle_input); + vrng->vdev.get_features = get_features; + + vrng->qdev = dev; + register_savevm(dev, "virtio-rng", -1, 1, virtio_rng_save, + virtio_rng_load, vrng); + + return vdev; +} + +void virtio_rng_exit(VirtIODevice *vdev) +{ + VirtIORNG *vrng = DO_UPCAST(VirtIORNG, vdev, vdev); + + close(vrng->input_fd); + unregister_savevm(vrng->qdev, "virtio-rng", vrng); + virtio_cleanup(vdev); +} diff --git a/hw/virtio-rng.h b/hw/virtio-rng.h new file mode 100644 index 0000000..2e1eba3 --- /dev/null +++ b/hw/virtio-rng.h @@ -0,0 +1,18 @@ +/* + * Virtio RNG Support + * + * Copyright Red Hat, Inc. 2012 + * Copyright Amit Shah <amit.s...@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#ifndef _QEMU_VIRTIO_RNG_H +#define _QEMU_VIRTIO_RNG_H + +/* The Virtio ID for the virtio rng device */ +#define VIRTIO_ID_RNG 4 + +#endif diff --git a/hw/virtio.h b/hw/virtio.h index 0aef7d1..0315e0c 100644 --- a/hw/virtio.h +++ b/hw/virtio.h @@ -201,6 +201,7 @@ VirtIODevice *virtio_serial_init(DeviceState *dev, virtio_serial_conf *serial); VirtIODevice *virtio_balloon_init(DeviceState *dev); typedef struct VirtIOSCSIConf VirtIOSCSIConf; VirtIODevice *virtio_scsi_init(DeviceState *dev, VirtIOSCSIConf *conf); +VirtIODevice *virtio_rng_init(DeviceState *dev); #ifdef CONFIG_LINUX VirtIODevice *virtio_9p_init(DeviceState *dev, V9fsConf *conf); #endif @@ -211,6 +212,7 @@ void virtio_blk_exit(VirtIODevice *vdev); void virtio_serial_exit(VirtIODevice *vdev); void virtio_balloon_exit(VirtIODevice *vdev); void virtio_scsi_exit(VirtIODevice *vdev); +void virtio_rng_exit(VirtIODevice *vdev); #define DEFINE_VIRTIO_COMMON_FEATURES(_state, _field) \ DEFINE_PROP_BIT("indirect_desc", _state, _field, \ -- 1.7.7.6