Commit | Line | Data |
---|---|---|
a4da0d50 ME |
1 | /* |
2 | * Copyright 2013, Michael Ellerman, IBM Corporation. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version | |
7 | * 2 of the License, or (at your option) any later version. | |
8 | */ | |
9 | ||
10 | #define pr_fmt(fmt) "powernv-rng: " fmt | |
11 | ||
12 | #include <linux/kernel.h> | |
13 | #include <linux/of.h> | |
1400b420 | 14 | #include <linux/of_address.h> |
a4da0d50 ME |
15 | #include <linux/of_platform.h> |
16 | #include <linux/slab.h> | |
1400b420 | 17 | #include <linux/smp.h> |
a4da0d50 ME |
18 | #include <asm/archrandom.h> |
19 | #include <asm/io.h> | |
1400b420 | 20 | #include <asm/prom.h> |
a4da0d50 | 21 | #include <asm/machdep.h> |
3eb906c6 | 22 | #include <asm/smp.h> |
a4da0d50 ME |
23 | |
24 | ||
25 | struct powernv_rng { | |
26 | void __iomem *regs; | |
e928e9cb | 27 | void __iomem *regs_real; |
a4da0d50 ME |
28 | unsigned long mask; |
29 | }; | |
30 | ||
31 | static DEFINE_PER_CPU(struct powernv_rng *, powernv_rng); | |
32 | ||
33 | ||
e928e9cb ME |
34 | int powernv_hwrng_present(void) |
35 | { | |
36 | struct powernv_rng *rng; | |
37 | ||
38 | rng = get_cpu_var(powernv_rng); | |
39 | put_cpu_var(rng); | |
40 | return rng != NULL; | |
41 | } | |
42 | ||
a4da0d50 ME |
43 | static unsigned long rng_whiten(struct powernv_rng *rng, unsigned long val) |
44 | { | |
45 | unsigned long parity; | |
46 | ||
47 | /* Calculate the parity of the value */ | |
48 | asm ("popcntd %0,%1" : "=r" (parity) : "r" (val)); | |
49 | ||
50 | /* xor our value with the previous mask */ | |
51 | val ^= rng->mask; | |
52 | ||
53 | /* update the mask based on the parity of this value */ | |
54 | rng->mask = (rng->mask << 1) | (parity & 1); | |
55 | ||
56 | return val; | |
57 | } | |
58 | ||
e928e9cb ME |
59 | int powernv_get_random_real_mode(unsigned long *v) |
60 | { | |
61 | struct powernv_rng *rng; | |
62 | ||
63 | rng = raw_cpu_read(powernv_rng); | |
64 | ||
65 | *v = rng_whiten(rng, in_rm64(rng->regs_real)); | |
66 | ||
67 | return 1; | |
68 | } | |
69 | ||
a4da0d50 ME |
70 | int powernv_get_random_long(unsigned long *v) |
71 | { | |
72 | struct powernv_rng *rng; | |
73 | ||
74 | rng = get_cpu_var(powernv_rng); | |
75 | ||
76 | *v = rng_whiten(rng, in_be64(rng->regs)); | |
77 | ||
78 | put_cpu_var(rng); | |
79 | ||
80 | return 1; | |
81 | } | |
82 | EXPORT_SYMBOL_GPL(powernv_get_random_long); | |
83 | ||
84 | static __init void rng_init_per_cpu(struct powernv_rng *rng, | |
85 | struct device_node *dn) | |
86 | { | |
87 | int chip_id, cpu; | |
88 | ||
89 | chip_id = of_get_ibm_chip_id(dn); | |
90 | if (chip_id == -1) | |
91 | pr_warn("No ibm,chip-id found for %s.\n", dn->full_name); | |
92 | ||
93 | for_each_possible_cpu(cpu) { | |
94 | if (per_cpu(powernv_rng, cpu) == NULL || | |
95 | cpu_to_chip_id(cpu) == chip_id) { | |
96 | per_cpu(powernv_rng, cpu) = rng; | |
97 | } | |
98 | } | |
99 | } | |
100 | ||
101 | static __init int rng_create(struct device_node *dn) | |
102 | { | |
103 | struct powernv_rng *rng; | |
e928e9cb | 104 | struct resource res; |
a4da0d50 ME |
105 | unsigned long val; |
106 | ||
107 | rng = kzalloc(sizeof(*rng), GFP_KERNEL); | |
108 | if (!rng) | |
109 | return -ENOMEM; | |
110 | ||
e928e9cb ME |
111 | if (of_address_to_resource(dn, 0, &res)) { |
112 | kfree(rng); | |
113 | return -ENXIO; | |
114 | } | |
115 | ||
116 | rng->regs_real = (void __iomem *)res.start; | |
117 | ||
a4da0d50 ME |
118 | rng->regs = of_iomap(dn, 0); |
119 | if (!rng->regs) { | |
120 | kfree(rng); | |
121 | return -ENXIO; | |
122 | } | |
123 | ||
124 | val = in_be64(rng->regs); | |
125 | rng->mask = val; | |
126 | ||
127 | rng_init_per_cpu(rng, dn); | |
128 | ||
129 | pr_info_once("Registering arch random hook.\n"); | |
130 | ||
01c9348c | 131 | ppc_md.get_random_seed = powernv_get_random_long; |
a4da0d50 ME |
132 | |
133 | return 0; | |
134 | } | |
135 | ||
136 | static __init int rng_init(void) | |
137 | { | |
138 | struct device_node *dn; | |
139 | int rc; | |
140 | ||
141 | for_each_compatible_node(dn, NULL, "ibm,power-rng") { | |
142 | rc = rng_create(dn); | |
143 | if (rc) { | |
144 | pr_err("Failed creating rng for %s (%d).\n", | |
145 | dn->full_name, rc); | |
146 | continue; | |
147 | } | |
148 | ||
149 | /* Create devices for hwrng driver */ | |
150 | of_platform_device_create(dn, NULL, NULL); | |
151 | } | |
152 | ||
153 | return 0; | |
154 | } | |
b14726c5 | 155 | machine_subsys_initcall(powernv, rng_init); |