2006-06-26 00:25:00 -07:00
/*
* RNG driver for AMD 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 )
2008-10-27 15:10:23 +00:00
* ( c ) Copyright 2001 Red Hat Inc
2006-06-26 00:25:00 -07:00
*
* 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 .
*/
2016-08-26 13:11:32 +02:00
# include <linux/delay.h>
# include <linux/hw_random.h>
2006-06-26 00:25:00 -07:00
# include <linux/kernel.h>
2016-08-26 13:11:32 +02:00
# include <linux/module.h>
2006-06-26 00:25:00 -07:00
# include <linux/pci.h>
2016-08-26 13:11:31 +02:00
# define DRV_NAME "AMD768-HWRNG"
2006-06-26 00:25:00 -07:00
2016-08-26 13:11:35 +02:00
# define RNGDATA 0x00
# define RNGDONE 0x04
# define PMBASE_OFFSET 0xF0
# define PMBASE_SIZE 8
2006-06-26 00:25:00 -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 [ ] = {
2009-06-25 13:50:53 +08:00
{ PCI_VDEVICE ( AMD , 0x7443 ) , 0 , } ,
{ PCI_VDEVICE ( AMD , 0x746b ) , 0 , } ,
2006-06-26 00:25:00 -07:00
{ 0 , } , /* terminate list */
} ;
MODULE_DEVICE_TABLE ( pci , pci_tbl ) ;
2016-08-26 13:11:34 +02:00
struct amd768_priv {
2016-08-26 13:11:35 +02:00
void __iomem * iobase ;
2016-08-26 13:11:34 +02:00
struct pci_dev * pcidev ;
2017-03-14 07:36:01 -04:00
u32 pmbase ;
2016-08-26 13:11:34 +02:00
} ;
2006-06-26 00:25:00 -07:00
2016-08-26 13:11:36 +02:00
static int amd_rng_read ( struct hwrng * rng , void * buf , size_t max , bool wait )
2006-06-26 00:25:00 -07:00
{
2016-08-26 13:11:36 +02:00
u32 * data = buf ;
2016-08-26 13:11:34 +02:00
struct amd768_priv * priv = ( struct amd768_priv * ) rng - > priv ;
2016-08-26 13:11:36 +02:00
size_t read = 0 ;
/* We will wait at maximum one time per read */
int timeout = max / 4 + 1 ;
/*
* RNG data is available when RNGDONE is set to 1
* New random numbers are generated approximately 128 microseconds
* after RNGDATA is read
*/
while ( read < max ) {
if ( ioread32 ( priv - > iobase + RNGDONE ) = = 0 ) {
if ( wait ) {
/* Delay given by datasheet */
usleep_range ( 128 , 196 ) ;
if ( timeout - - = = 0 )
return read ;
} else {
return 0 ;
}
} else {
* data = ioread32 ( priv - > iobase + RNGDATA ) ;
data + + ;
read + = 4 ;
}
2007-11-21 12:24:45 +08:00
}
2006-06-26 00:25:00 -07:00
2016-08-26 13:11:36 +02:00
return read ;
2006-06-26 00:25:00 -07:00
}
static int amd_rng_init ( struct hwrng * rng )
{
2016-08-26 13:11:34 +02:00
struct amd768_priv * priv = ( struct amd768_priv * ) rng - > priv ;
2006-06-26 00:25:00 -07:00
u8 rnen ;
2016-08-26 13:11:34 +02:00
pci_read_config_byte ( priv - > pcidev , 0x40 , & rnen ) ;
2016-08-26 13:11:30 +02:00
rnen | = BIT ( 7 ) ; /* RNG on */
2016-08-26 13:11:34 +02:00
pci_write_config_byte ( priv - > pcidev , 0x40 , rnen ) ;
2006-06-26 00:25:00 -07:00
2016-08-26 13:11:34 +02:00
pci_read_config_byte ( priv - > pcidev , 0x41 , & rnen ) ;
2016-08-26 13:11:30 +02:00
rnen | = BIT ( 7 ) ; /* PMIO enable */
2016-08-26 13:11:34 +02:00
pci_write_config_byte ( priv - > pcidev , 0x41 , rnen ) ;
2006-06-26 00:25:00 -07:00
return 0 ;
}
static void amd_rng_cleanup ( struct hwrng * rng )
{
2016-08-26 13:11:34 +02:00
struct amd768_priv * priv = ( struct amd768_priv * ) rng - > priv ;
2006-06-26 00:25:00 -07:00
u8 rnen ;
2016-08-26 13:11:34 +02:00
pci_read_config_byte ( priv - > pcidev , 0x40 , & rnen ) ;
2016-08-26 13:11:30 +02:00
rnen & = ~ BIT ( 7 ) ; /* RNG off */
2016-08-26 13:11:34 +02:00
pci_write_config_byte ( priv - > pcidev , 0x40 , rnen ) ;
2006-06-26 00:25:00 -07:00
}
static struct hwrng amd_rng = {
. name = " amd " ,
. init = amd_rng_init ,
. cleanup = amd_rng_cleanup ,
2016-08-26 13:11:36 +02:00
. read = amd_rng_read ,
2006-06-26 00:25:00 -07:00
} ;
2021-07-11 15:31:44 -07:00
static int __init amd_rng_mod_init ( void )
2006-06-26 00:25:00 -07:00
{
2021-04-29 12:32:53 +01:00
int err ;
2006-06-26 00:25:00 -07:00
struct pci_dev * pdev = NULL ;
const struct pci_device_id * ent ;
u32 pmbase ;
2016-08-26 13:11:34 +02:00
struct amd768_priv * priv ;
2006-06-26 00:25:00 -07:00
for_each_pci_dev ( pdev ) {
ent = pci_match_id ( pci_tbl , pdev ) ;
if ( ent )
goto found ;
}
/* Device not found. */
2016-08-26 13:11:34 +02:00
return - ENODEV ;
2006-06-26 00:25:00 -07:00
found :
err = pci_read_config_dword ( pdev , 0x58 , & pmbase ) ;
if ( err )
2016-08-26 13:11:34 +02:00
return err ;
2006-06-26 00:25:00 -07:00
pmbase & = 0x0000FF00 ;
if ( pmbase = = 0 )
2016-08-26 13:11:34 +02:00
return - EIO ;
2017-03-14 07:36:01 -04:00
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
2016-09-16 01:49:41 +00:00
if ( ! priv )
return - ENOMEM ;
2016-08-26 13:11:34 +02:00
2017-03-14 07:36:01 -04:00
if ( ! request_region ( pmbase + PMBASE_OFFSET , PMBASE_SIZE , DRV_NAME ) ) {
2016-08-26 13:11:31 +02:00
dev_err ( & pdev - > dev , DRV_NAME " region 0x%x already in use! \n " ,
2011-04-27 23:21:15 +04:00
pmbase + 0xF0 ) ;
2017-03-14 07:36:01 -04:00
err = - EBUSY ;
goto out ;
2011-04-27 23:21:15 +04:00
}
2016-08-26 13:11:35 +02:00
2017-03-14 07:36:01 -04:00
priv - > iobase = ioport_map ( pmbase + PMBASE_OFFSET , PMBASE_SIZE ) ;
2016-09-16 01:49:41 +00:00
if ( ! priv - > iobase ) {
2016-08-26 13:11:35 +02:00
pr_err ( DRV_NAME " Cannot map ioport \n " ) ;
2017-03-14 07:36:01 -04:00
err = - EINVAL ;
goto err_iomap ;
2016-08-26 13:11:35 +02:00
}
2016-08-26 13:11:34 +02:00
amd_rng . priv = ( unsigned long ) priv ;
2017-03-14 07:36:01 -04:00
priv - > pmbase = pmbase ;
2016-08-26 13:11:34 +02:00
priv - > pcidev = pdev ;
2006-06-26 00:25:00 -07:00
2016-08-26 13:11:31 +02:00
pr_info ( DRV_NAME " detected \n " ) ;
2017-03-14 07:36:01 -04:00
err = hwrng_register ( & amd_rng ) ;
if ( err ) {
pr_err ( DRV_NAME " registering failed (%d) \n " , err ) ;
goto err_hwrng ;
}
return 0 ;
err_hwrng :
ioport_unmap ( priv - > iobase ) ;
err_iomap :
release_region ( pmbase + PMBASE_OFFSET , PMBASE_SIZE ) ;
out :
kfree ( priv ) ;
return err ;
2006-06-26 00:25:00 -07:00
}
2021-07-11 15:31:44 -07:00
static void __exit amd_rng_mod_exit ( void )
2006-06-26 00:25:00 -07:00
{
2017-03-14 07:36:01 -04:00
struct amd768_priv * priv ;
priv = ( struct amd768_priv * ) amd_rng . priv ;
hwrng_unregister ( & amd_rng ) ;
ioport_unmap ( priv - > iobase ) ;
release_region ( priv - > pmbase + PMBASE_OFFSET , PMBASE_SIZE ) ;
kfree ( priv ) ;
2006-06-26 00:25:00 -07:00
}
2021-07-11 15:31:44 -07:00
module_init ( amd_rng_mod_init ) ;
module_exit ( amd_rng_mod_exit ) ;
2006-06-26 00:25:00 -07:00
MODULE_AUTHOR ( " The Linux Kernel team " ) ;
MODULE_DESCRIPTION ( " H/W RNG driver for AMD chipsets " ) ;
MODULE_LICENSE ( " GPL " ) ;