From 2c3c1048746a4622d8c89a29670120dc8fab93c4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:49:45 +0200 Subject: Adding upstream version 6.1.76. Signed-off-by: Daniel Baumann --- drivers/char/hw_random/core.c | 736 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 736 insertions(+) create mode 100644 drivers/char/hw_random/core.c (limited to 'drivers/char/hw_random/core.c') diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c new file mode 100644 index 000000000..c319f9937 --- /dev/null +++ b/drivers/char/hw_random/core.c @@ -0,0 +1,736 @@ +/* + * hw_random/core.c: HWRNG core API + * + * Copyright 2006 Michael Buesch + * Copyright 2005 (c) MontaVista Software, Inc. + * + * Please read Documentation/admin-guide/hw_random.rst for details on use. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RNG_MODULE_NAME "hw_random" + +#define RNG_BUFFER_SIZE (SMP_CACHE_BYTES < 32 ? 32 : SMP_CACHE_BYTES) + +static struct hwrng *current_rng; +/* the current rng has been explicitly chosen by user via sysfs */ +static int cur_rng_set_by_user; +static struct task_struct *hwrng_fill; +/* list of registered rngs */ +static LIST_HEAD(rng_list); +/* Protects rng_list and current_rng */ +static DEFINE_MUTEX(rng_mutex); +/* Protects rng read functions, data_avail, rng_buffer and rng_fillbuf */ +static DEFINE_MUTEX(reading_mutex); +static int data_avail; +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 1024 bits of input -- obsolete, use rng_quality instead"); +module_param(default_quality, ushort, 0644); +MODULE_PARM_DESC(default_quality, + "default entropy content of hwrng per 1024 bits of input"); + +static void drop_current_rng(void); +static int hwrng_init(struct hwrng *rng); +static int hwrng_fillfn(void *unused); + +static inline int rng_get_data(struct hwrng *rng, u8 *buffer, size_t size, + int wait); + +static size_t rng_buffer_size(void) +{ + return RNG_BUFFER_SIZE; +} + +static void add_early_randomness(struct hwrng *rng) +{ + int bytes_read; + + mutex_lock(&reading_mutex); + bytes_read = rng_get_data(rng, rng_fillbuf, 32, 0); + mutex_unlock(&reading_mutex); + if (bytes_read > 0) + add_device_randomness(rng_fillbuf, 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); + + complete(&rng->cleanup_done); +} + +static int set_current_rng(struct hwrng *rng) +{ + int err; + + BUG_ON(!mutex_is_locked(&rng_mutex)); + + err = hwrng_init(rng); + if (err) + return err; + + drop_current_rng(); + current_rng = rng; + + /* if necessary, start hwrng thread */ + if (!hwrng_fill) { + hwrng_fill = kthread_run(hwrng_fillfn, NULL, "hwrng"); + if (IS_ERR(hwrng_fill)) { + pr_err("hwrng_fill thread creation failed\n"); + hwrng_fill = NULL; + } + } + + return 0; +} + +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(¤t_rng->ref, cleanup_rng); + current_rng = NULL; +} + +/* Returns ERR_PTR(), NULL or refcounted hwrng */ +static struct hwrng *get_current_rng_nolock(void) +{ + if (current_rng) + kref_get(¤t_rng->ref); + + return current_rng; +} + +static struct hwrng *get_current_rng(void) +{ + struct hwrng *rng; + + if (mutex_lock_interruptible(&rng_mutex)) + return ERR_PTR(-ERESTARTSYS); + + rng = get_current_rng_nolock(); + + 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 int hwrng_init(struct hwrng *rng) +{ + if (kref_get_unless_zero(&rng->ref)) + goto skip_init; + + if (rng->init) { + int ret; + + ret = rng->init(rng); + if (ret) + return ret; + } + + kref_init(&rng->ref); + reinit_completion(&rng->cleanup_done); + +skip_init: + if (!rng->quality) + rng->quality = default_quality; + if (rng->quality > 1024) + rng->quality = 1024; + current_quality = rng->quality; /* obsolete */ + + return 0; +} + +static int rng_dev_open(struct inode *inode, struct file *filp) +{ + /* enforce read-only access to this chrdev */ + if ((filp->f_mode & FMODE_READ) == 0) + return -EINVAL; + if (filp->f_mode & FMODE_WRITE) + return -EINVAL; + return 0; +} + +static inline int rng_get_data(struct hwrng *rng, u8 *buffer, size_t size, + int wait) { + int present; + + BUG_ON(!mutex_is_locked(&reading_mutex)); + if (rng->read) + return rng->read(rng, (void *)buffer, size, wait); + + if (rng->data_present) + present = rng->data_present(rng, wait); + else + present = 1; + + if (present) + return rng->data_read(rng, (u32 *)buffer); + + return 0; +} + +static ssize_t rng_dev_read(struct file *filp, char __user *buf, + size_t size, loff_t *offp) +{ + u8 buffer[RNG_BUFFER_SIZE]; + ssize_t ret = 0; + int err = 0; + int bytes_read, len; + struct hwrng *rng; + + while (size) { + rng = get_current_rng(); + if (IS_ERR(rng)) { + err = PTR_ERR(rng); + goto out; + } + if (!rng) { + err = -ENODEV; + goto out; + } + + if (mutex_lock_interruptible(&reading_mutex)) { + err = -ERESTARTSYS; + goto out_put; + } + if (!data_avail) { + bytes_read = rng_get_data(rng, rng_buffer, + rng_buffer_size(), + !(filp->f_flags & O_NONBLOCK)); + if (bytes_read < 0) { + err = bytes_read; + goto out_unlock_reading; + } else if (bytes_read == 0 && + (filp->f_flags & O_NONBLOCK)) { + err = -EAGAIN; + goto out_unlock_reading; + } + + data_avail = bytes_read; + } + + len = data_avail; + if (len) { + if (len > size) + len = size; + + data_avail -= len; + + memcpy(buffer, rng_buffer + data_avail, len); + } + mutex_unlock(&reading_mutex); + put_rng(rng); + + if (len) { + if (copy_to_user(buf + ret, buffer, len)) { + err = -EFAULT; + goto out; + } + + size -= len; + ret += len; + } + + + if (need_resched()) + schedule_timeout_interruptible(1); + + if (signal_pending(current)) { + err = -ERESTARTSYS; + goto out; + } + } +out: + memzero_explicit(buffer, sizeof(buffer)); + return ret ? : err; + +out_unlock_reading: + mutex_unlock(&reading_mutex); +out_put: + put_rng(rng); + goto out; +} + +static const struct file_operations rng_chrdev_ops = { + .owner = THIS_MODULE, + .open = rng_dev_open, + .read = rng_dev_read, + .llseek = noop_llseek, +}; + +static const struct attribute_group *rng_dev_groups[]; + +static struct miscdevice rng_miscdev = { + .minor = HWRNG_MINOR, + .name = RNG_MODULE_NAME, + .nodename = "hwrng", + .fops = &rng_chrdev_ops, + .groups = rng_dev_groups, +}; + +static int enable_best_rng(void) +{ + struct hwrng *rng, *new_rng = NULL; + int ret = -ENODEV; + + BUG_ON(!mutex_is_locked(&rng_mutex)); + + /* no rng to use? */ + if (list_empty(&rng_list)) { + drop_current_rng(); + cur_rng_set_by_user = 0; + return 0; + } + + /* use the rng which offers the best quality */ + list_for_each_entry(rng, &rng_list, list) { + if (!new_rng || rng->quality > new_rng->quality) + new_rng = rng; + } + + ret = ((new_rng == current_rng) ? 0 : set_current_rng(new_rng)); + if (!ret) + cur_rng_set_by_user = 0; + + return ret; +} + +static ssize_t rng_current_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int err; + struct hwrng *rng, *old_rng, *new_rng; + + err = mutex_lock_interruptible(&rng_mutex); + if (err) + return -ERESTARTSYS; + + old_rng = current_rng; + if (sysfs_streq(buf, "")) { + err = enable_best_rng(); + } else { + list_for_each_entry(rng, &rng_list, list) { + if (sysfs_streq(rng->name, buf)) { + err = set_current_rng(rng); + if (!err) + cur_rng_set_by_user = 1; + break; + } + } + } + new_rng = get_current_rng_nolock(); + mutex_unlock(&rng_mutex); + + if (new_rng) { + if (new_rng != old_rng) + add_early_randomness(new_rng); + put_rng(new_rng); + } + + return err ? : len; +} + +static ssize_t rng_current_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + 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); + + return ret; +} + +static ssize_t rng_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err; + struct hwrng *rng; + + err = mutex_lock_interruptible(&rng_mutex); + if (err) + return -ERESTARTSYS; + buf[0] = '\0'; + list_for_each_entry(rng, &rng_list, list) { + strlcat(buf, rng->name, PAGE_SIZE); + strlcat(buf, " ", PAGE_SIZE); + } + strlcat(buf, "\n", PAGE_SIZE); + mutex_unlock(&rng_mutex); + + return strlen(buf); +} + +static ssize_t rng_selected_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", cur_rng_set_by_user); +} + +static ssize_t rng_quality_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + struct hwrng *rng; + + rng = get_current_rng(); + if (IS_ERR(rng)) + return PTR_ERR(rng); + + if (!rng) /* no need to put_rng */ + return -ENODEV; + + ret = sysfs_emit(buf, "%hu\n", rng->quality); + put_rng(rng); + + return ret; +} + +static ssize_t rng_quality_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + u16 quality; + int ret = -EINVAL; + + if (len < 2) + return -EINVAL; + + ret = mutex_lock_interruptible(&rng_mutex); + if (ret) + return -ERESTARTSYS; + + ret = kstrtou16(buf, 0, &quality); + if (ret || quality > 1024) { + ret = -EINVAL; + goto out; + } + + if (!current_rng) { + ret = -ENODEV; + goto out; + } + + current_rng->quality = quality; + current_quality = quality; /* obsolete */ + + /* the best available RNG may have changed */ + ret = enable_best_rng(); + +out: + mutex_unlock(&rng_mutex); + return ret ? ret : len; +} + +static DEVICE_ATTR_RW(rng_current); +static DEVICE_ATTR_RO(rng_available); +static DEVICE_ATTR_RO(rng_selected); +static DEVICE_ATTR_RW(rng_quality); + +static struct attribute *rng_dev_attrs[] = { + &dev_attr_rng_current.attr, + &dev_attr_rng_available.attr, + &dev_attr_rng_selected.attr, + &dev_attr_rng_quality.attr, + NULL +}; + +ATTRIBUTE_GROUPS(rng_dev); + +static void __exit unregister_miscdev(void) +{ + misc_deregister(&rng_miscdev); +} + +static int __init register_miscdev(void) +{ + return misc_register(&rng_miscdev); +} + +static int hwrng_fillfn(void *unused) +{ + size_t entropy, entropy_credit = 0; /* in 1/1024 of a bit */ + long rc; + + while (!kthread_should_stop()) { + unsigned short quality; + struct hwrng *rng; + + rng = get_current_rng(); + if (IS_ERR(rng) || !rng) + break; + mutex_lock(&reading_mutex); + rc = rng_get_data(rng, rng_fillbuf, + rng_buffer_size(), 1); + if (current_quality != rng->quality) + rng->quality = current_quality; /* obsolete */ + quality = rng->quality; + mutex_unlock(&reading_mutex); + + if (rc <= 0) + hwrng_msleep(rng, 10000); + + put_rng(rng); + + if (rc <= 0) + continue; + + /* If we cannot credit at least one bit of entropy, + * keep track of the remainder for the next iteration + */ + entropy = rc * quality * 8 + entropy_credit; + if ((entropy >> 10) == 0) + entropy_credit = entropy; + + /* Outside lock, sure, but y'know: randomness. */ + add_hwgenerator_randomness((void *)rng_fillbuf, rc, + entropy >> 10); + } + hwrng_fill = NULL; + return 0; +} + +int hwrng_register(struct hwrng *rng) +{ + int err = -EINVAL; + struct hwrng *tmp; + bool is_new_current = false; + + if (!rng->name || (!rng->data_read && !rng->read)) + goto out; + + mutex_lock(&rng_mutex); + + /* Must not register two RNGs with the same name. */ + err = -EEXIST; + list_for_each_entry(tmp, &rng_list, list) { + if (strcmp(tmp->name, rng->name) == 0) + goto out_unlock; + } + list_add_tail(&rng->list, &rng_list); + + init_completion(&rng->cleanup_done); + complete(&rng->cleanup_done); + init_completion(&rng->dying); + + if (!current_rng || + (!cur_rng_set_by_user && rng->quality > current_rng->quality)) { + /* + * Set new rng as current as the new rng source + * provides better entropy quality and was not + * chosen by userspace. + */ + err = set_current_rng(rng); + if (err) + goto out_unlock; + /* to use current_rng in add_early_randomness() we need + * to take a ref + */ + is_new_current = true; + kref_get(&rng->ref); + } + mutex_unlock(&rng_mutex); + if (is_new_current || !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 by set_current_rng(); so only use the + * randomness from devices that don't need an init callback + */ + add_early_randomness(rng); + } + if (is_new_current) + put_rng(rng); + return 0; +out_unlock: + mutex_unlock(&rng_mutex); +out: + return err; +} +EXPORT_SYMBOL_GPL(hwrng_register); + +void hwrng_unregister(struct hwrng *rng) +{ + struct hwrng *old_rng, *new_rng; + int err; + + mutex_lock(&rng_mutex); + + old_rng = current_rng; + list_del(&rng->list); + complete_all(&rng->dying); + if (current_rng == rng) { + err = enable_best_rng(); + if (err) { + drop_current_rng(); + cur_rng_set_by_user = 0; + } + } + + new_rng = get_current_rng_nolock(); + if (list_empty(&rng_list)) { + mutex_unlock(&rng_mutex); + if (hwrng_fill) + kthread_stop(hwrng_fill); + } else + mutex_unlock(&rng_mutex); + + if (new_rng) { + if (old_rng != new_rng) + add_early_randomness(new_rng); + put_rng(new_rng); + } + + wait_for_completion(&rng->cleanup_done); +} +EXPORT_SYMBOL_GPL(hwrng_unregister); + +static void devm_hwrng_release(struct device *dev, void *res) +{ + hwrng_unregister(*(struct hwrng **)res); +} + +static int devm_hwrng_match(struct device *dev, void *res, void *data) +{ + struct hwrng **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +int devm_hwrng_register(struct device *dev, struct hwrng *rng) +{ + struct hwrng **ptr; + int error; + + ptr = devres_alloc(devm_hwrng_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + error = hwrng_register(rng); + if (error) { + devres_free(ptr); + return error; + } + + *ptr = rng; + devres_add(dev, ptr); + return 0; +} +EXPORT_SYMBOL_GPL(devm_hwrng_register); + +void devm_hwrng_unregister(struct device *dev, struct hwrng *rng) +{ + devres_release(dev, devm_hwrng_release, devm_hwrng_match, rng); +} +EXPORT_SYMBOL_GPL(devm_hwrng_unregister); + +long hwrng_msleep(struct hwrng *rng, unsigned int msecs) +{ + unsigned long timeout = msecs_to_jiffies(msecs) + 1; + + return wait_for_completion_interruptible_timeout(&rng->dying, timeout); +} +EXPORT_SYMBOL_GPL(hwrng_msleep); + +long hwrng_yield(struct hwrng *rng) +{ + return wait_for_completion_interruptible_timeout(&rng->dying, 1); +} +EXPORT_SYMBOL_GPL(hwrng_yield); + +static int __init hwrng_modinit(void) +{ + int ret; + + /* kmalloc makes this safe for virt_to_page() in virtio_rng.c */ + rng_buffer = kmalloc(rng_buffer_size(), GFP_KERNEL); + if (!rng_buffer) + return -ENOMEM; + + rng_fillbuf = kmalloc(rng_buffer_size(), GFP_KERNEL); + if (!rng_fillbuf) { + kfree(rng_buffer); + return -ENOMEM; + } + + ret = register_miscdev(); + if (ret) { + kfree(rng_fillbuf); + kfree(rng_buffer); + } + + return ret; +} + +static void __exit hwrng_modexit(void) +{ + mutex_lock(&rng_mutex); + BUG_ON(current_rng); + kfree(rng_buffer); + kfree(rng_fillbuf); + mutex_unlock(&rng_mutex); + + unregister_miscdev(); +} + +fs_initcall(hwrng_modinit); /* depends on misc_register() */ +module_exit(hwrng_modexit); + +MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3