diff options
Diffstat (limited to 'drivers/char/hw_random/s390-trng.c')
-rw-r--r-- | drivers/char/hw_random/s390-trng.c | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/drivers/char/hw_random/s390-trng.c b/drivers/char/hw_random/s390-trng.c new file mode 100644 index 000000000..795853dfc --- /dev/null +++ b/drivers/char/hw_random/s390-trng.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * s390 TRNG device driver + * + * Driver for the TRNG (true random number generation) command + * available via CPACF extension MSA 7 on the s390 arch. + + * Copyright IBM Corp. 2017 + * Author(s): Harald Freudenberger <freude@de.ibm.com> + */ + +#define KMSG_COMPONENT "trng" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include <linux/hw_random.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/cpufeature.h> +#include <linux/miscdevice.h> +#include <linux/debugfs.h> +#include <linux/atomic.h> +#include <linux/random.h> +#include <linux/sched/signal.h> +#include <asm/debug.h> +#include <asm/cpacf.h> + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("IBM Corporation"); +MODULE_DESCRIPTION("s390 CPACF TRNG device driver"); + + +/* trng related debug feature things */ + +static debug_info_t *debug_info; + +#define DEBUG_DBG(...) debug_sprintf_event(debug_info, 6, ##__VA_ARGS__) +#define DEBUG_INFO(...) debug_sprintf_event(debug_info, 5, ##__VA_ARGS__) +#define DEBUG_WARN(...) debug_sprintf_event(debug_info, 4, ##__VA_ARGS__) +#define DEBUG_ERR(...) debug_sprintf_event(debug_info, 3, ##__VA_ARGS__) + + +/* trng helpers */ + +static atomic64_t trng_dev_counter = ATOMIC64_INIT(0); +static atomic64_t trng_hwrng_counter = ATOMIC64_INIT(0); + + +/* file io functions */ + +static int trng_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +static ssize_t trng_read(struct file *file, char __user *ubuf, + size_t nbytes, loff_t *ppos) +{ + u8 buf[32]; + u8 *p = buf; + unsigned int n; + ssize_t ret = 0; + + /* + * use buf for requests <= sizeof(buf), + * otherwise allocate one page and fetch + * pagewise. + */ + + if (nbytes > sizeof(buf)) { + p = (u8 *) __get_free_page(GFP_KERNEL); + if (!p) + return -ENOMEM; + } + + while (nbytes) { + if (need_resched()) { + if (signal_pending(current)) { + if (ret == 0) + ret = -ERESTARTSYS; + break; + } + schedule(); + } + n = nbytes > PAGE_SIZE ? PAGE_SIZE : nbytes; + cpacf_trng(NULL, 0, p, n); + atomic64_add(n, &trng_dev_counter); + if (copy_to_user(ubuf, p, n)) { + ret = -EFAULT; + break; + } + nbytes -= n; + ubuf += n; + ret += n; + } + + if (p != buf) + free_page((unsigned long) p); + + DEBUG_DBG("trng_read()=%zd\n", ret); + return ret; +} + + +/* sysfs */ + +static ssize_t trng_counter_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u64 dev_counter = atomic64_read(&trng_dev_counter); + u64 hwrng_counter = atomic64_read(&trng_hwrng_counter); + u64 arch_counter = atomic64_read(&s390_arch_random_counter); + + return sysfs_emit(buf, + "trng: %llu\n" + "hwrng: %llu\n" + "arch: %llu\n" + "total: %llu\n", + dev_counter, hwrng_counter, arch_counter, + dev_counter + hwrng_counter + arch_counter); +} +static DEVICE_ATTR(byte_counter, 0444, trng_counter_show, NULL); + +static struct attribute *trng_dev_attrs[] = { + &dev_attr_byte_counter.attr, + NULL +}; + +static const struct attribute_group trng_dev_attr_group = { + .attrs = trng_dev_attrs +}; + +static const struct attribute_group *trng_dev_attr_groups[] = { + &trng_dev_attr_group, + NULL +}; + +static const struct file_operations trng_fops = { + .owner = THIS_MODULE, + .open = &trng_open, + .release = NULL, + .read = &trng_read, + .llseek = noop_llseek, +}; + +static struct miscdevice trng_dev = { + .name = "trng", + .minor = MISC_DYNAMIC_MINOR, + .mode = 0444, + .fops = &trng_fops, + .groups = trng_dev_attr_groups, +}; + + +/* hwrng_register */ + +static inline void _trng_hwrng_read(u8 *buf, size_t len) +{ + cpacf_trng(NULL, 0, buf, len); + atomic64_add(len, &trng_hwrng_counter); +} + +static int trng_hwrng_data_read(struct hwrng *rng, u32 *data) +{ + size_t len = sizeof(*data); + + _trng_hwrng_read((u8 *) data, len); + + DEBUG_DBG("trng_hwrng_data_read()=%zu\n", len); + + return len; +} + +static int trng_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + size_t len = max <= PAGE_SIZE ? max : PAGE_SIZE; + + _trng_hwrng_read((u8 *) data, len); + + DEBUG_DBG("trng_hwrng_read()=%zu\n", len); + + return len; +} + +/* + * hwrng register struct + * The trng is supposed to have 100% entropy, and thus we register with a very + * high quality value. If we ever have a better driver in the future, we should + * change this value again when we merge this driver. + */ +static struct hwrng trng_hwrng_dev = { + .name = "s390-trng", + .data_read = trng_hwrng_data_read, + .read = trng_hwrng_read, + .quality = 1024, +}; + + +/* init and exit */ + +static void __init trng_debug_init(void) +{ + debug_info = debug_register("trng", 1, 1, 4 * sizeof(long)); + debug_register_view(debug_info, &debug_sprintf_view); + debug_set_level(debug_info, 3); +} + +static void trng_debug_exit(void) +{ + debug_unregister(debug_info); +} + +static int __init trng_init(void) +{ + int ret; + + trng_debug_init(); + + /* check if subfunction CPACF_PRNO_TRNG is available */ + if (!cpacf_query_func(CPACF_PRNO, CPACF_PRNO_TRNG)) { + DEBUG_INFO("trng_init CPACF_PRNO_TRNG not available\n"); + ret = -ENODEV; + goto out_dbg; + } + + ret = misc_register(&trng_dev); + if (ret) { + DEBUG_WARN("trng_init misc_register() failed rc=%d\n", ret); + goto out_dbg; + } + + ret = hwrng_register(&trng_hwrng_dev); + if (ret) { + DEBUG_WARN("trng_init hwrng_register() failed rc=%d\n", ret); + goto out_misc; + } + + DEBUG_DBG("trng_init successful\n"); + + return 0; + +out_misc: + misc_deregister(&trng_dev); +out_dbg: + trng_debug_exit(); + return ret; +} + +static void __exit trng_exit(void) +{ + hwrng_unregister(&trng_hwrng_dev); + misc_deregister(&trng_dev); + trng_debug_exit(); +} + +module_cpu_feature_match(S390_CPU_FEATURE_MSA, trng_init); +module_exit(trng_exit); |