2021-10-29 22:49:59 +05:30
// SPDX-License-Identifier: GPL-2.0
2016-08-23 16:27:15 -07:00
/*
2021-10-29 22:49:59 +05:30
* Hardware Random Number Generator support .
* Cavium Thunder , Marvell OcteonTx / Tx2 processor families .
2016-08-23 16:27:15 -07:00
*
* Copyright ( C ) 2016 Cavium , Inc .
*/
# include <linux/hw_random.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/pci.h>
# include <linux/pci_ids.h>
2021-10-29 22:49:59 +05:30
# include <asm/arch_timer.h>
/* PCI device IDs */
# define PCI_DEVID_CAVIUM_RNG_PF 0xA018
# define PCI_DEVID_CAVIUM_RNG_VF 0xA033
# define HEALTH_STATUS_REG 0x38
/* RST device info */
# define PCI_DEVICE_ID_RST_OTX2 0xA085
# define RST_BOOT_REG 0x1600ULL
# define CLOCK_BASE_RATE 50000000ULL
# define MSEC_TO_NSEC(x) (x * 1000000)
2016-08-23 16:27:15 -07:00
struct cavium_rng {
struct hwrng ops ;
void __iomem * result ;
2021-10-29 22:49:59 +05:30
void __iomem * pf_regbase ;
struct pci_dev * pdev ;
u64 clock_rate ;
u64 prev_error ;
u64 prev_time ;
2016-08-23 16:27:15 -07:00
} ;
2021-10-29 22:49:59 +05:30
static inline bool is_octeontx ( struct pci_dev * pdev )
{
if ( midr_is_cpu_model_range ( read_cpuid_id ( ) , MIDR_THUNDERX_83XX ,
MIDR_CPU_VAR_REV ( 0 , 0 ) ,
MIDR_CPU_VAR_REV ( 3 , 0 ) ) | |
midr_is_cpu_model_range ( read_cpuid_id ( ) , MIDR_THUNDERX_81XX ,
MIDR_CPU_VAR_REV ( 0 , 0 ) ,
MIDR_CPU_VAR_REV ( 3 , 0 ) ) | |
midr_is_cpu_model_range ( read_cpuid_id ( ) , MIDR_THUNDERX ,
MIDR_CPU_VAR_REV ( 0 , 0 ) ,
MIDR_CPU_VAR_REV ( 3 , 0 ) ) )
return true ;
return false ;
}
static u64 rng_get_coprocessor_clkrate ( void )
{
u64 ret = CLOCK_BASE_RATE * 16 ; /* Assume 800Mhz as default */
struct pci_dev * pdev ;
void __iomem * base ;
pdev = pci_get_device ( PCI_VENDOR_ID_CAVIUM ,
PCI_DEVICE_ID_RST_OTX2 , NULL ) ;
if ( ! pdev )
goto error ;
base = pci_ioremap_bar ( pdev , 0 ) ;
if ( ! base )
goto error_put_pdev ;
/* RST: PNR_MUL * 50Mhz gives clockrate */
ret = CLOCK_BASE_RATE * ( ( readq ( base + RST_BOOT_REG ) > > 33 ) & 0x3F ) ;
iounmap ( base ) ;
error_put_pdev :
pci_dev_put ( pdev ) ;
error :
return ret ;
}
static int check_rng_health ( struct cavium_rng * rng )
{
u64 cur_err , cur_time ;
u64 status , cycles ;
u64 time_elapsed ;
/* Skip checking health for OcteonTx */
if ( ! rng - > pf_regbase )
return 0 ;
status = readq ( rng - > pf_regbase + HEALTH_STATUS_REG ) ;
if ( status & BIT_ULL ( 0 ) ) {
dev_err ( & rng - > pdev - > dev , " HWRNG: Startup health test failed \n " ) ;
return - EIO ;
}
cycles = status > > 1 ;
if ( ! cycles )
return 0 ;
cur_time = arch_timer_read_counter ( ) ;
/* RNM_HEALTH_STATUS[CYCLES_SINCE_HEALTH_FAILURE]
* Number of coprocessor cycles times 2 since the last failure .
* This field doesn ' t get cleared / updated until another failure .
*/
cycles = cycles / 2 ;
cur_err = ( cycles * 1000000000 ) / rng - > clock_rate ; /* In nanosec */
/* Ignore errors that happenned a long time ago, these
* are most likely false positive errors .
*/
if ( cur_err > MSEC_TO_NSEC ( 10 ) ) {
rng - > prev_error = 0 ;
rng - > prev_time = 0 ;
return 0 ;
}
if ( rng - > prev_error ) {
/* Calculate time elapsed since last error
* ' 1 ' tick of CNTVCT is 10 ns , since it runs at 100 Mhz .
*/
time_elapsed = ( cur_time - rng - > prev_time ) * 10 ;
time_elapsed + = rng - > prev_error ;
/* Check if current error is a new one or the old one itself.
* If error is a new one then consider there is a persistent
* issue with entropy , declare hardware failure .
*/
if ( cur_err < time_elapsed ) {
dev_err ( & rng - > pdev - > dev , " HWRNG failure detected \n " ) ;
rng - > prev_error = cur_err ;
rng - > prev_time = cur_time ;
return - EIO ;
}
}
rng - > prev_error = cur_err ;
rng - > prev_time = cur_time ;
return 0 ;
}
2016-08-23 16:27:15 -07:00
/* Read data from the RNG unit */
static int cavium_rng_read ( struct hwrng * rng , void * dat , size_t max , bool wait )
{
struct cavium_rng * p = container_of ( rng , struct cavium_rng , ops ) ;
unsigned int size = max ;
2021-10-29 22:49:59 +05:30
int err = 0 ;
err = check_rng_health ( p ) ;
if ( err )
return err ;
2016-08-23 16:27:15 -07:00
while ( size > = 8 ) {
* ( ( u64 * ) dat ) = readq ( p - > result ) ;
size - = 8 ;
dat + = 8 ;
}
while ( size > 0 ) {
* ( ( u8 * ) dat ) = readb ( p - > result ) ;
size - - ;
dat + + ;
}
return max ;
}
2021-10-29 22:49:59 +05:30
static int cavium_map_pf_regs ( struct cavium_rng * rng )
{
struct pci_dev * pdev ;
/* Health status is not supported on 83xx, skip mapping PF CSRs */
if ( is_octeontx ( rng - > pdev ) ) {
rng - > pf_regbase = NULL ;
return 0 ;
}
pdev = pci_get_device ( PCI_VENDOR_ID_CAVIUM ,
PCI_DEVID_CAVIUM_RNG_PF , NULL ) ;
if ( ! pdev ) {
2022-02-25 14:38:59 +08:00
pr_err ( " Cannot find RNG PF device \n " ) ;
2021-10-29 22:49:59 +05:30
return - EIO ;
}
rng - > pf_regbase = ioremap ( pci_resource_start ( pdev , 0 ) ,
pci_resource_len ( pdev , 0 ) ) ;
if ( ! rng - > pf_regbase ) {
dev_err ( & pdev - > dev , " Failed to map PF CSR region \n " ) ;
pci_dev_put ( pdev ) ;
return - ENOMEM ;
}
pci_dev_put ( pdev ) ;
/* Get co-processor clock rate */
rng - > clock_rate = rng_get_coprocessor_clkrate ( ) ;
return 0 ;
}
2016-08-23 16:27:15 -07:00
/* Map Cavium RNG to an HWRNG object */
static int cavium_rng_probe_vf ( struct pci_dev * pdev ,
const struct pci_device_id * id )
{
struct cavium_rng * rng ;
int ret ;
rng = devm_kzalloc ( & pdev - > dev , sizeof ( * rng ) , GFP_KERNEL ) ;
if ( ! rng )
return - ENOMEM ;
2021-10-29 22:49:59 +05:30
rng - > pdev = pdev ;
2016-08-23 16:27:15 -07:00
/* Map the RNG result */
rng - > result = pcim_iomap ( pdev , 0 , 0 ) ;
if ( ! rng - > result ) {
dev_err ( & pdev - > dev , " Error iomap failed retrieving result. \n " ) ;
return - ENOMEM ;
}
2017-02-06 14:28:46 -08:00
rng - > ops . name = devm_kasprintf ( & pdev - > dev , GFP_KERNEL ,
" cavium-rng-%s " , dev_name ( & pdev - > dev ) ) ;
if ( ! rng - > ops . name )
return - ENOMEM ;
2016-08-23 16:27:15 -07:00
rng - > ops . read = cavium_rng_read ;
pci_set_drvdata ( pdev , rng ) ;
2021-10-29 22:49:59 +05:30
/* Health status is available only at PF, hence map PF registers. */
ret = cavium_map_pf_regs ( rng ) ;
if ( ret )
return ret ;
2019-07-25 16:01:55 +08:00
ret = devm_hwrng_register ( & pdev - > dev , & rng - > ops ) ;
2016-08-23 16:27:15 -07:00
if ( ret ) {
dev_err ( & pdev - > dev , " Error registering device as HWRNG. \n " ) ;
return ret ;
}
return 0 ;
}
2021-10-29 22:49:59 +05:30
/* Remove the VF */
static void cavium_rng_remove_vf ( struct pci_dev * pdev )
{
struct cavium_rng * rng ;
rng = pci_get_drvdata ( pdev ) ;
iounmap ( rng - > pf_regbase ) ;
}
2016-08-23 16:27:15 -07:00
static const struct pci_device_id cavium_rng_vf_id_table [ ] = {
2021-10-29 22:49:59 +05:30
{ PCI_DEVICE ( PCI_VENDOR_ID_CAVIUM , PCI_DEVID_CAVIUM_RNG_VF ) } ,
{ 0 , }
2016-08-23 16:27:15 -07:00
} ;
MODULE_DEVICE_TABLE ( pci , cavium_rng_vf_id_table ) ;
static struct pci_driver cavium_rng_vf_driver = {
. name = " cavium_rng_vf " ,
. id_table = cavium_rng_vf_id_table ,
. probe = cavium_rng_probe_vf ,
2021-10-29 22:49:59 +05:30
. remove = cavium_rng_remove_vf ,
2016-08-23 16:27:15 -07:00
} ;
module_pci_driver ( cavium_rng_vf_driver ) ;
MODULE_AUTHOR ( " Omer Khaliq <okhaliq@caviumnetworks.com> " ) ;
2021-10-29 22:49:59 +05:30
MODULE_LICENSE ( " GPL v2 " ) ;