Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/char/hw_random/cn10k-rng.c
29269 views
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(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(rng->reg_base + RNM_PF_TRNG_DAT);
111
if (*value)
112
return true;
113
status = readq(rng->reg_base + RNM_PF_TRNG_RES);
114
if (!status && (retry_count++ > 0x1000))
115
return false;
116
} while (!status);
117
}
118
119
*value = readq(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(rng->reg_base + RNM_PF_RANDOM);
126
lower = readq(rng->reg_base + RNM_PF_RANDOM);
127
while (!(upper & 0x00000000FFFFFFFFULL))
128
upper = readq(rng->reg_base + RNM_PF_RANDOM);
129
while (!(lower & 0xFFFFFFFF00000000ULL))
130
lower = readq(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))
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))
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(&pdev->dev, sizeof(*rng), GFP_KERNEL);
183
if (!rng)
184
return -ENOMEM;
185
186
rng->pdev = pdev;
187
pci_set_drvdata(pdev, rng);
188
189
rng->reg_base = pcim_iomap(pdev, 0, 0);
190
if (!rng->reg_base)
191
return dev_err_probe(&pdev->dev, -ENOMEM, "Error while mapping CSRs, exiting\n");
192
193
rng->ops.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
194
"cn10k-rng-%s", dev_name(&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(&pdev->dev, &rng->ops);
206
if (err)
207
return dev_err_probe(&pdev->dev, err, "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 <[email protected]>");
227
MODULE_DESCRIPTION("Marvell CN10K HW RNG Driver");
228
MODULE_LICENSE("GPL v2");
229
230