2006-09-20 17:59:34 +04:00
/*
* drivers / s390 / char / monwriter . c
*
* Character device driver for writing z / VM * MONITOR service records .
*
* Copyright ( C ) IBM Corp . 2006
*
* Author ( s ) : Melissa Howland < Melissa . Howland @ us . ibm . com >
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/init.h>
# include <linux/errno.h>
2008-05-20 21:16:18 +04:00
# include <linux/smp_lock.h>
2006-09-20 17:59:34 +04:00
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/miscdevice.h>
# include <linux/ctype.h>
# include <linux/poll.h>
2007-08-10 16:32:35 +04:00
# include <linux/mutex.h>
2006-09-20 17:59:34 +04:00
# include <asm/uaccess.h>
# include <asm/ebcdic.h>
# include <asm/io.h>
# include <asm/appldata.h>
# include <asm/monwriter.h>
2006-12-28 02:35:25 +03:00
# define MONWRITE_MAX_DATALEN 4010
2006-09-20 17:59:34 +04:00
static int mon_max_bufs = 255 ;
2006-10-06 18:38:26 +04:00
static int mon_buf_count ;
2006-09-20 17:59:34 +04:00
struct mon_buf {
struct list_head list ;
struct monwrite_hdr hdr ;
int diag_done ;
char * data ;
} ;
struct mon_private {
struct list_head list ;
struct monwrite_hdr hdr ;
size_t hdr_to_read ;
size_t data_to_read ;
struct mon_buf * current_buf ;
2007-08-10 16:32:35 +04:00
struct mutex thread_mutex ;
2006-09-20 17:59:34 +04:00
} ;
/*
* helper functions
*/
static int monwrite_diag ( struct monwrite_hdr * myhdr , char * buffer , int fcn )
{
struct appldata_product_id id ;
int rc ;
strcpy ( id . prod_nr , " LNXAPPL " ) ;
id . prod_fn = myhdr - > applid ;
id . record_nr = myhdr - > record_num ;
id . version_nr = myhdr - > version ;
id . release_nr = myhdr - > release ;
id . mod_lvl = myhdr - > mod_level ;
rc = appldata_asm ( & id , fcn , ( void * ) buffer , myhdr - > datalen ) ;
if ( rc < = 0 )
return rc ;
if ( rc = = 5 )
return - EPERM ;
printk ( " DIAG X'DC' error with return code: %i \n " , rc ) ;
return - EINVAL ;
}
2007-02-05 23:18:53 +03:00
static struct mon_buf * monwrite_find_hdr ( struct mon_private * monpriv ,
struct monwrite_hdr * monhdr )
2006-09-20 17:59:34 +04:00
{
struct mon_buf * entry , * next ;
list_for_each_entry_safe ( entry , next , & monpriv - > list , list )
2006-10-18 20:30:49 +04:00
if ( ( entry - > hdr . mon_function = = monhdr - > mon_function | |
monhdr - > mon_function = = MONWRITE_STOP_INTERVAL ) & &
entry - > hdr . applid = = monhdr - > applid & &
2006-09-20 17:59:34 +04:00
entry - > hdr . record_num = = monhdr - > record_num & &
entry - > hdr . version = = monhdr - > version & &
entry - > hdr . release = = monhdr - > release & &
entry - > hdr . mod_level = = monhdr - > mod_level )
return entry ;
2006-10-18 20:30:49 +04:00
2006-09-20 17:59:34 +04:00
return NULL ;
}
static int monwrite_new_hdr ( struct mon_private * monpriv )
{
struct monwrite_hdr * monhdr = & monpriv - > hdr ;
struct mon_buf * monbuf ;
int rc ;
if ( monhdr - > datalen > MONWRITE_MAX_DATALEN | |
monhdr - > mon_function > MONWRITE_START_CONFIG | |
monhdr - > hdrlen ! = sizeof ( struct monwrite_hdr ) )
return - EINVAL ;
2006-10-18 20:30:49 +04:00
monbuf = NULL ;
if ( monhdr - > mon_function ! = MONWRITE_GEN_EVENT )
monbuf = monwrite_find_hdr ( monpriv , monhdr ) ;
2006-09-20 17:59:34 +04:00
if ( monbuf ) {
if ( monhdr - > mon_function = = MONWRITE_STOP_INTERVAL ) {
monhdr - > datalen = monbuf - > hdr . datalen ;
rc = monwrite_diag ( monhdr , monbuf - > data ,
APPLDATA_STOP_REC ) ;
list_del ( & monbuf - > list ) ;
2006-10-06 18:38:26 +04:00
mon_buf_count - - ;
2006-09-20 17:59:34 +04:00
kfree ( monbuf - > data ) ;
kfree ( monbuf ) ;
monbuf = NULL ;
}
2006-10-18 20:30:49 +04:00
} else if ( monhdr - > mon_function ! = MONWRITE_STOP_INTERVAL ) {
2006-10-06 18:38:26 +04:00
if ( mon_buf_count > = mon_max_bufs )
2006-09-20 17:59:34 +04:00
return - ENOSPC ;
monbuf = kzalloc ( sizeof ( struct mon_buf ) , GFP_KERNEL ) ;
if ( ! monbuf )
return - ENOMEM ;
2006-10-11 17:31:34 +04:00
monbuf - > data = kzalloc ( monhdr - > datalen ,
2006-09-20 17:59:34 +04:00
GFP_KERNEL | GFP_DMA ) ;
if ( ! monbuf - > data ) {
kfree ( monbuf ) ;
return - ENOMEM ;
}
monbuf - > hdr = * monhdr ;
list_add_tail ( & monbuf - > list , & monpriv - > list ) ;
2006-10-18 20:30:49 +04:00
if ( monhdr - > mon_function ! = MONWRITE_GEN_EVENT )
mon_buf_count + + ;
2006-09-20 17:59:34 +04:00
}
monpriv - > current_buf = monbuf ;
return 0 ;
}
static int monwrite_new_data ( struct mon_private * monpriv )
{
struct monwrite_hdr * monhdr = & monpriv - > hdr ;
struct mon_buf * monbuf = monpriv - > current_buf ;
int rc = 0 ;
switch ( monhdr - > mon_function ) {
case MONWRITE_START_INTERVAL :
if ( ! monbuf - > diag_done ) {
rc = monwrite_diag ( monhdr , monbuf - > data ,
APPLDATA_START_INTERVAL_REC ) ;
monbuf - > diag_done = 1 ;
}
break ;
case MONWRITE_START_CONFIG :
if ( ! monbuf - > diag_done ) {
rc = monwrite_diag ( monhdr , monbuf - > data ,
APPLDATA_START_CONFIG_REC ) ;
monbuf - > diag_done = 1 ;
}
break ;
case MONWRITE_GEN_EVENT :
rc = monwrite_diag ( monhdr , monbuf - > data ,
APPLDATA_GEN_EVENT_REC ) ;
list_del ( & monpriv - > current_buf - > list ) ;
kfree ( monpriv - > current_buf - > data ) ;
kfree ( monpriv - > current_buf ) ;
monpriv - > current_buf = NULL ;
break ;
default :
/* monhdr->mon_function is checked in monwrite_new_hdr */
BUG ( ) ;
}
return rc ;
}
/*
* file operations
*/
static int monwrite_open ( struct inode * inode , struct file * filp )
{
struct mon_private * monpriv ;
monpriv = kzalloc ( sizeof ( struct mon_private ) , GFP_KERNEL ) ;
if ( ! monpriv )
return - ENOMEM ;
2008-05-20 21:16:18 +04:00
lock_kernel ( ) ;
2006-09-20 17:59:34 +04:00
INIT_LIST_HEAD ( & monpriv - > list ) ;
monpriv - > hdr_to_read = sizeof ( monpriv - > hdr ) ;
2007-08-10 16:32:35 +04:00
mutex_init ( & monpriv - > thread_mutex ) ;
2006-09-20 17:59:34 +04:00
filp - > private_data = monpriv ;
2008-05-20 21:16:18 +04:00
unlock_kernel ( ) ;
2006-09-20 17:59:34 +04:00
return nonseekable_open ( inode , filp ) ;
}
static int monwrite_close ( struct inode * inode , struct file * filp )
{
struct mon_private * monpriv = filp - > private_data ;
struct mon_buf * entry , * next ;
list_for_each_entry_safe ( entry , next , & monpriv - > list , list ) {
if ( entry - > hdr . mon_function ! = MONWRITE_GEN_EVENT )
monwrite_diag ( & entry - > hdr , entry - > data ,
APPLDATA_STOP_REC ) ;
2006-10-06 18:38:26 +04:00
mon_buf_count - - ;
2006-09-20 17:59:34 +04:00
list_del ( & entry - > list ) ;
kfree ( entry - > data ) ;
kfree ( entry ) ;
}
kfree ( monpriv ) ;
return 0 ;
}
static ssize_t monwrite_write ( struct file * filp , const char __user * data ,
size_t count , loff_t * ppos )
{
struct mon_private * monpriv = filp - > private_data ;
size_t len , written ;
void * to ;
int rc ;
2007-08-10 16:32:35 +04:00
mutex_lock ( & monpriv - > thread_mutex ) ;
2006-09-20 17:59:34 +04:00
for ( written = 0 ; written < count ; ) {
if ( monpriv - > hdr_to_read ) {
len = min ( count - written , monpriv - > hdr_to_read ) ;
to = ( char * ) & monpriv - > hdr +
sizeof ( monpriv - > hdr ) - monpriv - > hdr_to_read ;
if ( copy_from_user ( to , data + written , len ) ) {
rc = - EFAULT ;
goto out_error ;
}
monpriv - > hdr_to_read - = len ;
written + = len ;
if ( monpriv - > hdr_to_read > 0 )
continue ;
rc = monwrite_new_hdr ( monpriv ) ;
if ( rc )
goto out_error ;
monpriv - > data_to_read = monpriv - > current_buf ?
monpriv - > current_buf - > hdr . datalen : 0 ;
}
if ( monpriv - > data_to_read ) {
len = min ( count - written , monpriv - > data_to_read ) ;
to = monpriv - > current_buf - > data +
monpriv - > hdr . datalen - monpriv - > data_to_read ;
if ( copy_from_user ( to , data + written , len ) ) {
rc = - EFAULT ;
goto out_error ;
}
monpriv - > data_to_read - = len ;
written + = len ;
if ( monpriv - > data_to_read > 0 )
continue ;
rc = monwrite_new_data ( monpriv ) ;
if ( rc )
goto out_error ;
}
monpriv - > hdr_to_read = sizeof ( monpriv - > hdr ) ;
}
2007-08-10 16:32:35 +04:00
mutex_unlock ( & monpriv - > thread_mutex ) ;
2006-09-20 17:59:34 +04:00
return written ;
out_error :
monpriv - > data_to_read = 0 ;
monpriv - > hdr_to_read = sizeof ( struct monwrite_hdr ) ;
2007-08-10 16:32:35 +04:00
mutex_unlock ( & monpriv - > thread_mutex ) ;
2006-09-20 17:59:34 +04:00
return rc ;
}
2007-02-12 11:55:34 +03:00
static const struct file_operations monwrite_fops = {
2006-09-20 17:59:34 +04:00
. owner = THIS_MODULE ,
. open = & monwrite_open ,
. release = & monwrite_close ,
. write = & monwrite_write ,
} ;
static struct miscdevice mon_dev = {
. name = " monwriter " ,
. fops = & monwrite_fops ,
. minor = MISC_DYNAMIC_MINOR ,
} ;
/*
* module init / exit
*/
static int __init mon_init ( void )
{
if ( MACHINE_IS_VM )
return misc_register ( & mon_dev ) ;
else
return - ENODEV ;
}
static void __exit mon_exit ( void )
{
WARN_ON ( misc_deregister ( & mon_dev ) ! = 0 ) ;
}
module_init ( mon_init ) ;
module_exit ( mon_exit ) ;
module_param_named ( max_bufs , mon_max_bufs , int , 0644 ) ;
2008-01-26 16:11:10 +03:00
MODULE_PARM_DESC ( max_bufs , " Maximum number of sample monitor data buffers "
2006-09-20 17:59:34 +04:00
" that can be active at one time " ) ;
MODULE_AUTHOR ( " Melissa Howland <Melissa.Howland@us.ibm.com> " ) ;
MODULE_DESCRIPTION ( " Character device driver for writing z/VM "
" APPLDATA monitor records. " ) ;
MODULE_LICENSE ( " GPL " ) ;