blob: b1a3fbc6268072c81b5d4feb6c9844520a090737 [file] [log] [blame]
/*
Hardware driver for Intel i810 Random Number Generator (RNG)
Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com>
Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com>
Driver Web site: http://sourceforge.net/projects/gkernel/
Please read Documentation/i810_rng.txt for details on use.
----------------------------------------------------------
This software may be used and distributed according to the terms
of the GNU General Public License, incorporated herein by reference.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/random.h>
#include <linux/miscdevice.h>
#include <linux/smp_lock.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h>
/*
* core module and version information
*/
#define RNG_VERSION "0.9.8"
#define RNG_MODULE_NAME "i810_rng"
#define RNG_DRIVER_NAME RNG_MODULE_NAME " hardware driver " RNG_VERSION
#define PFX RNG_MODULE_NAME ": "
/*
* debugging macros
*/
#undef RNG_DEBUG /* define to enable copious debugging info */
#ifdef RNG_DEBUG
/* note: prints function name for you */
#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args)
#else
#define DPRINTK(fmt, args...)
#endif
#undef RNG_NDEBUG /* define to disable lightweight runtime checks */
#ifdef RNG_NDEBUG
#define assert(expr)
#else
#define assert(expr) \
if(!(expr)) { \
printk( "Assertion failed! %s,%s,%s,line=%d\n", \
#expr,__FILE__,__FUNCTION__,__LINE__); \
}
#endif
/*
* RNG registers (offsets from rng_mem)
*/
#define RNG_HW_STATUS 0
#define RNG_PRESENT 0x40
#define RNG_ENABLED 0x01
#define RNG_STATUS 1
#define RNG_DATA_PRESENT 0x01
#define RNG_DATA 2
/*
* Magic address at which Intel PCI bridges locate the RNG
*/
#define RNG_ADDR 0xFFBC015F
#define RNG_ADDR_LEN 3
#define RNG_MISCDEV_MINOR 183 /* official */
/*
* various RNG status variables. they are globals
* as we only support a single RNG device
*/
static void *rng_mem; /* token to our ioremap'd RNG register area */
static struct semaphore rng_open_sem; /* Semaphore for serializing rng_open/release */
/*
* inlined helper functions for accessing RNG registers
*/
static inline u8 rng_hwstatus (void)
{
assert (rng_mem != NULL);
return readb (rng_mem + RNG_HW_STATUS);
}
static inline u8 rng_hwstatus_set (u8 hw_status)
{
assert (rng_mem != NULL);
writeb (hw_status, rng_mem + RNG_HW_STATUS);
return rng_hwstatus ();
}
static inline int rng_data_present (void)
{
assert (rng_mem != NULL);
return (readb (rng_mem + RNG_STATUS) & RNG_DATA_PRESENT) ? 1 : 0;
}
static inline int rng_data_read (void)
{
assert (rng_mem != NULL);
return readb (rng_mem + RNG_DATA);
}
/*
* rng_enable - enable the RNG hardware
*/
static int rng_enable (void)
{
int rc = 0;
u8 hw_status, new_status;
DPRINTK ("ENTER\n");
hw_status = rng_hwstatus ();
if ((hw_status & RNG_ENABLED) == 0) {
new_status = rng_hwstatus_set (hw_status | RNG_ENABLED);
if (new_status & RNG_ENABLED)
printk (KERN_INFO PFX "RNG h/w enabled\n");
else {
printk (KERN_ERR PFX "Unable to enable the RNG\n");
rc = -EIO;
}
}
DPRINTK ("EXIT, returning %d\n", rc);
return rc;
}
/*
* rng_disable - disable the RNG hardware
*/
static void rng_disable(void)
{
u8 hw_status, new_status;
DPRINTK ("ENTER\n");
hw_status = rng_hwstatus ();
if (hw_status & RNG_ENABLED) {
new_status = rng_hwstatus_set (hw_status & ~RNG_ENABLED);
if ((new_status & RNG_ENABLED) == 0)
printk (KERN_INFO PFX "RNG h/w disabled\n");
else {
printk (KERN_ERR PFX "Unable to disable the RNG\n");
}
}
DPRINTK ("EXIT\n");
}
static int rng_dev_open (struct inode *inode, struct file *filp)
{
int rc;
if ((filp->f_mode & FMODE_READ) == 0)
return -EINVAL;
if (filp->f_mode & FMODE_WRITE)
return -EINVAL;
/* wait for device to become free */
if (filp->f_flags & O_NONBLOCK) {
if (down_trylock (&rng_open_sem))
return -EAGAIN;
} else {
if (down_interruptible (&rng_open_sem))
return -ERESTARTSYS;
}
rc = rng_enable ();
if (rc) {
up (&rng_open_sem);
return rc;
}
return 0;
}
static int rng_dev_release (struct inode *inode, struct file *filp)
{
rng_disable ();
up (&rng_open_sem);
return 0;
}
static ssize_t rng_dev_read (struct file *filp, char *buf, size_t size,
loff_t * offp)
{
static spinlock_t rng_lock = SPIN_LOCK_UNLOCKED;
int have_data;
u8 data = 0;
ssize_t ret = 0;
while (size) {
spin_lock (&rng_lock);
have_data = 0;
if (rng_data_present ()) {
data = rng_data_read ();
have_data = 1;
}
spin_unlock (&rng_lock);
if (have_data) {
if (put_user (data, buf++)) {
ret = ret ? : -EFAULT;
break;
}
size--;
ret++;
}
if (filp->f_flags & O_NONBLOCK)
return ret ? : -EAGAIN;
if (current->need_resched)
{
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(1);
}
else
udelay(200);
if (signal_pending (current))
return ret ? : -ERESTARTSYS;
}
return ret;
}
static struct file_operations rng_chrdev_ops = {
owner: THIS_MODULE,
open: rng_dev_open,
release: rng_dev_release,
read: rng_dev_read,
};
static struct miscdevice rng_miscdev = {
RNG_MISCDEV_MINOR,
RNG_MODULE_NAME,
&rng_chrdev_ops,
};
/*
* rng_init_one - look for and attempt to init a single RNG
*/
static int __init rng_init_one (struct pci_dev *dev)
{
int rc;
u8 hw_status;
DPRINTK ("ENTER\n");
rc = misc_register (&rng_miscdev);
if (rc) {
printk (KERN_ERR PFX "cannot register misc device\n");
DPRINTK ("EXIT, returning %d\n", rc);
goto err_out;
}
rng_mem = ioremap (RNG_ADDR, RNG_ADDR_LEN);
if (rng_mem == NULL) {
printk (KERN_ERR PFX "cannot ioremap RNG Memory\n");
DPRINTK ("EXIT, returning -EBUSY\n");
rc = -EBUSY;
goto err_out_free_miscdev;
}
/* Check for Intel 82802 */
hw_status = rng_hwstatus ();
if ((hw_status & RNG_PRESENT) == 0) {
printk (KERN_ERR PFX "RNG not detected\n");
DPRINTK ("EXIT, returning -ENODEV\n");
rc = -ENODEV;
goto err_out_free_map;
}
/* turn RNG h/w off, if it's on */
if (hw_status & RNG_ENABLED)
hw_status = rng_hwstatus_set (hw_status & ~RNG_ENABLED);
if (hw_status & RNG_ENABLED) {
printk (KERN_ERR PFX "cannot disable RNG, aborting\n");
goto err_out_free_map;
}
DPRINTK ("EXIT, returning 0\n");
return 0;
err_out_free_map:
iounmap (rng_mem);
err_out_free_miscdev:
misc_deregister (&rng_miscdev);
err_out:
return rc;
}
/*
* Data for PCI driver interface
*
* This data only exists for exporting the supported
* PCI ids via MODULE_DEVICE_TABLE. We do not actually
* register a pci_driver, because someone else might one day
* want to register another driver on the same PCI id.
*/
static struct pci_device_id rng_pci_tbl[] __initdata = {
{ 0x8086, 0x2418, PCI_ANY_ID, PCI_ANY_ID, },
{ 0x8086, 0x2428, PCI_ANY_ID, PCI_ANY_ID, },
{ 0x8086, 0x2448, PCI_ANY_ID, PCI_ANY_ID, },
{ 0x8086, 0x244e, PCI_ANY_ID, PCI_ANY_ID, },
{ 0x8086, 0x245e, PCI_ANY_ID, PCI_ANY_ID, },
{ 0, },
};
MODULE_DEVICE_TABLE (pci, rng_pci_tbl);
MODULE_AUTHOR("Jeff Garzik, Philipp Rumpf, Matt Sottek");
MODULE_DESCRIPTION("Intel i8xx chipset Random Number Generator (RNG) driver");
MODULE_LICENSE("GPL");
/*
* rng_init - initialize RNG module
*/
static int __init rng_init (void)
{
int rc;
struct pci_dev *pdev;
DPRINTK ("ENTER\n");
init_MUTEX (&rng_open_sem);
pci_for_each_dev(pdev) {
if (pci_match_device (rng_pci_tbl, pdev) != NULL)
goto match;
}
DPRINTK ("EXIT, returning -ENODEV\n");
return -ENODEV;
match:
rc = rng_init_one (pdev);
if (rc)
return rc;
printk (KERN_INFO RNG_DRIVER_NAME " loaded\n");
DPRINTK ("EXIT, returning 0\n");
return 0;
}
/*
* rng_init - shutdown RNG module
*/
static void __exit rng_cleanup (void)
{
DPRINTK ("ENTER\n");
misc_deregister (&rng_miscdev);
iounmap (rng_mem);
DPRINTK ("EXIT\n");
}
module_init (rng_init);
module_exit (rng_cleanup);