diff options
Diffstat (limited to 'drivers/hw_random')
-rw-r--r-- | drivers/hw_random/Kconfig | 63 | ||||
-rw-r--r-- | drivers/hw_random/Makefile | 9 | ||||
-rw-r--r-- | drivers/hw_random/atmel-rng.c | 165 | ||||
-rw-r--r-- | drivers/hw_random/bcm2835-rng.c | 199 | ||||
-rw-r--r-- | drivers/hw_random/core.c | 19 | ||||
-rw-r--r-- | drivers/hw_random/dev-random.c | 6 | ||||
-rw-r--r-- | drivers/hw_random/efi-rng.c | 53 | ||||
-rw-r--r-- | drivers/hw_random/iproc-rng200.c | 220 | ||||
-rw-r--r-- | drivers/hw_random/mxc-rngc.c | 23 | ||||
-rw-r--r-- | drivers/hw_random/omap-rng.c | 436 | ||||
-rw-r--r-- | drivers/hw_random/optee-rng.c | 302 | ||||
-rw-r--r-- | drivers/hw_random/rockchip-rng.c | 259 | ||||
-rw-r--r-- | drivers/hw_random/starfive-vic-rng.c | 9 | ||||
-rw-r--r-- | drivers/hw_random/stm32-rng.c | 15 | ||||
-rw-r--r-- | drivers/hw_random/timeriomem-rng.c | 145 |
15 files changed, 1877 insertions, 46 deletions
diff --git a/drivers/hw_random/Kconfig b/drivers/hw_random/Kconfig index 764911f4d3..763929f7d6 100644 --- a/drivers/hw_random/Kconfig +++ b/drivers/hw_random/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menuconfig HWRNG bool "HWRNG Support" help @@ -7,16 +8,23 @@ menuconfig HWRNG if HWRNG +config HW_RANDOM_TIMERIOMEM + tristate "Timer IOMEM HW Random Number Generator support" + help + This driver provides barebox support for a generic Random + Number Generator used by reading a 'dumb' iomem address that + is to be read no faster than, for example, once a second. + config HWRNG_MXC_RNGC tristate "Freescale i.MX RNGC Random Number Generator" depends on ARCH_IMX25 || ARCH_IMX35 || ARCH_IMX53 || COMPILE_TEST help - This driver provides kernel-side support for the Random Number + This driver provides barebox support for the Random Number Generator hardware found on some Freescale i.MX processors. config HWRNG_STM32 tristate "STM32 Random Number Generator" - depends on ARCH_STM32MP || COMPILE_TEST + depends on ARCH_STM32 || COMPILE_TEST help This driver provides barebox support for the Random Number Generator hardware found on the STM32 family of MPUs and MCUs. @@ -43,4 +51,55 @@ config HW_RANDOM_STARFIVE This driver provides barebox support for the Random Number Generator hardware found on the StarFive family of SoCs. +config HW_RANDOM_EFI + tristate "EFI Random Number Generator" + depends on EFI + help + This driver provides barebox support for the Random Number + Generator Protocol offered by EFI firmware + +config HW_RANDOM_OPTEE + tristate "OP-TEE based Random Number Generator support" + depends on OPTEE + help + This driver provides support for OP-TEE based Random Number + Generator on ARM SoCs where hardware entropy sources are not + accessible to normal world (barebox and e.g. Linux after it). + +config HW_RANDOM_ATMEL + tristate "Atmel Random Number Generator support" + depends on ARCH_AT91 || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on Atmel AT91 devices. + +config HW_RANDOM_BCM2835 + tristate "Broadcom BCM2835/BCM63xx Random Number Generator support" + depends on ARCH_BCM283X || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on the Broadcom BCM2835 SoCs. + +config HW_RANDOM_IPROC_RNG200 + tristate "Broadcom iProc/STB RNG200 support" + depends on ARCH_BCM283X || COMPILE_TEST + help + This driver provides barebox support for the RNG200 + hardware found on the BCM2711. + +config HW_RANDOM_ROCKCHIP + tristate "Rockchip Random Number Generator support" + depends on ARCH_ROCKCHIP || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on Rockchip cpus. + +config HW_RANDOM_OMAP + tristate "OMAP Random Number Generator support" + depends on ARCH_OMAP || ARCH_K3 || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on OMAP2/3/4/5, AM33xx/AM43xx + multimedia processors, and Marvell Armada 7k/8k SoCs. + endif diff --git a/drivers/hw_random/Makefile b/drivers/hw_random/Makefile index 4cf33d2d93..7f65a6c41e 100644 --- a/drivers/hw_random/Makefile +++ b/drivers/hw_random/Makefile @@ -1,6 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_HWRNG) += core.o obj-$(CONFIG_HWRNG_MXC_RNGC) += mxc-rngc.o obj-$(CONFIG_HWRNG_STM32) += stm32-rng.o obj-$(CONFIG_HWRNG_DEV_RANDOM) += dev-random.o obj-$(CONFIG_HW_RANDOM_VIRTIO) += virtio-rng.o obj-$(CONFIG_HW_RANDOM_STARFIVE) += starfive-vic-rng.o +obj-$(CONFIG_HW_RANDOM_EFI) += efi-rng.o +obj-$(CONFIG_HW_RANDOM_OPTEE) += optee-rng.o +obj-$(CONFIG_HW_RANDOM_ATMEL) += atmel-rng.o +obj-$(CONFIG_HW_RANDOM_BCM2835) += bcm2835-rng.o +obj-$(CONFIG_HW_RANDOM_IPROC_RNG200) += iproc-rng200.o +obj-$(CONFIG_HW_RANDOM_ROCKCHIP) += rockchip-rng.o +obj-$(CONFIG_HW_RANDOM_TIMERIOMEM) += timeriomem-rng.o +obj-$(CONFIG_HW_RANDOM_OMAP) += omap-rng.o diff --git a/drivers/hw_random/atmel-rng.c b/drivers/hw_random/atmel-rng.c new file mode 100644 index 0000000000..bdd2139b08 --- /dev/null +++ b/drivers/hw_random/atmel-rng.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2011 Peter Korsgaard <jacmet@sunsite.dk> + +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/hw_random.h> +#include <of.h> +#include <linux/device.h> + +#define TRNG_CR 0x00 +#define TRNG_MR 0x04 +#define TRNG_ISR 0x1c +#define TRNG_ISR_DATRDY BIT(0) +#define TRNG_ODATA 0x50 + +#define TRNG_KEY 0x524e4700 /* RNG */ + +#define TRNG_HALFR BIT(0) /* generate RN every 168 cycles */ + +struct atmel_trng_data { + bool has_half_rate; +}; + +struct atmel_trng { + struct clk *clk; + void __iomem *base; + struct hwrng rng; + bool has_half_rate; +}; + +static bool atmel_trng_wait_ready(struct atmel_trng *trng, bool wait) +{ + int ready; + + ready = readl(trng->base + TRNG_ISR) & TRNG_ISR_DATRDY; + if (!ready && wait) + readl_poll_timeout(trng->base + TRNG_ISR, ready, + ready & TRNG_ISR_DATRDY, 20000); + + return !!ready; +} + +static int atmel_trng_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng); + u32 *data = buf; + int ret; + + ret = atmel_trng_wait_ready(trng, wait); + if (!ret) + return 0; + + *data = readl(trng->base + TRNG_ODATA); + /* + * ensure data ready is only set again AFTER the next data word is ready + * in case it got set between checking ISR and reading ODATA, so we + * don't risk re-reading the same word + */ + readl(trng->base + TRNG_ISR); + ret = 4; + + return ret; +} + +static int atmel_trng_init(struct hwrng *rng) +{ + struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng); + unsigned long rate; + int ret; + + ret = clk_prepare_enable(trng->clk); + if (ret) + return ret; + + if (trng->has_half_rate) { + rate = clk_get_rate(trng->clk); + + /* if peripheral clk is above 100MHz, set HALFR */ + if (rate > 100000000) + writel(TRNG_HALFR, trng->base + TRNG_MR); + } + + writel(TRNG_KEY | 1, trng->base + TRNG_CR); + + return 0; +} + +static void atmel_trng_cleanup(struct atmel_trng *trng) +{ + writel(TRNG_KEY, trng->base + TRNG_CR); + clk_disable_unprepare(trng->clk); +} + +static int atmel_trng_probe(struct device *dev) +{ + struct atmel_trng *trng; + const struct atmel_trng_data *data; + + trng = devm_kzalloc(dev, sizeof(*trng), GFP_KERNEL); + if (!trng) + return -ENOMEM; + + trng->base = dev_platform_ioremap_resource(dev, 0); + if (IS_ERR(trng->base)) + return PTR_ERR(trng->base); + + trng->clk = clk_get(dev, NULL); + if (IS_ERR(trng->clk)) + return PTR_ERR(trng->clk); + data = device_get_match_data(dev); + if (!data) + return -ENODEV; + + trng->has_half_rate = data->has_half_rate; + trng->rng.name = dev_name(dev); + trng->rng.read = atmel_trng_read; + trng->rng.init = atmel_trng_init; + dev->priv = trng; + + return hwrng_register(dev, &trng->rng); +} + +static void atmel_trng_remove(struct device *dev) +{ + atmel_trng_cleanup(dev->priv); +} + +static const struct atmel_trng_data at91sam9g45_config = { + .has_half_rate = false, +}; + +static const struct atmel_trng_data sam9x60_config = { + .has_half_rate = true, +}; + +static const struct of_device_id atmel_trng_dt_ids[] = { + { + .compatible = "atmel,at91sam9g45-trng", + .data = &at91sam9g45_config, + }, { + .compatible = "microchip,sam9x60-trng", + .data = &sam9x60_config, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, atmel_trng_dt_ids); + +static struct driver atmel_trng_driver = { + .name = "atmel-trng", + .probe = atmel_trng_probe, + .remove = atmel_trng_remove, + .of_match_table = atmel_trng_dt_ids, +}; +device_platform_driver(atmel_trng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>"); +MODULE_DESCRIPTION("Atmel true random number generator driver"); diff --git a/drivers/hw_random/bcm2835-rng.c b/drivers/hw_random/bcm2835-rng.c new file mode 100644 index 0000000000..d82331c950 --- /dev/null +++ b/drivers/hw_random/bcm2835-rng.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2010-2012 Broadcom. All rights reserved. + * Copyright (c) 2013 Lubomir Rintel + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/reset.h> +#include <of.h> +#include <linux/device.h> +#include <linux/printk.h> +#include <linux/clk.h> +#include <linux/reset.h> + +#define RNG_CTRL 0x0 +#define RNG_STATUS 0x4 +#define RNG_DATA 0x8 +#define RNG_INT_MASK 0x10 + +/* enable rng */ +#define RNG_RBGEN 0x1 + +/* the initial numbers generated are "less random" so will be discarded */ +#define RNG_WARMUP_COUNT 0x40000 + +#define RNG_INT_OFF 0x1 + +struct bcm2835_rng_priv { + struct hwrng rng; + void __iomem *base; + bool mask_interrupts; + struct clk *clk; + struct reset_control *reset; +}; + +static inline struct bcm2835_rng_priv *to_rng_priv(struct hwrng *rng) +{ + return container_of(rng, struct bcm2835_rng_priv, rng); +} + +static inline u32 rng_readl(struct bcm2835_rng_priv *priv, u32 offset) +{ + /* MIPS chips strapped for BE will automagically configure the + * peripheral registers for CPU-native byte order. + */ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + return __raw_readl(priv->base + offset); + else + return readl(priv->base + offset); +} + +static inline void rng_writel(struct bcm2835_rng_priv *priv, u32 val, + u32 offset) +{ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + __raw_writel(val, priv->base + offset); + else + writel(val, priv->base + offset); +} + +static int bcm2835_rng_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct bcm2835_rng_priv *priv = to_rng_priv(rng); + u32 max_words = max / sizeof(u32); + u32 num_words, count; + + while ((rng_readl(priv, RNG_STATUS) >> 24) == 0) { + if (!wait) + return 0; + hwrng_yield(rng); + } + + num_words = rng_readl(priv, RNG_STATUS) >> 24; + if (num_words > max_words) + num_words = max_words; + + for (count = 0; count < num_words; count++) + ((u32 *)buf)[count] = rng_readl(priv, RNG_DATA); + + return num_words * sizeof(u32); +} + +static int bcm2835_rng_init(struct hwrng *rng) +{ + struct bcm2835_rng_priv *priv = to_rng_priv(rng); + int ret = 0; + u32 val; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + if (priv->mask_interrupts) { + /* mask the interrupt */ + val = rng_readl(priv, RNG_INT_MASK); + val |= RNG_INT_OFF; + rng_writel(priv, val, RNG_INT_MASK); + } + + /* set warm-up count & enable */ + rng_writel(priv, RNG_WARMUP_COUNT, RNG_STATUS); + rng_writel(priv, RNG_RBGEN, RNG_CTRL); + + return ret; +} + +static void bcm2835_rng_cleanup(struct bcm2835_rng_priv *priv) +{ + /* disable rng hardware */ + rng_writel(priv, 0, RNG_CTRL); + + clk_disable_unprepare(priv->clk); +} + +struct bcm2835_rng_of_data { + bool mask_interrupts; +}; + +static const struct bcm2835_rng_of_data nsp_rng_of_data = { + .mask_interrupts = true, +}; + +static const struct of_device_id bcm2835_rng_of_match[] = { + { .compatible = "brcm,bcm2835-rng"}, + { .compatible = "brcm,bcm-nsp-rng", .data = &nsp_rng_of_data }, + { .compatible = "brcm,bcm5301x-rng", .data = &nsp_rng_of_data }, + { .compatible = "brcm,bcm6368-rng"}, + {}, +}; + +static int bcm2835_rng_probe(struct device *dev) +{ + const struct bcm2835_rng_of_data *of_data; + struct bcm2835_rng_priv *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* map peripheral */ + priv->base = dev_platform_ioremap_resource(dev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + /* Clock is optional on most platforms */ + priv->clk = clk_get_optional(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->reset = reset_control_get_optional(dev, NULL); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->rng.name = dev_name(dev); + priv->rng.init = bcm2835_rng_init; + priv->rng.read = bcm2835_rng_read; + + of_data = device_get_match_data(dev); + if (of_data) + priv->mask_interrupts = of_data->mask_interrupts; + + /* register driver */ + err = hwrng_register(dev, &priv->rng); + if (err) + dev_err(dev, "hwrng registration failed\n"); + else + dev_info(dev, "hwrng registered\n"); + + dev->priv = priv; + + return err; +} + +static void bcm2835_rng_remove(struct device *dev) +{ + bcm2835_rng_cleanup(dev->priv); +} + +MODULE_DEVICE_TABLE(of, bcm2835_rng_of_match); + +static struct driver bcm2835_rng_driver = { + .name = "bcm2835-rng", + .of_match_table = bcm2835_rng_of_match, + .probe = bcm2835_rng_probe, + .remove = bcm2835_rng_remove, +}; +device_platform_driver(bcm2835_rng_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("BCM2835 Random Number Generator (RNG) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hw_random/core.c b/drivers/hw_random/core.c index 86214dc8ba..7bc3c33319 100644 --- a/drivers/hw_random/core.c +++ b/drivers/hw_random/core.c @@ -1,10 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2016 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * * derived from Linux kernel drivers/char/hw_random/core.c */ @@ -47,10 +44,10 @@ static ssize_t rng_dev_read(struct cdev *cdev, void *buf, size_t size, while (count) { int max = min(count, (size_t)RNG_BUFFER_SIZE); len = hwrng_get_data(rng, rng->buf, max, true); - if (len < 0) { - cur = len; - break; - } + if (len < 0) + return len; + if (!len && ctrlc()) + return cur; memcpy(buf + cur, rng->buf, len); @@ -67,12 +64,12 @@ static struct cdev_operations rng_chrdev_ops = { static int hwrng_register_cdev(struct hwrng *rng) { - struct device_d *dev = rng->dev; + struct device *dev = rng->dev; const char *alias; char *devname; int err; - alias = of_alias_get(dev->device_node); + alias = of_alias_get(dev->of_node); if (alias) { devname = xstrdup(alias); } else { @@ -106,7 +103,7 @@ struct hwrng *hwrng_get_first(void) return list_first_entry(&hwrngs, struct hwrng, list); } -int hwrng_register(struct device_d *dev, struct hwrng *rng) +int hwrng_register(struct device *dev, struct hwrng *rng) { int err; diff --git a/drivers/hw_random/dev-random.c b/drivers/hw_random/dev-random.c index 2170db7437..52f4847e4a 100644 --- a/drivers/hw_random/dev-random.c +++ b/drivers/hw_random/dev-random.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2019 Ahmad Fatoum, Pengutronix */ @@ -33,7 +33,7 @@ static int devrandom_rnd_init(struct hwrng *hwrng) return 0; } -static int devrandom_rnd_probe(struct device_d *dev) +static int devrandom_rnd_probe(struct device *dev) { struct devrandom_rnd *rnd; int ret; @@ -56,7 +56,7 @@ static int devrandom_rnd_probe(struct device_d *dev) return 0; } -static struct driver_d devrandom_rnd_driver = { +static struct driver devrandom_rnd_driver = { .name = "devrandom", .probe = devrandom_rnd_probe, }; diff --git a/drivers/hw_random/efi-rng.c b/drivers/hw_random/efi-rng.c new file mode 100644 index 0000000000..61cb01caf6 --- /dev/null +++ b/drivers/hw_random/efi-rng.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <common.h> +#include <driver.h> +#include <init.h> +#include <linux/hw_random.h> +#include <efi.h> +#include <efi/efi-device.h> + +struct efi_rng_priv { + struct efi_rng_protocol *protocol; + struct hwrng hwrng; +}; + +static inline struct efi_rng_priv *to_efi_rng(struct hwrng *hwrng) +{ + return container_of(hwrng, struct efi_rng_priv, hwrng); +} + +static int efi_rng_read(struct hwrng *hwrng, void *data, size_t len, bool wait) +{ + struct efi_rng_protocol *protocol = to_efi_rng(hwrng)->protocol; + efi_status_t efiret; + + efiret = protocol->get_rng(protocol, NULL, len, data); + + return -efi_errno(efiret) ?: len; +} + +static int efi_rng_probe(struct efi_device *efidev) +{ + struct efi_rng_priv *priv; + + priv = xzalloc(sizeof(*priv)); + + BS->handle_protocol(efidev->handle, &efi_rng_protocol_guid, + (void **)&priv->protocol); + if (!priv->protocol) + return -ENODEV; + + priv->hwrng.name = dev_name(&efidev->dev); + priv->hwrng.read = efi_rng_read; + + return hwrng_register(&efidev->dev, &priv->hwrng); +} + +static struct efi_driver efi_rng_driver = { + .driver = { + .name = "efi-rng", + }, + .probe = efi_rng_probe, + .guid = EFI_RNG_PROTOCOL_GUID, +}; +device_efi_driver(efi_rng_driver); diff --git a/drivers/hw_random/iproc-rng200.c b/drivers/hw_random/iproc-rng200.c new file mode 100644 index 0000000000..4cb3573a7d --- /dev/null +++ b/drivers/hw_random/iproc-rng200.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2015 Broadcom Corporation +* +*/ +/* + * DESCRIPTION: The Broadcom iProc RNG200 Driver + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/device.h> +#include <clock.h> + +/* Registers */ +#define RNG_CTRL_OFFSET 0x00 +#define RNG_CTRL_RNG_RBGEN_MASK 0x00001FFF +#define RNG_CTRL_RNG_RBGEN_ENABLE 0x00000001 + +#define RNG_SOFT_RESET_OFFSET 0x04 +#define RNG_SOFT_RESET 0x00000001 + +#define RBG_SOFT_RESET_OFFSET 0x08 +#define RBG_SOFT_RESET 0x00000001 + +#define RNG_INT_STATUS_OFFSET 0x18 +#define RNG_INT_STATUS_MASTER_FAIL_LOCKOUT_IRQ_MASK 0x80000000 +#define RNG_INT_STATUS_STARTUP_TRANSITIONS_MET_IRQ_MASK 0x00020000 +#define RNG_INT_STATUS_NIST_FAIL_IRQ_MASK 0x00000020 +#define RNG_INT_STATUS_TOTAL_BITS_COUNT_IRQ_MASK 0x00000001 + +#define RNG_FIFO_DATA_OFFSET 0x20 + +#define RNG_FIFO_COUNT_OFFSET 0x24 +#define RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK 0x000000FF + +struct iproc_rng200_dev { + struct hwrng rng; + void __iomem *base; +}; + +#define to_rng_priv(rng) container_of(rng, struct iproc_rng200_dev, rng) + +static void iproc_rng200_enable_set(void __iomem *rng_base, bool enable) +{ + u32 val; + + val = ioread32(rng_base + RNG_CTRL_OFFSET); + val &= ~RNG_CTRL_RNG_RBGEN_MASK; + + if (enable) + val |= RNG_CTRL_RNG_RBGEN_ENABLE; + + iowrite32(val, rng_base + RNG_CTRL_OFFSET); +} + +static void iproc_rng200_restart(void __iomem *rng_base) +{ + uint32_t val; + + iproc_rng200_enable_set(rng_base, false); + + /* Clear all interrupt status */ + iowrite32(0xFFFFFFFFUL, rng_base + RNG_INT_STATUS_OFFSET); + + /* Reset RNG and RBG */ + val = ioread32(rng_base + RBG_SOFT_RESET_OFFSET); + val |= RBG_SOFT_RESET; + iowrite32(val, rng_base + RBG_SOFT_RESET_OFFSET); + + val = ioread32(rng_base + RNG_SOFT_RESET_OFFSET); + val |= RNG_SOFT_RESET; + iowrite32(val, rng_base + RNG_SOFT_RESET_OFFSET); + + val = ioread32(rng_base + RNG_SOFT_RESET_OFFSET); + val &= ~RNG_SOFT_RESET; + iowrite32(val, rng_base + RNG_SOFT_RESET_OFFSET); + + val = ioread32(rng_base + RBG_SOFT_RESET_OFFSET); + val &= ~RBG_SOFT_RESET; + iowrite32(val, rng_base + RBG_SOFT_RESET_OFFSET); + + iproc_rng200_enable_set(rng_base, true); +} + +static int iproc_rng200_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + uint32_t num_remaining = max; + uint32_t status; + u64 start; + + #define MAX_RESETS_PER_READ 1 + uint32_t num_resets = 0; + + #define MAX_IDLE_TIME_NS (NSEC_PER_SEC) + + start = get_time_ns(); + + while ((num_remaining > 0) && !is_timeout(start, MAX_IDLE_TIME_NS)) { + + /* Is RNG sane? If not, reset it. */ + status = ioread32(priv->base + RNG_INT_STATUS_OFFSET); + if ((status & (RNG_INT_STATUS_MASTER_FAIL_LOCKOUT_IRQ_MASK | + RNG_INT_STATUS_NIST_FAIL_IRQ_MASK)) != 0) { + + if (num_resets >= MAX_RESETS_PER_READ) + return max - num_remaining; + + iproc_rng200_restart(priv->base); + num_resets++; + } + + /* Are there any random numbers available? */ + if ((ioread32(priv->base + RNG_FIFO_COUNT_OFFSET) & + RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK) > 0) { + + if (num_remaining >= sizeof(uint32_t)) { + /* Buffer has room to store entire word */ + *(uint32_t *)buf = ioread32(priv->base + + RNG_FIFO_DATA_OFFSET); + buf += sizeof(uint32_t); + num_remaining -= sizeof(uint32_t); + } else { + /* Buffer can only store partial word */ + uint32_t rnd_number = ioread32(priv->base + + RNG_FIFO_DATA_OFFSET); + memcpy(buf, &rnd_number, num_remaining); + buf += num_remaining; + num_remaining = 0; + } + + /* Reset the IDLE timeout */ + start = get_time_ns(); + } else { + if (!wait) + /* Cannot wait, return immediately */ + return max - num_remaining; + } + } + + return max - num_remaining; +} + +static int iproc_rng200_init(struct hwrng *rng) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + + iproc_rng200_enable_set(priv->base, true); + + return 0; +} + +static void iproc_rng200_cleanup(struct iproc_rng200_dev *priv) +{ + iproc_rng200_enable_set(priv->base, false); +} + +static int iproc_rng200_probe(struct device *dev) +{ + struct iproc_rng200_dev *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Map peripheral */ + priv->base = dev_platform_ioremap_resource(dev, 0); + if (IS_ERR(priv->base)) { + dev_err(dev, "failed to remap rng regs\n"); + return PTR_ERR(priv->base); + } + + dev->priv = priv; + + priv->rng.name = "iproc-rng200"; + priv->rng.read = iproc_rng200_read; + priv->rng.init = iproc_rng200_init; + + /* Register driver */ + ret = hwrng_register(dev, &priv->rng); + if (ret) { + dev_err(dev, "hwrng registration failed\n"); + return ret; + } + + dev_info(dev, "hwrng registered\n"); + + return 0; +} + +static void iproc_rng200_remove(struct device *dev) +{ + iproc_rng200_cleanup(dev->priv); +} + +static const struct of_device_id iproc_rng200_of_match[] = { + { .compatible = "brcm,bcm2711-rng200", }, + { .compatible = "brcm,bcm7211-rng200", }, + { .compatible = "brcm,bcm7278-rng200", }, + { .compatible = "brcm,iproc-rng200", }, + {}, +}; +MODULE_DEVICE_TABLE(of, iproc_rng200_of_match); + +static struct driver iproc_rng200_driver = { + .name = "iproc-rng200", + .of_match_table = iproc_rng200_of_match, + .probe = iproc_rng200_probe, + .remove = iproc_rng200_remove, +}; +device_platform_driver(iproc_rng200_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("iProc RNG200 Random Number Generator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hw_random/mxc-rngc.c b/drivers/hw_random/mxc-rngc.c index 075c20e437..39608b97af 100644 --- a/drivers/hw_random/mxc-rngc.c +++ b/drivers/hw_random/mxc-rngc.c @@ -1,19 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * RNG driver for Freescale RNGC * * Copyright (C) 2008-2012 Freescale Semiconductor, Inc. - */ - -/* - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html - */ - -/* * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> * @@ -27,10 +17,6 @@ * Hardware driver for Intel i810 Random Number Generator (RNG) * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com> * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com> - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include <common.h> @@ -115,7 +101,7 @@ #define RNG_ADDR_RANGE 0x34 struct mxc_rngc { - struct device_d *dev; + struct device *dev; struct clk *clk; void __iomem *base; struct hwrng rng; @@ -254,7 +240,7 @@ static int mxc_rngc_init(struct hwrng *rng) return 0; } -static int mxc_rngc_probe(struct device_d *dev) +static int mxc_rngc_probe(struct device *dev) { struct mxc_rngc *rngc; int ret; @@ -296,8 +282,9 @@ static const struct of_device_id mxc_rngc_dt_ids[] = { { .compatible = "fsl,imx25-rngb" }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, mxc_rngc_dt_ids); -static struct driver_d mxc_rngc_driver = { +static struct driver mxc_rngc_driver = { .name = "mxc_rngc", .probe = mxc_rngc_probe, .of_compatible = mxc_rngc_dt_ids, diff --git a/drivers/hw_random/omap-rng.c b/drivers/hw_random/omap-rng.c new file mode 100644 index 0000000000..9fa50bc8e7 --- /dev/null +++ b/drivers/hw_random/omap-rng.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * omap-rng.c - RNG driver for TI OMAP CPU family + * + * Author: Deepak Saxena <dsaxena@plexity.net> + * + * Copyright 2005 (c) MontaVista Software, Inc. + * + * Mostly based on original driver: + * + * Copyright (C) 2005 Nokia Corporation + * Author: Juha Yrjölä <juha.yrjola@nokia.com> + */ + +#include <init.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/hw_random.h> +#include <clock.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <of.h> +#include <linux/clk.h> +#include <linux/io.h> + +#define RNG_REG_STATUS_RDY (1 << 0) + +#define RNG_REG_INTACK_RDY_MASK (1 << 0) +#define RNG_REG_INTACK_SHUTDOWN_OFLO_MASK (1 << 1) +#define RNG_SHUTDOWN_OFLO_MASK (1 << 1) + +#define RNG_CONTROL_STARTUP_CYCLES_SHIFT 16 +#define RNG_CONTROL_STARTUP_CYCLES_MASK (0xffff << 16) +#define RNG_CONTROL_ENABLE_TRNG_SHIFT 10 +#define RNG_CONTROL_ENABLE_TRNG_MASK (1 << 10) + +#define RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT 16 +#define RNG_CONFIG_MAX_REFIL_CYCLES_MASK (0xffff << 16) +#define RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT 0 +#define RNG_CONFIG_MIN_REFIL_CYCLES_MASK (0xff << 0) + +#define RNG_CONTROL_STARTUP_CYCLES 0xff +#define RNG_CONFIG_MIN_REFIL_CYCLES 0x21 +#define RNG_CONFIG_MAX_REFIL_CYCLES 0x22 + +#define RNG_ALARMCNT_ALARM_TH_SHIFT 0x0 +#define RNG_ALARMCNT_ALARM_TH_MASK (0xff << 0) +#define RNG_ALARMCNT_SHUTDOWN_TH_SHIFT 16 +#define RNG_ALARMCNT_SHUTDOWN_TH_MASK (0x1f << 16) +#define RNG_ALARM_THRESHOLD 0xff +#define RNG_SHUTDOWN_THRESHOLD 0x4 + +#define RNG_REG_FROENABLE_MASK 0xffffff +#define RNG_REG_FRODETUNE_MASK 0xffffff + +#define OMAP2_RNG_OUTPUT_SIZE 0x4 +#define OMAP4_RNG_OUTPUT_SIZE 0x8 +#define EIP76_RNG_OUTPUT_SIZE 0x10 + +/* + * EIP76 RNG takes approx. 700us to produce 16 bytes of output data + * as per testing results. And to account for the lack of udelay()'s + * reliability, we keep the timeout as 1000us. + */ +#define RNG_DATA_FILL_TIMEOUT 100 + +enum { + RNG_OUTPUT_0_REG = 0, + RNG_OUTPUT_1_REG, + RNG_OUTPUT_2_REG, + RNG_OUTPUT_3_REG, + RNG_STATUS_REG, + RNG_INTMASK_REG, + RNG_INTACK_REG, + RNG_CONTROL_REG, + RNG_CONFIG_REG, + RNG_ALARMCNT_REG, + RNG_FROENABLE_REG, + RNG_FRODETUNE_REG, + RNG_ALARMMASK_REG, + RNG_ALARMSTOP_REG, + RNG_REV_REG, + RNG_SYSCONFIG_REG, +}; + +static const u16 reg_map_omap2[] = { + [RNG_OUTPUT_0_REG] = 0x0, + [RNG_STATUS_REG] = 0x4, + [RNG_CONFIG_REG] = 0x28, + [RNG_REV_REG] = 0x3c, + [RNG_SYSCONFIG_REG] = 0x40, +}; + +static const u16 reg_map_omap4[] = { + [RNG_OUTPUT_0_REG] = 0x0, + [RNG_OUTPUT_1_REG] = 0x4, + [RNG_STATUS_REG] = 0x8, + [RNG_INTMASK_REG] = 0xc, + [RNG_INTACK_REG] = 0x10, + [RNG_CONTROL_REG] = 0x14, + [RNG_CONFIG_REG] = 0x18, + [RNG_ALARMCNT_REG] = 0x1c, + [RNG_FROENABLE_REG] = 0x20, + [RNG_FRODETUNE_REG] = 0x24, + [RNG_ALARMMASK_REG] = 0x28, + [RNG_ALARMSTOP_REG] = 0x2c, + [RNG_REV_REG] = 0x1FE0, + [RNG_SYSCONFIG_REG] = 0x1FE4, +}; + +static const u16 reg_map_eip76[] = { + [RNG_OUTPUT_0_REG] = 0x0, + [RNG_OUTPUT_1_REG] = 0x4, + [RNG_OUTPUT_2_REG] = 0x8, + [RNG_OUTPUT_3_REG] = 0xc, + [RNG_STATUS_REG] = 0x10, + [RNG_INTACK_REG] = 0x10, + [RNG_CONTROL_REG] = 0x14, + [RNG_CONFIG_REG] = 0x18, + [RNG_ALARMCNT_REG] = 0x1c, + [RNG_FROENABLE_REG] = 0x20, + [RNG_FRODETUNE_REG] = 0x24, + [RNG_ALARMMASK_REG] = 0x28, + [RNG_ALARMSTOP_REG] = 0x2c, + [RNG_REV_REG] = 0x7c, +}; + +struct omap_rng_dev; +/** + * struct omap_rng_pdata - RNG IP block-specific data + * @regs: Pointer to the register offsets structure. + * @data_size: No. of bytes in RNG output. + * @data_present: Callback to determine if data is available. + * @init: Callback for IP specific initialization sequence. + * @cleanup: Callback for IP specific cleanup sequence. + */ +struct omap_rng_pdata { + u16 *regs; + u32 data_size; + u32 (*data_present)(struct omap_rng_dev *priv); + int (*init)(struct omap_rng_dev *priv); + void (*cleanup)(struct omap_rng_dev *priv); +}; + +struct omap_rng_dev { + void __iomem *base; + struct device *dev; + const struct omap_rng_pdata *pdata; + struct hwrng rng; + struct clk *clk; + struct clk *clk_reg; +}; + +static inline u32 omap_rng_read(struct omap_rng_dev *priv, u16 reg) +{ + return __raw_readl(priv->base + priv->pdata->regs[reg]); +} + +static inline void omap_rng_write(struct omap_rng_dev *priv, u16 reg, + u32 val) +{ + __raw_writel(val, priv->base + priv->pdata->regs[reg]); +} + + +static int omap_rng_do_read(struct hwrng *rng, void *data, size_t max, + bool wait) +{ + struct omap_rng_dev *priv; + int i, present; + + priv = (struct omap_rng_dev *)rng->priv; + + /* In Linux, max is always at least 32 bytes, which is greater than + * the 4 bytes required by the IP not to raise a data abort. + * In barebox, reading 4 bytes from a HWRNG is something we want + * support, so we check against 4 here and restrict memcpy_fromio + * size below. + */ + if (max < sizeof(u32)) + return -EFAULT; + + for (i = 0; i < RNG_DATA_FILL_TIMEOUT; i++) { + present = priv->pdata->data_present(priv); + if (present || !wait) + break; + + udelay(10); + } + if (!present) + return 0; + + max = min(max, priv->pdata->data_size); + + memcpy_fromio(data, priv->base + priv->pdata->regs[RNG_OUTPUT_0_REG], max); + + if (priv->pdata->regs[RNG_INTACK_REG]) + omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_RDY_MASK); + + return max; +} + +static int omap_rng_init(struct hwrng *rng) +{ + struct omap_rng_dev *priv; + + priv = (struct omap_rng_dev *)rng->priv; + return priv->pdata->init(priv); +} + +static inline u32 omap2_rng_data_present(struct omap_rng_dev *priv) +{ + return omap_rng_read(priv, RNG_STATUS_REG) ? 0 : 1; +} + +static int omap2_rng_init(struct omap_rng_dev *priv) +{ + omap_rng_write(priv, RNG_SYSCONFIG_REG, 0x1); + return 0; +} + +static void omap2_rng_cleanup(struct omap_rng_dev *priv) +{ + omap_rng_write(priv, RNG_SYSCONFIG_REG, 0x0); +} + +static struct omap_rng_pdata omap2_rng_pdata = { + .regs = (u16 *)reg_map_omap2, + .data_size = OMAP2_RNG_OUTPUT_SIZE, + .data_present = omap2_rng_data_present, + .init = omap2_rng_init, + .cleanup = omap2_rng_cleanup, +}; + +static inline u32 omap4_rng_data_present(struct omap_rng_dev *priv) +{ + return omap_rng_read(priv, RNG_STATUS_REG) & RNG_REG_STATUS_RDY; +} + +static int eip76_rng_init(struct omap_rng_dev *priv) +{ + u32 val; + + /* Return if RNG is already running. */ + if (omap_rng_read(priv, RNG_CONTROL_REG) & RNG_CONTROL_ENABLE_TRNG_MASK) + return 0; + + /* Number of 512 bit blocks of raw Noise Source output data that must + * be processed by either the Conditioning Function or the + * SP 800-90 DRBG ‘BC_DF’ functionality to yield a ‘full entropy’ + * output value. + */ + val = 0x5 << RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT; + + /* Number of FRO samples that are XOR-ed together into one bit to be + * shifted into the main shift register + */ + val |= RNG_CONFIG_MAX_REFIL_CYCLES << RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT; + omap_rng_write(priv, RNG_CONFIG_REG, val); + + /* Enable TRNG */ + val = RNG_CONTROL_ENABLE_TRNG_MASK; + omap_rng_write(priv, RNG_CONTROL_REG, val); + + return 0; +} + +static int omap4_rng_init(struct omap_rng_dev *priv) +{ + u32 val; + + /* Return if RNG is already running. */ + if (omap_rng_read(priv, RNG_CONTROL_REG) & RNG_CONTROL_ENABLE_TRNG_MASK) + return 0; + + val = RNG_CONFIG_MIN_REFIL_CYCLES << RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT; + val |= RNG_CONFIG_MAX_REFIL_CYCLES << RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT; + omap_rng_write(priv, RNG_CONFIG_REG, val); + + val = RNG_CONTROL_STARTUP_CYCLES << RNG_CONTROL_STARTUP_CYCLES_SHIFT; + val |= RNG_CONTROL_ENABLE_TRNG_MASK; + omap_rng_write(priv, RNG_CONTROL_REG, val); + + return 0; +} + +static void omap4_rng_cleanup(struct omap_rng_dev *priv) +{ + int val; + + val = omap_rng_read(priv, RNG_CONTROL_REG); + val &= ~RNG_CONTROL_ENABLE_TRNG_MASK; + omap_rng_write(priv, RNG_CONTROL_REG, val); +} + +static struct omap_rng_pdata omap4_rng_pdata = { + .regs = (u16 *)reg_map_omap4, + .data_size = OMAP4_RNG_OUTPUT_SIZE, + .data_present = omap4_rng_data_present, + .init = omap4_rng_init, + .cleanup = omap4_rng_cleanup, +}; + +static struct omap_rng_pdata eip76_rng_pdata = { + .regs = (u16 *)reg_map_eip76, + .data_size = EIP76_RNG_OUTPUT_SIZE, + .data_present = omap4_rng_data_present, + .init = eip76_rng_init, + .cleanup = omap4_rng_cleanup, +}; + +static const struct of_device_id omap_rng_of_match[] __maybe_unused = { + { + .compatible = "ti,omap2-rng", + .data = &omap2_rng_pdata, + }, + { + .compatible = "ti,omap4-rng", + .data = &omap4_rng_pdata, + }, + { + .compatible = "inside-secure,safexcel-eip76", + .data = &eip76_rng_pdata, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_rng_of_match); + +static int of_get_omap_rng_device_details(struct omap_rng_dev *priv, + struct device *dev) +{ + priv->pdata = device_get_match_data(dev); + if (!priv->pdata) + return -ENODEV; + + return 0; +} + +static struct clk *ti_sysc_clk_get_enabled(struct device *dev, const char *clk_id) +{ + struct clk *clk; + + clk = clk_get_optional_enabled(dev, clk_id); + if (!clk) + clk = clk_get_optional_enabled(dev->parent, clk_id); + + if (IS_ERR(clk)) + dev_errp_probe(dev, clk, "Unable to enable the clk\n"); + return clk; +} + +static int omap_rng_probe(struct device *dev) +{ + struct omap_rng_dev *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(struct omap_rng_dev), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->rng.read = omap_rng_do_read; + priv->rng.init = omap_rng_init; + + priv->rng.priv = (unsigned long)priv; + dev->priv = priv; + priv->dev = dev; + + priv->base = dev_platform_ioremap_resource(dev, 0); + if (IS_ERR(priv->base)) { + ret = PTR_ERR(priv->base); + goto err_ioremap; + } + + priv->rng.name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); + if (!priv->rng.name) { + ret = -ENOMEM; + goto err_ioremap; + } + + priv->clk = ti_sysc_clk_get_enabled(dev, NULL); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + goto err_ioremap; + } + + priv->clk_reg = ti_sysc_clk_get_enabled(dev, "reg"); + if (IS_ERR(priv->clk_reg)) { + ret = PTR_ERR(priv->clk_reg); + goto err_ioremap; + } + + ret = of_get_omap_rng_device_details(priv, dev); + if (ret) + goto err_register; + + ret = hwrng_register(dev, &priv->rng); + if (ret) + goto err_register; + + dev_info(dev, "Random Number Generator ver. %02x\n", + omap_rng_read(priv, RNG_REV_REG)); + + return 0; + +err_register: + priv->base = NULL; + + clk_disable_unprepare(priv->clk_reg); + clk_disable_unprepare(priv->clk); +err_ioremap: + dev_err(dev, "initialization failed.\n"); + return ret; +} + +static void omap_rng_remove(struct device *dev) +{ + struct omap_rng_dev *priv = dev->priv; + + + priv->pdata->cleanup(priv); + + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_reg); +} + +static struct driver omap_rng_driver = { + .name = "omap_rng", + .of_match_table = of_match_ptr(omap_rng_of_match), + .probe = omap_rng_probe, + .remove = omap_rng_remove, +}; + +device_platform_driver(omap_rng_driver); +MODULE_ALIAS("platform:omap_rng"); +MODULE_AUTHOR("Deepak Saxena (and others)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hw_random/optee-rng.c b/drivers/hw_random/optee-rng.c new file mode 100644 index 0000000000..d1d2904821 --- /dev/null +++ b/drivers/hw_random/optee-rng.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2019 Linaro Ltd. + */ + +#include <of.h> +#include <linux/hw_random.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/tee_drv.h> +#include <linux/uuid.h> + +#define DRIVER_NAME "optee-rng" + +#define TEE_ERROR_HEALTH_TEST_FAIL 0x00000001 + +/* + * TA_CMD_GET_ENTROPY - Get Entropy from RNG + * + * param[0] (inout memref) - Entropy buffer memory reference + * param[1] unused + * param[2] unused + * param[3] unused + * + * Result: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + * TEE_ERROR_NOT_SUPPORTED - Requested entropy size greater than size of pool + * TEE_ERROR_HEALTH_TEST_FAIL - Continuous health testing failed + */ +#define TA_CMD_GET_ENTROPY 0x0 + +/* + * TA_CMD_GET_RNG_INFO - Get RNG information + * + * param[0] (out value) - value.a: RNG data-rate in bytes per second + * value.b: Quality/Entropy per 1024 bit of data + * param[1] unused + * param[2] unused + * param[3] unused + * + * Result: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + */ +#define TA_CMD_GET_RNG_INFO 0x1 + +#define MAX_ENTROPY_REQ_SZ (4 * 1024) + +/** + * struct optee_rng_private - OP-TEE Random Number Generator private data + * @dev: OP-TEE based RNG device. + * @ctx: OP-TEE context handler. + * @session_id: RNG TA session identifier. + * @data_rate: RNG data rate. + * @entropy_shm_pool: Memory pool shared with RNG device. + * @optee_rng: OP-TEE RNG driver structure. + */ +struct optee_rng_private { + struct device *dev; + struct tee_context *ctx; + u32 session_id; + u32 data_rate; + struct tee_shm *entropy_shm_pool; + struct hwrng optee_rng; + u16 quality; + void (*bus_devinfo)(struct device *); +}; + +#define to_optee_rng_private(r) \ + container_of(r, struct optee_rng_private, optee_rng) + +static size_t get_optee_rng_data(struct optee_rng_private *pvt_data, + void *buf, size_t req_size) +{ + int ret = 0; + u8 *rng_data = NULL; + size_t rng_size = 0; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + /* Invoke TA_CMD_GET_ENTROPY function of Trusted App */ + inv_arg.func = TA_CMD_GET_ENTROPY; + inv_arg.session = pvt_data->session_id; + inv_arg.num_params = 4; + + /* Fill invoke cmd params */ + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT; + param[0].u.memref.shm = pvt_data->entropy_shm_pool; + param[0].u.memref.size = req_size; + param[0].u.memref.shm_offs = 0; + + ret = tee_client_invoke_func(pvt_data->ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(pvt_data->dev, "TA_CMD_GET_ENTROPY invoke err: %x\n", + inv_arg.ret); + return 0; + } + + rng_data = tee_shm_get_va(pvt_data->entropy_shm_pool, 0); + if (IS_ERR(rng_data)) { + dev_err(pvt_data->dev, "tee_shm_get_va failed\n"); + return 0; + } + + rng_size = param[0].u.memref.size; + memcpy(buf, rng_data, rng_size); + + return rng_size; +} + +static int optee_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct optee_rng_private *pvt_data = to_optee_rng_private(rng); + size_t read = 0, rng_size; + int timeout = 1; + u8 *data = buf; + + if (max > MAX_ENTROPY_REQ_SZ) + max = MAX_ENTROPY_REQ_SZ; + + while (read < max) { + rng_size = get_optee_rng_data(pvt_data, data, (max - read)); + + data += rng_size; + read += rng_size; + + if (wait && pvt_data->data_rate) { + if ((timeout-- == 0) || (read == max)) + return read; + } else { + return read; + } + } + + return read; +} + +static int optee_rng_init(struct hwrng *rng) +{ + struct optee_rng_private *pvt_data = to_optee_rng_private(rng); + struct tee_shm *entropy_shm_pool = NULL; + + entropy_shm_pool = tee_shm_alloc_kernel_buf(pvt_data->ctx, + MAX_ENTROPY_REQ_SZ); + if (IS_ERR(entropy_shm_pool)) { + dev_err(pvt_data->dev, "tee_shm_alloc_kernel_buf failed\n"); + return PTR_ERR(entropy_shm_pool); + } + + pvt_data->entropy_shm_pool = entropy_shm_pool; + + return 0; +} + +static struct optee_rng_private pvt_data = { + .optee_rng = { + .name = DRIVER_NAME, + .init = optee_rng_init, + .read = optee_rng_read, + } +}; + +static int get_optee_rng_info(struct device *dev) +{ + int ret = 0; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + /* Invoke TA_CMD_GET_RNG_INFO function of Trusted App */ + inv_arg.func = TA_CMD_GET_RNG_INFO; + inv_arg.session = pvt_data.session_id; + inv_arg.num_params = 4; + + /* Fill invoke cmd params */ + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT; + + ret = tee_client_invoke_func(pvt_data.ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(dev, "TA_CMD_GET_RNG_INFO invoke err: %x\n", + inv_arg.ret); + return -EINVAL; + } + + pvt_data.data_rate = param[0].u.value.a; + pvt_data.quality = param[0].u.value.b; + + return 0; +} + +static void optee_rng_devinfo(struct device *dev) +{ + printf("Data rate: %u\n", pvt_data.data_rate); + printf("Quality: %u\n", pvt_data.quality); + + if (pvt_data.bus_devinfo) + pvt_data.bus_devinfo(dev); +} + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) +{ + if (ver->impl_id == TEE_IMPL_ID_OPTEE) + return 1; + else + return 0; +} + +static int optee_rng_probe(struct device *dev) +{ + struct tee_client_device *rng_device = to_tee_client_device(dev); + int ret = 0, err = -ENODEV; + struct tee_ioctl_open_session_arg sess_arg; + + memset(&sess_arg, 0, sizeof(sess_arg)); + + /* Open context with TEE driver */ + pvt_data.ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, + NULL); + if (IS_ERR(pvt_data.ctx)) + return -ENODEV; + + /* Open session with hwrng Trusted App */ + export_uuid(sess_arg.uuid, &rng_device->id.uuid); + sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; + sess_arg.num_params = 0; + + ret = tee_client_open_session(pvt_data.ctx, &sess_arg, NULL); + if ((ret < 0) || (sess_arg.ret != 0)) { + dev_err(dev, "tee_client_open_session failed, err: %x\n", + sess_arg.ret); + err = -EINVAL; + goto out_ctx; + } + pvt_data.session_id = sess_arg.session; + + err = get_optee_rng_info(dev); + if (err) + goto out_sess; + + err = hwrng_register(dev, &pvt_data.optee_rng); + if (err) { + dev_err(dev, "hwrng registration failed (%d)\n", err); + goto out_sess; + } + + pvt_data.dev = dev; + pvt_data.bus_devinfo = dev->info; + dev->info = optee_rng_devinfo; + + return 0; + +out_sess: + tee_client_close_session(pvt_data.ctx, pvt_data.session_id); +out_ctx: + tee_client_close_context(pvt_data.ctx); + + return err; +} + +static void optee_rng_remove(struct device *dev) +{ + hwrng_unregister(&pvt_data.optee_rng); + + tee_shm_free(pvt_data.entropy_shm_pool); + + tee_client_close_session(pvt_data.ctx, pvt_data.session_id); + tee_client_close_context(pvt_data.ctx); +} + +static const struct tee_client_device_id optee_rng_id_table[] = { + {UUID_INIT(0xab7a617c, 0xb8e7, 0x4d8f, + 0x83, 0x01, 0xd0, 0x9b, 0x61, 0x03, 0x6b, 0x64)}, + {} +}; + +MODULE_DEVICE_TABLE(tee, optee_rng_id_table); + +static struct tee_client_driver optee_rng_driver = { + .id_table = optee_rng_id_table, + .driver = { + .name = DRIVER_NAME, + .bus = &tee_bus_type, + .probe = optee_rng_probe, + .remove = optee_rng_remove, + }, +}; + +static int __init optee_rng_mod_init(void) +{ + return driver_register(&optee_rng_driver.driver); +} +device_initcall(optee_rng_mod_init); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sumit Garg <sumit.garg@linaro.org>"); +MODULE_DESCRIPTION("OP-TEE based random number generator driver"); diff --git a/drivers/hw_random/rockchip-rng.c b/drivers/hw_random/rockchip-rng.c new file mode 100644 index 0000000000..990e5fc111 --- /dev/null +++ b/drivers/hw_random/rockchip-rng.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rockchip-rng.c Random Number Generator driver for the Rockchip + * + * Copyright (c) 2018, Fuzhou Rockchip Electronics Co., Ltd. + * Author: Lin Jinhan <troy.lin@rock-chips.com> + * + */ +#include <linux/clk.h> +#include <linux/hw_random.h> +#include <linux/iopoll.h> +#include <linux/reset.h> +#include <linux/bitfield.h> +#include <linux/mod_devicetable.h> +#include <of.h> +#include <of_address.h> +#include <linux/device.h> + +#define HIWORD_UPDATE(val, mask, shift) \ + ((val) << (shift) | (mask) << ((shift) + 16)) + +#define ROCKCHIP_AUTOSUSPEND_DELAY 100 +#define ROCKCHIP_POLL_PERIOD_US 100 +#define ROCKCHIP_POLL_TIMEOUT_US 10000 +#define RK_MAX_RNG_BYTE (32) + +#define CRYPTO_V1_CTRL 0x0008 +#define CRYPTO_V1_RNG_START BIT(8) +#define CRYPTO_V1_RNG_FLUSH BIT(9) +#define CRYPTO_V1_TRNG_CTRL 0x0200 +#define CRYPTO_V1_OSC_ENABLE BIT(16) +#define CRYPTO_V1_TRNG_SAMPLE_PERIOD(x) (x) +#define CRYPTO_V1_TRNG_DOUT_0 0x0204 + +#define CRYPTO_V2_RNG_CTL 0x0400 +#define CRYPTO_V2_RNG_BIT_LEN GENMASK(5, 4) +#define CRYPTO_V2_RNG_64_BIT_LEN FIELD_PREP(CRYPTO_V2_RNG_BIT_LEN, 0) +#define CRYPTO_V2_RNG_128_BIT_LEN FIELD_PREP(CRYPTO_V2_RNG_BIT_LEN, 1) +#define CRYPTO_V2_RNG_192_BIT_LEN FIELD_PREP(CRYPTO_V2_RNG_BIT_LEN, 2) +#define CRYPTO_V2_RNG_256_BIT_LEN FIELD_PREP(CRYPTO_V2_RNG_BIT_LEN, 3) +#define CRYPTO_V2_RNG_SOC_RING GENMASK(3, 2) +#define CRYPTO_V2_RNG_FASTEST_SOC_RING FIELD_PREP(CRYPTO_V2_RNG_SOC_RING, 0) +#define CRYPTO_V2_RNG_SLOWER_SOC_RING_0 FIELD_PREP(CRYPTO_V2_RNG_SOC_RING, 1) +#define CRYPTO_V2_RNG_SLOWER_SOC_RING_1 FIELD_PREP(CRYPTO_V2_RNG_SOC_RING, 2) +#define CRYPTO_V2_RNG_SLOWEST_SOC_RING FIELD_PREP(CRYPTO_V2_RNG_SOC_RING, 3) +#define CRYPTO_V2_RNG_ENABLE BIT(1) +#define CRYPTO_V2_RNG_START BIT(0) +#define CRYPTO_V2_RNG_SAMPLE_CNT 0x0404 +#define CRYPTO_V2_RNG_DOUT_0 0x0410 + +struct rk_rng_soc_data { + const char * const *clks; + int clks_num; + int (*rk_rng_read)(struct hwrng *rng, void *buf, size_t max, bool wait); +}; + +struct rk_rng { + struct device *dev; + struct hwrng rng; + void __iomem *mem; + struct rk_rng_soc_data *soc_data; + u32 clk_num; + struct clk_bulk_data *clk_bulks; +}; + +static void rk_rng_writel(struct rk_rng *rng, u32 val, u32 offset) +{ + __raw_writel(val, rng->mem + offset); +} + +static u32 rk_rng_readl(struct rk_rng *rng, u32 offset) +{ + return __raw_readl(rng->mem + offset); +} + +static int rk_rng_init(struct hwrng *rng) +{ + int ret; + struct rk_rng *rk_rng = container_of(rng, struct rk_rng, rng); + + ret = clk_bulk_prepare_enable(rk_rng->clk_num, rk_rng->clk_bulks); + if (ret < 0) { + dev_err(rk_rng->dev, "failed to enable clks %d\n", ret); + return ret; + } + + return 0; +} + +static void rk_rng_cleanup(struct rk_rng *rk_rng) +{ + clk_bulk_disable_unprepare(rk_rng->clk_num, rk_rng->clk_bulks); +} + +static void rk_rng_read_regs(struct rk_rng *rng, u32 offset, void *buf, + size_t size) +{ + u32 i, sample; + + for (i = 0; i < size; i += 4) { + sample = rk_rng_readl(rng, offset + i); + memcpy(buf + i, &sample, sizeof(sample)); + } +} + +static int rk_rng_v1_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + int ret = 0; + u32 reg_ctrl = 0; + struct rk_rng *rk_rng = container_of(rng, struct rk_rng, rng); + + /* enable osc_ring to get entropy, sample period is set as 100 */ + reg_ctrl = CRYPTO_V1_OSC_ENABLE | CRYPTO_V1_TRNG_SAMPLE_PERIOD(100); + rk_rng_writel(rk_rng, reg_ctrl, CRYPTO_V1_TRNG_CTRL); + + reg_ctrl = HIWORD_UPDATE(CRYPTO_V1_RNG_START, CRYPTO_V1_RNG_START, 0); + + rk_rng_writel(rk_rng, reg_ctrl, CRYPTO_V1_CTRL); + + ret = readl_poll_timeout(rk_rng->mem + CRYPTO_V1_CTRL, reg_ctrl, + !(reg_ctrl & CRYPTO_V1_RNG_START), + ROCKCHIP_POLL_TIMEOUT_US); + if (ret < 0) + goto out; + + ret = min_t(size_t, max, RK_MAX_RNG_BYTE); + + rk_rng_read_regs(rk_rng, CRYPTO_V1_TRNG_DOUT_0, buf, ret); + +out: + /* close TRNG */ + rk_rng_writel(rk_rng, HIWORD_UPDATE(0, CRYPTO_V1_RNG_START, 0), + CRYPTO_V1_CTRL); + + return ret; +} + +static int rk_rng_v2_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + int ret = 0; + u32 reg_ctrl = 0; + struct rk_rng *rk_rng = container_of(rng, struct rk_rng, rng); + + /* enable osc_ring to get entropy, sample period is set as 100 */ + rk_rng_writel(rk_rng, 100, CRYPTO_V2_RNG_SAMPLE_CNT); + + reg_ctrl |= CRYPTO_V2_RNG_256_BIT_LEN; + reg_ctrl |= CRYPTO_V2_RNG_SLOWER_SOC_RING_0; + reg_ctrl |= CRYPTO_V2_RNG_ENABLE; + reg_ctrl |= CRYPTO_V2_RNG_START; + + rk_rng_writel(rk_rng, HIWORD_UPDATE(reg_ctrl, 0xffff, 0), + CRYPTO_V2_RNG_CTL); + + ret = readl_poll_timeout(rk_rng->mem + CRYPTO_V2_RNG_CTL, reg_ctrl, + !(reg_ctrl & CRYPTO_V2_RNG_START), + ROCKCHIP_POLL_TIMEOUT_US); + if (ret < 0) + goto out; + + ret = min_t(size_t, max, RK_MAX_RNG_BYTE); + + rk_rng_read_regs(rk_rng, CRYPTO_V2_RNG_DOUT_0, buf, ret); + +out: + /* close TRNG */ + rk_rng_writel(rk_rng, HIWORD_UPDATE(0, 0xffff, 0), CRYPTO_V2_RNG_CTL); + + return ret; +} + +static const struct rk_rng_soc_data rk_rng_rk3399_soc_data = { + .clks_num = 3, + .rk_rng_read = rk_rng_v1_read, +}; + +static const struct rk_rng_soc_data rk_rng_v1_soc_data = { + .clks_num = 2, + .rk_rng_read = rk_rng_v1_read, +}; + +static const struct rk_rng_soc_data rk_rng_v2_soc_data = { + .clks_num = 2, + .rk_rng_read = rk_rng_v2_read, +}; + +static const struct of_device_id rk_rng_dt_match[] = { + { + .compatible = "rockchip,rk3399-crypto", + .data = (void *)&rk_rng_rk3399_soc_data, + }, + { + .compatible = "rockchip,cryptov1-rng", + .data = (void *)&rk_rng_v1_soc_data, + }, + { + .compatible = "rockchip,cryptov2-rng", + .data = (void *)&rk_rng_v2_soc_data, + }, + { }, +}; + +MODULE_DEVICE_TABLE(of, rk_rng_dt_match); + +static int rk_rng_probe(struct device *dev) +{ + int ret; + struct rk_rng *rk_rng; + struct device_node *np = dev->of_node; + const struct of_device_id *match; + + rk_rng = devm_kzalloc(dev, sizeof(struct rk_rng), GFP_KERNEL); + if (!rk_rng) + return -ENOMEM; + + match = of_match_node(rk_rng_dt_match, np); + rk_rng->soc_data = (struct rk_rng_soc_data *)match->data; + + rk_rng->dev = dev; + rk_rng->rng.name = "rockchip"; + rk_rng->rng.init = rk_rng_init; + rk_rng->rng.read = rk_rng->soc_data->rk_rng_read; + + rk_rng->clk_num = clk_bulk_get_all(dev, &rk_rng->clk_bulks); + if (rk_rng->clk_num < rk_rng->soc_data->clks_num) + return dev_err_probe(dev, -EINVAL, + "Missing clocks, got %d instead of %d\n", + rk_rng->clk_num, rk_rng->soc_data->clks_num); + + ret = device_reset_us(dev, 2); + if (ret) + return ret; + + rk_rng->mem = of_iomap(dev->device_node, 0); + if (IS_ERR(rk_rng->mem)) + return PTR_ERR(rk_rng->mem); + + dev->priv = rk_rng; + + return hwrng_register(dev, &rk_rng->rng); +} + +static void rk_rng_remove(struct device *dev) +{ + rk_rng_cleanup(dev->priv); +} + +static struct driver rk_rng_driver = { + .name = "rockchip-rng", + .of_match_table = rk_rng_dt_match, + .probe = rk_rng_probe, + .remove = rk_rng_remove, +}; + +device_platform_driver(rk_rng_driver); + +MODULE_DESCRIPTION("ROCKCHIP H/W Random Number Generator driver"); +MODULE_AUTHOR("Lin Jinhan <troy.lin@rock-chips.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hw_random/starfive-vic-rng.c b/drivers/hw_random/starfive-vic-rng.c index f7b7585884..329e845197 100644 --- a/drivers/hw_random/starfive-vic-rng.c +++ b/drivers/hw_random/starfive-vic-rng.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd. */ @@ -72,7 +72,7 @@ #define to_vic_rng(p) container_of(p, struct vic_rng, rng) struct vic_rng { - struct device_d *dev; + struct device *dev; void __iomem *base; struct hwrng rng; }; @@ -170,7 +170,7 @@ static int vic_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) return max; } -static int vic_rng_probe(struct device_d *dev) +static int vic_rng_probe(struct device *dev) { struct vic_rng *hrng; struct resource *res; @@ -195,8 +195,9 @@ static const struct of_device_id vic_rng_dt_ids[] = { { .compatible = "starfive,vic-rng" }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, vic_rng_dt_ids); -static struct driver_d vic_rng_driver = { +static struct driver vic_rng_driver = { .name = "vic-rng", .probe = vic_rng_probe, .of_compatible = vic_rng_dt_ids, diff --git a/drivers/hw_random/stm32-rng.c b/drivers/hw_random/stm32-rng.c index 440b53684f..03bc4a5cbf 100644 --- a/drivers/hw_random/stm32-rng.c +++ b/drivers/hw_random/stm32-rng.c @@ -51,7 +51,7 @@ static int stm32_rng_read(struct hwrng *hwrng, void *data, size_t len, bool wait ret = readl_poll_timeout(rng->base + RNG_SR, sr, sr & RNG_SR_DRDY, 10 * USEC_PER_MSEC); if (ret) - goto out; + return ret; if (sr & (RNG_SR_SEIS | RNG_SR_SECS)) { int i; @@ -61,8 +61,7 @@ static int stm32_rng_read(struct hwrng *hwrng, void *data, size_t len, bool wait readl(rng->base + RNG_DR); if (readl(rng->base + RNG_SR) & RNG_SR_SEIS) { pr_warn("RNG Noise"); - ret = -EIO; - goto out; + return -EIO; } /* start again */ @@ -84,8 +83,7 @@ static int stm32_rng_read(struct hwrng *hwrng, void *data, size_t len, bool wait } } -out: - return len ?: ret; + return len; } static int stm32_rng_init(struct hwrng *hwrng) @@ -106,7 +104,7 @@ static int stm32_rng_init(struct hwrng *hwrng) return 0; } -static void stm32_rng_remove(struct device_d *dev) +static void stm32_rng_remove(struct device *dev) { struct stm32_rng *rng = dev->priv; @@ -114,7 +112,7 @@ static void stm32_rng_remove(struct device_d *dev) clk_disable(rng->clk); } -static int stm32_rng_probe(struct device_d *dev) +static int stm32_rng_probe(struct device *dev) { struct stm32_rng *rng; struct resource *res; @@ -154,8 +152,9 @@ static const struct of_device_id stm32_rng_dt_ids[] = { { .compatible = "st,stm32-rng" }, { /* sentinel */}, }; +MODULE_DEVICE_TABLE(of, stm32_rng_dt_ids); -static struct driver_d stm32_rng_driver = { +static struct driver stm32_rng_driver = { .name = "stm32-rng", .probe = stm32_rng_probe, .remove = stm32_rng_remove, diff --git a/drivers/hw_random/timeriomem-rng.c b/drivers/hw_random/timeriomem-rng.c new file mode 100644 index 0000000000..8d47058306 --- /dev/null +++ b/drivers/hw_random/timeriomem-rng.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/char/hw_random/timeriomem-rng.c + * + * Copyright (C) 2009 Alexander Clouter <alex@digriz.org.uk> + * + * Derived from drivers/char/hw_random/omap-rng.c + * Copyright 2005 (c) MontaVista Software, Inc. + * Author: Deepak Saxena <dsaxena@plexity.net> + * + * Overview: + * This driver is useful for platforms that have an IO range that provides + * periodic random data from a single IO memory address. All the platform + * has to do is provide the address and 'wait time' that new data becomes + * available. + * + * TODO: add support for reading sizes other than 32bits and masking + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/ktime.h> +#include <of.h> +#include <linux/device.h> +#include <linux/time.h> + +struct timeriomem_rng_private { + void __iomem *io_base; + + ktime_t period; + ktime_t next_read; + + struct hwrng rng_ops; +}; + +static int timeriomem_rng_read(struct hwrng *hwrng, void *data, + size_t max, bool wait) +{ + 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); + ktime_t now = ktime_get(); + + /* + * 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 (ktime_before(now, priv->next_read)) { + if (!wait) + return 0; + + udelay(ktime_to_us(ktime_sub(priv->next_read, now))); + } + + 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) + udelay(period_us); + + *(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->next_read = ktime_add(ktime_get(), priv->period); + + return retval; +} + +static int timeriomem_rng_probe(struct device *dev) +{ + struct timeriomem_rng_private *priv; + struct resource *res; + int err = 0; + int period; + + /* Allocate memory for the device structure (and zero it) */ + priv = devm_kzalloc(dev, + sizeof(struct timeriomem_rng_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->io_base = dev_platform_get_and_ioremap_resource(dev, 0, &res); + if (IS_ERR(priv->io_base)) + return PTR_ERR(priv->io_base); + + if (res->start % 4 != 0 || resource_size(res) < 4) { + dev_err(dev, + "address must be at least four bytes wide and 32-bit aligned\n"); + return -EINVAL; + } + + if (of_property_read_u32(dev->of_node, "period", &period)) + return dev_err_probe(dev, -EINVAL, "missing period\n"); + + priv->period = ns_to_ktime(period * NSEC_PER_USEC); + + priv->rng_ops.name = dev_name(dev); + priv->rng_ops.read = timeriomem_rng_read; + + /* Assume random data is already available. */ + priv->next_read = ktime_get(); + + err = hwrng_register(dev, &priv->rng_ops); + if (err) { + dev_err(dev, "problem registering\n"); + return err; + } + + dev_info(dev, "32bits from 0x%p @ %dus\n", + priv->io_base, period); + + return 0; +} + +static const struct of_device_id timeriomem_rng_match[] = { + { .compatible = "timeriomem_rng" }, + {}, +}; +MODULE_DEVICE_TABLE(of, timeriomem_rng_match); + +static struct driver timeriomem_rng_driver = { + .name = "timeriomem_rng", + .of_match_table = timeriomem_rng_match, + .probe = timeriomem_rng_probe, +}; + +device_platform_driver(timeriomem_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alexander Clouter <alex@digriz.org.uk>"); +MODULE_DESCRIPTION("Timer IOMEM H/W RNG driver"); |