mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
402
drivers/char/hw_random/Kconfig
Normal file
402
drivers/char/hw_random/Kconfig
Normal file
|
@ -0,0 +1,402 @@
|
|||
#
|
||||
# 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/hw_random, and which exposes one
|
||||
of possibly several hardware random number generators.
|
||||
|
||||
These hardware random number generators do not feed directly
|
||||
into the kernel's random number generator. That is usually
|
||||
handled by the "rngd" daemon. Documentation/hw_random.txt
|
||||
has more information.
|
||||
|
||||
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
|
||||
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_BCM63XX
|
||||
tristate "Broadcom BCM63xx Random Number Generator support"
|
||||
depends on BCM63XX
|
||||
default HW_RANDOM
|
||||
---help---
|
||||
This driver provides kernel-side support for the Random Number
|
||||
Generator hardware found on the Broadcom BCM63xx SoCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called bcm63xx-rng
|
||||
|
||||
If unusure, say Y.
|
||||
|
||||
config HW_RANDOM_BCM2835
|
||||
tristate "Broadcom BCM2835 Random Number Generator support"
|
||||
depends on ARCH_BCM2835
|
||||
default HW_RANDOM
|
||||
---help---
|
||||
This driver provides kernel-side support for the Random Number
|
||||
Generator hardware found on the Broadcom BCM2835 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_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
|
||||
default HW_RANDOM
|
||||
---help---
|
||||
This driver provides kernel-side support for the Random Number
|
||||
Generator hardware found on OMAP16xx, OMAP2/3/4/5 and AM33xx/AM43xx
|
||||
multimedia processors.
|
||||
|
||||
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 ARCH_HAS_RNGA
|
||||
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_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_PPC4XX
|
||||
tristate "PowerPC 4xx generic true random number generator support"
|
||||
depends on PPC && 4xx
|
||||
default HW_RANDOM
|
||||
---help---
|
||||
This driver provides the kernel-side support for the TRNG hardware
|
||||
found in the security function of some PowerPC 4xx SoCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ppc4xx-rng.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
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_EXYNOS
|
||||
tristate "EXYNOS HW random number generator support"
|
||||
depends on ARCH_EXYNOS
|
||||
default HW_RANDOM
|
||||
---help---
|
||||
This driver provides kernel-side support for the Random Number
|
||||
Generator hardware found on EXYNOS SOCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called exynos-rng.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config HW_RANDOM_EXYNOS_SWD
|
||||
tristate "EXYNOS SWD HW random number generator support"
|
||||
depends on ARCH_EXYNOS
|
||||
default HW_RANDOM
|
||||
---help---
|
||||
This driver provides kernel-side support for TRNG hardware
|
||||
found in secure world by using smc call
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called exynos-rng.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config EXYRNG_FIPS_COMPLIANCE
|
||||
bool "Compliant with FIPS 140-2"
|
||||
depends on HW_RANDOM_EXYNOS_SWD
|
||||
|
||||
choice
|
||||
prompt "Fail Policy"
|
||||
depends on EXYRNG_FIPS_COMPLIANCE
|
||||
default EXYRNG_FAIL_POLICY_DISABLE
|
||||
|
||||
config EXYRNG_FAIL_POLICY_DISABLE
|
||||
bool "Disable RNG if the tests fail"
|
||||
depends on EXYRNG_FIPS_COMPLIANCE
|
||||
|
||||
config EXYRNG_FAIL_POLICY_RESET
|
||||
bool "Reset if the tests fail"
|
||||
depends on EXYRNG_FIPS_COMPLIANCE
|
||||
|
||||
endchoice
|
||||
|
||||
config EXYRNG_USE_CRYPTOMANAGER
|
||||
tristate "Use CryptoManager"
|
||||
depends on HW_RANDOM_EXYNOS_SWD
|
||||
default n
|
||||
|
||||
config HW_RANDOM_TPM
|
||||
tristate "TPM HW Random Number Generator support"
|
||||
depends on TCG_TPM
|
||||
default HW_RANDOM
|
||||
---help---
|
||||
This driver provides kernel-side support for the Random Number
|
||||
Generator in the Trusted Platform Module
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called tpm-rng.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config HW_RANDOM_MSM
|
||||
tristate "Qualcomm SoCs Random Number Generator support"
|
||||
depends on HW_RANDOM && ARCH_QCOM
|
||||
default HW_RANDOM
|
||||
---help---
|
||||
This driver provides kernel-side support for the Random Number
|
||||
Generator hardware found on Qualcomm SoCs.
|
||||
|
||||
To compile this driver as a module, choose M here. the
|
||||
module will be called msm-rng.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config HW_RANDOM_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.
|
||||
|
||||
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.
|
||||
|
33
drivers/char/hw_random/Makefile
Normal file
33
drivers/char/hw_random/Makefile
Normal file
|
@ -0,0 +1,33 @@
|
|||
#
|
||||
# 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_EXYNOS_SWD) += exyswd-rng.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_BCM63XX) += bcm63xx-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_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_OCTEON) += octeon-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_PPC4XX) += ppc4xx-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_POWERNV) += powernv-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_EXYNOS) += exynos-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_TPM) += tpm-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_BCM2835) += bcm2835-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_MSM) += msm-rng.o
|
||||
obj-$(CONFIG_HW_RANDOM_XGENE) += xgene-rng.o
|
169
drivers/char/hw_random/amd-rng.c
Normal file
169
drivers/char/hw_random/amd-rng.c
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/hw_random.h>
|
||||
#include <linux/delay.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
|
||||
#define PFX KBUILD_MODNAME ": "
|
||||
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
static struct pci_dev *amd_pdev;
|
||||
|
||||
|
||||
static int amd_rng_data_present(struct hwrng *rng, int wait)
|
||||
{
|
||||
u32 pmbase = (u32)rng->priv;
|
||||
int data, i;
|
||||
|
||||
for (i = 0; i < 20; i++) {
|
||||
data = !!(inl(pmbase + 0xF4) & 1);
|
||||
if (data || !wait)
|
||||
break;
|
||||
udelay(10);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static int amd_rng_data_read(struct hwrng *rng, u32 *data)
|
||||
{
|
||||
u32 pmbase = (u32)rng->priv;
|
||||
|
||||
*data = inl(pmbase + 0xF0);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
static int amd_rng_init(struct hwrng *rng)
|
||||
{
|
||||
u8 rnen;
|
||||
|
||||
pci_read_config_byte(amd_pdev, 0x40, &rnen);
|
||||
rnen |= (1 << 7); /* RNG on */
|
||||
pci_write_config_byte(amd_pdev, 0x40, rnen);
|
||||
|
||||
pci_read_config_byte(amd_pdev, 0x41, &rnen);
|
||||
rnen |= (1 << 7); /* PMIO enable */
|
||||
pci_write_config_byte(amd_pdev, 0x41, rnen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_rng_cleanup(struct hwrng *rng)
|
||||
{
|
||||
u8 rnen;
|
||||
|
||||
pci_read_config_byte(amd_pdev, 0x40, &rnen);
|
||||
rnen &= ~(1 << 7); /* RNG off */
|
||||
pci_write_config_byte(amd_pdev, 0x40, rnen);
|
||||
}
|
||||
|
||||
|
||||
static struct hwrng amd_rng = {
|
||||
.name = "amd",
|
||||
.init = amd_rng_init,
|
||||
.cleanup = amd_rng_cleanup,
|
||||
.data_present = amd_rng_data_present,
|
||||
.data_read = amd_rng_data_read,
|
||||
};
|
||||
|
||||
|
||||
static int __init mod_init(void)
|
||||
{
|
||||
int err = -ENODEV;
|
||||
struct pci_dev *pdev = NULL;
|
||||
const struct pci_device_id *ent;
|
||||
u32 pmbase;
|
||||
|
||||
for_each_pci_dev(pdev) {
|
||||
ent = pci_match_id(pci_tbl, pdev);
|
||||
if (ent)
|
||||
goto found;
|
||||
}
|
||||
/* Device not found. */
|
||||
goto out;
|
||||
|
||||
found:
|
||||
err = pci_read_config_dword(pdev, 0x58, &pmbase);
|
||||
if (err)
|
||||
goto out;
|
||||
err = -EIO;
|
||||
pmbase &= 0x0000FF00;
|
||||
if (pmbase == 0)
|
||||
goto out;
|
||||
if (!request_region(pmbase + 0xF0, 8, "AMD HWRNG")) {
|
||||
dev_err(&pdev->dev, "AMD HWRNG region 0x%x already in use!\n",
|
||||
pmbase + 0xF0);
|
||||
err = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
amd_rng.priv = (unsigned long)pmbase;
|
||||
amd_pdev = pdev;
|
||||
|
||||
pr_info("AMD768 RNG detected\n");
|
||||
err = hwrng_register(&amd_rng);
|
||||
if (err) {
|
||||
pr_err(PFX "RNG registering failed (%d)\n",
|
||||
err);
|
||||
release_region(pmbase + 0xF0, 8);
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit mod_exit(void)
|
||||
{
|
||||
u32 pmbase = (unsigned long)amd_rng.priv;
|
||||
release_region(pmbase + 0xF0, 8);
|
||||
hwrng_unregister(&amd_rng);
|
||||
}
|
||||
|
||||
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");
|
142
drivers/char/hw_random/atmel-rng.c
Normal file
142
drivers/char/hw_random/atmel-rng.c
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* 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/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 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_enable(trng->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
writel(TRNG_KEY | 1, trng->base + TRNG_CR);
|
||||
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(trng->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_trng_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct atmel_trng *trng = platform_get_drvdata(pdev);
|
||||
|
||||
hwrng_unregister(&trng->rng);
|
||||
|
||||
writel(TRNG_KEY, trng->base + TRNG_CR);
|
||||
clk_disable(trng->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int atmel_trng_suspend(struct device *dev)
|
||||
{
|
||||
struct atmel_trng *trng = dev_get_drvdata(dev);
|
||||
|
||||
clk_disable(trng->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_trng_resume(struct device *dev)
|
||||
{
|
||||
struct atmel_trng *trng = dev_get_drvdata(dev);
|
||||
|
||||
return clk_enable(trng->clk);
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops atmel_trng_pm_ops = {
|
||||
.suspend = atmel_trng_suspend,
|
||||
.resume = atmel_trng_resume,
|
||||
};
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static struct platform_driver atmel_trng_driver = {
|
||||
.probe = atmel_trng_probe,
|
||||
.remove = atmel_trng_remove,
|
||||
.driver = {
|
||||
.name = "atmel-trng",
|
||||
.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_PM
|
||||
.pm = &atmel_trng_pm_ops,
|
||||
#endif /* CONFIG_PM */
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(atmel_trng_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>");
|
||||
MODULE_DESCRIPTION("Atmel true random number generator driver");
|
112
drivers/char/hw_random/bcm2835-rng.c
Normal file
112
drivers/char/hw_random/bcm2835-rng.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* 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>
|
||||
|
||||
#define RNG_CTRL 0x0
|
||||
#define RNG_STATUS 0x4
|
||||
#define RNG_DATA 0x8
|
||||
|
||||
/* enable rng */
|
||||
#define RNG_RBGEN 0x1
|
||||
|
||||
/* the initial numbers generated are "less random" so will be discarded */
|
||||
#define RNG_WARMUP_COUNT 0x40000
|
||||
|
||||
static int bcm2835_rng_read(struct hwrng *rng, void *buf, size_t max,
|
||||
bool wait)
|
||||
{
|
||||
void __iomem *rng_base = (void __iomem *)rng->priv;
|
||||
|
||||
while ((__raw_readl(rng_base + RNG_STATUS) >> 24) == 0) {
|
||||
if (!wait)
|
||||
return 0;
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
*(u32 *)buf = __raw_readl(rng_base + RNG_DATA);
|
||||
return sizeof(u32);
|
||||
}
|
||||
|
||||
static struct hwrng bcm2835_rng_ops = {
|
||||
.name = "bcm2835",
|
||||
.read = bcm2835_rng_read,
|
||||
};
|
||||
|
||||
static int bcm2835_rng_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
void __iomem *rng_base;
|
||||
int err;
|
||||
|
||||
/* map peripheral */
|
||||
rng_base = of_iomap(np, 0);
|
||||
if (!rng_base) {
|
||||
dev_err(dev, "failed to remap rng regs");
|
||||
return -ENODEV;
|
||||
}
|
||||
bcm2835_rng_ops.priv = (unsigned long)rng_base;
|
||||
|
||||
/* set warm-up count & enable */
|
||||
__raw_writel(RNG_WARMUP_COUNT, rng_base + RNG_STATUS);
|
||||
__raw_writel(RNG_RBGEN, rng_base + RNG_CTRL);
|
||||
|
||||
/* register driver */
|
||||
err = hwrng_register(&bcm2835_rng_ops);
|
||||
if (err) {
|
||||
dev_err(dev, "hwrng registration failed\n");
|
||||
iounmap(rng_base);
|
||||
} else
|
||||
dev_info(dev, "hwrng registered\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm2835_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
void __iomem *rng_base = (void __iomem *)bcm2835_rng_ops.priv;
|
||||
|
||||
/* disable rng hardware */
|
||||
__raw_writel(0, rng_base + RNG_CTRL);
|
||||
|
||||
/* unregister driver */
|
||||
hwrng_unregister(&bcm2835_rng_ops);
|
||||
iounmap(rng_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id bcm2835_rng_of_match[] = {
|
||||
{ .compatible = "brcm,bcm2835-rng", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, bcm2835_rng_of_match);
|
||||
|
||||
static struct platform_driver bcm2835_rng_driver = {
|
||||
.driver = {
|
||||
.name = "bcm2835-rng",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = bcm2835_rng_of_match,
|
||||
},
|
||||
.probe = bcm2835_rng_probe,
|
||||
.remove = bcm2835_rng_remove,
|
||||
};
|
||||
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");
|
173
drivers/char/hw_random/bcm63xx-rng.c
Normal file
173
drivers/char/hw_random/bcm63xx-rng.c
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Broadcom BCM63xx Random Number Generator support
|
||||
*
|
||||
* Copyright (C) 2011, Florian Fainelli <florian@openwrt.org>
|
||||
* Copyright (C) 2009, Broadcom Corporation
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/hw_random.h>
|
||||
|
||||
#include <bcm63xx_io.h>
|
||||
#include <bcm63xx_regs.h>
|
||||
|
||||
struct bcm63xx_rng_priv {
|
||||
struct clk *clk;
|
||||
void __iomem *regs;
|
||||
};
|
||||
|
||||
#define to_rng_priv(rng) ((struct bcm63xx_rng_priv *)rng->priv)
|
||||
|
||||
static int bcm63xx_rng_init(struct hwrng *rng)
|
||||
{
|
||||
struct bcm63xx_rng_priv *priv = to_rng_priv(rng);
|
||||
u32 val;
|
||||
|
||||
val = bcm_readl(priv->regs + RNG_CTRL);
|
||||
val |= RNG_EN;
|
||||
bcm_writel(val, priv->regs + RNG_CTRL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bcm63xx_rng_cleanup(struct hwrng *rng)
|
||||
{
|
||||
struct bcm63xx_rng_priv *priv = to_rng_priv(rng);
|
||||
u32 val;
|
||||
|
||||
val = bcm_readl(priv->regs + RNG_CTRL);
|
||||
val &= ~RNG_EN;
|
||||
bcm_writel(val, priv->regs + RNG_CTRL);
|
||||
}
|
||||
|
||||
static int bcm63xx_rng_data_present(struct hwrng *rng, int wait)
|
||||
{
|
||||
struct bcm63xx_rng_priv *priv = to_rng_priv(rng);
|
||||
|
||||
return bcm_readl(priv->regs + RNG_STAT) & RNG_AVAIL_MASK;
|
||||
}
|
||||
|
||||
static int bcm63xx_rng_data_read(struct hwrng *rng, u32 *data)
|
||||
{
|
||||
struct bcm63xx_rng_priv *priv = to_rng_priv(rng);
|
||||
|
||||
*data = bcm_readl(priv->regs + RNG_DATA);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
static int bcm63xx_rng_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *r;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
struct bcm63xx_rng_priv *priv;
|
||||
struct hwrng *rng;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r) {
|
||||
dev_err(&pdev->dev, "no iomem resource\n");
|
||||
ret = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
dev_err(&pdev->dev, "no memory for private structure\n");
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rng = kzalloc(sizeof(*rng), GFP_KERNEL);
|
||||
if (!rng) {
|
||||
dev_err(&pdev->dev, "no memory for rng structure\n");
|
||||
ret = -ENOMEM;
|
||||
goto out_free_priv;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, rng);
|
||||
rng->priv = (unsigned long)priv;
|
||||
rng->name = pdev->name;
|
||||
rng->init = bcm63xx_rng_init;
|
||||
rng->cleanup = bcm63xx_rng_cleanup;
|
||||
rng->data_present = bcm63xx_rng_data_present;
|
||||
rng->data_read = bcm63xx_rng_data_read;
|
||||
|
||||
clk = clk_get(&pdev->dev, "ipsec");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "no clock for device\n");
|
||||
ret = PTR_ERR(clk);
|
||||
goto out_free_rng;
|
||||
}
|
||||
|
||||
priv->clk = clk;
|
||||
|
||||
if (!devm_request_mem_region(&pdev->dev, r->start,
|
||||
resource_size(r), pdev->name)) {
|
||||
dev_err(&pdev->dev, "request mem failed");
|
||||
ret = -ENOMEM;
|
||||
goto out_free_rng;
|
||||
}
|
||||
|
||||
priv->regs = devm_ioremap_nocache(&pdev->dev, r->start,
|
||||
resource_size(r));
|
||||
if (!priv->regs) {
|
||||
dev_err(&pdev->dev, "ioremap failed");
|
||||
ret = -ENOMEM;
|
||||
goto out_free_rng;
|
||||
}
|
||||
|
||||
clk_enable(clk);
|
||||
|
||||
ret = hwrng_register(rng);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register rng device\n");
|
||||
goto out_clk_disable;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "registered RNG driver\n");
|
||||
|
||||
return 0;
|
||||
|
||||
out_clk_disable:
|
||||
clk_disable(clk);
|
||||
out_free_rng:
|
||||
kfree(rng);
|
||||
out_free_priv:
|
||||
kfree(priv);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bcm63xx_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hwrng *rng = platform_get_drvdata(pdev);
|
||||
struct bcm63xx_rng_priv *priv = to_rng_priv(rng);
|
||||
|
||||
hwrng_unregister(rng);
|
||||
clk_disable(priv->clk);
|
||||
kfree(priv);
|
||||
kfree(rng);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver bcm63xx_rng_driver = {
|
||||
.probe = bcm63xx_rng_probe,
|
||||
.remove = bcm63xx_rng_remove,
|
||||
.driver = {
|
||||
.name = "bcm63xx-rng",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(bcm63xx_rng_driver);
|
||||
|
||||
MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
|
||||
MODULE_DESCRIPTION("Broadcom BCM63xx RNG driver");
|
||||
MODULE_LICENSE("GPL");
|
501
drivers/char/hw_random/core.c
Normal file
501
drivers/char/hw_random/core.c
Normal file
|
@ -0,0 +1,501 @@
|
|||
/*
|
||||
Added support for the AMD Geode LX RNG
|
||||
(c) Copyright 2004-2005 Advanced Micro Devices, Inc.
|
||||
|
||||
derived 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 <alan@redhat.com>
|
||||
|
||||
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>
|
||||
|
||||
Added generic RNG 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/device.h>
|
||||
#include <linux/hw_random.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
|
||||
#define RNG_MODULE_NAME "hw_random"
|
||||
#define PFX RNG_MODULE_NAME ": "
|
||||
#define RNG_MISCDEV_MINOR 183 /* official */
|
||||
|
||||
|
||||
static struct hwrng *current_rng;
|
||||
static struct task_struct *hwrng_fill;
|
||||
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 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)
|
||||
{
|
||||
unsigned char bytes[16];
|
||||
int bytes_read;
|
||||
|
||||
mutex_lock(&reading_mutex);
|
||||
bytes_read = rng_get_data(rng, bytes, sizeof(bytes), 1);
|
||||
mutex_unlock(&reading_mutex);
|
||||
if (bytes_read > 0)
|
||||
add_device_randomness(bytes, bytes_read);
|
||||
}
|
||||
|
||||
static inline int hwrng_init(struct hwrng *rng)
|
||||
{
|
||||
if (rng->init) {
|
||||
int ret;
|
||||
|
||||
ret = rng->init(rng);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
add_early_randomness(rng);
|
||||
|
||||
current_quality = rng->quality ? : default_quality;
|
||||
current_quality &= 1023;
|
||||
|
||||
if (current_quality == 0 && hwrng_fill)
|
||||
kthread_stop(hwrng_fill);
|
||||
if (current_quality > 0 && !hwrng_fill)
|
||||
start_khwrngd();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void hwrng_cleanup(struct hwrng *rng)
|
||||
{
|
||||
if (rng && rng->cleanup)
|
||||
rng->cleanup(rng);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
while (size) {
|
||||
if (mutex_lock_interruptible(&rng_mutex)) {
|
||||
err = -ERESTARTSYS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!current_rng) {
|
||||
err = -ENODEV;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
mutex_lock(&reading_mutex);
|
||||
if (!data_avail) {
|
||||
bytes_read = rng_get_data(current_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(&rng_mutex);
|
||||
mutex_unlock(&reading_mutex);
|
||||
|
||||
if (need_resched())
|
||||
schedule_timeout_interruptible(1);
|
||||
|
||||
if (signal_pending(current)) {
|
||||
err = -ERESTARTSYS;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
out:
|
||||
return ret ? : err;
|
||||
out_unlock:
|
||||
mutex_unlock(&rng_mutex);
|
||||
goto out;
|
||||
out_unlock_reading:
|
||||
mutex_unlock(&reading_mutex);
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
|
||||
static const struct file_operations rng_chrdev_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rng_dev_open,
|
||||
.read = rng_dev_read,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
static struct miscdevice rng_miscdev = {
|
||||
.minor = RNG_MISCDEV_MINOR,
|
||||
.name = RNG_MODULE_NAME,
|
||||
.nodename = "hwrng",
|
||||
.fops = &rng_chrdev_ops,
|
||||
};
|
||||
|
||||
|
||||
static ssize_t hwrng_attr_current_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
int err;
|
||||
struct hwrng *rng;
|
||||
|
||||
err = mutex_lock_interruptible(&rng_mutex);
|
||||
if (err)
|
||||
return -ERESTARTSYS;
|
||||
err = -ENODEV;
|
||||
list_for_each_entry(rng, &rng_list, list) {
|
||||
if (strcmp(rng->name, buf) == 0) {
|
||||
if (rng == current_rng) {
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
err = hwrng_init(rng);
|
||||
if (err)
|
||||
break;
|
||||
hwrng_cleanup(current_rng);
|
||||
current_rng = rng;
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&rng_mutex);
|
||||
|
||||
return err ? : len;
|
||||
}
|
||||
|
||||
static ssize_t hwrng_attr_current_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int err;
|
||||
ssize_t ret;
|
||||
const char *name = "none";
|
||||
|
||||
err = mutex_lock_interruptible(&rng_mutex);
|
||||
if (err)
|
||||
return -ERESTARTSYS;
|
||||
if (current_rng)
|
||||
name = current_rng->name;
|
||||
ret = snprintf(buf, PAGE_SIZE, "%s\n", name);
|
||||
mutex_unlock(&rng_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t hwrng_attr_available_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int err;
|
||||
ssize_t ret = 0;
|
||||
struct hwrng *rng;
|
||||
|
||||
err = mutex_lock_interruptible(&rng_mutex);
|
||||
if (err)
|
||||
return -ERESTARTSYS;
|
||||
buf[0] = '\0';
|
||||
list_for_each_entry(rng, &rng_list, list) {
|
||||
strncat(buf, rng->name, PAGE_SIZE - ret - 1);
|
||||
ret += strlen(rng->name);
|
||||
strncat(buf, " ", PAGE_SIZE - ret - 1);
|
||||
ret++;
|
||||
}
|
||||
strncat(buf, "\n", PAGE_SIZE - ret - 1);
|
||||
ret++;
|
||||
mutex_unlock(&rng_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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 void unregister_miscdev(void)
|
||||
{
|
||||
device_remove_file(rng_miscdev.this_device, &dev_attr_rng_available);
|
||||
device_remove_file(rng_miscdev.this_device, &dev_attr_rng_current);
|
||||
misc_deregister(&rng_miscdev);
|
||||
}
|
||||
|
||||
static int register_miscdev(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = misc_register(&rng_miscdev);
|
||||
if (err)
|
||||
goto out;
|
||||
err = device_create_file(rng_miscdev.this_device,
|
||||
&dev_attr_rng_current);
|
||||
if (err)
|
||||
goto err_misc_dereg;
|
||||
err = device_create_file(rng_miscdev.this_device,
|
||||
&dev_attr_rng_available);
|
||||
if (err)
|
||||
goto err_remove_current;
|
||||
out:
|
||||
return err;
|
||||
|
||||
err_remove_current:
|
||||
device_remove_file(rng_miscdev.this_device, &dev_attr_rng_current);
|
||||
err_misc_dereg:
|
||||
misc_deregister(&rng_miscdev);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int hwrng_fillfn(void *unused)
|
||||
{
|
||||
long rc;
|
||||
|
||||
set_freezable();
|
||||
|
||||
while (!kthread_should_stop()) {
|
||||
if (!current_rng)
|
||||
break;
|
||||
mutex_lock(&reading_mutex);
|
||||
rc = rng_get_data(current_rng, rng_fillbuf,
|
||||
rng_buffer_size(), 1);
|
||||
mutex_unlock(&reading_mutex);
|
||||
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 (hwrng_fill == ERR_PTR(-ENOMEM)) {
|
||||
pr_err("hwrng_fill thread creation failed");
|
||||
hwrng_fill = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int hwrng_register(struct hwrng *rng)
|
||||
{
|
||||
int err = -EINVAL;
|
||||
struct hwrng *old_rng, *tmp;
|
||||
|
||||
if (rng->name == NULL ||
|
||||
(rng->data_read == NULL && rng->read == NULL))
|
||||
goto out;
|
||||
|
||||
mutex_lock(&rng_mutex);
|
||||
|
||||
/* kmalloc makes this safe for virt_to_page() in virtio_rng.c */
|
||||
err = -ENOMEM;
|
||||
if (!rng_buffer) {
|
||||
rng_buffer = kmalloc(rng_buffer_size(), GFP_KERNEL);
|
||||
if (!rng_buffer)
|
||||
goto out_unlock;
|
||||
}
|
||||
if (!rng_fillbuf) {
|
||||
rng_fillbuf = kmalloc(rng_buffer_size(), GFP_KERNEL);
|
||||
if (!rng_fillbuf) {
|
||||
kfree(rng_buffer);
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
old_rng = current_rng;
|
||||
if (!old_rng) {
|
||||
err = hwrng_init(rng);
|
||||
if (err)
|
||||
goto out_unlock;
|
||||
current_rng = rng;
|
||||
}
|
||||
err = 0;
|
||||
if (!old_rng) {
|
||||
err = register_miscdev();
|
||||
if (err) {
|
||||
hwrng_cleanup(rng);
|
||||
current_rng = NULL;
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
INIT_LIST_HEAD(&rng->list);
|
||||
list_add_tail(&rng->list, &rng_list);
|
||||
|
||||
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) {
|
||||
hwrng_cleanup(rng);
|
||||
if (list_empty(&rng_list)) {
|
||||
current_rng = NULL;
|
||||
} else {
|
||||
current_rng = list_entry(rng_list.prev, struct hwrng, list);
|
||||
err = hwrng_init(current_rng);
|
||||
if (err)
|
||||
current_rng = NULL;
|
||||
}
|
||||
}
|
||||
if (list_empty(&rng_list)) {
|
||||
unregister_miscdev();
|
||||
if (hwrng_fill)
|
||||
kthread_stop(hwrng_fill);
|
||||
}
|
||||
|
||||
mutex_unlock(&rng_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hwrng_unregister);
|
||||
|
||||
static void __exit hwrng_exit(void)
|
||||
{
|
||||
mutex_lock(&rng_mutex);
|
||||
BUG_ON(current_rng);
|
||||
kfree(rng_buffer);
|
||||
kfree(rng_fillbuf);
|
||||
mutex_unlock(&rng_mutex);
|
||||
}
|
||||
|
||||
module_exit(hwrng_exit);
|
||||
|
||||
MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver");
|
||||
MODULE_LICENSE("GPL");
|
183
drivers/char/hw_random/exynos-rng.c
Normal file
183
drivers/char/hw_random/exynos-rng.c
Normal file
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* exynos-rng.c - Random Number Generator driver for the exynos
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electronics
|
||||
* Jonghwa Lee <jonghwa3.lee@smasung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation;
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/hw_random.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#define EXYNOS_PRNG_STATUS_OFFSET 0x10
|
||||
#define EXYNOS_PRNG_SEED_OFFSET 0x140
|
||||
#define EXYNOS_PRNG_OUT1_OFFSET 0x160
|
||||
#define SEED_SETTING_DONE BIT(1)
|
||||
#define PRNG_START 0x18
|
||||
#define PRNG_DONE BIT(5)
|
||||
#define EXYNOS_AUTOSUSPEND_DELAY 100
|
||||
|
||||
struct exynos_rng {
|
||||
struct device *dev;
|
||||
struct hwrng rng;
|
||||
void __iomem *mem;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static u32 exynos_rng_readl(struct exynos_rng *rng, u32 offset)
|
||||
{
|
||||
return __raw_readl(rng->mem + offset);
|
||||
}
|
||||
|
||||
static void exynos_rng_writel(struct exynos_rng *rng, u32 val, u32 offset)
|
||||
{
|
||||
__raw_writel(val, rng->mem + offset);
|
||||
}
|
||||
|
||||
static int exynos_init(struct hwrng *rng)
|
||||
{
|
||||
struct exynos_rng *exynos_rng = container_of(rng,
|
||||
struct exynos_rng, rng);
|
||||
int i;
|
||||
int ret = 0;
|
||||
|
||||
pm_runtime_get_sync(exynos_rng->dev);
|
||||
|
||||
for (i = 0 ; i < 5 ; i++)
|
||||
exynos_rng_writel(exynos_rng, jiffies,
|
||||
EXYNOS_PRNG_SEED_OFFSET + 4*i);
|
||||
|
||||
if (!(exynos_rng_readl(exynos_rng, EXYNOS_PRNG_STATUS_OFFSET)
|
||||
& SEED_SETTING_DONE))
|
||||
ret = -EIO;
|
||||
|
||||
pm_runtime_put_noidle(exynos_rng->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos_read(struct hwrng *rng, void *buf,
|
||||
size_t max, bool wait)
|
||||
{
|
||||
struct exynos_rng *exynos_rng = container_of(rng,
|
||||
struct exynos_rng, rng);
|
||||
u32 *data = buf;
|
||||
|
||||
pm_runtime_get_sync(exynos_rng->dev);
|
||||
|
||||
exynos_rng_writel(exynos_rng, PRNG_START, 0);
|
||||
|
||||
while (!(exynos_rng_readl(exynos_rng,
|
||||
EXYNOS_PRNG_STATUS_OFFSET) & PRNG_DONE))
|
||||
cpu_relax();
|
||||
|
||||
exynos_rng_writel(exynos_rng, PRNG_DONE, EXYNOS_PRNG_STATUS_OFFSET);
|
||||
|
||||
*data = exynos_rng_readl(exynos_rng, EXYNOS_PRNG_OUT1_OFFSET);
|
||||
|
||||
pm_runtime_mark_last_busy(exynos_rng->dev);
|
||||
pm_runtime_autosuspend(exynos_rng->dev);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
static int exynos_rng_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_rng *exynos_rng;
|
||||
struct resource *res;
|
||||
|
||||
exynos_rng = devm_kzalloc(&pdev->dev, sizeof(struct exynos_rng),
|
||||
GFP_KERNEL);
|
||||
if (!exynos_rng)
|
||||
return -ENOMEM;
|
||||
|
||||
exynos_rng->dev = &pdev->dev;
|
||||
exynos_rng->rng.name = "exynos";
|
||||
exynos_rng->rng.init = exynos_init;
|
||||
exynos_rng->rng.read = exynos_read;
|
||||
exynos_rng->clk = devm_clk_get(&pdev->dev, "secss");
|
||||
if (IS_ERR(exynos_rng->clk)) {
|
||||
dev_err(&pdev->dev, "Couldn't get clock.\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
exynos_rng->mem = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(exynos_rng->mem))
|
||||
return PTR_ERR(exynos_rng->mem);
|
||||
|
||||
platform_set_drvdata(pdev, exynos_rng);
|
||||
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, EXYNOS_AUTOSUSPEND_DELAY);
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
return hwrng_register(&exynos_rng->rng);
|
||||
}
|
||||
|
||||
static int exynos_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_rng *exynos_rng = platform_get_drvdata(pdev);
|
||||
|
||||
hwrng_unregister(&exynos_rng->rng);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME)
|
||||
static int exynos_rng_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct exynos_rng *exynos_rng = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable_unprepare(exynos_rng->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_rng_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct exynos_rng *exynos_rng = platform_get_drvdata(pdev);
|
||||
|
||||
return clk_prepare_enable(exynos_rng->clk);
|
||||
}
|
||||
#endif
|
||||
|
||||
static UNIVERSAL_DEV_PM_OPS(exynos_rng_pm_ops, exynos_rng_runtime_suspend,
|
||||
exynos_rng_runtime_resume, NULL);
|
||||
|
||||
static struct platform_driver exynos_rng_driver = {
|
||||
.driver = {
|
||||
.name = "exynos-rng",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &exynos_rng_pm_ops,
|
||||
},
|
||||
.probe = exynos_rng_probe,
|
||||
.remove = exynos_rng_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(exynos_rng_driver);
|
||||
|
||||
MODULE_DESCRIPTION("EXYNOS 4 H/W Random Number Generator driver");
|
||||
MODULE_AUTHOR("Jonghwa Lee <jonghwa3.lee@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
363
drivers/char/hw_random/exyswd-rng.c
Normal file
363
drivers/char/hw_random/exyswd-rng.c
Normal file
|
@ -0,0 +1,363 @@
|
|||
/*
|
||||
* exyswd-rng.c - Random Number Generator driver for the exynos
|
||||
*
|
||||
* Copyright (C) 2013 Samsung Electronics
|
||||
* Sehee Kim <sehi.kim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/hw_random.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/smc.h>
|
||||
|
||||
#define HWRNG_RET_OK 0
|
||||
#define HWRNG_RET_INVALID_ERROR 1
|
||||
#define HWRNG_RET_RETRY_ERROR 2
|
||||
#define HWRNG_RET_INVALID_FLAG_ERROR 3
|
||||
#define HWRNG_RET_TEST_ERROR 4
|
||||
#define HWRNG_RET_START_UP_TEST_DONE 5
|
||||
|
||||
#define EXYRNG_MAX_FAILURES 25
|
||||
#define EXYRNG_START_UP_SIZE (4096 + 1)
|
||||
#define EXYRNG_RETRY_MAX_COUNT 1000
|
||||
#define EXYRNG_START_UP_TEST_MAX_RETRY 2
|
||||
|
||||
uint32_t hwrng_read_flag;
|
||||
static struct hwrng rng;
|
||||
|
||||
spinlock_t hwrandom_lock;
|
||||
#if defined(CONFIG_EXYRNG_FAIL_POLICY_DISABLE)
|
||||
static int hwrng_disabled;
|
||||
#endif
|
||||
static int start_up_test;
|
||||
|
||||
#ifdef CONFIG_EXYRNG_DEBUG
|
||||
#define exyrng_debug(args...) printk(KERN_INFO args)
|
||||
#else
|
||||
#define exyrng_debug(args...)
|
||||
#endif
|
||||
|
||||
void exynos_swd_test_fail(void)
|
||||
{
|
||||
#if defined(CONFIG_EXYRNG_FAIL_POLICY_DISABLE)
|
||||
hwrng_disabled = 1;
|
||||
printk("[ExyRNG] disabled for test failures\n");
|
||||
#else /* defined(CONFIG_EXYRNG_POLICY_RESET) */
|
||||
panic("[ExyRNG] It failed to health tests. It means that it detects "
|
||||
"the malfunction of TRNG(HW) which generates random numbers. If it "
|
||||
"doesn't offer enough entropy, it should not be used. The system "
|
||||
"reset could be a way to solve it. The health tests are designed "
|
||||
"to have the false positive rate of approximately once per billion "
|
||||
"based on min-entropy of TRNG.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int exynos_swd_startup_test(void)
|
||||
{
|
||||
uint32_t start_up_size;
|
||||
uint32_t retry_cnt;
|
||||
uint32_t test_cnt;
|
||||
int ret = HWRNG_RET_OK;
|
||||
|
||||
start_up_size = EXYRNG_START_UP_SIZE;
|
||||
|
||||
retry_cnt = 0;
|
||||
test_cnt = 1;
|
||||
while (start_up_size) {
|
||||
ret = exynos_smc(SMC_CMD_RANDOM, HWRNG_GET_DATA, 1, 0);
|
||||
if (ret == HWRNG_RET_RETRY_ERROR) {
|
||||
if (retry_cnt++ > EXYRNG_RETRY_MAX_COUNT) {
|
||||
printk("[ExyRNG] exceed retry in start-up test\n");
|
||||
break;
|
||||
}
|
||||
usleep_range(50, 100);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ret == HWRNG_RET_TEST_ERROR) {
|
||||
#ifndef CONFIG_EXYRNG_USE_CRYPTOMANAGER
|
||||
if (test_cnt < EXYRNG_START_UP_TEST_MAX_RETRY) {
|
||||
start_up_size = EXYRNG_START_UP_SIZE;
|
||||
test_cnt++;
|
||||
printk("[ExyRNG] It performs start-up test "
|
||||
"again to detect the malfunction of TRNG with "
|
||||
"accuracy\n");
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
exynos_swd_test_fail();
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* start-up test is performed already */
|
||||
if (ret == HWRNG_RET_START_UP_TEST_DONE) {
|
||||
ret = HWRNG_RET_OK;
|
||||
exyrng_debug("[ExyRNG] start-up test is already done\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != HWRNG_RET_OK) {
|
||||
exyrng_debug("[ExyRNG] failed to get random\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (start_up_size >= 32)
|
||||
start_up_size -= 32;
|
||||
else
|
||||
start_up_size = 0;
|
||||
|
||||
retry_cnt = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos_swd_read(struct hwrng *rng, void *data, size_t max, bool wait)
|
||||
{
|
||||
uint32_t *read_buf = data;
|
||||
uint32_t read_size = max;
|
||||
uint32_t r_data[2];
|
||||
unsigned long flag;
|
||||
uint32_t retry_cnt;
|
||||
int ret = HWRNG_RET_OK;
|
||||
|
||||
register u64 reg0 __asm__("x0");
|
||||
register u64 reg1 __asm__("x1");
|
||||
register u64 reg2 __asm__("x2");
|
||||
register u64 reg3 __asm__("x3");
|
||||
|
||||
#if defined(CONFIG_EXYRNG_FAIL_POLICY_DISABLE)
|
||||
if (hwrng_disabled)
|
||||
return -EPERM;
|
||||
#endif
|
||||
|
||||
reg0 = 0;
|
||||
reg1 = 0;
|
||||
reg2 = 0;
|
||||
reg3 = 0;
|
||||
|
||||
retry_cnt = 0;
|
||||
do {
|
||||
spin_lock_irqsave(&hwrandom_lock, flag);
|
||||
if (hwrng_read_flag == 0) {
|
||||
ret = exynos_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0);
|
||||
if (ret == HWRNG_RET_OK)
|
||||
hwrng_read_flag = 1;
|
||||
spin_unlock_irqrestore(&hwrandom_lock, flag);
|
||||
|
||||
if (ret == HWRNG_RET_RETRY_ERROR) {
|
||||
if (retry_cnt++ > EXYRNG_RETRY_MAX_COUNT) {
|
||||
printk("[ExyRNG] exceed retry in init\n");
|
||||
break;
|
||||
}
|
||||
usleep_range(50, 100);
|
||||
}
|
||||
} else {
|
||||
spin_unlock_irqrestore(&hwrandom_lock, flag);
|
||||
break;
|
||||
}
|
||||
} while (ret == HWRNG_RET_RETRY_ERROR);
|
||||
if (ret != HWRNG_RET_OK) {
|
||||
msleep(1);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (start_up_test) {
|
||||
ret = exynos_swd_startup_test();
|
||||
if (ret != HWRNG_RET_OK)
|
||||
goto out;
|
||||
|
||||
start_up_test = 0;
|
||||
printk("[ExyRNG] passed the start-up test\n");
|
||||
}
|
||||
|
||||
retry_cnt = 0;
|
||||
while (read_size) {
|
||||
spin_lock_irqsave(&hwrandom_lock, flag);
|
||||
ret = exynos_smc(SMC_CMD_RANDOM, HWRNG_GET_DATA, 0, 0);
|
||||
__asm__ volatile(
|
||||
"\t"
|
||||
: "+r"(reg0), "+r"(reg1), "+r"(reg2), "+r"(reg3)
|
||||
);
|
||||
r_data[0] = (uint32_t)reg2;
|
||||
r_data[1] = (uint32_t)reg3;
|
||||
spin_unlock_irqrestore(&hwrandom_lock, flag);
|
||||
|
||||
if (ret == HWRNG_RET_RETRY_ERROR) {
|
||||
if (retry_cnt++ > EXYRNG_RETRY_MAX_COUNT) {
|
||||
ret = -EFAULT;
|
||||
printk("[ExyRNG] exceed retry in read\n");
|
||||
goto out;
|
||||
}
|
||||
usleep_range(50, 100);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ret == HWRNG_RET_TEST_ERROR) {
|
||||
exyrng_debug("[ExyRNG] failed to continuous test\n");
|
||||
ret = -EAGAIN;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ret != HWRNG_RET_OK) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*(uint32_t*)(read_buf++) = r_data[0];
|
||||
*(uint32_t*)(read_buf++) = r_data[1];
|
||||
|
||||
read_size -= 8;
|
||||
retry_cnt = 0;
|
||||
}
|
||||
|
||||
ret = max;
|
||||
|
||||
out:
|
||||
retry_cnt = 0;
|
||||
do {
|
||||
spin_lock_irqsave(&hwrandom_lock, flag);
|
||||
if (!exynos_smc(SMC_CMD_RANDOM, HWRNG_EXIT, 0, 0)) {
|
||||
hwrng_read_flag = 0;
|
||||
spin_unlock_irqrestore(&hwrandom_lock, flag);
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&hwrandom_lock, flag);
|
||||
|
||||
if (retry_cnt++ > EXYRNG_RETRY_MAX_COUNT) {
|
||||
printk("[ExyRNG] exceed retry in exit\n");
|
||||
break;
|
||||
}
|
||||
usleep_range(50, 100);
|
||||
} while(1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exyswd_rng_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
rng.name = "exyswd_rng";
|
||||
rng.read = exynos_swd_read;
|
||||
rng.quality = 500;
|
||||
|
||||
spin_lock_init(&hwrandom_lock);
|
||||
#if defined(CONFIG_EXYRNG_FIPS_COMPLIANCE)
|
||||
start_up_test = 1;
|
||||
#endif
|
||||
|
||||
ret = hwrng_register(&rng);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
printk(KERN_INFO "ExyRNG: hwrng registered\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exyswd_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
hwrng_unregister(&rng);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME)
|
||||
static int exyswd_rng_suspend(struct device *dev)
|
||||
{
|
||||
unsigned long flag;
|
||||
int ret = HWRNG_RET_OK;
|
||||
|
||||
spin_lock_irqsave(&hwrandom_lock, flag);
|
||||
if (hwrng_read_flag) {
|
||||
ret = exynos_smc(SMC_CMD_RANDOM, HWRNG_EXIT, 0, 0);
|
||||
if (ret != HWRNG_RET_OK)
|
||||
printk("[ExyRNG] failed to enter suspend with %d\n", ret);
|
||||
}
|
||||
spin_unlock_irqrestore(&hwrandom_lock, flag);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exyswd_rng_resume(struct device *dev)
|
||||
{
|
||||
unsigned long flag;
|
||||
int ret = HWRNG_RET_OK;
|
||||
|
||||
spin_lock_irqsave(&hwrandom_lock, flag);
|
||||
#if defined(CONFIG_EXYRNG_FIPS_COMPLIANCE)
|
||||
ret = exynos_smc(SMC_CMD_RANDOM, HWRNG_RESUME, 0, 0);
|
||||
if (ret != HWRNG_RET_OK)
|
||||
printk("[ExyRNG] failed to resume with %d\n", ret);
|
||||
#endif
|
||||
if (hwrng_read_flag) {
|
||||
ret = exynos_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0);
|
||||
if (ret != HWRNG_RET_OK)
|
||||
printk("[ExyRNG] failed to resume with %d\n", ret);
|
||||
}
|
||||
spin_unlock_irqrestore(&hwrandom_lock, flag);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static UNIVERSAL_DEV_PM_OPS(exyswd_rng_pm_ops, exyswd_rng_suspend, exyswd_rng_resume, NULL);
|
||||
|
||||
static struct platform_driver exyswd_rng_driver = {
|
||||
.probe = exyswd_rng_probe,
|
||||
.remove = exyswd_rng_remove,
|
||||
.driver = {
|
||||
.name = "exyswd_rng",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &exyswd_rng_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device exyswd_rng_device = {
|
||||
.name = "exyswd_rng",
|
||||
.id = -1,
|
||||
};
|
||||
|
||||
static int __init exyswd_rng_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = platform_device_register(&exyswd_rng_device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = platform_driver_register(&exyswd_rng_driver);
|
||||
if (ret) {
|
||||
platform_device_unregister(&exyswd_rng_device);
|
||||
return ret;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "ExyRNG driver, (c) 2014 Samsung Electronics\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit exyswd_rng_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&exyswd_rng_driver);
|
||||
platform_device_unregister(&exyswd_rng_device);
|
||||
}
|
||||
|
||||
module_init(exyswd_rng_init);
|
||||
module_exit(exyswd_rng_exit);
|
||||
|
||||
MODULE_DESCRIPTION("EXYNOS H/W Random Number Generator driver");
|
||||
MODULE_AUTHOR("Sehee Kim <sehi.kim@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
139
drivers/char/hw_random/geode-rng.c
Normal file
139
drivers/char/hw_random/geode-rng.c
Normal file
|
@ -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/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/hw_random.h>
|
||||
#include <linux/delay.h>
|
||||
#include <asm/io.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");
|
418
drivers/char/hw_random/intel-rng.c
Normal file
418
drivers/char/hw_random/intel-rng.c
Normal file
|
@ -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");
|
75
drivers/char/hw_random/ixp4xx-rng.c
Normal file
75
drivers/char/hw_random/ixp4xx-rng.c
Normal file
|
@ -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");
|
197
drivers/char/hw_random/msm-rng.c
Normal file
197
drivers/char/hw_random/msm-rng.c
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only 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/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hw_random.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
/* Device specific register offsets */
|
||||
#define PRNG_DATA_OUT 0x0000
|
||||
#define PRNG_STATUS 0x0004
|
||||
#define PRNG_LFSR_CFG 0x0100
|
||||
#define PRNG_CONFIG 0x0104
|
||||
|
||||
/* Device specific register masks and config values */
|
||||
#define PRNG_LFSR_CFG_MASK 0x0000ffff
|
||||
#define PRNG_LFSR_CFG_CLOCKS 0x0000dddd
|
||||
#define PRNG_CONFIG_HW_ENABLE BIT(1)
|
||||
#define PRNG_STATUS_DATA_AVAIL BIT(0)
|
||||
|
||||
#define MAX_HW_FIFO_DEPTH 16
|
||||
#define MAX_HW_FIFO_SIZE (MAX_HW_FIFO_DEPTH * 4)
|
||||
#define WORD_SZ 4
|
||||
|
||||
struct msm_rng {
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
struct hwrng hwrng;
|
||||
};
|
||||
|
||||
#define to_msm_rng(p) container_of(p, struct msm_rng, hwrng)
|
||||
|
||||
static int msm_rng_enable(struct hwrng *hwrng, int enable)
|
||||
{
|
||||
struct msm_rng *rng = to_msm_rng(hwrng);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(rng->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (enable) {
|
||||
/* Enable PRNG only if it is not already enabled */
|
||||
val = readl_relaxed(rng->base + PRNG_CONFIG);
|
||||
if (val & PRNG_CONFIG_HW_ENABLE)
|
||||
goto already_enabled;
|
||||
|
||||
val = readl_relaxed(rng->base + PRNG_LFSR_CFG);
|
||||
val &= ~PRNG_LFSR_CFG_MASK;
|
||||
val |= PRNG_LFSR_CFG_CLOCKS;
|
||||
writel(val, rng->base + PRNG_LFSR_CFG);
|
||||
|
||||
val = readl_relaxed(rng->base + PRNG_CONFIG);
|
||||
val |= PRNG_CONFIG_HW_ENABLE;
|
||||
writel(val, rng->base + PRNG_CONFIG);
|
||||
} else {
|
||||
val = readl_relaxed(rng->base + PRNG_CONFIG);
|
||||
val &= ~PRNG_CONFIG_HW_ENABLE;
|
||||
writel(val, rng->base + PRNG_CONFIG);
|
||||
}
|
||||
|
||||
already_enabled:
|
||||
clk_disable_unprepare(rng->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int msm_rng_read(struct hwrng *hwrng, void *data, size_t max, bool wait)
|
||||
{
|
||||
struct msm_rng *rng = to_msm_rng(hwrng);
|
||||
size_t currsize = 0;
|
||||
u32 *retdata = data;
|
||||
size_t maxsize;
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
/* calculate max size bytes to transfer back to caller */
|
||||
maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max);
|
||||
|
||||
/* no room for word data */
|
||||
if (maxsize < WORD_SZ)
|
||||
return 0;
|
||||
|
||||
ret = clk_prepare_enable(rng->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* read random data from hardware */
|
||||
do {
|
||||
val = readl_relaxed(rng->base + PRNG_STATUS);
|
||||
if (!(val & PRNG_STATUS_DATA_AVAIL))
|
||||
break;
|
||||
|
||||
val = readl_relaxed(rng->base + PRNG_DATA_OUT);
|
||||
if (!val)
|
||||
break;
|
||||
|
||||
*retdata++ = val;
|
||||
currsize += WORD_SZ;
|
||||
|
||||
/* make sure we stay on 32bit boundary */
|
||||
if ((maxsize - currsize) < WORD_SZ)
|
||||
break;
|
||||
} while (currsize < maxsize);
|
||||
|
||||
clk_disable_unprepare(rng->clk);
|
||||
|
||||
return currsize;
|
||||
}
|
||||
|
||||
static int msm_rng_init(struct hwrng *hwrng)
|
||||
{
|
||||
return msm_rng_enable(hwrng, 1);
|
||||
}
|
||||
|
||||
static void msm_rng_cleanup(struct hwrng *hwrng)
|
||||
{
|
||||
msm_rng_enable(hwrng, 0);
|
||||
}
|
||||
|
||||
static int msm_rng_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
struct msm_rng *rng;
|
||||
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->clk = devm_clk_get(&pdev->dev, "core");
|
||||
if (IS_ERR(rng->clk))
|
||||
return PTR_ERR(rng->clk);
|
||||
|
||||
rng->hwrng.name = KBUILD_MODNAME,
|
||||
rng->hwrng.init = msm_rng_init,
|
||||
rng->hwrng.cleanup = msm_rng_cleanup,
|
||||
rng->hwrng.read = msm_rng_read,
|
||||
|
||||
ret = hwrng_register(&rng->hwrng);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register hwrng\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int msm_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct msm_rng *rng = platform_get_drvdata(pdev);
|
||||
|
||||
hwrng_unregister(&rng->hwrng);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id msm_rng_of_match[] = {
|
||||
{ .compatible = "qcom,prng", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, msm_rng_of_match);
|
||||
|
||||
static struct platform_driver msm_rng_driver = {
|
||||
.probe = msm_rng_probe,
|
||||
.remove = msm_rng_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(msm_rng_of_match),
|
||||
}
|
||||
};
|
||||
module_platform_driver(msm_rng_driver);
|
||||
|
||||
MODULE_ALIAS("platform:" KBUILD_MODNAME);
|
||||
MODULE_AUTHOR("The Linux Foundation");
|
||||
MODULE_DESCRIPTION("Qualcomm MSM random number generator driver");
|
||||
MODULE_LICENSE("GPL v2");
|
218
drivers/char/hw_random/mxc-rnga.c
Normal file
218
drivers/char/hw_random/mxc-rnga.c
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/hw_random.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.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 = -ENODEV;
|
||||
struct resource *res;
|
||||
struct mxc_rng *mxc_rng;
|
||||
|
||||
mxc_rng = devm_kzalloc(&pdev->dev, sizeof(struct 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");
|
||||
err = PTR_ERR(mxc_rng->clk);
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(mxc_rng->clk);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "MXC RNGA Registered.\n");
|
||||
|
||||
return 0;
|
||||
|
||||
err_ioremap:
|
||||
clk_disable_unprepare(mxc_rng->clk);
|
||||
|
||||
out:
|
||||
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 struct platform_driver mxc_rnga_driver = {
|
||||
.driver = {
|
||||
.name = "mxc_rnga",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.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");
|
79
drivers/char/hw_random/n2-asm.S
Normal file
79
drivers/char/hw_random/n2-asm.S
Normal file
|
@ -0,0 +1,79 @@
|
|||
/* 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)
|
760
drivers/char/hw_random/n2-drv.c
Normal file
760
drivers/char/hw_random/n2-drv.c
Normal file
|
@ -0,0 +1,760 @@
|
|||
/* 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.2"
|
||||
#define DRV_MODULE_RELDATE "July 27, 2011"
|
||||
|
||||
static char version[] =
|
||||
DRV_MODULE_NAME ".c: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);
|
||||
}
|
||||
|
||||
#define CONTROL_DEFAULT_BASE \
|
||||
((2 << RNG_CTL_ASEL_SHIFT) | \
|
||||
(N2RNG_ACCUM_CYCLES_DEFAULT << RNG_CTL_WAIT_SHIFT) | \
|
||||
RNG_CTL_LFSR)
|
||||
|
||||
#define CONTROL_DEFAULT_0 \
|
||||
(CONTROL_DEFAULT_BASE | \
|
||||
(1 << RNG_CTL_VCO_SHIFT) | \
|
||||
RNG_CTL_ES1)
|
||||
#define CONTROL_DEFAULT_1 \
|
||||
(CONTROL_DEFAULT_BASE | \
|
||||
(2 << RNG_CTL_VCO_SHIFT) | \
|
||||
RNG_CTL_ES2)
|
||||
#define CONTROL_DEFAULT_2 \
|
||||
(CONTROL_DEFAULT_BASE | \
|
||||
(3 << RNG_CTL_VCO_SHIFT) | \
|
||||
RNG_CTL_ES3)
|
||||
#define CONTROL_DEFAULT_3 \
|
||||
(CONTROL_DEFAULT_BASE | \
|
||||
RNG_CTL_ES1 | RNG_CTL_ES2 | RNG_CTL_ES3)
|
||||
|
||||
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] = CONTROL_DEFAULT_0;
|
||||
up->control[1] = CONTROL_DEFAULT_1;
|
||||
up->control[2] = CONTROL_DEFAULT_2;
|
||||
up->control[3] = CONTROL_DEFAULT_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->buffer = np->test_data >> 32;
|
||||
*data = np->test_data & 0xffffffff;
|
||||
len = 4;
|
||||
} else {
|
||||
dev_err(&np->op->dev, "RNG error, restesting\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 = SELFTEST_VAL;
|
||||
int err, matches, limit;
|
||||
|
||||
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;
|
||||
|
||||
np->test_control[0] = (0x2 << RNG_CTL_ASEL_SHIFT);
|
||||
np->test_control[1] = (0x2 << RNG_CTL_ASEL_SHIFT);
|
||||
np->test_control[2] = (0x2 << RNG_CTL_ASEL_SHIFT);
|
||||
np->test_control[3] = ((0x2 << RNG_CTL_ASEL_SHIFT) |
|
||||
RNG_CTL_LFSR |
|
||||
((SELFTEST_TICKS - 2) << RNG_CTL_WAIT_SHIFT));
|
||||
|
||||
|
||||
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;
|
||||
|
||||
base = ((np->accum_cycles << RNG_CTL_WAIT_SHIFT) |
|
||||
(2 << RNG_CTL_ASEL_SHIFT) |
|
||||
RNG_CTL_LFSR);
|
||||
|
||||
/* 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 << RNG_CTL_VCO_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;
|
||||
|
||||
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 (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 multi_capable;
|
||||
int err = -ENOMEM;
|
||||
struct n2rng *np;
|
||||
|
||||
match = of_match_device(n2rng_match, &op->dev);
|
||||
if (!match)
|
||||
return -EINVAL;
|
||||
multi_capable = (match->data != NULL);
|
||||
|
||||
n2rng_driver_version();
|
||||
np = devm_kzalloc(&op->dev, sizeof(*np), GFP_KERNEL);
|
||||
if (!np)
|
||||
goto out;
|
||||
np->op = op;
|
||||
|
||||
INIT_DELAYED_WORK(&np->work, n2rng_work);
|
||||
|
||||
if (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_kzalloc(&op->dev,
|
||||
sizeof(struct n2rng_unit) * np->num_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 = "n2rng";
|
||||
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 const struct of_device_id n2rng_match[] = {
|
||||
{
|
||||
.name = "random-number-generator",
|
||||
.compatible = "SUNW,n2-rng",
|
||||
},
|
||||
{
|
||||
.name = "random-number-generator",
|
||||
.compatible = "SUNW,vf-rng",
|
||||
.data = (void *) 1,
|
||||
},
|
||||
{
|
||||
.name = "random-number-generator",
|
||||
.compatible = "SUNW,kt-rng",
|
||||
.data = (void *) 1,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, n2rng_match);
|
||||
|
||||
static struct platform_driver n2rng_driver = {
|
||||
.driver = {
|
||||
.name = "n2rng",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = n2rng_match,
|
||||
},
|
||||
.probe = n2rng_probe,
|
||||
.remove = n2rng_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(n2rng_driver);
|
118
drivers/char/hw_random/n2rng.h
Normal file
118
drivers/char/hw_random/n2rng.h
Normal file
|
@ -0,0 +1,118 @@
|
|||
/* n2rng.h: Niagara2 RNG defines.
|
||||
*
|
||||
* Copyright (C) 2008 David S. Miller <davem@davemloft.net>
|
||||
*/
|
||||
|
||||
#ifndef _N2RNG_H
|
||||
#define _N2RNG_H
|
||||
|
||||
#define RNG_CTL_WAIT 0x0000000001fffe00ULL /* Minimum wait time */
|
||||
#define RNG_CTL_WAIT_SHIFT 9
|
||||
#define RNG_CTL_BYPASS 0x0000000000000100ULL /* VCO voltage source */
|
||||
#define RNG_CTL_VCO 0x00000000000000c0ULL /* VCO rate control */
|
||||
#define RNG_CTL_VCO_SHIFT 6
|
||||
#define RNG_CTL_ASEL 0x0000000000000030ULL /* Analog MUX select */
|
||||
#define RNG_CTL_ASEL_SHIFT 4
|
||||
#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 */
|
||||
|
||||
#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);
|
||||
|
||||
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 */
|
||||
|
||||
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 SELFTEST_TICKS 38859
|
||||
#define SELFTEST_VAL ((u64)0xB8820C7BD387E32C)
|
||||
#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 */
|
106
drivers/char/hw_random/nomadik-rng.c
Normal file
106
drivers/char/hw_random/nomadik-rng.c
Normal file
|
@ -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");
|
129
drivers/char/hw_random/octeon-rng.c
Normal file
129
drivers/char/hw_random/octeon-rng.c
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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 = hwrng_register(&rng->ops);
|
||||
if (ret)
|
||||
return -ENOENT;
|
||||
|
||||
dev_info(&pdev->dev, "Octeon Random Number Generator\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __exit octeon_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hwrng *rng = platform_get_drvdata(pdev);
|
||||
|
||||
hwrng_unregister(rng);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver octeon_rng_driver = {
|
||||
.driver = {
|
||||
.name = "octeon_rng",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = octeon_rng_probe,
|
||||
.remove = __exit_p(octeon_rng_remove),
|
||||
};
|
||||
|
||||
module_platform_driver(octeon_rng_driver);
|
||||
|
||||
MODULE_AUTHOR("David Daney");
|
||||
MODULE_LICENSE("GPL");
|
470
drivers/char/hw_random/omap-rng.c
Normal file
470
drivers/char/hw_random/omap-rng.c
Normal file
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* 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 <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
|
||||
|
||||
enum {
|
||||
RNG_OUTPUT_L_REG = 0,
|
||||
RNG_OUTPUT_H_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_L_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_L_REG] = 0x0,
|
||||
[RNG_OUTPUT_H_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,
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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_data_present(struct hwrng *rng, int wait)
|
||||
{
|
||||
struct omap_rng_dev *priv;
|
||||
int data, i;
|
||||
|
||||
priv = (struct omap_rng_dev *)rng->priv;
|
||||
|
||||
for (i = 0; i < 20; i++) {
|
||||
data = priv->pdata->data_present(priv);
|
||||
if (data || !wait)
|
||||
break;
|
||||
/* RNG produces data fast enough (2+ MBit/sec, even
|
||||
* during "rngtest" loads, that these delays don't
|
||||
* seem to trigger. We *could* use the RNG IRQ, but
|
||||
* that'd be higher overhead ... so why bother?
|
||||
*/
|
||||
udelay(10);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static int omap_rng_data_read(struct hwrng *rng, u32 *data)
|
||||
{
|
||||
struct omap_rng_dev *priv;
|
||||
u32 data_size, i;
|
||||
|
||||
priv = (struct omap_rng_dev *)rng->priv;
|
||||
data_size = priv->pdata->data_size;
|
||||
|
||||
for (i = 0; i < data_size / sizeof(u32); i++)
|
||||
data[i] = omap_rng_read(priv, RNG_OUTPUT_L_REG + i);
|
||||
|
||||
if (priv->pdata->regs[RNG_INTACK_REG])
|
||||
omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_RDY_MASK);
|
||||
return 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 struct hwrng omap_rng_ops = {
|
||||
.name = "omap",
|
||||
.data_present = omap_rng_data_present,
|
||||
.data_read = omap_rng_data_read,
|
||||
.init = omap_rng_init,
|
||||
.cleanup = omap_rng_cleanup,
|
||||
};
|
||||
|
||||
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 omap4_rng_init(struct omap_rng_dev *priv)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Return if RNG is already running. */
|
||||
if (omap_rng_read(priv, RNG_CONFIG_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_CONFIG_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 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,
|
||||
},
|
||||
{},
|
||||
};
|
||||
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")) {
|
||||
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;
|
||||
}
|
||||
omap_rng_write(priv, RNG_INTMASK_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;
|
||||
|
||||
omap_rng_ops.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;
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
|
||||
ret = (dev->of_node) ? of_get_omap_rng_device_details(priv, pdev) :
|
||||
get_omap_rng_device_details(priv);
|
||||
if (ret)
|
||||
goto err_ioremap;
|
||||
|
||||
ret = hwrng_register(&omap_rng_ops);
|
||||
if (ret)
|
||||
goto err_register;
|
||||
|
||||
dev_info(&pdev->dev, "OMAP Random Number Generator ver. %02x\n",
|
||||
omap_rng_read(priv, RNG_REV_REG));
|
||||
|
||||
return 0;
|
||||
|
||||
err_register:
|
||||
priv->base = NULL;
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
err_ioremap:
|
||||
dev_err(dev, "initialization failed.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit omap_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_rng_dev *priv = platform_get_drvdata(pdev);
|
||||
|
||||
hwrng_unregister(&omap_rng_ops);
|
||||
|
||||
priv->pdata->cleanup(priv);
|
||||
|
||||
pm_runtime_put_sync(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
||||
static int 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 omap_rng_resume(struct device *dev)
|
||||
{
|
||||
struct omap_rng_dev *priv = dev_get_drvdata(dev);
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
priv->pdata->init(priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(omap_rng_pm, omap_rng_suspend, omap_rng_resume);
|
||||
#define OMAP_RNG_PM (&omap_rng_pm)
|
||||
|
||||
#else
|
||||
|
||||
#define OMAP_RNG_PM NULL
|
||||
|
||||
#endif
|
||||
|
||||
static struct platform_driver omap_rng_driver = {
|
||||
.driver = {
|
||||
.name = "omap_rng",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = OMAP_RNG_PM,
|
||||
.of_match_table = of_match_ptr(omap_rng_of_match),
|
||||
},
|
||||
.probe = omap_rng_probe,
|
||||
.remove = __exit_p(omap_rng_remove),
|
||||
};
|
||||
|
||||
module_platform_driver(omap_rng_driver);
|
||||
MODULE_ALIAS("platform:omap_rng");
|
||||
MODULE_AUTHOR("Deepak Saxena (and others)");
|
||||
MODULE_LICENSE("GPL");
|
140
drivers/char/hw_random/omap3-rom-rng.c
Normal file
140
drivers/char/hw_random/omap3-rom-rng.c
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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/timer.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.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 timer_list idle_timer;
|
||||
static int rng_idle;
|
||||
static struct clk *rng_clk;
|
||||
|
||||
static void omap3_rom_rng_idle(unsigned long data)
|
||||
{
|
||||
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;
|
||||
|
||||
del_timer_sync(&idle_timer);
|
||||
if (rng_idle) {
|
||||
clk_prepare_enable(rng_clk);
|
||||
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);
|
||||
mod_timer(&idle_timer, jiffies + msecs_to_jiffies(500));
|
||||
if (r != 0)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap3_rom_rng_data_present(struct hwrng *rng, int wait)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int omap3_rom_rng_data_read(struct hwrng *rng, u32 *data)
|
||||
{
|
||||
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",
|
||||
.data_present = omap3_rom_rng_data_present,
|
||||
.data_read = omap3_rom_rng_data_read,
|
||||
};
|
||||
|
||||
static int omap3_rom_rng_probe(struct platform_device *pdev)
|
||||
{
|
||||
pr_info("initializing\n");
|
||||
|
||||
omap3_rom_rng_call = pdev->dev.platform_data;
|
||||
if (!omap3_rom_rng_call) {
|
||||
pr_err("omap3_rom_rng_call is NULL\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
setup_timer(&idle_timer, omap3_rom_rng_idle, 0);
|
||||
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. */
|
||||
clk_prepare_enable(rng_clk);
|
||||
omap3_rom_rng_idle(0);
|
||||
|
||||
return hwrng_register(&omap3_rom_rng_ops);
|
||||
}
|
||||
|
||||
static int omap3_rom_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
hwrng_unregister(&omap3_rom_rng_ops);
|
||||
clk_disable_unprepare(rng_clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap3_rom_rng_driver = {
|
||||
.driver = {
|
||||
.name = "omap3-rom-rng",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.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");
|
156
drivers/char/hw_random/pasemi-rng.c
Normal file
156
drivers/char/hw_random/pasemi-rng.c
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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 <asm/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 *ofdev)
|
||||
{
|
||||
void __iomem *rng_regs;
|
||||
struct device_node *rng_np = ofdev->dev.of_node;
|
||||
struct resource res;
|
||||
int err = 0;
|
||||
|
||||
err = of_address_to_resource(rng_np, 0, &res);
|
||||
if (err)
|
||||
return -ENODEV;
|
||||
|
||||
rng_regs = ioremap(res.start, 0x100);
|
||||
|
||||
if (!rng_regs)
|
||||
return -ENOMEM;
|
||||
|
||||
pasemi_rng.priv = (unsigned long)rng_regs;
|
||||
|
||||
pr_info("Registering PA Semi RNG\n");
|
||||
|
||||
err = hwrng_register(&pasemi_rng);
|
||||
|
||||
if (err)
|
||||
iounmap(rng_regs);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rng_remove(struct platform_device *dev)
|
||||
{
|
||||
void __iomem *rng_regs = (void __iomem *)pasemi_rng.priv;
|
||||
|
||||
hwrng_unregister(&pasemi_rng);
|
||||
iounmap(rng_regs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id rng_match[] = {
|
||||
{ .compatible = "1682m-rng", },
|
||||
{ .compatible = "pasemi,pwrficient-rng", },
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver rng_driver = {
|
||||
.driver = {
|
||||
.name = "pasemi-rng",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = rng_match,
|
||||
},
|
||||
.probe = rng_probe,
|
||||
.remove = rng_remove,
|
||||
};
|
||||
|
||||
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");
|
81
drivers/char/hw_random/powernv-rng.c
Normal file
81
drivers/char/hw_random/powernv-rng.c
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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/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 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");
|
147
drivers/char/hw_random/ppc4xx-rng.c
Normal file
147
drivers/char/hw_random/ppc4xx-rng.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Generic PowerPC 44x RNG driver
|
||||
*
|
||||
* Copyright 2011 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; version 2 of the License.
|
||||
*/
|
||||
|
||||
#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 <asm/io.h>
|
||||
|
||||
#define PPC4XX_TRNG_DEV_CTRL 0x60080
|
||||
|
||||
#define PPC4XX_TRNGE 0x00020000
|
||||
#define PPC4XX_TRNG_CTRL 0x0008
|
||||
#define PPC4XX_TRNG_CTRL_DALM 0x20
|
||||
#define PPC4XX_TRNG_STAT 0x0004
|
||||
#define PPC4XX_TRNG_STAT_B 0x1
|
||||
#define PPC4XX_TRNG_DATA 0x0000
|
||||
|
||||
#define MODULE_NAME "ppc4xx_rng"
|
||||
|
||||
static int ppc4xx_rng_data_present(struct hwrng *rng, int wait)
|
||||
{
|
||||
void __iomem *rng_regs = (void __iomem *) rng->priv;
|
||||
int busy, i, present = 0;
|
||||
|
||||
for (i = 0; i < 20; i++) {
|
||||
busy = (in_le32(rng_regs + PPC4XX_TRNG_STAT) & PPC4XX_TRNG_STAT_B);
|
||||
if (!busy || !wait) {
|
||||
present = 1;
|
||||
break;
|
||||
}
|
||||
udelay(10);
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
static int ppc4xx_rng_data_read(struct hwrng *rng, u32 *data)
|
||||
{
|
||||
void __iomem *rng_regs = (void __iomem *) rng->priv;
|
||||
*data = in_le32(rng_regs + PPC4XX_TRNG_DATA);
|
||||
return 4;
|
||||
}
|
||||
|
||||
static int ppc4xx_rng_enable(int enable)
|
||||
{
|
||||
struct device_node *ctrl;
|
||||
void __iomem *ctrl_reg;
|
||||
int err = 0;
|
||||
u32 val;
|
||||
|
||||
/* Find the main crypto device node and map it to turn the TRNG on */
|
||||
ctrl = of_find_compatible_node(NULL, NULL, "amcc,ppc4xx-crypto");
|
||||
if (!ctrl)
|
||||
return -ENODEV;
|
||||
|
||||
ctrl_reg = of_iomap(ctrl, 0);
|
||||
if (!ctrl_reg) {
|
||||
err = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
val = in_le32(ctrl_reg + PPC4XX_TRNG_DEV_CTRL);
|
||||
|
||||
if (enable)
|
||||
val |= PPC4XX_TRNGE;
|
||||
else
|
||||
val = val & ~PPC4XX_TRNGE;
|
||||
|
||||
out_le32(ctrl_reg + PPC4XX_TRNG_DEV_CTRL, val);
|
||||
iounmap(ctrl_reg);
|
||||
|
||||
out:
|
||||
of_node_put(ctrl);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct hwrng ppc4xx_rng = {
|
||||
.name = MODULE_NAME,
|
||||
.data_present = ppc4xx_rng_data_present,
|
||||
.data_read = ppc4xx_rng_data_read,
|
||||
};
|
||||
|
||||
static int ppc4xx_rng_probe(struct platform_device *dev)
|
||||
{
|
||||
void __iomem *rng_regs;
|
||||
int err = 0;
|
||||
|
||||
rng_regs = of_iomap(dev->dev.of_node, 0);
|
||||
if (!rng_regs)
|
||||
return -ENODEV;
|
||||
|
||||
err = ppc4xx_rng_enable(1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
out_le32(rng_regs + PPC4XX_TRNG_CTRL, PPC4XX_TRNG_CTRL_DALM);
|
||||
ppc4xx_rng.priv = (unsigned long) rng_regs;
|
||||
|
||||
err = hwrng_register(&ppc4xx_rng);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ppc4xx_rng_remove(struct platform_device *dev)
|
||||
{
|
||||
void __iomem *rng_regs = (void __iomem *) ppc4xx_rng.priv;
|
||||
|
||||
hwrng_unregister(&ppc4xx_rng);
|
||||
ppc4xx_rng_enable(0);
|
||||
iounmap(rng_regs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id ppc4xx_rng_match[] = {
|
||||
{ .compatible = "ppc4xx-rng", },
|
||||
{ .compatible = "amcc,ppc460ex-rng", },
|
||||
{ .compatible = "amcc,ppc440epx-rng", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver ppc4xx_rng_driver = {
|
||||
.driver = {
|
||||
.name = MODULE_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = ppc4xx_rng_match,
|
||||
},
|
||||
.probe = ppc4xx_rng_probe,
|
||||
.remove = ppc4xx_rng_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ppc4xx_rng_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Josh Boyer <jwboyer@linux.vnet.ibm.com>");
|
||||
MODULE_DESCRIPTION("HW RNG driver for PPC 4xx processors");
|
106
drivers/char/hw_random/pseries-rng.c
Normal file
106
drivers/char/hw_random/pseries-rng.c
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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];
|
||||
size_t size = max < 8 ? max : 8;
|
||||
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, size);
|
||||
|
||||
/* The hypervisor interface returns 64 bits */
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 __init pseries_rng_probe(struct vio_dev *dev,
|
||||
const struct vio_device_id *id)
|
||||
{
|
||||
return hwrng_register(&pseries_rng);
|
||||
}
|
||||
|
||||
static int __exit pseries_rng_remove(struct vio_dev *dev)
|
||||
{
|
||||
hwrng_unregister(&pseries_rng);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static 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");
|
214
drivers/char/hw_random/timeriomem-rng.c
Normal file
214
drivers/char/hw_random/timeriomem-rng.c
Normal file
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/hw_random.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/timeriomem-rng.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
struct timeriomem_rng_private_data {
|
||||
void __iomem *io_base;
|
||||
unsigned int expires;
|
||||
unsigned int period;
|
||||
unsigned int present:1;
|
||||
|
||||
struct timer_list timer;
|
||||
struct completion completion;
|
||||
|
||||
struct hwrng timeriomem_rng_ops;
|
||||
};
|
||||
|
||||
#define to_rng_priv(rng) \
|
||||
((struct timeriomem_rng_private_data *)rng->priv)
|
||||
|
||||
/*
|
||||
* have data return 1, however return 0 if we have nothing
|
||||
*/
|
||||
static int timeriomem_rng_data_present(struct hwrng *rng, int wait)
|
||||
{
|
||||
struct timeriomem_rng_private_data *priv = to_rng_priv(rng);
|
||||
|
||||
if (!wait || priv->present)
|
||||
return priv->present;
|
||||
|
||||
wait_for_completion(&priv->completion);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int timeriomem_rng_data_read(struct hwrng *rng, u32 *data)
|
||||
{
|
||||
struct timeriomem_rng_private_data *priv = to_rng_priv(rng);
|
||||
unsigned long cur;
|
||||
s32 delay;
|
||||
|
||||
*data = readl(priv->io_base);
|
||||
|
||||
cur = jiffies;
|
||||
|
||||
delay = cur - priv->expires;
|
||||
delay = priv->period - (delay % priv->period);
|
||||
|
||||
priv->expires = cur + delay;
|
||||
priv->present = 0;
|
||||
|
||||
reinit_completion(&priv->completion);
|
||||
mod_timer(&priv->timer, priv->expires);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
static void timeriomem_rng_trigger(unsigned long data)
|
||||
{
|
||||
struct timeriomem_rng_private_data *priv
|
||||
= (struct timeriomem_rng_private_data *)data;
|
||||
|
||||
priv->present = 1;
|
||||
complete(&priv->completion);
|
||||
}
|
||||
|
||||
static int timeriomem_rng_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct timeriomem_rng_data *pdata = pdev->dev.platform_data;
|
||||
struct timeriomem_rng_private_data *priv;
|
||||
struct 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_data), 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;
|
||||
}
|
||||
} else {
|
||||
period = pdata->period;
|
||||
}
|
||||
|
||||
priv->period = usecs_to_jiffies(period);
|
||||
if (priv->period < 1) {
|
||||
dev_err(&pdev->dev, "period is less than one jiffy\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv->expires = jiffies;
|
||||
priv->present = 1;
|
||||
|
||||
init_completion(&priv->completion);
|
||||
complete(&priv->completion);
|
||||
|
||||
setup_timer(&priv->timer, timeriomem_rng_trigger, (unsigned long)priv);
|
||||
|
||||
priv->timeriomem_rng_ops.name = dev_name(&pdev->dev);
|
||||
priv->timeriomem_rng_ops.data_present = timeriomem_rng_data_present;
|
||||
priv->timeriomem_rng_ops.data_read = timeriomem_rng_data_read;
|
||||
priv->timeriomem_rng_ops.priv = (unsigned long)priv;
|
||||
|
||||
priv->io_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(priv->io_base)) {
|
||||
err = PTR_ERR(priv->io_base);
|
||||
goto out_timer;
|
||||
}
|
||||
|
||||
err = hwrng_register(&priv->timeriomem_rng_ops);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "problem registering\n");
|
||||
goto out_timer;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "32bits from 0x%p @ %dus\n",
|
||||
priv->io_base, period);
|
||||
|
||||
return 0;
|
||||
|
||||
out_timer:
|
||||
del_timer_sync(&priv->timer);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int timeriomem_rng_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct timeriomem_rng_private_data *priv = platform_get_drvdata(pdev);
|
||||
|
||||
hwrng_unregister(&priv->timeriomem_rng_ops);
|
||||
|
||||
del_timer_sync(&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",
|
||||
.owner = THIS_MODULE,
|
||||
.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");
|
50
drivers/char/hw_random/tpm-rng.c
Normal file
50
drivers/char/hw_random/tpm-rng.c
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Kent Yoder IBM Corporation
|
||||
*
|
||||
* HWRNG interfaces to pull RNG data from a TPM
|
||||
*
|
||||
* 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/hw_random.h>
|
||||
#include <linux/tpm.h>
|
||||
|
||||
#define MODULE_NAME "tpm-rng"
|
||||
|
||||
static int tpm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
|
||||
{
|
||||
return tpm_get_random(TPM_ANY_NUM, data, max);
|
||||
}
|
||||
|
||||
static struct hwrng tpm_rng = {
|
||||
.name = MODULE_NAME,
|
||||
.read = tpm_rng_read,
|
||||
};
|
||||
|
||||
static int __init rng_init(void)
|
||||
{
|
||||
return hwrng_register(&tpm_rng);
|
||||
}
|
||||
module_init(rng_init);
|
||||
|
||||
static void __exit rng_exit(void)
|
||||
{
|
||||
hwrng_unregister(&tpm_rng);
|
||||
}
|
||||
module_exit(rng_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Kent Yoder <key@linux.vnet.ibm.com>");
|
||||
MODULE_DESCRIPTION("RNG driver for TPM devices");
|
169
drivers/char/hw_random/tx4939-rng.c
Normal file
169
drivers/char/hw_random/tx4939-rng.c
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* 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 hwrng_register(&rngdev->rng);
|
||||
}
|
||||
|
||||
static int __exit tx4939_rng_remove(struct platform_device *dev)
|
||||
{
|
||||
struct tx4939_rng *rngdev = platform_get_drvdata(dev);
|
||||
|
||||
hwrng_unregister(&rngdev->rng);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver tx4939_rng_driver = {
|
||||
.driver = {
|
||||
.name = "tx4939-rng",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.remove = tx4939_rng_remove,
|
||||
};
|
||||
|
||||
module_platform_driver_probe(tx4939_rng_driver, tx4939_rng_probe);
|
||||
|
||||
MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver for TX4939");
|
||||
MODULE_LICENSE("GPL");
|
231
drivers/char/hw_random/via-rng.c
Normal file
231
drivers/char/hw_random/via-rng.c
Normal file
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* 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/i387.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, so
|
||||
* we have to call them in the context of irq_ts_save/restore()
|
||||
*/
|
||||
|
||||
static inline u32 xstore(u32 *addr, u32 edx_in)
|
||||
{
|
||||
u32 eax_out;
|
||||
int ts_state;
|
||||
|
||||
ts_state = irq_ts_save();
|
||||
|
||||
asm(".byte 0x0F,0xA7,0xC0 /* xstore %%edi (addr=%0) */"
|
||||
: "=m" (*addr), "=a" (eax_out), "+d" (edx_in), "+D" (addr));
|
||||
|
||||
irq_ts_restore(ts_state);
|
||||
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)) {
|
||||
if (!cpu_has_xstore_enabled) {
|
||||
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_mask > 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 (!cpu_has_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);
|
213
drivers/char/hw_random/virtio-rng.c
Normal file
213
drivers/char/hw_random/virtio-rng.c
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* 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;
|
||||
init_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)
|
||||
{
|
||||
return probe_common(vdev);
|
||||
}
|
||||
#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");
|
423
drivers/char/hw_random/xgene-rng.c
Normal file
423
drivers/char/hw_random/xgene-rng.c
Normal file
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* 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/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(unsigned long arg)
|
||||
{
|
||||
struct xgene_rng_dev *ctx = (struct xgene_rng_dev *) arg;
|
||||
|
||||
/* 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.data = (unsigned long) ctx;
|
||||
ctx->failure_timer.function = xgene_rng_expired_timer;
|
||||
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;
|
||||
init_timer(&ctx->failure_timer);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ctx->irq = platform_get_irq(pdev, 0);
|
||||
if (ctx->irq < 0) {
|
||||
dev_err(&pdev->dev, "No IRQ resource\n");
|
||||
return ctx->irq;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(xgene_rng_driver);
|
||||
MODULE_DESCRIPTION("APM X-Gene RNG driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Add table
Add a link
Reference in a new issue