1 /*
2  * RNG driver for AMD Geode RNGs
3  *
4  * Copyright 2008 Willy Tarreau <w@1wt.eu>
5  *
6  * Inspired by drivers/char/hw_random/geode-rng.c from kernel 2.6.25.
7  *
8  * This file is licensed under  the terms of the GNU General Public
9  * License version 2. This program is licensed "as is" without any
10  * warranty of any kind, whether express or implied.
11  */
12 
13 #include <linux/errno.h>
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/pci.h>
17 #include <linux/random.h>
18 #include <linux/sched.h>
19 #include <linux/timer.h>
20 #include <asm/io.h>
21 
22 /* We read the random generator every 50 ms */
23 #define RNG_INTERVAL (HZ/20+1)
24 
25 #define GEODE_RNG_DATA_REG   0x50
26 #define GEODE_RNG_STATUS_REG 0x54
27 
28 static struct timer_list timer;
29 static void __iomem *rng_addr;
30 static u32 rng_data[2];
31 static int nb_data;
32 
geode_rng_data_read(void)33 static u32 geode_rng_data_read(void)
34 {
35 	return readl(rng_addr + GEODE_RNG_DATA_REG);
36 }
37 
geode_rng_data_present(void)38 static int geode_rng_data_present(void)
39 {
40 	return !!(readl(rng_addr + GEODE_RNG_STATUS_REG));
41 }
42 
geode_rng_timer(unsigned long data)43 static void geode_rng_timer(unsigned long data)
44 {
45 	if (!geode_rng_data_present())
46 		goto out;
47 
48 	rng_data[nb_data] = geode_rng_data_read();
49 	nb_data++;
50 	if (nb_data > 1) {
51 		nb_data = 0;
52 		/* We have collected 64 bits. Maybe we should reduce the
53 		 * announced entropy ? At least, check for changing data
54 		 * and refuse to feed consts.
55 		 */
56 		if (rng_data[0] != rng_data[1])
57 			batch_entropy_store(rng_data[0], rng_data[1], 64);
58 	}
59  out:
60 	timer.expires = jiffies + RNG_INTERVAL;
61 	add_timer(&timer);
62 }
63 
geode_rng_init(void)64 static int __init geode_rng_init(void)
65 {
66 	struct pci_dev *pdev = NULL;
67 	unsigned long rng_base;
68 	int err = -ENODEV;
69 
70 	pdev = pci_find_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LX_AES, NULL);
71 
72 	if (pdev == NULL) {
73 		printk(KERN_ERR "geode-rng: AMD Geode RNG device not found\n");
74 		goto out;
75 	}
76 
77 	if ((err = pci_enable_device(pdev)))
78 		goto out;
79 
80 	if ((err = pci_enable_device_bars(pdev, 1)))
81 		goto out;
82 
83 	rng_base = pci_resource_start(pdev, 0);
84 	if (rng_base == 0)
85 		goto out;
86 
87 	err = -ENOMEM;
88 	rng_addr = ioremap(rng_base, 0x58);
89 	if (!rng_addr)
90 		goto out;
91 
92 	printk(KERN_INFO "AMD Geode RNG detected and enabled\n");
93 
94 	init_timer(&timer);
95 	timer.function = geode_rng_timer;
96 	timer.data = 0;
97 	timer.expires = jiffies + RNG_INTERVAL;
98 	add_timer(&timer);
99 	err = 0;
100 out:
101 	return err;
102 }
103 
geode_rng_exit(void)104 static void __exit geode_rng_exit(void)
105 {
106 	del_timer_sync(&timer);
107 	iounmap(rng_addr);
108 }
109 
110 module_init(geode_rng_init);
111 module_exit(geode_rng_exit);
112 
113 MODULE_AUTHOR("Willy Tarreau");
114 MODULE_LICENSE("GPL");
115