Skip to content

Commit

Permalink
virtio-rng: support multiple virtio-rng devices
Browse files Browse the repository at this point in the history
Current hwrng core supports to register multiple hwrng devices,
and there is only one device really works in the same time.
QEMU alsu supports to have multiple virtio-rng backends.

This patch changes virtio-rng driver to support multiple
virtio-rng devices.

]# cat /sys/class/misc/hw_random/rng_available
virtio_rng.0 virtio_rng.1
]# cat /sys/class/misc/hw_random/rng_current
virtio_rng.0
]# echo -n virtio_rng.1 > /sys/class/misc/hw_random/rng_current
]# dd if=/dev/hwrng of=/dev/null

Signed-off-by: Amos Kong <akong@redhat.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
  • Loading branch information
Amos Kong authored and Rusty Russell committed May 14, 2014
1 parent e75279c commit 08e53fb
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 40 deletions.
102 changes: 63 additions & 39 deletions drivers/char/hw_random/virtio-rng.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,88 +25,108 @@
#include <linux/virtio_rng.h>
#include <linux/module.h>

static struct virtqueue *vq;
static unsigned int data_avail;
static DECLARE_COMPLETION(have_data);
static bool busy;

struct virtrng_info {
struct virtio_device *vdev;
struct hwrng hwrng;
struct virtqueue *vq;
unsigned int data_avail;
struct completion have_data;
bool busy;
};

static void random_recv_done(struct virtqueue *vq)
{
struct virtrng_info *vi = vq->vdev->priv;

/* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */
if (!virtqueue_get_buf(vq, &data_avail))
if (!virtqueue_get_buf(vi->vq, &vi->data_avail))
return;

complete(&have_data);
complete(&vi->have_data);
}

/* The host will fill any buffer we give it with sweet, sweet randomness. */
static void register_buffer(u8 *buf, size_t size)
static void register_buffer(struct virtrng_info *vi, u8 *buf, size_t size)
{
struct scatterlist sg;

sg_init_one(&sg, buf, size);

/* There should always be room for one buffer. */
virtqueue_add_inbuf(vq, &sg, 1, buf, GFP_KERNEL);
virtqueue_add_inbuf(vi->vq, &sg, 1, buf, GFP_KERNEL);

virtqueue_kick(vq);
virtqueue_kick(vi->vq);
}

static int virtio_read(struct hwrng *rng, void *buf, size_t size, bool wait)
{
int ret;
struct virtrng_info *vi = (struct virtrng_info *)rng->priv;

if (!busy) {
busy = true;
init_completion(&have_data);
register_buffer(buf, size);
if (!vi->busy) {
vi->busy = true;
init_completion(&vi->have_data);
register_buffer(vi, buf, size);
}

if (!wait)
return 0;

ret = wait_for_completion_killable(&have_data);
ret = wait_for_completion_killable(&vi->have_data);
if (ret < 0)
return ret;

busy = false;
vi->busy = false;

return data_avail;
return vi->data_avail;
}

static void virtio_cleanup(struct hwrng *rng)
{
if (busy)
wait_for_completion(&have_data);
}

struct virtrng_info *vi = (struct virtrng_info *)rng->priv;

static struct hwrng virtio_hwrng = {
.name = "virtio",
.cleanup = virtio_cleanup,
.read = virtio_read,
};
if (vi->busy)
wait_for_completion(&vi->have_data);
}

static int probe_common(struct virtio_device *vdev)
{
int err;
int err, i;
struct virtrng_info *vi = NULL;

vi = kmalloc(sizeof(struct virtrng_info), GFP_KERNEL);
vi->hwrng.name = kmalloc(40, GFP_KERNEL);
init_completion(&vi->have_data);

vi->hwrng.read = virtio_read;
vi->hwrng.cleanup = virtio_cleanup;
vi->hwrng.priv = (unsigned long)vi;
vdev->priv = vi;

if (vq) {
/* We only support one device for now */
return -EBUSY;
}
/* We expect a single virtqueue. */
vq = virtio_find_single_vq(vdev, random_recv_done, "input");
if (IS_ERR(vq)) {
err = PTR_ERR(vq);
vq = NULL;
vi->vq = virtio_find_single_vq(vdev, random_recv_done, "input");
if (IS_ERR(vi->vq)) {
err = PTR_ERR(vi->vq);
kfree(vi->hwrng.name);
vi->vq = NULL;
kfree(vi);
vi = NULL;
return err;
}

err = hwrng_register(&virtio_hwrng);
i = 0;
do {
sprintf(vi->hwrng.name, "virtio_rng.%d", i++);
err = hwrng_register(&vi->hwrng);
} while (err == -EEXIST);

if (err) {
vdev->config->del_vqs(vdev);
vq = NULL;
kfree(vi->hwrng.name);
vi->vq = NULL;
kfree(vi);
vi = NULL;
return err;
}

Expand All @@ -115,11 +135,15 @@ static int probe_common(struct virtio_device *vdev)

static void remove_common(struct virtio_device *vdev)
{
struct virtrng_info *vi = vdev->priv;
vdev->config->reset(vdev);
busy = false;
hwrng_unregister(&virtio_hwrng);
vi->busy = false;
hwrng_unregister(&vi->hwrng);
vdev->config->del_vqs(vdev);
vq = NULL;
kfree(vi->hwrng.name);
vi->vq = NULL;
kfree(vi);
vi = NULL;
}

static int virtrng_probe(struct virtio_device *vdev)
Expand Down
2 changes: 1 addition & 1 deletion include/linux/hw_random.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* @priv: Private data, for use by the RNG driver.
*/
struct hwrng {
const char *name;
char *name;
int (*init)(struct hwrng *rng);
void (*cleanup)(struct hwrng *rng);
int (*data_present)(struct hwrng *rng, int wait);
Expand Down

0 comments on commit 08e53fb

Please sign in to comment.