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/axm5508-amarillo.dts | 4 + arch/arm/boot/dts/axm5512-amarillo.dts | 4 + arch/arm/boot/dts/axm5516-amarillo.dts | 4 + arch/arm/boot/dts/axm55xx.dtsi | 7 + drivers/char/hw_random/Kconfig | 9 + drivers/char/hw_random/Makefile | 1 + drivers/char/hw_random/axxia-rng.c | 584 ++++++++++++++++++++++++++++++++ 7 files changed, 613 insertions(+) create mode 100644 drivers/char/hw_random/axxia-rng.c diff --git a/arch/arm/boot/dts/axm5508-amarillo.dts b/arch/arm/boot/dts/axm5508-amarillo.dts index c24234a..1ba6a3c 100644 --- a/arch/arm/boot/dts/axm5508-amarillo.dts +++ b/arch/arm/boot/dts/axm5508-amarillo.dts @@ -72,6 +72,10 @@ status = "okay"; }; +&trng { + status = "okay"; +}; + &serial0 { status = "okay"; }; diff --git a/arch/arm/boot/dts/axm5512-amarillo.dts b/arch/arm/boot/dts/axm5512-amarillo.dts index 5567b80..8930fe1 100644 --- a/arch/arm/boot/dts/axm5512-amarillo.dts +++ b/arch/arm/boot/dts/axm5512-amarillo.dts @@ -72,6 +72,10 @@ status = "okay"; }; +&trng { + status = "okay"; +}; + &serial0 { status = "okay"; }; diff --git a/arch/arm/boot/dts/axm5516-amarillo.dts b/arch/arm/boot/dts/axm5516-amarillo.dts index 59eeb53..a30df3c 100644 --- a/arch/arm/boot/dts/axm5516-amarillo.dts +++ b/arch/arm/boot/dts/axm5516-amarillo.dts @@ -76,6 +76,10 @@ status = "okay"; }; +&trng { + status = "okay"; +}; + &serial0 { status = "okay"; }; diff --git a/arch/arm/boot/dts/axm55xx.dtsi b/arch/arm/boot/dts/axm55xx.dtsi index 72ed71f..abfbd63 100644 --- a/arch/arm/boot/dts/axm55xx.dtsi +++ b/arch/arm/boot/dts/axm55xx.dtsi @@ -329,6 +329,13 @@ status = "disabled"; }; + trng: trng@20101a0000 { + compatible = "lsi,trng"; + reg = <0x20 0x101a0000 0 0x20000>; + interrupts = <0 8 4>; + status = "disabled"; + }; + nca@2020100000 { compatible = "lsi,nca"; reg = <0x20 0x20100000 0 0x20000>; diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index 2f2b084..9f13423 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -20,6 +20,15 @@ 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 3ae7755..f9980cb 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..b752e6b --- /dev/null +++ b/drivers/char/hw_random/axxia-rng.c @@ -0,0 +1,584 @@ +/* + * 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; + +struct ncp_trng_axm55xx_reg_sts_t { +#ifdef NCP_BIG_ENDIAN + unsigned data_cnt:16; + unsigned prediction_resistance:1; + unsigned continuous_test_off:1; + unsigned reseed_required:1; + unsigned ro_entropy:1; + unsigned rdy:1; + unsigned err:1; + unsigned ro_test_in_progress:1; + unsigned rng_test_in_progress:1; + unsigned continuous_test_failed:1; + unsigned ro_test_failed:1; + unsigned rng_test_failed:1; + unsigned data_valid:1; + unsigned standby:1; + unsigned cmd:3; +#else + unsigned cmd:3; + unsigned standby:1; + unsigned data_valid:1; + unsigned rng_test_failed:1; + unsigned ro_test_failed:1; + unsigned continuous_test_failed:1; + unsigned rng_test_in_progress:1; + unsigned ro_test_in_progress:1; + unsigned err:1; + unsigned rdy:1; + unsigned ro_entropy:1; + unsigned reseed_required:1; + unsigned continuous_test_off:1; + unsigned prediction_resistance:1; + unsigned data_cnt:16; +#endif +} ncp_trng_axm55xx_reg_sts_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; + u32 rsts; + struct ncp_trng_axm55xx_reg_sts_t 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 = *((struct ncp_trng_axm55xx_reg_sts_t *) + &(dev->regs->sts)); + + if (sts.err) + return -EIO; + else if (sts.data_valid) + dev->words_avail = sts.data_cnt; + else if (sts.cmd != 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