1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright 2013, Michael Ellerman, IBM Corporation.
4 */
5
6#define pr_fmt(fmt)	"powernv-rng: " fmt
7
8#include <linux/kernel.h>
9#include <linux/of.h>
10#include <linux/of_address.h>
11#include <linux/of_platform.h>
12#include <linux/slab.h>
13#include <linux/smp.h>
14#include <asm/archrandom.h>
15#include <asm/cputable.h>
16#include <asm/io.h>
17#include <asm/prom.h>
18#include <asm/machdep.h>
19#include <asm/smp.h>
20#include "powernv.h"
21
22#define DARN_ERR 0xFFFFFFFFFFFFFFFFul
23
24struct powernv_rng {
25	void __iomem *regs;
26	void __iomem *regs_real;
27	unsigned long mask;
28};
29
30static DEFINE_PER_CPU(struct powernv_rng *, powernv_rng);
31
32int powernv_hwrng_present(void)
33{
34	struct powernv_rng *rng;
35
36	rng = get_cpu_var(powernv_rng);
37	put_cpu_var(rng);
38	return rng != NULL;
39}
40
41static unsigned long rng_whiten(struct powernv_rng *rng, unsigned long val)
42{
43	unsigned long parity;
44
45	/* Calculate the parity of the value */
46	asm (".machine push;   \
47	      .machine power7; \
48	      popcntd %0,%1;   \
49	      .machine pop;"
50	     : "=r" (parity) : "r" (val));
51
52	/* xor our value with the previous mask */
53	val ^= rng->mask;
54
55	/* update the mask based on the parity of this value */
56	rng->mask = (rng->mask << 1) | (parity & 1);
57
58	return val;
59}
60
61int powernv_get_random_real_mode(unsigned long *v)
62{
63	struct powernv_rng *rng;
64
65	rng = raw_cpu_read(powernv_rng);
66	if (!rng)
67		return 0;
68
69	*v = rng_whiten(rng, __raw_rm_readq(rng->regs_real));
70
71	return 1;
72}
73
74static int powernv_get_random_darn(unsigned long *v)
75{
76	unsigned long val;
77
78	/* Using DARN with L=1 - 64-bit conditioned random number */
79	asm volatile(PPC_DARN(%0, 1) : "=r"(val));
80
81	if (val == DARN_ERR)
82		return 0;
83
84	*v = val;
85
86	return 1;
87}
88
89static int initialise_darn(void)
90{
91	unsigned long val;
92	int i;
93
94	if (!cpu_has_feature(CPU_FTR_ARCH_300))
95		return -ENODEV;
96
97	for (i = 0; i < 10; i++) {
98		if (powernv_get_random_darn(&val)) {
99			ppc_md.get_random_seed = powernv_get_random_darn;
100			return 0;
101		}
102	}
103	return -EIO;
104}
105
106int powernv_get_random_long(unsigned long *v)
107{
108	struct powernv_rng *rng;
109
110	rng = get_cpu_var(powernv_rng);
111
112	*v = rng_whiten(rng, in_be64(rng->regs));
113
114	put_cpu_var(rng);
115
116	return 1;
117}
118EXPORT_SYMBOL_GPL(powernv_get_random_long);
119
120static __init void rng_init_per_cpu(struct powernv_rng *rng,
121				    struct device_node *dn)
122{
123	int chip_id, cpu;
124
125	chip_id = of_get_ibm_chip_id(dn);
126	if (chip_id == -1)
127		pr_warn("No ibm,chip-id found for %pOF.\n", dn);
128
129	for_each_possible_cpu(cpu) {
130		if (per_cpu(powernv_rng, cpu) == NULL ||
131		    cpu_to_chip_id(cpu) == chip_id) {
132			per_cpu(powernv_rng, cpu) = rng;
133		}
134	}
135}
136
137static __init int rng_create(struct device_node *dn)
138{
139	struct powernv_rng *rng;
140	struct resource res;
141	unsigned long val;
142
143	rng = kzalloc(sizeof(*rng), GFP_KERNEL);
144	if (!rng)
145		return -ENOMEM;
146
147	if (of_address_to_resource(dn, 0, &res)) {
148		kfree(rng);
149		return -ENXIO;
150	}
151
152	rng->regs_real = (void __iomem *)res.start;
153
154	rng->regs = of_iomap(dn, 0);
155	if (!rng->regs) {
156		kfree(rng);
157		return -ENXIO;
158	}
159
160	val = in_be64(rng->regs);
161	rng->mask = val;
162
163	rng_init_per_cpu(rng, dn);
164
165	ppc_md.get_random_seed = powernv_get_random_long;
166
167	return 0;
168}
169
170static int __init pnv_get_random_long_early(unsigned long *v)
171{
172	struct device_node *dn;
173
174	if (!slab_is_available())
175		return 0;
176
177	if (cmpxchg(&ppc_md.get_random_seed, pnv_get_random_long_early,
178		    NULL) != pnv_get_random_long_early)
179		return 0;
180
181	for_each_compatible_node(dn, NULL, "ibm,power-rng")
182		rng_create(dn);
183
184	if (!ppc_md.get_random_seed)
185		return 0;
186	return ppc_md.get_random_seed(v);
187}
188
189void __init pnv_rng_init(void)
190{
191	struct device_node *dn;
192
193	/* Prefer darn over the rest. */
194	if (!initialise_darn())
195		return;
196
197	dn = of_find_compatible_node(NULL, NULL, "ibm,power-rng");
198	if (dn)
199		ppc_md.get_random_seed = pnv_get_random_long_early;
200
201	of_node_put(dn);
202}
203
204static int __init pnv_rng_late_init(void)
205{
206	struct device_node *dn;
207	unsigned long v;
208
209	/* In case it wasn't called during init for some other reason. */
210	if (ppc_md.get_random_seed == pnv_get_random_long_early)
211		pnv_get_random_long_early(&v);
212
213	if (ppc_md.get_random_seed == powernv_get_random_long) {
214		for_each_compatible_node(dn, NULL, "ibm,power-rng")
215			of_platform_device_create(dn, NULL, NULL);
216	}
217
218	return 0;
219}
220machine_subsys_initcall(powernv, pnv_rng_late_init);
221