.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
---|
1 | 2 | /* |
---|
2 | | - * Hardware Random Number Generator support for Cavium, Inc. |
---|
3 | | - * Thunder processor family. |
---|
4 | | - * |
---|
5 | | - * This file is subject to the terms and conditions of the GNU General Public |
---|
6 | | - * License. See the file "COPYING" in the main directory of this archive |
---|
7 | | - * for more details. |
---|
| 3 | + * Hardware Random Number Generator support. |
---|
| 4 | + * Cavium Thunder, Marvell OcteonTx/Tx2 processor families. |
---|
8 | 5 | * |
---|
9 | 6 | * Copyright (C) 2016 Cavium, Inc. |
---|
10 | 7 | */ |
---|
.. | .. |
---|
15 | 12 | #include <linux/pci.h> |
---|
16 | 13 | #include <linux/pci_ids.h> |
---|
17 | 14 | |
---|
| 15 | +#include <asm/arch_timer.h> |
---|
| 16 | + |
---|
| 17 | +/* PCI device IDs */ |
---|
| 18 | +#define PCI_DEVID_CAVIUM_RNG_PF 0xA018 |
---|
| 19 | +#define PCI_DEVID_CAVIUM_RNG_VF 0xA033 |
---|
| 20 | + |
---|
| 21 | +#define HEALTH_STATUS_REG 0x38 |
---|
| 22 | + |
---|
| 23 | +/* RST device info */ |
---|
| 24 | +#define PCI_DEVICE_ID_RST_OTX2 0xA085 |
---|
| 25 | +#define RST_BOOT_REG 0x1600ULL |
---|
| 26 | +#define CLOCK_BASE_RATE 50000000ULL |
---|
| 27 | +#define MSEC_TO_NSEC(x) (x * 1000000) |
---|
| 28 | + |
---|
18 | 29 | struct cavium_rng { |
---|
19 | 30 | struct hwrng ops; |
---|
20 | 31 | void __iomem *result; |
---|
| 32 | + void __iomem *pf_regbase; |
---|
| 33 | + struct pci_dev *pdev; |
---|
| 34 | + u64 clock_rate; |
---|
| 35 | + u64 prev_error; |
---|
| 36 | + u64 prev_time; |
---|
21 | 37 | }; |
---|
| 38 | + |
---|
| 39 | +static inline bool is_octeontx(struct pci_dev *pdev) |
---|
| 40 | +{ |
---|
| 41 | + if (midr_is_cpu_model_range(read_cpuid_id(), MIDR_THUNDERX_83XX, |
---|
| 42 | + MIDR_CPU_VAR_REV(0, 0), |
---|
| 43 | + MIDR_CPU_VAR_REV(3, 0)) || |
---|
| 44 | + midr_is_cpu_model_range(read_cpuid_id(), MIDR_THUNDERX_81XX, |
---|
| 45 | + MIDR_CPU_VAR_REV(0, 0), |
---|
| 46 | + MIDR_CPU_VAR_REV(3, 0)) || |
---|
| 47 | + midr_is_cpu_model_range(read_cpuid_id(), MIDR_THUNDERX, |
---|
| 48 | + MIDR_CPU_VAR_REV(0, 0), |
---|
| 49 | + MIDR_CPU_VAR_REV(3, 0))) |
---|
| 50 | + return true; |
---|
| 51 | + |
---|
| 52 | + return false; |
---|
| 53 | +} |
---|
| 54 | + |
---|
| 55 | +static u64 rng_get_coprocessor_clkrate(void) |
---|
| 56 | +{ |
---|
| 57 | + u64 ret = CLOCK_BASE_RATE * 16; /* Assume 800Mhz as default */ |
---|
| 58 | + struct pci_dev *pdev; |
---|
| 59 | + void __iomem *base; |
---|
| 60 | + |
---|
| 61 | + pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM, |
---|
| 62 | + PCI_DEVICE_ID_RST_OTX2, NULL); |
---|
| 63 | + if (!pdev) |
---|
| 64 | + goto error; |
---|
| 65 | + |
---|
| 66 | + base = pci_ioremap_bar(pdev, 0); |
---|
| 67 | + if (!base) |
---|
| 68 | + goto error_put_pdev; |
---|
| 69 | + |
---|
| 70 | + /* RST: PNR_MUL * 50Mhz gives clockrate */ |
---|
| 71 | + ret = CLOCK_BASE_RATE * ((readq(base + RST_BOOT_REG) >> 33) & 0x3F); |
---|
| 72 | + |
---|
| 73 | + iounmap(base); |
---|
| 74 | + |
---|
| 75 | +error_put_pdev: |
---|
| 76 | + pci_dev_put(pdev); |
---|
| 77 | + |
---|
| 78 | +error: |
---|
| 79 | + return ret; |
---|
| 80 | +} |
---|
| 81 | + |
---|
| 82 | +static int check_rng_health(struct cavium_rng *rng) |
---|
| 83 | +{ |
---|
| 84 | + u64 cur_err, cur_time; |
---|
| 85 | + u64 status, cycles; |
---|
| 86 | + u64 time_elapsed; |
---|
| 87 | + |
---|
| 88 | + |
---|
| 89 | + /* Skip checking health for OcteonTx */ |
---|
| 90 | + if (!rng->pf_regbase) |
---|
| 91 | + return 0; |
---|
| 92 | + |
---|
| 93 | + status = readq(rng->pf_regbase + HEALTH_STATUS_REG); |
---|
| 94 | + if (status & BIT_ULL(0)) { |
---|
| 95 | + dev_err(&rng->pdev->dev, "HWRNG: Startup health test failed\n"); |
---|
| 96 | + return -EIO; |
---|
| 97 | + } |
---|
| 98 | + |
---|
| 99 | + cycles = status >> 1; |
---|
| 100 | + if (!cycles) |
---|
| 101 | + return 0; |
---|
| 102 | + |
---|
| 103 | + cur_time = arch_timer_read_counter(); |
---|
| 104 | + |
---|
| 105 | + /* RNM_HEALTH_STATUS[CYCLES_SINCE_HEALTH_FAILURE] |
---|
| 106 | + * Number of coprocessor cycles times 2 since the last failure. |
---|
| 107 | + * This field doesn't get cleared/updated until another failure. |
---|
| 108 | + */ |
---|
| 109 | + cycles = cycles / 2; |
---|
| 110 | + cur_err = (cycles * 1000000000) / rng->clock_rate; /* In nanosec */ |
---|
| 111 | + |
---|
| 112 | + /* Ignore errors that happenned a long time ago, these |
---|
| 113 | + * are most likely false positive errors. |
---|
| 114 | + */ |
---|
| 115 | + if (cur_err > MSEC_TO_NSEC(10)) { |
---|
| 116 | + rng->prev_error = 0; |
---|
| 117 | + rng->prev_time = 0; |
---|
| 118 | + return 0; |
---|
| 119 | + } |
---|
| 120 | + |
---|
| 121 | + if (rng->prev_error) { |
---|
| 122 | + /* Calculate time elapsed since last error |
---|
| 123 | + * '1' tick of CNTVCT is 10ns, since it runs at 100Mhz. |
---|
| 124 | + */ |
---|
| 125 | + time_elapsed = (cur_time - rng->prev_time) * 10; |
---|
| 126 | + time_elapsed += rng->prev_error; |
---|
| 127 | + |
---|
| 128 | + /* Check if current error is a new one or the old one itself. |
---|
| 129 | + * If error is a new one then consider there is a persistent |
---|
| 130 | + * issue with entropy, declare hardware failure. |
---|
| 131 | + */ |
---|
| 132 | + if (cur_err < time_elapsed) { |
---|
| 133 | + dev_err(&rng->pdev->dev, "HWRNG failure detected\n"); |
---|
| 134 | + rng->prev_error = cur_err; |
---|
| 135 | + rng->prev_time = cur_time; |
---|
| 136 | + return -EIO; |
---|
| 137 | + } |
---|
| 138 | + } |
---|
| 139 | + |
---|
| 140 | + rng->prev_error = cur_err; |
---|
| 141 | + rng->prev_time = cur_time; |
---|
| 142 | + return 0; |
---|
| 143 | +} |
---|
22 | 144 | |
---|
23 | 145 | /* Read data from the RNG unit */ |
---|
24 | 146 | static int cavium_rng_read(struct hwrng *rng, void *dat, size_t max, bool wait) |
---|
25 | 147 | { |
---|
26 | 148 | struct cavium_rng *p = container_of(rng, struct cavium_rng, ops); |
---|
27 | 149 | unsigned int size = max; |
---|
| 150 | + int err = 0; |
---|
| 151 | + |
---|
| 152 | + err = check_rng_health(p); |
---|
| 153 | + if (err) |
---|
| 154 | + return err; |
---|
28 | 155 | |
---|
29 | 156 | while (size >= 8) { |
---|
30 | 157 | *((u64 *)dat) = readq(p->result); |
---|
.. | .. |
---|
39 | 166 | return max; |
---|
40 | 167 | } |
---|
41 | 168 | |
---|
| 169 | +static int cavium_map_pf_regs(struct cavium_rng *rng) |
---|
| 170 | +{ |
---|
| 171 | + struct pci_dev *pdev; |
---|
| 172 | + |
---|
| 173 | + /* Health status is not supported on 83xx, skip mapping PF CSRs */ |
---|
| 174 | + if (is_octeontx(rng->pdev)) { |
---|
| 175 | + rng->pf_regbase = NULL; |
---|
| 176 | + return 0; |
---|
| 177 | + } |
---|
| 178 | + |
---|
| 179 | + pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM, |
---|
| 180 | + PCI_DEVID_CAVIUM_RNG_PF, NULL); |
---|
| 181 | + if (!pdev) { |
---|
| 182 | + dev_err(&pdev->dev, "Cannot find RNG PF device\n"); |
---|
| 183 | + return -EIO; |
---|
| 184 | + } |
---|
| 185 | + |
---|
| 186 | + rng->pf_regbase = ioremap(pci_resource_start(pdev, 0), |
---|
| 187 | + pci_resource_len(pdev, 0)); |
---|
| 188 | + if (!rng->pf_regbase) { |
---|
| 189 | + dev_err(&pdev->dev, "Failed to map PF CSR region\n"); |
---|
| 190 | + pci_dev_put(pdev); |
---|
| 191 | + return -ENOMEM; |
---|
| 192 | + } |
---|
| 193 | + |
---|
| 194 | + pci_dev_put(pdev); |
---|
| 195 | + |
---|
| 196 | + /* Get co-processor clock rate */ |
---|
| 197 | + rng->clock_rate = rng_get_coprocessor_clkrate(); |
---|
| 198 | + |
---|
| 199 | + return 0; |
---|
| 200 | +} |
---|
| 201 | + |
---|
42 | 202 | /* Map Cavium RNG to an HWRNG object */ |
---|
43 | 203 | static int cavium_rng_probe_vf(struct pci_dev *pdev, |
---|
44 | 204 | const struct pci_device_id *id) |
---|
.. | .. |
---|
49 | 209 | rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); |
---|
50 | 210 | if (!rng) |
---|
51 | 211 | return -ENOMEM; |
---|
| 212 | + |
---|
| 213 | + rng->pdev = pdev; |
---|
52 | 214 | |
---|
53 | 215 | /* Map the RNG result */ |
---|
54 | 216 | rng->result = pcim_iomap(pdev, 0, 0); |
---|
.. | .. |
---|
67 | 229 | |
---|
68 | 230 | pci_set_drvdata(pdev, rng); |
---|
69 | 231 | |
---|
70 | | - ret = hwrng_register(&rng->ops); |
---|
| 232 | + /* Health status is available only at PF, hence map PF registers. */ |
---|
| 233 | + ret = cavium_map_pf_regs(rng); |
---|
| 234 | + if (ret) |
---|
| 235 | + return ret; |
---|
| 236 | + |
---|
| 237 | + ret = devm_hwrng_register(&pdev->dev, &rng->ops); |
---|
71 | 238 | if (ret) { |
---|
72 | 239 | dev_err(&pdev->dev, "Error registering device as HWRNG.\n"); |
---|
73 | 240 | return ret; |
---|
.. | .. |
---|
77 | 244 | } |
---|
78 | 245 | |
---|
79 | 246 | /* Remove the VF */ |
---|
80 | | -static void cavium_rng_remove_vf(struct pci_dev *pdev) |
---|
| 247 | +static void cavium_rng_remove_vf(struct pci_dev *pdev) |
---|
81 | 248 | { |
---|
82 | 249 | struct cavium_rng *rng; |
---|
83 | 250 | |
---|
84 | 251 | rng = pci_get_drvdata(pdev); |
---|
85 | | - hwrng_unregister(&rng->ops); |
---|
| 252 | + iounmap(rng->pf_regbase); |
---|
86 | 253 | } |
---|
87 | 254 | |
---|
88 | 255 | static const struct pci_device_id cavium_rng_vf_id_table[] = { |
---|
89 | | - { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa033), 0, 0, 0}, |
---|
90 | | - {0,}, |
---|
| 256 | + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVID_CAVIUM_RNG_VF) }, |
---|
| 257 | + { 0, } |
---|
91 | 258 | }; |
---|
92 | 259 | MODULE_DEVICE_TABLE(pci, cavium_rng_vf_id_table); |
---|
93 | 260 | |
---|
.. | .. |
---|
100 | 267 | module_pci_driver(cavium_rng_vf_driver); |
---|
101 | 268 | |
---|
102 | 269 | MODULE_AUTHOR("Omer Khaliq <okhaliq@caviumnetworks.com>"); |
---|
103 | | -MODULE_LICENSE("GPL"); |
---|
| 270 | +MODULE_LICENSE("GPL v2"); |
---|