diff options
Diffstat (limited to 'drivers/char/hw_random')
-rw-r--r-- | drivers/char/hw_random/core.c | 73 | ||||
-rw-r--r-- | drivers/char/hw_random/virtio-rng.c | 40 |
2 files changed, 83 insertions, 30 deletions
diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c index c4419ea1ab07..aa30a25c8d49 100644 --- a/drivers/char/hw_random/core.c +++ b/drivers/char/hw_random/core.c @@ -38,6 +38,7 @@ #include <linux/fs.h> #include <linux/sched.h> #include <linux/miscdevice.h> +#include <linux/kthread.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/random.h> @@ -50,10 +51,22 @@ static struct hwrng *current_rng; +static struct task_struct *hwrng_fill; static LIST_HEAD(rng_list); static DEFINE_MUTEX(rng_mutex); static int data_avail; -static u8 *rng_buffer; +static u8 *rng_buffer, *rng_fillbuf; +static unsigned short current_quality; +static unsigned short default_quality; /* = 0; default to "off" */ + +module_param(current_quality, ushort, 0644); +MODULE_PARM_DESC(current_quality, + "current hwrng entropy estimation per mill"); +module_param(default_quality, ushort, 0644); +MODULE_PARM_DESC(default_quality, + "default entropy content of hwrng per mill"); + +static void start_khwrngd(void); static inline int rng_get_data(struct hwrng *rng, u8 *buffer, size_t size, int wait); @@ -68,12 +81,6 @@ static void add_early_randomness(struct hwrng *rng) unsigned char bytes[16]; int bytes_read; - /* - * Currently only virtio-rng cannot return data during device - * probe, and that's handled in virtio-rng.c itself. If there - * are more such devices, this call to rng_get_data can be - * made conditional here instead of doing it per-device. - */ bytes_read = rng_get_data(rng, bytes, sizeof(bytes), 1); if (bytes_read > 0) add_device_randomness(bytes, bytes_read); @@ -89,6 +96,15 @@ static inline int hwrng_init(struct hwrng *rng) return ret; } add_early_randomness(rng); + + current_quality = rng->quality ? : default_quality; + current_quality &= 1023; + + if (current_quality == 0 && hwrng_fill) + kthread_stop(hwrng_fill); + if (current_quality > 0 && !hwrng_fill) + start_khwrngd(); + return 0; } @@ -325,6 +341,36 @@ err_misc_dereg: goto out; } +static int hwrng_fillfn(void *unused) +{ + long rc; + + while (!kthread_should_stop()) { + if (!current_rng) + break; + rc = rng_get_data(current_rng, rng_fillbuf, + rng_buffer_size(), 1); + if (rc <= 0) { + pr_warn("hwrng: no data available\n"); + msleep_interruptible(10000); + continue; + } + add_hwgenerator_randomness((void *)rng_fillbuf, rc, + rc * current_quality * 8 >> 10); + } + hwrng_fill = NULL; + return 0; +} + +static void start_khwrngd(void) +{ + hwrng_fill = kthread_run(hwrng_fillfn, NULL, "hwrng"); + if (hwrng_fill == ERR_PTR(-ENOMEM)) { + pr_err("hwrng_fill thread creation failed"); + hwrng_fill = NULL; + } +} + int hwrng_register(struct hwrng *rng) { int err = -EINVAL; @@ -343,6 +389,13 @@ int hwrng_register(struct hwrng *rng) if (!rng_buffer) goto out_unlock; } + if (!rng_fillbuf) { + rng_fillbuf = kmalloc(rng_buffer_size(), GFP_KERNEL); + if (!rng_fillbuf) { + kfree(rng_buffer); + goto out_unlock; + } + } /* Must not register two RNGs with the same name. */ err = -EEXIST; @@ -406,8 +459,11 @@ void hwrng_unregister(struct hwrng *rng) current_rng = NULL; } } - if (list_empty(&rng_list)) + if (list_empty(&rng_list)) { unregister_miscdev(); + if (hwrng_fill) + kthread_stop(hwrng_fill); + } mutex_unlock(&rng_mutex); } @@ -418,6 +474,7 @@ static void __exit hwrng_exit(void) mutex_lock(&rng_mutex); BUG_ON(current_rng); kfree(rng_buffer); + kfree(rng_fillbuf); mutex_unlock(&rng_mutex); } diff --git a/drivers/char/hw_random/virtio-rng.c b/drivers/char/hw_random/virtio-rng.c index e9b15bc18b4d..2e3139eda93b 100644 --- a/drivers/char/hw_random/virtio-rng.c +++ b/drivers/char/hw_random/virtio-rng.c @@ -28,17 +28,16 @@ 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]; + unsigned int data_avail; int index; + bool busy; + bool hwrng_register_done; }; -static bool probe_done; static void random_recv_done(struct virtqueue *vq) { @@ -69,13 +68,6 @@ 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; - /* - * 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); @@ -124,6 +116,7 @@ static int probe_common(struct virtio_device *vdev) .cleanup = virtio_cleanup, .priv = (unsigned long)vi, .name = vi->name, + .quality = 1000, }; vdev->priv = vi; @@ -137,25 +130,17 @@ static int probe_common(struct virtio_device *vdev) return err; } - err = hwrng_register(&vi->hwrng); - if (err) { - vdev->config->del_vqs(vdev); - vi->vq = NULL; - kfree(vi); - ida_simple_remove(&rng_index_ida, index); - return err; - } - - probe_done = true; return 0; } static void remove_common(struct virtio_device *vdev) { struct virtrng_info *vi = vdev->priv; + vdev->config->reset(vdev); vi->busy = false; - hwrng_unregister(&vi->hwrng); + if (vi->hwrng_register_done) + hwrng_unregister(&vi->hwrng); vdev->config->del_vqs(vdev); ida_simple_remove(&rng_index_ida, vi->index); kfree(vi); @@ -171,6 +156,16 @@ static void virtrng_remove(struct virtio_device *vdev) remove_common(vdev); } +static void virtrng_scan(struct virtio_device *vdev) +{ + struct virtrng_info *vi = vdev->priv; + int err; + + err = hwrng_register(&vi->hwrng); + if (!err) + vi->hwrng_register_done = true; +} + #ifdef CONFIG_PM_SLEEP static int virtrng_freeze(struct virtio_device *vdev) { @@ -195,6 +190,7 @@ static struct virtio_driver virtio_rng_driver = { .id_table = id_table, .probe = virtrng_probe, .remove = virtrng_remove, + .scan = virtrng_scan, #ifdef CONFIG_PM_SLEEP .freeze = virtrng_freeze, .restore = virtrng_restore, |