diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index 59f25286befe..e5ec06382c05 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -308,6 +308,20 @@ config HW_RANDOM_HISI If unsure, say Y. +config HW_RANDOM_MSM_LEGACY + tristate "QTI MSM Random Number Generator support (LEGACY)" + depends on HW_RANDOM && ARCH_QCOM + select CRYPTO_AES + select CRYPTO_ECB + help + This driver provides kernel-side support for the Random Number + Generator hardware found on QTI MSM SoCs. + + To compile this driver as a module, choose M here: the + module will be called msm_rng. + + If unsure, say Y. + config HW_RANDOM_ST tristate "ST Microelectronics HW Random Number Generator support" depends on HW_RANDOM && ARCH_STI diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index 7c9ef4a7667f..07ef1206f7a7 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_HW_RANDOM_POWERNV) += powernv-rng.o obj-$(CONFIG_HW_RANDOM_HISI) += hisi-rng.o obj-$(CONFIG_HW_RANDOM_BCM2835) += bcm2835-rng.o obj-$(CONFIG_HW_RANDOM_IPROC_RNG200) += iproc-rng200.o +obj-$(CONFIG_HW_RANDOM_MSM_LEGACY) += msm_rng.o obj-$(CONFIG_HW_RANDOM_ST) += st-rng.o obj-$(CONFIG_HW_RANDOM_XGENE) += xgene-rng.o obj-$(CONFIG_HW_RANDOM_STM32) += stm32-rng.o diff --git a/drivers/char/hw_random/msm_rng.c b/drivers/char/hw_random/msm_rng.c new file mode 100644 index 000000000000..a71d1c7967ef --- /dev/null +++ b/drivers/char/hw_random/msm_rng.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2013, 2015, 2017-2019 The Linux Foundation. All rights + * reserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "msm_rng" + +/* Device specific register offsets */ +#define PRNG_DATA_OUT_OFFSET 0x0000 +#define PRNG_STATUS_OFFSET 0x0004 +#define PRNG_LFSR_CFG_OFFSET 0x0100 +#define PRNG_CONFIG_OFFSET 0x0104 + +/* Device specific register masks and config values */ +#define PRNG_LFSR_CFG_MASK 0xFFFF0000 +#define PRNG_LFSR_CFG_CLOCKS 0x0000DDDD +#define PRNG_CONFIG_MASK 0xFFFFFFFD +#define PRNG_HW_ENABLE 0x00000002 + +#define MAX_HW_FIFO_DEPTH 16 /* FIFO is 16 words deep */ +#define MAX_HW_FIFO_SIZE (MAX_HW_FIFO_DEPTH * 4) /* FIFO is 32 bits wide */ + +#define RETRY_MAX_CNT 5 /* max retry times to read register */ +#define RETRY_DELAY_INTERVAL 440 /* retry delay interval in us */ + +struct msm_rng_device { + struct platform_device *pdev; + void __iomem *base; + struct clk *prng_clk; + struct mutex rng_lock; + struct icc_path *icc_path; +}; + +static struct msm_rng_device msm_rng_device_info; +static struct msm_rng_device *msm_rng_dev_cached; +static struct mutex cached_rng_lock; +static long msm_rng_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + + switch (cmd) { + case QRNG_IOCTL_RESET_BUS_BANDWIDTH: + pr_debug("calling msm_rng_bus_scale(LOW)\n"); + ret = icc_set_bw(msm_rng_device_info.icc_path, 0, 0); + if (ret) + pr_err("failed qrng_reset_bus_bw, ret = %ld\n", ret); + break; + default: + pr_err("Unsupported IOCTL call\n"); + break; + } + return ret; +} + +/* + * + * This function calls hardware random bit generator directory and retuns it + * back to caller + * + */ +static int msm_rng_direct_read(struct msm_rng_device *msm_rng_dev, + void *data, size_t max) +{ + struct platform_device *pdev; + void __iomem *base; + size_t currsize = 0; + u32 val = 0; + u32 *retdata = data; + int ret; + int failed = 0; + + pdev = msm_rng_dev->pdev; + base = msm_rng_dev->base; + + /* no room for word data */ + if (max < 4) + return 0; + + mutex_lock(&msm_rng_dev->rng_lock); + + if (msm_rng_dev->icc_path) { + ret = icc_set_bw(msm_rng_dev->icc_path, 0, 300000); + if (ret) { + pr_err("bus_scale_client_update_req failed\n"); + goto bus_err; + } + } + /* enable PRNG clock */ + if (msm_rng_dev->prng_clk) { + ret = clk_prepare_enable(msm_rng_dev->prng_clk); + if (ret) { + pr_err("failed to enable prng clock\n"); + goto err; + } + } + /* read random data from h/w */ + do { + /* check status bit if data is available */ + if (!(readl_relaxed(base + PRNG_STATUS_OFFSET) + & 0x00000001)) { + if (failed++ == RETRY_MAX_CNT) { + if (currsize == 0) + pr_err("Data not available\n"); + break; + } + udelay(RETRY_DELAY_INTERVAL); + } else { + + /* read FIFO */ + val = readl_relaxed(base + PRNG_DATA_OUT_OFFSET); + + /* write data back to callers pointer */ + *(retdata++) = val; + currsize += 4; + /* make sure we stay on 32bit boundary */ + if ((max - currsize) < 4) + break; + } + + } while (currsize < max); + + /* vote to turn off clock */ + if (msm_rng_dev->prng_clk) + clk_disable_unprepare(msm_rng_dev->prng_clk); +err: + if (msm_rng_dev->icc_path) { + ret = icc_set_bw(msm_rng_dev->icc_path, 0, 0); + if (ret) + pr_err("bus_scale_client_update_req failed\n"); + } +bus_err: + mutex_unlock(&msm_rng_dev->rng_lock); + + val = 0L; + return currsize; +} +static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + struct msm_rng_device *msm_rng_dev; + int rv = 0; + + msm_rng_dev = (struct msm_rng_device *)rng->priv; + rv = msm_rng_direct_read(msm_rng_dev, data, max); + + return rv; +} + + +static struct hwrng msm_rng = { + .name = DRIVER_NAME, + .read = msm_rng_read, + .quality = 1024, +}; + +static int msm_rng_enable_hw(struct msm_rng_device *msm_rng_dev) +{ + unsigned long val = 0; + unsigned long reg_val = 0; + int ret = 0; + + if (msm_rng_dev->icc_path) { + ret = icc_set_bw(msm_rng_dev->icc_path, 0, 30000); + if (ret) + pr_err("bus_scale_client_update_req failed\n"); + } + /* Enable the PRNG CLK */ + if (msm_rng_dev->prng_clk) { + ret = clk_prepare_enable(msm_rng_dev->prng_clk); + if (ret) { + dev_err(&(msm_rng_dev->pdev)->dev, + "failed to enable clock in probe\n"); + return -EPERM; + } + } + + /* Enable PRNG h/w only if it is NOT ON */ + val = readl_relaxed(msm_rng_dev->base + PRNG_CONFIG_OFFSET) & + PRNG_HW_ENABLE; + /* PRNG H/W is not ON */ + if (val != PRNG_HW_ENABLE) { + val = readl_relaxed(msm_rng_dev->base + PRNG_LFSR_CFG_OFFSET); + val &= PRNG_LFSR_CFG_MASK; + val |= PRNG_LFSR_CFG_CLOCKS; + writel_relaxed(val, msm_rng_dev->base + PRNG_LFSR_CFG_OFFSET); + + /* The PRNG CONFIG register should be first written */ + mb(); + + reg_val = readl_relaxed(msm_rng_dev->base + PRNG_CONFIG_OFFSET) + & PRNG_CONFIG_MASK; + reg_val |= PRNG_HW_ENABLE; + writel_relaxed(reg_val, msm_rng_dev->base + PRNG_CONFIG_OFFSET); + + /* The PRNG clk should be disabled only after we enable the + * PRNG h/w by writing to the PRNG CONFIG register. + */ + mb(); + } + if (msm_rng_dev->prng_clk) + clk_disable_unprepare(msm_rng_dev->prng_clk); + + if (msm_rng_dev->icc_path) { + ret = icc_set_bw(msm_rng_dev->icc_path, 0, 0); + if (ret) + pr_err("bus_scale_client_update_req failed\n"); + } + + return 0; +} + +static const struct file_operations msm_rng_fops = { + .unlocked_ioctl = msm_rng_ioctl, +}; +static struct class *msm_rng_class; +static struct cdev msm_rng_cdev; + +static int msm_rng_probe(struct platform_device *pdev) +{ + struct resource *res; + struct msm_rng_device *msm_rng_dev = NULL; + void __iomem *base = NULL; + bool configure_qrng = true; + int error = 0; + struct device *dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "invalid address\n"); + error = -EFAULT; + goto err_exit; + } + + msm_rng_dev = kzalloc(sizeof(struct msm_rng_device), GFP_KERNEL); + if (!msm_rng_dev) { + error = -ENOMEM; + goto err_exit; + } + + base = ioremap(res->start, resource_size(res)); + if (!base) { + dev_err(&pdev->dev, "ioremap failed\n"); + error = -ENOMEM; + goto err_iomap; + } + msm_rng_dev->base = base; + + /* create a handle for clock control */ + if (pdev->dev.of_node) { + if (of_property_read_bool(pdev->dev.of_node, + "qcom,no-clock-support")) + msm_rng_dev->prng_clk = NULL; + else + msm_rng_dev->prng_clk = clk_get(&pdev->dev, + "km_clk_src"); + } + + if (IS_ERR(msm_rng_dev->prng_clk)) { + dev_err(&pdev->dev, "failed to register clock source\n"); + error = -ENODEV; + goto err_clk_get; + } + + /* save away pdev and register driver data */ + msm_rng_dev->pdev = pdev; + platform_set_drvdata(pdev, msm_rng_dev); + + if (pdev->dev.of_node) { + msm_rng_dev->icc_path = of_icc_get(&pdev->dev, "data_path"); + msm_rng_device_info.icc_path = msm_rng_dev->icc_path; + if (IS_ERR(msm_rng_dev->icc_path)) { + error = PTR_ERR(msm_rng_dev->icc_path); + dev_err(&pdev->dev, "get icc path err %d\n", error); + goto err_icc_get; + } + } + + /* Enable rng h/w for the targets which can access the entire + * address space of PRNG. + */ + if ((pdev->dev.of_node) && (of_property_read_bool(pdev->dev.of_node, + "qcom,no-qrng-config"))) + configure_qrng = false; + if (configure_qrng) { + error = msm_rng_enable_hw(msm_rng_dev); + if (error) + goto err_icc_get; + } + + mutex_init(&msm_rng_dev->rng_lock); + mutex_init(&cached_rng_lock); + + /* register with hwrng framework */ + msm_rng.priv = (unsigned long) msm_rng_dev; + error = hwrng_register(&msm_rng); + if (error) { + dev_err(&pdev->dev, "failed to register hwrng\n"); + goto err_reg_hwrng; + } + error = register_chrdev(QRNG_IOC_MAGIC, DRIVER_NAME, &msm_rng_fops); + if (error) { + dev_err(&pdev->dev, "failed to register chrdev\n"); + goto err_reg_chrdev; + } + + msm_rng_class = class_create(THIS_MODULE, "msm-rng"); + if (IS_ERR(msm_rng_class)) { + pr_err("class_create failed\n"); + error = PTR_ERR(msm_rng_class); + goto err_create_cls; + } + + dev = device_create(msm_rng_class, NULL, MKDEV(QRNG_IOC_MAGIC, 0), + NULL, "msm-rng"); + if (IS_ERR(dev)) { + pr_err("Device create failed\n"); + error = PTR_ERR(dev); + goto err_create_dev; + } + cdev_init(&msm_rng_cdev, &msm_rng_fops); + msm_rng_dev_cached = msm_rng_dev; + return error; + +err_create_dev: + class_destroy(msm_rng_class); +err_create_cls: + unregister_chrdev(QRNG_IOC_MAGIC, DRIVER_NAME); +err_reg_chrdev: + hwrng_unregister(&msm_rng); +err_reg_hwrng: + if (msm_rng_dev->icc_path) + icc_put(msm_rng_dev->icc_path); +err_icc_get: + if (msm_rng_dev->prng_clk) + clk_put(msm_rng_dev->prng_clk); +err_clk_get: + iounmap(msm_rng_dev->base); +err_iomap: + kzfree(msm_rng_dev); +err_exit: + return error; +} + +static int msm_rng_remove(struct platform_device *pdev) +{ + struct msm_rng_device *msm_rng_dev = platform_get_drvdata(pdev); + + unregister_chrdev(QRNG_IOC_MAGIC, DRIVER_NAME); + hwrng_unregister(&msm_rng); + if (msm_rng_dev->prng_clk) + clk_put(msm_rng_dev->prng_clk); + iounmap(msm_rng_dev->base); + platform_set_drvdata(pdev, NULL); + if (msm_rng_dev->icc_path) + icc_put(msm_rng_dev->icc_path); + + kzfree(msm_rng_dev); + msm_rng_dev_cached = NULL; + return 0; +} + +static int qrng_get_random(struct crypto_rng *tfm, const u8 *src, + unsigned int slen, u8 *rdata, + unsigned int dlen) +{ + int sizeread = 0; + int rv = -EFAULT; + + if (!msm_rng_dev_cached) { + pr_err("%s: msm_rng_dev is not initialized\n", __func__); + rv = -ENODEV; + goto err_exit; + } + + if (!rdata) { + pr_err("%s: data buffer is null\n", __func__); + rv = -EINVAL; + goto err_exit; + } + + if (signal_pending(current) || + mutex_lock_interruptible(&cached_rng_lock)) { + pr_err("%s: mutex lock interrupted\n", __func__); + rv = -ERESTARTSYS; + goto err_exit; + } + sizeread = msm_rng_direct_read(msm_rng_dev_cached, rdata, dlen); + + if (sizeread == dlen) + rv = 0; + + mutex_unlock(&cached_rng_lock); +err_exit: + return rv; + +} + +static int qrng_reset(struct crypto_rng *tfm, const u8 *seed, unsigned int slen) +{ + return 0; +} + +static struct rng_alg rng_algs[] = { { + .generate = qrng_get_random, + .seed = qrng_reset, + .seedsize = 0, + .base = { + .cra_name = "qrng", + .cra_driver_name = "fips_hw_qrng", + .cra_priority = 300, + .cra_ctxsize = 0, + .cra_module = THIS_MODULE, + } +} }; + +static const struct of_device_id qrng_match[] = { + {.compatible = "qcom,msm-rng"}, + {}, +}; + +static struct platform_driver rng_driver = { + .probe = msm_rng_probe, + .remove = msm_rng_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = qrng_match, + }, +}; + +static int __init msm_rng_init(void) +{ + int ret; + + msm_rng_dev_cached = NULL; + ret = platform_driver_register(&rng_driver); + if (ret) { + pr_err("%s: platform_driver_register error:%d\n", + __func__, ret); + goto err_exit; + } + ret = crypto_register_rngs(rng_algs, ARRAY_SIZE(rng_algs)); + if (ret) { + pr_err("%s: crypto_register_algs error:%d\n", + __func__, ret); + goto err_exit; + } + +err_exit: + return ret; +} + +module_init(msm_rng_init); + +static void __exit msm_rng_exit(void) +{ + crypto_unregister_rngs(rng_algs, ARRAY_SIZE(rng_algs)); + platform_driver_unregister(&rng_driver); +} + +module_exit(msm_rng_exit); + +MODULE_DESCRIPTION("QTI MSM Random Number Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/uapi/linux/qrng.h b/include/uapi/linux/qrng.h new file mode 100644 index 000000000000..b999eeeebe41 --- /dev/null +++ b/include/uapi/linux/qrng.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +#ifndef _UAPI_QRNG_H_ +#define _UAPI_QRNG_H_ + +#include +#include + +#define QRNG_IOC_MAGIC 0x100 + +#define QRNG_IOCTL_RESET_BUS_BANDWIDTH\ + _IO(QRNG_IOC_MAGIC, 1) + +#endif /* _UAPI_QRNG_H_ */