diff options
Diffstat (limited to 'drivers/char/hw_random/virtio-rng.c')
| -rw-r--r-- | drivers/char/hw_random/virtio-rng.c | 206 |
1 files changed, 129 insertions, 77 deletions
diff --git a/drivers/char/hw_random/virtio-rng.c b/drivers/char/hw_random/virtio-rng.c index d0e563e4fc3..e9b15bc18b4 100644 --- a/drivers/char/hw_random/virtio-rng.c +++ b/drivers/char/hw_random/virtio-rng.c @@ -16,140 +16,192 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + #include <linux/err.h> #include <linux/hw_random.h> #include <linux/scatterlist.h> #include <linux/spinlock.h> #include <linux/virtio.h> #include <linux/virtio_rng.h> +#include <linux/module.h> + +static DEFINE_IDA(rng_index_ida); + +struct virtrng_info { + struct virtio_device *vdev; + struct hwrng hwrng; + struct virtqueue *vq; + unsigned int data_avail; + struct completion have_data; + bool busy; + char name[25]; + int index; +}; -/* The host will fill any buffer we give it with sweet, sweet randomness. We - * give it 64 bytes at a time, and the hwrng framework takes it 4 bytes at a - * time. */ -#define RANDOM_DATA_SIZE 64 - -static struct virtqueue *vq; -static u32 *random_data; -static unsigned int data_left; -static DECLARE_COMPLETION(have_data); +static bool probe_done; static void random_recv_done(struct virtqueue *vq) { - int len; + struct virtrng_info *vi = vq->vdev->priv; - /* We never get spurious callbacks. */ - if (!vq->vq_ops->get_buf(vq, &len)) - BUG(); + /* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */ + if (!virtqueue_get_buf(vi->vq, &vi->data_avail)) + return; - data_left = len / sizeof(random_data[0]); - complete(&have_data); + complete(&vi->have_data); } -static void register_buffer(void) +/* The host will fill any buffer we give it with sweet, sweet randomness. */ +static void register_buffer(struct virtrng_info *vi, u8 *buf, size_t size) { struct scatterlist sg; - sg_init_one(&sg, random_data, RANDOM_DATA_SIZE); + sg_init_one(&sg, buf, size); + /* There should always be room for one buffer. */ - if (vq->vq_ops->add_buf(vq, &sg, 0, 1, random_data) != 0) - BUG(); - vq->vq_ops->kick(vq); + virtqueue_add_inbuf(vi->vq, &sg, 1, buf, GFP_KERNEL); + + virtqueue_kick(vi->vq); } -/* At least we don't udelay() in a loop like some other drivers. */ -static int virtio_data_present(struct hwrng *rng, int wait) +static int virtio_read(struct hwrng *rng, void *buf, size_t size, bool wait) { - if (data_left) - return 1; + int ret; + struct virtrng_info *vi = (struct virtrng_info *)rng->priv; + + /* + * Don't ask host for data till we're setup. This call can + * happen during hwrng_register(), after commit d9e7972619. + */ + if (unlikely(!probe_done)) + return 0; + + if (!vi->busy) { + vi->busy = true; + init_completion(&vi->have_data); + register_buffer(vi, buf, size); + } if (!wait) return 0; - wait_for_completion(&have_data); - return 1; + ret = wait_for_completion_killable(&vi->have_data); + if (ret < 0) + return ret; + + vi->busy = false; + + return vi->data_avail; } -/* virtio_data_present() must have succeeded before this is called. */ -static int virtio_data_read(struct hwrng *rng, u32 *data) +static void virtio_cleanup(struct hwrng *rng) { - BUG_ON(!data_left); - - *data = random_data[--data_left]; + struct virtrng_info *vi = (struct virtrng_info *)rng->priv; - if (!data_left) { - init_completion(&have_data); - register_buffer(); - } - return sizeof(*data); + if (vi->busy) + wait_for_completion(&vi->have_data); } -static struct hwrng virtio_hwrng = { - .name = "virtio", - .data_present = virtio_data_present, - .data_read = virtio_data_read, -}; - -static int virtrng_probe(struct virtio_device *vdev) +static int probe_common(struct virtio_device *vdev) { - int err; + int err, index; + struct virtrng_info *vi = NULL; + + vi = kzalloc(sizeof(struct virtrng_info), GFP_KERNEL); + if (!vi) + return -ENOMEM; + + vi->index = index = ida_simple_get(&rng_index_ida, 0, 0, GFP_KERNEL); + if (index < 0) { + kfree(vi); + return index; + } + sprintf(vi->name, "virtio_rng.%d", index); + init_completion(&vi->have_data); + + vi->hwrng = (struct hwrng) { + .read = virtio_read, + .cleanup = virtio_cleanup, + .priv = (unsigned long)vi, + .name = vi->name, + }; + vdev->priv = vi; /* We expect a single virtqueue. */ - vq = vdev->config->find_vq(vdev, 0, random_recv_done); - if (IS_ERR(vq)) - return PTR_ERR(vq); + vi->vq = virtio_find_single_vq(vdev, random_recv_done, "input"); + if (IS_ERR(vi->vq)) { + err = PTR_ERR(vi->vq); + vi->vq = NULL; + kfree(vi); + ida_simple_remove(&rng_index_ida, index); + return err; + } - err = hwrng_register(&virtio_hwrng); + err = hwrng_register(&vi->hwrng); if (err) { - vdev->config->del_vq(vq); + vdev->config->del_vqs(vdev); + vi->vq = NULL; + kfree(vi); + ida_simple_remove(&rng_index_ida, index); return err; } - register_buffer(); + probe_done = true; return 0; } -static void virtrng_remove(struct virtio_device *vdev) +static void remove_common(struct virtio_device *vdev) { + struct virtrng_info *vi = vdev->priv; vdev->config->reset(vdev); - hwrng_unregister(&virtio_hwrng); - vdev->config->del_vq(vq); + vi->busy = false; + hwrng_unregister(&vi->hwrng); + vdev->config->del_vqs(vdev); + ida_simple_remove(&rng_index_ida, vi->index); + kfree(vi); } +static int virtrng_probe(struct virtio_device *vdev) +{ + return probe_common(vdev); +} + +static void virtrng_remove(struct virtio_device *vdev) +{ + remove_common(vdev); +} + +#ifdef CONFIG_PM_SLEEP +static int virtrng_freeze(struct virtio_device *vdev) +{ + remove_common(vdev); + return 0; +} + +static int virtrng_restore(struct virtio_device *vdev) +{ + return probe_common(vdev); +} +#endif + static struct virtio_device_id id_table[] = { { VIRTIO_ID_RNG, VIRTIO_DEV_ANY_ID }, { 0 }, }; -static struct virtio_driver virtio_rng = { +static struct virtio_driver virtio_rng_driver = { .driver.name = KBUILD_MODNAME, .driver.owner = THIS_MODULE, .id_table = id_table, .probe = virtrng_probe, - .remove = __devexit_p(virtrng_remove), + .remove = virtrng_remove, +#ifdef CONFIG_PM_SLEEP + .freeze = virtrng_freeze, + .restore = virtrng_restore, +#endif }; -static int __init init(void) -{ - int err; - - random_data = kmalloc(RANDOM_DATA_SIZE, GFP_KERNEL); - if (!random_data) - return -ENOMEM; - - err = register_virtio_driver(&virtio_rng); - if (err) - kfree(random_data); - return err; -} - -static void __exit fini(void) -{ - kfree(random_data); - unregister_virtio_driver(&virtio_rng); -} -module_init(init); -module_exit(fini); - +module_virtio_driver(virtio_rng_driver); MODULE_DEVICE_TABLE(virtio, id_table); MODULE_DESCRIPTION("Virtio random number driver"); MODULE_LICENSE("GPL"); |
