Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | // SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2020 Xiphera Ltd. */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/err.h> #include <linux/io.h> #include <linux/hw_random.h> #include <linux/platform_device.h> #include <linux/delay.h> #define CONTROL_REG 0x00000000 #define STATUS_REG 0x00000004 #define RAND_REG 0x00000000 #define HOST_TO_TRNG_RESET 0x00000001 #define HOST_TO_TRNG_RELEASE_RESET 0x00000002 #define HOST_TO_TRNG_ENABLE 0x80000000 #define HOST_TO_TRNG_ZEROIZE 0x80000004 #define HOST_TO_TRNG_ACK_ZEROIZE 0x80000008 #define HOST_TO_TRNG_READ 0x8000000F /* trng statuses */ #define TRNG_ACK_RESET 0x000000AC #define TRNG_SUCCESSFUL_STARTUP 0x00000057 #define TRNG_FAILED_STARTUP 0x000000FA #define TRNG_NEW_RAND_AVAILABLE 0x000000ED struct xiphera_trng { void __iomem *mem; struct hwrng rng; }; static int xiphera_trng_read(struct hwrng *rng, void *buf, size_t max, bool wait) { struct xiphera_trng *trng = container_of(rng, struct xiphera_trng, rng); int ret = 0; while (max >= sizeof(u32)) { /* check for data */ if (readl(trng->mem + STATUS_REG) == TRNG_NEW_RAND_AVAILABLE) { *(u32 *)buf = readl(trng->mem + RAND_REG); /* * Inform the trng of the read * and re-enable it to produce a new random number */ writel(HOST_TO_TRNG_READ, trng->mem + CONTROL_REG); writel(HOST_TO_TRNG_ENABLE, trng->mem + CONTROL_REG); ret += sizeof(u32); buf += sizeof(u32); max -= sizeof(u32); } else { break; } } return ret; } static int xiphera_trng_probe(struct platform_device *pdev) { int ret; struct xiphera_trng *trng; struct device *dev = &pdev->dev; trng = devm_kzalloc(dev, sizeof(*trng), GFP_KERNEL); if (!trng) return -ENOMEM; trng->mem = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(trng->mem)) return PTR_ERR(trng->mem); /* * the trng needs to be reset first which might not happen in time, * hence we incorporate a small delay to ensure proper behaviour */ writel(HOST_TO_TRNG_RESET, trng->mem + CONTROL_REG); usleep_range(100, 200); if (readl(trng->mem + STATUS_REG) != TRNG_ACK_RESET) { /* * there is a small chance the trng is just not ready yet, * so we try one more time. If the second time fails, we give up */ usleep_range(100, 200); if (readl(trng->mem + STATUS_REG) != TRNG_ACK_RESET) { dev_err(dev, "failed to reset the trng ip\n"); return -ENODEV; } } /* * once again, to ensure proper behaviour we sleep * for a while after zeroizing the trng */ writel(HOST_TO_TRNG_RELEASE_RESET, trng->mem + CONTROL_REG); writel(HOST_TO_TRNG_ENABLE, trng->mem + CONTROL_REG); writel(HOST_TO_TRNG_ZEROIZE, trng->mem + CONTROL_REG); msleep(20); if (readl(trng->mem + STATUS_REG) != TRNG_SUCCESSFUL_STARTUP) { /* diagnose the reason for the failure */ if (readl(trng->mem + STATUS_REG) == TRNG_FAILED_STARTUP) { dev_err(dev, "trng ip startup-tests failed\n"); return -ENODEV; } dev_err(dev, "startup-tests yielded no response\n"); return -ENODEV; } writel(HOST_TO_TRNG_ACK_ZEROIZE, trng->mem + CONTROL_REG); trng->rng.name = pdev->name; trng->rng.read = xiphera_trng_read; trng->rng.quality = 900; ret = devm_hwrng_register(dev, &trng->rng); if (ret) { dev_err(dev, "failed to register rng device: %d\n", ret); return ret; } return 0; } static const struct of_device_id xiphera_trng_of_match[] = { { .compatible = "xiphera,xip8001b-trng", }, {}, }; MODULE_DEVICE_TABLE(of, xiphera_trng_of_match); static struct platform_driver xiphera_trng_driver = { .driver = { .name = "xiphera-trng", .of_match_table = xiphera_trng_of_match, }, .probe = xiphera_trng_probe, }; module_platform_driver(xiphera_trng_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Atte Tommiska"); MODULE_DESCRIPTION("Xiphera FPGA-based true random number generator driver"); |