[linux-yocto] [PATCH 1/2] char: hwrng: AXXIA HW Random Number Generator support
Cristian Bercaru
cristian.bercaru at windriver.com
Fri May 15 07:44:36 PDT 2015
From: Sreedevi Joshi <sreedevi.joshi at intel.com>
AXXIA TRNG block driver for random number generation has
been added. This provides HW Random number generation using
AXXIA HW block. When enabled in the device tree,
/dev/hwrng device is available and random numbers can be read
from there.
Signed-off-by: Sreedevi Joshi <sreedevi.joshi at intel.com>
---
arch/arm/boot/dts/axm55xx.dts | 6 +
arch/powerpc/boot/dts/acp35xx.dts | 9 +
drivers/char/hw_random/Kconfig | 10 +
drivers/char/hw_random/Makefile | 1 +
drivers/char/hw_random/axxia-rng.c | 548 ++++++++++++++++++++++++++++++++++++
5 files changed, 574 insertions(+)
create mode 100644 drivers/char/hw_random/axxia-rng.c
diff --git a/arch/arm/boot/dts/axm55xx.dts b/arch/arm/boot/dts/axm55xx.dts
index 91d3f42..6c7617e 100644
--- a/arch/arm/boot/dts/axm55xx.dts
+++ b/arch/arm/boot/dts/axm55xx.dts
@@ -437,6 +437,12 @@
interrupts = <0 45 4>;
};
+ trng: trng at 20101a0000 {
+ compatible = "lsi,trng";
+ reg = <0x20 0x101a0000 0 0x20000>;
+ interrupts = <0 8 4>;
+ };
+
rio0: rapidio at 0x3100000000 {
index = <0>;
status = "okay";
diff --git a/arch/powerpc/boot/dts/acp35xx.dts b/arch/powerpc/boot/dts/acp35xx.dts
index 1a5a33e..a715c50 100644
--- a/arch/powerpc/boot/dts/acp35xx.dts
+++ b/arch/powerpc/boot/dts/acp35xx.dts
@@ -340,6 +340,15 @@
/* config space access MPAGE7 registers*/
reg = <0x590000 0x2000>;
};
+ TRNG: trng {
+ compatible = "lsi,trng";
+ enabled = <1>;
+ reg = <0x00460000 0x20000>;
+ interrupt-parent = <&MPIC>;
+ interrupts = <43>;
+ status = "ok";
+ };
+
};
};
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig
index b2402eb..d2ce5e1 100644
--- a/drivers/char/hw_random/Kconfig
+++ b/drivers/char/hw_random/Kconfig
@@ -20,6 +20,16 @@ config HW_RANDOM
If unsure, say Y.
+config HW_RANDOM_AXXIA
+ tristate "Intel Axxia HW TRNG"
+ depends on HW_RANDOM
+ ---help---
+ The True Random Number Generator(TRNG) is a random number generator
+ as part of Intel-Axxia chips.
+ This provids a driver for the HW block. RNG device is usually created
+ as /dev/hwrng. Random numbers can be read from this device.
+
+
config HW_RANDOM_TIMERIOMEM
tristate "Timer IOMEM HW Random Number Generator support"
depends on HW_RANDOM && HAS_IOMEM
diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile
index b2ff526..dcdf609 100644
--- a/drivers/char/hw_random/Makefile
+++ b/drivers/char/hw_random/Makefile
@@ -4,6 +4,7 @@
obj-$(CONFIG_HW_RANDOM) += rng-core.o
rng-core-y := core.o
+obj-$(CONFIG_HW_RANDOM_AXXIA) += axxia-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
diff --git a/drivers/char/hw_random/axxia-rng.c b/drivers/char/hw_random/axxia-rng.c
new file mode 100644
index 0000000..c7ddb47
--- /dev/null
+++ b/drivers/char/hw_random/axxia-rng.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2015 Intel 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.
+ *
+ * Device driver for AXXIA True Random Number Generator (TRNG)
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/interrupt.h>
+#include <linux/hw_random.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/atomic.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+
+
+/* For internal testing only.. */
+static int trng_test_mode = -1;
+
+#define TRNG_AXM55xx_DEADMAN_LP_CNT 0x1000
+#define NCP_AXM55xx_TRNG_DOUT_COUNT (4)
+#define TRNG_AXM55xx_SLEEP_USECS 100
+
+/* TRNG registers */
+struct trng_regs {
+ u32 dummy; /* 0x00 */
+ u32 scratch; /* 0x04 */
+ u32 reserved_0c[2]; /* 0x8,0x0c */
+ u32 cmd; /* 0x10 */
+ u32 reserved_1c[3]; /* 0x14..0x1c */
+ u32 sts; /* 0x20 */
+ u32 reserved_2c[3]; /* 0x24..0x2c */
+ u32 rosc_sts; /* 0x30 */
+ u32 reserved_3c[3]; /* 0x34..0x3c */
+ u32 dout; /* 0x40 */
+ u32 reserved_4c[3]; /* 0x44..0x4c */
+ u32 rosc_out; /* 0x50 */
+ u32 reserved_5c[3]; /* 0x54..0x5c */
+ u32 req_len; /* 0x60 */
+ u32 reserved_6c[3]; /* 0x64..0x6c */
+ u32 add_in_len; /* 0x70 */
+ u32 reserved_7c[3]; /* 0x74..0x7c */
+ u32 ctrl; /* 0x80 */
+ u32 reserved_fc[7]; /* 0x84..0x9c */
+ u32 entropy[8]; /* 0x100-0x11c */
+ u32 reserved_1fc[52]; /* 0x120..0x1fc */
+ u32 add_in[8]; /* 0x200-0x21c */
+ u32 reserved_2fc[52]; /* 0x220-0x2fc */
+ u32 int_status; /* 0x300 */
+ u32 int_enable; /* 0x304 */
+ u32 int_frc; /* 0x308 */
+};
+
+
+#ifdef __TRNG_SIMULATION
+struct trng_regs _trng_regs;
+#endif
+
+/******************************************************/
+/* register definitions generated from RDL */
+
+/******************************************************/
+#define TRNG_AXM55xx_REG_SCRATCH (0x00000004) /* RW */
+#define TRNG_AXM55xx_REG_CMD (0x00000010) /* W */
+#define TRNG_AXM55xx_REG_STS (0x00000020) /* R */
+#define TRNG_AXM55xx_REG_ROSC_STS (0x00000030) /* Rclr */
+#define TRNG_AXM55xx_REG_DOUT (0x00000040) /* R */
+#define TRNG_AXM55xx_REG_ROSC_OUT (0x00000050) /* R */
+#define TRNG_AXM55xx_REG_REQ_LEN (0x00000060) /* RW */
+#define TRNG_AXM55xx_REG_ADD_IN_LEN (0x00000070) /* RW */
+#define TRNG_AXM55xx_REG_CTRL (0x00000080) /* RW */
+#define TRNG_AXM55xx_REG_ENTROPY (0x00000100) /* W 0x100-0x11c */
+#define TRNG_AXM55xx_REG_ADD_IN (0x00000200) /* W 0x200-0x21c */
+#define TRNG_AXM55xx_REG_ISR (0x00000300) /* RWoclr */
+#define TRNG_AXM55xx_REG_IER (0x00000304) /* RW */
+#define TRNG_AXM55xx_REG_IFR (0x00000308) /* RW */
+
+
+enum {
+ TRNG_AXM55xx_CMD_INSTANTIATE = 0x1,
+ TRNG_AXM55xx_CMD_RESEED = 0x2,
+ TRNG_AXM55xx_CMD_GENERATE = 0x3,
+ TRNG_AXM55xx_CMD_TEST = 0x4,
+ TRNG_AXM55xx_CMD_UNINSTANTIATE = 0x5,
+ TRNG_AXM55xx_CMD_STANDBY = 0x6,
+ TRNG_AXM55xx_CMD_RESET = 0x7,
+} ncp_trng_acp34xx_reg_cmds_t;
+
+
+#define TRNG_AXM55xx_STS_RDY_MSK (0x0800)
+#define TRNG_AXM55xx_STS_ERR_MSK (0x0400)
+#define TRNG_AXM55xx_STS_DOUT_VALID_MSK (0x0010)
+#define TRNG_AXM55xx_STS_CMD_MSK (0x0003)
+
+
+#define TRNG_AXM55xx_REQ_LEN_MAX 255
+#define TRNG_AXM55xx_CTRL_RO_ENTROPY_MSK (0x0001)
+
+
+/******************************************************/
+/* end of RDL register definitions */
+/******************************************************/
+
+struct trng_dev {
+ struct kref ref;
+ struct platform_device *pdev;
+ unsigned long flags;
+#define FLAG_REGISTERED 0 /* Misc device registered */
+ struct trng_regs __iomem *regs;
+ unsigned int irq;
+ struct hwrng rng_dev;
+ unsigned int words_gen;
+ unsigned int words_avail;
+};
+
+#define rngdev_to_trng(mdev) container_of(mdev, struct trng_dev, trng_dev)
+
+/* Called when removed and last reference is released */
+static void trng_destroy(struct kref *ref);
+
+
+
+static irqreturn_t trng_isr(int irq_no, void *arg)
+{
+ struct trng_dev *priv = arg;
+ u32 status = readl(&priv->regs->int_status);
+
+ pr_debug("trng: int status %#x\n", status);
+
+ /* Handle interrupt */
+ /* ... */
+
+ /* Write bits to clear interrupt status */
+ writel(status, &priv->regs->int_status);
+
+ return IRQ_HANDLED;
+}
+
+
+static int
+axxia_trng_poll(
+ struct trng_dev *dev,
+ unsigned int waitmask,
+ unsigned int *regval)
+{
+ int deadman = 0;
+
+ do {
+ if (trng_test_mode == -1)
+ *regval = readl(&dev->regs->sts);
+ else
+ *regval = readl(&dev->regs->rosc_sts);
+ if (*regval & waitmask)
+ break;
+
+ usleep_range(TRNG_AXM55xx_SLEEP_USECS,
+ TRNG_AXM55xx_SLEEP_USECS+1);
+ deadman++;
+
+ } while (deadman < TRNG_AXM55xx_DEADMAN_LP_CNT);
+
+ if (*regval & TRNG_AXM55xx_STS_ERR_MSK)
+ return -1;
+ if (deadman >= TRNG_AXM55xx_DEADMAN_LP_CNT)
+ return -1;
+
+ return 0;
+}
+
+static int axxia_trng_init(struct hwrng *rng)
+{
+ static struct trng_dev *dev;
+ unsigned int val = 0;
+ unsigned int j = 0;
+ unsigned int pers_str[9] = { 0x41435033, 0x34303020, 0x52756e74,
+ 0x696d6520, 0x456e7669, 0x726f6e6d,
+ 0x656e7420, 0x4c634920, 0x00000000 };
+
+ if (!rng)
+ return -EINVAL;
+
+ dev = (struct trng_dev *) (rng->priv);
+
+ if (!dev)
+ return -EINVAL;
+
+ dev->words_gen = 0;
+ dev->words_avail = 0;
+
+ /* 1. Reset to start work from known state. */
+ writel(TRNG_AXM55xx_CMD_RESET, &(dev->regs->cmd));
+
+ /* 2. Poll status register until rdy or err bits are set */
+ if (axxia_trng_poll(dev,
+ TRNG_AXM55xx_STS_RDY_MSK | TRNG_AXM55xx_STS_ERR_MSK,
+ &val) != 0) {
+ /* couldn't initialize */
+ return -EIO;
+ }
+
+ /* 3. Ring_oscillators' entropy, prediction resistance OFF,
+ * continuous test ON
+ */
+ writel(TRNG_AXM55xx_CTRL_RO_ENTROPY_MSK, &dev->regs->ctrl);
+
+ /* 4. Compute and Write random initialization key */
+ get_random_bytes(pers_str, 32);
+
+ /*setup add_in_len */
+ writel(256, &dev->regs->add_in_len);
+
+ for (j = 0; j < 8; j++)
+ writel(pers_str[j], &dev->regs->add_in[j]);
+
+ /* Issue INSTANTIATE cmd to tell the device to finish internal setup */
+ writel(TRNG_AXM55xx_CMD_INSTANTIATE, &dev->regs->cmd);
+
+ /* Poll until RDY or ERR bits are set */
+ if (axxia_trng_poll(dev,
+ TRNG_AXM55xx_STS_RDY_MSK | TRNG_AXM55xx_STS_ERR_MSK,
+ &val) != 0) {
+ /* failure. */
+ return -EIO;
+ }
+
+ if ((val & TRNG_AXM55xx_STS_CMD_MSK) != TRNG_AXM55xx_CMD_INSTANTIATE) {
+ /* failure. */
+ return -EIO;
+ }
+
+ /* No additioanl input */
+ writel(0, &dev->regs->add_in_len);
+
+ /* Length of random bit stream desired: 255 *128 bits */
+ dev->words_gen = TRNG_AXM55xx_REQ_LEN_MAX * (128/32);
+ writel(TRNG_AXM55xx_REQ_LEN_MAX, &dev->regs->req_len);
+
+ /* Start the data generator */
+ writel(TRNG_AXM55xx_CMD_GENERATE, &dev->regs->cmd);
+
+ return 0;
+}
+
+static void axxia_trng_destroy(struct hwrng *rng)
+{
+ static struct trng_dev *dev;
+ if ((!rng))
+ return;
+
+ dev = (struct trng_dev *) (rng->priv);
+
+ if ((!dev) || !(dev->regs))
+ return;
+
+ /* reset*/
+ writel(TRNG_AXM55xx_CMD_RESET, &(dev->regs->cmd));
+}
+
+
+static int axxia_trng_fill_buf(void *buf, u32 rword,
+ unsigned int index,
+ unsigned int max)
+{
+ int k = 0;
+ int num = 0;
+ int i = index;
+
+ for (k = 0; k < 4; k++) {
+ ((unsigned char *)(buf))[i] =
+ (rword & (0xFF << k*8)) >> (k*8);
+ num++;
+ i++;
+ if (i >= max)
+ break;
+ }
+ return num;
+}
+
+static int axxia_trng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
+{
+ static struct trng_dev *dev;
+ unsigned int j = 0, num = 0;
+ u32 rword = 0;
+ u32 rsts = 0;
+ u32 sts = 0;
+
+ if ((!rng) || (!buf))
+ return -EINVAL;
+
+ dev = (struct trng_dev *) (rng->priv);
+
+ if (!dev)
+ return -EINVAL;
+
+ /* test mode is 0 or 1, read from ROSC_STS + ROSC_OUT */
+ if ((trng_test_mode == 0) || (trng_test_mode == 1)) {
+ for (j = 0; j < max; ) {
+ rsts = readl(&dev->regs->rosc_sts);
+ if (!rsts)
+ continue;
+
+ rword = readl(&dev->regs->rosc_out);
+ num = axxia_trng_fill_buf(buf, rword, j, max);
+ j += num;
+ if (j >= max)
+ break;
+
+ /* copy status register as well */
+ if (trng_test_mode > 0) {
+ num = axxia_trng_fill_buf(buf, rsts, j, max);
+ j += num;
+ }
+ }
+ return j;
+ }
+
+ if (trng_test_mode != -1)
+ return -EINVAL;
+
+ for (j = 0; j < max; ) {
+ /* Read from STS + DOUT */
+ if (dev->words_avail) {
+ /*
+ we read 4 bytes at a time from device.
+ */
+ rword = readl(&dev->regs->dout);
+ num = axxia_trng_fill_buf(buf, rword, j, max);
+ j += num;
+ dev->words_avail--;
+ dev->words_gen--;
+ continue;
+ } else if (dev->words_gen == 0) {
+
+ dev->words_gen = TRNG_AXM55xx_REQ_LEN_MAX * 4;
+ writel(TRNG_AXM55xx_REQ_LEN_MAX, &dev->regs->req_len);
+ /* And start the data generator ... */
+ writel(TRNG_AXM55xx_CMD_GENERATE, &dev->regs->cmd);
+ /* If it can't wait, return as much as we have.
+ above operation will make it ready for next time.
+ */
+ if (!wait)
+ break;
+
+ /* wait. status checked in next pass. */
+ usleep_range(TRNG_AXM55xx_SLEEP_USECS,
+ TRNG_AXM55xx_SLEEP_USECS + 1);
+ } else {
+ /* get next data count */
+ sts = readl(&dev->regs->sts);
+ if (sts & TRNG_AXM55xx_STS_ERR_MSK)
+ return -EIO;
+ else if (sts & TRNG_AXM55xx_STS_DOUT_VALID_MSK)
+ dev->words_avail = (sts >> 16) & 0xFFFF;
+ else if ((sts & TRNG_AXM55xx_STS_CMD_MSK) !=
+ TRNG_AXM55xx_CMD_GENERATE)
+ return -EIO;
+ }
+ }
+
+ return j;
+}
+
+
+
+static ssize_t trng_attr_testmode_show(struct device_driver *drv,
+ char *buf)
+{
+ ssize_t ret;
+
+ ret = snprintf(buf, PAGE_SIZE, "%d\n", trng_test_mode);
+ return ret;
+}
+
+
+
+static ssize_t trng_attr_testmode_store(struct device_driver *drv,
+ const char *buf, size_t count)
+{
+ /* do we need locks? */
+ sscanf(buf, "%d", &trng_test_mode);
+
+ return count;
+}
+
+
+static DRIVER_ATTR(trng_test_mode, S_IRUGO | S_IWUSR,
+ trng_attr_testmode_show,
+ trng_attr_testmode_store);
+
+
+/**
+ * trng_probe
+ *
+ * Initialize device.
+ */
+static int trng_probe(struct platform_device *pdev)
+{
+ static struct trng_dev *dev;
+ void __iomem *regs;
+ int rc;
+ u32 *pregs;
+
+ pr_debug("!!!!TRNG: trng_probe()\n");
+ /* Allocate space for device private data */
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ dev_set_drvdata(&pdev->dev, &dev);
+ kref_init(&dev->ref);
+ dev->pdev = pdev;
+
+ /* Map hardware registers */
+ regs = of_iomap(pdev->dev.of_node, 0);
+ if (!regs) {
+ rc = -EINVAL;
+ goto err;
+ }
+ pregs = (u32 *) regs;
+#ifdef __TRNG_SIMULATION
+ dev->regs = &_trng_regs;
+
+#else
+ dev->regs = regs;
+#endif
+ /* Attach to IRQ */
+ dev->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
+ rc = request_irq(dev->irq, trng_isr, 0, "trng", &dev);
+ if (rc)
+ goto err;
+
+ /* Initialize hardware */
+ /* ... */
+
+ /*
+ * Register device client interface
+ */
+ dev->rng_dev.name = "axxia-trng";
+ dev->rng_dev.init = axxia_trng_init;
+ dev->rng_dev.cleanup = axxia_trng_destroy;
+ dev->rng_dev.read = axxia_trng_read;
+ dev->rng_dev.priv = (unsigned long) dev;
+
+ rc = hwrng_register(&dev->rng_dev);
+ if (rc)
+ goto err;
+ set_bit(FLAG_REGISTERED, &dev->flags);
+
+ /* test_mode */
+ rc = driver_create_file(pdev->dev.driver, &driver_attr_trng_test_mode);
+ if (rc) {
+ pr_info("Can't create testmode?\n");
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ if (dev)
+ kref_put(&dev->ref, trng_destroy);
+ dev_err(&pdev->dev, "Failed to probe device (%d)\n", rc);
+ return rc;
+}
+
+/**
+ * trng_remove
+ */
+static int trng_remove(struct platform_device *pdev)
+{
+ struct trng_dev *dev = dev_get_drvdata(&pdev->dev);
+ /* test_mode */
+ driver_remove_file(pdev->dev.driver, &driver_attr_trng_test_mode);
+
+ kref_put(&dev->ref, trng_destroy);
+ return 0;
+}
+
+/*
+ * trng_destroy
+ *
+ * Called when refcount reaches zero to unregister device and free resources.
+ */
+static void trng_destroy(struct kref *ref)
+{
+ struct trng_dev *dev = container_of(ref, struct trng_dev, ref);
+
+ dev_set_drvdata(&dev->pdev->dev, NULL);
+ if (test_and_clear_bit(FLAG_REGISTERED, &dev->flags))
+ hwrng_unregister(&dev->rng_dev);
+ if (dev->irq)
+ free_irq(dev->irq, &dev);
+ iounmap(dev->regs);
+ kfree(dev);
+}
+
+#ifdef CONFIG_PM
+static int trng_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return -ENOSYS;
+}
+
+static int trng_resume(struct platform_device *pdev)
+{
+ return -ENOSYS;
+}
+#else
+#define trng_suspend NULL
+#define trng_resume NULL
+#endif
+
+static const struct of_device_id trng_of_ids[] = {
+ {.compatible = "lsi,trng"},
+ {}
+};
+
+static struct platform_driver trng_driver = {
+ .driver = {
+ .name = "trng",
+ .owner = THIS_MODULE,
+ .of_match_table = trng_of_ids,
+ },
+ .probe = trng_probe,
+ .remove = trng_remove,
+ .suspend = trng_suspend,
+ .resume = trng_resume
+};
+
+module_platform_driver(trng_driver);
+
+MODULE_AUTHOR("Sreedevi Joshi");
+MODULE_DESCRIPTION("AXXIA TRNG- Random Number Generator driver");
+MODULE_LICENSE("GPL");
--
1.7.9.5
More information about the linux-yocto
mailing list