2006-06-26 00:24:59 -07:00
/*
* RNG driver for Intel RNGs
*
* Copyright 2005 ( c ) MontaVista Software , Inc .
*
* with the majority of the code coming 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 >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
2007-05-08 00:25:08 -07:00
# include <linux/hw_random.h>
2006-06-26 00:24:59 -07:00
# include <linux/kernel.h>
2007-05-08 00:25:08 -07:00
# include <linux/module.h>
2006-06-26 00:24:59 -07:00
# include <linux/pci.h>
2007-05-08 00:25:08 -07:00
# include <linux/stop_machine.h>
2007-11-21 12:24:45 +08:00
# include <linux/delay.h>
2006-06-26 00:24:59 -07:00
# include <asm/io.h>
# define PFX KBUILD_MODNAME ": "
/*
* RNG registers
*/
# 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
2006-09-29 01:59:42 -07:00
/*
* LPC bridge PCI config space registers
*/
# define FWH_DEC_EN1_REG_OLD 0xe3
# define FWH_DEC_EN1_REG_NEW 0xd9 /* high byte of 16-bit register */
# define FWH_F8_EN_MASK 0x80
# define BIOS_CNTL_REG_OLD 0x4e
# define BIOS_CNTL_REG_NEW 0xdc
# define BIOS_CNTL_WRITE_ENABLE_MASK 0x01
# define BIOS_CNTL_LOCK_ENABLE_MASK 0x02
/*
* Magic address at which Intel Firmware Hubs get accessed
*/
# define INTEL_FWH_ADDR 0xffff0000
# define INTEL_FWH_ADDR_LEN 2
/*
* Intel Firmware Hub command codes ( write to any address inside the device )
*/
# define INTEL_FWH_RESET_CMD 0xff /* aka READ_ARRAY */
# define INTEL_FWH_READ_ID_CMD 0x90
/*
* Intel Firmware Hub Read ID command result addresses
*/
# define INTEL_FWH_MANUFACTURER_CODE_ADDRESS 0x000000
# define INTEL_FWH_DEVICE_CODE_ADDRESS 0x000001
/*
* Intel Firmware Hub Read ID command result values
*/
# define INTEL_FWH_MANUFACTURER_CODE 0x89
# define INTEL_FWH_DEVICE_CODE_8M 0xac
# define INTEL_FWH_DEVICE_CODE_4M 0xad
2006-06-26 00:24:59 -07:00
/*
* 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 const struct pci_device_id pci_tbl [ ] = {
2006-09-29 01:59:42 -07:00
/* AA
2007-02-12 00:51:48 -08:00
{ PCI_DEVICE ( 0x8086 , 0x2418 ) } , */
{ PCI_DEVICE ( 0x8086 , 0x2410 ) } , /* AA */
2006-09-29 01:59:42 -07:00
/* AB
2007-02-12 00:51:48 -08:00
{ PCI_DEVICE ( 0x8086 , 0x2428 ) } , */
{ PCI_DEVICE ( 0x8086 , 0x2420 ) } , /* AB */
2006-09-29 01:59:42 -07:00
/* ??
2007-02-12 00:51:48 -08:00
{ PCI_DEVICE ( 0x8086 , 0x2430 ) } , */
2006-09-29 01:59:42 -07:00
/* BAM, CAM, DBM, FBM, GxM
2007-02-12 00:51:48 -08:00
{ PCI_DEVICE ( 0x8086 , 0x2448 ) } , */
{ PCI_DEVICE ( 0x8086 , 0x244c ) } , /* BAM */
{ PCI_DEVICE ( 0x8086 , 0x248c ) } , /* CAM */
{ PCI_DEVICE ( 0x8086 , 0x24cc ) } , /* DBM */
{ PCI_DEVICE ( 0x8086 , 0x2641 ) } , /* FBM */
{ PCI_DEVICE ( 0x8086 , 0x27b9 ) } , /* GxM */
{ PCI_DEVICE ( 0x8086 , 0x27bd ) } , /* GxM DH */
2006-09-29 01:59:42 -07:00
/* BA, CA, DB, Ex, 6300, Fx, 631x/632x, Gx
2007-02-12 00:51:48 -08:00
{ PCI_DEVICE ( 0x8086 , 0x244e ) } , */
{ PCI_DEVICE ( 0x8086 , 0x2440 ) } , /* BA */
{ PCI_DEVICE ( 0x8086 , 0x2480 ) } , /* CA */
{ PCI_DEVICE ( 0x8086 , 0x24c0 ) } , /* DB */
{ PCI_DEVICE ( 0x8086 , 0x24d0 ) } , /* Ex */
{ PCI_DEVICE ( 0x8086 , 0x25a1 ) } , /* 6300 */
{ PCI_DEVICE ( 0x8086 , 0x2640 ) } , /* Fx */
{ PCI_DEVICE ( 0x8086 , 0x2670 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2671 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2672 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2673 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2674 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2675 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2676 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2677 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2678 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x2679 ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x267a ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x267b ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x267c ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x267d ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x267e ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x267f ) } , /* 631x/632x */
{ PCI_DEVICE ( 0x8086 , 0x27b8 ) } , /* Gx */
2006-09-29 01:59:42 -07:00
/* E
2007-02-12 00:51:48 -08:00
{ PCI_DEVICE ( 0x8086 , 0x245e ) } , */
{ PCI_DEVICE ( 0x8086 , 0x2450 ) } , /* E */
2006-06-26 00:24:59 -07:00
{ 0 , } , /* terminate list */
} ;
MODULE_DEVICE_TABLE ( pci , pci_tbl ) ;
2007-01-10 23:15:41 -08:00
static __initdata int no_fwh_detect ;
module_param ( no_fwh_detect , int , 0 ) ;
MODULE_PARM_DESC ( no_fwh_detect , " Skip FWH detection: \n "
" positive value - skip if FWH space locked read-only \n "
" negative value - skip always " ) ;
2006-06-26 00:24:59 -07:00
static inline u8 hwstatus_get ( void __iomem * mem )
{
return readb ( mem + INTEL_RNG_HW_STATUS ) ;
}
static inline u8 hwstatus_set ( void __iomem * mem ,
u8 hw_status )
{
writeb ( hw_status , mem + INTEL_RNG_HW_STATUS ) ;
return hwstatus_get ( mem ) ;
}
2007-11-21 12:24:45 +08:00
static int intel_rng_data_present ( struct hwrng * rng , int wait )
2006-06-26 00:24:59 -07:00
{
void __iomem * mem = ( void __iomem * ) rng - > priv ;
2007-11-21 12:24:45 +08:00
int data , i ;
for ( i = 0 ; i < 20 ; i + + ) {
data = ! ! ( readb ( mem + INTEL_RNG_STATUS ) &
INTEL_RNG_DATA_PRESENT ) ;
if ( data | | ! wait )
break ;
udelay ( 10 ) ;
}
return data ;
2006-06-26 00:24:59 -07:00
}
static int intel_rng_data_read ( struct hwrng * rng , u32 * data )
{
void __iomem * mem = ( void __iomem * ) rng - > priv ;
* data = readb ( mem + INTEL_RNG_DATA ) ;
return 1 ;
}
static int intel_rng_init ( struct hwrng * rng )
{
void __iomem * mem = ( void __iomem * ) rng - > priv ;
u8 hw_status ;
int err = - EIO ;
hw_status = hwstatus_get ( mem ) ;
/* turn RNG h/w on, if it's off */
if ( ( hw_status & INTEL_RNG_ENABLED ) = = 0 )
hw_status = hwstatus_set ( mem , hw_status | INTEL_RNG_ENABLED ) ;
if ( ( hw_status & INTEL_RNG_ENABLED ) = = 0 ) {
printk ( KERN_ERR PFX " cannot enable RNG, aborting \n " ) ;
goto out ;
}
err = 0 ;
out :
return err ;
}
static void intel_rng_cleanup ( struct hwrng * rng )
{
void __iomem * mem = ( void __iomem * ) rng - > priv ;
u8 hw_status ;
hw_status = hwstatus_get ( mem ) ;
if ( hw_status & INTEL_RNG_ENABLED )
hwstatus_set ( mem , hw_status & ~ INTEL_RNG_ENABLED ) ;
else
printk ( KERN_WARNING PFX " unusual: RNG already disabled \n " ) ;
}
static struct hwrng intel_rng = {
. name = " intel " ,
. init = intel_rng_init ,
. cleanup = intel_rng_cleanup ,
. data_present = intel_rng_data_present ,
. data_read = intel_rng_data_read ,
} ;
2007-05-08 00:25:08 -07:00
struct intel_rng_hw {
struct pci_dev * dev ;
void __iomem * mem ;
u8 bios_cntl_off ;
u8 bios_cntl_val ;
u8 fwh_dec_en1_off ;
u8 fwh_dec_en1_val ;
} ;
2006-06-26 00:24:59 -07:00
2007-05-08 00:25:08 -07:00
static int __init intel_rng_hw_init ( void * _intel_rng_hw )
{
struct intel_rng_hw * intel_rng_hw = _intel_rng_hw ;
u8 mfc , dvc ;
/* interrupts disabled in stop_machine_run call */
if ( ! ( intel_rng_hw - > fwh_dec_en1_val & FWH_F8_EN_MASK ) )
pci_write_config_byte ( intel_rng_hw - > dev ,
intel_rng_hw - > fwh_dec_en1_off ,
intel_rng_hw - > fwh_dec_en1_val |
FWH_F8_EN_MASK ) ;
if ( ! ( intel_rng_hw - > bios_cntl_val & BIOS_CNTL_WRITE_ENABLE_MASK ) )
pci_write_config_byte ( intel_rng_hw - > dev ,
intel_rng_hw - > bios_cntl_off ,
intel_rng_hw - > bios_cntl_val |
BIOS_CNTL_WRITE_ENABLE_MASK ) ;
writeb ( INTEL_FWH_RESET_CMD , intel_rng_hw - > mem ) ;
writeb ( INTEL_FWH_READ_ID_CMD , intel_rng_hw - > mem ) ;
mfc = readb ( intel_rng_hw - > mem + INTEL_FWH_MANUFACTURER_CODE_ADDRESS ) ;
dvc = readb ( intel_rng_hw - > mem + INTEL_FWH_DEVICE_CODE_ADDRESS ) ;
writeb ( INTEL_FWH_RESET_CMD , intel_rng_hw - > mem ) ;
if ( ! ( intel_rng_hw - > bios_cntl_val &
( BIOS_CNTL_LOCK_ENABLE_MASK | BIOS_CNTL_WRITE_ENABLE_MASK ) ) )
pci_write_config_byte ( intel_rng_hw - > dev ,
intel_rng_hw - > bios_cntl_off ,
intel_rng_hw - > bios_cntl_val ) ;
if ( ! ( intel_rng_hw - > fwh_dec_en1_val & FWH_F8_EN_MASK ) )
pci_write_config_byte ( intel_rng_hw - > dev ,
intel_rng_hw - > fwh_dec_en1_off ,
intel_rng_hw - > fwh_dec_en1_val ) ;
2006-09-29 01:59:42 -07:00
2007-05-08 00:25:08 -07:00
if ( mfc ! = INTEL_FWH_MANUFACTURER_CODE | |
( dvc ! = INTEL_FWH_DEVICE_CODE_8M & &
dvc ! = INTEL_FWH_DEVICE_CODE_4M ) ) {
printk ( KERN_ERR PFX " FWH not detected \n " ) ;
return - ENODEV ;
}
return 0 ;
}
static int __init intel_init_hw_struct ( struct intel_rng_hw * intel_rng_hw ,
struct pci_dev * dev )
2006-09-29 01:59:42 -07:00
{
2007-05-08 00:25:08 -07:00
intel_rng_hw - > bios_cntl_val = 0xff ;
intel_rng_hw - > fwh_dec_en1_val = 0xff ;
intel_rng_hw - > dev = dev ;
/* Check for Intel 82802 */
if ( dev - > device < 0x2640 ) {
intel_rng_hw - > fwh_dec_en1_off = FWH_DEC_EN1_REG_OLD ;
intel_rng_hw - > bios_cntl_off = BIOS_CNTL_REG_OLD ;
} else {
intel_rng_hw - > fwh_dec_en1_off = FWH_DEC_EN1_REG_NEW ;
intel_rng_hw - > bios_cntl_off = BIOS_CNTL_REG_NEW ;
}
pci_read_config_byte ( dev , intel_rng_hw - > fwh_dec_en1_off ,
& intel_rng_hw - > fwh_dec_en1_val ) ;
pci_read_config_byte ( dev , intel_rng_hw - > bios_cntl_off ,
& intel_rng_hw - > bios_cntl_val ) ;
if ( ( intel_rng_hw - > bios_cntl_val &
( BIOS_CNTL_LOCK_ENABLE_MASK | BIOS_CNTL_WRITE_ENABLE_MASK ) )
= = BIOS_CNTL_LOCK_ENABLE_MASK ) {
static __initdata /*const*/ char warning [ ] =
2007-07-15 23:40:54 -07:00
KERN_WARNING PFX " Firmware space is locked read-only. If you can't or \n "
KERN_WARNING PFX " don't want to disable this in firmware setup, and if \n "
KERN_WARNING PFX " you are certain that your system has a functional \n "
KERN_WARNING PFX " RNG, try using the 'no_fwh_detect' option. \n " ;
2007-05-08 00:25:08 -07:00
if ( no_fwh_detect )
return - ENODEV ;
printk ( warning ) ;
return - EBUSY ;
}
intel_rng_hw - > mem = ioremap_nocache ( INTEL_FWH_ADDR , INTEL_FWH_ADDR_LEN ) ;
if ( intel_rng_hw - > mem = = NULL )
return - EBUSY ;
return 0 ;
2006-09-29 01:59:42 -07:00
}
2007-05-08 00:25:08 -07:00
2006-09-29 01:59:42 -07:00
2006-06-26 00:24:59 -07:00
static int __init mod_init ( void )
{
int err = - ENODEV ;
2007-05-08 00:25:08 -07:00
int i ;
2006-09-29 01:59:42 -07:00
struct pci_dev * dev = NULL ;
2007-05-08 00:25:08 -07:00
void __iomem * mem = mem ;
u8 hw_status ;
struct intel_rng_hw * intel_rng_hw ;
2006-06-26 00:24:59 -07:00
2006-09-29 01:59:42 -07:00
for ( i = 0 ; ! dev & & pci_tbl [ i ] . vendor ; + + i )
2007-05-08 00:25:08 -07:00
dev = pci_get_device ( pci_tbl [ i ] . vendor , pci_tbl [ i ] . device ,
NULL ) ;
2006-09-29 01:59:42 -07:00
if ( ! dev )
2006-06-26 00:24:59 -07:00
goto out ; /* Device not found. */
2007-01-10 23:15:41 -08:00
if ( no_fwh_detect < 0 ) {
pci_dev_put ( dev ) ;
goto fwh_done ;
}
2007-05-08 00:25:08 -07:00
intel_rng_hw = kmalloc ( sizeof ( * intel_rng_hw ) , GFP_KERNEL ) ;
if ( ! intel_rng_hw ) {
2007-01-10 23:15:41 -08:00
pci_dev_put ( dev ) ;
goto out ;
}
2007-05-08 00:25:08 -07:00
err = intel_init_hw_struct ( intel_rng_hw , dev ) ;
if ( err ) {
2006-09-29 01:59:42 -07:00
pci_dev_put ( dev ) ;
2007-05-08 00:25:08 -07:00
kfree ( intel_rng_hw ) ;
if ( err = = - ENODEV )
goto fwh_done ;
2006-09-29 01:59:42 -07:00
goto out ;
}
/*
* Since the BIOS code / data is going to disappear from its normal
* location with the Read ID command , all activity on the system
* must be stopped until the state is back to normal .
2007-05-08 00:25:08 -07:00
*
* Use stop_machine_run because IPIs can be blocked by disabling
* interrupts .
2006-09-29 01:59:42 -07:00
*/
2007-05-08 00:25:08 -07:00
err = stop_machine_run ( intel_rng_hw_init , intel_rng_hw , NR_CPUS ) ;
2006-09-29 01:59:42 -07:00
pci_dev_put ( dev ) ;
2007-05-08 00:25:08 -07:00
iounmap ( intel_rng_hw - > mem ) ;
kfree ( intel_rng_hw ) ;
if ( err )
2006-09-29 01:59:42 -07:00
goto out ;
2007-01-10 23:15:41 -08:00
fwh_done :
2006-06-26 00:24:59 -07:00
err = - ENOMEM ;
mem = ioremap ( INTEL_RNG_ADDR , INTEL_RNG_ADDR_LEN ) ;
if ( ! mem )
goto out ;
intel_rng . priv = ( unsigned long ) mem ;
2006-09-29 01:59:42 -07:00
/* Check for Random Number Generator */
2006-06-26 00:24:59 -07:00
err = - ENODEV ;
hw_status = hwstatus_get ( mem ) ;
2007-05-08 00:25:08 -07:00
if ( ( hw_status & INTEL_RNG_PRESENT ) = = 0 ) {
iounmap ( mem ) ;
goto out ;
}
2006-06-26 00:24:59 -07:00
printk ( KERN_INFO " Intel 82802 RNG detected \n " ) ;
err = hwrng_register ( & intel_rng ) ;
if ( err ) {
printk ( KERN_ERR PFX " RNG registering failed (%d) \n " ,
err ) ;
2007-05-08 00:25:08 -07:00
iounmap ( mem ) ;
2006-06-26 00:24:59 -07:00
}
out :
return err ;
}
static void __exit mod_exit ( void )
{
void __iomem * mem = ( void __iomem * ) intel_rng . priv ;
hwrng_unregister ( & intel_rng ) ;
iounmap ( mem ) ;
}
2007-01-10 23:15:43 -08:00
module_init ( mod_init ) ;
2006-06-26 00:24:59 -07:00
module_exit ( mod_exit ) ;
MODULE_DESCRIPTION ( " H/W RNG driver for Intel chipsets " ) ;
MODULE_LICENSE ( " GPL " ) ;