diff options
Diffstat (limited to 'drivers/char/hw_random')
38 files changed, 8760 insertions, 0 deletions
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig new file mode 100644 index 000000000..dac895dc0 --- /dev/null +++ b/drivers/char/hw_random/Kconfig @@ -0,0 +1,449 @@ +# +# Hardware Random Number Generator (RNG) configuration +# + +menuconfig HW_RANDOM + tristate "Hardware Random Number Generator Core support" + default m + ---help--- + Hardware Random Number Generator Core infrastructure. + + To compile this driver as a module, choose M here: the + module will be called rng-core. This provides a device + that's usually called /dev/hwrng, and which exposes one + of possibly several hardware random number generators. + + These hardware random number generators do feed into the + kernel's random number generator entropy pool. + + If unsure, say Y. + +if HW_RANDOM + +config HW_RANDOM_TIMERIOMEM + tristate "Timer IOMEM HW Random Number Generator support" + depends on HAS_IOMEM + ---help--- + This driver provides kernel-side 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; + the default FPGA bitstream on the TS-7800 has such functionality. + + To compile this driver as a module, choose M here: the + module will be called timeriomem-rng. + + If unsure, say Y. + +config HW_RANDOM_INTEL + tristate "Intel HW Random Number Generator support" + depends on (X86 || IA64) && PCI + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Intel i8xx-based motherboards. + + To compile this driver as a module, choose M here: the + module will be called intel-rng. + + If unsure, say Y. + +config HW_RANDOM_AMD + tristate "AMD HW Random Number Generator support" + depends on (X86 || PPC_MAPLE) && PCI + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on AMD 76x-based motherboards. + + To compile this driver as a module, choose M here: the + module will be called amd-rng. + + If unsure, say Y. + +config HW_RANDOM_ATMEL + tristate "Atmel Random Number Generator support" + depends on ARCH_AT91 && HAVE_CLK && OF + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Atmel AT91 devices. + + To compile this driver as a module, choose M here: the + module will be called atmel-rng. + + If unsure, say Y. + +config HW_RANDOM_BCM2835 + tristate "Broadcom BCM2835/BCM63xx Random Number Generator support" + depends on ARCH_BCM2835 || ARCH_BCM_NSP || ARCH_BCM_5301X || \ + ARCH_BCM_63XX || BCM63XX || BMIPS_GENERIC + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on the Broadcom BCM2835 and BCM63xx SoCs. + + To compile this driver as a module, choose M here: the + module will be called bcm2835-rng + + If unsure, say Y. + +config HW_RANDOM_IPROC_RNG200 + tristate "Broadcom iProc/STB RNG200 support" + depends on ARCH_BCM_IPROC || ARCH_BRCMSTB + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the RNG200 + hardware found on the Broadcom iProc and STB SoCs. + + To compile this driver as a module, choose M here: the + module will be called iproc-rng200 + + If unsure, say Y. + +config HW_RANDOM_GEODE + tristate "AMD Geode HW Random Number Generator support" + depends on X86_32 && PCI + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on the AMD Geode LX. + + To compile this driver as a module, choose M here: the + module will be called geode-rng. + + If unsure, say Y. + +config HW_RANDOM_N2RNG + tristate "Niagara2 Random Number Generator support" + depends on SPARC64 + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Niagara2 cpus. + + To compile this driver as a module, choose M here: the + module will be called n2-rng. + + If unsure, say Y. + +config HW_RANDOM_VIA + tristate "VIA HW Random Number Generator support" + depends on X86 + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on VIA based motherboards. + + To compile this driver as a module, choose M here: the + module will be called via-rng. + + If unsure, say Y. + +config HW_RANDOM_IXP4XX + tristate "Intel IXP4xx NPU HW Pseudo-Random Number Generator support" + depends on ARCH_IXP4XX + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Pseudo-Random + Number Generator hardware found on the Intel IXP45x/46x NPU. + + To compile this driver as a module, choose M here: the + module will be called ixp4xx-rng. + + If unsure, say Y. + +config HW_RANDOM_OMAP + tristate "OMAP Random Number Generator support" + depends on ARCH_OMAP16XX || ARCH_OMAP2PLUS || ARCH_MVEBU + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on OMAP16xx, OMAP2/3/4/5, AM33xx/AM43xx + multimedia processors, and Marvell Armada 7k/8k SoCs. + + To compile this driver as a module, choose M here: the + module will be called omap-rng. + + If unsure, say Y. + +config HW_RANDOM_OMAP3_ROM + tristate "OMAP3 ROM Random Number Generator support" + depends on ARCH_OMAP3 + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on OMAP34xx processors. + + To compile this driver as a module, choose M here: the + module will be called omap3-rom-rng. + + If unsure, say Y. + +config HW_RANDOM_OCTEON + tristate "Octeon Random Number Generator support" + depends on CAVIUM_OCTEON_SOC + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Octeon processors. + + To compile this driver as a module, choose M here: the + module will be called octeon-rng. + + If unsure, say Y. + +config HW_RANDOM_PASEMI + tristate "PA Semi HW Random Number Generator support" + depends on PPC_PASEMI + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on PA Semi PWRficient SoCs. + + To compile this driver as a module, choose M here: the + module will be called pasemi-rng. + + If unsure, say Y. + +config HW_RANDOM_VIRTIO + tristate "VirtIO Random Number Generator support" + depends on VIRTIO + ---help--- + This driver provides kernel-side support for the virtual Random Number + Generator hardware. + + To compile this driver as a module, choose M here: the + module will be called virtio-rng. If unsure, say N. + +config HW_RANDOM_TX4939 + tristate "TX4939 Random Number Generator support" + depends on SOC_TX4939 + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on TX4939 SoC. + + To compile this driver as a module, choose M here: the + module will be called tx4939-rng. + + If unsure, say Y. + +config HW_RANDOM_MXC_RNGA + tristate "Freescale i.MX RNGA Random Number Generator" + depends on SOC_IMX31 + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Freescale i.MX processors. + + To compile this driver as a module, choose M here: the + module will be called mxc-rnga. + + If unsure, say Y. + +config HW_RANDOM_IMX_RNGC + tristate "Freescale i.MX RNGC Random Number Generator" + depends on ARCH_MXC + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator Version C hardware found on some Freescale i.MX + processors. Version B is also supported by this driver. + + To compile this driver as a module, choose M here: the + module will be called imx-rngc. + + If unsure, say Y. + +config HW_RANDOM_NOMADIK + tristate "ST-Ericsson Nomadik Random Number Generator support" + depends on ARCH_NOMADIK + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on ST-Ericsson SoCs (8815 and 8500). + + To compile this driver as a module, choose M here: the + module will be called nomadik-rng. + + If unsure, say Y. + +config HW_RANDOM_PSERIES + tristate "pSeries HW Random Number Generator support" + depends on PPC64 && IBMVIO + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on POWER7+ machines and above + + To compile this driver as a module, choose M here: the + module will be called pseries-rng. + + If unsure, say Y. + +config HW_RANDOM_POWERNV + tristate "PowerNV Random Number Generator support" + depends on PPC_POWERNV + default HW_RANDOM + ---help--- + This is the driver for Random Number Generator hardware found + in POWER7+ and above machines for PowerNV platform. + + To compile this driver as a module, choose M here: the + module will be called powernv-rng. + + If unsure, say Y. + +config HW_RANDOM_HISI + tristate "Hisilicon Random Number Generator support" + depends on HW_RANDOM && ARCH_HISI + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Hisilicon Hip04 and Hip05 SoC. + + To compile this driver as a module, choose M here: the + module will be called hisi-rng. + + If unsure, say Y. + +config HW_RANDOM_ST + tristate "ST Microelectronics HW Random Number Generator support" + depends on HW_RANDOM && ARCH_STI + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on STi series of SoCs. + + To compile this driver as a module, choose M here: the + module will be called st-rng. + +config HW_RANDOM_XGENE + tristate "APM X-Gene True Random Number Generator (TRNG) support" + depends on HW_RANDOM && ARCH_XGENE + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on APM X-Gene SoC. + + To compile this driver as a module, choose M here: the + module will be called xgene_rng. + + If unsure, say Y. + +config HW_RANDOM_STM32 + tristate "STMicroelectronics STM32 random number generator" + depends on HW_RANDOM && (ARCH_STM32 || COMPILE_TEST) + depends on HAS_IOMEM + default HW_RANDOM + help + This driver provides kernel-side support for the Random Number + Generator hardware found on STM32 microcontrollers. + + To compile this driver as a module, choose M here: the + module will be called stm32-rng. + + If unsure, say N. + +config HW_RANDOM_PIC32 + tristate "Microchip PIC32 Random Number Generator support" + depends on HW_RANDOM && MACH_PIC32 + default y + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on a PIC32. + + To compile this driver as a module, choose M here. the + module will be called pic32-rng. + + If unsure, say Y. + +config HW_RANDOM_MESON + tristate "Amlogic Meson Random Number Generator support" + depends on HW_RANDOM + depends on ARCH_MESON || COMPILE_TEST + default y + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Amlogic Meson SoCs. + + To compile this driver as a module, choose M here. the + module will be called meson-rng. + + If unsure, say Y. + +config HW_RANDOM_CAVIUM + tristate "Cavium ThunderX Random Number Generator support" + depends on HW_RANDOM && PCI && (ARM64 || (COMPILE_TEST && 64BIT)) + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Cavium SoCs. + + To compile this driver as a module, choose M here: the + module will be called cavium_rng. + + 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 + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the True + Random Number Generator available as CPACF extension + on modern s390 hardware platforms. + + To compile this driver as a module, choose M here: the + module will be called s390-trng. + + If unsure, say Y. + +config HW_RANDOM_EXYNOS + tristate "Samsung Exynos True Random Number Generator support" + depends on ARCH_EXYNOS || COMPILE_TEST + default HW_RANDOM + ---help--- + This driver provides support for the True Random Number + Generator available in Exynos SoCs. + + To compile this driver as a module, choose M here: the module + will be called exynos-trng. + + If unsure, say Y. +endif # HW_RANDOM + +config UML_RANDOM + depends on UML + tristate "Hardware random number generator" + help + This option enables UML's "hardware" random number generator. It + attaches itself to the host's /dev/random, supplying as much entropy + as the host has, rather than the small amount the UML gets from its + own drivers. It registers itself as a standard hardware random number + generator, major 10, minor 183, and the canonical device name is + /dev/hwrng. + The way to make use of this is to install the rng-tools package + (check your distro, or download from + http://sourceforge.net/projects/gkernel/). rngd periodically reads + /dev/hwrng and injects the entropy into /dev/random. + +config HW_RANDOM_KEYSTONE + depends on ARCH_KEYSTONE + default HW_RANDOM + tristate "TI Keystone NETCP SA Hardware random number generator" + help + This option enables Keystone's hardware random generator. diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile new file mode 100644 index 000000000..e35ec3ce3 --- /dev/null +++ b/drivers/char/hw_random/Makefile @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for HW Random Number Generator (RNG) device drivers. +# + +obj-$(CONFIG_HW_RANDOM) += rng-core.o +rng-core-y := core.o +obj-$(CONFIG_HW_RANDOM_TIMERIOMEM) += timeriomem-rng.o +obj-$(CONFIG_HW_RANDOM_INTEL) += intel-rng.o +obj-$(CONFIG_HW_RANDOM_AMD) += amd-rng.o +obj-$(CONFIG_HW_RANDOM_ATMEL) += atmel-rng.o +obj-$(CONFIG_HW_RANDOM_GEODE) += geode-rng.o +obj-$(CONFIG_HW_RANDOM_N2RNG) += n2-rng.o +n2-rng-y := n2-drv.o n2-asm.o +obj-$(CONFIG_HW_RANDOM_VIA) += via-rng.o +obj-$(CONFIG_HW_RANDOM_EXYNOS) += exynos-trng.o +obj-$(CONFIG_HW_RANDOM_IXP4XX) += ixp4xx-rng.o +obj-$(CONFIG_HW_RANDOM_OMAP) += omap-rng.o +obj-$(CONFIG_HW_RANDOM_OMAP3_ROM) += omap3-rom-rng.o +obj-$(CONFIG_HW_RANDOM_PASEMI) += pasemi-rng.o +obj-$(CONFIG_HW_RANDOM_VIRTIO) += virtio-rng.o +obj-$(CONFIG_HW_RANDOM_TX4939) += tx4939-rng.o +obj-$(CONFIG_HW_RANDOM_MXC_RNGA) += mxc-rnga.o +obj-$(CONFIG_HW_RANDOM_IMX_RNGC) += imx-rngc.o +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_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_ST) += st-rng.o +obj-$(CONFIG_HW_RANDOM_XGENE) += xgene-rng.o +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 +obj-$(CONFIG_HW_RANDOM_KEYSTONE) += ks-sa-rng.o diff --git a/drivers/char/hw_random/amd-rng.c b/drivers/char/hw_random/amd-rng.c new file mode 100644 index 000000000..9959c762d --- /dev/null +++ b/drivers/char/hw_random/amd-rng.c @@ -0,0 +1,211 @@ +/* + * RNG driver for AMD RNGs + * + * Copyright 2005 (c) MontaVista Software, Inc. + * + * with the majority of the code coming from: + * + * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) + * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> + * + * derived from + * + * Hardware driver for the AMD 768 Random Number Generator (RNG) + * (c) Copyright 2001 Red Hat Inc + * + * derived from + * + * 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 <linux/delay.h> +#include <linux/hw_random.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> + +#define DRV_NAME "AMD768-HWRNG" + +#define RNGDATA 0x00 +#define RNGDONE 0x04 +#define PMBASE_OFFSET 0xF0 +#define PMBASE_SIZE 8 + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * register a pci_driver, because someone else might one day + * want to register another driver on the same PCI id. + */ +static const struct pci_device_id pci_tbl[] = { + { PCI_VDEVICE(AMD, 0x7443), 0, }, + { PCI_VDEVICE(AMD, 0x746b), 0, }, + { 0, }, /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, pci_tbl); + +struct amd768_priv { + void __iomem *iobase; + struct pci_dev *pcidev; + u32 pmbase; +}; + +static int amd_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + u32 *data = buf; + struct amd768_priv *priv = (struct amd768_priv *)rng->priv; + size_t read = 0; + /* We will wait at maximum one time per read */ + int timeout = max / 4 + 1; + + /* + * RNG data is available when RNGDONE is set to 1 + * New random numbers are generated approximately 128 microseconds + * after RNGDATA is read + */ + while (read < max) { + if (ioread32(priv->iobase + RNGDONE) == 0) { + if (wait) { + /* Delay given by datasheet */ + usleep_range(128, 196); + if (timeout-- == 0) + return read; + } else { + return 0; + } + } else { + *data = ioread32(priv->iobase + RNGDATA); + data++; + read += 4; + } + } + + return read; +} + +static int amd_rng_init(struct hwrng *rng) +{ + struct amd768_priv *priv = (struct amd768_priv *)rng->priv; + u8 rnen; + + pci_read_config_byte(priv->pcidev, 0x40, &rnen); + rnen |= BIT(7); /* RNG on */ + pci_write_config_byte(priv->pcidev, 0x40, rnen); + + pci_read_config_byte(priv->pcidev, 0x41, &rnen); + rnen |= BIT(7); /* PMIO enable */ + pci_write_config_byte(priv->pcidev, 0x41, rnen); + + return 0; +} + +static void amd_rng_cleanup(struct hwrng *rng) +{ + struct amd768_priv *priv = (struct amd768_priv *)rng->priv; + u8 rnen; + + pci_read_config_byte(priv->pcidev, 0x40, &rnen); + rnen &= ~BIT(7); /* RNG off */ + pci_write_config_byte(priv->pcidev, 0x40, rnen); +} + +static struct hwrng amd_rng = { + .name = "amd", + .init = amd_rng_init, + .cleanup = amd_rng_cleanup, + .read = amd_rng_read, +}; + +static int __init mod_init(void) +{ + int err = -ENODEV; + struct pci_dev *pdev = NULL; + const struct pci_device_id *ent; + u32 pmbase; + struct amd768_priv *priv; + + for_each_pci_dev(pdev) { + ent = pci_match_id(pci_tbl, pdev); + if (ent) + goto found; + } + /* Device not found. */ + return -ENODEV; + +found: + err = pci_read_config_dword(pdev, 0x58, &pmbase); + if (err) + return err; + + pmbase &= 0x0000FF00; + if (pmbase == 0) + return -EIO; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (!request_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE, DRV_NAME)) { + dev_err(&pdev->dev, DRV_NAME " region 0x%x already in use!\n", + pmbase + 0xF0); + err = -EBUSY; + goto out; + } + + priv->iobase = ioport_map(pmbase + PMBASE_OFFSET, PMBASE_SIZE); + if (!priv->iobase) { + pr_err(DRV_NAME "Cannot map ioport\n"); + err = -EINVAL; + goto err_iomap; + } + + amd_rng.priv = (unsigned long)priv; + priv->pmbase = pmbase; + priv->pcidev = pdev; + + pr_info(DRV_NAME " detected\n"); + err = hwrng_register(&amd_rng); + if (err) { + pr_err(DRV_NAME " registering failed (%d)\n", err); + goto err_hwrng; + } + return 0; + +err_hwrng: + ioport_unmap(priv->iobase); +err_iomap: + release_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE); +out: + kfree(priv); + return err; +} + +static void __exit mod_exit(void) +{ + struct amd768_priv *priv; + + priv = (struct amd768_priv *)amd_rng.priv; + + hwrng_unregister(&amd_rng); + + ioport_unmap(priv->iobase); + + release_region(priv->pmbase + PMBASE_OFFSET, PMBASE_SIZE); + + kfree(priv); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("The Linux Kernel team"); +MODULE_DESCRIPTION("H/W RNG driver for AMD chipsets"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/atmel-rng.c b/drivers/char/hw_random/atmel-rng.c new file mode 100644 index 000000000..40e6951ea --- /dev/null +++ b/drivers/char/hw_random/atmel-rng.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2011 Peter Korsgaard <jacmet@sunsite.dk> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/hw_random.h> +#include <linux/platform_device.h> + +#define TRNG_CR 0x00 +#define TRNG_ISR 0x1c +#define TRNG_ODATA 0x50 + +#define TRNG_KEY 0x524e4700 /* RNG */ + +struct atmel_trng { + struct clk *clk; + void __iomem *base; + struct hwrng rng; +}; + +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; + + /* data ready? */ + if (readl(trng->base + TRNG_ISR) & 1) { + *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); + return 4; + } else + return 0; +} + +static void atmel_trng_enable(struct atmel_trng *trng) +{ + writel(TRNG_KEY | 1, trng->base + TRNG_CR); +} + +static void atmel_trng_disable(struct atmel_trng *trng) +{ + writel(TRNG_KEY, trng->base + TRNG_CR); +} + +static int atmel_trng_probe(struct platform_device *pdev) +{ + struct atmel_trng *trng; + struct resource *res; + int ret; + + trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL); + if (!trng) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + trng->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(trng->base)) + return PTR_ERR(trng->base); + + trng->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(trng->clk)) + return PTR_ERR(trng->clk); + + ret = clk_prepare_enable(trng->clk); + if (ret) + return ret; + + atmel_trng_enable(trng); + trng->rng.name = pdev->name; + trng->rng.read = atmel_trng_read; + + ret = hwrng_register(&trng->rng); + if (ret) + goto err_register; + + platform_set_drvdata(pdev, trng); + + return 0; + +err_register: + clk_disable_unprepare(trng->clk); + atmel_trng_disable(trng); + return ret; +} + +static int atmel_trng_remove(struct platform_device *pdev) +{ + struct atmel_trng *trng = platform_get_drvdata(pdev); + + hwrng_unregister(&trng->rng); + + atmel_trng_disable(trng); + clk_disable_unprepare(trng->clk); + + return 0; +} + +#ifdef CONFIG_PM +static int atmel_trng_suspend(struct device *dev) +{ + struct atmel_trng *trng = dev_get_drvdata(dev); + + atmel_trng_disable(trng); + clk_disable_unprepare(trng->clk); + + return 0; +} + +static int atmel_trng_resume(struct device *dev) +{ + struct atmel_trng *trng = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(trng->clk); + if (ret) + return ret; + + atmel_trng_enable(trng); + + return 0; +} + +static const struct dev_pm_ops atmel_trng_pm_ops = { + .suspend = atmel_trng_suspend, + .resume = atmel_trng_resume, +}; +#endif /* CONFIG_PM */ + +static const struct of_device_id atmel_trng_dt_ids[] = { + { .compatible = "atmel,at91sam9g45-trng" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atmel_trng_dt_ids); + +static struct platform_driver atmel_trng_driver = { + .probe = atmel_trng_probe, + .remove = atmel_trng_remove, + .driver = { + .name = "atmel-trng", +#ifdef CONFIG_PM + .pm = &atmel_trng_pm_ops, +#endif /* CONFIG_PM */ + .of_match_table = atmel_trng_dt_ids, + }, +}; + +module_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/char/hw_random/bcm2835-rng.c b/drivers/char/hw_random/bcm2835-rng.c new file mode 100644 index 000000000..19bde680a --- /dev/null +++ b/drivers/char/hw_random/bcm2835-rng.c @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2010-2012 Broadcom. All rights reserved. + * Copyright (c) 2013 Lubomir Rintel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License ("GPL") + * version 2, as published by the Free Software Foundation. + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/clk.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; +}; + +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; + cpu_relax(); + } + + 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; + + if (!IS_ERR(priv->clk)) { + ret = clk_prepare_enable(priv->clk); + 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 hwrng *rng) +{ + struct bcm2835_rng_priv *priv = to_rng_priv(rng); + + /* disable rng hardware */ + rng_writel(priv, 0, RNG_CTRL); + + if (!IS_ERR(priv->clk)) + 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 platform_device *pdev) +{ + const struct bcm2835_rng_of_data *of_data; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct of_device_id *rng_id; + struct bcm2835_rng_priv *priv; + struct resource *r; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + /* map peripheral */ + priv->base = devm_ioremap_resource(dev, r); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + /* Clock is optional on most platforms */ + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk) && PTR_ERR(priv->clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + priv->rng.name = pdev->name; + priv->rng.init = bcm2835_rng_init; + priv->rng.read = bcm2835_rng_read; + priv->rng.cleanup = bcm2835_rng_cleanup; + + if (dev_of_node(dev)) { + rng_id = of_match_node(bcm2835_rng_of_match, np); + if (!rng_id) + return -EINVAL; + + /* Check for rng init function, execute it */ + of_data = rng_id->data; + if (of_data) + priv->mask_interrupts = of_data->mask_interrupts; + } + + /* register driver */ + err = devm_hwrng_register(dev, &priv->rng); + if (err) + dev_err(dev, "hwrng registration failed\n"); + else + dev_info(dev, "hwrng registered\n"); + + return err; +} + +MODULE_DEVICE_TABLE(of, bcm2835_rng_of_match); + +static struct platform_device_id bcm2835_rng_devtype[] = { + { .name = "bcm2835-rng" }, + { .name = "bcm63xx-rng" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, bcm2835_rng_devtype); + +static struct platform_driver bcm2835_rng_driver = { + .driver = { + .name = "bcm2835-rng", + .of_match_table = bcm2835_rng_of_match, + }, + .probe = bcm2835_rng_probe, + .id_table = bcm2835_rng_devtype, +}; +module_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/char/hw_random/cavium-rng-vf.c b/drivers/char/hw_random/cavium-rng-vf.c new file mode 100644 index 000000000..2d1352b67 --- /dev/null +++ b/drivers/char/hw_random/cavium-rng-vf.c @@ -0,0 +1,103 @@ +/* + * Hardware Random Number Generator support for Cavium, Inc. + * Thunder processor family. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2016 Cavium, Inc. + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> + +struct cavium_rng { + struct hwrng ops; + void __iomem *result; +}; + +/* Read data from the RNG unit */ +static int cavium_rng_read(struct hwrng *rng, void *dat, size_t max, bool wait) +{ + struct cavium_rng *p = container_of(rng, struct cavium_rng, ops); + unsigned int size = max; + + while (size >= 8) { + *((u64 *)dat) = readq(p->result); + size -= 8; + dat += 8; + } + while (size > 0) { + *((u8 *)dat) = readb(p->result); + size--; + dat++; + } + return max; +} + +/* Map Cavium RNG to an HWRNG object */ +static int cavium_rng_probe_vf(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct cavium_rng *rng; + int ret; + + rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); + if (!rng) + return -ENOMEM; + + /* Map the RNG result */ + rng->result = pcim_iomap(pdev, 0, 0); + if (!rng->result) { + dev_err(&pdev->dev, "Error iomap failed retrieving result.\n"); + return -ENOMEM; + } + + rng->ops.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "cavium-rng-%s", dev_name(&pdev->dev)); + if (!rng->ops.name) + return -ENOMEM; + + rng->ops.read = cavium_rng_read; + rng->ops.quality = 1000; + + pci_set_drvdata(pdev, rng); + + ret = hwrng_register(&rng->ops); + if (ret) { + dev_err(&pdev->dev, "Error registering device as HWRNG.\n"); + return ret; + } + + return 0; +} + +/* Remove the VF */ +static void cavium_rng_remove_vf(struct pci_dev *pdev) +{ + struct cavium_rng *rng; + + rng = pci_get_drvdata(pdev); + hwrng_unregister(&rng->ops); +} + +static const struct pci_device_id cavium_rng_vf_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa033), 0, 0, 0}, + {0,}, +}; +MODULE_DEVICE_TABLE(pci, cavium_rng_vf_id_table); + +static struct pci_driver cavium_rng_vf_driver = { + .name = "cavium_rng_vf", + .id_table = cavium_rng_vf_id_table, + .probe = cavium_rng_probe_vf, + .remove = cavium_rng_remove_vf, +}; +module_pci_driver(cavium_rng_vf_driver); + +MODULE_AUTHOR("Omer Khaliq <okhaliq@caviumnetworks.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/cavium-rng.c b/drivers/char/hw_random/cavium-rng.c new file mode 100644 index 000000000..63d6e68c2 --- /dev/null +++ b/drivers/char/hw_random/cavium-rng.c @@ -0,0 +1,94 @@ +/* + * Hardware Random Number Generator support for Cavium Inc. + * Thunder processor family. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2016 Cavium, Inc. + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> + +#define THUNDERX_RNM_ENT_EN 0x1 +#define THUNDERX_RNM_RNG_EN 0x2 + +struct cavium_rng_pf { + void __iomem *control_status; +}; + +/* Enable the RNG hardware and activate the VF */ +static int cavium_rng_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct cavium_rng_pf *rng; + int iov_err; + + rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); + if (!rng) + return -ENOMEM; + + /*Map the RNG control */ + rng->control_status = pcim_iomap(pdev, 0, 0); + if (!rng->control_status) { + dev_err(&pdev->dev, + "Error iomap failed retrieving control_status.\n"); + return -ENOMEM; + } + + /* Enable the RNG hardware and entropy source */ + writeq(THUNDERX_RNM_RNG_EN | THUNDERX_RNM_ENT_EN, + rng->control_status); + + pci_set_drvdata(pdev, rng); + + /* Enable the Cavium RNG as a VF */ + iov_err = pci_enable_sriov(pdev, 1); + if (iov_err != 0) { + /* Disable the RNG hardware and entropy source */ + writeq(0, rng->control_status); + dev_err(&pdev->dev, + "Error initializing RNG virtual function,(%i).\n", + iov_err); + return iov_err; + } + + return 0; +} + +/* Disable VF and RNG Hardware */ +static void cavium_rng_remove(struct pci_dev *pdev) +{ + struct cavium_rng_pf *rng; + + rng = pci_get_drvdata(pdev); + + /* Remove the VF */ + pci_disable_sriov(pdev); + + /* Disable the RNG hardware and entropy source */ + writeq(0, rng->control_status); +} + +static const struct pci_device_id cavium_rng_pf_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa018), 0, 0, 0}, /* Thunder RNM */ + {0,}, +}; + +MODULE_DEVICE_TABLE(pci, cavium_rng_pf_id_table); + +static struct pci_driver cavium_rng_pf_driver = { + .name = "cavium_rng_pf", + .id_table = cavium_rng_pf_id_table, + .probe = cavium_rng_probe, + .remove = cavium_rng_remove, +}; + +module_pci_driver(cavium_rng_pf_driver); +MODULE_AUTHOR("Omer Khaliq <okhaliq@caviumnetworks.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c new file mode 100644 index 000000000..c9757fa2d --- /dev/null +++ b/drivers/char/hw_random/core.c @@ -0,0 +1,625 @@ +/* + * hw_random/core.c: HWRNG core API + * + * Copyright 2006 Michael Buesch <m@bues.ch> + * Copyright 2005 (c) MontaVista Software, Inc. + * + * Please read Documentation/hw_random.txt for details on use. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/hw_random.h> +#include <linux/random.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/sched/signal.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/random.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#define RNG_MODULE_NAME "hw_random" + +static struct hwrng *current_rng; +/* the current rng has been explicitly chosen by user via sysfs */ +static int cur_rng_set_by_user; +static struct task_struct *hwrng_fill; +/* list of registered rngs, sorted decending by quality */ +static LIST_HEAD(rng_list); +/* Protects rng_list and current_rng */ +static DEFINE_MUTEX(rng_mutex); +/* Protects rng read functions, data_avail, rng_buffer and rng_fillbuf */ +static DEFINE_MUTEX(reading_mutex); +static int data_avail; +static u8 *rng_buffer, *rng_fillbuf; +static unsigned short current_quality; +static unsigned short default_quality; /* = 0; default to "off" */ + +module_param(current_quality, ushort, 0644); +MODULE_PARM_DESC(current_quality, + "current hwrng entropy estimation per mill"); +module_param(default_quality, ushort, 0644); +MODULE_PARM_DESC(default_quality, + "default entropy content of hwrng per mill"); + +static void drop_current_rng(void); +static int hwrng_init(struct hwrng *rng); +static void start_khwrngd(void); + +static inline int rng_get_data(struct hwrng *rng, u8 *buffer, size_t size, + int wait); + +static size_t rng_buffer_size(void) +{ + return SMP_CACHE_BYTES < 32 ? 32 : SMP_CACHE_BYTES; +} + +static void add_early_randomness(struct hwrng *rng) +{ + int bytes_read; + size_t size = min_t(size_t, 16, rng_buffer_size()); + + mutex_lock(&reading_mutex); + bytes_read = rng_get_data(rng, rng_buffer, size, 0); + mutex_unlock(&reading_mutex); + if (bytes_read > 0) + add_device_randomness(rng_buffer, bytes_read); +} + +static inline void cleanup_rng(struct kref *kref) +{ + struct hwrng *rng = container_of(kref, struct hwrng, ref); + + if (rng->cleanup) + rng->cleanup(rng); + + complete(&rng->cleanup_done); +} + +static int set_current_rng(struct hwrng *rng) +{ + int err; + + BUG_ON(!mutex_is_locked(&rng_mutex)); + + err = hwrng_init(rng); + if (err) + return err; + + drop_current_rng(); + current_rng = rng; + + return 0; +} + +static void drop_current_rng(void) +{ + BUG_ON(!mutex_is_locked(&rng_mutex)); + if (!current_rng) + return; + + /* decrease last reference for triggering the cleanup */ + kref_put(¤t_rng->ref, cleanup_rng); + current_rng = NULL; +} + +/* Returns ERR_PTR(), NULL or refcounted hwrng */ +static struct hwrng *get_current_rng(void) +{ + struct hwrng *rng; + + if (mutex_lock_interruptible(&rng_mutex)) + return ERR_PTR(-ERESTARTSYS); + + rng = current_rng; + if (rng) + kref_get(&rng->ref); + + mutex_unlock(&rng_mutex); + return rng; +} + +static void put_rng(struct hwrng *rng) +{ + /* + * Hold rng_mutex here so we serialize in case they set_current_rng + * on rng again immediately. + */ + mutex_lock(&rng_mutex); + if (rng) + kref_put(&rng->ref, cleanup_rng); + mutex_unlock(&rng_mutex); +} + +static int hwrng_init(struct hwrng *rng) +{ + if (kref_get_unless_zero(&rng->ref)) + goto skip_init; + + if (rng->init) { + int ret; + + ret = rng->init(rng); + if (ret) + return ret; + } + + kref_init(&rng->ref); + reinit_completion(&rng->cleanup_done); + +skip_init: + add_early_randomness(rng); + + current_quality = rng->quality ? : default_quality; + if (current_quality > 1024) + current_quality = 1024; + + if (current_quality == 0 && hwrng_fill) + kthread_stop(hwrng_fill); + if (current_quality > 0 && !hwrng_fill) + start_khwrngd(); + + return 0; +} + +static int rng_dev_open(struct inode *inode, struct file *filp) +{ + /* enforce read-only access to this chrdev */ + if ((filp->f_mode & FMODE_READ) == 0) + return -EINVAL; + if (filp->f_mode & FMODE_WRITE) + return -EINVAL; + return 0; +} + +static inline int rng_get_data(struct hwrng *rng, u8 *buffer, size_t size, + int wait) { + int present; + + BUG_ON(!mutex_is_locked(&reading_mutex)); + if (rng->read) + return rng->read(rng, (void *)buffer, size, wait); + + if (rng->data_present) + present = rng->data_present(rng, wait); + else + present = 1; + + if (present) + return rng->data_read(rng, (u32 *)buffer); + + return 0; +} + +static ssize_t rng_dev_read(struct file *filp, char __user *buf, + size_t size, loff_t *offp) +{ + ssize_t ret = 0; + int err = 0; + int bytes_read, len; + struct hwrng *rng; + + while (size) { + rng = get_current_rng(); + if (IS_ERR(rng)) { + err = PTR_ERR(rng); + goto out; + } + if (!rng) { + err = -ENODEV; + goto out; + } + + if (mutex_lock_interruptible(&reading_mutex)) { + err = -ERESTARTSYS; + goto out_put; + } + if (!data_avail) { + bytes_read = rng_get_data(rng, rng_buffer, + rng_buffer_size(), + !(filp->f_flags & O_NONBLOCK)); + if (bytes_read < 0) { + err = bytes_read; + goto out_unlock_reading; + } + data_avail = bytes_read; + } + + if (!data_avail) { + if (filp->f_flags & O_NONBLOCK) { + err = -EAGAIN; + goto out_unlock_reading; + } + } else { + len = data_avail; + if (len > size) + len = size; + + data_avail -= len; + + if (copy_to_user(buf + ret, rng_buffer + data_avail, + len)) { + err = -EFAULT; + goto out_unlock_reading; + } + + size -= len; + ret += len; + } + + mutex_unlock(&reading_mutex); + put_rng(rng); + + if (need_resched()) + schedule_timeout_interruptible(1); + + if (signal_pending(current)) { + err = -ERESTARTSYS; + goto out; + } + } +out: + return ret ? : err; + +out_unlock_reading: + mutex_unlock(&reading_mutex); +out_put: + put_rng(rng); + goto out; +} + +static const struct file_operations rng_chrdev_ops = { + .owner = THIS_MODULE, + .open = rng_dev_open, + .read = rng_dev_read, + .llseek = noop_llseek, +}; + +static const struct attribute_group *rng_dev_groups[]; + +static struct miscdevice rng_miscdev = { + .minor = HWRNG_MINOR, + .name = RNG_MODULE_NAME, + .nodename = "hwrng", + .fops = &rng_chrdev_ops, + .groups = rng_dev_groups, +}; + +static int enable_best_rng(void) +{ + int ret = -ENODEV; + + BUG_ON(!mutex_is_locked(&rng_mutex)); + + /* rng_list is sorted by quality, use the best (=first) one */ + if (!list_empty(&rng_list)) { + struct hwrng *new_rng; + + new_rng = list_entry(rng_list.next, struct hwrng, list); + ret = ((new_rng == current_rng) ? 0 : set_current_rng(new_rng)); + if (!ret) + cur_rng_set_by_user = 0; + } else { + drop_current_rng(); + cur_rng_set_by_user = 0; + ret = 0; + } + + return ret; +} + +static ssize_t hwrng_attr_current_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int err = -ENODEV; + struct hwrng *rng; + + err = mutex_lock_interruptible(&rng_mutex); + if (err) + return -ERESTARTSYS; + + if (sysfs_streq(buf, "")) { + err = enable_best_rng(); + } else { + list_for_each_entry(rng, &rng_list, list) { + if (sysfs_streq(rng->name, buf)) { + cur_rng_set_by_user = 1; + err = set_current_rng(rng); + break; + } + } + } + + mutex_unlock(&rng_mutex); + + return err ? : len; +} + +static ssize_t hwrng_attr_current_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + struct hwrng *rng; + + rng = get_current_rng(); + if (IS_ERR(rng)) + return PTR_ERR(rng); + + ret = snprintf(buf, PAGE_SIZE, "%s\n", rng ? rng->name : "none"); + put_rng(rng); + + return ret; +} + +static ssize_t hwrng_attr_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err; + struct hwrng *rng; + + err = mutex_lock_interruptible(&rng_mutex); + if (err) + return -ERESTARTSYS; + buf[0] = '\0'; + list_for_each_entry(rng, &rng_list, list) { + strlcat(buf, rng->name, PAGE_SIZE); + strlcat(buf, " ", PAGE_SIZE); + } + strlcat(buf, "\n", PAGE_SIZE); + mutex_unlock(&rng_mutex); + + return strlen(buf); +} + +static ssize_t hwrng_attr_selected_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", cur_rng_set_by_user); +} + +static DEVICE_ATTR(rng_current, S_IRUGO | S_IWUSR, + hwrng_attr_current_show, + hwrng_attr_current_store); +static DEVICE_ATTR(rng_available, S_IRUGO, + hwrng_attr_available_show, + NULL); +static DEVICE_ATTR(rng_selected, S_IRUGO, + hwrng_attr_selected_show, + NULL); + +static struct attribute *rng_dev_attrs[] = { + &dev_attr_rng_current.attr, + &dev_attr_rng_available.attr, + &dev_attr_rng_selected.attr, + NULL +}; + +ATTRIBUTE_GROUPS(rng_dev); + +static void __exit unregister_miscdev(void) +{ + misc_deregister(&rng_miscdev); +} + +static int __init register_miscdev(void) +{ + return misc_register(&rng_miscdev); +} + +static int hwrng_fillfn(void *unused) +{ + long rc; + + while (!kthread_should_stop()) { + struct hwrng *rng; + + rng = get_current_rng(); + if (IS_ERR(rng) || !rng) + break; + mutex_lock(&reading_mutex); + rc = rng_get_data(rng, rng_fillbuf, + rng_buffer_size(), 1); + mutex_unlock(&reading_mutex); + put_rng(rng); + if (rc <= 0) { + pr_warn("hwrng: no data available\n"); + msleep_interruptible(10000); + continue; + } + /* Outside lock, sure, but y'know: randomness. */ + add_hwgenerator_randomness((void *)rng_fillbuf, rc, + rc * current_quality * 8 >> 10); + } + hwrng_fill = NULL; + return 0; +} + +static void start_khwrngd(void) +{ + hwrng_fill = kthread_run(hwrng_fillfn, NULL, "hwrng"); + if (IS_ERR(hwrng_fill)) { + pr_err("hwrng_fill thread creation failed\n"); + hwrng_fill = NULL; + } +} + +int hwrng_register(struct hwrng *rng) +{ + int err = -EINVAL; + struct hwrng *old_rng, *tmp; + struct list_head *rng_list_ptr; + + if (!rng->name || (!rng->data_read && !rng->read)) + goto out; + + mutex_lock(&rng_mutex); + /* Must not register two RNGs with the same name. */ + err = -EEXIST; + list_for_each_entry(tmp, &rng_list, list) { + if (strcmp(tmp->name, rng->name) == 0) + goto out_unlock; + } + + init_completion(&rng->cleanup_done); + complete(&rng->cleanup_done); + + /* rng_list is sorted by decreasing quality */ + list_for_each(rng_list_ptr, &rng_list) { + tmp = list_entry(rng_list_ptr, struct hwrng, list); + if (tmp->quality < rng->quality) + break; + } + list_add_tail(&rng->list, rng_list_ptr); + + old_rng = current_rng; + err = 0; + if (!old_rng || + (!cur_rng_set_by_user && rng->quality > old_rng->quality)) { + /* + * Set new rng as current as the new rng source + * provides better entropy quality and was not + * chosen by userspace. + */ + err = set_current_rng(rng); + if (err) + goto out_unlock; + } + + if (old_rng && !rng->init) { + /* + * Use a new device's input to add some randomness to + * the system. If this rng device isn't going to be + * used right away, its init function hasn't been + * called yet; so only use the randomness from devices + * that don't need an init callback. + */ + add_early_randomness(rng); + } + +out_unlock: + mutex_unlock(&rng_mutex); +out: + return err; +} +EXPORT_SYMBOL_GPL(hwrng_register); + +void hwrng_unregister(struct hwrng *rng) +{ + int err; + + mutex_lock(&rng_mutex); + + list_del(&rng->list); + if (current_rng == rng) { + err = enable_best_rng(); + if (err) { + drop_current_rng(); + cur_rng_set_by_user = 0; + } + } + + if (list_empty(&rng_list)) { + mutex_unlock(&rng_mutex); + if (hwrng_fill) + kthread_stop(hwrng_fill); + } else + mutex_unlock(&rng_mutex); + + wait_for_completion(&rng->cleanup_done); +} +EXPORT_SYMBOL_GPL(hwrng_unregister); + +static void devm_hwrng_release(struct device *dev, void *res) +{ + hwrng_unregister(*(struct hwrng **)res); +} + +static int devm_hwrng_match(struct device *dev, void *res, void *data) +{ + struct hwrng **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +int devm_hwrng_register(struct device *dev, struct hwrng *rng) +{ + struct hwrng **ptr; + int error; + + ptr = devres_alloc(devm_hwrng_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + error = hwrng_register(rng); + if (error) { + devres_free(ptr); + return error; + } + + *ptr = rng; + devres_add(dev, ptr); + return 0; +} +EXPORT_SYMBOL_GPL(devm_hwrng_register); + +void devm_hwrng_unregister(struct device *dev, struct hwrng *rng) +{ + devres_release(dev, devm_hwrng_release, devm_hwrng_match, rng); +} +EXPORT_SYMBOL_GPL(devm_hwrng_unregister); + +static int __init hwrng_modinit(void) +{ + int ret = -ENOMEM; + + /* kmalloc makes this safe for virt_to_page() in virtio_rng.c */ + rng_buffer = kmalloc(rng_buffer_size(), GFP_KERNEL); + if (!rng_buffer) + return -ENOMEM; + + rng_fillbuf = kmalloc(rng_buffer_size(), GFP_KERNEL); + if (!rng_fillbuf) { + kfree(rng_buffer); + return -ENOMEM; + } + + ret = register_miscdev(); + if (ret) { + kfree(rng_fillbuf); + kfree(rng_buffer); + } + + return ret; +} + +static void __exit hwrng_modexit(void) +{ + mutex_lock(&rng_mutex); + BUG_ON(current_rng); + kfree(rng_buffer); + kfree(rng_fillbuf); + mutex_unlock(&rng_mutex); + + unregister_miscdev(); +} + +module_init(hwrng_modinit); +module_exit(hwrng_modexit); + +MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/exynos-trng.c b/drivers/char/hw_random/exynos-trng.c new file mode 100644 index 000000000..942357619 --- /dev/null +++ b/drivers/char/hw_random/exynos-trng.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RNG driver for Exynos TRNGs + * + * Author: Łukasz Stelmach <l.stelmach@samsung.com> + * + * Copyright 2017 (c) Samsung Electronics Software, Inc. + * + * Based on the Exynos PRNG driver drivers/crypto/exynos-rng by + * Krzysztof Kozłowski <krzk@kernel.org> + */ + +#include <linux/clk.h> +#include <linux/crypto.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/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#define EXYNOS_TRNG_CLKDIV (0x0) + +#define EXYNOS_TRNG_CTRL (0x20) +#define EXYNOS_TRNG_CTRL_RNGEN BIT(31) + +#define EXYNOS_TRNG_POST_CTRL (0x30) +#define EXYNOS_TRNG_ONLINE_CTRL (0x40) +#define EXYNOS_TRNG_ONLINE_STAT (0x44) +#define EXYNOS_TRNG_ONLINE_MAXCHI2 (0x48) +#define EXYNOS_TRNG_FIFO_CTRL (0x50) +#define EXYNOS_TRNG_FIFO_0 (0x80) +#define EXYNOS_TRNG_FIFO_1 (0x84) +#define EXYNOS_TRNG_FIFO_2 (0x88) +#define EXYNOS_TRNG_FIFO_3 (0x8c) +#define EXYNOS_TRNG_FIFO_4 (0x90) +#define EXYNOS_TRNG_FIFO_5 (0x94) +#define EXYNOS_TRNG_FIFO_6 (0x98) +#define EXYNOS_TRNG_FIFO_7 (0x9c) +#define EXYNOS_TRNG_FIFO_LEN (8) +#define EXYNOS_TRNG_CLOCK_RATE (500000) + + +struct exynos_trng_dev { + struct device *dev; + void __iomem *mem; + struct clk *clk; + struct hwrng rng; +}; + +static int exynos_trng_do_read(struct hwrng *rng, void *data, size_t max, + bool wait) +{ + struct exynos_trng_dev *trng; + int val; + + max = min_t(size_t, max, (EXYNOS_TRNG_FIFO_LEN * 4)); + + trng = (struct exynos_trng_dev *)rng->priv; + + writel_relaxed(max * 8, trng->mem + EXYNOS_TRNG_FIFO_CTRL); + val = readl_poll_timeout(trng->mem + EXYNOS_TRNG_FIFO_CTRL, val, + val == 0, 200, 1000000); + if (val < 0) + return val; + + memcpy_fromio(data, trng->mem + EXYNOS_TRNG_FIFO_0, max); + + return max; +} + +static int exynos_trng_init(struct hwrng *rng) +{ + struct exynos_trng_dev *trng = (struct exynos_trng_dev *)rng->priv; + unsigned long sss_rate; + u32 val; + + sss_rate = clk_get_rate(trng->clk); + + /* + * For most TRNG circuits the clock frequency of under 500 kHz + * is safe. + */ + val = sss_rate / (EXYNOS_TRNG_CLOCK_RATE * 2); + if (val > 0x7fff) { + dev_err(trng->dev, "clock divider too large: %d", val); + return -ERANGE; + } + val = val << 1; + writel_relaxed(val, trng->mem + EXYNOS_TRNG_CLKDIV); + + /* Enable the generator. */ + val = EXYNOS_TRNG_CTRL_RNGEN; + writel_relaxed(val, trng->mem + EXYNOS_TRNG_CTRL); + + /* + * Disable post-processing. /dev/hwrng is supposed to deliver + * unprocessed data. + */ + writel_relaxed(0, trng->mem + EXYNOS_TRNG_POST_CTRL); + + return 0; +} + +static int exynos_trng_probe(struct platform_device *pdev) +{ + struct exynos_trng_dev *trng; + struct resource *res; + int ret = -ENOMEM; + + trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL); + if (!trng) + return ret; + + trng->rng.name = devm_kstrdup(&pdev->dev, dev_name(&pdev->dev), + GFP_KERNEL); + if (!trng->rng.name) + return ret; + + trng->rng.init = exynos_trng_init; + trng->rng.read = exynos_trng_do_read; + trng->rng.priv = (unsigned long) trng; + + platform_set_drvdata(pdev, trng); + trng->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + trng->mem = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(trng->mem)) + return PTR_ERR(trng->mem); + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "Could not get runtime PM.\n"); + goto err_pm_get; + } + + trng->clk = devm_clk_get(&pdev->dev, "secss"); + if (IS_ERR(trng->clk)) { + ret = PTR_ERR(trng->clk); + dev_err(&pdev->dev, "Could not get clock.\n"); + goto err_clock; + } + + ret = clk_prepare_enable(trng->clk); + if (ret) { + dev_err(&pdev->dev, "Could not enable the clk.\n"); + goto err_clock; + } + + ret = hwrng_register(&trng->rng); + if (ret) { + dev_err(&pdev->dev, "Could not register hwrng device.\n"); + goto err_register; + } + + dev_info(&pdev->dev, "Exynos True Random Number Generator.\n"); + + return 0; + +err_register: + clk_disable_unprepare(trng->clk); + +err_clock: + pm_runtime_put_sync(&pdev->dev); + +err_pm_get: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int exynos_trng_remove(struct platform_device *pdev) +{ + struct exynos_trng_dev *trng = platform_get_drvdata(pdev); + + hwrng_unregister(&trng->rng); + clk_disable_unprepare(trng->clk); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused exynos_trng_suspend(struct device *dev) +{ + pm_runtime_put_sync(dev); + + return 0; +} + +static int __maybe_unused exynos_trng_resume(struct device *dev) +{ + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "Could not get runtime PM.\n"); + pm_runtime_put_noidle(dev); + return ret; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(exynos_trng_pm_ops, exynos_trng_suspend, + exynos_trng_resume); + +static const struct of_device_id exynos_trng_dt_match[] = { + { + .compatible = "samsung,exynos5250-trng", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos_trng_dt_match); + +static struct platform_driver exynos_trng_driver = { + .driver = { + .name = "exynos-trng", + .pm = &exynos_trng_pm_ops, + .of_match_table = exynos_trng_dt_match, + }, + .probe = exynos_trng_probe, + .remove = exynos_trng_remove, +}; + +module_platform_driver(exynos_trng_driver); +MODULE_AUTHOR("Łukasz Stelmach"); +MODULE_DESCRIPTION("H/W TRNG driver for Exynos chips"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/hw_random/geode-rng.c b/drivers/char/hw_random/geode-rng.c new file mode 100644 index 000000000..e1d421a36 --- /dev/null +++ b/drivers/char/hw_random/geode-rng.c @@ -0,0 +1,139 @@ +/* + * RNG driver for AMD Geode RNGs + * + * Copyright 2005 (c) MontaVista Software, Inc. + * + * with the majority of the code coming from: + * + * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) + * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> + * + * derived from + * + * Hardware driver for the AMD 768 Random Number Generator (RNG) + * (c) Copyright 2001 Red Hat Inc + * + * derived from + * + * 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 <linux/delay.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> + + +#define PFX KBUILD_MODNAME ": " + +#define GEODE_RNG_DATA_REG 0x50 +#define GEODE_RNG_STATUS_REG 0x54 + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * register a pci_driver, because someone else might one day + * want to register another driver on the same PCI id. + */ +static const struct pci_device_id pci_tbl[] = { + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_LX_AES), 0, }, + { 0, }, /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, pci_tbl); + + +static int geode_rng_data_read(struct hwrng *rng, u32 *data) +{ + void __iomem *mem = (void __iomem *)rng->priv; + + *data = readl(mem + GEODE_RNG_DATA_REG); + + return 4; +} + +static int geode_rng_data_present(struct hwrng *rng, int wait) +{ + void __iomem *mem = (void __iomem *)rng->priv; + int data, i; + + for (i = 0; i < 20; i++) { + data = !!(readl(mem + GEODE_RNG_STATUS_REG)); + if (data || !wait) + break; + udelay(10); + } + return data; +} + + +static struct hwrng geode_rng = { + .name = "geode", + .data_present = geode_rng_data_present, + .data_read = geode_rng_data_read, +}; + + +static int __init mod_init(void) +{ + int err = -ENODEV; + struct pci_dev *pdev = NULL; + const struct pci_device_id *ent; + void __iomem *mem; + unsigned long rng_base; + + for_each_pci_dev(pdev) { + ent = pci_match_id(pci_tbl, pdev); + if (ent) + goto found; + } + /* Device not found. */ + goto out; + +found: + rng_base = pci_resource_start(pdev, 0); + if (rng_base == 0) + goto out; + err = -ENOMEM; + mem = ioremap(rng_base, 0x58); + if (!mem) + goto out; + geode_rng.priv = (unsigned long)mem; + + pr_info("AMD Geode RNG detected\n"); + err = hwrng_register(&geode_rng); + if (err) { + pr_err(PFX "RNG registering failed (%d)\n", + err); + goto err_unmap; + } +out: + return err; + +err_unmap: + iounmap(mem); + goto out; +} + +static void __exit mod_exit(void) +{ + void __iomem *mem = (void __iomem *)geode_rng.priv; + + hwrng_unregister(&geode_rng); + iounmap(mem); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_DESCRIPTION("H/W RNG driver for AMD Geode LX CPUs"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/hisi-rng.c b/drivers/char/hw_random/hisi-rng.c new file mode 100644 index 000000000..40d96572c --- /dev/null +++ b/drivers/char/hw_random/hisi-rng.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2016 HiSilicon Co., Ltd. + * + * 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. + */ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/random.h> + +#define RNG_SEED 0x0 +#define RNG_CTRL 0x4 + #define RNG_SEED_SEL BIT(2) + #define RNG_RING_EN BIT(1) + #define RNG_EN BIT(0) +#define RNG_RAN_NUM 0x10 +#define RNG_PHY_SEED 0x14 + +#define to_hisi_rng(p) container_of(p, struct hisi_rng, rng) + +static int seed_sel; +module_param(seed_sel, int, S_IRUGO); +MODULE_PARM_DESC(seed_sel, "Auto reload seed. 0, use LFSR(default); 1, use ring oscillator."); + +struct hisi_rng { + void __iomem *base; + struct hwrng rng; +}; + +static int hisi_rng_init(struct hwrng *rng) +{ + struct hisi_rng *hrng = to_hisi_rng(rng); + int val = RNG_EN; + u32 seed; + + /* get a random number as initial seed */ + get_random_bytes(&seed, sizeof(seed)); + + writel_relaxed(seed, hrng->base + RNG_SEED); + + /** + * The seed is reload periodically, there are two choice + * of seeds, default seed using the value from LFSR, or + * will use seed generated by ring oscillator. + */ + if (seed_sel == 1) + val |= RNG_RING_EN | RNG_SEED_SEL; + + writel_relaxed(val, hrng->base + RNG_CTRL); + return 0; +} + +static void hisi_rng_cleanup(struct hwrng *rng) +{ + struct hisi_rng *hrng = to_hisi_rng(rng); + + writel_relaxed(0, hrng->base + RNG_CTRL); +} + +static int hisi_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct hisi_rng *hrng = to_hisi_rng(rng); + u32 *data = buf; + + *data = readl_relaxed(hrng->base + RNG_RAN_NUM); + return 4; +} + +static int hisi_rng_probe(struct platform_device *pdev) +{ + struct hisi_rng *rng; + struct resource *res; + int ret; + + rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); + if (!rng) + return -ENOMEM; + + platform_set_drvdata(pdev, rng); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rng->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rng->base)) + return PTR_ERR(rng->base); + + rng->rng.name = pdev->name; + rng->rng.init = hisi_rng_init; + rng->rng.cleanup = hisi_rng_cleanup; + rng->rng.read = hisi_rng_read; + + ret = devm_hwrng_register(&pdev->dev, &rng->rng); + if (ret) { + dev_err(&pdev->dev, "failed to register hwrng\n"); + return ret; + } + + return 0; +} + +static const struct of_device_id hisi_rng_dt_ids[] = { + { .compatible = "hisilicon,hip04-rng" }, + { .compatible = "hisilicon,hip05-rng" }, + { } +}; +MODULE_DEVICE_TABLE(of, hisi_rng_dt_ids); + +static struct platform_driver hisi_rng_driver = { + .probe = hisi_rng_probe, + .driver = { + .name = "hisi-rng", + .of_match_table = of_match_ptr(hisi_rng_dt_ids), + }, +}; + +module_platform_driver(hisi_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kefeng Wang <wangkefeng.wang@huawei>"); +MODULE_DESCRIPTION("Hisilicon random number generator driver"); diff --git a/drivers/char/hw_random/imx-rngc.c b/drivers/char/hw_random/imx-rngc.c new file mode 100644 index 000000000..dc9b8f377 --- /dev/null +++ b/drivers/char/hw_random/imx-rngc.c @@ -0,0 +1,327 @@ +/* + * RNG driver for Freescale RNGC + * + * Copyright (C) 2008-2012 Freescale Semiconductor, Inc. + * Copyright (C) 2017 Martin Kaiser <martin@kaiser.cx> + * + * 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 + */ + +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/hw_random.h> +#include <linux/completion.h> +#include <linux/io.h> + +#define RNGC_COMMAND 0x0004 +#define RNGC_CONTROL 0x0008 +#define RNGC_STATUS 0x000C +#define RNGC_ERROR 0x0010 +#define RNGC_FIFO 0x0014 + +#define RNGC_CMD_CLR_ERR 0x00000020 +#define RNGC_CMD_CLR_INT 0x00000010 +#define RNGC_CMD_SEED 0x00000002 +#define RNGC_CMD_SELF_TEST 0x00000001 + +#define RNGC_CTRL_MASK_ERROR 0x00000040 +#define RNGC_CTRL_MASK_DONE 0x00000020 + +#define RNGC_STATUS_ERROR 0x00010000 +#define RNGC_STATUS_FIFO_LEVEL_MASK 0x00000f00 +#define RNGC_STATUS_FIFO_LEVEL_SHIFT 8 +#define RNGC_STATUS_SEED_DONE 0x00000020 +#define RNGC_STATUS_ST_DONE 0x00000010 + +#define RNGC_ERROR_STATUS_STAT_ERR 0x00000008 + +#define RNGC_TIMEOUT 3000 /* 3 sec */ + + +static bool self_test = true; +module_param(self_test, bool, 0); + +struct imx_rngc { + struct device *dev; + struct clk *clk; + void __iomem *base; + struct hwrng rng; + struct completion rng_op_done; + /* + * err_reg is written only by the irq handler and read only + * when interrupts are masked, we need no spinlock + */ + u32 err_reg; +}; + + +static inline void imx_rngc_irq_mask_clear(struct imx_rngc *rngc) +{ + u32 ctrl, cmd; + + /* mask interrupts */ + ctrl = readl(rngc->base + RNGC_CONTROL); + ctrl |= RNGC_CTRL_MASK_DONE | RNGC_CTRL_MASK_ERROR; + writel(ctrl, rngc->base + RNGC_CONTROL); + + /* + * CLR_INT clears the interrupt only if there's no error + * CLR_ERR clear the interrupt and the error register if there + * is an error + */ + cmd = readl(rngc->base + RNGC_COMMAND); + cmd |= RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR; + writel(cmd, rngc->base + RNGC_COMMAND); +} + +static inline void imx_rngc_irq_unmask(struct imx_rngc *rngc) +{ + u32 ctrl; + + ctrl = readl(rngc->base + RNGC_CONTROL); + ctrl &= ~(RNGC_CTRL_MASK_DONE | RNGC_CTRL_MASK_ERROR); + writel(ctrl, rngc->base + RNGC_CONTROL); +} + +static int imx_rngc_self_test(struct imx_rngc *rngc) +{ + u32 cmd; + int ret; + + imx_rngc_irq_unmask(rngc); + + /* run self test */ + cmd = readl(rngc->base + RNGC_COMMAND); + writel(cmd | RNGC_CMD_SELF_TEST, rngc->base + RNGC_COMMAND); + + ret = wait_for_completion_timeout(&rngc->rng_op_done, RNGC_TIMEOUT); + if (!ret) { + imx_rngc_irq_mask_clear(rngc); + return -ETIMEDOUT; + } + + if (rngc->err_reg != 0) { + imx_rngc_irq_mask_clear(rngc); + return -EIO; + } + + return 0; +} + +static int imx_rngc_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + struct imx_rngc *rngc = container_of(rng, struct imx_rngc, rng); + unsigned int status; + unsigned int level; + int retval = 0; + + while (max >= sizeof(u32)) { + status = readl(rngc->base + RNGC_STATUS); + + /* is there some error while reading this random number? */ + if (status & RNGC_STATUS_ERROR) + break; + + /* how many random numbers are in FIFO? [0-16] */ + level = (status & RNGC_STATUS_FIFO_LEVEL_MASK) >> + RNGC_STATUS_FIFO_LEVEL_SHIFT; + + if (level) { + /* retrieve a random number from FIFO */ + *(u32 *)data = readl(rngc->base + RNGC_FIFO); + + retval += sizeof(u32); + data += sizeof(u32); + max -= sizeof(u32); + } + } + + return retval ? retval : -EIO; +} + +static irqreturn_t imx_rngc_irq(int irq, void *priv) +{ + struct imx_rngc *rngc = (struct imx_rngc *)priv; + u32 status; + + /* + * clearing the interrupt will also clear the error register + * read error and status before clearing + */ + status = readl(rngc->base + RNGC_STATUS); + rngc->err_reg = readl(rngc->base + RNGC_ERROR); + + imx_rngc_irq_mask_clear(rngc); + + if (status & (RNGC_STATUS_SEED_DONE | RNGC_STATUS_ST_DONE)) + complete(&rngc->rng_op_done); + + return IRQ_HANDLED; +} + +static int imx_rngc_init(struct hwrng *rng) +{ + struct imx_rngc *rngc = container_of(rng, struct imx_rngc, rng); + u32 cmd; + int ret; + + /* clear error */ + cmd = readl(rngc->base + RNGC_COMMAND); + writel(cmd | RNGC_CMD_CLR_ERR, rngc->base + RNGC_COMMAND); + + /* create seed, repeat while there is some statistical error */ + do { + imx_rngc_irq_unmask(rngc); + + /* seed creation */ + cmd = readl(rngc->base + RNGC_COMMAND); + writel(cmd | RNGC_CMD_SEED, rngc->base + RNGC_COMMAND); + + ret = wait_for_completion_timeout(&rngc->rng_op_done, + RNGC_TIMEOUT); + + if (!ret) { + imx_rngc_irq_mask_clear(rngc); + return -ETIMEDOUT; + } + + } while (rngc->err_reg == RNGC_ERROR_STATUS_STAT_ERR); + + return rngc->err_reg ? -EIO : 0; +} + +static int imx_rngc_probe(struct platform_device *pdev) +{ + struct imx_rngc *rngc; + struct resource *res; + int ret; + int irq; + + rngc = devm_kzalloc(&pdev->dev, sizeof(*rngc), GFP_KERNEL); + if (!rngc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rngc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rngc->base)) + return PTR_ERR(rngc->base); + + rngc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(rngc->clk)) { + dev_err(&pdev->dev, "Can not get rng_clk\n"); + return PTR_ERR(rngc->clk); + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "Couldn't get irq %d\n", irq); + return irq; + } + + ret = clk_prepare_enable(rngc->clk); + if (ret) + return ret; + + ret = devm_request_irq(&pdev->dev, + irq, imx_rngc_irq, 0, pdev->name, (void *)rngc); + if (ret) { + dev_err(rngc->dev, "Can't get interrupt working.\n"); + goto err; + } + + init_completion(&rngc->rng_op_done); + + rngc->rng.name = pdev->name; + rngc->rng.init = imx_rngc_init; + rngc->rng.read = imx_rngc_read; + + rngc->dev = &pdev->dev; + platform_set_drvdata(pdev, rngc); + + imx_rngc_irq_mask_clear(rngc); + + if (self_test) { + ret = imx_rngc_self_test(rngc); + if (ret) { + dev_err(rngc->dev, "FSL RNGC self test failed.\n"); + goto err; + } + } + + ret = hwrng_register(&rngc->rng); + if (ret) { + dev_err(&pdev->dev, "FSL RNGC registering failed (%d)\n", ret); + goto err; + } + + dev_info(&pdev->dev, "Freescale RNGC registered.\n"); + return 0; + +err: + clk_disable_unprepare(rngc->clk); + + return ret; +} + +static int __exit imx_rngc_remove(struct platform_device *pdev) +{ + struct imx_rngc *rngc = platform_get_drvdata(pdev); + + hwrng_unregister(&rngc->rng); + + clk_disable_unprepare(rngc->clk); + + return 0; +} + +static int __maybe_unused imx_rngc_suspend(struct device *dev) +{ + struct imx_rngc *rngc = dev_get_drvdata(dev); + + clk_disable_unprepare(rngc->clk); + + return 0; +} + +static int __maybe_unused imx_rngc_resume(struct device *dev) +{ + struct imx_rngc *rngc = dev_get_drvdata(dev); + + clk_prepare_enable(rngc->clk); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(imx_rngc_pm_ops, imx_rngc_suspend, imx_rngc_resume); + +static const struct of_device_id imx_rngc_dt_ids[] = { + { .compatible = "fsl,imx25-rngb", .data = NULL, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_rngc_dt_ids); + +static struct platform_driver imx_rngc_driver = { + .driver = { + .name = "imx_rngc", + .pm = &imx_rngc_pm_ops, + .of_match_table = imx_rngc_dt_ids, + }, + .remove = __exit_p(imx_rngc_remove), +}; + +module_platform_driver_probe(imx_rngc_driver, imx_rngc_probe); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("H/W RNGC driver for i.MX"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/intel-rng.c b/drivers/char/hw_random/intel-rng.c new file mode 100644 index 000000000..290c88026 --- /dev/null +++ b/drivers/char/hw_random/intel-rng.c @@ -0,0 +1,418 @@ +/* + * RNG driver for Intel RNGs + * + * Copyright 2005 (c) MontaVista Software, Inc. + * + * with the majority of the code coming from: + * + * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) + * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> + * + * derived from + * + * Hardware driver for the AMD 768 Random Number Generator (RNG) + * (c) Copyright 2001 Red Hat Inc + * + * derived from + * + * 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 <linux/hw_random.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/stop_machine.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <asm/io.h> + + +#define PFX KBUILD_MODNAME ": " + +/* + * RNG registers + */ +#define INTEL_RNG_HW_STATUS 0 +#define INTEL_RNG_PRESENT 0x40 +#define INTEL_RNG_ENABLED 0x01 +#define INTEL_RNG_STATUS 1 +#define INTEL_RNG_DATA_PRESENT 0x01 +#define INTEL_RNG_DATA 2 + +/* + * Magic address at which Intel PCI bridges locate the RNG + */ +#define INTEL_RNG_ADDR 0xFFBC015F +#define INTEL_RNG_ADDR_LEN 3 + +/* + * LPC bridge PCI config space registers + */ +#define FWH_DEC_EN1_REG_OLD 0xe3 +#define FWH_DEC_EN1_REG_NEW 0xd9 /* high byte of 16-bit register */ +#define FWH_F8_EN_MASK 0x80 + +#define BIOS_CNTL_REG_OLD 0x4e +#define BIOS_CNTL_REG_NEW 0xdc +#define BIOS_CNTL_WRITE_ENABLE_MASK 0x01 +#define BIOS_CNTL_LOCK_ENABLE_MASK 0x02 + +/* + * Magic address at which Intel Firmware Hubs get accessed + */ +#define INTEL_FWH_ADDR 0xffff0000 +#define INTEL_FWH_ADDR_LEN 2 + +/* + * Intel Firmware Hub command codes (write to any address inside the device) + */ +#define INTEL_FWH_RESET_CMD 0xff /* aka READ_ARRAY */ +#define INTEL_FWH_READ_ID_CMD 0x90 + +/* + * Intel Firmware Hub Read ID command result addresses + */ +#define INTEL_FWH_MANUFACTURER_CODE_ADDRESS 0x000000 +#define INTEL_FWH_DEVICE_CODE_ADDRESS 0x000001 + +/* + * Intel Firmware Hub Read ID command result values + */ +#define INTEL_FWH_MANUFACTURER_CODE 0x89 +#define INTEL_FWH_DEVICE_CODE_8M 0xac +#define INTEL_FWH_DEVICE_CODE_4M 0xad + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * register a pci_driver, because someone else might one day + * want to register another driver on the same PCI id. + */ +static const struct pci_device_id pci_tbl[] = { +/* AA + { PCI_DEVICE(0x8086, 0x2418) }, */ + { PCI_DEVICE(0x8086, 0x2410) }, /* AA */ +/* AB + { PCI_DEVICE(0x8086, 0x2428) }, */ + { PCI_DEVICE(0x8086, 0x2420) }, /* AB */ +/* ?? + { PCI_DEVICE(0x8086, 0x2430) }, */ +/* BAM, CAM, DBM, FBM, GxM + { PCI_DEVICE(0x8086, 0x2448) }, */ + { PCI_DEVICE(0x8086, 0x244c) }, /* BAM */ + { PCI_DEVICE(0x8086, 0x248c) }, /* CAM */ + { PCI_DEVICE(0x8086, 0x24cc) }, /* DBM */ + { PCI_DEVICE(0x8086, 0x2641) }, /* FBM */ + { PCI_DEVICE(0x8086, 0x27b9) }, /* GxM */ + { PCI_DEVICE(0x8086, 0x27bd) }, /* GxM DH */ +/* BA, CA, DB, Ex, 6300, Fx, 631x/632x, Gx + { PCI_DEVICE(0x8086, 0x244e) }, */ + { PCI_DEVICE(0x8086, 0x2440) }, /* BA */ + { PCI_DEVICE(0x8086, 0x2480) }, /* CA */ + { PCI_DEVICE(0x8086, 0x24c0) }, /* DB */ + { PCI_DEVICE(0x8086, 0x24d0) }, /* Ex */ + { PCI_DEVICE(0x8086, 0x25a1) }, /* 6300 */ + { PCI_DEVICE(0x8086, 0x2640) }, /* Fx */ + { PCI_DEVICE(0x8086, 0x2670) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2671) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2672) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2673) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2674) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2675) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2676) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2677) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2678) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x2679) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x267a) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x267b) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x267c) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x267d) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x267e) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x267f) }, /* 631x/632x */ + { PCI_DEVICE(0x8086, 0x27b8) }, /* Gx */ +/* E + { PCI_DEVICE(0x8086, 0x245e) }, */ + { PCI_DEVICE(0x8086, 0x2450) }, /* E */ + { 0, }, /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static __initdata int no_fwh_detect; +module_param(no_fwh_detect, int, 0); +MODULE_PARM_DESC(no_fwh_detect, "Skip FWH detection:\n" + " positive value - skip if FWH space locked read-only\n" + " negative value - skip always"); + +static inline u8 hwstatus_get(void __iomem *mem) +{ + return readb(mem + INTEL_RNG_HW_STATUS); +} + +static inline u8 hwstatus_set(void __iomem *mem, + u8 hw_status) +{ + writeb(hw_status, mem + INTEL_RNG_HW_STATUS); + return hwstatus_get(mem); +} + +static int intel_rng_data_present(struct hwrng *rng, int wait) +{ + void __iomem *mem = (void __iomem *)rng->priv; + int data, i; + + for (i = 0; i < 20; i++) { + data = !!(readb(mem + INTEL_RNG_STATUS) & + INTEL_RNG_DATA_PRESENT); + if (data || !wait) + break; + udelay(10); + } + return data; +} + +static int intel_rng_data_read(struct hwrng *rng, u32 *data) +{ + void __iomem *mem = (void __iomem *)rng->priv; + + *data = readb(mem + INTEL_RNG_DATA); + + return 1; +} + +static int intel_rng_init(struct hwrng *rng) +{ + void __iomem *mem = (void __iomem *)rng->priv; + u8 hw_status; + int err = -EIO; + + hw_status = hwstatus_get(mem); + /* turn RNG h/w on, if it's off */ + if ((hw_status & INTEL_RNG_ENABLED) == 0) + hw_status = hwstatus_set(mem, hw_status | INTEL_RNG_ENABLED); + if ((hw_status & INTEL_RNG_ENABLED) == 0) { + pr_err(PFX "cannot enable RNG, aborting\n"); + goto out; + } + err = 0; +out: + return err; +} + +static void intel_rng_cleanup(struct hwrng *rng) +{ + void __iomem *mem = (void __iomem *)rng->priv; + u8 hw_status; + + hw_status = hwstatus_get(mem); + if (hw_status & INTEL_RNG_ENABLED) + hwstatus_set(mem, hw_status & ~INTEL_RNG_ENABLED); + else + pr_warn(PFX "unusual: RNG already disabled\n"); +} + + +static struct hwrng intel_rng = { + .name = "intel", + .init = intel_rng_init, + .cleanup = intel_rng_cleanup, + .data_present = intel_rng_data_present, + .data_read = intel_rng_data_read, +}; + +struct intel_rng_hw { + struct pci_dev *dev; + void __iomem *mem; + u8 bios_cntl_off; + u8 bios_cntl_val; + u8 fwh_dec_en1_off; + u8 fwh_dec_en1_val; +}; + +static int __init intel_rng_hw_init(void *_intel_rng_hw) +{ + struct intel_rng_hw *intel_rng_hw = _intel_rng_hw; + u8 mfc, dvc; + + /* interrupts disabled in stop_machine call */ + + if (!(intel_rng_hw->fwh_dec_en1_val & FWH_F8_EN_MASK)) + pci_write_config_byte(intel_rng_hw->dev, + intel_rng_hw->fwh_dec_en1_off, + intel_rng_hw->fwh_dec_en1_val | + FWH_F8_EN_MASK); + if (!(intel_rng_hw->bios_cntl_val & BIOS_CNTL_WRITE_ENABLE_MASK)) + pci_write_config_byte(intel_rng_hw->dev, + intel_rng_hw->bios_cntl_off, + intel_rng_hw->bios_cntl_val | + BIOS_CNTL_WRITE_ENABLE_MASK); + + writeb(INTEL_FWH_RESET_CMD, intel_rng_hw->mem); + writeb(INTEL_FWH_READ_ID_CMD, intel_rng_hw->mem); + mfc = readb(intel_rng_hw->mem + INTEL_FWH_MANUFACTURER_CODE_ADDRESS); + dvc = readb(intel_rng_hw->mem + INTEL_FWH_DEVICE_CODE_ADDRESS); + writeb(INTEL_FWH_RESET_CMD, intel_rng_hw->mem); + + if (!(intel_rng_hw->bios_cntl_val & + (BIOS_CNTL_LOCK_ENABLE_MASK|BIOS_CNTL_WRITE_ENABLE_MASK))) + pci_write_config_byte(intel_rng_hw->dev, + intel_rng_hw->bios_cntl_off, + intel_rng_hw->bios_cntl_val); + if (!(intel_rng_hw->fwh_dec_en1_val & FWH_F8_EN_MASK)) + pci_write_config_byte(intel_rng_hw->dev, + intel_rng_hw->fwh_dec_en1_off, + intel_rng_hw->fwh_dec_en1_val); + + if (mfc != INTEL_FWH_MANUFACTURER_CODE || + (dvc != INTEL_FWH_DEVICE_CODE_8M && + dvc != INTEL_FWH_DEVICE_CODE_4M)) { + pr_notice(PFX "FWH not detected\n"); + return -ENODEV; + } + + return 0; +} + +static int __init intel_init_hw_struct(struct intel_rng_hw *intel_rng_hw, + struct pci_dev *dev) +{ + intel_rng_hw->bios_cntl_val = 0xff; + intel_rng_hw->fwh_dec_en1_val = 0xff; + intel_rng_hw->dev = dev; + + /* Check for Intel 82802 */ + if (dev->device < 0x2640) { + intel_rng_hw->fwh_dec_en1_off = FWH_DEC_EN1_REG_OLD; + intel_rng_hw->bios_cntl_off = BIOS_CNTL_REG_OLD; + } else { + intel_rng_hw->fwh_dec_en1_off = FWH_DEC_EN1_REG_NEW; + intel_rng_hw->bios_cntl_off = BIOS_CNTL_REG_NEW; + } + + pci_read_config_byte(dev, intel_rng_hw->fwh_dec_en1_off, + &intel_rng_hw->fwh_dec_en1_val); + pci_read_config_byte(dev, intel_rng_hw->bios_cntl_off, + &intel_rng_hw->bios_cntl_val); + + if ((intel_rng_hw->bios_cntl_val & + (BIOS_CNTL_LOCK_ENABLE_MASK|BIOS_CNTL_WRITE_ENABLE_MASK)) + == BIOS_CNTL_LOCK_ENABLE_MASK) { + static __initdata /*const*/ char warning[] = +PFX "Firmware space is locked read-only. If you can't or\n" +PFX "don't want to disable this in firmware setup, and if\n" +PFX "you are certain that your system has a functional\n" +PFX "RNG, try using the 'no_fwh_detect' option.\n"; + + if (no_fwh_detect) + return -ENODEV; + pr_warn("%s", warning); + return -EBUSY; + } + + intel_rng_hw->mem = ioremap_nocache(INTEL_FWH_ADDR, INTEL_FWH_ADDR_LEN); + if (intel_rng_hw->mem == NULL) + return -EBUSY; + + return 0; +} + + +static int __init mod_init(void) +{ + int err = -ENODEV; + int i; + struct pci_dev *dev = NULL; + void __iomem *mem = mem; + u8 hw_status; + struct intel_rng_hw *intel_rng_hw; + + for (i = 0; !dev && pci_tbl[i].vendor; ++i) + dev = pci_get_device(pci_tbl[i].vendor, pci_tbl[i].device, + NULL); + + if (!dev) + goto out; /* Device not found. */ + + if (no_fwh_detect < 0) { + pci_dev_put(dev); + goto fwh_done; + } + + intel_rng_hw = kmalloc(sizeof(*intel_rng_hw), GFP_KERNEL); + if (!intel_rng_hw) { + pci_dev_put(dev); + goto out; + } + + err = intel_init_hw_struct(intel_rng_hw, dev); + if (err) { + pci_dev_put(dev); + kfree(intel_rng_hw); + if (err == -ENODEV) + goto fwh_done; + goto out; + } + + /* + * Since the BIOS code/data is going to disappear from its normal + * location with the Read ID command, all activity on the system + * must be stopped until the state is back to normal. + * + * Use stop_machine because IPIs can be blocked by disabling + * interrupts. + */ + err = stop_machine(intel_rng_hw_init, intel_rng_hw, NULL); + pci_dev_put(dev); + iounmap(intel_rng_hw->mem); + kfree(intel_rng_hw); + if (err) + goto out; + +fwh_done: + err = -ENOMEM; + mem = ioremap(INTEL_RNG_ADDR, INTEL_RNG_ADDR_LEN); + if (!mem) + goto out; + intel_rng.priv = (unsigned long)mem; + + /* Check for Random Number Generator */ + err = -ENODEV; + hw_status = hwstatus_get(mem); + if ((hw_status & INTEL_RNG_PRESENT) == 0) { + iounmap(mem); + goto out; + } + + pr_info("Intel 82802 RNG detected\n"); + err = hwrng_register(&intel_rng); + if (err) { + pr_err(PFX "RNG registering failed (%d)\n", + err); + iounmap(mem); + } +out: + return err; + +} + +static void __exit mod_exit(void) +{ + void __iomem *mem = (void __iomem *)intel_rng.priv; + + hwrng_unregister(&intel_rng); + iounmap(mem); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_DESCRIPTION("H/W RNG driver for Intel chipsets"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/iproc-rng200.c b/drivers/char/hw_random/iproc-rng200.c new file mode 100644 index 000000000..8b5a20b35 --- /dev/null +++ b/drivers/char/hw_random/iproc-rng200.c @@ -0,0 +1,240 @@ +/* +* Copyright (C) 2015 Broadcom Corporation +* +* 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 version 2. +* +* This program is distributed "as is" WITHOUT ANY WARRANTY of any +* kind, whether express or implied; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +/* + * DESCRIPTION: The Broadcom iProc RNG200 Driver + */ + +#include <linux/hw_random.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/delay.h> + +/* Registers */ +#define RNG_CTRL_OFFSET 0x00 +#define RNG_CTRL_RNG_RBGEN_MASK 0x00001FFF +#define RNG_CTRL_RNG_RBGEN_ENABLE 0x00000001 +#define RNG_CTRL_RNG_RBGEN_DISABLE 0x00000000 + +#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_restart(void __iomem *rng_base) +{ + uint32_t val; + + /* Disable RBG */ + val = ioread32(rng_base + RNG_CTRL_OFFSET); + val &= ~RNG_CTRL_RNG_RBGEN_MASK; + val |= RNG_CTRL_RNG_RBGEN_DISABLE; + iowrite32(val, rng_base + RNG_CTRL_OFFSET); + + /* 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); + + /* Enable RBG */ + val = ioread32(rng_base + RNG_CTRL_OFFSET); + val &= ~RNG_CTRL_RNG_RBGEN_MASK; + val |= RNG_CTRL_RNG_RBGEN_ENABLE; + iowrite32(val, rng_base + RNG_CTRL_OFFSET); +} + +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; + + #define MAX_RESETS_PER_READ 1 + uint32_t num_resets = 0; + + #define MAX_IDLE_TIME (1 * HZ) + unsigned long idle_endtime = jiffies + MAX_IDLE_TIME; + + while ((num_remaining > 0) && time_before(jiffies, idle_endtime)) { + + /* 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 */ + idle_endtime = jiffies + MAX_IDLE_TIME; + } else { + if (!wait) + /* Cannot wait, return immediately */ + return max - num_remaining; + + /* Can wait, give others chance to run */ + usleep_range(min(num_remaining * 10, 500U), 500); + } + } + + return max - num_remaining; +} + +static int iproc_rng200_init(struct hwrng *rng) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + uint32_t val; + + /* Setup RNG. */ + val = ioread32(priv->base + RNG_CTRL_OFFSET); + val &= ~RNG_CTRL_RNG_RBGEN_MASK; + val |= RNG_CTRL_RNG_RBGEN_ENABLE; + iowrite32(val, priv->base + RNG_CTRL_OFFSET); + + return 0; +} + +static void iproc_rng200_cleanup(struct hwrng *rng) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + uint32_t val; + + /* Disable RNG hardware */ + val = ioread32(priv->base + RNG_CTRL_OFFSET); + val &= ~RNG_CTRL_RNG_RBGEN_MASK; + val |= RNG_CTRL_RNG_RBGEN_DISABLE; + iowrite32(val, priv->base + RNG_CTRL_OFFSET); +} + +static int iproc_rng200_probe(struct platform_device *pdev) +{ + struct iproc_rng200_dev *priv; + struct resource *res; + struct device *dev = &pdev->dev; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Map peripheral */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "failed to get rng resources\n"); + return -EINVAL; + } + + priv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base)) { + dev_err(dev, "failed to remap rng regs\n"); + return PTR_ERR(priv->base); + } + + priv->rng.name = "iproc-rng200", + priv->rng.read = iproc_rng200_read, + priv->rng.init = iproc_rng200_init, + priv->rng.cleanup = iproc_rng200_cleanup, + + /* Register driver */ + ret = devm_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 const struct of_device_id iproc_rng200_of_match[] = { + { .compatible = "brcm,bcm7278-rng200", }, + { .compatible = "brcm,iproc-rng200", }, + {}, +}; +MODULE_DEVICE_TABLE(of, iproc_rng200_of_match); + +static struct platform_driver iproc_rng200_driver = { + .driver = { + .name = "iproc-rng200", + .of_match_table = iproc_rng200_of_match, + }, + .probe = iproc_rng200_probe, +}; +module_platform_driver(iproc_rng200_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("iProc RNG200 Random Number Generator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/hw_random/ixp4xx-rng.c b/drivers/char/hw_random/ixp4xx-rng.c new file mode 100644 index 000000000..beec1627d --- /dev/null +++ b/drivers/char/hw_random/ixp4xx-rng.c @@ -0,0 +1,75 @@ +/* + * drivers/char/hw_random/ixp4xx-rng.c + * + * RNG driver for Intel IXP4xx family of NPUs + * + * Author: Deepak Saxena <dsaxena@plexity.net> + * + * Copyright 2005 (c) MontaVista Software, Inc. + * + * Fixes by Michael Buesch + * + * 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 <linux/kernel.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/hw_random.h> + +#include <asm/io.h> +#include <mach/hardware.h> + + +static int ixp4xx_rng_data_read(struct hwrng *rng, u32 *buffer) +{ + void __iomem * rng_base = (void __iomem *)rng->priv; + + *buffer = __raw_readl(rng_base); + + return 4; +} + +static struct hwrng ixp4xx_rng_ops = { + .name = "ixp4xx", + .data_read = ixp4xx_rng_data_read, +}; + +static int __init ixp4xx_rng_init(void) +{ + void __iomem * rng_base; + int err; + + if (!cpu_is_ixp46x()) /* includes IXP455 */ + return -ENOSYS; + + rng_base = ioremap(0x70002100, 4); + if (!rng_base) + return -ENOMEM; + ixp4xx_rng_ops.priv = (unsigned long)rng_base; + err = hwrng_register(&ixp4xx_rng_ops); + if (err) + iounmap(rng_base); + + return err; +} + +static void __exit ixp4xx_rng_exit(void) +{ + void __iomem * rng_base = (void __iomem *)ixp4xx_rng_ops.priv; + + hwrng_unregister(&ixp4xx_rng_ops); + iounmap(rng_base); +} + +module_init(ixp4xx_rng_init); +module_exit(ixp4xx_rng_exit); + +MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); +MODULE_DESCRIPTION("H/W Pseudo-Random Number Generator (RNG) driver for IXP45x/46x"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/ks-sa-rng.c b/drivers/char/hw_random/ks-sa-rng.c new file mode 100644 index 000000000..b6d7db362 --- /dev/null +++ b/drivers/char/hw_random/ks-sa-rng.c @@ -0,0 +1,258 @@ +/* + * Random Number Generator driver for the Keystone SOC + * + * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Sandeep Nair + * Vitaly Andrianov + * + * 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. + * + * 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. + */ + +#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> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/delay.h> + +#define SA_CMD_STATUS_OFS 0x8 + +/* TRNG enable control in SA System module*/ +#define SA_CMD_STATUS_REG_TRNG_ENABLE BIT(3) + +/* TRNG start control in TRNG module */ +#define TRNG_CNTL_REG_TRNG_ENABLE BIT(10) + +/* Data ready indicator in STATUS register */ +#define TRNG_STATUS_REG_READY BIT(0) + +/* Data ready clear control in INTACK register */ +#define TRNG_INTACK_REG_READY BIT(0) + +/* + * Number of samples taken to gather entropy during startup. + * If value is 0, the number of samples is 2^24 else + * equals value times 2^8. + */ +#define TRNG_DEF_STARTUP_CYCLES 0 +#define TRNG_CNTL_REG_STARTUP_CYCLES_SHIFT 16 + +/* + * Minimum number of samples taken to regenerate entropy + * If value is 0, the number of samples is 2^24 else + * equals value times 2^6. + */ +#define TRNG_DEF_MIN_REFILL_CYCLES 1 +#define TRNG_CFG_REG_MIN_REFILL_CYCLES_SHIFT 0 + +/* + * Maximum number of samples taken to regenerate entropy + * If value is 0, the number of samples is 2^24 else + * equals value times 2^8. + */ +#define TRNG_DEF_MAX_REFILL_CYCLES 0 +#define TRNG_CFG_REG_MAX_REFILL_CYCLES_SHIFT 16 + +/* Number of CLK input cycles between samples */ +#define TRNG_DEF_CLK_DIV_CYCLES 0 +#define TRNG_CFG_REG_SAMPLE_DIV_SHIFT 8 + +/* Maximum retries to get rng data */ +#define SA_MAX_RNG_DATA_RETRIES 5 +/* Delay between retries (in usecs) */ +#define SA_RNG_DATA_RETRY_DELAY 5 + +struct trng_regs { + u32 output_l; + u32 output_h; + u32 status; + u32 intmask; + u32 intack; + u32 control; + u32 config; +}; + +struct ks_sa_rng { + struct device *dev; + struct hwrng rng; + struct clk *clk; + struct regmap *regmap_cfg; + struct trng_regs *reg_rng; +}; + +static int ks_sa_rng_init(struct hwrng *rng) +{ + u32 value; + struct device *dev = (struct device *)rng->priv; + struct ks_sa_rng *ks_sa_rng = dev_get_drvdata(dev); + + /* Enable RNG module */ + regmap_write_bits(ks_sa_rng->regmap_cfg, SA_CMD_STATUS_OFS, + SA_CMD_STATUS_REG_TRNG_ENABLE, + SA_CMD_STATUS_REG_TRNG_ENABLE); + + /* Configure RNG module */ + writel(0, &ks_sa_rng->reg_rng->control); + value = TRNG_DEF_STARTUP_CYCLES << TRNG_CNTL_REG_STARTUP_CYCLES_SHIFT; + writel(value, &ks_sa_rng->reg_rng->control); + + value = (TRNG_DEF_MIN_REFILL_CYCLES << + TRNG_CFG_REG_MIN_REFILL_CYCLES_SHIFT) | + (TRNG_DEF_MAX_REFILL_CYCLES << + TRNG_CFG_REG_MAX_REFILL_CYCLES_SHIFT) | + (TRNG_DEF_CLK_DIV_CYCLES << + TRNG_CFG_REG_SAMPLE_DIV_SHIFT); + + writel(value, &ks_sa_rng->reg_rng->config); + + /* Disable all interrupts from TRNG */ + writel(0, &ks_sa_rng->reg_rng->intmask); + + /* Enable RNG */ + value = readl(&ks_sa_rng->reg_rng->control); + value |= TRNG_CNTL_REG_TRNG_ENABLE; + writel(value, &ks_sa_rng->reg_rng->control); + + return 0; +} + +static void ks_sa_rng_cleanup(struct hwrng *rng) +{ + struct device *dev = (struct device *)rng->priv; + struct ks_sa_rng *ks_sa_rng = dev_get_drvdata(dev); + + /* Disable RNG */ + writel(0, &ks_sa_rng->reg_rng->control); + regmap_write_bits(ks_sa_rng->regmap_cfg, SA_CMD_STATUS_OFS, + SA_CMD_STATUS_REG_TRNG_ENABLE, 0); +} + +static int ks_sa_rng_data_read(struct hwrng *rng, u32 *data) +{ + struct device *dev = (struct device *)rng->priv; + struct ks_sa_rng *ks_sa_rng = dev_get_drvdata(dev); + + /* Read random data */ + data[0] = readl(&ks_sa_rng->reg_rng->output_l); + data[1] = readl(&ks_sa_rng->reg_rng->output_h); + + writel(TRNG_INTACK_REG_READY, &ks_sa_rng->reg_rng->intack); + + return sizeof(u32) * 2; +} + +static int ks_sa_rng_data_present(struct hwrng *rng, int wait) +{ + struct device *dev = (struct device *)rng->priv; + struct ks_sa_rng *ks_sa_rng = dev_get_drvdata(dev); + + u32 ready; + int j; + + for (j = 0; j < SA_MAX_RNG_DATA_RETRIES; j++) { + ready = readl(&ks_sa_rng->reg_rng->status); + ready &= TRNG_STATUS_REG_READY; + + if (ready || !wait) + break; + + udelay(SA_RNG_DATA_RETRY_DELAY); + } + + return ready; +} + +static int ks_sa_rng_probe(struct platform_device *pdev) +{ + struct ks_sa_rng *ks_sa_rng; + struct device *dev = &pdev->dev; + int ret; + struct resource *mem; + + ks_sa_rng = devm_kzalloc(dev, sizeof(*ks_sa_rng), GFP_KERNEL); + if (!ks_sa_rng) + return -ENOMEM; + + ks_sa_rng->dev = dev; + ks_sa_rng->rng = (struct hwrng) { + .name = "ks_sa_hwrng", + .init = ks_sa_rng_init, + .data_read = ks_sa_rng_data_read, + .data_present = ks_sa_rng_data_present, + .cleanup = ks_sa_rng_cleanup, + }; + ks_sa_rng->rng.priv = (unsigned long)dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ks_sa_rng->reg_rng = devm_ioremap_resource(dev, mem); + if (IS_ERR(ks_sa_rng->reg_rng)) + return PTR_ERR(ks_sa_rng->reg_rng); + + ks_sa_rng->regmap_cfg = + syscon_regmap_lookup_by_phandle(dev->of_node, + "ti,syscon-sa-cfg"); + + if (IS_ERR(ks_sa_rng->regmap_cfg)) { + dev_err(dev, "syscon_node_to_regmap failed\n"); + return -EINVAL; + } + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "Failed to enable SA power-domain\n"); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + return ret; + } + + platform_set_drvdata(pdev, ks_sa_rng); + + return devm_hwrng_register(&pdev->dev, &ks_sa_rng->rng); +} + +static int ks_sa_rng_remove(struct platform_device *pdev) +{ + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id ks_sa_rng_dt_match[] = { + { + .compatible = "ti,keystone-rng", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ks_sa_rng_dt_match); + +static struct platform_driver ks_sa_rng_driver = { + .driver = { + .name = "ks-sa-rng", + .of_match_table = ks_sa_rng_dt_match, + }, + .probe = ks_sa_rng_probe, + .remove = ks_sa_rng_remove, +}; + +module_platform_driver(ks_sa_rng_driver); + +MODULE_DESCRIPTION("Keystone NETCP SA H/W Random Number Generator driver"); +MODULE_AUTHOR("Vitaly Andrianov <vitalya@ti.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/meson-rng.c b/drivers/char/hw_random/meson-rng.c new file mode 100644 index 000000000..2e23be802 --- /dev/null +++ b/drivers/char/hw_random/meson-rng.c @@ -0,0 +1,150 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright (c) 2016 BayLibre, SAS. + * Author: Neil Armstrong <narmstrong@baylibre.com> + * Copyright (C) 2014 Amlogic, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 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, see <http://www.gnu.org/licenses/>. + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * BSD LICENSE + * + * Copyright (c) 2016 BayLibre, SAS. + * Author: Neil Armstrong <narmstrong@baylibre.com> + * Copyright (C) 2014 Amlogic, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include <linux/err.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/hw_random.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/of.h> +#include <linux/clk.h> + +#define RNG_DATA 0x00 + +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) +{ + struct meson_rng_data *data = + container_of(rng, struct meson_rng_data, rng); + + *(u32 *)buf = readl_relaxed(data->base + RNG_DATA); + + 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) + return -ENOMEM; + + data->pdev = pdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_resource(dev, res); + 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; + + platform_set_drvdata(pdev, data); + + return devm_hwrng_register(dev, &data->rng); +} + +static const struct of_device_id meson_rng_of_match[] = { + { .compatible = "amlogic,meson-rng", }, + {}, +}; +MODULE_DEVICE_TABLE(of, meson_rng_of_match); + +static struct platform_driver meson_rng_driver = { + .probe = meson_rng_probe, + .driver = { + .name = "meson-rng", + .of_match_table = meson_rng_of_match, + }, +}; + +module_platform_driver(meson_rng_driver); + +MODULE_DESCRIPTION("Meson H/W Random Number Generator driver"); +MODULE_AUTHOR("Lawrence Mok <lawrence.mok@amlogic.com>"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/char/hw_random/mtk-rng.c b/drivers/char/hw_random/mtk-rng.c new file mode 100644 index 000000000..8dc256c76 --- /dev/null +++ b/drivers/char/hw_random/mtk-rng.c @@ -0,0 +1,216 @@ +/* + * 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> +#include <linux/pm_runtime.h> + +/* Runtime PM autosuspend timeout: */ +#define RNG_AUTOSUSPEND_TIMEOUT 100 + +#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; + + pm_runtime_get_sync((struct device *)priv->rng.priv); + + 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); + } + + pm_runtime_mark_last_busy((struct device *)priv->rng.priv); + pm_runtime_put_sync_autosuspend((struct device *)priv->rng.priv); + + 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; +#ifndef CONFIG_PM + priv->rng.init = mtk_rng_init; + priv->rng.cleanup = mtk_rng_cleanup; +#endif + priv->rng.read = mtk_rng_read; + priv->rng.priv = (unsigned long)&pdev->dev; + priv->rng.quality = 900; + + 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_set_drvdata(&pdev->dev, priv); + pm_runtime_set_autosuspend_delay(&pdev->dev, RNG_AUTOSUSPEND_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + dev_info(&pdev->dev, "registered RNG driver\n"); + + return 0; +} + +#ifdef CONFIG_PM +static int mtk_rng_runtime_suspend(struct device *dev) +{ + struct mtk_rng *priv = dev_get_drvdata(dev); + + mtk_rng_cleanup(&priv->rng); + + return 0; +} + +static int mtk_rng_runtime_resume(struct device *dev) +{ + struct mtk_rng *priv = dev_get_drvdata(dev); + + return mtk_rng_init(&priv->rng); +} + +static const struct dev_pm_ops mtk_rng_pm_ops = { + SET_RUNTIME_PM_OPS(mtk_rng_runtime_suspend, + mtk_rng_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +#define MTK_RNG_PM_OPS (&mtk_rng_pm_ops) +#else /* CONFIG_PM */ +#define MTK_RNG_PM_OPS NULL +#endif /* CONFIG_PM */ + +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, + .pm = MTK_RNG_PM_OPS, + .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/mxc-rnga.c b/drivers/char/hw_random/mxc-rnga.c new file mode 100644 index 000000000..f83bee513 --- /dev/null +++ b/drivers/char/hw_random/mxc-rnga.c @@ -0,0 +1,216 @@ +/* + * RNG driver for Freescale RNGA + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Author: Alan Carvalho de Assis <acassis@gmail.com> + */ + +/* + * 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 + * + * This driver is based on other RNG drivers. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +/* RNGA Registers */ +#define RNGA_CONTROL 0x00 +#define RNGA_STATUS 0x04 +#define RNGA_ENTROPY 0x08 +#define RNGA_OUTPUT_FIFO 0x0c +#define RNGA_MODE 0x10 +#define RNGA_VERIFICATION_CONTROL 0x14 +#define RNGA_OSC_CONTROL_COUNTER 0x18 +#define RNGA_OSC1_COUNTER 0x1c +#define RNGA_OSC2_COUNTER 0x20 +#define RNGA_OSC_COUNTER_STATUS 0x24 + +/* RNGA Registers Range */ +#define RNG_ADDR_RANGE 0x28 + +/* RNGA Control Register */ +#define RNGA_CONTROL_SLEEP 0x00000010 +#define RNGA_CONTROL_CLEAR_INT 0x00000008 +#define RNGA_CONTROL_MASK_INTS 0x00000004 +#define RNGA_CONTROL_HIGH_ASSURANCE 0x00000002 +#define RNGA_CONTROL_GO 0x00000001 + +#define RNGA_STATUS_LEVEL_MASK 0x0000ff00 + +/* RNGA Status Register */ +#define RNGA_STATUS_OSC_DEAD 0x80000000 +#define RNGA_STATUS_SLEEP 0x00000010 +#define RNGA_STATUS_ERROR_INT 0x00000008 +#define RNGA_STATUS_FIFO_UNDERFLOW 0x00000004 +#define RNGA_STATUS_LAST_READ_STATUS 0x00000002 +#define RNGA_STATUS_SECURITY_VIOLATION 0x00000001 + +struct mxc_rng { + struct device *dev; + struct hwrng rng; + void __iomem *mem; + struct clk *clk; +}; + +static int mxc_rnga_data_present(struct hwrng *rng, int wait) +{ + int i; + struct mxc_rng *mxc_rng = container_of(rng, struct mxc_rng, rng); + + for (i = 0; i < 20; i++) { + /* how many random numbers are in FIFO? [0-16] */ + int level = (__raw_readl(mxc_rng->mem + RNGA_STATUS) & + RNGA_STATUS_LEVEL_MASK) >> 8; + if (level || !wait) + return !!level; + udelay(10); + } + return 0; +} + +static int mxc_rnga_data_read(struct hwrng *rng, u32 * data) +{ + int err; + u32 ctrl; + struct mxc_rng *mxc_rng = container_of(rng, struct mxc_rng, rng); + + /* retrieve a random number from FIFO */ + *data = __raw_readl(mxc_rng->mem + RNGA_OUTPUT_FIFO); + + /* some error while reading this random number? */ + err = __raw_readl(mxc_rng->mem + RNGA_STATUS) & RNGA_STATUS_ERROR_INT; + + /* if error: clear error interrupt, but doesn't return random number */ + if (err) { + dev_dbg(mxc_rng->dev, "Error while reading random number!\n"); + ctrl = __raw_readl(mxc_rng->mem + RNGA_CONTROL); + __raw_writel(ctrl | RNGA_CONTROL_CLEAR_INT, + mxc_rng->mem + RNGA_CONTROL); + return 0; + } else + return 4; +} + +static int mxc_rnga_init(struct hwrng *rng) +{ + u32 ctrl, osc; + struct mxc_rng *mxc_rng = container_of(rng, struct mxc_rng, rng); + + /* wake up */ + ctrl = __raw_readl(mxc_rng->mem + RNGA_CONTROL); + __raw_writel(ctrl & ~RNGA_CONTROL_SLEEP, mxc_rng->mem + RNGA_CONTROL); + + /* verify if oscillator is working */ + osc = __raw_readl(mxc_rng->mem + RNGA_STATUS); + if (osc & RNGA_STATUS_OSC_DEAD) { + dev_err(mxc_rng->dev, "RNGA Oscillator is dead!\n"); + return -ENODEV; + } + + /* go running */ + ctrl = __raw_readl(mxc_rng->mem + RNGA_CONTROL); + __raw_writel(ctrl | RNGA_CONTROL_GO, mxc_rng->mem + RNGA_CONTROL); + + return 0; +} + +static void mxc_rnga_cleanup(struct hwrng *rng) +{ + u32 ctrl; + struct mxc_rng *mxc_rng = container_of(rng, struct mxc_rng, rng); + + ctrl = __raw_readl(mxc_rng->mem + RNGA_CONTROL); + + /* stop rnga */ + __raw_writel(ctrl & ~RNGA_CONTROL_GO, mxc_rng->mem + RNGA_CONTROL); +} + +static int __init mxc_rnga_probe(struct platform_device *pdev) +{ + int err; + struct resource *res; + struct mxc_rng *mxc_rng; + + mxc_rng = devm_kzalloc(&pdev->dev, sizeof(*mxc_rng), GFP_KERNEL); + if (!mxc_rng) + return -ENOMEM; + + mxc_rng->dev = &pdev->dev; + mxc_rng->rng.name = "mxc-rnga"; + mxc_rng->rng.init = mxc_rnga_init; + mxc_rng->rng.cleanup = mxc_rnga_cleanup, + mxc_rng->rng.data_present = mxc_rnga_data_present, + mxc_rng->rng.data_read = mxc_rnga_data_read, + + mxc_rng->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mxc_rng->clk)) { + dev_err(&pdev->dev, "Could not get rng_clk!\n"); + return PTR_ERR(mxc_rng->clk); + } + + err = clk_prepare_enable(mxc_rng->clk); + if (err) + return err; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mxc_rng->mem = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mxc_rng->mem)) { + err = PTR_ERR(mxc_rng->mem); + goto err_ioremap; + } + + err = hwrng_register(&mxc_rng->rng); + if (err) { + dev_err(&pdev->dev, "MXC RNGA registering failed (%d)\n", err); + goto err_ioremap; + } + + return 0; + +err_ioremap: + clk_disable_unprepare(mxc_rng->clk); + return err; +} + +static int __exit mxc_rnga_remove(struct platform_device *pdev) +{ + struct mxc_rng *mxc_rng = platform_get_drvdata(pdev); + + hwrng_unregister(&mxc_rng->rng); + + clk_disable_unprepare(mxc_rng->clk); + + return 0; +} + +static const struct of_device_id mxc_rnga_of_match[] = { + { .compatible = "fsl,imx21-rnga", }, + { .compatible = "fsl,imx31-rnga", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mxc_rnga_of_match); + +static struct platform_driver mxc_rnga_driver = { + .driver = { + .name = "mxc_rnga", + .of_match_table = mxc_rnga_of_match, + }, + .remove = __exit_p(mxc_rnga_remove), +}; + +module_platform_driver_probe(mxc_rnga_driver, mxc_rnga_probe); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("H/W RNGA driver for i.MX"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/n2-asm.S b/drivers/char/hw_random/n2-asm.S new file mode 100644 index 000000000..c205df43d --- /dev/null +++ b/drivers/char/hw_random/n2-asm.S @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* n2-asm.S: Niagara2 RNG hypervisor call assembler. + * + * Copyright (C) 2008 David S. Miller <davem@davemloft.net> + */ +#include <linux/linkage.h> +#include <asm/hypervisor.h> +#include "n2rng.h" + + .text + +ENTRY(sun4v_rng_get_diag_ctl) + mov HV_FAST_RNG_GET_DIAG_CTL, %o5 + ta HV_FAST_TRAP + retl + nop +ENDPROC(sun4v_rng_get_diag_ctl) + +ENTRY(sun4v_rng_ctl_read_v1) + mov %o1, %o3 + mov %o2, %o4 + mov HV_FAST_RNG_CTL_READ, %o5 + ta HV_FAST_TRAP + stx %o1, [%o3] + retl + stx %o2, [%o4] +ENDPROC(sun4v_rng_ctl_read_v1) + +ENTRY(sun4v_rng_ctl_read_v2) + save %sp, -192, %sp + mov %i0, %o0 + mov %i1, %o1 + mov HV_FAST_RNG_CTL_READ, %o5 + ta HV_FAST_TRAP + stx %o1, [%i2] + stx %o2, [%i3] + stx %o3, [%i4] + stx %o4, [%i5] + ret + restore %g0, %o0, %o0 +ENDPROC(sun4v_rng_ctl_read_v2) + +ENTRY(sun4v_rng_ctl_write_v1) + mov %o3, %o4 + mov HV_FAST_RNG_CTL_WRITE, %o5 + ta HV_FAST_TRAP + retl + stx %o1, [%o4] +ENDPROC(sun4v_rng_ctl_write_v1) + +ENTRY(sun4v_rng_ctl_write_v2) + mov HV_FAST_RNG_CTL_WRITE, %o5 + ta HV_FAST_TRAP + retl + nop +ENDPROC(sun4v_rng_ctl_write_v2) + +ENTRY(sun4v_rng_data_read_diag_v1) + mov %o2, %o4 + mov HV_FAST_RNG_DATA_READ_DIAG, %o5 + ta HV_FAST_TRAP + retl + stx %o1, [%o4] +ENDPROC(sun4v_rng_data_read_diag_v1) + +ENTRY(sun4v_rng_data_read_diag_v2) + mov %o3, %o4 + mov HV_FAST_RNG_DATA_READ_DIAG, %o5 + ta HV_FAST_TRAP + retl + stx %o1, [%o4] +ENDPROC(sun4v_rng_data_read_diag_v2) + +ENTRY(sun4v_rng_data_read) + mov %o1, %o4 + mov HV_FAST_RNG_DATA_READ, %o5 + ta HV_FAST_TRAP + retl + stx %o1, [%o4] +ENDPROC(sun4v_rng_data_read) diff --git a/drivers/char/hw_random/n2-drv.c b/drivers/char/hw_random/n2-drv.c new file mode 100644 index 000000000..f8411515f --- /dev/null +++ b/drivers/char/hw_random/n2-drv.c @@ -0,0 +1,871 @@ +/* n2-drv.c: Niagara-2 RNG driver. + * + * Copyright (C) 2008, 2011 David S. Miller <davem@davemloft.net> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/preempt.h> +#include <linux/hw_random.h> + +#include <linux/of.h> +#include <linux/of_device.h> + +#include <asm/hypervisor.h> + +#include "n2rng.h" + +#define DRV_MODULE_NAME "n2rng" +#define PFX DRV_MODULE_NAME ": " +#define DRV_MODULE_VERSION "0.3" +#define DRV_MODULE_RELDATE "Jan 7, 2017" + +static char version[] = + DRV_MODULE_NAME " v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n"; + +MODULE_AUTHOR("David S. Miller (davem@davemloft.net)"); +MODULE_DESCRIPTION("Niagara2 RNG driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_MODULE_VERSION); + +/* The Niagara2 RNG provides a 64-bit read-only random number + * register, plus a control register. Access to the RNG is + * virtualized through the hypervisor so that both guests and control + * nodes can access the device. + * + * The entropy source consists of raw entropy sources, each + * constructed from a voltage controlled oscillator whose phase is + * jittered by thermal noise sources. + * + * The oscillator in each of the three raw entropy sources run at + * different frequencies. Normally, all three generator outputs are + * gathered, xored together, and fed into a CRC circuit, the output of + * which is the 64-bit read-only register. + * + * Some time is necessary for all the necessary entropy to build up + * such that a full 64-bits of entropy are available in the register. + * In normal operating mode (RNG_CTL_LFSR is set), the chip implements + * an interlock which blocks register reads until sufficient entropy + * is available. + * + * A control register is provided for adjusting various aspects of RNG + * operation, and to enable diagnostic modes. Each of the three raw + * entropy sources has an enable bit (RNG_CTL_ES{1,2,3}). Also + * provided are fields for controlling the minimum time in cycles + * between read accesses to the register (RNG_CTL_WAIT, this controls + * the interlock described in the previous paragraph). + * + * The standard setting is to have the mode bit (RNG_CTL_LFSR) set, + * all three entropy sources enabled, and the interlock time set + * appropriately. + * + * The CRC polynomial used by the chip is: + * + * P(X) = x64 + x61 + x57 + x56 + x52 + x51 + x50 + x48 + x47 + x46 + + * x43 + x42 + x41 + x39 + x38 + x37 + x35 + x32 + x28 + x25 + + * x22 + x21 + x17 + x15 + x13 + x12 + x11 + x7 + x5 + x + 1 + * + * The RNG_CTL_VCO value of each noise cell must be programmed + * separately. This is why 4 control register values must be provided + * to the hypervisor. During a write, the hypervisor writes them all, + * one at a time, to the actual RNG_CTL register. The first three + * values are used to setup the desired RNG_CTL_VCO for each entropy + * source, for example: + * + * control 0: (1 << RNG_CTL_VCO_SHIFT) | RNG_CTL_ES1 + * control 1: (2 << RNG_CTL_VCO_SHIFT) | RNG_CTL_ES2 + * control 2: (3 << RNG_CTL_VCO_SHIFT) | RNG_CTL_ES3 + * + * And then the fourth value sets the final chip state and enables + * desired. + */ + +static int n2rng_hv_err_trans(unsigned long hv_err) +{ + switch (hv_err) { + case HV_EOK: + return 0; + case HV_EWOULDBLOCK: + return -EAGAIN; + case HV_ENOACCESS: + return -EPERM; + case HV_EIO: + return -EIO; + case HV_EBUSY: + return -EBUSY; + case HV_EBADALIGN: + case HV_ENORADDR: + return -EFAULT; + default: + return -EINVAL; + } +} + +static unsigned long n2rng_generic_read_control_v2(unsigned long ra, + unsigned long unit) +{ + unsigned long hv_err, state, ticks, watchdog_delta, watchdog_status; + int block = 0, busy = 0; + + while (1) { + hv_err = sun4v_rng_ctl_read_v2(ra, unit, &state, + &ticks, + &watchdog_delta, + &watchdog_status); + if (hv_err == HV_EOK) + break; + + if (hv_err == HV_EBUSY) { + if (++busy >= N2RNG_BUSY_LIMIT) + break; + + udelay(1); + } else if (hv_err == HV_EWOULDBLOCK) { + if (++block >= N2RNG_BLOCK_LIMIT) + break; + + __delay(ticks); + } else + break; + } + + return hv_err; +} + +/* In multi-socket situations, the hypervisor might need to + * queue up the RNG control register write if it's for a unit + * that is on a cpu socket other than the one we are executing on. + * + * We poll here waiting for a successful read of that control + * register to make sure the write has been actually performed. + */ +static unsigned long n2rng_control_settle_v2(struct n2rng *np, int unit) +{ + unsigned long ra = __pa(&np->scratch_control[0]); + + return n2rng_generic_read_control_v2(ra, unit); +} + +static unsigned long n2rng_write_ctl_one(struct n2rng *np, int unit, + unsigned long state, + unsigned long control_ra, + unsigned long watchdog_timeout, + unsigned long *ticks) +{ + unsigned long hv_err; + + if (np->hvapi_major == 1) { + hv_err = sun4v_rng_ctl_write_v1(control_ra, state, + watchdog_timeout, ticks); + } else { + hv_err = sun4v_rng_ctl_write_v2(control_ra, state, + watchdog_timeout, unit); + if (hv_err == HV_EOK) + hv_err = n2rng_control_settle_v2(np, unit); + *ticks = N2RNG_ACCUM_CYCLES_DEFAULT; + } + + return hv_err; +} + +static int n2rng_generic_read_data(unsigned long data_ra) +{ + unsigned long ticks, hv_err; + int block = 0, hcheck = 0; + + while (1) { + hv_err = sun4v_rng_data_read(data_ra, &ticks); + if (hv_err == HV_EOK) + return 0; + + if (hv_err == HV_EWOULDBLOCK) { + if (++block >= N2RNG_BLOCK_LIMIT) + return -EWOULDBLOCK; + __delay(ticks); + } else if (hv_err == HV_ENOACCESS) { + return -EPERM; + } else if (hv_err == HV_EIO) { + if (++hcheck >= N2RNG_HCHECK_LIMIT) + return -EIO; + udelay(10000); + } else + return -ENODEV; + } +} + +static unsigned long n2rng_read_diag_data_one(struct n2rng *np, + unsigned long unit, + unsigned long data_ra, + unsigned long data_len, + unsigned long *ticks) +{ + unsigned long hv_err; + + if (np->hvapi_major == 1) { + hv_err = sun4v_rng_data_read_diag_v1(data_ra, data_len, ticks); + } else { + hv_err = sun4v_rng_data_read_diag_v2(data_ra, data_len, + unit, ticks); + if (!*ticks) + *ticks = N2RNG_ACCUM_CYCLES_DEFAULT; + } + return hv_err; +} + +static int n2rng_generic_read_diag_data(struct n2rng *np, + unsigned long unit, + unsigned long data_ra, + unsigned long data_len) +{ + unsigned long ticks, hv_err; + int block = 0; + + while (1) { + hv_err = n2rng_read_diag_data_one(np, unit, + data_ra, data_len, + &ticks); + if (hv_err == HV_EOK) + return 0; + + if (hv_err == HV_EWOULDBLOCK) { + if (++block >= N2RNG_BLOCK_LIMIT) + return -EWOULDBLOCK; + __delay(ticks); + } else if (hv_err == HV_ENOACCESS) { + return -EPERM; + } else if (hv_err == HV_EIO) { + return -EIO; + } else + return -ENODEV; + } +} + + +static int n2rng_generic_write_control(struct n2rng *np, + unsigned long control_ra, + unsigned long unit, + unsigned long state) +{ + unsigned long hv_err, ticks; + int block = 0, busy = 0; + + while (1) { + hv_err = n2rng_write_ctl_one(np, unit, state, control_ra, + np->wd_timeo, &ticks); + if (hv_err == HV_EOK) + return 0; + + if (hv_err == HV_EWOULDBLOCK) { + if (++block >= N2RNG_BLOCK_LIMIT) + return -EWOULDBLOCK; + __delay(ticks); + } else if (hv_err == HV_EBUSY) { + if (++busy >= N2RNG_BUSY_LIMIT) + return -EBUSY; + udelay(1); + } else + return -ENODEV; + } +} + +/* Just try to see if we can successfully access the control register + * of the RNG on the domain on which we are currently executing. + */ +static int n2rng_try_read_ctl(struct n2rng *np) +{ + unsigned long hv_err; + unsigned long x; + + if (np->hvapi_major == 1) { + hv_err = sun4v_rng_get_diag_ctl(); + } else { + /* We purposefully give invalid arguments, HV_NOACCESS + * is higher priority than the errors we'd get from + * these other cases, and that's the error we are + * truly interested in. + */ + hv_err = sun4v_rng_ctl_read_v2(0UL, ~0UL, &x, &x, &x, &x); + switch (hv_err) { + case HV_EWOULDBLOCK: + case HV_ENOACCESS: + break; + default: + hv_err = HV_EOK; + break; + } + } + + return n2rng_hv_err_trans(hv_err); +} + +static u64 n2rng_control_default(struct n2rng *np, int ctl) +{ + u64 val = 0; + + if (np->data->chip_version == 1) { + val = ((2 << RNG_v1_CTL_ASEL_SHIFT) | + (N2RNG_ACCUM_CYCLES_DEFAULT << RNG_v1_CTL_WAIT_SHIFT) | + RNG_CTL_LFSR); + + switch (ctl) { + case 0: + val |= (1 << RNG_v1_CTL_VCO_SHIFT) | RNG_CTL_ES1; + break; + case 1: + val |= (2 << RNG_v1_CTL_VCO_SHIFT) | RNG_CTL_ES2; + break; + case 2: + val |= (3 << RNG_v1_CTL_VCO_SHIFT) | RNG_CTL_ES3; + break; + case 3: + val |= RNG_CTL_ES1 | RNG_CTL_ES2 | RNG_CTL_ES3; + break; + default: + break; + } + + } else { + val = ((2 << RNG_v2_CTL_ASEL_SHIFT) | + (N2RNG_ACCUM_CYCLES_DEFAULT << RNG_v2_CTL_WAIT_SHIFT) | + RNG_CTL_LFSR); + + switch (ctl) { + case 0: + val |= (1 << RNG_v2_CTL_VCO_SHIFT) | RNG_CTL_ES1; + break; + case 1: + val |= (2 << RNG_v2_CTL_VCO_SHIFT) | RNG_CTL_ES2; + break; + case 2: + val |= (3 << RNG_v2_CTL_VCO_SHIFT) | RNG_CTL_ES3; + break; + case 3: + val |= RNG_CTL_ES1 | RNG_CTL_ES2 | RNG_CTL_ES3; + break; + default: + break; + } + } + + return val; +} + +static void n2rng_control_swstate_init(struct n2rng *np) +{ + int i; + + np->flags |= N2RNG_FLAG_CONTROL; + + np->health_check_sec = N2RNG_HEALTH_CHECK_SEC_DEFAULT; + np->accum_cycles = N2RNG_ACCUM_CYCLES_DEFAULT; + np->wd_timeo = N2RNG_WD_TIMEO_DEFAULT; + + for (i = 0; i < np->num_units; i++) { + struct n2rng_unit *up = &np->units[i]; + + up->control[0] = n2rng_control_default(np, 0); + up->control[1] = n2rng_control_default(np, 1); + up->control[2] = n2rng_control_default(np, 2); + up->control[3] = n2rng_control_default(np, 3); + } + + np->hv_state = HV_RNG_STATE_UNCONFIGURED; +} + +static int n2rng_grab_diag_control(struct n2rng *np) +{ + int i, busy_count, err = -ENODEV; + + busy_count = 0; + for (i = 0; i < 100; i++) { + err = n2rng_try_read_ctl(np); + if (err != -EAGAIN) + break; + + if (++busy_count > 100) { + dev_err(&np->op->dev, + "Grab diag control timeout.\n"); + return -ENODEV; + } + + udelay(1); + } + + return err; +} + +static int n2rng_init_control(struct n2rng *np) +{ + int err = n2rng_grab_diag_control(np); + + /* Not in the control domain, that's OK we are only a consumer + * of the RNG data, we don't setup and program it. + */ + if (err == -EPERM) + return 0; + if (err) + return err; + + n2rng_control_swstate_init(np); + + return 0; +} + +static int n2rng_data_read(struct hwrng *rng, u32 *data) +{ + struct n2rng *np = (struct n2rng *) rng->priv; + unsigned long ra = __pa(&np->test_data); + int len; + + if (!(np->flags & N2RNG_FLAG_READY)) { + len = 0; + } else if (np->flags & N2RNG_FLAG_BUFFER_VALID) { + np->flags &= ~N2RNG_FLAG_BUFFER_VALID; + *data = np->buffer; + len = 4; + } else { + int err = n2rng_generic_read_data(ra); + if (!err) { + np->flags |= N2RNG_FLAG_BUFFER_VALID; + np->buffer = np->test_data >> 32; + *data = np->test_data & 0xffffffff; + len = 4; + } else { + dev_err(&np->op->dev, "RNG error, retesting\n"); + np->flags &= ~N2RNG_FLAG_READY; + if (!(np->flags & N2RNG_FLAG_SHUTDOWN)) + schedule_delayed_work(&np->work, 0); + len = 0; + } + } + + return len; +} + +/* On a guest node, just make sure we can read random data properly. + * If a control node reboots or reloads it's n2rng driver, this won't + * work during that time. So we have to keep probing until the device + * becomes usable. + */ +static int n2rng_guest_check(struct n2rng *np) +{ + unsigned long ra = __pa(&np->test_data); + + return n2rng_generic_read_data(ra); +} + +static int n2rng_entropy_diag_read(struct n2rng *np, unsigned long unit, + u64 *pre_control, u64 pre_state, + u64 *buffer, unsigned long buf_len, + u64 *post_control, u64 post_state) +{ + unsigned long post_ctl_ra = __pa(post_control); + unsigned long pre_ctl_ra = __pa(pre_control); + unsigned long buffer_ra = __pa(buffer); + int err; + + err = n2rng_generic_write_control(np, pre_ctl_ra, unit, pre_state); + if (err) + return err; + + err = n2rng_generic_read_diag_data(np, unit, + buffer_ra, buf_len); + + (void) n2rng_generic_write_control(np, post_ctl_ra, unit, + post_state); + + return err; +} + +static u64 advance_polynomial(u64 poly, u64 val, int count) +{ + int i; + + for (i = 0; i < count; i++) { + int highbit_set = ((s64)val < 0); + + val <<= 1; + if (highbit_set) + val ^= poly; + } + + return val; +} + +static int n2rng_test_buffer_find(struct n2rng *np, u64 val) +{ + int i, count = 0; + + /* Purposefully skip over the first word. */ + for (i = 1; i < SELFTEST_BUFFER_WORDS; i++) { + if (np->test_buffer[i] == val) + count++; + } + return count; +} + +static void n2rng_dump_test_buffer(struct n2rng *np) +{ + int i; + + for (i = 0; i < SELFTEST_BUFFER_WORDS; i++) + dev_err(&np->op->dev, "Test buffer slot %d [0x%016llx]\n", + i, np->test_buffer[i]); +} + +static int n2rng_check_selftest_buffer(struct n2rng *np, unsigned long unit) +{ + u64 val; + int err, matches, limit; + + switch (np->data->id) { + case N2_n2_rng: + case N2_vf_rng: + case N2_kt_rng: + case N2_m4_rng: /* yes, m4 uses the old value */ + val = RNG_v1_SELFTEST_VAL; + break; + default: + val = RNG_v2_SELFTEST_VAL; + break; + } + + matches = 0; + for (limit = 0; limit < SELFTEST_LOOPS_MAX; limit++) { + matches += n2rng_test_buffer_find(np, val); + if (matches >= SELFTEST_MATCH_GOAL) + break; + val = advance_polynomial(SELFTEST_POLY, val, 1); + } + + err = 0; + if (limit >= SELFTEST_LOOPS_MAX) { + err = -ENODEV; + dev_err(&np->op->dev, "Selftest failed on unit %lu\n", unit); + n2rng_dump_test_buffer(np); + } else + dev_info(&np->op->dev, "Selftest passed on unit %lu\n", unit); + + return err; +} + +static int n2rng_control_selftest(struct n2rng *np, unsigned long unit) +{ + int err; + u64 base, base3; + + switch (np->data->id) { + case N2_n2_rng: + case N2_vf_rng: + case N2_kt_rng: + base = RNG_v1_CTL_ASEL_NOOUT << RNG_v1_CTL_ASEL_SHIFT; + base3 = base | RNG_CTL_LFSR | + ((RNG_v1_SELFTEST_TICKS - 2) << RNG_v1_CTL_WAIT_SHIFT); + break; + case N2_m4_rng: + base = RNG_v2_CTL_ASEL_NOOUT << RNG_v2_CTL_ASEL_SHIFT; + base3 = base | RNG_CTL_LFSR | + ((RNG_v1_SELFTEST_TICKS - 2) << RNG_v2_CTL_WAIT_SHIFT); + break; + default: + base = RNG_v2_CTL_ASEL_NOOUT << RNG_v2_CTL_ASEL_SHIFT; + base3 = base | RNG_CTL_LFSR | + (RNG_v2_SELFTEST_TICKS << RNG_v2_CTL_WAIT_SHIFT); + break; + } + + np->test_control[0] = base; + np->test_control[1] = base; + np->test_control[2] = base; + np->test_control[3] = base3; + + err = n2rng_entropy_diag_read(np, unit, np->test_control, + HV_RNG_STATE_HEALTHCHECK, + np->test_buffer, + sizeof(np->test_buffer), + &np->units[unit].control[0], + np->hv_state); + if (err) + return err; + + return n2rng_check_selftest_buffer(np, unit); +} + +static int n2rng_control_check(struct n2rng *np) +{ + int i; + + for (i = 0; i < np->num_units; i++) { + int err = n2rng_control_selftest(np, i); + if (err) + return err; + } + return 0; +} + +/* The sanity checks passed, install the final configuration into the + * chip, it's ready to use. + */ +static int n2rng_control_configure_units(struct n2rng *np) +{ + int unit, err; + + err = 0; + for (unit = 0; unit < np->num_units; unit++) { + struct n2rng_unit *up = &np->units[unit]; + unsigned long ctl_ra = __pa(&up->control[0]); + int esrc; + u64 base, shift; + + if (np->data->chip_version == 1) { + base = ((np->accum_cycles << RNG_v1_CTL_WAIT_SHIFT) | + (RNG_v1_CTL_ASEL_NOOUT << RNG_v1_CTL_ASEL_SHIFT) | + RNG_CTL_LFSR); + shift = RNG_v1_CTL_VCO_SHIFT; + } else { + base = ((np->accum_cycles << RNG_v2_CTL_WAIT_SHIFT) | + (RNG_v2_CTL_ASEL_NOOUT << RNG_v2_CTL_ASEL_SHIFT) | + RNG_CTL_LFSR); + shift = RNG_v2_CTL_VCO_SHIFT; + } + + /* XXX This isn't the best. We should fetch a bunch + * XXX of words using each entropy source combined XXX + * with each VCO setting, and see which combinations + * XXX give the best random data. + */ + for (esrc = 0; esrc < 3; esrc++) + up->control[esrc] = base | + (esrc << shift) | + (RNG_CTL_ES1 << esrc); + + up->control[3] = base | + (RNG_CTL_ES1 | RNG_CTL_ES2 | RNG_CTL_ES3); + + err = n2rng_generic_write_control(np, ctl_ra, unit, + HV_RNG_STATE_CONFIGURED); + if (err) + break; + } + + return err; +} + +static void n2rng_work(struct work_struct *work) +{ + struct n2rng *np = container_of(work, struct n2rng, work.work); + int err = 0; + static int retries = 4; + + if (!(np->flags & N2RNG_FLAG_CONTROL)) { + err = n2rng_guest_check(np); + } else { + preempt_disable(); + err = n2rng_control_check(np); + preempt_enable(); + + if (!err) + err = n2rng_control_configure_units(np); + } + + if (!err) { + np->flags |= N2RNG_FLAG_READY; + dev_info(&np->op->dev, "RNG ready\n"); + } + + if (--retries == 0) + dev_err(&np->op->dev, "Self-test retries failed, RNG not ready\n"); + else if (err && !(np->flags & N2RNG_FLAG_SHUTDOWN)) + schedule_delayed_work(&np->work, HZ * 2); +} + +static void n2rng_driver_version(void) +{ + static int n2rng_version_printed; + + if (n2rng_version_printed++ == 0) + pr_info("%s", version); +} + +static const struct of_device_id n2rng_match[]; +static int n2rng_probe(struct platform_device *op) +{ + const struct of_device_id *match; + int err = -ENOMEM; + struct n2rng *np; + + match = of_match_device(n2rng_match, &op->dev); + if (!match) + return -EINVAL; + + n2rng_driver_version(); + np = devm_kzalloc(&op->dev, sizeof(*np), GFP_KERNEL); + if (!np) + goto out; + np->op = op; + np->data = (struct n2rng_template *)match->data; + + INIT_DELAYED_WORK(&np->work, n2rng_work); + + if (np->data->multi_capable) + np->flags |= N2RNG_FLAG_MULTI; + + err = -ENODEV; + np->hvapi_major = 2; + if (sun4v_hvapi_register(HV_GRP_RNG, + np->hvapi_major, + &np->hvapi_minor)) { + np->hvapi_major = 1; + if (sun4v_hvapi_register(HV_GRP_RNG, + np->hvapi_major, + &np->hvapi_minor)) { + dev_err(&op->dev, "Cannot register suitable " + "HVAPI version.\n"); + goto out; + } + } + + if (np->flags & N2RNG_FLAG_MULTI) { + if (np->hvapi_major < 2) { + dev_err(&op->dev, "multi-unit-capable RNG requires " + "HVAPI major version 2 or later, got %lu\n", + np->hvapi_major); + goto out_hvapi_unregister; + } + np->num_units = of_getintprop_default(op->dev.of_node, + "rng-#units", 0); + if (!np->num_units) { + dev_err(&op->dev, "VF RNG lacks rng-#units property\n"); + goto out_hvapi_unregister; + } + } else { + np->num_units = 1; + } + + dev_info(&op->dev, "Registered RNG HVAPI major %lu minor %lu\n", + np->hvapi_major, np->hvapi_minor); + np->units = devm_kcalloc(&op->dev, np->num_units, sizeof(*np->units), + GFP_KERNEL); + err = -ENOMEM; + if (!np->units) + goto out_hvapi_unregister; + + err = n2rng_init_control(np); + if (err) + goto out_hvapi_unregister; + + dev_info(&op->dev, "Found %s RNG, units: %d\n", + ((np->flags & N2RNG_FLAG_MULTI) ? + "multi-unit-capable" : "single-unit"), + np->num_units); + + np->hwrng.name = DRV_MODULE_NAME; + np->hwrng.data_read = n2rng_data_read; + np->hwrng.priv = (unsigned long) np; + + err = hwrng_register(&np->hwrng); + if (err) + goto out_hvapi_unregister; + + platform_set_drvdata(op, np); + + schedule_delayed_work(&np->work, 0); + + return 0; + +out_hvapi_unregister: + sun4v_hvapi_unregister(HV_GRP_RNG); + +out: + return err; +} + +static int n2rng_remove(struct platform_device *op) +{ + struct n2rng *np = platform_get_drvdata(op); + + np->flags |= N2RNG_FLAG_SHUTDOWN; + + cancel_delayed_work_sync(&np->work); + + hwrng_unregister(&np->hwrng); + + sun4v_hvapi_unregister(HV_GRP_RNG); + + return 0; +} + +static struct n2rng_template n2_template = { + .id = N2_n2_rng, + .multi_capable = 0, + .chip_version = 1, +}; + +static struct n2rng_template vf_template = { + .id = N2_vf_rng, + .multi_capable = 1, + .chip_version = 1, +}; + +static struct n2rng_template kt_template = { + .id = N2_kt_rng, + .multi_capable = 1, + .chip_version = 1, +}; + +static struct n2rng_template m4_template = { + .id = N2_m4_rng, + .multi_capable = 1, + .chip_version = 2, +}; + +static struct n2rng_template m7_template = { + .id = N2_m7_rng, + .multi_capable = 1, + .chip_version = 2, +}; + +static const struct of_device_id n2rng_match[] = { + { + .name = "random-number-generator", + .compatible = "SUNW,n2-rng", + .data = &n2_template, + }, + { + .name = "random-number-generator", + .compatible = "SUNW,vf-rng", + .data = &vf_template, + }, + { + .name = "random-number-generator", + .compatible = "SUNW,kt-rng", + .data = &kt_template, + }, + { + .name = "random-number-generator", + .compatible = "ORCL,m4-rng", + .data = &m4_template, + }, + { + .name = "random-number-generator", + .compatible = "ORCL,m7-rng", + .data = &m7_template, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, n2rng_match); + +static struct platform_driver n2rng_driver = { + .driver = { + .name = "n2rng", + .of_match_table = n2rng_match, + }, + .probe = n2rng_probe, + .remove = n2rng_remove, +}; + +module_platform_driver(n2rng_driver); diff --git a/drivers/char/hw_random/n2rng.h b/drivers/char/hw_random/n2rng.h new file mode 100644 index 000000000..9a870f5dc --- /dev/null +++ b/drivers/char/hw_random/n2rng.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* n2rng.h: Niagara2 RNG defines. + * + * Copyright (C) 2008 David S. Miller <davem@davemloft.net> + */ + +#ifndef _N2RNG_H +#define _N2RNG_H + +/* ver1 devices - n2-rng, vf-rng, kt-rng */ +#define RNG_v1_CTL_WAIT 0x0000000001fffe00ULL /* Minimum wait time */ +#define RNG_v1_CTL_WAIT_SHIFT 9 +#define RNG_v1_CTL_BYPASS 0x0000000000000100ULL /* VCO voltage source */ +#define RNG_v1_CTL_VCO 0x00000000000000c0ULL /* VCO rate control */ +#define RNG_v1_CTL_VCO_SHIFT 6 +#define RNG_v1_CTL_ASEL 0x0000000000000030ULL /* Analog MUX select */ +#define RNG_v1_CTL_ASEL_SHIFT 4 +#define RNG_v1_CTL_ASEL_NOOUT 2 + +/* these are the same in v2 as in v1 */ +#define RNG_CTL_LFSR 0x0000000000000008ULL /* Use LFSR or plain shift */ +#define RNG_CTL_ES3 0x0000000000000004ULL /* Enable entropy source 3 */ +#define RNG_CTL_ES2 0x0000000000000002ULL /* Enable entropy source 2 */ +#define RNG_CTL_ES1 0x0000000000000001ULL /* Enable entropy source 1 */ + +/* ver2 devices - m4-rng, m7-rng */ +#define RNG_v2_CTL_WAIT 0x0000000007fff800ULL /* Minimum wait time */ +#define RNG_v2_CTL_WAIT_SHIFT 12 +#define RNG_v2_CTL_BYPASS 0x0000000000000400ULL /* VCO voltage source */ +#define RNG_v2_CTL_VCO 0x0000000000000300ULL /* VCO rate control */ +#define RNG_v2_CTL_VCO_SHIFT 9 +#define RNG_v2_CTL_PERF 0x0000000000000180ULL /* Perf */ +#define RNG_v2_CTL_ASEL 0x0000000000000070ULL /* Analog MUX select */ +#define RNG_v2_CTL_ASEL_SHIFT 4 +#define RNG_v2_CTL_ASEL_NOOUT 7 + + +#define HV_FAST_RNG_GET_DIAG_CTL 0x130 +#define HV_FAST_RNG_CTL_READ 0x131 +#define HV_FAST_RNG_CTL_WRITE 0x132 +#define HV_FAST_RNG_DATA_READ_DIAG 0x133 +#define HV_FAST_RNG_DATA_READ 0x134 + +#define HV_RNG_STATE_UNCONFIGURED 0 +#define HV_RNG_STATE_CONFIGURED 1 +#define HV_RNG_STATE_HEALTHCHECK 2 +#define HV_RNG_STATE_ERROR 3 + +#define HV_RNG_NUM_CONTROL 4 + +#ifndef __ASSEMBLY__ +extern unsigned long sun4v_rng_get_diag_ctl(void); +extern unsigned long sun4v_rng_ctl_read_v1(unsigned long ctl_regs_ra, + unsigned long *state, + unsigned long *tick_delta); +extern unsigned long sun4v_rng_ctl_read_v2(unsigned long ctl_regs_ra, + unsigned long unit, + unsigned long *state, + unsigned long *tick_delta, + unsigned long *watchdog, + unsigned long *write_status); +extern unsigned long sun4v_rng_ctl_write_v1(unsigned long ctl_regs_ra, + unsigned long state, + unsigned long write_timeout, + unsigned long *tick_delta); +extern unsigned long sun4v_rng_ctl_write_v2(unsigned long ctl_regs_ra, + unsigned long state, + unsigned long write_timeout, + unsigned long unit); +extern unsigned long sun4v_rng_data_read_diag_v1(unsigned long data_ra, + unsigned long len, + unsigned long *tick_delta); +extern unsigned long sun4v_rng_data_read_diag_v2(unsigned long data_ra, + unsigned long len, + unsigned long unit, + unsigned long *tick_delta); +extern unsigned long sun4v_rng_data_read(unsigned long data_ra, + unsigned long *tick_delta); + +enum n2rng_compat_id { + N2_n2_rng, + N2_vf_rng, + N2_kt_rng, + N2_m4_rng, + N2_m7_rng, +}; + +struct n2rng_template { + enum n2rng_compat_id id; + int multi_capable; + int chip_version; +}; + +struct n2rng_unit { + u64 control[HV_RNG_NUM_CONTROL]; +}; + +struct n2rng { + struct platform_device *op; + + unsigned long flags; +#define N2RNG_FLAG_MULTI 0x00000001 /* Multi-unit capable RNG */ +#define N2RNG_FLAG_CONTROL 0x00000002 /* Operating in control domain */ +#define N2RNG_FLAG_READY 0x00000008 /* Ready for hw-rng layer */ +#define N2RNG_FLAG_SHUTDOWN 0x00000010 /* Driver unregistering */ +#define N2RNG_FLAG_BUFFER_VALID 0x00000020 /* u32 buffer holds valid data */ + + struct n2rng_template *data; + int num_units; + struct n2rng_unit *units; + + struct hwrng hwrng; + u32 buffer; + + /* Registered hypervisor group API major and minor version. */ + unsigned long hvapi_major; + unsigned long hvapi_minor; + + struct delayed_work work; + + unsigned long hv_state; /* HV_RNG_STATE_foo */ + + unsigned long health_check_sec; + unsigned long accum_cycles; + unsigned long wd_timeo; +#define N2RNG_HEALTH_CHECK_SEC_DEFAULT 0 +#define N2RNG_ACCUM_CYCLES_DEFAULT 2048 +#define N2RNG_WD_TIMEO_DEFAULT 0 + + u64 scratch_control[HV_RNG_NUM_CONTROL]; + +#define RNG_v1_SELFTEST_TICKS 38859 +#define RNG_v1_SELFTEST_VAL ((u64)0xB8820C7BD387E32C) +#define RNG_v2_SELFTEST_TICKS 64 +#define RNG_v2_SELFTEST_VAL ((u64)0xffffffffffffffff) +#define SELFTEST_POLY ((u64)0x231DCEE91262B8A3) +#define SELFTEST_MATCH_GOAL 6 +#define SELFTEST_LOOPS_MAX 40000 +#define SELFTEST_BUFFER_WORDS 8 + + u64 test_data; + u64 test_control[HV_RNG_NUM_CONTROL]; + u64 test_buffer[SELFTEST_BUFFER_WORDS]; +}; + +#define N2RNG_BLOCK_LIMIT 60000 +#define N2RNG_BUSY_LIMIT 100 +#define N2RNG_HCHECK_LIMIT 100 + +#endif /* !(__ASSEMBLY__) */ + +#endif /* _N2RNG_H */ diff --git a/drivers/char/hw_random/nomadik-rng.c b/drivers/char/hw_random/nomadik-rng.c new file mode 100644 index 000000000..9c8581577 --- /dev/null +++ b/drivers/char/hw_random/nomadik-rng.c @@ -0,0 +1,106 @@ +/* + * Nomadik RNG support + * Copyright 2009 Alessandro Rubini + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/amba/bus.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> + +static struct clk *rng_clk; + +static int nmk_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + void __iomem *base = (void __iomem *)rng->priv; + + /* + * The register is 32 bits and gives 16 random bits (low half). + * A subsequent read will delay the core for 400ns, so we just read + * once and accept the very unlikely very small delay, even if wait==0. + */ + *(u16 *)data = __raw_readl(base + 8) & 0xffff; + return 2; +} + +/* we have at most one RNG per machine, granted */ +static struct hwrng nmk_rng = { + .name = "nomadik", + .read = nmk_rng_read, +}; + +static int nmk_rng_probe(struct amba_device *dev, const struct amba_id *id) +{ + void __iomem *base; + int ret; + + rng_clk = devm_clk_get(&dev->dev, NULL); + if (IS_ERR(rng_clk)) { + dev_err(&dev->dev, "could not get rng clock\n"); + ret = PTR_ERR(rng_clk); + return ret; + } + + clk_prepare_enable(rng_clk); + + ret = amba_request_regions(dev, dev->dev.init_name); + if (ret) + goto out_clk; + ret = -ENOMEM; + base = devm_ioremap(&dev->dev, dev->res.start, + resource_size(&dev->res)); + if (!base) + goto out_release; + nmk_rng.priv = (unsigned long)base; + ret = hwrng_register(&nmk_rng); + if (ret) + goto out_release; + return 0; + +out_release: + amba_release_regions(dev); +out_clk: + clk_disable(rng_clk); + return ret; +} + +static int nmk_rng_remove(struct amba_device *dev) +{ + hwrng_unregister(&nmk_rng); + amba_release_regions(dev); + clk_disable(rng_clk); + return 0; +} + +static struct amba_id nmk_rng_ids[] = { + { + .id = 0x000805e1, + .mask = 0x000fffff, /* top bits are rev and cfg: accept all */ + }, + {0, 0}, +}; + +MODULE_DEVICE_TABLE(amba, nmk_rng_ids); + +static struct amba_driver nmk_rng_driver = { + .drv = { + .owner = THIS_MODULE, + .name = "rng", + }, + .probe = nmk_rng_probe, + .remove = nmk_rng_remove, + .id_table = nmk_rng_ids, +}; + +module_amba_driver(nmk_rng_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/octeon-rng.c b/drivers/char/hw_random/octeon-rng.c new file mode 100644 index 000000000..8c78aa090 --- /dev/null +++ b/drivers/char/hw_random/octeon-rng.c @@ -0,0 +1,118 @@ +/* + * Hardware Random Number Generator support for Cavium Networks + * Octeon processor family. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009 Cavium Networks + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/gfp.h> + +#include <asm/octeon/octeon.h> +#include <asm/octeon/cvmx-rnm-defs.h> + +struct octeon_rng { + struct hwrng ops; + void __iomem *control_status; + void __iomem *result; +}; + +static int octeon_rng_init(struct hwrng *rng) +{ + union cvmx_rnm_ctl_status ctl; + struct octeon_rng *p = container_of(rng, struct octeon_rng, ops); + + ctl.u64 = 0; + ctl.s.ent_en = 1; /* Enable the entropy source. */ + ctl.s.rng_en = 1; /* Enable the RNG hardware. */ + cvmx_write_csr((u64)p->control_status, ctl.u64); + return 0; +} + +static void octeon_rng_cleanup(struct hwrng *rng) +{ + union cvmx_rnm_ctl_status ctl; + struct octeon_rng *p = container_of(rng, struct octeon_rng, ops); + + ctl.u64 = 0; + /* Disable everything. */ + cvmx_write_csr((u64)p->control_status, ctl.u64); +} + +static int octeon_rng_data_read(struct hwrng *rng, u32 *data) +{ + struct octeon_rng *p = container_of(rng, struct octeon_rng, ops); + + *data = cvmx_read64_uint32((u64)p->result); + return sizeof(u32); +} + +static int octeon_rng_probe(struct platform_device *pdev) +{ + struct resource *res_ports; + struct resource *res_result; + struct octeon_rng *rng; + int ret; + struct hwrng ops = { + .name = "octeon", + .init = octeon_rng_init, + .cleanup = octeon_rng_cleanup, + .data_read = octeon_rng_data_read + }; + + rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); + if (!rng) + return -ENOMEM; + + res_ports = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_ports) + return -ENOENT; + + res_result = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res_result) + return -ENOENT; + + + rng->control_status = devm_ioremap_nocache(&pdev->dev, + res_ports->start, + sizeof(u64)); + if (!rng->control_status) + return -ENOENT; + + rng->result = devm_ioremap_nocache(&pdev->dev, + res_result->start, + sizeof(u64)); + if (!rng->result) + return -ENOENT; + + rng->ops = ops; + + platform_set_drvdata(pdev, &rng->ops); + ret = devm_hwrng_register(&pdev->dev, &rng->ops); + if (ret) + return -ENOENT; + + dev_info(&pdev->dev, "Octeon Random Number Generator\n"); + + return 0; +} + +static struct platform_driver octeon_rng_driver = { + .driver = { + .name = "octeon_rng", + }, + .probe = octeon_rng_probe, +}; + +module_platform_driver(octeon_rng_driver); + +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/omap-rng.c b/drivers/char/hw_random/omap-rng.c new file mode 100644 index 000000000..498825242 --- /dev/null +++ b/drivers/char/hw_random/omap-rng.c @@ -0,0 +1,590 @@ +/* + * 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> + * + * 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 <linux/module.h> +#include <linux/init.h> +#include <linux/random.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/hw_random.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/interrupt.h> +#include <linux/clk.h> + +#include <asm/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; + + if (max < priv->pdata->data_size) + return 0; + + 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; + + memcpy_fromio(data, priv->base + priv->pdata->regs[RNG_OUTPUT_0_REG], + priv->pdata->data_size); + + if (priv->pdata->regs[RNG_INTACK_REG]) + omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_RDY_MASK); + + return priv->pdata->data_size; +} + +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 void omap_rng_cleanup(struct hwrng *rng) +{ + struct omap_rng_dev *priv; + + priv = (struct omap_rng_dev *)rng->priv; + priv->pdata->cleanup(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, +}; + +#if defined(CONFIG_OF) +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 all available FROs */ + omap_rng_write(priv, RNG_FRODETUNE_REG, 0x0); + omap_rng_write(priv, RNG_FROENABLE_REG, RNG_REG_FROENABLE_MASK); + + /* 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); + + omap_rng_write(priv, RNG_FRODETUNE_REG, 0x0); + omap_rng_write(priv, RNG_FROENABLE_REG, RNG_REG_FROENABLE_MASK); + val = RNG_ALARM_THRESHOLD << RNG_ALARMCNT_ALARM_TH_SHIFT; + val |= RNG_SHUTDOWN_THRESHOLD << RNG_ALARMCNT_SHUTDOWN_TH_SHIFT; + omap_rng_write(priv, RNG_ALARMCNT_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 irqreturn_t omap4_rng_irq(int irq, void *dev_id) +{ + struct omap_rng_dev *priv = dev_id; + u32 fro_detune, fro_enable; + + /* + * Interrupt raised by a fro shutdown threshold, do the following: + * 1. Clear the alarm events. + * 2. De tune the FROs which are shutdown. + * 3. Re enable the shutdown FROs. + */ + omap_rng_write(priv, RNG_ALARMMASK_REG, 0x0); + omap_rng_write(priv, RNG_ALARMSTOP_REG, 0x0); + + fro_enable = omap_rng_read(priv, RNG_FROENABLE_REG); + fro_detune = ~fro_enable & RNG_REG_FRODETUNE_MASK; + fro_detune = fro_detune | omap_rng_read(priv, RNG_FRODETUNE_REG); + fro_enable = RNG_REG_FROENABLE_MASK; + + omap_rng_write(priv, RNG_FRODETUNE_REG, fro_detune); + omap_rng_write(priv, RNG_FROENABLE_REG, fro_enable); + + omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_SHUTDOWN_OFLO_MASK); + + return IRQ_HANDLED; +} + +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[] = { + { + .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 platform_device *pdev) +{ + const struct of_device_id *match; + struct device *dev = &pdev->dev; + int irq, err; + + match = of_match_device(of_match_ptr(omap_rng_of_match), dev); + if (!match) { + dev_err(dev, "no compatible OF match\n"); + return -EINVAL; + } + priv->pdata = match->data; + + if (of_device_is_compatible(dev->of_node, "ti,omap4-rng") || + of_device_is_compatible(dev->of_node, "inside-secure,safexcel-eip76")) { + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "%s: error getting IRQ resource - %d\n", + __func__, irq); + return irq; + } + + err = devm_request_irq(dev, irq, omap4_rng_irq, + IRQF_TRIGGER_NONE, dev_name(dev), priv); + if (err) { + dev_err(dev, "unable to request irq %d, err = %d\n", + irq, err); + return err; + } + + /* + * On OMAP4, enabling the shutdown_oflo interrupt is + * done in the interrupt mask register. There is no + * such register on EIP76, and it's enabled by the + * same bit in the control register + */ + if (priv->pdata->regs[RNG_INTMASK_REG]) + omap_rng_write(priv, RNG_INTMASK_REG, + RNG_SHUTDOWN_OFLO_MASK); + else + omap_rng_write(priv, RNG_CONTROL_REG, + RNG_SHUTDOWN_OFLO_MASK); + } + return 0; +} +#else +static int of_get_omap_rng_device_details(struct omap_rng_dev *omap_rng, + struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + +static int get_omap_rng_device_details(struct omap_rng_dev *omap_rng) +{ + /* Only OMAP2/3 can be non-DT */ + omap_rng->pdata = &omap2_rng_pdata; + return 0; +} + +static int omap_rng_probe(struct platform_device *pdev) +{ + struct omap_rng_dev *priv; + struct resource *res; + struct device *dev = &pdev->dev; + 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.cleanup = omap_rng_cleanup; + priv->rng.quality = 900; + + priv->rng.priv = (unsigned long)priv; + platform_set_drvdata(pdev, priv); + priv->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(dev, res); + 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; + } + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to runtime_get device: %d\n", ret); + pm_runtime_put_noidle(&pdev->dev); + 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; + } + } + + priv->clk_reg = devm_clk_get(&pdev->dev, "reg"); + if (IS_ERR(priv->clk_reg) && PTR_ERR(priv->clk_reg) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (!IS_ERR(priv->clk_reg)) { + ret = clk_prepare_enable(priv->clk_reg); + if (ret) { + dev_err(&pdev->dev, + "Unable to enable the register 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) + goto err_register; + + ret = hwrng_register(&priv->rng); + if (ret) + goto err_register; + + dev_info(&pdev->dev, "Random Number Generator ver. %02x\n", + omap_rng_read(priv, RNG_REV_REG)); + + return 0; + +err_register: + priv->base = NULL; + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + clk_disable_unprepare(priv->clk_reg); + clk_disable_unprepare(priv->clk); +err_ioremap: + dev_err(dev, "initialization failed.\n"); + return ret; +} + +static int omap_rng_remove(struct platform_device *pdev) +{ + struct omap_rng_dev *priv = platform_get_drvdata(pdev); + + hwrng_unregister(&priv->rng); + + priv->pdata->cleanup(priv); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_reg); + + return 0; +} + +static int __maybe_unused omap_rng_suspend(struct device *dev) +{ + struct omap_rng_dev *priv = dev_get_drvdata(dev); + + priv->pdata->cleanup(priv); + pm_runtime_put_sync(dev); + + return 0; +} + +static int __maybe_unused omap_rng_resume(struct device *dev) +{ + struct omap_rng_dev *priv = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "Failed to runtime_get device: %d\n", ret); + pm_runtime_put_noidle(dev); + return ret; + } + + priv->pdata->init(priv); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(omap_rng_pm, omap_rng_suspend, omap_rng_resume); + +static struct platform_driver omap_rng_driver = { + .driver = { + .name = "omap_rng", + .pm = &omap_rng_pm, + .of_match_table = of_match_ptr(omap_rng_of_match), + }, + .probe = omap_rng_probe, + .remove = omap_rng_remove, +}; + +module_platform_driver(omap_rng_driver); +MODULE_ALIAS("platform:omap_rng"); +MODULE_AUTHOR("Deepak Saxena (and others)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/omap3-rom-rng.c b/drivers/char/hw_random/omap3-rom-rng.c new file mode 100644 index 000000000..8df3cad7c --- /dev/null +++ b/drivers/char/hw_random/omap3-rom-rng.c @@ -0,0 +1,155 @@ +/* + * omap3-rom-rng.c - RNG driver for TI OMAP3 CPU family + * + * Copyright (C) 2009 Nokia Corporation + * Author: Juha Yrjola <juha.yrjola@solidboot.com> + * + * Copyright (C) 2013 Pali Rohár <pali.rohar@gmail.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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/random.h> +#include <linux/hw_random.h> +#include <linux/workqueue.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +#define RNG_RESET 0x01 +#define RNG_GEN_PRNG_HW_INIT 0x02 +#define RNG_GEN_HW 0x08 + +/* param1: ptr, param2: count, param3: flag */ +static u32 (*omap3_rom_rng_call)(u32, u32, u32); + +static struct delayed_work idle_work; +static int rng_idle; +static struct clk *rng_clk; + +static void omap3_rom_rng_idle(struct work_struct *work) +{ + int r; + + r = omap3_rom_rng_call(0, 0, RNG_RESET); + if (r != 0) { + pr_err("reset failed: %d\n", r); + return; + } + clk_disable_unprepare(rng_clk); + rng_idle = 1; +} + +static int omap3_rom_rng_get_random(void *buf, unsigned int count) +{ + u32 r; + u32 ptr; + + cancel_delayed_work_sync(&idle_work); + if (rng_idle) { + r = clk_prepare_enable(rng_clk); + if (r) + return r; + + r = omap3_rom_rng_call(0, 0, RNG_GEN_PRNG_HW_INIT); + if (r != 0) { + clk_disable_unprepare(rng_clk); + pr_err("HW init failed: %d\n", r); + return -EIO; + } + rng_idle = 0; + } + + ptr = virt_to_phys(buf); + r = omap3_rom_rng_call(ptr, count, RNG_GEN_HW); + schedule_delayed_work(&idle_work, msecs_to_jiffies(500)); + if (r != 0) + return -EINVAL; + return 0; +} + +static int omap3_rom_rng_read(struct hwrng *rng, void *data, size_t max, bool w) +{ + int r; + + r = omap3_rom_rng_get_random(data, 4); + if (r < 0) + return r; + return 4; +} + +static struct hwrng omap3_rom_rng_ops = { + .name = "omap3-rom", +}; + +static int omap3_rom_rng_probe(struct platform_device *pdev) +{ + int ret = 0; + + omap3_rom_rng_ops.read = of_device_get_match_data(&pdev->dev); + if (!omap3_rom_rng_ops.read) { + dev_err(&pdev->dev, "missing rom code handler\n"); + + return -ENODEV; + } + + omap3_rom_rng_call = pdev->dev.platform_data; + if (!omap3_rom_rng_call) { + pr_err("omap3_rom_rng_call is NULL\n"); + return -EINVAL; + } + + INIT_DELAYED_WORK(&idle_work, omap3_rom_rng_idle); + rng_clk = devm_clk_get(&pdev->dev, "ick"); + if (IS_ERR(rng_clk)) { + pr_err("unable to get RNG clock\n"); + return PTR_ERR(rng_clk); + } + + /* Leave the RNG in reset state. */ + ret = clk_prepare_enable(rng_clk); + if (ret) + return ret; + omap3_rom_rng_idle(0); + + return hwrng_register(&omap3_rom_rng_ops); +} + +static int omap3_rom_rng_remove(struct platform_device *pdev) +{ + cancel_delayed_work_sync(&idle_work); + hwrng_unregister(&omap3_rom_rng_ops); + if (!rng_idle) + clk_disable_unprepare(rng_clk); + return 0; +} + +static const struct of_device_id omap_rom_rng_match[] = { + { .compatible = "nokia,n900-rom-rng", .data = omap3_rom_rng_read, }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, omap_rom_rng_match); + +static struct platform_driver omap3_rom_rng_driver = { + .driver = { + .name = "omap3-rom-rng", + .of_match_table = omap_rom_rng_match, + }, + .probe = omap3_rom_rng_probe, + .remove = omap3_rom_rng_remove, +}; + +module_platform_driver(omap3_rom_rng_driver); + +MODULE_ALIAS("platform:omap3-rom-rng"); +MODULE_AUTHOR("Juha Yrjola"); +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/pasemi-rng.c b/drivers/char/hw_random/pasemi-rng.c new file mode 100644 index 000000000..545df485b --- /dev/null +++ b/drivers/char/hw_random/pasemi-rng.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2006-2007 PA Semi, Inc + * + * Maintained by: Olof Johansson <olof@lixom.net> + * + * Driver for the PWRficient onchip rng + * + * 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. + * + * 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/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/hw_random.h> +#include <linux/delay.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/io.h> + +#define SDCRNG_CTL_REG 0x00 +#define SDCRNG_CTL_FVLD_M 0x0000f000 +#define SDCRNG_CTL_FVLD_S 12 +#define SDCRNG_CTL_KSZ 0x00000800 +#define SDCRNG_CTL_RSRC_CRG 0x00000010 +#define SDCRNG_CTL_RSRC_RRG 0x00000000 +#define SDCRNG_CTL_CE 0x00000004 +#define SDCRNG_CTL_RE 0x00000002 +#define SDCRNG_CTL_DR 0x00000001 +#define SDCRNG_CTL_SELECT_RRG_RNG (SDCRNG_CTL_RE | SDCRNG_CTL_RSRC_RRG) +#define SDCRNG_CTL_SELECT_CRG_RNG (SDCRNG_CTL_CE | SDCRNG_CTL_RSRC_CRG) +#define SDCRNG_VAL_REG 0x20 + +#define MODULE_NAME "pasemi_rng" + +static int pasemi_rng_data_present(struct hwrng *rng, int wait) +{ + void __iomem *rng_regs = (void __iomem *)rng->priv; + int data, i; + + for (i = 0; i < 20; i++) { + data = (in_le32(rng_regs + SDCRNG_CTL_REG) + & SDCRNG_CTL_FVLD_M) ? 1 : 0; + if (data || !wait) + break; + udelay(10); + } + return data; +} + +static int pasemi_rng_data_read(struct hwrng *rng, u32 *data) +{ + void __iomem *rng_regs = (void __iomem *)rng->priv; + *data = in_le32(rng_regs + SDCRNG_VAL_REG); + return 4; +} + +static int pasemi_rng_init(struct hwrng *rng) +{ + void __iomem *rng_regs = (void __iomem *)rng->priv; + u32 ctl; + + ctl = SDCRNG_CTL_DR | SDCRNG_CTL_SELECT_RRG_RNG | SDCRNG_CTL_KSZ; + out_le32(rng_regs + SDCRNG_CTL_REG, ctl); + out_le32(rng_regs + SDCRNG_CTL_REG, ctl & ~SDCRNG_CTL_DR); + + return 0; +} + +static void pasemi_rng_cleanup(struct hwrng *rng) +{ + void __iomem *rng_regs = (void __iomem *)rng->priv; + u32 ctl; + + ctl = SDCRNG_CTL_RE | SDCRNG_CTL_CE; + out_le32(rng_regs + SDCRNG_CTL_REG, + in_le32(rng_regs + SDCRNG_CTL_REG) & ~ctl); +} + +static struct hwrng pasemi_rng = { + .name = MODULE_NAME, + .init = pasemi_rng_init, + .cleanup = pasemi_rng_cleanup, + .data_present = pasemi_rng_data_present, + .data_read = pasemi_rng_data_read, +}; + +static int rng_probe(struct platform_device *pdev) +{ + void __iomem *rng_regs; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rng_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rng_regs)) + return PTR_ERR(rng_regs); + + pasemi_rng.priv = (unsigned long)rng_regs; + + pr_info("Registering PA Semi RNG\n"); + return devm_hwrng_register(&pdev->dev, &pasemi_rng); +} + +static const struct of_device_id rng_match[] = { + { .compatible = "1682m-rng", }, + { .compatible = "pasemi,pwrficient-rng", }, + { }, +}; +MODULE_DEVICE_TABLE(of, rng_match); + +static struct platform_driver rng_driver = { + .driver = { + .name = "pasemi-rng", + .of_match_table = rng_match, + }, + .probe = rng_probe, +}; + +module_platform_driver(rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>"); +MODULE_DESCRIPTION("H/W RNG driver for PA Semi processor"); diff --git a/drivers/char/hw_random/pic32-rng.c b/drivers/char/hw_random/pic32-rng.c new file mode 100644 index 000000000..9b5e68a71 --- /dev/null +++ b/drivers/char/hw_random/pic32-rng.c @@ -0,0 +1,151 @@ +/* + * PIC32 RNG driver + * + * Joshua Henderson <joshua.henderson@microchip.com> + * Copyright (C) 2016 Microchip Technology Inc. All rights reserved. + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define RNGCON 0x04 +#define TRNGEN BIT(8) +#define PRNGEN BIT(9) +#define PRNGCONT BIT(10) +#define TRNGMOD BIT(11) +#define SEEDLOAD BIT(12) +#define RNGPOLY1 0x08 +#define RNGPOLY2 0x0C +#define RNGNUMGEN1 0x10 +#define RNGNUMGEN2 0x14 +#define RNGSEED1 0x18 +#define RNGSEED2 0x1C +#define RNGRCNT 0x20 +#define RCNT_MASK 0x7F + +struct pic32_rng { + void __iomem *base; + struct hwrng rng; + struct clk *clk; +}; + +/* + * The TRNG can generate up to 24Mbps. This is a timeout that should be safe + * enough given the instructions in the loop and that the TRNG may not always + * be at maximum rate. + */ +#define RNG_TIMEOUT 500 + +static int pic32_rng_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct pic32_rng *priv = container_of(rng, struct pic32_rng, rng); + u64 *data = buf; + u32 t; + unsigned int timeout = RNG_TIMEOUT; + + do { + t = readl(priv->base + RNGRCNT) & RCNT_MASK; + if (t == 64) { + /* TRNG value comes through the seed registers */ + *data = ((u64)readl(priv->base + RNGSEED2) << 32) + + readl(priv->base + RNGSEED1); + return 8; + } + } while (wait && --timeout); + + return -EIO; +} + +static int pic32_rng_probe(struct platform_device *pdev) +{ + struct pic32_rng *priv; + struct resource *res; + u32 v; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + /* enable TRNG in enhanced mode */ + v = TRNGEN | TRNGMOD; + writel(v, priv->base + RNGCON); + + priv->rng.name = pdev->name; + priv->rng.read = pic32_rng_read; + + ret = hwrng_register(&priv->rng); + if (ret) + goto err_register; + + platform_set_drvdata(pdev, priv); + + return 0; + +err_register: + clk_disable_unprepare(priv->clk); + return ret; +} + +static int pic32_rng_remove(struct platform_device *pdev) +{ + struct pic32_rng *rng = platform_get_drvdata(pdev); + + hwrng_unregister(&rng->rng); + writel(0, rng->base + RNGCON); + clk_disable_unprepare(rng->clk); + return 0; +} + +static const struct of_device_id pic32_rng_of_match[] = { + { .compatible = "microchip,pic32mzda-rng", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pic32_rng_of_match); + +static struct platform_driver pic32_rng_driver = { + .probe = pic32_rng_probe, + .remove = pic32_rng_remove, + .driver = { + .name = "pic32-rng", + .of_match_table = of_match_ptr(pic32_rng_of_match), + }, +}; + +module_platform_driver(pic32_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>"); +MODULE_DESCRIPTION("Microchip PIC32 RNG Driver"); diff --git a/drivers/char/hw_random/powernv-rng.c b/drivers/char/hw_random/powernv-rng.c new file mode 100644 index 000000000..791182aa8 --- /dev/null +++ b/drivers/char/hw_random/powernv-rng.c @@ -0,0 +1,82 @@ +/* + * Copyright 2013 Michael Ellerman, Guo Chao, IBM Corp. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/random.h> +#include <linux/hw_random.h> + +static int powernv_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + unsigned long *buf; + int i, len; + + /* We rely on rng_buffer_size() being >= sizeof(unsigned long) */ + len = max / sizeof(unsigned long); + + buf = (unsigned long *)data; + + for (i = 0; i < len; i++) + powernv_get_random_long(buf++); + + return len * sizeof(unsigned long); +} + +static struct hwrng powernv_hwrng = { + .name = "powernv-rng", + .read = powernv_rng_read, +}; + +static int powernv_rng_remove(struct platform_device *pdev) +{ + hwrng_unregister(&powernv_hwrng); + + return 0; +} + +static int powernv_rng_probe(struct platform_device *pdev) +{ + int rc; + + rc = hwrng_register(&powernv_hwrng); + if (rc) { + /* We only register one device, ignore any others */ + if (rc == -EEXIST) + rc = -ENODEV; + + return rc; + } + + pr_info("Registered powernv hwrng.\n"); + + return 0; +} + +static const struct of_device_id powernv_rng_match[] = { + { .compatible = "ibm,power-rng",}, + {}, +}; +MODULE_DEVICE_TABLE(of, powernv_rng_match); + +static struct platform_driver powernv_rng_driver = { + .driver = { + .name = "powernv_rng", + .of_match_table = powernv_rng_match, + }, + .probe = powernv_rng_probe, + .remove = powernv_rng_remove, +}; +module_platform_driver(powernv_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Bare metal HWRNG driver for POWER7+ and above"); diff --git a/drivers/char/hw_random/pseries-rng.c b/drivers/char/hw_random/pseries-rng.c new file mode 100644 index 000000000..4e2a3f635 --- /dev/null +++ b/drivers/char/hw_random/pseries-rng.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 Michael Neuling IBM Corporation + * + * Driver for the pseries hardware RNG for POWER7+ and above + * + * 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. + * + * 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 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/hw_random.h> +#include <asm/vio.h> + + +static int pseries_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + u64 buffer[PLPAR_HCALL_BUFSIZE]; + int rc; + + rc = plpar_hcall(H_RANDOM, (unsigned long *)buffer); + if (rc != H_SUCCESS) { + pr_err_ratelimited("H_RANDOM call failed %d\n", rc); + return -EIO; + } + memcpy(data, buffer, 8); + + /* The hypervisor interface returns 64 bits */ + return 8; +} + +/** + * pseries_rng_get_desired_dma - Return desired DMA allocate for CMO operations + * + * This is a required function for a driver to operate in a CMO environment + * but this device does not make use of DMA allocations, return 0. + * + * Return value: + * Number of bytes of IO data the driver will need to perform well -> 0 + */ +static unsigned long pseries_rng_get_desired_dma(struct vio_dev *vdev) +{ + return 0; +}; + +static struct hwrng pseries_rng = { + .name = KBUILD_MODNAME, + .read = pseries_rng_read, +}; + +static int pseries_rng_probe(struct vio_dev *dev, + const struct vio_device_id *id) +{ + return hwrng_register(&pseries_rng); +} + +static int pseries_rng_remove(struct vio_dev *dev) +{ + hwrng_unregister(&pseries_rng); + return 0; +} + +static const struct vio_device_id pseries_rng_driver_ids[] = { + { "ibm,random-v1", "ibm,random"}, + { "", "" } +}; +MODULE_DEVICE_TABLE(vio, pseries_rng_driver_ids); + +static struct vio_driver pseries_rng_driver = { + .name = KBUILD_MODNAME, + .probe = pseries_rng_probe, + .remove = pseries_rng_remove, + .get_desired_dma = pseries_rng_get_desired_dma, + .id_table = pseries_rng_driver_ids +}; + +static int __init rng_init(void) +{ + pr_info("Registering IBM pSeries RNG driver\n"); + return vio_register_driver(&pseries_rng_driver); +} + +module_init(rng_init); + +static void __exit rng_exit(void) +{ + vio_unregister_driver(&pseries_rng_driver); +} +module_exit(rng_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Neuling <mikey@neuling.org>"); +MODULE_DESCRIPTION("H/W RNG driver for IBM pSeries processors"); diff --git a/drivers/char/hw_random/s390-trng.c b/drivers/char/hw_random/s390-trng.c new file mode 100644 index 000000000..aca48e893 --- /dev/null +++ b/drivers/char/hw_random/s390-trng.c @@ -0,0 +1,268 @@ +/* + * s390 TRNG device driver + * + * Driver for the TRNG (true random number generation) command + * available via CPACF extension MSA 7 on the s390 arch. + + * Copyright IBM Corp. 2017 + * Author(s): Harald Freudenberger <freude@de.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2 only) + * as published by the Free Software Foundation. + * + */ + +#define KMSG_COMPONENT "trng" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include <linux/hw_random.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/cpufeature.h> +#include <linux/miscdevice.h> +#include <linux/debugfs.h> +#include <linux/atomic.h> +#include <linux/random.h> +#include <linux/sched/signal.h> +#include <asm/debug.h> +#include <asm/cpacf.h> + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("IBM Corporation"); +MODULE_DESCRIPTION("s390 CPACF TRNG device driver"); + + +/* trng related debug feature things */ + +static debug_info_t *debug_info; + +#define DEBUG_DBG(...) debug_sprintf_event(debug_info, 6, ##__VA_ARGS__) +#define DEBUG_INFO(...) debug_sprintf_event(debug_info, 5, ##__VA_ARGS__) +#define DEBUG_WARN(...) debug_sprintf_event(debug_info, 4, ##__VA_ARGS__) +#define DEBUG_ERR(...) debug_sprintf_event(debug_info, 3, ##__VA_ARGS__) + + +/* trng helpers */ + +static atomic64_t trng_dev_counter = ATOMIC64_INIT(0); +static atomic64_t trng_hwrng_counter = ATOMIC64_INIT(0); + + +/* file io functions */ + +static int trng_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +static ssize_t trng_read(struct file *file, char __user *ubuf, + size_t nbytes, loff_t *ppos) +{ + u8 buf[32]; + u8 *p = buf; + unsigned int n; + ssize_t ret = 0; + + /* + * use buf for requests <= sizeof(buf), + * otherwise allocate one page and fetch + * pagewise. + */ + + if (nbytes > sizeof(buf)) { + p = (u8 *) __get_free_page(GFP_KERNEL); + if (!p) + return -ENOMEM; + } + + while (nbytes) { + if (need_resched()) { + if (signal_pending(current)) { + if (ret == 0) + ret = -ERESTARTSYS; + break; + } + schedule(); + } + n = nbytes > PAGE_SIZE ? PAGE_SIZE : nbytes; + cpacf_trng(NULL, 0, p, n); + atomic64_add(n, &trng_dev_counter); + if (copy_to_user(ubuf, p, n)) { + ret = -EFAULT; + break; + } + nbytes -= n; + ubuf += n; + ret += n; + } + + if (p != buf) + free_page((unsigned long) p); + + DEBUG_DBG("trng_read()=%zd\n", ret); + return ret; +} + + +/* sysfs */ + +static ssize_t trng_counter_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u64 dev_counter = atomic64_read(&trng_dev_counter); + u64 hwrng_counter = atomic64_read(&trng_hwrng_counter); +#if IS_ENABLED(CONFIG_ARCH_RANDOM) + u64 arch_counter = atomic64_read(&s390_arch_random_counter); + + return snprintf(buf, PAGE_SIZE, + "trng: %llu\n" + "hwrng: %llu\n" + "arch: %llu\n" + "total: %llu\n", + dev_counter, hwrng_counter, arch_counter, + dev_counter + hwrng_counter + arch_counter); +#else + return snprintf(buf, PAGE_SIZE, + "trng: %llu\n" + "hwrng: %llu\n" + "total: %llu\n", + dev_counter, hwrng_counter, + dev_counter + hwrng_counter); +#endif +} +static DEVICE_ATTR(byte_counter, 0444, trng_counter_show, NULL); + +static struct attribute *trng_dev_attrs[] = { + &dev_attr_byte_counter.attr, + NULL +}; + +static const struct attribute_group trng_dev_attr_group = { + .attrs = trng_dev_attrs +}; + +static const struct attribute_group *trng_dev_attr_groups[] = { + &trng_dev_attr_group, + NULL +}; + +static const struct file_operations trng_fops = { + .owner = THIS_MODULE, + .open = &trng_open, + .release = NULL, + .read = &trng_read, + .llseek = noop_llseek, +}; + +static struct miscdevice trng_dev = { + .name = "trng", + .minor = MISC_DYNAMIC_MINOR, + .mode = 0444, + .fops = &trng_fops, + .groups = trng_dev_attr_groups, +}; + + +/* hwrng_register */ + +static inline void _trng_hwrng_read(u8 *buf, size_t len) +{ + cpacf_trng(NULL, 0, buf, len); + atomic64_add(len, &trng_hwrng_counter); +} + +static int trng_hwrng_data_read(struct hwrng *rng, u32 *data) +{ + size_t len = sizeof(*data); + + _trng_hwrng_read((u8 *) data, len); + + DEBUG_DBG("trng_hwrng_data_read()=%zu\n", len); + + return len; +} + +static int trng_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + size_t len = max <= PAGE_SIZE ? max : PAGE_SIZE; + + _trng_hwrng_read((u8 *) data, len); + + DEBUG_DBG("trng_hwrng_read()=%zu\n", len); + + return len; +} + +/* + * hwrng register struct + * The trng is suppost to have 100% entropy, and thus + * we register with a very high quality value. + */ +static struct hwrng trng_hwrng_dev = { + .name = "s390-trng", + .data_read = trng_hwrng_data_read, + .read = trng_hwrng_read, + .quality = 999, +}; + + +/* init and exit */ + +static void __init trng_debug_init(void) +{ + debug_info = debug_register("trng", 1, 1, 4 * sizeof(long)); + debug_register_view(debug_info, &debug_sprintf_view); + debug_set_level(debug_info, 3); +} + +static void trng_debug_exit(void) +{ + debug_unregister(debug_info); +} + +static int __init trng_init(void) +{ + int ret; + + trng_debug_init(); + + /* check if subfunction CPACF_PRNO_TRNG is available */ + if (!cpacf_query_func(CPACF_PRNO, CPACF_PRNO_TRNG)) { + DEBUG_INFO("trng_init CPACF_PRNO_TRNG not available\n"); + ret = -ENODEV; + goto out_dbg; + } + + ret = misc_register(&trng_dev); + if (ret) { + DEBUG_WARN("trng_init misc_register() failed rc=%d\n", ret); + goto out_dbg; + } + + ret = hwrng_register(&trng_hwrng_dev); + if (ret) { + DEBUG_WARN("trng_init hwrng_register() failed rc=%d\n", ret); + goto out_misc; + } + + DEBUG_DBG("trng_init successful\n"); + + return 0; + +out_misc: + misc_deregister(&trng_dev); +out_dbg: + trng_debug_exit(); + return ret; +} + +static void __exit trng_exit(void) +{ + hwrng_unregister(&trng_hwrng_dev); + misc_deregister(&trng_dev); + trng_debug_exit(); +} + +module_cpu_feature_match(MSA, trng_init); +module_exit(trng_exit); diff --git a/drivers/char/hw_random/st-rng.c b/drivers/char/hw_random/st-rng.c new file mode 100644 index 000000000..938ec10e7 --- /dev/null +++ b/drivers/char/hw_random/st-rng.c @@ -0,0 +1,149 @@ +/* + * ST Random Number Generator Driver ST's Platforms + * + * Author: Pankaj Dev: <pankaj.dev@st.com> + * Lee Jones <lee.jones@linaro.org> + * + * Copyright (C) 2015 STMicroelectronics (R&D) Limited + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* Registers */ +#define ST_RNG_STATUS_REG 0x20 +#define ST_RNG_DATA_REG 0x24 + +/* Registers fields */ +#define ST_RNG_STATUS_BAD_SEQUENCE BIT(0) +#define ST_RNG_STATUS_BAD_ALTERNANCE BIT(1) +#define ST_RNG_STATUS_FIFO_FULL BIT(5) + +#define ST_RNG_SAMPLE_SIZE 2 /* 2 Byte (16bit) samples */ +#define ST_RNG_FIFO_DEPTH 4 +#define ST_RNG_FIFO_SIZE (ST_RNG_FIFO_DEPTH * ST_RNG_SAMPLE_SIZE) + +/* + * Samples are documented to be available every 0.667us, so in theory + * the 4 sample deep FIFO should take 2.668us to fill. However, during + * thorough testing, it became apparent that filling the FIFO actually + * takes closer to 12us. We then multiply by 2 in order to account for + * the lack of udelay()'s reliability, suggested by Russell King. + */ +#define ST_RNG_FILL_FIFO_TIMEOUT (12 * 2) + +struct st_rng_data { + void __iomem *base; + struct clk *clk; + struct hwrng ops; +}; + +static int st_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + struct st_rng_data *ddata = (struct st_rng_data *)rng->priv; + u32 status; + int i; + + /* Wait until FIFO is full - max 4uS*/ + for (i = 0; i < ST_RNG_FILL_FIFO_TIMEOUT; i++) { + status = readl_relaxed(ddata->base + ST_RNG_STATUS_REG); + if (status & ST_RNG_STATUS_FIFO_FULL) + break; + udelay(1); + } + + if (i == ST_RNG_FILL_FIFO_TIMEOUT) + return 0; + + for (i = 0; i < ST_RNG_FIFO_SIZE && i < max; i += 2) + *(u16 *)(data + i) = + readl_relaxed(ddata->base + ST_RNG_DATA_REG); + + return i; /* No of bytes read */ +} + +static int st_rng_probe(struct platform_device *pdev) +{ + struct st_rng_data *ddata; + struct resource *res; + struct clk *clk; + void __iomem *base; + int ret; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_prepare_enable(clk); + if (ret) + return ret; + + ddata->ops.priv = (unsigned long)ddata; + ddata->ops.read = st_rng_read; + ddata->ops.name = pdev->name; + ddata->base = base; + ddata->clk = clk; + + dev_set_drvdata(&pdev->dev, ddata); + + ret = hwrng_register(&ddata->ops); + if (ret) { + dev_err(&pdev->dev, "Failed to register HW RNG\n"); + clk_disable_unprepare(clk); + return ret; + } + + dev_info(&pdev->dev, "Successfully registered HW RNG\n"); + + return 0; +} + +static int st_rng_remove(struct platform_device *pdev) +{ + struct st_rng_data *ddata = dev_get_drvdata(&pdev->dev); + + hwrng_unregister(&ddata->ops); + + clk_disable_unprepare(ddata->clk); + + return 0; +} + +static const struct of_device_id st_rng_match[] = { + { .compatible = "st,rng" }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_rng_match); + +static struct platform_driver st_rng_driver = { + .driver = { + .name = "st-hwrandom", + .of_match_table = of_match_ptr(st_rng_match), + }, + .probe = st_rng_probe, + .remove = st_rng_remove +}; + +module_platform_driver(st_rng_driver); + +MODULE_AUTHOR("Pankaj Dev <pankaj.dev@st.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/hw_random/stm32-rng.c b/drivers/char/hw_random/stm32-rng.c new file mode 100644 index 000000000..37b338a76 --- /dev/null +++ b/drivers/char/hw_random/stm32-rng.c @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2015, Daniel Thompson + * + * This file 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 file 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. + */ + +#include <linux/clk.h> +#include <linux/delay.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_address.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#define RNG_CR 0x00 +#define RNG_CR_RNGEN BIT(2) +#define RNG_CR_CED BIT(5) + +#define RNG_SR 0x04 +#define RNG_SR_SEIS BIT(6) +#define RNG_SR_CEIS BIT(5) +#define RNG_SR_DRDY BIT(0) + +#define RNG_DR 0x08 + +struct stm32_rng_private { + struct hwrng rng; + void __iomem *base; + struct clk *clk; + struct reset_control *rst; + bool ced; +}; + +static int stm32_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + struct stm32_rng_private *priv = + container_of(rng, struct stm32_rng_private, rng); + u32 sr; + int retval = 0; + + pm_runtime_get_sync((struct device *) priv->rng.priv); + + while (max > sizeof(u32)) { + sr = readl_relaxed(priv->base + RNG_SR); + /* Manage timeout which is based on timer and take */ + /* care of initial delay time when enabling rng */ + if (!sr && wait) { + retval = readl_relaxed_poll_timeout_atomic(priv->base + + RNG_SR, + sr, sr, + 10, 50000); + if (retval) + dev_err((struct device *)priv->rng.priv, + "%s: timeout %x!\n", __func__, sr); + } + + /* If error detected or data not ready... */ + if (sr != RNG_SR_DRDY) { + if (WARN_ONCE(sr & (RNG_SR_SEIS | RNG_SR_CEIS), + "bad RNG status - %x\n", sr)) + writel_relaxed(0, priv->base + RNG_SR); + break; + } + + *(u32 *)data = readl_relaxed(priv->base + RNG_DR); + + retval += sizeof(u32); + data += sizeof(u32); + max -= sizeof(u32); + } + + pm_runtime_mark_last_busy((struct device *) priv->rng.priv); + pm_runtime_put_sync_autosuspend((struct device *) priv->rng.priv); + + return retval || !wait ? retval : -EIO; +} + +static int stm32_rng_init(struct hwrng *rng) +{ + struct stm32_rng_private *priv = + container_of(rng, struct stm32_rng_private, rng); + int err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + + if (priv->ced) + writel_relaxed(RNG_CR_RNGEN, priv->base + RNG_CR); + else + writel_relaxed(RNG_CR_RNGEN | RNG_CR_CED, + priv->base + RNG_CR); + + /* clear error indicators */ + writel_relaxed(0, priv->base + RNG_SR); + + return 0; +} + +static void stm32_rng_cleanup(struct hwrng *rng) +{ + struct stm32_rng_private *priv = + container_of(rng, struct stm32_rng_private, rng); + + writel_relaxed(0, priv->base + RNG_CR); + clk_disable_unprepare(priv->clk); +} + +static int stm32_rng_probe(struct platform_device *ofdev) +{ + struct device *dev = &ofdev->dev; + struct device_node *np = ofdev->dev.of_node; + struct stm32_rng_private *priv; + struct resource res; + int err; + + priv = devm_kzalloc(dev, sizeof(struct stm32_rng_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + err = of_address_to_resource(np, 0, &res); + if (err) + return err; + + priv->base = devm_ioremap_resource(dev, &res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(&ofdev->dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->rst = devm_reset_control_get(&ofdev->dev, NULL); + if (!IS_ERR(priv->rst)) { + reset_control_assert(priv->rst); + udelay(2); + reset_control_deassert(priv->rst); + } + + priv->ced = of_property_read_bool(np, "clock-error-detect"); + + dev_set_drvdata(dev, priv); + + priv->rng.name = dev_driver_string(dev), +#ifndef CONFIG_PM + priv->rng.init = stm32_rng_init, + priv->rng.cleanup = stm32_rng_cleanup, +#endif + priv->rng.read = stm32_rng_read, + priv->rng.priv = (unsigned long) dev; + + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + return devm_hwrng_register(dev, &priv->rng); +} + +static int stm32_rng_remove(struct platform_device *ofdev) +{ + pm_runtime_disable(&ofdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int stm32_rng_runtime_suspend(struct device *dev) +{ + struct stm32_rng_private *priv = dev_get_drvdata(dev); + + stm32_rng_cleanup(&priv->rng); + + return 0; +} + +static int stm32_rng_runtime_resume(struct device *dev) +{ + struct stm32_rng_private *priv = dev_get_drvdata(dev); + + return stm32_rng_init(&priv->rng); +} +#endif + +static const struct dev_pm_ops stm32_rng_pm_ops = { + SET_RUNTIME_PM_OPS(stm32_rng_runtime_suspend, + stm32_rng_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + + +static const struct of_device_id stm32_rng_match[] = { + { + .compatible = "st,stm32-rng", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_rng_match); + +static struct platform_driver stm32_rng_driver = { + .driver = { + .name = "stm32-rng", + .pm = &stm32_rng_pm_ops, + .of_match_table = stm32_rng_match, + }, + .probe = stm32_rng_probe, + .remove = stm32_rng_remove, +}; + +module_platform_driver(stm32_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel Thompson <daniel.thompson@linaro.org>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 RNG device driver"); diff --git a/drivers/char/hw_random/timeriomem-rng.c b/drivers/char/hw_random/timeriomem-rng.c new file mode 100644 index 000000000..ba01f24db --- /dev/null +++ b/drivers/char/hw_random/timeriomem-rng.c @@ -0,0 +1,216 @@ +/* + * 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> + * + * 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. + * + * 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/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> + +struct timeriomem_rng_private { + void __iomem *io_base; + ktime_t period; + unsigned int present:1; + + struct hrtimer timer; + struct completion completion; + + 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); + + /* + * 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); + + 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 + max(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); + hrtimer_forward_now(&priv->timer, priv->period); + hrtimer_restart(&priv->timer); + + return retval; +} + +static enum hrtimer_restart timeriomem_rng_trigger(struct hrtimer *timer) +{ + 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 *priv; + struct resource *res; + int err = 0; + int period; + + if (!pdev->dev.of_node && !pdata) { + dev_err(&pdev->dev, "timeriomem_rng_data is missing\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + if (res->start % 4 != 0 || resource_size(res) != 4) { + dev_err(&pdev->dev, + "address must be four bytes wide and aligned\n"); + return -EINVAL; + } + + /* Allocate memory for the device structure (and zero it) */ + priv = devm_kzalloc(&pdev->dev, + sizeof(struct timeriomem_rng_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + if (pdev->dev.of_node) { + int i; + + if (!of_property_read_u32(pdev->dev.of_node, + "period", &i)) + period = i; + else { + dev_err(&pdev->dev, "missing period\n"); + return -EINVAL; + } + + if (!of_property_read_u32(pdev->dev.of_node, + "quality", &i)) + priv->rng_ops.quality = i; + else + priv->rng_ops.quality = 0; + } else { + period = pdata->period; + priv->rng_ops.quality = pdata->quality; + } + + priv->period = ns_to_ktime(period * NSEC_PER_USEC); + init_completion(&priv->completion); + hrtimer_init(&priv->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + priv->timer.function = timeriomem_rng_trigger; + + 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)) { + return PTR_ERR(priv->io_base); + } + + /* 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"); + return err; + } + + dev_info(&pdev->dev, "32bits from 0x%p @ %dus\n", + priv->io_base, period); + + return 0; +} + +static int timeriomem_rng_remove(struct platform_device *pdev) +{ + struct timeriomem_rng_private *priv = platform_get_drvdata(pdev); + + hwrng_unregister(&priv->rng_ops); + hrtimer_cancel(&priv->timer); + + return 0; +} + +static const struct of_device_id timeriomem_rng_match[] = { + { .compatible = "timeriomem_rng" }, + {}, +}; +MODULE_DEVICE_TABLE(of, timeriomem_rng_match); + +static struct platform_driver timeriomem_rng_driver = { + .driver = { + .name = "timeriomem_rng", + .of_match_table = timeriomem_rng_match, + }, + .probe = timeriomem_rng_probe, + .remove = timeriomem_rng_remove, +}; + +module_platform_driver(timeriomem_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alexander Clouter <alex@digriz.org.uk>"); +MODULE_DESCRIPTION("Timer IOMEM H/W RNG driver"); diff --git a/drivers/char/hw_random/tx4939-rng.c b/drivers/char/hw_random/tx4939-rng.c new file mode 100644 index 000000000..1093583b5 --- /dev/null +++ b/drivers/char/hw_random/tx4939-rng.c @@ -0,0 +1,159 @@ +/* + * RNG driver for TX4939 Random Number Generators (RNG) + * + * Copyright (C) 2009 Atsushi Nemoto <anemo@mba.ocn.ne.jp> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/hw_random.h> +#include <linux/gfp.h> + +#define TX4939_RNG_RCSR 0x00000000 +#define TX4939_RNG_ROR(n) (0x00000018 + (n) * 8) + +#define TX4939_RNG_RCSR_INTE 0x00000008 +#define TX4939_RNG_RCSR_RST 0x00000004 +#define TX4939_RNG_RCSR_FIN 0x00000002 +#define TX4939_RNG_RCSR_ST 0x00000001 + +struct tx4939_rng { + struct hwrng rng; + void __iomem *base; + u64 databuf[3]; + unsigned int data_avail; +}; + +static void rng_io_start(void) +{ +#ifndef CONFIG_64BIT + /* + * readq is reading a 64-bit register using a 64-bit load. On + * a 32-bit kernel however interrupts or any other processor + * exception would clobber the upper 32-bit of the processor + * register so interrupts need to be disabled. + */ + local_irq_disable(); +#endif +} + +static void rng_io_end(void) +{ +#ifndef CONFIG_64BIT + local_irq_enable(); +#endif +} + +static u64 read_rng(void __iomem *base, unsigned int offset) +{ + return ____raw_readq(base + offset); +} + +static void write_rng(u64 val, void __iomem *base, unsigned int offset) +{ + return ____raw_writeq(val, base + offset); +} + +static int tx4939_rng_data_present(struct hwrng *rng, int wait) +{ + struct tx4939_rng *rngdev = container_of(rng, struct tx4939_rng, rng); + int i; + + if (rngdev->data_avail) + return rngdev->data_avail; + for (i = 0; i < 20; i++) { + rng_io_start(); + if (!(read_rng(rngdev->base, TX4939_RNG_RCSR) + & TX4939_RNG_RCSR_ST)) { + rngdev->databuf[0] = + read_rng(rngdev->base, TX4939_RNG_ROR(0)); + rngdev->databuf[1] = + read_rng(rngdev->base, TX4939_RNG_ROR(1)); + rngdev->databuf[2] = + read_rng(rngdev->base, TX4939_RNG_ROR(2)); + rngdev->data_avail = + sizeof(rngdev->databuf) / sizeof(u32); + /* Start RNG */ + write_rng(TX4939_RNG_RCSR_ST, + rngdev->base, TX4939_RNG_RCSR); + wait = 0; + } + rng_io_end(); + if (!wait) + break; + /* 90 bus clock cycles by default for generation */ + ndelay(90 * 5); + } + return rngdev->data_avail; +} + +static int tx4939_rng_data_read(struct hwrng *rng, u32 *buffer) +{ + struct tx4939_rng *rngdev = container_of(rng, struct tx4939_rng, rng); + + rngdev->data_avail--; + *buffer = *((u32 *)&rngdev->databuf + rngdev->data_avail); + return sizeof(u32); +} + +static int __init tx4939_rng_probe(struct platform_device *dev) +{ + struct tx4939_rng *rngdev; + struct resource *r; + int i; + + rngdev = devm_kzalloc(&dev->dev, sizeof(*rngdev), GFP_KERNEL); + if (!rngdev) + return -ENOMEM; + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + rngdev->base = devm_ioremap_resource(&dev->dev, r); + if (IS_ERR(rngdev->base)) + return PTR_ERR(rngdev->base); + + rngdev->rng.name = dev_name(&dev->dev); + rngdev->rng.data_present = tx4939_rng_data_present; + rngdev->rng.data_read = tx4939_rng_data_read; + + rng_io_start(); + /* Reset RNG */ + write_rng(TX4939_RNG_RCSR_RST, rngdev->base, TX4939_RNG_RCSR); + write_rng(0, rngdev->base, TX4939_RNG_RCSR); + /* Start RNG */ + write_rng(TX4939_RNG_RCSR_ST, rngdev->base, TX4939_RNG_RCSR); + rng_io_end(); + /* + * Drop first two results. From the datasheet: + * The quality of the random numbers generated immediately + * after reset can be insufficient. Therefore, do not use + * random numbers obtained from the first and second + * generations; use the ones from the third or subsequent + * generation. + */ + for (i = 0; i < 2; i++) { + rngdev->data_avail = 0; + if (!tx4939_rng_data_present(&rngdev->rng, 1)) + return -EIO; + } + + platform_set_drvdata(dev, rngdev); + return devm_hwrng_register(&dev->dev, &rngdev->rng); +} + +static struct platform_driver tx4939_rng_driver = { + .driver = { + .name = "tx4939-rng", + }, +}; + +module_platform_driver_probe(tx4939_rng_driver, tx4939_rng_probe); + +MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver for TX4939"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/via-rng.c b/drivers/char/hw_random/via-rng.c new file mode 100644 index 000000000..ffe9b0c6c --- /dev/null +++ b/drivers/char/hw_random/via-rng.c @@ -0,0 +1,228 @@ +/* + * RNG driver for VIA RNGs + * + * Copyright 2005 (c) MontaVista Software, Inc. + * + * with the majority of the code coming from: + * + * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) + * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> + * + * derived from + * + * Hardware driver for the AMD 768 Random Number Generator (RNG) + * (c) Copyright 2001 Red Hat Inc + * + * derived from + * + * 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 <crypto/padlock.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/hw_random.h> +#include <linux/delay.h> +#include <asm/cpu_device_id.h> +#include <asm/io.h> +#include <asm/msr.h> +#include <asm/cpufeature.h> +#include <asm/fpu/api.h> + + + + +enum { + VIA_STRFILT_CNT_SHIFT = 16, + VIA_STRFILT_FAIL = (1 << 15), + VIA_STRFILT_ENABLE = (1 << 14), + VIA_RAWBITS_ENABLE = (1 << 13), + VIA_RNG_ENABLE = (1 << 6), + VIA_NOISESRC1 = (1 << 8), + VIA_NOISESRC2 = (1 << 9), + VIA_XSTORE_CNT_MASK = 0x0F, + + VIA_RNG_CHUNK_8 = 0x00, /* 64 rand bits, 64 stored bits */ + VIA_RNG_CHUNK_4 = 0x01, /* 32 rand bits, 32 stored bits */ + VIA_RNG_CHUNK_4_MASK = 0xFFFFFFFF, + VIA_RNG_CHUNK_2 = 0x02, /* 16 rand bits, 32 stored bits */ + VIA_RNG_CHUNK_2_MASK = 0xFFFF, + VIA_RNG_CHUNK_1 = 0x03, /* 8 rand bits, 32 stored bits */ + VIA_RNG_CHUNK_1_MASK = 0xFF, +}; + +/* + * Investigate using the 'rep' prefix to obtain 32 bits of random data + * in one insn. The upside is potentially better performance. The + * downside is that the instruction becomes no longer atomic. Due to + * this, just like familiar issues with /dev/random itself, the worst + * case of a 'rep xstore' could potentially pause a cpu for an + * unreasonably long time. In practice, this condition would likely + * only occur when the hardware is failing. (or so we hope :)) + * + * Another possible performance boost may come from simply buffering + * until we have 4 bytes, thus returning a u32 at a time, + * instead of the current u8-at-a-time. + * + * Padlock instructions can generate a spurious DNA fault, but the + * kernel doesn't use CR0.TS, so this doesn't matter. + */ + +static inline u32 xstore(u32 *addr, u32 edx_in) +{ + u32 eax_out; + + asm(".byte 0x0F,0xA7,0xC0 /* xstore %%edi (addr=%0) */" + : "=m" (*addr), "=a" (eax_out), "+d" (edx_in), "+D" (addr)); + + return eax_out; +} + +static int via_rng_data_present(struct hwrng *rng, int wait) +{ + char buf[16 + PADLOCK_ALIGNMENT - STACK_ALIGN] __attribute__ + ((aligned(STACK_ALIGN))); + u32 *via_rng_datum = (u32 *)PTR_ALIGN(&buf[0], PADLOCK_ALIGNMENT); + u32 bytes_out; + int i; + + /* We choose the recommended 1-byte-per-instruction RNG rate, + * for greater randomness at the expense of speed. Larger + * values 2, 4, or 8 bytes-per-instruction yield greater + * speed at lesser randomness. + * + * If you change this to another VIA_CHUNK_n, you must also + * change the ->n_bytes values in rng_vendor_ops[] tables. + * VIA_CHUNK_8 requires further code changes. + * + * A copy of MSR_VIA_RNG is placed in eax_out when xstore + * completes. + */ + + for (i = 0; i < 20; i++) { + *via_rng_datum = 0; /* paranoia, not really necessary */ + bytes_out = xstore(via_rng_datum, VIA_RNG_CHUNK_1); + bytes_out &= VIA_XSTORE_CNT_MASK; + if (bytes_out || !wait) + break; + udelay(10); + } + rng->priv = *via_rng_datum; + return bytes_out ? 1 : 0; +} + +static int via_rng_data_read(struct hwrng *rng, u32 *data) +{ + u32 via_rng_datum = (u32)rng->priv; + + *data = via_rng_datum; + + return 1; +} + +static int via_rng_init(struct hwrng *rng) +{ + struct cpuinfo_x86 *c = &cpu_data(0); + u32 lo, hi, old_lo; + + /* VIA Nano CPUs don't have the MSR_VIA_RNG anymore. The RNG + * is always enabled if CPUID rng_en is set. There is no + * RNG configuration like it used to be the case in this + * register */ + if (((c->x86 == 6) && (c->x86_model >= 0x0f)) || (c->x86 > 6)){ + if (!boot_cpu_has(X86_FEATURE_XSTORE_EN)) { + pr_err(PFX "can't enable hardware RNG " + "if XSTORE is not enabled\n"); + return -ENODEV; + } + return 0; + } + + /* Control the RNG via MSR. Tread lightly and pay very close + * close attention to values written, as the reserved fields + * are documented to be "undefined and unpredictable"; but it + * does not say to write them as zero, so I make a guess that + * we restore the values we find in the register. + */ + rdmsr(MSR_VIA_RNG, lo, hi); + + old_lo = lo; + lo &= ~(0x7f << VIA_STRFILT_CNT_SHIFT); + lo &= ~VIA_XSTORE_CNT_MASK; + lo &= ~(VIA_STRFILT_ENABLE | VIA_STRFILT_FAIL | VIA_RAWBITS_ENABLE); + lo |= VIA_RNG_ENABLE; + lo |= VIA_NOISESRC1; + + /* Enable secondary noise source on CPUs where it is present. */ + + /* Nehemiah stepping 8 and higher */ + if ((c->x86_model == 9) && (c->x86_stepping > 7)) + lo |= VIA_NOISESRC2; + + /* Esther */ + if (c->x86_model >= 10) + lo |= VIA_NOISESRC2; + + if (lo != old_lo) + wrmsr(MSR_VIA_RNG, lo, hi); + + /* perhaps-unnecessary sanity check; remove after testing if + unneeded */ + rdmsr(MSR_VIA_RNG, lo, hi); + if ((lo & VIA_RNG_ENABLE) == 0) { + pr_err(PFX "cannot enable VIA C3 RNG, aborting\n"); + return -ENODEV; + } + + return 0; +} + + +static struct hwrng via_rng = { + .name = "via", + .init = via_rng_init, + .data_present = via_rng_data_present, + .data_read = via_rng_data_read, +}; + + +static int __init mod_init(void) +{ + int err; + + if (!boot_cpu_has(X86_FEATURE_XSTORE)) + return -ENODEV; + + pr_info("VIA RNG detected\n"); + err = hwrng_register(&via_rng); + if (err) { + pr_err(PFX "RNG registering failed (%d)\n", + err); + goto out; + } +out: + return err; +} + +static void __exit mod_exit(void) +{ + hwrng_unregister(&via_rng); +} + +module_init(mod_init); +module_exit(mod_exit); + +static struct x86_cpu_id __maybe_unused via_rng_cpu_id[] = { + X86_FEATURE_MATCH(X86_FEATURE_XSTORE), + {} +}; + +MODULE_DESCRIPTION("H/W RNG driver for VIA CPU with PadLock"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(x86cpu, via_rng_cpu_id); diff --git a/drivers/char/hw_random/virtio-rng.c b/drivers/char/hw_random/virtio-rng.c new file mode 100644 index 000000000..7abd604e9 --- /dev/null +++ b/drivers/char/hw_random/virtio-rng.c @@ -0,0 +1,231 @@ +/* + * Randomness driver for virtio + * Copyright (C) 2007, 2008 Rusty Russell IBM Corporation + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/err.h> +#include <linux/hw_random.h> +#include <linux/scatterlist.h> +#include <linux/spinlock.h> +#include <linux/virtio.h> +#include <linux/virtio_rng.h> +#include <linux/module.h> + +static DEFINE_IDA(rng_index_ida); + +struct virtrng_info { + struct hwrng hwrng; + struct virtqueue *vq; + struct completion have_data; + char name[25]; + unsigned int data_avail; + int index; + bool busy; + bool hwrng_register_done; + bool hwrng_removed; +}; + +static void random_recv_done(struct virtqueue *vq) +{ + struct virtrng_info *vi = vq->vdev->priv; + + /* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */ + if (!virtqueue_get_buf(vi->vq, &vi->data_avail)) + return; + + complete(&vi->have_data); +} + +/* The host will fill any buffer we give it with sweet, sweet randomness. */ +static void register_buffer(struct virtrng_info *vi, u8 *buf, size_t size) +{ + struct scatterlist sg; + + sg_init_one(&sg, buf, size); + + /* There should always be room for one buffer. */ + virtqueue_add_inbuf(vi->vq, &sg, 1, buf, GFP_KERNEL); + + virtqueue_kick(vi->vq); +} + +static int virtio_read(struct hwrng *rng, void *buf, size_t size, bool wait) +{ + int ret; + struct virtrng_info *vi = (struct virtrng_info *)rng->priv; + + if (vi->hwrng_removed) + return -ENODEV; + + if (!vi->busy) { + vi->busy = true; + reinit_completion(&vi->have_data); + register_buffer(vi, buf, size); + } + + if (!wait) + return 0; + + ret = wait_for_completion_killable(&vi->have_data); + if (ret < 0) + return ret; + + vi->busy = false; + + return vi->data_avail; +} + +static void virtio_cleanup(struct hwrng *rng) +{ + struct virtrng_info *vi = (struct virtrng_info *)rng->priv; + + if (vi->busy) + wait_for_completion(&vi->have_data); +} + +static int probe_common(struct virtio_device *vdev) +{ + int err, index; + struct virtrng_info *vi = NULL; + + vi = kzalloc(sizeof(struct virtrng_info), GFP_KERNEL); + if (!vi) + return -ENOMEM; + + vi->index = index = ida_simple_get(&rng_index_ida, 0, 0, GFP_KERNEL); + if (index < 0) { + err = index; + goto err_ida; + } + sprintf(vi->name, "virtio_rng.%d", index); + init_completion(&vi->have_data); + + vi->hwrng = (struct hwrng) { + .read = virtio_read, + .cleanup = virtio_cleanup, + .priv = (unsigned long)vi, + .name = vi->name, + .quality = 1000, + }; + vdev->priv = vi; + + /* We expect a single virtqueue. */ + vi->vq = virtio_find_single_vq(vdev, random_recv_done, "input"); + if (IS_ERR(vi->vq)) { + err = PTR_ERR(vi->vq); + goto err_find; + } + + return 0; + +err_find: + ida_simple_remove(&rng_index_ida, index); +err_ida: + kfree(vi); + return err; +} + +static void remove_common(struct virtio_device *vdev) +{ + struct virtrng_info *vi = vdev->priv; + + vi->hwrng_removed = true; + vi->data_avail = 0; + complete(&vi->have_data); + vdev->config->reset(vdev); + vi->busy = false; + if (vi->hwrng_register_done) + hwrng_unregister(&vi->hwrng); + vdev->config->del_vqs(vdev); + ida_simple_remove(&rng_index_ida, vi->index); + kfree(vi); +} + +static int virtrng_probe(struct virtio_device *vdev) +{ + return probe_common(vdev); +} + +static void virtrng_remove(struct virtio_device *vdev) +{ + remove_common(vdev); +} + +static void virtrng_scan(struct virtio_device *vdev) +{ + struct virtrng_info *vi = vdev->priv; + int err; + + err = hwrng_register(&vi->hwrng); + if (!err) + vi->hwrng_register_done = true; +} + +#ifdef CONFIG_PM_SLEEP +static int virtrng_freeze(struct virtio_device *vdev) +{ + remove_common(vdev); + return 0; +} + +static int virtrng_restore(struct virtio_device *vdev) +{ + int err; + + err = probe_common(vdev); + if (!err) { + struct virtrng_info *vi = vdev->priv; + + /* + * Set hwrng_removed to ensure that virtio_read() + * does not block waiting for data before the + * registration is complete. + */ + vi->hwrng_removed = true; + err = hwrng_register(&vi->hwrng); + if (!err) { + vi->hwrng_register_done = true; + vi->hwrng_removed = false; + } + } + + return err; +} +#endif + +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_RNG, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_rng_driver = { + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .probe = virtrng_probe, + .remove = virtrng_remove, + .scan = virtrng_scan, +#ifdef CONFIG_PM_SLEEP + .freeze = virtrng_freeze, + .restore = virtrng_restore, +#endif +}; + +module_virtio_driver(virtio_rng_driver); +MODULE_DEVICE_TABLE(virtio, id_table); +MODULE_DESCRIPTION("Virtio random number driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/xgene-rng.c b/drivers/char/hw_random/xgene-rng.c new file mode 100644 index 000000000..71755790c --- /dev/null +++ b/drivers/char/hw_random/xgene-rng.c @@ -0,0 +1,432 @@ +/* + * APM X-Gene SoC RNG Driver + * + * Copyright (c) 2014, Applied Micro Circuits Corporation + * Author: Rameshwar Prasad Sahu <rsahu@apm.com> + * Shamal Winchurkar <swinchurkar@apm.com> + * Feng Kan <fkan@apm.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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/hw_random.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/timer.h> + +#define RNG_MAX_DATUM 4 +#define MAX_TRY 100 +#define XGENE_RNG_RETRY_COUNT 20 +#define XGENE_RNG_RETRY_INTERVAL 10 + +/* RNG Registers */ +#define RNG_INOUT_0 0x00 +#define RNG_INTR_STS_ACK 0x10 +#define RNG_CONTROL 0x14 +#define RNG_CONFIG 0x18 +#define RNG_ALARMCNT 0x1c +#define RNG_FROENABLE 0x20 +#define RNG_FRODETUNE 0x24 +#define RNG_ALARMMASK 0x28 +#define RNG_ALARMSTOP 0x2c +#define RNG_OPTIONS 0x78 +#define RNG_EIP_REV 0x7c + +#define MONOBIT_FAIL_MASK BIT(7) +#define POKER_FAIL_MASK BIT(6) +#define LONG_RUN_FAIL_MASK BIT(5) +#define RUN_FAIL_MASK BIT(4) +#define NOISE_FAIL_MASK BIT(3) +#define STUCK_OUT_MASK BIT(2) +#define SHUTDOWN_OFLO_MASK BIT(1) +#define READY_MASK BIT(0) + +#define MAJOR_HW_REV_RD(src) (((src) & 0x0f000000) >> 24) +#define MINOR_HW_REV_RD(src) (((src) & 0x00f00000) >> 20) +#define HW_PATCH_LEVEL_RD(src) (((src) & 0x000f0000) >> 16) +#define MAX_REFILL_CYCLES_SET(dst, src) \ + ((dst & ~0xffff0000) | (((u32)src << 16) & 0xffff0000)) +#define MIN_REFILL_CYCLES_SET(dst, src) \ + ((dst & ~0x000000ff) | (((u32)src) & 0x000000ff)) +#define ALARM_THRESHOLD_SET(dst, src) \ + ((dst & ~0x000000ff) | (((u32)src) & 0x000000ff)) +#define ENABLE_RNG_SET(dst, src) \ + ((dst & ~BIT(10)) | (((u32)src << 10) & BIT(10))) +#define REGSPEC_TEST_MODE_SET(dst, src) \ + ((dst & ~BIT(8)) | (((u32)src << 8) & BIT(8))) +#define MONOBIT_FAIL_MASK_SET(dst, src) \ + ((dst & ~BIT(7)) | (((u32)src << 7) & BIT(7))) +#define POKER_FAIL_MASK_SET(dst, src) \ + ((dst & ~BIT(6)) | (((u32)src << 6) & BIT(6))) +#define LONG_RUN_FAIL_MASK_SET(dst, src) \ + ((dst & ~BIT(5)) | (((u32)src << 5) & BIT(5))) +#define RUN_FAIL_MASK_SET(dst, src) \ + ((dst & ~BIT(4)) | (((u32)src << 4) & BIT(4))) +#define NOISE_FAIL_MASK_SET(dst, src) \ + ((dst & ~BIT(3)) | (((u32)src << 3) & BIT(3))) +#define STUCK_OUT_MASK_SET(dst, src) \ + ((dst & ~BIT(2)) | (((u32)src << 2) & BIT(2))) +#define SHUTDOWN_OFLO_MASK_SET(dst, src) \ + ((dst & ~BIT(1)) | (((u32)src << 1) & BIT(1))) + +struct xgene_rng_dev { + u32 irq; + void __iomem *csr_base; + u32 revision; + u32 datum_size; + u32 failure_cnt; /* Failure count last minute */ + unsigned long failure_ts;/* First failure timestamp */ + struct timer_list failure_timer; + struct device *dev; + struct clk *clk; +}; + +static void xgene_rng_expired_timer(struct timer_list *t) +{ + struct xgene_rng_dev *ctx = from_timer(ctx, t, failure_timer); + + /* Clear failure counter as timer expired */ + disable_irq(ctx->irq); + ctx->failure_cnt = 0; + del_timer(&ctx->failure_timer); + enable_irq(ctx->irq); +} + +static void xgene_rng_start_timer(struct xgene_rng_dev *ctx) +{ + ctx->failure_timer.expires = jiffies + 120 * HZ; + add_timer(&ctx->failure_timer); +} + +/* + * Initialize or reinit free running oscillators (FROs) + */ +static void xgene_rng_init_fro(struct xgene_rng_dev *ctx, u32 fro_val) +{ + writel(fro_val, ctx->csr_base + RNG_FRODETUNE); + writel(0x00000000, ctx->csr_base + RNG_ALARMMASK); + writel(0x00000000, ctx->csr_base + RNG_ALARMSTOP); + writel(0xFFFFFFFF, ctx->csr_base + RNG_FROENABLE); +} + +static void xgene_rng_chk_overflow(struct xgene_rng_dev *ctx) +{ + u32 val; + + val = readl(ctx->csr_base + RNG_INTR_STS_ACK); + if (val & MONOBIT_FAIL_MASK) + /* + * LFSR detected an out-of-bounds number of 1s after + * checking 20,000 bits (test T1 as specified in the + * AIS-31 standard) + */ + dev_err(ctx->dev, "test monobit failure error 0x%08X\n", val); + if (val & POKER_FAIL_MASK) + /* + * LFSR detected an out-of-bounds value in at least one + * of the 16 poker_count_X counters or an out of bounds sum + * of squares value after checking 20,000 bits (test T2 as + * specified in the AIS-31 standard) + */ + dev_err(ctx->dev, "test poker failure error 0x%08X\n", val); + if (val & LONG_RUN_FAIL_MASK) + /* + * LFSR detected a sequence of 34 identical bits + * (test T4 as specified in the AIS-31 standard) + */ + dev_err(ctx->dev, "test long run failure error 0x%08X\n", val); + if (val & RUN_FAIL_MASK) + /* + * LFSR detected an outof-bounds value for at least one + * of the running counters after checking 20,000 bits + * (test T3 as specified in the AIS-31 standard) + */ + dev_err(ctx->dev, "test run failure error 0x%08X\n", val); + if (val & NOISE_FAIL_MASK) + /* LFSR detected a sequence of 48 identical bits */ + dev_err(ctx->dev, "noise failure error 0x%08X\n", val); + if (val & STUCK_OUT_MASK) + /* + * Detected output data registers generated same value twice + * in a row + */ + dev_err(ctx->dev, "stuck out failure error 0x%08X\n", val); + + if (val & SHUTDOWN_OFLO_MASK) { + u32 frostopped; + + /* FROs shut down after a second error event. Try recover. */ + if (++ctx->failure_cnt == 1) { + /* 1st time, just recover */ + ctx->failure_ts = jiffies; + frostopped = readl(ctx->csr_base + RNG_ALARMSTOP); + xgene_rng_init_fro(ctx, frostopped); + + /* + * We must start a timer to clear out this error + * in case the system timer wrap around + */ + xgene_rng_start_timer(ctx); + } else { + /* 2nd time failure in lesser than 1 minute? */ + if (time_after(ctx->failure_ts + 60 * HZ, jiffies)) { + dev_err(ctx->dev, + "FRO shutdown failure error 0x%08X\n", + val); + } else { + /* 2nd time failure after 1 minutes, recover */ + ctx->failure_ts = jiffies; + ctx->failure_cnt = 1; + /* + * We must start a timer to clear out this + * error in case the system timer wrap + * around + */ + xgene_rng_start_timer(ctx); + } + frostopped = readl(ctx->csr_base + RNG_ALARMSTOP); + xgene_rng_init_fro(ctx, frostopped); + } + } + /* Clear them all */ + writel(val, ctx->csr_base + RNG_INTR_STS_ACK); +} + +static irqreturn_t xgene_rng_irq_handler(int irq, void *id) +{ + struct xgene_rng_dev *ctx = (struct xgene_rng_dev *) id; + + /* RNG Alarm Counter overflow */ + xgene_rng_chk_overflow(ctx); + + return IRQ_HANDLED; +} + +static int xgene_rng_data_present(struct hwrng *rng, int wait) +{ + struct xgene_rng_dev *ctx = (struct xgene_rng_dev *) rng->priv; + u32 i, val = 0; + + for (i = 0; i < XGENE_RNG_RETRY_COUNT; i++) { + val = readl(ctx->csr_base + RNG_INTR_STS_ACK); + if ((val & READY_MASK) || !wait) + break; + udelay(XGENE_RNG_RETRY_INTERVAL); + } + + return (val & READY_MASK); +} + +static int xgene_rng_data_read(struct hwrng *rng, u32 *data) +{ + struct xgene_rng_dev *ctx = (struct xgene_rng_dev *) rng->priv; + int i; + + for (i = 0; i < ctx->datum_size; i++) + data[i] = readl(ctx->csr_base + RNG_INOUT_0 + i * 4); + + /* Clear ready bit to start next transaction */ + writel(READY_MASK, ctx->csr_base + RNG_INTR_STS_ACK); + + return ctx->datum_size << 2; +} + +static void xgene_rng_init_internal(struct xgene_rng_dev *ctx) +{ + u32 val; + + writel(0x00000000, ctx->csr_base + RNG_CONTROL); + + val = MAX_REFILL_CYCLES_SET(0, 10); + val = MIN_REFILL_CYCLES_SET(val, 10); + writel(val, ctx->csr_base + RNG_CONFIG); + + val = ALARM_THRESHOLD_SET(0, 0xFF); + writel(val, ctx->csr_base + RNG_ALARMCNT); + + xgene_rng_init_fro(ctx, 0); + + writel(MONOBIT_FAIL_MASK | + POKER_FAIL_MASK | + LONG_RUN_FAIL_MASK | + RUN_FAIL_MASK | + NOISE_FAIL_MASK | + STUCK_OUT_MASK | + SHUTDOWN_OFLO_MASK | + READY_MASK, ctx->csr_base + RNG_INTR_STS_ACK); + + val = ENABLE_RNG_SET(0, 1); + val = MONOBIT_FAIL_MASK_SET(val, 1); + val = POKER_FAIL_MASK_SET(val, 1); + val = LONG_RUN_FAIL_MASK_SET(val, 1); + val = RUN_FAIL_MASK_SET(val, 1); + val = NOISE_FAIL_MASK_SET(val, 1); + val = STUCK_OUT_MASK_SET(val, 1); + val = SHUTDOWN_OFLO_MASK_SET(val, 1); + writel(val, ctx->csr_base + RNG_CONTROL); +} + +static int xgene_rng_init(struct hwrng *rng) +{ + struct xgene_rng_dev *ctx = (struct xgene_rng_dev *) rng->priv; + + ctx->failure_cnt = 0; + timer_setup(&ctx->failure_timer, xgene_rng_expired_timer, 0); + + ctx->revision = readl(ctx->csr_base + RNG_EIP_REV); + + dev_dbg(ctx->dev, "Rev %d.%d.%d\n", + MAJOR_HW_REV_RD(ctx->revision), + MINOR_HW_REV_RD(ctx->revision), + HW_PATCH_LEVEL_RD(ctx->revision)); + + dev_dbg(ctx->dev, "Options 0x%08X", + readl(ctx->csr_base + RNG_OPTIONS)); + + xgene_rng_init_internal(ctx); + + ctx->datum_size = RNG_MAX_DATUM; + + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id xgene_rng_acpi_match[] = { + { "APMC0D18", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, xgene_rng_acpi_match); +#endif + +static struct hwrng xgene_rng_func = { + .name = "xgene-rng", + .init = xgene_rng_init, + .data_present = xgene_rng_data_present, + .data_read = xgene_rng_data_read, +}; + +static int xgene_rng_probe(struct platform_device *pdev) +{ + struct resource *res; + struct xgene_rng_dev *ctx; + int rc = 0; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->dev = &pdev->dev; + platform_set_drvdata(pdev, ctx); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctx->csr_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ctx->csr_base)) + return PTR_ERR(ctx->csr_base); + + rc = platform_get_irq(pdev, 0); + if (rc < 0) { + dev_err(&pdev->dev, "No IRQ resource\n"); + return rc; + } + ctx->irq = rc; + + dev_dbg(&pdev->dev, "APM X-Gene RNG BASE %p ALARM IRQ %d", + ctx->csr_base, ctx->irq); + + rc = devm_request_irq(&pdev->dev, ctx->irq, xgene_rng_irq_handler, 0, + dev_name(&pdev->dev), ctx); + if (rc) { + dev_err(&pdev->dev, "Could not request RNG alarm IRQ\n"); + return rc; + } + + /* Enable IP clock */ + ctx->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ctx->clk)) { + dev_warn(&pdev->dev, "Couldn't get the clock for RNG\n"); + } else { + rc = clk_prepare_enable(ctx->clk); + if (rc) { + dev_warn(&pdev->dev, + "clock prepare enable failed for RNG"); + return rc; + } + } + + xgene_rng_func.priv = (unsigned long) ctx; + + rc = hwrng_register(&xgene_rng_func); + if (rc) { + dev_err(&pdev->dev, "RNG registering failed error %d\n", rc); + if (!IS_ERR(ctx->clk)) + clk_disable_unprepare(ctx->clk); + return rc; + } + + rc = device_init_wakeup(&pdev->dev, 1); + if (rc) { + dev_err(&pdev->dev, "RNG device_init_wakeup failed error %d\n", + rc); + if (!IS_ERR(ctx->clk)) + clk_disable_unprepare(ctx->clk); + hwrng_unregister(&xgene_rng_func); + return rc; + } + + return 0; +} + +static int xgene_rng_remove(struct platform_device *pdev) +{ + struct xgene_rng_dev *ctx = platform_get_drvdata(pdev); + int rc; + + rc = device_init_wakeup(&pdev->dev, 0); + if (rc) + dev_err(&pdev->dev, "RNG init wakeup failed error %d\n", rc); + if (!IS_ERR(ctx->clk)) + clk_disable_unprepare(ctx->clk); + hwrng_unregister(&xgene_rng_func); + + return rc; +} + +static const struct of_device_id xgene_rng_of_match[] = { + { .compatible = "apm,xgene-rng" }, + { } +}; + +MODULE_DEVICE_TABLE(of, xgene_rng_of_match); + +static struct platform_driver xgene_rng_driver = { + .probe = xgene_rng_probe, + .remove = xgene_rng_remove, + .driver = { + .name = "xgene-rng", + .of_match_table = xgene_rng_of_match, + .acpi_match_table = ACPI_PTR(xgene_rng_acpi_match), + }, +}; + +module_platform_driver(xgene_rng_driver); +MODULE_DESCRIPTION("APM X-Gene RNG driver"); +MODULE_LICENSE("GPL"); |