2013-10-11 14:07:57 +11:00
/*
* Copyright 2013 , Michael Ellerman , IBM Corporation .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# define pr_fmt(fmt) "powernv-rng: " fmt
# include <linux/kernel.h>
# include <linux/of.h>
2013-10-28 19:34:41 +11:00
# include <linux/of_address.h>
2013-10-11 14:07:57 +11:00
# include <linux/of_platform.h>
# include <linux/slab.h>
2013-10-28 19:34:41 +11:00
# include <linux/smp.h>
2013-10-11 14:07:57 +11:00
# include <asm/archrandom.h>
2017-08-04 11:12:18 +10:00
# include <asm/cputable.h>
2013-10-11 14:07:57 +11:00
# include <asm/io.h>
2013-10-28 19:34:41 +11:00
# include <asm/prom.h>
2013-10-11 14:07:57 +11:00
# include <asm/machdep.h>
2013-11-20 11:05:01 +11:00
# include <asm/smp.h>
2013-10-11 14:07:57 +11:00
2017-08-04 11:12:18 +10:00
# define DARN_ERR 0xFFFFFFFFFFFFFFFFul
2013-10-11 14:07:57 +11:00
struct powernv_rng {
void __iomem * regs ;
2015-03-20 20:39:41 +11:00
void __iomem * regs_real ;
2013-10-11 14:07:57 +11:00
unsigned long mask ;
} ;
static DEFINE_PER_CPU ( struct powernv_rng * , powernv_rng ) ;
2015-03-20 20:39:41 +11:00
int powernv_hwrng_present ( void )
{
struct powernv_rng * rng ;
rng = get_cpu_var ( powernv_rng ) ;
put_cpu_var ( rng ) ;
return rng ! = NULL ;
}
2013-10-11 14:07:57 +11:00
static unsigned long rng_whiten ( struct powernv_rng * rng , unsigned long val )
{
unsigned long parity ;
/* Calculate the parity of the value */
asm ( " popcntd %0,%1 " : " =r " ( parity ) : " r " ( val ) ) ;
/* xor our value with the previous mask */
val ^ = rng - > mask ;
/* update the mask based on the parity of this value */
rng - > mask = ( rng - > mask < < 1 ) | ( parity & 1 ) ;
return val ;
}
2015-03-20 20:39:41 +11:00
int powernv_get_random_real_mode ( unsigned long * v )
{
struct powernv_rng * rng ;
rng = raw_cpu_read ( powernv_rng ) ;
2017-04-05 17:54:54 +10:00
* v = rng_whiten ( rng , __raw_rm_readq ( rng - > regs_real ) ) ;
2015-03-20 20:39:41 +11:00
return 1 ;
}
2017-08-04 11:12:18 +10:00
int powernv_get_random_darn ( unsigned long * v )
{
unsigned long val ;
/* Using DARN with L=1 - 64-bit conditioned random number */
asm volatile ( PPC_DARN ( % 0 , 1 ) : " =r " ( val ) ) ;
if ( val = = DARN_ERR )
return 0 ;
* v = val ;
return 1 ;
}
static int initialise_darn ( void )
{
unsigned long val ;
int i ;
if ( ! cpu_has_feature ( CPU_FTR_ARCH_300 ) )
return - ENODEV ;
for ( i = 0 ; i < 10 ; i + + ) {
if ( powernv_get_random_darn ( & val ) ) {
ppc_md . get_random_seed = powernv_get_random_darn ;
return 0 ;
}
}
pr_warn ( " Unable to use DARN for get_random_seed() \n " ) ;
return - EIO ;
}
2013-10-11 14:07:57 +11:00
int powernv_get_random_long ( unsigned long * v )
{
struct powernv_rng * rng ;
rng = get_cpu_var ( powernv_rng ) ;
* v = rng_whiten ( rng , in_be64 ( rng - > regs ) ) ;
put_cpu_var ( rng ) ;
return 1 ;
}
EXPORT_SYMBOL_GPL ( powernv_get_random_long ) ;
static __init void rng_init_per_cpu ( struct powernv_rng * rng ,
struct device_node * dn )
{
int chip_id , cpu ;
chip_id = of_get_ibm_chip_id ( dn ) ;
if ( chip_id = = - 1 )
2017-08-21 10:16:47 -05:00
pr_warn ( " No ibm,chip-id found for %pOF. \n " , dn ) ;
2013-10-11 14:07:57 +11:00
for_each_possible_cpu ( cpu ) {
if ( per_cpu ( powernv_rng , cpu ) = = NULL | |
cpu_to_chip_id ( cpu ) = = chip_id ) {
per_cpu ( powernv_rng , cpu ) = rng ;
}
}
}
static __init int rng_create ( struct device_node * dn )
{
struct powernv_rng * rng ;
2015-03-20 20:39:41 +11:00
struct resource res ;
2013-10-11 14:07:57 +11:00
unsigned long val ;
rng = kzalloc ( sizeof ( * rng ) , GFP_KERNEL ) ;
if ( ! rng )
return - ENOMEM ;
2015-03-20 20:39:41 +11:00
if ( of_address_to_resource ( dn , 0 , & res ) ) {
kfree ( rng ) ;
return - ENXIO ;
}
rng - > regs_real = ( void __iomem * ) res . start ;
2013-10-11 14:07:57 +11:00
rng - > regs = of_iomap ( dn , 0 ) ;
if ( ! rng - > regs ) {
kfree ( rng ) ;
return - ENXIO ;
}
val = in_be64 ( rng - > regs ) ;
rng - > mask = val ;
rng_init_per_cpu ( rng , dn ) ;
pr_info_once ( " Registering arch random hook. \n " ) ;
2015-07-17 20:11:43 +10:00
ppc_md . get_random_seed = powernv_get_random_long ;
2013-10-11 14:07:57 +11:00
return 0 ;
}
static __init int rng_init ( void )
{
struct device_node * dn ;
int rc ;
for_each_compatible_node ( dn , NULL , " ibm,power-rng " ) {
rc = rng_create ( dn ) ;
if ( rc ) {
2017-08-21 10:16:47 -05:00
pr_err ( " Failed creating rng for %pOF (%d). \n " ,
dn , rc ) ;
2013-10-11 14:07:57 +11:00
continue ;
}
/* Create devices for hwrng driver */
of_platform_device_create ( dn , NULL , NULL ) ;
}
2017-08-04 11:12:18 +10:00
initialise_darn ( ) ;
2013-10-11 14:07:57 +11:00
return 0 ;
}
2014-07-15 22:22:24 +10:00
machine_subsys_initcall ( powernv , rng_init ) ;