Instead of having only one hwrng feeding /dev/random at a time, maintain a list of devices and cycle between them when filling the entropy pool. Signed-off-by: Keith Packard <keithp@xxxxxxxxxx> --- drivers/char/hw_random/core.c | 155 +++++++++++++++++++++++++++--------------- include/linux/hw_random.h | 2 + 2 files changed, 104 insertions(+), 53 deletions(-) diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c index 9203f2d..97fd4a7 100644 --- a/drivers/char/hw_random/core.c +++ b/drivers/char/hw_random/core.c @@ -51,10 +51,10 @@ #define RNG_MISCDEV_MINOR 183 /* official */ -static struct hwrng *current_rng; +static LIST_HEAD(active_rng); static struct task_struct *hwrng_fill; static LIST_HEAD(rng_list); -/* Protects rng_list and current_rng */ +/* Protects rng_list and active_rng */ static DEFINE_MUTEX(rng_mutex); /* Protects rng read functions, data_avail, rng_buffer and rng_fillbuf */ static DEFINE_MUTEX(reading_mutex); @@ -70,7 +70,6 @@ module_param(default_quality, ushort, 0644); MODULE_PARM_DESC(default_quality, "default entropy content of hwrng per mill"); -static void drop_current_rng(void); static int hwrng_init(struct hwrng *rng); static void start_khwrngd(void); @@ -104,31 +103,65 @@ static inline void cleanup_rng(struct kref *kref) complete(&rng->cleanup_done); } -static int set_current_rng(struct hwrng *rng) +static unsigned short rng_quality(struct hwrng *rng) +{ + unsigned short quality; + + quality = current_quality; + if (!quality) { + quality = rng->quality; + if (!quality) + quality = default_quality; + } + if (quality > 1024) + quality = 1024; + return quality; +} + +static int add_active_rng(struct hwrng *rng) { int err; BUG_ON(!mutex_is_locked(&rng_mutex)); + if (rng->is_active) + return 0; + err = hwrng_init(rng); if (err) return err; - drop_current_rng(); - current_rng = rng; + if (rng_quality(rng) != 0) { + rng->is_active = true; + list_add(&rng->active, &active_rng); + if (!hwrng_fill) + start_khwrngd(); + } return 0; } -static void drop_current_rng(void) +static int drop_active_rng(struct hwrng *rng) { BUG_ON(!mutex_is_locked(&rng_mutex)); - if (!current_rng) - return; + if (!rng->is_active) + return 0; + + list_del(&rng->active); + rng->is_active = false; /* decrease last reference for triggering the cleanup */ - kref_put(¤t_rng->ref, cleanup_rng); - current_rng = NULL; + kref_put(&rng->ref, cleanup_rng); + + if (list_empty(&active_rng) && hwrng_fill) + kthread_stop(hwrng_fill); + + return 0; +} + +static struct hwrng *first_active_rng(void) +{ + return list_first_entry_or_null(&active_rng, struct hwrng, active); } /* Returns ERR_PTR(), NULL or refcounted hwrng */ @@ -139,18 +172,32 @@ static struct hwrng *get_current_rng(void) if (mutex_lock_interruptible(&rng_mutex)) return ERR_PTR(-ERESTARTSYS); - rng = current_rng; + rng = first_active_rng(); if (rng) kref_get(&rng->ref); mutex_unlock(&rng_mutex); + return rng; } +static int next_rng(void) +{ + if (mutex_lock_interruptible(&rng_mutex)) + return -ERESTARTSYS; + + if (!list_empty(&active_rng) && !list_is_singular(&active_rng)) + list_rotate_left(&active_rng); + + mutex_unlock(&rng_mutex); + + return 0; +} + static void put_rng(struct hwrng *rng) { /* - * Hold rng_mutex here so we serialize in case they set_current_rng + * Hold rng_mutex here so we serialize in case they add_active_rng * on rng again immediately. */ mutex_lock(&rng_mutex); @@ -178,15 +225,6 @@ static int hwrng_init(struct hwrng *rng) skip_init: add_early_randomness(rng); - current_quality = rng->quality ? : default_quality; - if (current_quality > 1024) - current_quality = 1024; - - if (current_quality == 0 && hwrng_fill) - kthread_stop(hwrng_fill); - if (current_quality > 0 && !hwrng_fill) - start_khwrngd(); - return 0; } @@ -246,6 +284,7 @@ static ssize_t rng_dev_read(struct file *filp, char __user *buf, bytes_read = rng_get_data(rng, rng_buffer, rng_buffer_size(), !(filp->f_flags & O_NONBLOCK)); + next_rng(); if (bytes_read < 0) { err = bytes_read; goto out_unlock_reading; @@ -320,20 +359,30 @@ static ssize_t hwrng_attr_current_store(struct device *dev, const char *buf, size_t len) { int err; + int add = 1; struct hwrng *rng; err = mutex_lock_interruptible(&rng_mutex); if (err) return -ERESTARTSYS; + if (*buf == '-') { + add = 0; + buf++; + } else if (*buf == '+') { + add = 1; + buf++; + } err = -ENODEV; list_for_each_entry(rng, &rng_list, list) { if (sysfs_streq(rng->name, buf)) { - err = 0; - if (rng != current_rng) - err = set_current_rng(rng); + if (add) + err = add_active_rng(rng); + else + err = drop_active_rng(rng); break; } } + mutex_unlock(&rng_mutex); return err ? : len; @@ -343,17 +392,26 @@ static ssize_t hwrng_attr_current_show(struct device *dev, struct device_attribute *attr, char *buf) { - ssize_t ret; + int err; struct hwrng *rng; - rng = get_current_rng(); - if (IS_ERR(rng)) - return PTR_ERR(rng); - - ret = snprintf(buf, PAGE_SIZE, "%s\n", rng ? rng->name : "none"); - put_rng(rng); + err = mutex_lock_interruptible(&rng_mutex); + if (err) + return -ERESTARTSYS; + buf[0] = '\0'; + if (list_empty(&active_rng)) { + strlcat(buf, "none", PAGE_SIZE); + } else { + list_for_each_entry(rng, &active_rng, active) { + if (rng != first_active_rng()) + strlcat(buf, " ", PAGE_SIZE); + strlcat(buf, rng->name, PAGE_SIZE); + } + } + strlcat(buf, "\n", PAGE_SIZE); + mutex_unlock(&rng_mutex); - return ret; + return strlen(buf); } static ssize_t hwrng_attr_available_show(struct device *dev, @@ -412,6 +470,7 @@ static int hwrng_fillfn(void *unused) rng = get_current_rng(); if (IS_ERR(rng) || !rng) break; + next_rng(); mutex_lock(&reading_mutex); rc = rng_get_data(rng, rng_fillbuf, rng_buffer_size(), 1); @@ -442,7 +501,7 @@ static void start_khwrngd(void) int hwrng_register(struct hwrng *rng) { int err = -EINVAL; - struct hwrng *old_rng, *tmp; + struct hwrng *tmp; if (rng->name == NULL || (rng->data_read == NULL && rng->read == NULL)) @@ -472,19 +531,18 @@ int hwrng_register(struct hwrng *rng) goto out_unlock; } + rng->is_active = false; + init_completion(&rng->cleanup_done); complete(&rng->cleanup_done); - old_rng = current_rng; - err = 0; - if (!old_rng) { - err = set_current_rng(rng); - if (err) - goto out_unlock; - } + err = add_active_rng(rng); + if (err) + goto out_unlock; + list_add_tail(&rng->list, &rng_list); - if (old_rng && !rng->init) { + if (!rng->init) { /* * Use a new device's input to add some randomness to * the system. If this rng device isn't going to be @@ -507,16 +565,7 @@ void hwrng_unregister(struct hwrng *rng) mutex_lock(&rng_mutex); list_del(&rng->list); - if (current_rng == rng) { - drop_current_rng(); - if (!list_empty(&rng_list)) { - struct hwrng *tail; - - tail = list_entry(rng_list.prev, struct hwrng, list); - - set_current_rng(tail); - } - } + drop_active_rng(rng); if (list_empty(&rng_list)) { mutex_unlock(&rng_mutex); @@ -579,7 +628,7 @@ static int __init hwrng_modinit(void) static void __exit hwrng_modexit(void) { mutex_lock(&rng_mutex); - BUG_ON(current_rng); + WARN_ON(!list_empty(&active_rng)); kfree(rng_buffer); kfree(rng_fillbuf); mutex_unlock(&rng_mutex); diff --git a/include/linux/hw_random.h b/include/linux/hw_random.h index 4f7d8f4..c655563 100644 --- a/include/linux/hw_random.h +++ b/include/linux/hw_random.h @@ -45,7 +45,9 @@ struct hwrng { unsigned short quality; /* internal. */ + bool is_active; struct list_head list; + struct list_head active; struct kref ref; struct completion cleanup_done; }; -- 2.8.1 -- To unsubscribe from this list: send the line "unsubscribe linux-crypto" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html