xref: /linux/arch/powerpc/platforms/powernv/rng.c (revision c6fbb759)
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 pnv_rng {
25 	void __iomem *regs;
26 	void __iomem *regs_real;
27 	unsigned long mask;
28 };
29 
30 static DEFINE_PER_CPU(struct pnv_rng *, pnv_rng);
31 
32 static unsigned long rng_whiten(struct pnv_rng *rng, unsigned long val)
33 {
34 	unsigned long parity;
35 
36 	/* Calculate the parity of the value */
37 	asm (".machine push;   \
38 	      .machine power7; \
39 	      popcntd %0,%1;   \
40 	      .machine pop;"
41 	     : "=r" (parity) : "r" (val));
42 
43 	/* xor our value with the previous mask */
44 	val ^= rng->mask;
45 
46 	/* update the mask based on the parity of this value */
47 	rng->mask = (rng->mask << 1) | (parity & 1);
48 
49 	return val;
50 }
51 
52 static int pnv_get_random_darn(unsigned long *v)
53 {
54 	unsigned long val;
55 
56 	/* Using DARN with L=1 - 64-bit conditioned random number */
57 	asm volatile(PPC_DARN(%0, 1) : "=r"(val));
58 
59 	if (val == DARN_ERR)
60 		return 0;
61 
62 	*v = val;
63 
64 	return 1;
65 }
66 
67 static int __init initialise_darn(void)
68 {
69 	unsigned long val;
70 	int i;
71 
72 	if (!cpu_has_feature(CPU_FTR_ARCH_300))
73 		return -ENODEV;
74 
75 	for (i = 0; i < 10; i++) {
76 		if (pnv_get_random_darn(&val)) {
77 			ppc_md.get_random_seed = pnv_get_random_darn;
78 			return 0;
79 		}
80 	}
81 	return -EIO;
82 }
83 
84 int pnv_get_random_long(unsigned long *v)
85 {
86 	struct pnv_rng *rng;
87 
88 	if (mfmsr() & MSR_DR) {
89 		rng = get_cpu_var(pnv_rng);
90 		*v = rng_whiten(rng, in_be64(rng->regs));
91 		put_cpu_var(rng);
92 	} else {
93 		rng = raw_cpu_read(pnv_rng);
94 		*v = rng_whiten(rng, __raw_rm_readq(rng->regs_real));
95 	}
96 	return 1;
97 }
98 EXPORT_SYMBOL_GPL(pnv_get_random_long);
99 
100 static __init void rng_init_per_cpu(struct pnv_rng *rng,
101 				    struct device_node *dn)
102 {
103 	int chip_id, cpu;
104 
105 	chip_id = of_get_ibm_chip_id(dn);
106 	if (chip_id == -1)
107 		pr_warn("No ibm,chip-id found for %pOF.\n", dn);
108 
109 	for_each_possible_cpu(cpu) {
110 		if (per_cpu(pnv_rng, cpu) == NULL ||
111 		    cpu_to_chip_id(cpu) == chip_id) {
112 			per_cpu(pnv_rng, cpu) = rng;
113 		}
114 	}
115 }
116 
117 static __init int rng_create(struct device_node *dn)
118 {
119 	struct pnv_rng *rng;
120 	struct resource res;
121 	unsigned long val;
122 
123 	rng = kzalloc(sizeof(*rng), GFP_KERNEL);
124 	if (!rng)
125 		return -ENOMEM;
126 
127 	if (of_address_to_resource(dn, 0, &res)) {
128 		kfree(rng);
129 		return -ENXIO;
130 	}
131 
132 	rng->regs_real = (void __iomem *)res.start;
133 
134 	rng->regs = of_iomap(dn, 0);
135 	if (!rng->regs) {
136 		kfree(rng);
137 		return -ENXIO;
138 	}
139 
140 	val = in_be64(rng->regs);
141 	rng->mask = val;
142 
143 	rng_init_per_cpu(rng, dn);
144 
145 	ppc_md.get_random_seed = pnv_get_random_long;
146 
147 	return 0;
148 }
149 
150 static int __init pnv_get_random_long_early(unsigned long *v)
151 {
152 	struct device_node *dn;
153 
154 	if (!slab_is_available())
155 		return 0;
156 
157 	if (cmpxchg(&ppc_md.get_random_seed, pnv_get_random_long_early,
158 		    NULL) != pnv_get_random_long_early)
159 		return 0;
160 
161 	for_each_compatible_node(dn, NULL, "ibm,power-rng")
162 		rng_create(dn);
163 
164 	if (!ppc_md.get_random_seed)
165 		return 0;
166 	return ppc_md.get_random_seed(v);
167 }
168 
169 void __init pnv_rng_init(void)
170 {
171 	struct device_node *dn;
172 
173 	/* Prefer darn over the rest. */
174 	if (!initialise_darn())
175 		return;
176 
177 	dn = of_find_compatible_node(NULL, NULL, "ibm,power-rng");
178 	if (dn)
179 		ppc_md.get_random_seed = pnv_get_random_long_early;
180 
181 	of_node_put(dn);
182 }
183 
184 static int __init pnv_rng_late_init(void)
185 {
186 	struct device_node *dn;
187 	unsigned long v;
188 
189 	/* In case it wasn't called during init for some other reason. */
190 	if (ppc_md.get_random_seed == pnv_get_random_long_early)
191 		pnv_get_random_long_early(&v);
192 
193 	if (ppc_md.get_random_seed == pnv_get_random_long) {
194 		for_each_compatible_node(dn, NULL, "ibm,power-rng")
195 			of_platform_device_create(dn, NULL, NULL);
196 	}
197 
198 	return 0;
199 }
200 machine_subsys_initcall(powernv, pnv_rng_late_init);
201