aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char/hw_random/cavium-rng-vf.c
blob: 168ed8c7a5481e74ff394b36037d6515e16b7b22 (plain)
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// SPDX-License-Identifier: GPL-2.0
/*
 * Hardware Random Number Generator support for Cavium, Inc.
 * Thunder, OcteonTx/Tx2 processor families.
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) 2016 Cavium, Inc.
 */

#include <linux/hw_random.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>

#include <asm/arch_timer.h>

/* PCI device IDs */
#define	PCI_DEVID_CAVIUM_RNG_PF		0xA018
#define	PCI_DEVID_CAVIUM_RNG_VF		0xA033

#define HEALTH_STATUS_REG		0x38

/* RST device info */
#define PCI_DEVICE_ID_RST_OTX2		0xA085
#define RST_BOOT_REG			0x1600ULL
#define CLOCK_BASE_RATE			50000000ULL
#define MSEC_TO_NSEC(x)			(x * 1000000)

struct cavium_rng {
	struct hwrng ops;
	void __iomem *result;
	void __iomem *pf_regbase;
	struct pci_dev *pdev;
	u64  clock_rate;
	u64  prev_error;
	u64  prev_time;
};

static inline bool is_octeontx(struct pci_dev *pdev)
{
	if (midr_is_cpu_model_range(read_cpuid_id(), MIDR_THUNDERX_83XX,
				    MIDR_CPU_VAR_REV(0, 0),
				    MIDR_CPU_VAR_REV(3, 0)) ||
	    midr_is_cpu_model_range(read_cpuid_id(), MIDR_THUNDERX_81XX,
				    MIDR_CPU_VAR_REV(0, 0),
				    MIDR_CPU_VAR_REV(3, 0)))
		return true;

	return false;
}

static u64 rng_get_coprocessor_clkrate(void)
{
	u64 ret = CLOCK_BASE_RATE * 16; /* Assume 800Mhz as default */
	struct pci_dev *pdev;
	void __iomem *base;

	pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM,
			      PCI_DEVICE_ID_RST_OTX2, NULL);
	if (!pdev)
		goto error;

	base = pci_ioremap_bar(pdev, 0);
	if (!base)
		goto error_put_pdev;

	/* RST: PNR_MUL * 50Mhz gives clockrate */
	ret = CLOCK_BASE_RATE * ((readq(base + RST_BOOT_REG) >> 33) & 0x3F);

	iounmap(base);

error_put_pdev:
	pci_dev_put(pdev);

error:
	return ret;
}

static int check_rng_health(struct cavium_rng *rng)
{
	u64 cur_err, cur_time;
	u64 status, cycles;
	u64 time_elapsed;


	/* Skip checking health for OcteonTx */
	if (!rng->pf_regbase)
		return 0;

	status = readq(rng->pf_regbase + HEALTH_STATUS_REG);
	if (status & BIT_ULL(0)) {
		dev_err(&rng->pdev->dev, "HWRNG: Startup health test failed\n");
		return -EIO;
	}

	cycles = status >> 1;
	if (!cycles)
		return 0;

	cur_time = arch_timer_read_counter();

	/* RNM_HEALTH_STATUS[CYCLES_SINCE_HEALTH_FAILURE]
	 * Number of coprocessor cycles times 2 since the last failure.
	 * This field doesn't get cleared/updated until another failure.
	 */
	cycles = cycles / 2;
	cur_err = (cycles * 1000000000) / rng->clock_rate; /* In nanosec */

	/* Ignore errors that happenned a long time ago, these
	 * are most likely false positive errors.
	 */
	if (cur_err > MSEC_TO_NSEC(10)) {
		rng->prev_error = 0;
		rng->prev_time = 0;
		return 0;
	}

	if (rng->prev_error) {
		/* Calculate time elapsed since last error
		 * '1' tick of CNTVCT is 10ns, since it runs at 100Mhz.
		 */
		time_elapsed = (cur_time - rng->prev_time) * 10;
		time_elapsed += rng->prev_error;

		/* Check if current error is a new one or the old one itself.
		 * If error is a new one then consider there is a persistent
		 * issue with entropy, declare hardware failure.
		 */
		if (cur_err < time_elapsed) {
			dev_err(&rng->pdev->dev, "HWRNG failure detected\n");
			rng->prev_error = cur_err;
			rng->prev_time = cur_time;
			return -EIO;
		}
	}

	rng->prev_error = cur_err;
	rng->prev_time = cur_time;
	return 0;
}

