summaryrefslogtreecommitdiffstats
path: root/drivers/char/hw_random
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/hw_random')
-rw-r--r--drivers/char/hw_random/core.c114
-rw-r--r--drivers/char/hw_random/virtio-rng.c10
2 files changed, 114 insertions, 10 deletions
diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c
index 334601cc81cf..6e02ec103cc7 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,21 +51,67 @@
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);
static size_t rng_buffer_size(void)
{
return SMP_CACHE_BYTES < 32 ? 32 : SMP_CACHE_BYTES;
}
+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);
+}
+
static inline int hwrng_init(struct hwrng *rng)
{
- if (!rng->init)
- return 0;
- return rng->init(rng);
+ if (rng->init) {
+ int ret;
+
+ ret = rng->init(rng);
+ if (ret)
+ 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;
}
static inline void hwrng_cleanup(struct hwrng *rng)
@@ -300,12 +347,40 @@ 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;
struct hwrng *old_rng, *tmp;
- unsigned char bytes[16];
- int bytes_read;
if (rng->name == NULL ||
(rng->data_read == NULL && rng->read == NULL))
@@ -320,6 +395,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;
@@ -347,9 +429,17 @@ int hwrng_register(struct hwrng *rng)
INIT_LIST_HEAD(&rng->list);
list_add_tail(&rng->list, &rng_list);
- bytes_read = rng_get_data(rng, bytes, sizeof(bytes), 1);
- if (bytes_read > 0)
- add_device_randomness(bytes, bytes_read);
+ if (old_rng && !rng->init) {
+ /*
+ * Use a new device's input to add some randomness to
+ * the system. If this rng device isn't going to be
+ * used right away, its init function hasn't been
+ * called yet; so only use the randomness from devices
+ * that don't need an init callback.
+ */
+ add_early_randomness(rng);
+ }
+
out_unlock:
mutex_unlock(&rng_mutex);
out:
@@ -375,8 +465,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);
}
@@ -387,6 +480,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 f3e71501de54..e9b15bc18b4d 100644
--- a/drivers/char/hw_random/virtio-rng.c
+++ b/drivers/char/hw_random/virtio-rng.c
@@ -38,6 +38,8 @@ struct virtrng_info {
int index;
};
+static bool probe_done;
+
static void random_recv_done(struct virtqueue *vq)
{
struct virtrng_info *vi = vq->vdev->priv;
@@ -67,6 +69,13 @@ 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);
@@ -137,6 +146,7 @@ static int probe_common(struct virtio_device *vdev)
return err;
}
+ probe_done = true;
return 0;
}
OpenPOWER on IntegriCloud