diff options
author | Michael Buesch <mb@bu3sch.de> | 2006-06-26 03:24:59 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-06-26 12:58:19 -0400 |
commit | 844dd05fec172d98b0dacecd9b9e9f6595204c13 (patch) | |
tree | a62ebcbd314ed4be35c233eb6a5eba414493a50f /drivers/char/hw_random | |
parent | 59f5d35f83738bf07e66f8cdcff32a433df804a3 (diff) |
[PATCH] Add new generic HW RNG core
Signed-off-by: Michael Buesch <mb@bu3sch.de>
Cc: Jeff Garzik <jeff@garzik.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/char/hw_random')
-rw-r--r-- | drivers/char/hw_random/Kconfig | 11 | ||||
-rw-r--r-- | drivers/char/hw_random/Makefile | 5 | ||||
-rw-r--r-- | drivers/char/hw_random/core.c | 354 |
3 files changed, 370 insertions, 0 deletions
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig new file mode 100644 index 000000000000..fa4a245127ad --- /dev/null +++ b/drivers/char/hw_random/Kconfig | |||
@@ -0,0 +1,11 @@ | |||
1 | # | ||
2 | # Hardware Random Number Generator (RNG) configuration | ||
3 | # | ||
4 | |||
5 | config HW_RANDOM | ||
6 | bool "Hardware Random Number Generator Core support" | ||
7 | default y | ||
8 | ---help--- | ||
9 | Hardware Random Number Generator Core infrastructure. | ||
10 | |||
11 | If unsure, say Y. | ||
diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile new file mode 100644 index 000000000000..aa752af468ce --- /dev/null +++ b/drivers/char/hw_random/Makefile | |||
@@ -0,0 +1,5 @@ | |||
1 | # | ||
2 | # Makefile for HW Random Number Generator (RNG) device drivers. | ||
3 | # | ||
4 | |||
5 | obj-$(CONFIG_HW_RANDOM) += core.o | ||
diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c new file mode 100644 index 000000000000..88b026639f10 --- /dev/null +++ b/drivers/char/hw_random/core.c | |||
@@ -0,0 +1,354 @@ | |||
1 | /* | ||
2 | Added support for the AMD Geode LX RNG | ||
3 | (c) Copyright 2004-2005 Advanced Micro Devices, Inc. | ||
4 | |||
5 | derived from | ||
6 | |||
7 | Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) | ||
8 | (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> | ||
9 | |||
10 | derived from | ||
11 | |||
12 | Hardware driver for the AMD 768 Random Number Generator (RNG) | ||
13 | (c) Copyright 2001 Red Hat Inc <alan@redhat.com> | ||
14 | |||
15 | derived from | ||
16 | |||
17 | Hardware driver for Intel i810 Random Number Generator (RNG) | ||
18 | Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com> | ||
19 | Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com> | ||
20 | |||
21 | Added generic RNG API | ||
22 | Copyright 2006 Michael Buesch <mbuesch@freenet.de> | ||
23 | Copyright 2005 (c) MontaVista Software, Inc. | ||
24 | |||
25 | Please read Documentation/hw_random.txt for details on use. | ||
26 | |||
27 | ---------------------------------------------------------- | ||
28 | This software may be used and distributed according to the terms | ||
29 | of the GNU General Public License, incorporated herein by reference. | ||
30 | |||
31 | */ | ||
32 | |||
33 | |||
34 | #include <linux/device.h> | ||
35 | #include <linux/hw_random.h> | ||
36 | #include <linux/module.h> | ||
37 | #include <linux/kernel.h> | ||
38 | #include <linux/fs.h> | ||
39 | #include <linux/init.h> | ||
40 | #include <linux/miscdevice.h> | ||
41 | #include <linux/delay.h> | ||
42 | #include <asm/uaccess.h> | ||
43 | |||
44 | |||
45 | #define RNG_MODULE_NAME "hw_random" | ||
46 | #define PFX RNG_MODULE_NAME ": " | ||
47 | #define RNG_MISCDEV_MINOR 183 /* official */ | ||
48 | |||
49 | |||
50 | static struct hwrng *current_rng; | ||
51 | static LIST_HEAD(rng_list); | ||
52 | static DEFINE_MUTEX(rng_mutex); | ||
53 | |||
54 | |||
55 | static inline int hwrng_init(struct hwrng *rng) | ||
56 | { | ||
57 | if (!rng->init) | ||
58 | return 0; | ||
59 | return rng->init(rng); | ||
60 | } | ||
61 | |||
62 | static inline void hwrng_cleanup(struct hwrng *rng) | ||
63 | { | ||
64 | if (rng && rng->cleanup) | ||
65 | rng->cleanup(rng); | ||
66 | } | ||
67 | |||
68 | static inline int hwrng_data_present(struct hwrng *rng) | ||
69 | { | ||
70 | if (!rng->data_present) | ||
71 | return 1; | ||
72 | return rng->data_present(rng); | ||
73 | } | ||
74 | |||
75 | static inline int hwrng_data_read(struct hwrng *rng, u32 *data) | ||
76 | { | ||
77 | return rng->data_read(rng, data); | ||
78 | } | ||
79 | |||
80 | |||
81 | static int rng_dev_open(struct inode *inode, struct file *filp) | ||
82 | { | ||
83 | /* enforce read-only access to this chrdev */ | ||
84 | if ((filp->f_mode & FMODE_READ) == 0) | ||
85 | return -EINVAL; | ||
86 | if (filp->f_mode & FMODE_WRITE) | ||
87 | return -EINVAL; | ||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | static ssize_t rng_dev_read(struct file *filp, char __user *buf, | ||
92 | size_t size, loff_t *offp) | ||
93 | { | ||
94 | u32 data; | ||
95 | ssize_t ret = 0; | ||
96 | int i, err = 0; | ||
97 | int data_present; | ||
98 | int bytes_read; | ||
99 | |||
100 | while (size) { | ||
101 | err = -ERESTARTSYS; | ||
102 | if (mutex_lock_interruptible(&rng_mutex)) | ||
103 | goto out; | ||
104 | if (!current_rng) { | ||
105 | mutex_unlock(&rng_mutex); | ||
106 | err = -ENODEV; | ||
107 | goto out; | ||
108 | } | ||
109 | if (filp->f_flags & O_NONBLOCK) { | ||
110 | data_present = hwrng_data_present(current_rng); | ||
111 | } else { | ||
112 | /* Some RNG require some time between data_reads to gather | ||
113 | * new entropy. Poll it. | ||
114 | */ | ||
115 | for (i = 0; i < 20; i++) { | ||
116 | data_present = hwrng_data_present(current_rng); | ||
117 | if (data_present) | ||
118 | break; | ||
119 | udelay(10); | ||
120 | } | ||
121 | } | ||
122 | bytes_read = 0; | ||
123 | if (data_present) | ||
124 | bytes_read = hwrng_data_read(current_rng, &data); | ||
125 | mutex_unlock(&rng_mutex); | ||
126 | |||
127 | err = -EAGAIN; | ||
128 | if (!bytes_read && (filp->f_flags & O_NONBLOCK)) | ||
129 | goto out; | ||
130 | |||
131 | err = -EFAULT; | ||
132 | while (bytes_read && size) { | ||
133 | if (put_user((u8)data, buf++)) | ||
134 | goto out; | ||
135 | size--; | ||
136 | ret++; | ||
137 | bytes_read--; | ||
138 | data >>= 8; | ||
139 | } | ||
140 | |||
141 | if (need_resched()) | ||
142 | schedule_timeout_interruptible(1); | ||
143 | err = -ERESTARTSYS; | ||
144 | if (signal_pending(current)) | ||
145 | goto out; | ||
146 | } | ||
147 | out: | ||
148 | return ret ? : err; | ||
149 | } | ||
150 | |||
151 | |||
152 | static struct file_operations rng_chrdev_ops = { | ||
153 | .owner = THIS_MODULE, | ||
154 | .open = rng_dev_open, | ||
155 | .read = rng_dev_read, | ||
156 | }; | ||
157 | |||
158 | static struct miscdevice rng_miscdev = { | ||
159 | .minor = RNG_MISCDEV_MINOR, | ||
160 | .name = RNG_MODULE_NAME, | ||
161 | .fops = &rng_chrdev_ops, | ||
162 | }; | ||
163 | |||
164 | |||
165 | static ssize_t hwrng_attr_current_store(struct class_device *class, | ||
166 | const char *buf, size_t len) | ||
167 | { | ||
168 | int err; | ||
169 | struct hwrng *rng; | ||
170 | |||
171 | err = mutex_lock_interruptible(&rng_mutex); | ||
172 | if (err) | ||
173 | return -ERESTARTSYS; | ||
174 | err = -ENODEV; | ||
175 | list_for_each_entry(rng, &rng_list, list) { | ||
176 | if (strcmp(rng->name, buf) == 0) { | ||
177 | if (rng == current_rng) { | ||
178 | err = 0; | ||
179 | break; | ||
180 | } | ||
181 | err = hwrng_init(rng); | ||
182 | if (err) | ||
183 | break; | ||
184 | hwrng_cleanup(current_rng); | ||
185 | current_rng = rng; | ||
186 | err = 0; | ||
187 | break; | ||
188 | } | ||
189 | } | ||
190 | mutex_unlock(&rng_mutex); | ||
191 | |||
192 | return err ? : len; | ||
193 | } | ||
194 | |||
195 | static ssize_t hwrng_attr_current_show(struct class_device *class, | ||
196 | char *buf) | ||
197 | { | ||
198 | int err; | ||
199 | ssize_t ret; | ||
200 | const char *name = "none"; | ||
201 | |||
202 | err = mutex_lock_interruptible(&rng_mutex); | ||
203 | if (err) | ||
204 | return -ERESTARTSYS; | ||
205 | if (current_rng) | ||
206 | name = current_rng->name; | ||
207 | ret = snprintf(buf, PAGE_SIZE, "%s\n", name); | ||
208 | mutex_unlock(&rng_mutex); | ||
209 | |||
210 | return ret; | ||
211 | } | ||
212 | |||
213 | static ssize_t hwrng_attr_available_show(struct class_device *class, | ||
214 | char *buf) | ||
215 | { | ||
216 | int err; | ||
217 | ssize_t ret = 0; | ||
218 | struct hwrng *rng; | ||
219 | |||
220 | err = mutex_lock_interruptible(&rng_mutex); | ||
221 | if (err) | ||
222 | return -ERESTARTSYS; | ||
223 | buf[0] = '\0'; | ||
224 | list_for_each_entry(rng, &rng_list, list) { | ||
225 | strncat(buf, rng->name, PAGE_SIZE - ret - 1); | ||
226 | ret += strlen(rng->name); | ||
227 | strncat(buf, " ", PAGE_SIZE - ret - 1); | ||
228 | ret++; | ||
229 | } | ||
230 | strncat(buf, "\n", PAGE_SIZE - ret - 1); | ||
231 | ret++; | ||
232 | mutex_unlock(&rng_mutex); | ||
233 | |||
234 | return ret; | ||
235 | } | ||
236 | |||
237 | static CLASS_DEVICE_ATTR(rng_current, S_IRUGO | S_IWUSR, | ||
238 | hwrng_attr_current_show, | ||
239 | hwrng_attr_current_store); | ||
240 | static CLASS_DEVICE_ATTR(rng_available, S_IRUGO, | ||
241 | hwrng_attr_available_show, | ||
242 | NULL); | ||
243 | |||
244 | |||
245 | static void unregister_miscdev(void) | ||
246 | { | ||
247 | class_device_remove_file(rng_miscdev.class, | ||
248 | &class_device_attr_rng_available); | ||
249 | class_device_remove_file(rng_miscdev.class, | ||
250 | &class_device_attr_rng_current); | ||
251 | misc_deregister(&rng_miscdev); | ||
252 | } | ||
253 | |||
254 | static int register_miscdev(void) | ||
255 | { | ||
256 | int err; | ||
257 | |||
258 | err = misc_register(&rng_miscdev); | ||
259 | if (err) | ||
260 | goto out; | ||
261 | err = class_device_create_file(rng_miscdev.class, | ||
262 | &class_device_attr_rng_current); | ||
263 | if (err) | ||
264 | goto err_misc_dereg; | ||
265 | err = class_device_create_file(rng_miscdev.class, | ||
266 | &class_device_attr_rng_available); | ||
267 | if (err) | ||
268 | goto err_remove_current; | ||
269 | out: | ||
270 | return err; | ||
271 | |||
272 | err_remove_current: | ||
273 | class_device_remove_file(rng_miscdev.class, | ||
274 | &class_device_attr_rng_current); | ||
275 | err_misc_dereg: | ||
276 | misc_deregister(&rng_miscdev); | ||
277 | goto out; | ||
278 | } | ||
279 | |||
280 | int hwrng_register(struct hwrng *rng) | ||
281 | { | ||
282 | int must_register_misc; | ||
283 | int err = -EINVAL; | ||
284 | struct hwrng *old_rng, *tmp; | ||
285 | |||
286 | if (rng->name == NULL || | ||
287 | rng->data_read == NULL) | ||
288 | goto out; | ||
289 | |||
290 | mutex_lock(&rng_mutex); | ||
291 | |||
292 | /* Must not register two RNGs with the same name. */ | ||
293 | err = -EEXIST; | ||
294 | list_for_each_entry(tmp, &rng_list, list) { | ||
295 | if (strcmp(tmp->name, rng->name) == 0) | ||
296 | goto out_unlock; | ||
297 | } | ||
298 | |||
299 | must_register_misc = (current_rng == NULL); | ||
300 | old_rng = current_rng; | ||
301 | if (!old_rng) { | ||
302 | err = hwrng_init(rng); | ||
303 | if (err) | ||
304 | goto out_unlock; | ||
305 | current_rng = rng; | ||
306 | } | ||
307 | err = 0; | ||
308 | if (must_register_misc) { | ||
309 | err = register_miscdev(); | ||
310 | if (err) { | ||
311 | if (!old_rng) { | ||
312 | hwrng_cleanup(rng); | ||
313 | current_rng = NULL; | ||
314 | } | ||
315 | goto out_unlock; | ||
316 | } | ||
317 | } | ||
318 | INIT_LIST_HEAD(&rng->list); | ||
319 | list_add_tail(&rng->list, &rng_list); | ||
320 | out_unlock: | ||
321 | mutex_unlock(&rng_mutex); | ||
322 | out: | ||
323 | return err; | ||
324 | } | ||
325 | EXPORT_SYMBOL_GPL(hwrng_register); | ||
326 | |||
327 | void hwrng_unregister(struct hwrng *rng) | ||
328 | { | ||
329 | int err; | ||
330 | |||
331 | mutex_lock(&rng_mutex); | ||
332 | |||
333 | list_del(&rng->list); | ||
334 | if (current_rng == rng) { | ||
335 | hwrng_cleanup(rng); | ||
336 | if (list_empty(&rng_list)) { | ||
337 | current_rng = NULL; | ||
338 | } else { | ||
339 | current_rng = list_entry(rng_list.prev, struct hwrng, list); | ||
340 | err = hwrng_init(current_rng); | ||
341 | if (err) | ||
342 | current_rng = NULL; | ||
343 | } | ||
344 | } | ||
345 | if (list_empty(&rng_list)) | ||
346 | unregister_miscdev(); | ||
347 | |||
348 | mutex_unlock(&rng_mutex); | ||
349 | } | ||
350 | EXPORT_SYMBOL_GPL(hwrng_unregister); | ||
351 | |||
352 | |||
353 | MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver"); | ||
354 | MODULE_LICENSE("GPL"); | ||