2019-05-31 01:09:36 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2017-02-24 09:10:05 +01:00
/*
* s390 TRNG device driver
*
* Driver for the TRNG ( true random number generation ) command
* available via CPACF extension MSA 7 on the s390 arch .
* Copyright IBM Corp . 2017
* Author ( s ) : Harald Freudenberger < freude @ de . ibm . com >
*/
# define KMSG_COMPONENT "trng"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
# include <linux/hw_random.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/cpufeature.h>
# include <linux/miscdevice.h>
# include <linux/debugfs.h>
# include <linux/atomic.h>
# include <linux/random.h>
# include <linux/sched/signal.h>
# include <asm/debug.h>
# include <asm/cpacf.h>
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " IBM Corporation " ) ;
MODULE_DESCRIPTION ( " s390 CPACF TRNG device driver " ) ;
/* trng related debug feature things */
static debug_info_t * debug_info ;
# define DEBUG_DBG(...) debug_sprintf_event(debug_info, 6, ##__VA_ARGS__)
# define DEBUG_INFO(...) debug_sprintf_event(debug_info, 5, ##__VA_ARGS__)
# define DEBUG_WARN(...) debug_sprintf_event(debug_info, 4, ##__VA_ARGS__)
# define DEBUG_ERR(...) debug_sprintf_event(debug_info, 3, ##__VA_ARGS__)
/* trng helpers */
static atomic64_t trng_dev_counter = ATOMIC64_INIT ( 0 ) ;
static atomic64_t trng_hwrng_counter = ATOMIC64_INIT ( 0 ) ;
/* file io functions */
static int trng_open ( struct inode * inode , struct file * file )
{
return nonseekable_open ( inode , file ) ;
}
static ssize_t trng_read ( struct file * file , char __user * ubuf ,
size_t nbytes , loff_t * ppos )
{
u8 buf [ 32 ] ;
u8 * p = buf ;
unsigned int n ;
ssize_t ret = 0 ;
/*
* use buf for requests < = sizeof ( buf ) ,
* otherwise allocate one page and fetch
* pagewise .
*/
if ( nbytes > sizeof ( buf ) ) {
p = ( u8 * ) __get_free_page ( GFP_KERNEL ) ;
if ( ! p )
return - ENOMEM ;
}
while ( nbytes ) {
if ( need_resched ( ) ) {
if ( signal_pending ( current ) ) {
if ( ret = = 0 )
ret = - ERESTARTSYS ;
break ;
}
schedule ( ) ;
}
n = nbytes > PAGE_SIZE ? PAGE_SIZE : nbytes ;
cpacf_trng ( NULL , 0 , p , n ) ;
atomic64_add ( n , & trng_dev_counter ) ;
if ( copy_to_user ( ubuf , p , n ) ) {
ret = - EFAULT ;
break ;
}
nbytes - = n ;
ubuf + = n ;
ret + = n ;
}
if ( p ! = buf )
free_page ( ( unsigned long ) p ) ;
DEBUG_DBG ( " trng_read()=%zd \n " , ret ) ;
return ret ;
}
/* sysfs */
static ssize_t trng_counter_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
u64 dev_counter = atomic64_read ( & trng_dev_counter ) ;
u64 hwrng_counter = atomic64_read ( & trng_hwrng_counter ) ;
# if IS_ENABLED(CONFIG_ARCH_RANDOM)
u64 arch_counter = atomic64_read ( & s390_arch_random_counter ) ;
return snprintf ( buf , PAGE_SIZE ,
" trng: %llu \n "
" hwrng: %llu \n "
" arch: %llu \n "
" total: %llu \n " ,
dev_counter , hwrng_counter , arch_counter ,
dev_counter + hwrng_counter + arch_counter ) ;
# else
return snprintf ( buf , PAGE_SIZE ,
" trng: %llu \n "
" hwrng: %llu \n "
" total: %llu \n " ,
dev_counter , hwrng_counter ,
dev_counter + hwrng_counter ) ;
# endif
}
static DEVICE_ATTR ( byte_counter , 0444 , trng_counter_show , NULL ) ;
static struct attribute * trng_dev_attrs [ ] = {
& dev_attr_byte_counter . attr ,
NULL
} ;
static const struct attribute_group trng_dev_attr_group = {
. attrs = trng_dev_attrs
} ;
static const struct attribute_group * trng_dev_attr_groups [ ] = {
& trng_dev_attr_group ,
NULL
} ;
static const struct file_operations trng_fops = {
. owner = THIS_MODULE ,
. open = & trng_open ,
. release = NULL ,
. read = & trng_read ,
. llseek = noop_llseek ,
} ;
static struct miscdevice trng_dev = {
. name = " trng " ,
. minor = MISC_DYNAMIC_MINOR ,
. mode = 0444 ,
. fops = & trng_fops ,
. groups = trng_dev_attr_groups ,
} ;
/* hwrng_register */
static inline void _trng_hwrng_read ( u8 * buf , size_t len )
{
cpacf_trng ( NULL , 0 , buf , len ) ;
atomic64_add ( len , & trng_hwrng_counter ) ;
}
static int trng_hwrng_data_read ( struct hwrng * rng , u32 * data )
{
size_t len = sizeof ( * data ) ;
_trng_hwrng_read ( ( u8 * ) data , len ) ;
DEBUG_DBG ( " trng_hwrng_data_read()=%zu \n " , len ) ;
return len ;
}
static int trng_hwrng_read ( struct hwrng * rng , void * data , size_t max , bool wait )
{
size_t len = max < = PAGE_SIZE ? max : PAGE_SIZE ;
_trng_hwrng_read ( ( u8 * ) data , len ) ;
DEBUG_DBG ( " trng_hwrng_read()=%zu \n " , len ) ;
return len ;
}
/*
* hwrng register struct
* The trng is suppost to have 100 % entropy , and thus
* we register with a very high quality value .
*/
static struct hwrng trng_hwrng_dev = {
. name = " s390-trng " ,
. data_read = trng_hwrng_data_read ,
. read = trng_hwrng_read ,
. quality = 999 ,
} ;
/* init and exit */
static void __init trng_debug_init ( void )
{
debug_info = debug_register ( " trng " , 1 , 1 , 4 * sizeof ( long ) ) ;
debug_register_view ( debug_info , & debug_sprintf_view ) ;
debug_set_level ( debug_info , 3 ) ;
}
static void trng_debug_exit ( void )
{
debug_unregister ( debug_info ) ;
}
static int __init trng_init ( void )
{
int ret ;
trng_debug_init ( ) ;
/* check if subfunction CPACF_PRNO_TRNG is available */
if ( ! cpacf_query_func ( CPACF_PRNO , CPACF_PRNO_TRNG ) ) {
DEBUG_INFO ( " trng_init CPACF_PRNO_TRNG not available \n " ) ;
ret = - ENODEV ;
goto out_dbg ;
}
ret = misc_register ( & trng_dev ) ;
if ( ret ) {
DEBUG_WARN ( " trng_init misc_register() failed rc=%d \n " , ret ) ;
goto out_dbg ;
}
ret = hwrng_register ( & trng_hwrng_dev ) ;
if ( ret ) {
DEBUG_WARN ( " trng_init hwrng_register() failed rc=%d \n " , ret ) ;
goto out_misc ;
}
DEBUG_DBG ( " trng_init successful \n " ) ;
return 0 ;
out_misc :
misc_deregister ( & trng_dev ) ;
out_dbg :
trng_debug_exit ( ) ;
return ret ;
}
static void __exit trng_exit ( void )
{
hwrng_unregister ( & trng_hwrng_dev ) ;
misc_deregister ( & trng_dev ) ;
trng_debug_exit ( ) ;
}
module_cpu_feature_match ( MSA , trng_init ) ;
module_exit ( trng_exit ) ;