/* Read data from the RNG unit */
static int cavium_rng_read(struct hwrng *rng, void *dat, size_t max, bool wait)
{
	struct cavium_rng *p = container_of(rng, struct cavium_rng, ops);
	unsigned int size = max;
	int err = 0;

	err = check_rng_health(p);
	if (err)
		return err;

	while (size >= 8) {
		*((u64 *)dat) = readq(p->result);
		size -= 8;
		dat += 8;
	}
	while (size > 0) {
		*((u8 *)dat) = readb(p->result);
		size--;
		dat++;
	}
	return max;
}

static int cavium_map_pf_regs(struct cavium_rng *rng)
{
	struct pci_dev *pdev;

	/* Health status is not supported on 83xx, skip mapping PF CSRs */
	if (is_octeontx(rng->pdev)) {
		rng->pf_regbase = NULL;
		return 0;
	}

	pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM,
			      PCI_DEVID_CAVIUM_RNG_PF, NULL);
	if (!pdev) {
		dev_err(&pdev->dev, "Cannot find RNG PF device\n");
		return -EIO;
	}

	rng->pf_regbase = ioremap(pci_resource_start(pdev, 0),
				  pci_resource_len(pdev, 0));
	if (!rng->pf_regbase) {
		dev_err(&pdev->dev, "Failed to map PF CSR region\n");
		pci_dev_put(pdev);
		return -ENOMEM;
	}

	pci_dev_put(pdev);

	/* Get co-processor clock rate */
	rng->clock_rate = rng_get_coprocessor_clkrate();

	return 0;
}

/* Map Cavium RNG to an HWRNG object */
static int cavium_rng_probe_vf(struct	pci_dev		*pdev,
			 const struct	pci_device_id	*id)
{
	struct	cavium_rng *rng;
	int	ret;

	rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
	if (!rng)
		return -ENOMEM;

	rng->pdev = pdev;

	/* Map the RNG result */
	rng->result = pcim_iomap(pdev, 0, 0);
	if (!rng->result) {
		dev_err(&pdev->dev, "Error iomap failed retrieving result.\n");
		return -ENOMEM;
	}

	rng->ops.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
				       "cavium-rng-%s", dev_name(&pdev->dev));
	if (!rng->ops.name)
		return -ENOMEM;

	rng->ops.read    = cavium_rng_read;
	rng->ops.quality = 1000;

	pci_set_drvdata(pdev, rng);

	/* Health status is available only at PF, hence map PF registers. */
	ret = cavium_map_pf_regs(rng);
	if (ret)
		return ret;

	ret = devm_hwrng_register(&pdev->dev, &rng->ops);
	if (ret) {
		dev_err(&pdev->dev, "Error registering device as HWRNG.\n");
		return ret;
	}

	return 0;
}

/* Remove the VF */
static void cavium_rng_remove_vf(struct pci_dev *pdev)
{
	struct cavium_rng *rng;

	rng = pci_get_drvdata(pdev);
	iounmap(rng->pf_regbase);
}

static const struct pci_device_id cavium_rng_vf_id_table[] = {
	{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVID_CAVIUM_RNG_VF) },
	{ 0, }
};
MODULE_DEVICE_TABLE(pci, cavium_rng_vf_id_table);

static struct pci_driver cavium_rng_vf_driver = {
	.name		= "cavium_rng_vf",
	.id_table	= cavium_rng_vf_id_table,
	.probe		= cavium_rng_probe_vf,
	.remove		= cavium_rng_remove_vf,
};
module_pci_driver(cavium_rng_vf_driver);

MODULE_AUTHOR("Omer Khaliq <okhaliq@caviumnetworks.com>");
MODULE_LICENSE("GPL v2");