1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Marvell CN10K RVU Hardware Random Number Generator. |
3 | * |
4 | * Copyright (C) 2021 Marvell. |
5 | * |
6 | */ |
7 | |
8 | #include <linux/hw_random.h> |
9 | #include <linux/io.h> |
10 | #include <linux/module.h> |
11 | #include <linux/pci.h> |
12 | #include <linux/pci_ids.h> |
13 | #include <linux/delay.h> |
14 | |
15 | #include <linux/arm-smccc.h> |
16 | |
17 | /* CSRs */ |
18 | #define RNM_CTL_STATUS 0x000 |
19 | #define RNM_ENTROPY_STATUS 0x008 |
20 | #define RNM_CONST 0x030 |
21 | #define RNM_EBG_ENT 0x048 |
22 | #define RNM_PF_EBG_HEALTH 0x050 |
23 | #define RNM_PF_RANDOM 0x400 |
24 | #define RNM_TRNG_RESULT 0x408 |
25 | |
26 | /* Extended TRNG Read and Status Registers */ |
27 | #define RNM_PF_TRNG_DAT 0x1000 |
28 | #define RNM_PF_TRNG_RES 0x1008 |
29 | |
30 | struct cn10k_rng { |
31 | void __iomem *reg_base; |
32 | struct hwrng ops; |
33 | struct pci_dev *pdev; |
34 | /* Octeon CN10K-A A0/A1, CNF10K-A A0/A1 and CNF10K-B A0/B0 |
35 | * does not support extended TRNG registers |
36 | */ |
37 | bool extended_trng_regs; |
38 | }; |
39 | |
40 | #define PLAT_OCTEONTX_RESET_RNG_EBG_HEALTH_STATE 0xc2000b0f |
41 | |
42 | #define PCI_SUBSYS_DEVID_CN10K_A_RNG 0xB900 |
43 | #define PCI_SUBSYS_DEVID_CNF10K_A_RNG 0xBA00 |
44 | #define PCI_SUBSYS_DEVID_CNF10K_B_RNG 0xBC00 |
45 | |
46 | static bool cn10k_is_extended_trng_regs_supported(struct pci_dev *pdev) |
47 | { |
48 | /* CN10K-A A0/A1 */ |
49 | if ((pdev->subsystem_device == PCI_SUBSYS_DEVID_CN10K_A_RNG) && |
50 | (!pdev->revision || (pdev->revision & 0xff) == 0x50 || |
51 | (pdev->revision & 0xff) == 0x51)) |
52 | return false; |
53 | |
54 | /* CNF10K-A A0 */ |
55 | if ((pdev->subsystem_device == PCI_SUBSYS_DEVID_CNF10K_A_RNG) && |
56 | (!pdev->revision || (pdev->revision & 0xff) == 0x60 || |
57 | (pdev->revision & 0xff) == 0x61)) |
58 | return false; |
59 | |
60 | /* CNF10K-B A0/B0 */ |
61 | if ((pdev->subsystem_device == PCI_SUBSYS_DEVID_CNF10K_B_RNG) && |
62 | (!pdev->revision || (pdev->revision & 0xff) == 0x70 || |
63 | (pdev->revision & 0xff) == 0x74)) |
64 | return false; |
65 | |
66 | return true; |
67 | } |
68 | |
69 | static unsigned long reset_rng_health_state(struct cn10k_rng *rng) |
70 | { |
71 | struct arm_smccc_res res; |
72 | |
73 | /* Send SMC service call to reset EBG health state */ |
74 | arm_smccc_smc(PLAT_OCTEONTX_RESET_RNG_EBG_HEALTH_STATE, 0, 0, 0, 0, 0, 0, 0, &res); |
75 | return res.a0; |
76 | } |
77 | |
78 | static int check_rng_health(struct cn10k_rng *rng) |
79 | { |
80 | u64 status; |
81 | unsigned long err; |
82 | |
83 | /* Skip checking health */ |
84 | if (!rng->reg_base) |
85 | return -ENODEV; |
86 | |
87 | status = readq(addr: rng->reg_base + RNM_PF_EBG_HEALTH); |
88 | if (status & BIT_ULL(20)) { |
89 | err = reset_rng_health_state(rng); |
90 | if (err) { |
91 | dev_err(&rng->pdev->dev, "HWRNG: Health test failed (status=%llx)\n" , |
92 | status); |
93 | dev_err(&rng->pdev->dev, "HWRNG: error during reset (error=%lx)\n" , |
94 | err); |
95 | return -EIO; |
96 | } |
97 | } |
98 | return 0; |
99 | } |
100 | |
101 | /* Returns true when valid data available otherwise return false */ |
102 | static bool cn10k_read_trng(struct cn10k_rng *rng, u64 *value) |
103 | { |
104 | u16 retry_count = 0; |
105 | u64 upper, lower; |
106 | u64 status; |
107 | |
108 | if (rng->extended_trng_regs) { |
109 | do { |
110 | *value = readq(addr: rng->reg_base + RNM_PF_TRNG_DAT); |
111 | if (*value) |
112 | return true; |
113 | status = readq(addr: rng->reg_base + RNM_PF_TRNG_RES); |
114 | if (!status && (retry_count++ > 0x1000)) |
115 | return false; |
116 | } while (!status); |
117 | } |
118 | |
119 | *value = readq(addr: rng->reg_base + RNM_PF_RANDOM); |
120 | |
121 | /* HW can run out of entropy if large amount random data is read in |
122 | * quick succession. Zeros may not be real random data from HW. |
123 | */ |
124 | if (!*value) { |
125 | upper = readq(addr: rng->reg_base + RNM_PF_RANDOM); |
126 | lower = readq(addr: rng->reg_base + RNM_PF_RANDOM); |
127 | while (!(upper & 0x00000000FFFFFFFFULL)) |
128 | upper = readq(addr: rng->reg_base + RNM_PF_RANDOM); |
129 | while (!(lower & 0xFFFFFFFF00000000ULL)) |
130 | lower = readq(addr: rng->reg_base + RNM_PF_RANDOM); |
131 | |
132 | *value = (upper & 0xFFFFFFFF00000000) | (lower & 0xFFFFFFFF); |
133 | } |
134 | return true; |
135 | } |
136 | |
137 | static int cn10k_rng_read(struct hwrng *hwrng, void *data, |
138 | size_t max, bool wait) |
139 | { |
140 | struct cn10k_rng *rng = (struct cn10k_rng *)hwrng->priv; |
141 | unsigned int size; |
142 | u8 *pos = data; |
143 | int err = 0; |
144 | u64 value; |
145 | |
146 | err = check_rng_health(rng); |
147 | if (err) |
148 | return err; |
149 | |
150 | size = max; |
151 | |
152 | while (size >= 8) { |
153 | if (!cn10k_read_trng(rng, value: &value)) |
154 | goto out; |
155 | |
156 | *((u64 *)pos) = value; |
157 | size -= 8; |
158 | pos += 8; |
159 | } |
160 | |
161 | if (size > 0) { |
162 | if (!cn10k_read_trng(rng, value: &value)) |
163 | goto out; |
164 | |
165 | while (size > 0) { |
166 | *pos = (u8)value; |
167 | value >>= 8; |
168 | size--; |
169 | pos++; |
170 | } |
171 | } |
172 | |
173 | out: |
174 | return max - size; |
175 | } |
176 | |
177 | static int cn10k_rng_probe(struct pci_dev *pdev, const struct pci_device_id *id) |
178 | { |
179 | struct cn10k_rng *rng; |
180 | int err; |
181 | |
182 | rng = devm_kzalloc(dev: &pdev->dev, size: sizeof(*rng), GFP_KERNEL); |
183 | if (!rng) |
184 | return -ENOMEM; |
185 | |
186 | rng->pdev = pdev; |
187 | pci_set_drvdata(pdev, data: rng); |
188 | |
189 | rng->reg_base = pcim_iomap(pdev, bar: 0, maxlen: 0); |
190 | if (!rng->reg_base) |
191 | return dev_err_probe(dev: &pdev->dev, err: -ENOMEM, fmt: "Error while mapping CSRs, exiting\n" ); |
192 | |
193 | rng->ops.name = devm_kasprintf(dev: &pdev->dev, GFP_KERNEL, |
194 | fmt: "cn10k-rng-%s" , dev_name(dev: &pdev->dev)); |
195 | if (!rng->ops.name) |
196 | return -ENOMEM; |
197 | |
198 | rng->ops.read = cn10k_rng_read; |
199 | rng->ops.priv = (unsigned long)rng; |
200 | |
201 | rng->extended_trng_regs = cn10k_is_extended_trng_regs_supported(pdev); |
202 | |
203 | reset_rng_health_state(rng); |
204 | |
205 | err = devm_hwrng_register(dev: &pdev->dev, rng: &rng->ops); |
206 | if (err) |
207 | return dev_err_probe(dev: &pdev->dev, err, fmt: "Could not register hwrng device.\n" ); |
208 | |
209 | return 0; |
210 | } |
211 | |
212 | static const struct pci_device_id cn10k_rng_id_table[] = { |
213 | { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA098) }, /* RNG PF */ |
214 | {0,}, |
215 | }; |
216 | |
217 | MODULE_DEVICE_TABLE(pci, cn10k_rng_id_table); |
218 | |
219 | static struct pci_driver cn10k_rng_driver = { |
220 | .name = "cn10k_rng" , |
221 | .id_table = cn10k_rng_id_table, |
222 | .probe = cn10k_rng_probe, |
223 | }; |
224 | |
225 | module_pci_driver(cn10k_rng_driver); |
226 | MODULE_AUTHOR("Sunil Goutham <sgoutham@marvell.com>" ); |
227 | MODULE_DESCRIPTION("Marvell CN10K HW RNG Driver" ); |
228 | MODULE_LICENSE("GPL v2" ); |
229 | |