• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
24 struct powernv_rng {
25 	void __iomem *regs;
26 	void __iomem *regs_real;
27 	unsigned long mask;
28 };
29 
30 static DEFINE_PER_CPU(struct powernv_rng *, powernv_rng);
31 
powernv_hwrng_present(void)32 int 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 
rng_whiten(struct powernv_rng * rng,unsigned long val)41 static 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 
powernv_get_random_real_mode(unsigned long * v)61 int 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 
powernv_get_random_darn(unsigned long * v)74 static 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 
initialise_darn(void)89 static 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 
powernv_get_random_long(unsigned long * v)106 int 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 }
118 EXPORT_SYMBOL_GPL(powernv_get_random_long);
119 
rng_init_per_cpu(struct powernv_rng * rng,struct device_node * dn)120 static __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 
rng_create(struct device_node * dn)137 static __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 
pnv_get_random_long_early(unsigned long * v)170 static 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 
pnv_rng_init(void)189 void __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 
pnv_rng_late_init(void)204 static 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 }
220 machine_subsys_initcall(powernv, pnv_rng_late_init);
221