Linux Audio

Check our new training course

Embedded Linux training

Mar 10-20, 2025, special US time zones
Register
Loading...
Note: File does not exist in v3.5.6.
  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
 30struct 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
 46static 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
 69static 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
 78static 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 */
102static 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
137static 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
173out:
174	return max - size;
175}
176
177static 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
212static const struct pci_device_id cn10k_rng_id_table[] = {
213	{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA098) }, /* RNG PF */
214	{0,},
215};
216
217MODULE_DEVICE_TABLE(pci, cn10k_rng_id_table);
218
219static struct pci_driver cn10k_rng_driver = {
220	.name		= "cn10k_rng",
221	.id_table	= cn10k_rng_id_table,
222	.probe		= cn10k_rng_probe,
223};
224
225module_pci_driver(cn10k_rng_driver);
226MODULE_AUTHOR("Sunil Goutham <sgoutham@marvell.com>");
227MODULE_DESCRIPTION("Marvell CN10K HW RNG Driver");
228MODULE_LICENSE("GPL v2");