1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2023, Arm Ltd.
4 *
5 * Use the (optional) ARMv8.5 RNDR register to provide random numbers to
6 * U-Boot's UCLASS_RNG users.
7 * Detection is done at runtime using the CPU ID registers.
8 */
9
10#define LOG_CATEGORY UCLASS_RNG
11
12#include <dm.h>
13#include <rng.h>
14#include <asm/system.h>
15#include <linux/kernel.h>
16
17#define DRIVER_NAME	"arm-rndr"
18
19static bool cpu_has_rndr(void)
20{
21	uint64_t reg;
22
23	__asm__ volatile("mrs %0, ID_AA64ISAR0_EL1\n" : "=r" (reg));
24	return !!(reg & ID_AA64ISAR0_EL1_RNDR);
25}
26
27/*
28 * The system register name is RNDR, but this isn't widely known among older
29 * toolchains, and also triggers errors because of it being an architecture
30 * extension. Since we check the availability of the register before, it's
31 * fine to use here, though.
32 */
33#define RNDR	"S3_3_C2_C4_0"
34
35static uint64_t read_rndr(void)
36{
37	uint64_t reg;
38
39	__asm__ volatile("mrs %0, " RNDR "\n" : "=r" (reg));
40
41	return reg;
42}
43
44static int arm_rndr_read(struct udevice *dev, void *data, size_t len)
45{
46	uint64_t random;
47
48	while (len) {
49		int tocopy = min(sizeof(uint64_t), len);
50
51		random = read_rndr();
52		memcpy(data, &random, tocopy);
53		len -= tocopy;
54		data += tocopy;
55	}
56
57	return 0;
58}
59
60static const struct dm_rng_ops arm_rndr_ops = {
61	.read = arm_rndr_read,
62};
63
64static int arm_rndr_bind(struct udevice *dev)
65{
66	if (!cpu_has_rndr())
67		return -ENOENT;
68
69	return 0;
70}
71
72U_BOOT_DRIVER(arm_rndr) = {
73	.name = DRIVER_NAME,
74	.id = UCLASS_RNG,
75	.ops = &arm_rndr_ops,
76	.bind = arm_rndr_bind,
77};
78
79U_BOOT_DRVINFO(cpu_arm_rndr) = {
80	.name = DRIVER_NAME,
81};
82