Commit 3a2c0ba5 authored by Rusty Russell's avatar Rusty Russell Committed by Herbert Xu

hwrng: use reference counts on each struct hwrng.

current_rng holds one reference, and we bump it every time we want
to do a read from it.

This means we only hold the rng_mutex to grab or drop a reference,
so accessing /sys/devices/virtual/misc/hw_random/rng_current doesn't
block on read of /dev/hwrng.

Using a kref is overkill (we're always under the rng_mutex), but
a standard pattern.

This also solves the problem that the hwrng_fillfn thread was
accessing current_rng without a lock, which could change (eg. to NULL)
underneath it.
Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
Signed-off-by: default avatarAmos Kong <akong@redhat.com>
Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
parent 1dacb395
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/random.h> #include <linux/random.h>
#include <linux/err.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
...@@ -91,6 +92,60 @@ static void add_early_randomness(struct hwrng *rng) ...@@ -91,6 +92,60 @@ static void add_early_randomness(struct hwrng *rng)
add_device_randomness(bytes, bytes_read); add_device_randomness(bytes, bytes_read);
} }
static inline void cleanup_rng(struct kref *kref)
{
struct hwrng *rng = container_of(kref, struct hwrng, ref);
if (rng->cleanup)
rng->cleanup(rng);
}
static void set_current_rng(struct hwrng *rng)
{
BUG_ON(!mutex_is_locked(&rng_mutex));
kref_get(&rng->ref);
current_rng = rng;
}
static void drop_current_rng(void)
{
BUG_ON(!mutex_is_locked(&rng_mutex));
if (!current_rng)
return;
/* decrease last reference for triggering the cleanup */
kref_put(&current_rng->ref, cleanup_rng);
current_rng = NULL;
}
/* Returns ERR_PTR(), NULL or refcounted hwrng */
static struct hwrng *get_current_rng(void)
{
struct hwrng *rng;
if (mutex_lock_interruptible(&rng_mutex))
return ERR_PTR(-ERESTARTSYS);
rng = current_rng;
if (rng)
kref_get(&rng->ref);
mutex_unlock(&rng_mutex);
return rng;
}
static void put_rng(struct hwrng *rng)
{
/*
* Hold rng_mutex here so we serialize in case they set_current_rng
* on rng again immediately.
*/
mutex_lock(&rng_mutex);
if (rng)
kref_put(&rng->ref, cleanup_rng);
mutex_unlock(&rng_mutex);
}
static inline int hwrng_init(struct hwrng *rng) static inline int hwrng_init(struct hwrng *rng)
{ {
if (rng->init) { if (rng->init) {
...@@ -113,12 +168,6 @@ static inline int hwrng_init(struct hwrng *rng) ...@@ -113,12 +168,6 @@ static inline int hwrng_init(struct hwrng *rng)
return 0; return 0;
} }
static inline void hwrng_cleanup(struct hwrng *rng)
{
if (rng && rng->cleanup)
rng->cleanup(rng);
}
static int rng_dev_open(struct inode *inode, struct file *filp) static int rng_dev_open(struct inode *inode, struct file *filp)
{ {
/* enforce read-only access to this chrdev */ /* enforce read-only access to this chrdev */
...@@ -154,21 +203,22 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf, ...@@ -154,21 +203,22 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
ssize_t ret = 0; ssize_t ret = 0;
int err = 0; int err = 0;
int bytes_read, len; int bytes_read, len;
struct hwrng *rng;
while (size) { while (size) {
if (mutex_lock_interruptible(&rng_mutex)) { rng = get_current_rng();
err = -ERESTARTSYS; if (IS_ERR(rng)) {
err = PTR_ERR(rng);
goto out; goto out;
} }
if (!rng) {
if (!current_rng) {
err = -ENODEV; err = -ENODEV;
goto out_unlock; goto out;
} }
mutex_lock(&reading_mutex); mutex_lock(&reading_mutex);
if (!data_avail) { if (!data_avail) {
bytes_read = rng_get_data(current_rng, rng_buffer, bytes_read = rng_get_data(rng, rng_buffer,
rng_buffer_size(), rng_buffer_size(),
!(filp->f_flags & O_NONBLOCK)); !(filp->f_flags & O_NONBLOCK));
if (bytes_read < 0) { if (bytes_read < 0) {
...@@ -200,8 +250,8 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf, ...@@ -200,8 +250,8 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
ret += len; ret += len;
} }
mutex_unlock(&rng_mutex);
mutex_unlock(&reading_mutex); mutex_unlock(&reading_mutex);
put_rng(rng);
if (need_resched()) if (need_resched())
schedule_timeout_interruptible(1); schedule_timeout_interruptible(1);
...@@ -213,12 +263,11 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf, ...@@ -213,12 +263,11 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf,
} }
out: out:
return ret ? : err; return ret ? : err;
out_unlock:
mutex_unlock(&rng_mutex);
goto out;
out_unlock_reading: out_unlock_reading:
mutex_unlock(&reading_mutex); mutex_unlock(&reading_mutex);
goto out_unlock; put_rng(rng);
goto out;
} }
...@@ -257,8 +306,8 @@ static ssize_t hwrng_attr_current_store(struct device *dev, ...@@ -257,8 +306,8 @@ static ssize_t hwrng_attr_current_store(struct device *dev,
err = hwrng_init(rng); err = hwrng_init(rng);
if (err) if (err)
break; break;
hwrng_cleanup(current_rng); drop_current_rng();
current_rng = rng; set_current_rng(rng);
err = 0; err = 0;
break; break;
} }
...@@ -272,17 +321,15 @@ static ssize_t hwrng_attr_current_show(struct device *dev, ...@@ -272,17 +321,15 @@ static ssize_t hwrng_attr_current_show(struct device *dev,
struct device_attribute *attr, struct device_attribute *attr,
char *buf) char *buf)
{ {
int err;
ssize_t ret; ssize_t ret;
const char *name = "none"; struct hwrng *rng;
err = mutex_lock_interruptible(&rng_mutex); rng = get_current_rng();
if (err) if (IS_ERR(rng))
return -ERESTARTSYS; return PTR_ERR(rng);
if (current_rng)
name = current_rng->name; ret = snprintf(buf, PAGE_SIZE, "%s\n", rng ? rng->name : "none");
ret = snprintf(buf, PAGE_SIZE, "%s\n", name); put_rng(rng);
mutex_unlock(&rng_mutex);
return ret; return ret;
} }
...@@ -353,12 +400,16 @@ static int hwrng_fillfn(void *unused) ...@@ -353,12 +400,16 @@ static int hwrng_fillfn(void *unused)
long rc; long rc;
while (!kthread_should_stop()) { while (!kthread_should_stop()) {
if (!current_rng) struct hwrng *rng;
rng = get_current_rng();
if (IS_ERR(rng) || !rng)
break; break;
mutex_lock(&reading_mutex); mutex_lock(&reading_mutex);
rc = rng_get_data(current_rng, rng_fillbuf, rc = rng_get_data(rng, rng_fillbuf,
rng_buffer_size(), 1); rng_buffer_size(), 1);
mutex_unlock(&reading_mutex); mutex_unlock(&reading_mutex);
put_rng(rng);
if (rc <= 0) { if (rc <= 0) {
pr_warn("hwrng: no data available\n"); pr_warn("hwrng: no data available\n");
msleep_interruptible(10000); msleep_interruptible(10000);
...@@ -419,14 +470,13 @@ int hwrng_register(struct hwrng *rng) ...@@ -419,14 +470,13 @@ int hwrng_register(struct hwrng *rng)
err = hwrng_init(rng); err = hwrng_init(rng);
if (err) if (err)
goto out_unlock; goto out_unlock;
current_rng = rng; set_current_rng(rng);
} }
err = 0; err = 0;
if (!old_rng) { if (!old_rng) {
err = register_miscdev(); err = register_miscdev();
if (err) { if (err) {
hwrng_cleanup(rng); drop_current_rng();
current_rng = NULL;
goto out_unlock; goto out_unlock;
} }
} }
...@@ -453,22 +503,21 @@ EXPORT_SYMBOL_GPL(hwrng_register); ...@@ -453,22 +503,21 @@ EXPORT_SYMBOL_GPL(hwrng_register);
void hwrng_unregister(struct hwrng *rng) void hwrng_unregister(struct hwrng *rng)
{ {
int err;
mutex_lock(&rng_mutex); mutex_lock(&rng_mutex);
list_del(&rng->list); list_del(&rng->list);
if (current_rng == rng) { if (current_rng == rng) {
hwrng_cleanup(rng); drop_current_rng();
if (list_empty(&rng_list)) { if (!list_empty(&rng_list)) {
current_rng = NULL; struct hwrng *tail;
} else {
current_rng = list_entry(rng_list.prev, struct hwrng, list); tail = list_entry(rng_list.prev, struct hwrng, list);
err = hwrng_init(current_rng);
if (err) if (hwrng_init(tail) == 0)
current_rng = NULL; set_current_rng(tail);
} }
} }
if (list_empty(&rng_list)) { if (list_empty(&rng_list)) {
mutex_unlock(&rng_mutex); mutex_unlock(&rng_mutex);
unregister_miscdev(); unregister_miscdev();
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/kref.h>
/** /**
* struct hwrng - Hardware Random Number Generator driver * struct hwrng - Hardware Random Number Generator driver
...@@ -44,6 +45,7 @@ struct hwrng { ...@@ -44,6 +45,7 @@ struct hwrng {
/* internal. */ /* internal. */
struct list_head list; struct list_head list;
struct kref ref;
}; };
/** Register a new Hardware Random Number Generator driver. */ /** Register a new Hardware Random Number Generator driver. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment