| /* |
| Added support for the AMD Geode LX RNG |
| (c) Copyright 2004-2005 Advanced Micro Devices, Inc. |
| |
| derived from |
| |
| Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) |
| (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> |
| |
| derived from |
| |
| Hardware driver for the AMD 768 Random Number Generator (RNG) |
| (c) Copyright 2001 Red Hat Inc <alan@redhat.com> |
| |
| derived from |
| |
| 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> |
| |
| Please read Documentation/hw_random.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> |
| |
| #ifdef __i386__ |
| #include <asm/msr.h> |
| #include <asm/cpufeature.h> |
| #endif |
| |
| #include <asm/io.h> |
| #include <asm/uaccess.h> |
| |
| |
| /* |
| * core module and version information |
| */ |
| #define RNG_VERSION "1.0.0" |
| #define RNG_MODULE_NAME "hw_random" |
| #define RNG_DRIVER_NAME RNG_MODULE_NAME " hardware driver " RNG_VERSION |
| #define PFX RNG_MODULE_NAME ": " |
| |
| |
| /* |
| * debugging macros |
| */ |
| |
| /* pr_debug() collapses to a no-op if DEBUG is not defined */ |
| #define DPRINTK(fmt, args...) pr_debug(PFX "%s: " fmt, __FUNCTION__ , ## args) |
| |
| |
| #undef RNG_NDEBUG /* define to enable lightweight runtime checks */ |
| #ifdef RNG_NDEBUG |
| #define assert(expr) \ |
| if(!(expr)) { \ |
| printk(KERN_DEBUG PFX "Assertion failed! %s,%s,%s," \ |
| "line=%d\n", #expr, __FILE__, __FUNCTION__, __LINE__); \ |
| } |
| #else |
| #define assert(expr) |
| #endif |
| |
| #define RNG_MISCDEV_MINOR 183 /* official */ |
| |
| static int rng_dev_open (struct inode *inode, struct file *filp); |
| static ssize_t rng_dev_read (struct file *filp, char __user *buf, size_t size, |
| loff_t * offp); |
| |
| static int __init intel_init (struct pci_dev *dev); |
| static void intel_cleanup(void); |
| static unsigned int intel_data_present (void); |
| static u32 intel_data_read (void); |
| |
| static int __init amd_init (struct pci_dev *dev); |
| static void amd_cleanup(void); |
| static unsigned int amd_data_present (void); |
| static u32 amd_data_read (void); |
| |
| #ifdef __i386__ |
| static int __init via_init(struct pci_dev *dev); |
| static void via_cleanup(void); |
| static unsigned int via_data_present (void); |
| static u32 via_data_read (void); |
| #endif |
| |
| static int __init geode_init(struct pci_dev *dev); |
| static void geode_cleanup(void); |
| static unsigned int geode_data_present (void); |
| static u32 geode_data_read (void); |
| |
| struct rng_operations { |
| int (*init) (struct pci_dev *dev); |
| void (*cleanup) (void); |
| unsigned int (*data_present) (void); |
| u32 (*data_read) (void); |
| unsigned int n_bytes; /* number of bytes per ->data_read */ |
| }; |
| static struct rng_operations *rng_ops; |
| |
| static struct file_operations rng_chrdev_ops = { |
| .owner = THIS_MODULE, |
| .open = rng_dev_open, |
| .read = rng_dev_read, |
| }; |
| |
| |
| static struct miscdevice rng_miscdev = { |
| RNG_MISCDEV_MINOR, |
| RNG_MODULE_NAME, |
| &rng_chrdev_ops, |
| }; |
| |
| enum { |
| rng_hw_none, |
| rng_hw_intel, |
| rng_hw_amd, |
| rng_hw_via, |
| rng_hw_geode, |
| }; |
| |
| static struct rng_operations rng_vendor_ops[] = { |
| /* rng_hw_none */ |
| { }, |
| |
| /* rng_hw_intel */ |
| { intel_init, intel_cleanup, intel_data_present, |
| intel_data_read, 1 }, |
| |
| /* rng_hw_amd */ |
| { amd_init, amd_cleanup, amd_data_present, amd_data_read, 4 }, |
| |
| #ifdef __i386__ |
| /* rng_hw_via */ |
| { via_init, via_cleanup, via_data_present, via_data_read, 1 }, |
| #endif |
| |
| /* rng_hw_geode */ |
| { geode_init, geode_cleanup, geode_data_present, geode_data_read, 4 } |
| }; |
| |
| /* |
| * 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[] = { |
| { 0x1022, 0x7443, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_amd }, |
| { 0x1022, 0x746b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_amd }, |
| |
| { 0x8086, 0x2418, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel }, |
| { 0x8086, 0x2428, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel }, |
| { 0x8086, 0x2448, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel }, |
| { 0x8086, 0x244e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel }, |
| { 0x8086, 0x245e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel }, |
| |
| { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LX_AES, |
| PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_geode }, |
| |
| { 0, }, /* terminate list */ |
| }; |
| MODULE_DEVICE_TABLE (pci, rng_pci_tbl); |
| |
| |
| /*********************************************************************** |
| * |
| * Intel RNG operations |
| * |
| */ |
| |
| /* |
| * RNG registers (offsets from rng_mem) |
| */ |
| #define INTEL_RNG_HW_STATUS 0 |
| #define INTEL_RNG_PRESENT 0x40 |
| #define INTEL_RNG_ENABLED 0x01 |
| #define INTEL_RNG_STATUS 1 |
| #define INTEL_RNG_DATA_PRESENT 0x01 |
| #define INTEL_RNG_DATA 2 |
| |
| /* |
| * Magic address at which Intel PCI bridges locate the RNG |
| */ |
| #define INTEL_RNG_ADDR 0xFFBC015F |
| #define INTEL_RNG_ADDR_LEN 3 |
| |
| /* token to our ioremap'd RNG register area */ |
| static void __iomem *rng_mem; |
| |
| static inline u8 intel_hwstatus (void) |
| { |
| assert (rng_mem != NULL); |
| return readb (rng_mem + INTEL_RNG_HW_STATUS); |
| } |
| |
| static inline u8 intel_hwstatus_set (u8 hw_status) |
| { |
| assert (rng_mem != NULL); |
| writeb (hw_status, rng_mem + INTEL_RNG_HW_STATUS); |
| return intel_hwstatus (); |
| } |
| |
| static unsigned int intel_data_present(void) |
| { |
| assert (rng_mem != NULL); |
| |
| return (readb (rng_mem + INTEL_RNG_STATUS) & INTEL_RNG_DATA_PRESENT) ? |
| 1 : 0; |
| } |
| |
| static u32 intel_data_read(void) |
| { |
| assert (rng_mem != NULL); |
| |
| return readb (rng_mem + INTEL_RNG_DATA); |
| } |
| |
| static int __init intel_init (struct pci_dev *dev) |
| { |
| int rc; |
| u8 hw_status; |
| |
| DPRINTK ("ENTER\n"); |
| |
| rng_mem = ioremap (INTEL_RNG_ADDR, INTEL_RNG_ADDR_LEN); |
| if (rng_mem == NULL) { |
| printk (KERN_ERR PFX "cannot ioremap RNG Memory\n"); |
| rc = -EBUSY; |
| goto err_out; |
| } |
| |
| /* Check for Intel 82802 */ |
| hw_status = intel_hwstatus (); |
| if ((hw_status & INTEL_RNG_PRESENT) == 0) { |
| printk (KERN_ERR PFX "RNG not detected\n"); |
| rc = -ENODEV; |
| goto err_out_free_map; |
| } |
| |
| /* turn RNG h/w on, if it's off */ |
| if ((hw_status & INTEL_RNG_ENABLED) == 0) |
| hw_status = intel_hwstatus_set (hw_status | INTEL_RNG_ENABLED); |
| if ((hw_status & INTEL_RNG_ENABLED) == 0) { |
| printk (KERN_ERR PFX "cannot enable RNG, aborting\n"); |
| rc = -EIO; |
| goto err_out_free_map; |
| } |
| |
| DPRINTK ("EXIT, returning 0\n"); |
| return 0; |
| |
| err_out_free_map: |
| iounmap (rng_mem); |
| rng_mem = NULL; |
| err_out: |
| DPRINTK ("EXIT, returning %d\n", rc); |
| return rc; |
| } |
| |
| static void intel_cleanup(void) |
| { |
| u8 hw_status; |
| |
| hw_status = intel_hwstatus (); |
| if (hw_status & INTEL_RNG_ENABLED) |
| intel_hwstatus_set (hw_status & ~INTEL_RNG_ENABLED); |
| else |
| printk(KERN_WARNING PFX "unusual: RNG already disabled\n"); |
| iounmap(rng_mem); |
| rng_mem = NULL; |
| } |
| |
| /*********************************************************************** |
| * |
| * AMD RNG operations |
| * |
| */ |
| |
| static u32 pmbase; /* PMxx I/O base */ |
| static struct pci_dev *amd_dev; |
| |
| static unsigned int amd_data_present (void) |
| { |
| return inl(pmbase + 0xF4) & 1; |
| } |
| |
| |
| static u32 amd_data_read (void) |
| { |
| return inl(pmbase + 0xF0); |
| } |
| |
| static int __init amd_init (struct pci_dev *dev) |
| { |
| int rc; |
| u8 rnen; |
| |
| DPRINTK ("ENTER\n"); |
| |
| pci_read_config_dword(dev, 0x58, &pmbase); |
| |
| pmbase &= 0x0000FF00; |
| |
| if (pmbase == 0) |
| { |
| printk (KERN_ERR PFX "power management base not set\n"); |
| rc = -EIO; |
| goto err_out; |
| } |
| |
| pci_read_config_byte(dev, 0x40, &rnen); |
| rnen |= (1 << 7); /* RNG on */ |
| pci_write_config_byte(dev, 0x40, rnen); |
| |
| pci_read_config_byte(dev, 0x41, &rnen); |
| rnen |= (1 << 7); /* PMIO enable */ |
| pci_write_config_byte(dev, 0x41, rnen); |
| |
| pr_info( PFX "AMD768 system management I/O registers at 0x%X.\n", |
| pmbase); |
| |
| amd_dev = dev; |
| |
| DPRINTK ("EXIT, returning 0\n"); |
| return 0; |
| |
| err_out: |
| DPRINTK ("EXIT, returning %d\n", rc); |
| return rc; |
| } |
| |
| static void amd_cleanup(void) |
| { |
| u8 rnen; |
| |
| pci_read_config_byte(amd_dev, 0x40, &rnen); |
| rnen &= ~(1 << 7); /* RNG off */ |
| pci_write_config_byte(amd_dev, 0x40, rnen); |
| |
| /* FIXME: twiddle pmio, also? */ |
| } |
| |
| #ifdef __i386__ |
| /*********************************************************************** |
| * |
| * VIA RNG operations |
| * |
| */ |
| |
| enum { |
| VIA_STRFILT_CNT_SHIFT = 16, |
| VIA_STRFILT_FAIL = (1 << 15), |
| VIA_STRFILT_ENABLE = (1 << 14), |
| VIA_RAWBITS_ENABLE = (1 << 13), |
| VIA_RNG_ENABLE = (1 << 6), |
| VIA_XSTORE_CNT_MASK = 0x0F, |
| |
| VIA_RNG_CHUNK_8 = 0x00, /* 64 rand bits, 64 stored bits */ |
| VIA_RNG_CHUNK_4 = 0x01, /* 32 rand bits, 32 stored bits */ |
| VIA_RNG_CHUNK_4_MASK = 0xFFFFFFFF, |
| VIA_RNG_CHUNK_2 = 0x02, /* 16 rand bits, 32 stored bits */ |
| VIA_RNG_CHUNK_2_MASK = 0xFFFF, |
| VIA_RNG_CHUNK_1 = 0x03, /* 8 rand bits, 32 stored bits */ |
| VIA_RNG_CHUNK_1_MASK = 0xFF, |
| }; |
| |
| static u32 via_rng_datum; |
| |
| /* |
| * Investigate using the 'rep' prefix to obtain 32 bits of random data |
| * in one insn. The upside is potentially better performance. The |
| * downside is that the instruction becomes no longer atomic. Due to |
| * this, just like familiar issues with /dev/random itself, the worst |
| * case of a 'rep xstore' could potentially pause a cpu for an |
| * unreasonably long time. In practice, this condition would likely |
| * only occur when the hardware is failing. (or so we hope :)) |
| * |
| * Another possible performance boost may come from simply buffering |
| * until we have 4 bytes, thus returning a u32 at a time, |
| * instead of the current u8-at-a-time. |
| */ |
| |
| static inline u32 xstore(u32 *addr, u32 edx_in) |
| { |
| u32 eax_out; |
| |
| asm(".byte 0x0F,0xA7,0xC0 /* xstore %%edi (addr=%0) */" |
| :"=m"(*addr), "=a"(eax_out) |
| :"D"(addr), "d"(edx_in)); |
| |
| return eax_out; |
| } |
| |
| static unsigned int via_data_present(void) |
| { |
| u32 bytes_out; |
| |
| /* We choose the recommended 1-byte-per-instruction RNG rate, |
| * for greater randomness at the expense of speed. Larger |
| * values 2, 4, or 8 bytes-per-instruction yield greater |
| * speed at lesser randomness. |
| * |
| * If you change this to another VIA_CHUNK_n, you must also |
| * change the ->n_bytes values in rng_vendor_ops[] tables. |
| * VIA_CHUNK_8 requires further code changes. |
| * |
| * A copy of MSR_VIA_RNG is placed in eax_out when xstore |
| * completes. |
| */ |
| via_rng_datum = 0; /* paranoia, not really necessary */ |
| bytes_out = xstore(&via_rng_datum, VIA_RNG_CHUNK_1) & VIA_XSTORE_CNT_MASK; |
| if (bytes_out == 0) |
| return 0; |
| |
| return 1; |
| } |
| |
| static u32 via_data_read(void) |
| { |
| return via_rng_datum; |
| } |
| |
| static int __init via_init(struct pci_dev *dev) |
| { |
| u32 lo, hi, old_lo; |
| |
| /* Control the RNG via MSR. Tread lightly and pay very close |
| * close attention to values written, as the reserved fields |
| * are documented to be "undefined and unpredictable"; but it |
| * does not say to write them as zero, so I make a guess that |
| * we restore the values we find in the register. |
| */ |
| rdmsr(MSR_VIA_RNG, lo, hi); |
| |
| old_lo = lo; |
| lo &= ~(0x7f << VIA_STRFILT_CNT_SHIFT); |
| lo &= ~VIA_XSTORE_CNT_MASK; |
| lo &= ~(VIA_STRFILT_ENABLE | VIA_STRFILT_FAIL | VIA_RAWBITS_ENABLE); |
| lo |= VIA_RNG_ENABLE; |
| |
| if (lo != old_lo) |
| wrmsr(MSR_VIA_RNG, lo, hi); |
| |
| /* perhaps-unnecessary sanity check; remove after testing if |
| unneeded */ |
| rdmsr(MSR_VIA_RNG, lo, hi); |
| if ((lo & VIA_RNG_ENABLE) == 0) { |
| printk(KERN_ERR PFX "cannot enable VIA C3 RNG, aborting\n"); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static void via_cleanup(void) |
| { |
| /* do nothing */ |
| } |
| #endif |
| |
| /*********************************************************************** |
| * |
| * AMD Geode RNG operations |
| * |
| */ |
| |
| static void __iomem *geode_rng_base = NULL; |
| |
| #define GEODE_RNG_DATA_REG 0x50 |
| #define GEODE_RNG_STATUS_REG 0x54 |
| |
| static u32 geode_data_read(void) |
| { |
| u32 val; |
| |
| assert(geode_rng_base != NULL); |
| val = readl(geode_rng_base + GEODE_RNG_DATA_REG); |
| return val; |
| } |
| |
| static unsigned int geode_data_present(void) |
| { |
| u32 val; |
| |
| assert(geode_rng_base != NULL); |
| val = readl(geode_rng_base + GEODE_RNG_STATUS_REG); |
| return val; |
| } |
| |
| static void geode_cleanup(void) |
| { |
| iounmap(geode_rng_base); |
| geode_rng_base = NULL; |
| } |
| |
| static int geode_init(struct pci_dev *dev) |
| { |
| unsigned long rng_base = pci_resource_start(dev, 0); |
| |
| if (rng_base == 0) |
| return 1; |
| |
| geode_rng_base = ioremap(rng_base, 0x58); |
| |
| if (geode_rng_base == NULL) { |
| printk(KERN_ERR PFX "Cannot ioremap RNG memory\n"); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| /*********************************************************************** |
| * |
| * /dev/hwrandom character device handling (major 10, minor 183) |
| * |
| */ |
| |
| static int rng_dev_open (struct inode *inode, struct file *filp) |
| { |
| /* enforce read-only access to this chrdev */ |
| if ((filp->f_mode & FMODE_READ) == 0) |
| return -EINVAL; |
| if (filp->f_mode & FMODE_WRITE) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| |
| static ssize_t rng_dev_read (struct file *filp, char __user *buf, size_t size, |
| loff_t * offp) |
| { |
| static DEFINE_SPINLOCK(rng_lock); |
| unsigned int have_data; |
| u32 data = 0; |
| ssize_t ret = 0; |
| |
| while (size) { |
| spin_lock(&rng_lock); |
| |
| have_data = 0; |
| if (rng_ops->data_present()) { |
| data = rng_ops->data_read(); |
| have_data = rng_ops->n_bytes; |
| } |
| |
| spin_unlock (&rng_lock); |
| |
| while (have_data && size) { |
| if (put_user((u8)data, buf++)) { |
| ret = ret ? : -EFAULT; |
| break; |
| } |
| size--; |
| ret++; |
| have_data--; |
| data>>=8; |
| } |
| |
| if (filp->f_flags & O_NONBLOCK) |
| return ret ? : -EAGAIN; |
| |
| if(need_resched()) |
| schedule_timeout_interruptible(1); |
| else |
| udelay(200); /* FIXME: We could poll for 250uS ?? */ |
| |
| if (signal_pending (current)) |
| return ret ? : -ERESTARTSYS; |
| } |
| return ret; |
| } |
| |
| |
| |
| /* |
| * rng_init_one - look for and attempt to init a single RNG |
| */ |
| static int __init rng_init_one (struct pci_dev *dev) |
| { |
| int rc; |
| |
| DPRINTK ("ENTER\n"); |
| |
| assert(rng_ops != NULL); |
| |
| rc = rng_ops->init(dev); |
| if (rc) |
| goto err_out; |
| |
| rc = misc_register (&rng_miscdev); |
| if (rc) { |
| printk (KERN_ERR PFX "misc device register failed\n"); |
| goto err_out_cleanup_hw; |
| } |
| |
| DPRINTK ("EXIT, returning 0\n"); |
| return 0; |
| |
| err_out_cleanup_hw: |
| rng_ops->cleanup(); |
| err_out: |
| DPRINTK ("EXIT, returning %d\n", rc); |
| return rc; |
| } |
| |
| |
| |
| MODULE_AUTHOR("The Linux Kernel team"); |
| MODULE_DESCRIPTION("H/W 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 = NULL; |
| const struct pci_device_id *ent; |
| |
| DPRINTK ("ENTER\n"); |
| |
| /* Probe for Intel, AMD, Geode RNGs */ |
| for_each_pci_dev(pdev) { |
| ent = pci_match_id(rng_pci_tbl, pdev); |
| if (ent) { |
| rng_ops = &rng_vendor_ops[ent->driver_data]; |
| goto match; |
| } |
| } |
| |
| #ifdef __i386__ |
| /* Probe for VIA RNG */ |
| if (cpu_has_xstore) { |
| rng_ops = &rng_vendor_ops[rng_hw_via]; |
| pdev = NULL; |
| goto match; |
| } |
| #endif |
| |
| DPRINTK ("EXIT, returning -ENODEV\n"); |
| return -ENODEV; |
| |
| match: |
| rc = rng_init_one (pdev); |
| if (rc) |
| return rc; |
| |
| pr_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); |
| |
| if (rng_ops->cleanup) |
| rng_ops->cleanup(); |
| |
| DPRINTK ("EXIT\n"); |
| } |
| |
| |
| module_init (rng_init); |
| module_exit (rng_cleanup); |