Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 08e53fbd authored by Amos Kong's avatar Amos Kong Committed by Rusty Russell
Browse files

virtio-rng: support multiple virtio-rng devices



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: default avatarAmos Kong <akong@redhat.com>
Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent e75279c4
Loading
Loading
Loading
Loading
+63 −39
Original line number Original line Diff line number Diff line
@@ -25,88 +25,108 @@
#include <linux/virtio_rng.h>
#include <linux/virtio_rng.h>
#include <linux/module.h>
#include <linux/module.h>


static struct virtqueue *vq;

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


static void random_recv_done(struct virtqueue *vq)
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. */
	/* 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;
		return;


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


/* The host will fill any buffer we give it with sweet, sweet randomness. */
/* 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;
	struct scatterlist sg;


	sg_init_one(&sg, buf, size);
	sg_init_one(&sg, buf, size);


	/* There should always be room for one buffer. */
	/* 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)
static int virtio_read(struct hwrng *rng, void *buf, size_t size, bool wait)
{
{
	int ret;
	int ret;
	struct virtrng_info *vi = (struct virtrng_info *)rng->priv;


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


	if (!wait)
	if (!wait)
		return 0;
		return 0;


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


	busy = false;
	vi->busy = false;


	return data_avail;
	return vi->data_avail;
}
}


static void virtio_cleanup(struct hwrng *rng)
static void virtio_cleanup(struct hwrng *rng)
{
{
	if (busy)
	struct virtrng_info *vi = (struct virtrng_info *)rng->priv;
		wait_for_completion(&have_data);
}



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


static int probe_common(struct virtio_device *vdev)
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. */
	/* We expect a single virtqueue. */
	vq = virtio_find_single_vq(vdev, random_recv_done, "input");
	vi->vq = virtio_find_single_vq(vdev, random_recv_done, "input");
	if (IS_ERR(vq)) {
	if (IS_ERR(vi->vq)) {
		err = PTR_ERR(vq);
		err = PTR_ERR(vi->vq);
		vq = NULL;
		kfree(vi->hwrng.name);
		vi->vq = NULL;
		kfree(vi);
		vi = NULL;
		return err;
		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) {
	if (err) {
		vdev->config->del_vqs(vdev);
		vdev->config->del_vqs(vdev);
		vq = NULL;
		kfree(vi->hwrng.name);
		vi->vq = NULL;
		kfree(vi);
		vi = NULL;
		return err;
		return err;
	}
	}


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


static void remove_common(struct virtio_device *vdev)
static void remove_common(struct virtio_device *vdev)
{
{
	struct virtrng_info *vi = vdev->priv;
	vdev->config->reset(vdev);
	vdev->config->reset(vdev);
	busy = false;
	vi->busy = false;
	hwrng_unregister(&virtio_hwrng);
	hwrng_unregister(&vi->hwrng);
	vdev->config->del_vqs(vdev);
	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)
static int virtrng_probe(struct virtio_device *vdev)
+1 −1
Original line number Original line Diff line number Diff line
@@ -31,7 +31,7 @@
 * @priv:		Private data, for use by the RNG driver.
 * @priv:		Private data, for use by the RNG driver.
 */
 */
struct hwrng {
struct hwrng {
	const char *name;
	char *name;
	int (*init)(struct hwrng *rng);
	int (*init)(struct hwrng *rng);
	void (*cleanup)(struct hwrng *rng);
	void (*cleanup)(struct hwrng *rng);
	int (*data_present)(struct hwrng *rng, int wait);
	int (*data_present)(struct hwrng *rng, int wait);