diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-03 00:53:46 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-03 00:53:46 +0200 |
commit | 5a0387a8a8efb90ae7fea1e2e5c62de3efa74691 (patch) | |
tree | 9e5bbbafe7fea01c843d86c7c3d40f29f962c474 /drivers/char/hw_random | |
parent | Merge branch 'work.compat' of git://git.kernel.org/pub/scm/linux/kernel/git/v... (diff) | |
parent | crypto: stm32 - Fix OF module alias information (diff) | |
download | linux-5a0387a8a8efb90ae7fea1e2e5c62de3efa74691.tar.xz linux-5a0387a8a8efb90ae7fea1e2e5c62de3efa74691.zip |
Merge branch 'linus' of git://git.kernel.org/pub/scm/linux/kernel/git/herbert/crypto-2.6
Pull crypto updates from Herbert Xu:
"Here is the crypto update for 4.12:
API:
- Add batch registration for acomp/scomp
- Change acomp testing to non-unique compressed result
- Extend algorithm name limit to 128 bytes
- Require setkey before accept(2) in algif_aead
Algorithms:
- Add support for deflate rfc1950 (zlib)
Drivers:
- Add accelerated crct10dif for powerpc
- Add crc32 in stm32
- Add sha384/sha512 in ccp
- Add 3des/gcm(aes) for v5 devices in ccp
- Add Queue Interface (QI) backend support in caam
- Add new Exynos RNG driver
- Add ThunderX ZIP driver
- Add driver for hardware random generator on MT7623 SoC"
* 'linus' of git://git.kernel.org/pub/scm/linux/kernel/git/herbert/crypto-2.6: (101 commits)
crypto: stm32 - Fix OF module alias information
crypto: algif_aead - Require setkey before accept(2)
crypto: scomp - add support for deflate rfc1950 (zlib)
crypto: scomp - allow registration of multiple scomps
crypto: ccp - Change ISR handler method for a v5 CCP
crypto: ccp - Change ISR handler method for a v3 CCP
crypto: crypto4xx - rename ce_ring_contol to ce_ring_control
crypto: testmgr - Allow ecb(cipher_null) in FIPS mode
Revert "crypto: arm64/sha - Add constant operand modifier to ASM_EXPORT"
crypto: ccp - Disable interrupts early on unload
crypto: ccp - Use only the relevant interrupt bits
hwrng: mtk - Add driver for hardware random generator on MT7623 SoC
dt-bindings: hwrng: Add Mediatek hardware random generator bindings
crypto: crct10dif-vpmsum - Fix missing preempt_disable()
crypto: testmgr - replace compression known answer test
crypto: acomp - allow registration of multiple acomps
hwrng: n2 - Use devm_kcalloc() in n2rng_probe()
crypto: chcr - Fix error handling related to 'chcr_alloc_shash'
padata: get_next is never NULL
crypto: exynos - Add new Exynos RNG driver
...
Diffstat (limited to 'drivers/char/hw_random')
-rw-r--r-- | drivers/char/hw_random/Kconfig | 28 | ||||
-rw-r--r-- | drivers/char/hw_random/Makefile | 2 | ||||
-rw-r--r-- | drivers/char/hw_random/exynos-rng.c | 231 | ||||
-rw-r--r-- | drivers/char/hw_random/meson-rng.c | 22 | ||||
-rw-r--r-- | drivers/char/hw_random/mtk-rng.c | 168 | ||||
-rw-r--r-- | drivers/char/hw_random/n2-drv.c | 4 | ||||
-rw-r--r-- | drivers/char/hw_random/omap-rng.c | 22 | ||||
-rw-r--r-- | drivers/char/hw_random/timeriomem-rng.c | 157 |
8 files changed, 298 insertions, 336 deletions
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index b9918fb9587d..1b223c32a8ae 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -294,20 +294,6 @@ config HW_RANDOM_POWERNV If unsure, say Y. -config HW_RANDOM_EXYNOS - tristate "EXYNOS HW random number generator support" - depends on ARCH_EXYNOS || COMPILE_TEST - depends on HAS_IOMEM - default HW_RANDOM - ---help--- - This driver provides kernel-side support for the Random Number - Generator hardware found on EXYNOS SOCs. - - To compile this driver as a module, choose M here: the - module will be called exynos-rng. - - If unsure, say Y. - config HW_RANDOM_TPM tristate "TPM HW Random Number Generator support" depends on TCG_TPM @@ -423,6 +409,20 @@ config HW_RANDOM_CAVIUM If unsure, say Y. +config HW_RANDOM_MTK + tristate "Mediatek Random Number Generator support" + depends on HW_RANDOM + depends on ARCH_MEDIATEK || COMPILE_TEST + default y + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Mediatek SoCs. + + To compile this driver as a module, choose M here. the + module will be called mtk-rng. + + If unsure, say Y. + config HW_RANDOM_S390 tristate "S390 True Random Number Generator support" depends on S390 diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index dd1765246255..b085975ec1d2 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -24,7 +24,6 @@ obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o obj-$(CONFIG_HW_RANDOM_POWERNV) += powernv-rng.o -obj-$(CONFIG_HW_RANDOM_EXYNOS) += exynos-rng.o obj-$(CONFIG_HW_RANDOM_HISI) += hisi-rng.o obj-$(CONFIG_HW_RANDOM_TPM) += tpm-rng.o obj-$(CONFIG_HW_RANDOM_BCM2835) += bcm2835-rng.o @@ -36,4 +35,5 @@ obj-$(CONFIG_HW_RANDOM_STM32) += stm32-rng.o obj-$(CONFIG_HW_RANDOM_PIC32) += pic32-rng.o obj-$(CONFIG_HW_RANDOM_MESON) += meson-rng.o obj-$(CONFIG_HW_RANDOM_CAVIUM) += cavium-rng.o cavium-rng-vf.o +obj-$(CONFIG_HW_RANDOM_MTK) += mtk-rng.o obj-$(CONFIG_HW_RANDOM_S390) += s390-trng.o diff --git a/drivers/char/hw_random/exynos-rng.c b/drivers/char/hw_random/exynos-rng.c deleted file mode 100644 index 23d358553b21..000000000000 --- a/drivers/char/hw_random/exynos-rng.c +++ /dev/null @@ -1,231 +0,0 @@ -/* - * exynos-rng.c - Random Number Generator driver for the exynos - * - * Copyright (C) 2012 Samsung Electronics - * Jonghwa Lee <jonghwa3.lee@samsung.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include <linux/hw_random.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/io.h> -#include <linux/platform_device.h> -#include <linux/clk.h> -#include <linux/pm_runtime.h> -#include <linux/err.h> - -#define EXYNOS_PRNG_STATUS_OFFSET 0x10 -#define EXYNOS_PRNG_SEED_OFFSET 0x140 -#define EXYNOS_PRNG_OUT1_OFFSET 0x160 -#define SEED_SETTING_DONE BIT(1) -#define PRNG_START 0x18 -#define PRNG_DONE BIT(5) -#define EXYNOS_AUTOSUSPEND_DELAY 100 - -struct exynos_rng { - struct device *dev; - struct hwrng rng; - void __iomem *mem; - struct clk *clk; -}; - -static u32 exynos_rng_readl(struct exynos_rng *rng, u32 offset) -{ - return readl_relaxed(rng->mem + offset); -} - -static void exynos_rng_writel(struct exynos_rng *rng, u32 val, u32 offset) -{ - writel_relaxed(val, rng->mem + offset); -} - -static int exynos_rng_configure(struct exynos_rng *exynos_rng) -{ - int i; - int ret = 0; - - for (i = 0 ; i < 5 ; i++) - exynos_rng_writel(exynos_rng, jiffies, - EXYNOS_PRNG_SEED_OFFSET + 4*i); - - if (!(exynos_rng_readl(exynos_rng, EXYNOS_PRNG_STATUS_OFFSET) - & SEED_SETTING_DONE)) - ret = -EIO; - - return ret; -} - -static int exynos_init(struct hwrng *rng) -{ - struct exynos_rng *exynos_rng = container_of(rng, - struct exynos_rng, rng); - int ret = 0; - - pm_runtime_get_sync(exynos_rng->dev); - ret = exynos_rng_configure(exynos_rng); - pm_runtime_mark_last_busy(exynos_rng->dev); - pm_runtime_put_autosuspend(exynos_rng->dev); - - return ret; -} - -static int exynos_read(struct hwrng *rng, void *buf, - size_t max, bool wait) -{ - struct exynos_rng *exynos_rng = container_of(rng, - struct exynos_rng, rng); - u32 *data = buf; - int retry = 100; - int ret = 4; - - pm_runtime_get_sync(exynos_rng->dev); - - exynos_rng_writel(exynos_rng, PRNG_START, 0); - - while (!(exynos_rng_readl(exynos_rng, - EXYNOS_PRNG_STATUS_OFFSET) & PRNG_DONE) && --retry) - cpu_relax(); - if (!retry) { - ret = -ETIMEDOUT; - goto out; - } - - exynos_rng_writel(exynos_rng, PRNG_DONE, EXYNOS_PRNG_STATUS_OFFSET); - - *data = exynos_rng_readl(exynos_rng, EXYNOS_PRNG_OUT1_OFFSET); - -out: - pm_runtime_mark_last_busy(exynos_rng->dev); - pm_runtime_put_sync_autosuspend(exynos_rng->dev); - - return ret; -} - -static int exynos_rng_probe(struct platform_device *pdev) -{ - struct exynos_rng *exynos_rng; - struct resource *res; - int ret; - - exynos_rng = devm_kzalloc(&pdev->dev, sizeof(struct exynos_rng), - GFP_KERNEL); - if (!exynos_rng) - return -ENOMEM; - - exynos_rng->dev = &pdev->dev; - exynos_rng->rng.name = "exynos"; - exynos_rng->rng.init = exynos_init; - exynos_rng->rng.read = exynos_read; - exynos_rng->clk = devm_clk_get(&pdev->dev, "secss"); - if (IS_ERR(exynos_rng->clk)) { - dev_err(&pdev->dev, "Couldn't get clock.\n"); - return -ENOENT; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - exynos_rng->mem = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(exynos_rng->mem)) - return PTR_ERR(exynos_rng->mem); - - platform_set_drvdata(pdev, exynos_rng); - - pm_runtime_set_autosuspend_delay(&pdev->dev, EXYNOS_AUTOSUSPEND_DELAY); - pm_runtime_use_autosuspend(&pdev->dev); - pm_runtime_enable(&pdev->dev); - - ret = devm_hwrng_register(&pdev->dev, &exynos_rng->rng); - if (ret) { - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_disable(&pdev->dev); - } - - return ret; -} - -static int exynos_rng_remove(struct platform_device *pdev) -{ - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_disable(&pdev->dev); - - return 0; -} - -static int __maybe_unused exynos_rng_runtime_suspend(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct exynos_rng *exynos_rng = platform_get_drvdata(pdev); - - clk_disable_unprepare(exynos_rng->clk); - - return 0; -} - -static int __maybe_unused exynos_rng_runtime_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct exynos_rng *exynos_rng = platform_get_drvdata(pdev); - - return clk_prepare_enable(exynos_rng->clk); -} - -static int __maybe_unused exynos_rng_suspend(struct device *dev) -{ - return pm_runtime_force_suspend(dev); -} - -static int __maybe_unused exynos_rng_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct exynos_rng *exynos_rng = platform_get_drvdata(pdev); - int ret; - - ret = pm_runtime_force_resume(dev); - if (ret) - return ret; - - return exynos_rng_configure(exynos_rng); -} - -static const struct dev_pm_ops exynos_rng_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(exynos_rng_suspend, exynos_rng_resume) - SET_RUNTIME_PM_OPS(exynos_rng_runtime_suspend, - exynos_rng_runtime_resume, NULL) -}; - -static const struct of_device_id exynos_rng_dt_match[] = { - { - .compatible = "samsung,exynos4-rng", - }, - { }, -}; -MODULE_DEVICE_TABLE(of, exynos_rng_dt_match); - -static struct platform_driver exynos_rng_driver = { - .driver = { - .name = "exynos-rng", - .pm = &exynos_rng_pm_ops, - .of_match_table = exynos_rng_dt_match, - }, - .probe = exynos_rng_probe, - .remove = exynos_rng_remove, -}; - -module_platform_driver(exynos_rng_driver); - -MODULE_DESCRIPTION("EXYNOS 4 H/W Random Number Generator driver"); -MODULE_AUTHOR("Jonghwa Lee <jonghwa3.lee@samsung.com>"); -MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/meson-rng.c b/drivers/char/hw_random/meson-rng.c index 119d698439ae..2e23be802a62 100644 --- a/drivers/char/hw_random/meson-rng.c +++ b/drivers/char/hw_random/meson-rng.c @@ -62,6 +62,7 @@ #include <linux/slab.h> #include <linux/types.h> #include <linux/of.h> +#include <linux/clk.h> #define RNG_DATA 0x00 @@ -69,6 +70,7 @@ struct meson_rng_data { void __iomem *base; struct platform_device *pdev; struct hwrng rng; + struct clk *core_clk; }; static int meson_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) @@ -81,11 +83,17 @@ static int meson_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) return sizeof(u32); } +static void meson_rng_clk_disable(void *data) +{ + clk_disable_unprepare(data); +} + static int meson_rng_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct meson_rng_data *data; struct resource *res; + int ret; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -98,6 +106,20 @@ static int meson_rng_probe(struct platform_device *pdev) if (IS_ERR(data->base)) return PTR_ERR(data->base); + data->core_clk = devm_clk_get(dev, "core"); + if (IS_ERR(data->core_clk)) + data->core_clk = NULL; + + if (data->core_clk) { + ret = clk_prepare_enable(data->core_clk); + if (ret) + return ret; + ret = devm_add_action_or_reset(dev, meson_rng_clk_disable, + data->core_clk); + if (ret) + return ret; + } + data->rng.name = pdev->name; data->rng.read = meson_rng_read; diff --git a/drivers/char/hw_random/mtk-rng.c b/drivers/char/hw_random/mtk-rng.c new file mode 100644 index 000000000000..df8eb54fd5a3 --- /dev/null +++ b/drivers/char/hw_random/mtk-rng.c @@ -0,0 +1,168 @@ +/* + * Driver for Mediatek Hardware Random Number Generator + * + * Copyright (C) 2017 Sean Wang <sean.wang@mediatek.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define MTK_RNG_DEV KBUILD_MODNAME + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define USEC_POLL 2 +#define TIMEOUT_POLL 20 + +#define RNG_CTRL 0x00 +#define RNG_EN BIT(0) +#define RNG_READY BIT(31) + +#define RNG_DATA 0x08 + +#define to_mtk_rng(p) container_of(p, struct mtk_rng, rng) + +struct mtk_rng { + void __iomem *base; + struct clk *clk; + struct hwrng rng; +}; + +static int mtk_rng_init(struct hwrng *rng) +{ + struct mtk_rng *priv = to_mtk_rng(rng); + u32 val; + int err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + + val = readl(priv->base + RNG_CTRL); + val |= RNG_EN; + writel(val, priv->base + RNG_CTRL); + + return 0; +} + +static void mtk_rng_cleanup(struct hwrng *rng) +{ + struct mtk_rng *priv = to_mtk_rng(rng); + u32 val; + + val = readl(priv->base + RNG_CTRL); + val &= ~RNG_EN; + writel(val, priv->base + RNG_CTRL); + + clk_disable_unprepare(priv->clk); +} + +static bool mtk_rng_wait_ready(struct hwrng *rng, bool wait) +{ + struct mtk_rng *priv = to_mtk_rng(rng); + int ready; + + ready = readl(priv->base + RNG_CTRL) & RNG_READY; + if (!ready && wait) + readl_poll_timeout_atomic(priv->base + RNG_CTRL, ready, + ready & RNG_READY, USEC_POLL, + TIMEOUT_POLL); + return !!ready; +} + +static int mtk_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct mtk_rng *priv = to_mtk_rng(rng); + int retval = 0; + + while (max >= sizeof(u32)) { + if (!mtk_rng_wait_ready(rng, wait)) + break; + + *(u32 *)buf = readl(priv->base + RNG_DATA); + retval += sizeof(u32); + buf += sizeof(u32); + max -= sizeof(u32); + } + + return retval || !wait ? retval : -EIO; +} + +static int mtk_rng_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + struct mtk_rng *priv; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no iomem resource\n"); + return -ENXIO; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->rng.name = pdev->name; + priv->rng.init = mtk_rng_init; + priv->rng.cleanup = mtk_rng_cleanup; + priv->rng.read = mtk_rng_read; + + priv->clk = devm_clk_get(&pdev->dev, "rng"); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + dev_err(&pdev->dev, "no clock for device: %d\n", ret); + return ret; + } + + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + ret = devm_hwrng_register(&pdev->dev, &priv->rng); + if (ret) { + dev_err(&pdev->dev, "failed to register rng device: %d\n", + ret); + return ret; + } + + dev_info(&pdev->dev, "registered RNG driver\n"); + + return 0; +} + +static const struct of_device_id mtk_rng_match[] = { + { .compatible = "mediatek,mt7623-rng" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_rng_match); + +static struct platform_driver mtk_rng_driver = { + .probe = mtk_rng_probe, + .driver = { + .name = MTK_RNG_DEV, + .of_match_table = mtk_rng_match, + }, +}; + +module_platform_driver(mtk_rng_driver); + +MODULE_DESCRIPTION("Mediatek Random Number Generator Driver"); +MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/n2-drv.c b/drivers/char/hw_random/n2-drv.c index 31cbdbbaebfc..92dd4e925315 100644 --- a/drivers/char/hw_random/n2-drv.c +++ b/drivers/char/hw_random/n2-drv.c @@ -748,9 +748,7 @@ static int n2rng_probe(struct platform_device *op) dev_info(&op->dev, "Registered RNG HVAPI major %lu minor %lu\n", np->hvapi_major, np->hvapi_minor); - - np->units = devm_kzalloc(&op->dev, - sizeof(struct n2rng_unit) * np->num_units, + np->units = devm_kcalloc(&op->dev, np->num_units, sizeof(*np->units), GFP_KERNEL); err = -ENOMEM; if (!np->units) diff --git a/drivers/char/hw_random/omap-rng.c b/drivers/char/hw_random/omap-rng.c index b1ad12552b56..74d11ae6abe9 100644 --- a/drivers/char/hw_random/omap-rng.c +++ b/drivers/char/hw_random/omap-rng.c @@ -398,16 +398,6 @@ static int of_get_omap_rng_device_details(struct omap_rng_dev *priv, return err; } - priv->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(priv->clk) && PTR_ERR(priv->clk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - if (!IS_ERR(priv->clk)) { - err = clk_prepare_enable(priv->clk); - if (err) - dev_err(&pdev->dev, "unable to enable the clk, " - "err = %d\n", err); - } - /* * On OMAP4, enabling the shutdown_oflo interrupt is * done in the interrupt mask register. There is no @@ -478,6 +468,18 @@ static int omap_rng_probe(struct platform_device *pdev) goto err_ioremap; } + priv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->clk) && PTR_ERR(priv->clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (!IS_ERR(priv->clk)) { + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(&pdev->dev, + "Unable to enable the clk: %d\n", ret); + goto err_register; + } + } + ret = (dev->of_node) ? of_get_omap_rng_device_details(priv, pdev) : get_omap_rng_device_details(priv); if (ret) diff --git a/drivers/char/hw_random/timeriomem-rng.c b/drivers/char/hw_random/timeriomem-rng.c index cf37db263ecd..a0faa5f05deb 100644 --- a/drivers/char/hw_random/timeriomem-rng.c +++ b/drivers/char/hw_random/timeriomem-rng.c @@ -20,84 +20,100 @@ * TODO: add support for reading sizes other than 32bits and masking */ -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/platform_device.h> -#include <linux/of.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/hrtimer.h> #include <linux/hw_random.h> #include <linux/io.h> +#include <linux/ktime.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> #include <linux/slab.h> +#include <linux/time.h> #include <linux/timeriomem-rng.h> -#include <linux/jiffies.h> -#include <linux/sched.h> -#include <linux/timer.h> -#include <linux/completion.h> -struct timeriomem_rng_private_data { +struct timeriomem_rng_private { void __iomem *io_base; - unsigned int expires; - unsigned int period; + ktime_t period; unsigned int present:1; - struct timer_list timer; + struct hrtimer timer; struct completion completion; - struct hwrng timeriomem_rng_ops; + struct hwrng rng_ops; }; -#define to_rng_priv(rng) \ - ((struct timeriomem_rng_private_data *)rng->priv) - -/* - * have data return 1, however return 0 if we have nothing - */ -static int timeriomem_rng_data_present(struct hwrng *rng, int wait) +static int timeriomem_rng_read(struct hwrng *hwrng, void *data, + size_t max, bool wait) { - struct timeriomem_rng_private_data *priv = to_rng_priv(rng); - - if (!wait || priv->present) - return priv->present; + struct timeriomem_rng_private *priv = + container_of(hwrng, struct timeriomem_rng_private, rng_ops); + int retval = 0; + int period_us = ktime_to_us(priv->period); + + /* + * The RNG provides 32-bits per read. Ensure there is enough space for + * at minimum one read. + */ + if (max < sizeof(u32)) + return 0; + + /* + * There may not have been enough time for new data to be generated + * since the last request. If the caller doesn't want to wait, let them + * bail out. Otherwise, wait for the completion. If the new data has + * already been generated, the completion should already be available. + */ + if (!wait && !priv->present) + return 0; wait_for_completion(&priv->completion); - return 1; -} - -static int timeriomem_rng_data_read(struct hwrng *rng, u32 *data) -{ - struct timeriomem_rng_private_data *priv = to_rng_priv(rng); - unsigned long cur; - s32 delay; - - *data = readl(priv->io_base); - - cur = jiffies; - - delay = cur - priv->expires; - delay = priv->period - (delay % priv->period); - - priv->expires = cur + delay; + do { + /* + * After the first read, all additional reads will need to wait + * for the RNG to generate new data. Since the period can have + * a wide range of values (1us to 1s have been observed), allow + * for 1% tolerance in the sleep time rather than a fixed value. + */ + if (retval > 0) + usleep_range(period_us, + period_us + min(1, period_us / 100)); + + *(u32 *)data = readl(priv->io_base); + retval += sizeof(u32); + data += sizeof(u32); + max -= sizeof(u32); + } while (wait && max > sizeof(u32)); + + /* + * Block any new callers until the RNG has had time to generate new + * data. + */ priv->present = 0; - reinit_completion(&priv->completion); - mod_timer(&priv->timer, priv->expires); + hrtimer_forward_now(&priv->timer, priv->period); + hrtimer_restart(&priv->timer); - return 4; + return retval; } -static void timeriomem_rng_trigger(unsigned long data) +static enum hrtimer_restart timeriomem_rng_trigger(struct hrtimer *timer) { - struct timeriomem_rng_private_data *priv - = (struct timeriomem_rng_private_data *)data; + struct timeriomem_rng_private *priv + = container_of(timer, struct timeriomem_rng_private, timer); priv->present = 1; complete(&priv->completion); + + return HRTIMER_NORESTART; } static int timeriomem_rng_probe(struct platform_device *pdev) { struct timeriomem_rng_data *pdata = pdev->dev.platform_data; - struct timeriomem_rng_private_data *priv; + struct timeriomem_rng_private *priv; struct resource *res; int err = 0; int period; @@ -119,7 +135,7 @@ static int timeriomem_rng_probe(struct platform_device *pdev) /* Allocate memory for the device structure (and zero it) */ priv = devm_kzalloc(&pdev->dev, - sizeof(struct timeriomem_rng_private_data), GFP_KERNEL); + sizeof(struct timeriomem_rng_private), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -139,54 +155,41 @@ static int timeriomem_rng_probe(struct platform_device *pdev) period = pdata->period; } - priv->period = usecs_to_jiffies(period); - if (priv->period < 1) { - dev_err(&pdev->dev, "period is less than one jiffy\n"); - return -EINVAL; - } - - priv->expires = jiffies; - priv->present = 1; - + priv->period = ns_to_ktime(period * NSEC_PER_USEC); init_completion(&priv->completion); - complete(&priv->completion); + hrtimer_init(&priv->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + priv->timer.function = timeriomem_rng_trigger; - setup_timer(&priv->timer, timeriomem_rng_trigger, (unsigned long)priv); - - priv->timeriomem_rng_ops.name = dev_name(&pdev->dev); - priv->timeriomem_rng_ops.data_present = timeriomem_rng_data_present; - priv->timeriomem_rng_ops.data_read = timeriomem_rng_data_read; - priv->timeriomem_rng_ops.priv = (unsigned long)priv; + priv->rng_ops.name = dev_name(&pdev->dev); + priv->rng_ops.read = timeriomem_rng_read; priv->io_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->io_base)) { - err = PTR_ERR(priv->io_base); - goto out_timer; + return PTR_ERR(priv->io_base); } - err = hwrng_register(&priv->timeriomem_rng_ops); + /* Assume random data is already available. */ + priv->present = 1; + complete(&priv->completion); + + err = hwrng_register(&priv->rng_ops); if (err) { dev_err(&pdev->dev, "problem registering\n"); - goto out_timer; + return err; } dev_info(&pdev->dev, "32bits from 0x%p @ %dus\n", priv->io_base, period); return 0; - -out_timer: - del_timer_sync(&priv->timer); - return err; } static int timeriomem_rng_remove(struct platform_device *pdev) { - struct timeriomem_rng_private_data *priv = platform_get_drvdata(pdev); - - hwrng_unregister(&priv->timeriomem_rng_ops); + struct timeriomem_rng_private *priv = platform_get_drvdata(pdev); - del_timer_sync(&priv->timer); + hwrng_unregister(&priv->rng_ops); + hrtimer_cancel(&priv->timer); return 0; } |