From: Sreedevi Joshi <sreedevi.jo...@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.jo...@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@20101a0000 {
+               compatible = "lsi,trng";
+               reg = <0x20 0x101a0000 0 0x20000>;
+               interrupts = <0 8 4>;
+       };
+
         rio0: rapidio@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

-- 
_______________________________________________
linux-yocto mailing list
linux-yocto@yoctoproject.org
https://lists.yoctoproject.org/listinfo/linux-yocto

Reply via email to