2005-04-17 02:20:36 +04:00
/*
* drivers / s390 / char / vmlogrdr . c
* character device driver for reading z / VM system service records
*
*
2007-02-09 00:40:41 +03:00
* Copyright 2004 IBM Corporation
2005-04-17 02:20:36 +04:00
* character device driver for reading z / VM system service records ,
* Version 1.0
* Author ( s ) : Xenia Tkatschow < xenia @ us . ibm . com >
* Stefan Weinhuber < wein @ de . ibm . com >
*
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/types.h>
# include <linux/interrupt.h>
# include <linux/spinlock.h>
# include <asm/atomic.h>
# include <asm/uaccess.h>
# include <asm/cpcmd.h>
# include <asm/debug.h>
# include <asm/ebcdic.h>
2007-02-09 00:40:41 +03:00
# include <net/iucv/iucv.h>
2005-04-17 02:20:36 +04:00
# include <linux/kmod.h>
# include <linux/cdev.h>
# include <linux/device.h>
# include <linux/string.h>
MODULE_AUTHOR
( " (C) 2004 IBM Corporation by Xenia Tkatschow (xenia@us.ibm.com) \n "
" Stefan Weinhuber (wein@de.ibm.com) " ) ;
MODULE_DESCRIPTION ( " Character device driver for reading z/VM "
" system service records. " ) ;
MODULE_LICENSE ( " GPL " ) ;
/*
* The size of the buffer for iucv data transfer is one page ,
* but in addition to the data we read from iucv we also
* place an integer and some characters into that buffer ,
* so the maximum size for record data is a little less then
* one page .
*/
# define NET_BUFFER_SIZE (PAGE_SIZE - sizeof(int) - sizeof(FENCE))
/*
* The elements that are concurrently accessed by bottom halves are
* connection_established , iucv_path_severed , local_interrupt_buffer
* and receive_ready . The first three can be protected by
* priv_lock . receive_ready is atomic , so it can be incremented and
* decremented without holding a lock .
* The variable dev_in_use needs to be protected by the lock , since
* it ' s a flag used by open to make sure that the device is opened only
* by one user at the same time .
*/
struct vmlogrdr_priv_t {
char system_service [ 8 ] ;
char internal_name [ 8 ] ;
char recording_name [ 8 ] ;
2007-02-09 00:40:41 +03:00
struct iucv_path * path ;
2005-04-17 02:20:36 +04:00
int connection_established ;
int iucv_path_severed ;
2007-02-09 00:40:41 +03:00
struct iucv_message local_interrupt_buffer ;
2005-04-17 02:20:36 +04:00
atomic_t receive_ready ;
int minor_num ;
char * buffer ;
char * current_position ;
int remaining ;
ulong residual_length ;
int buffer_free ;
int dev_in_use ; /* 1: already opened, 0: not opened*/
spinlock_t priv_lock ;
struct device * device ;
2007-10-22 14:52:42 +04:00
struct device * class_device ;
2005-04-17 02:20:36 +04:00
int autorecording ;
int autopurge ;
} ;
/*
* File operation structure for vmlogrdr devices
*/
static int vmlogrdr_open ( struct inode * , struct file * ) ;
static int vmlogrdr_release ( struct inode * , struct file * ) ;
2006-07-12 18:41:55 +04:00
static ssize_t vmlogrdr_read ( struct file * filp , char __user * data ,
size_t count , loff_t * ppos ) ;
2005-04-17 02:20:36 +04:00
2007-02-12 11:55:34 +03:00
static const struct file_operations vmlogrdr_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. open = vmlogrdr_open ,
. release = vmlogrdr_release ,
. read = vmlogrdr_read ,
} ;
2007-02-09 00:40:41 +03:00
static void vmlogrdr_iucv_path_complete ( struct iucv_path * , u8 ipuser [ 16 ] ) ;
static void vmlogrdr_iucv_path_severed ( struct iucv_path * , u8 ipuser [ 16 ] ) ;
static void vmlogrdr_iucv_message_pending ( struct iucv_path * ,
struct iucv_message * ) ;
2005-04-17 02:20:36 +04:00
2007-02-09 00:40:41 +03:00
static struct iucv_handler vmlogrdr_iucv_handler = {
. path_complete = vmlogrdr_iucv_path_complete ,
. path_severed = vmlogrdr_iucv_path_severed ,
. message_pending = vmlogrdr_iucv_message_pending ,
2005-04-17 02:20:36 +04:00
} ;
2007-02-05 23:16:47 +03:00
static DECLARE_WAIT_QUEUE_HEAD ( conn_wait_queue ) ;
static DECLARE_WAIT_QUEUE_HEAD ( read_wait_queue ) ;
2005-04-17 02:20:36 +04:00
/*
* pointer to system service private structure
* minor number 0 - - > logrec
* minor number 1 - - > account
* minor number 2 - - > symptom
*/
static struct vmlogrdr_priv_t sys_ser [ ] = {
{ . system_service = " *LOGREC " ,
. internal_name = " logrec " ,
. recording_name = " EREP " ,
. minor_num = 0 ,
. buffer_free = 1 ,
2007-04-27 18:02:01 +04:00
. priv_lock = __SPIN_LOCK_UNLOCKED ( sys_ser [ 0 ] . priv_lock ) ,
2005-04-17 02:20:36 +04:00
. autorecording = 1 ,
. autopurge = 1 ,
} ,
{ . system_service = " *ACCOUNT " ,
. internal_name = " account " ,
. recording_name = " ACCOUNT " ,
. minor_num = 1 ,
. buffer_free = 1 ,
2007-04-27 18:02:01 +04:00
. priv_lock = __SPIN_LOCK_UNLOCKED ( sys_ser [ 1 ] . priv_lock ) ,
2005-04-17 02:20:36 +04:00
. autorecording = 1 ,
. autopurge = 1 ,
} ,
{ . system_service = " *SYMPTOM " ,
. internal_name = " symptom " ,
. recording_name = " SYMPTOM " ,
. minor_num = 2 ,
. buffer_free = 1 ,
2007-04-27 18:02:01 +04:00
. priv_lock = __SPIN_LOCK_UNLOCKED ( sys_ser [ 2 ] . priv_lock ) ,
2005-04-17 02:20:36 +04:00
. autorecording = 1 ,
. autopurge = 1 ,
}
} ;
# define MAXMINOR (sizeof(sys_ser) / sizeof(struct vmlogrdr_priv_t))
static char FENCE [ ] = { " EOR " } ;
static int vmlogrdr_major = 0 ;
static struct cdev * vmlogrdr_cdev = NULL ;
static int recording_class_AB ;
2007-02-09 00:40:41 +03:00
static void vmlogrdr_iucv_path_complete ( struct iucv_path * path , u8 ipuser [ 16 ] )
2005-04-17 02:20:36 +04:00
{
2007-02-09 00:40:41 +03:00
struct vmlogrdr_priv_t * logptr = path - > private ;
2005-04-17 02:20:36 +04:00
spin_lock ( & logptr - > priv_lock ) ;
logptr - > connection_established = 1 ;
spin_unlock ( & logptr - > priv_lock ) ;
wake_up ( & conn_wait_queue ) ;
}
2007-02-09 00:40:41 +03:00
static void vmlogrdr_iucv_path_severed ( struct iucv_path * path , u8 ipuser [ 16 ] )
2005-04-17 02:20:36 +04:00
{
2007-02-09 00:40:41 +03:00
struct vmlogrdr_priv_t * logptr = path - > private ;
u8 reason = ( u8 ) ipuser [ 8 ] ;
2005-04-17 02:20:36 +04:00
printk ( KERN_ERR " vmlogrdr: connection severed with "
" reason %i \n " , reason ) ;
2007-02-09 00:40:41 +03:00
iucv_path_sever ( path , NULL ) ;
kfree ( path ) ;
logptr - > path = NULL ;
2005-04-17 02:20:36 +04:00
spin_lock ( & logptr - > priv_lock ) ;
logptr - > connection_established = 0 ;
logptr - > iucv_path_severed = 1 ;
spin_unlock ( & logptr - > priv_lock ) ;
wake_up ( & conn_wait_queue ) ;
/* just in case we're sleeping waiting for a record */
wake_up_interruptible ( & read_wait_queue ) ;
}
2007-02-09 00:40:41 +03:00
static void vmlogrdr_iucv_message_pending ( struct iucv_path * path ,
struct iucv_message * msg )
2005-04-17 02:20:36 +04:00
{
2007-02-09 00:40:41 +03:00
struct vmlogrdr_priv_t * logptr = path - > private ;
2005-04-17 02:20:36 +04:00
/*
* This function is the bottom half so it should be quick .
* Copy the external interrupt data into our local eib and increment
* the usage count
*/
spin_lock ( & logptr - > priv_lock ) ;
2007-02-09 00:40:41 +03:00
memcpy ( & logptr - > local_interrupt_buffer , msg , sizeof ( * msg ) ) ;
2005-04-17 02:20:36 +04:00
atomic_inc ( & logptr - > receive_ready ) ;
spin_unlock ( & logptr - > priv_lock ) ;
wake_up_interruptible ( & read_wait_queue ) ;
}
2007-02-09 00:40:41 +03:00
static int vmlogrdr_get_recording_class_AB ( void )
{
2005-04-17 02:20:36 +04:00
char cp_command [ ] = " QUERY COMMAND RECORDING " ;
char cp_response [ 80 ] ;
char * tail ;
int len , i ;
printk ( KERN_DEBUG " vmlogrdr: query command: %s \n " , cp_command ) ;
2005-06-26 01:55:32 +04:00
cpcmd ( cp_command , cp_response , sizeof ( cp_response ) , NULL ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_DEBUG " vmlogrdr: response: %s " , cp_response ) ;
len = strnlen ( cp_response , sizeof ( cp_response ) ) ;
// now the parsing
tail = strnchr ( cp_response , len , ' = ' ) ;
if ( ! tail )
return 0 ;
tail + + ;
if ( ! strncmp ( " ANY " , tail , 3 ) )
return 1 ;
if ( ! strncmp ( " NONE " , tail , 4 ) )
return 0 ;
/*
* expect comma separated list of classes here , if one of them
* is A or B return 1 otherwise 0
*/
for ( i = tail - cp_response ; i < len ; i + + )
if ( cp_response [ i ] = = ' A ' | | cp_response [ i ] = = ' B ' )
return 1 ;
return 0 ;
}
2007-02-09 00:40:41 +03:00
static int vmlogrdr_recording ( struct vmlogrdr_priv_t * logptr ,
int action , int purge )
{
2005-04-17 02:20:36 +04:00
char cp_command [ 80 ] ;
char cp_response [ 160 ] ;
char * onoff , * qid_string ;
memset ( cp_command , 0x00 , sizeof ( cp_command ) ) ;
memset ( cp_response , 0x00 , sizeof ( cp_response ) ) ;
onoff = ( ( action = = 1 ) ? " ON " : " OFF " ) ;
qid_string = ( ( recording_class_AB = = 1 ) ? " QID * " : " " ) ;
/*
* The recording commands needs to be called with option QID
* for guests that have previlege classes A or B .
* Purging has to be done as separate step , because recording
* can ' t be switched on as long as records are on the queue .
* Doing both at the same time doesn ' t work .
*/
if ( purge ) {
snprintf ( cp_command , sizeof ( cp_command ) ,
" RECORDING %s PURGE %s " ,
logptr - > recording_name ,
qid_string ) ;
printk ( KERN_DEBUG " vmlogrdr: recording command: %s \n " ,
cp_command ) ;
2005-06-26 01:55:32 +04:00
cpcmd ( cp_command , cp_response , sizeof ( cp_response ) , NULL ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_DEBUG " vmlogrdr: recording response: %s " ,
cp_response ) ;
}
memset ( cp_command , 0x00 , sizeof ( cp_command ) ) ;
memset ( cp_response , 0x00 , sizeof ( cp_response ) ) ;
snprintf ( cp_command , sizeof ( cp_command ) , " RECORDING %s %s %s " ,
logptr - > recording_name ,
onoff ,
qid_string ) ;
printk ( KERN_DEBUG " vmlogrdr: recording command: %s \n " , cp_command ) ;
2005-06-26 01:55:32 +04:00
cpcmd ( cp_command , cp_response , sizeof ( cp_response ) , NULL ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_DEBUG " vmlogrdr: recording response: %s " ,
cp_response ) ;
/* The recording command will usually answer with 'Command complete'
* on success , but when the specific service was never connected
* before then there might be an additional informational message
* ' HCPCRC8072I Recording entry not found ' before the
* ' Command complete ' . So I use strstr rather then the strncmp .
*/
if ( strstr ( cp_response , " Command complete " ) )
return 0 ;
else
return - EIO ;
}
2007-02-09 00:40:41 +03:00
static int vmlogrdr_open ( struct inode * inode , struct file * filp )
2005-04-17 02:20:36 +04:00
{
int dev_num = 0 ;
struct vmlogrdr_priv_t * logptr = NULL ;
int connect_rc = 0 ;
int ret ;
dev_num = iminor ( inode ) ;
if ( dev_num > MAXMINOR )
return - ENODEV ;
logptr = & sys_ser [ dev_num ] ;
/*
* only allow for blocking reads to be open
*/
if ( filp - > f_flags & O_NONBLOCK )
return - ENOSYS ;
/* Besure this device hasn't already been opened */
spin_lock_bh ( & logptr - > priv_lock ) ;
if ( logptr - > dev_in_use ) {
spin_unlock_bh ( & logptr - > priv_lock ) ;
return - EBUSY ;
}
2007-02-09 00:40:41 +03:00
logptr - > dev_in_use = 1 ;
logptr - > connection_established = 0 ;
logptr - > iucv_path_severed = 0 ;
2005-04-17 02:20:36 +04:00
atomic_set ( & logptr - > receive_ready , 0 ) ;
logptr - > buffer_free = 1 ;
2007-02-09 00:40:41 +03:00
spin_unlock_bh ( & logptr - > priv_lock ) ;
2005-04-17 02:20:36 +04:00
/* set the file options */
filp - > private_data = logptr ;
filp - > f_op = & vmlogrdr_fops ;
/* start recording for this service*/
2007-02-09 00:40:41 +03:00
if ( logptr - > autorecording ) {
2005-04-17 02:20:36 +04:00
ret = vmlogrdr_recording ( logptr , 1 , logptr - > autopurge ) ;
2007-02-09 00:40:41 +03:00
if ( ret )
printk ( KERN_WARNING " vmlogrdr: failed to start "
" recording automatically \n " ) ;
2005-04-17 02:20:36 +04:00
}
/* create connection to the system service */
2007-02-09 00:40:41 +03:00
logptr - > path = iucv_path_alloc ( 10 , 0 , GFP_KERNEL ) ;
if ( ! logptr - > path )
goto out_dev ;
connect_rc = iucv_path_connect ( logptr - > path , & vmlogrdr_iucv_handler ,
logptr - > system_service , NULL , NULL ,
logptr ) ;
2005-04-17 02:20:36 +04:00
if ( connect_rc ) {
printk ( KERN_ERR " vmlogrdr: iucv connection to %s "
" failed with rc %i \n " , logptr - > system_service ,
connect_rc ) ;
2007-02-09 00:40:41 +03:00
goto out_path ;
2005-04-17 02:20:36 +04:00
}
/* We've issued the connect and now we must wait for a
* ConnectionComplete or ConnectinSevered Interrupt
* before we can continue to process .
*/
wait_event ( conn_wait_queue , ( logptr - > connection_established )
| | ( logptr - > iucv_path_severed ) ) ;
2007-02-09 00:40:41 +03:00
if ( logptr - > iucv_path_severed )
goto out_record ;
2005-04-17 02:20:36 +04:00
return nonseekable_open ( inode , filp ) ;
2007-02-09 00:40:41 +03:00
out_record :
2005-04-17 02:20:36 +04:00
if ( logptr - > autorecording )
vmlogrdr_recording ( logptr , 0 , logptr - > autopurge ) ;
2007-02-09 00:40:41 +03:00
out_path :
kfree ( logptr - > path ) ; /* kfree(NULL) is ok. */
logptr - > path = NULL ;
out_dev :
2005-04-17 02:20:36 +04:00
logptr - > dev_in_use = 0 ;
return - EIO ;
}
2007-02-09 00:40:41 +03:00
static int vmlogrdr_release ( struct inode * inode , struct file * filp )
2005-04-17 02:20:36 +04:00
{
int ret ;
struct vmlogrdr_priv_t * logptr = filp - > private_data ;
2007-04-27 18:01:52 +04:00
iucv_path_sever ( logptr - > path , NULL ) ;
kfree ( logptr - > path ) ;
logptr - > path = NULL ;
2005-04-17 02:20:36 +04:00
if ( logptr - > autorecording ) {
ret = vmlogrdr_recording ( logptr , 0 , logptr - > autopurge ) ;
if ( ret )
printk ( KERN_WARNING " vmlogrdr: failed to stop "
" recording automatically \n " ) ;
}
logptr - > dev_in_use = 0 ;
return 0 ;
}
2007-02-09 00:40:41 +03:00
static int vmlogrdr_receive_data ( struct vmlogrdr_priv_t * priv )
{
2005-04-17 02:20:36 +04:00
int rc , * temp ;
/* we need to keep track of two data sizes here:
* The number of bytes we need to receive from iucv and
* the total number of bytes we actually write into the buffer .
*/
int user_data_count , iucv_data_count ;
char * buffer ;
if ( atomic_read ( & priv - > receive_ready ) ) {
spin_lock_bh ( & priv - > priv_lock ) ;
if ( priv - > residual_length ) {
/* receive second half of a record */
iucv_data_count = priv - > residual_length ;
user_data_count = 0 ;
buffer = priv - > buffer ;
} else {
/* receive a new record:
* We need to return the total length of the record
* + size of FENCE in the first 4 bytes of the buffer .
*/
2007-02-09 00:40:41 +03:00
iucv_data_count = priv - > local_interrupt_buffer . length ;
2005-04-17 02:20:36 +04:00
user_data_count = sizeof ( int ) ;
temp = ( int * ) priv - > buffer ;
* temp = iucv_data_count + sizeof ( FENCE ) ;
buffer = priv - > buffer + sizeof ( int ) ;
}
/*
* If the record is bigger then our buffer , we receive only
* a part of it . We can get the rest later .
*/
if ( iucv_data_count > NET_BUFFER_SIZE )
iucv_data_count = NET_BUFFER_SIZE ;
2007-02-09 00:40:41 +03:00
rc = iucv_message_receive ( priv - > path ,
& priv - > local_interrupt_buffer ,
0 , buffer , iucv_data_count ,
& priv - > residual_length ) ;
2005-04-17 02:20:36 +04:00
spin_unlock_bh ( & priv - > priv_lock ) ;
/* An rc of 5 indicates that the record was bigger then
* the buffer , which is OK for us . A 9 indicates that the
* record was purged befor we could receive it .
*/
if ( rc = = 5 )
rc = 0 ;
if ( rc = = 9 )
atomic_set ( & priv - > receive_ready , 0 ) ;
} else {
rc = 1 ;
}
if ( ! rc ) {
priv - > buffer_free = 0 ;
user_data_count + = iucv_data_count ;
priv - > current_position = priv - > buffer ;
if ( priv - > residual_length = = 0 ) {
/* the whole record has been captured,
* now add the fence */
atomic_dec ( & priv - > receive_ready ) ;
buffer = priv - > buffer + user_data_count ;
memcpy ( buffer , FENCE , sizeof ( FENCE ) ) ;
user_data_count + = sizeof ( FENCE ) ;
}
priv - > remaining = user_data_count ;
}
return rc ;
}
2007-02-09 00:40:41 +03:00
static ssize_t vmlogrdr_read ( struct file * filp , char __user * data ,
size_t count , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
int rc ;
struct vmlogrdr_priv_t * priv = filp - > private_data ;
while ( priv - > buffer_free ) {
rc = vmlogrdr_receive_data ( priv ) ;
if ( rc ) {
rc = wait_event_interruptible ( read_wait_queue ,
atomic_read ( & priv - > receive_ready ) ) ;
if ( rc )
return rc ;
}
}
/* copy only up to end of record */
if ( count > priv - > remaining )
count = priv - > remaining ;
if ( copy_to_user ( data , priv - > current_position , count ) )
return - EFAULT ;
* ppos + = count ;
priv - > current_position + = count ;
priv - > remaining - = count ;
/* if all data has been transferred, set buffer free */
if ( priv - > remaining = = 0 )
priv - > buffer_free = 1 ;
return count ;
}
2007-02-09 00:40:41 +03:00
static ssize_t vmlogrdr_autopurge_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
2005-04-17 02:20:36 +04:00
struct vmlogrdr_priv_t * priv = dev - > driver_data ;
ssize_t ret = count ;
switch ( buf [ 0 ] ) {
case ' 0 ' :
priv - > autopurge = 0 ;
break ;
case ' 1 ' :
priv - > autopurge = 1 ;
break ;
default :
ret = - EINVAL ;
}
return ret ;
}
2007-02-09 00:40:41 +03:00
static ssize_t vmlogrdr_autopurge_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
2005-04-17 02:20:36 +04:00
struct vmlogrdr_priv_t * priv = dev - > driver_data ;
return sprintf ( buf , " %u \n " , priv - > autopurge ) ;
}
static DEVICE_ATTR ( autopurge , 0644 , vmlogrdr_autopurge_show ,
vmlogrdr_autopurge_store ) ;
2007-02-09 00:40:41 +03:00
static ssize_t vmlogrdr_purge_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
2005-04-17 02:20:36 +04:00
char cp_command [ 80 ] ;
char cp_response [ 80 ] ;
struct vmlogrdr_priv_t * priv = dev - > driver_data ;
if ( buf [ 0 ] ! = ' 1 ' )
return - EINVAL ;
memset ( cp_command , 0x00 , sizeof ( cp_command ) ) ;
memset ( cp_response , 0x00 , sizeof ( cp_response ) ) ;
/*
* The recording command needs to be called with option QID
* for guests that have previlege classes A or B .
* Other guests will not recognize the command and we have to
* issue the same command without the QID parameter .
*/
if ( recording_class_AB )
snprintf ( cp_command , sizeof ( cp_command ) ,
" RECORDING %s PURGE QID * " ,
priv - > recording_name ) ;
else
snprintf ( cp_command , sizeof ( cp_command ) ,
" RECORDING %s PURGE " ,
priv - > recording_name ) ;
printk ( KERN_DEBUG " vmlogrdr: recording command: %s \n " , cp_command ) ;
2005-06-26 01:55:32 +04:00
cpcmd ( cp_command , cp_response , sizeof ( cp_response ) , NULL ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_DEBUG " vmlogrdr: recording response: %s " ,
cp_response ) ;
return count ;
}
static DEVICE_ATTR ( purge , 0200 , NULL , vmlogrdr_purge_store ) ;
2007-02-09 00:40:41 +03:00
static ssize_t vmlogrdr_autorecording_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
2005-04-17 02:20:36 +04:00
struct vmlogrdr_priv_t * priv = dev - > driver_data ;
ssize_t ret = count ;
switch ( buf [ 0 ] ) {
case ' 0 ' :
priv - > autorecording = 0 ;
break ;
case ' 1 ' :
priv - > autorecording = 1 ;
break ;
default :
ret = - EINVAL ;
}
return ret ;
}
2007-02-09 00:40:41 +03:00
static ssize_t vmlogrdr_autorecording_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
2005-04-17 02:20:36 +04:00
struct vmlogrdr_priv_t * priv = dev - > driver_data ;
return sprintf ( buf , " %u \n " , priv - > autorecording ) ;
}
static DEVICE_ATTR ( autorecording , 0644 , vmlogrdr_autorecording_show ,
vmlogrdr_autorecording_store ) ;
2007-02-09 00:40:41 +03:00
static ssize_t vmlogrdr_recording_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
2005-04-17 02:20:36 +04:00
struct vmlogrdr_priv_t * priv = dev - > driver_data ;
ssize_t ret ;
switch ( buf [ 0 ] ) {
case ' 0 ' :
ret = vmlogrdr_recording ( priv , 0 , 0 ) ;
break ;
case ' 1 ' :
ret = vmlogrdr_recording ( priv , 1 , 0 ) ;
break ;
default :
ret = - EINVAL ;
}
if ( ret )
return ret ;
else
return count ;
}
static DEVICE_ATTR ( recording , 0200 , NULL , vmlogrdr_recording_store ) ;
2007-02-09 00:40:41 +03:00
static ssize_t vmlogrdr_recording_status_show ( struct device_driver * driver ,
char * buf )
{
2005-04-17 02:20:36 +04:00
char cp_command [ ] = " QUERY RECORDING " ;
int len ;
2005-06-26 01:55:32 +04:00
cpcmd ( cp_command , buf , 4096 , NULL ) ;
2005-04-17 02:20:36 +04:00
len = strlen ( buf ) ;
return len ;
}
static DRIVER_ATTR ( recording_status , 0444 , vmlogrdr_recording_status_show ,
NULL ) ;
static struct attribute * vmlogrdr_attrs [ ] = {
& dev_attr_autopurge . attr ,
& dev_attr_purge . attr ,
& dev_attr_autorecording . attr ,
& dev_attr_recording . attr ,
NULL ,
} ;
static struct attribute_group vmlogrdr_attr_group = {
. attrs = vmlogrdr_attrs ,
} ;
2005-03-23 21:01:41 +03:00
static struct class * vmlogrdr_class ;
2005-04-17 02:20:36 +04:00
static struct device_driver vmlogrdr_driver = {
. name = " vmlogrdr " ,
. bus = & iucv_bus ,
} ;
2007-02-09 00:40:41 +03:00
static int vmlogrdr_register_driver ( void )
{
2005-04-17 02:20:36 +04:00
int ret ;
2007-02-09 00:40:41 +03:00
/* Register with iucv driver */
ret = iucv_register ( & vmlogrdr_iucv_handler , 1 ) ;
if ( ret ) {
2008-01-26 16:11:10 +03:00
printk ( KERN_ERR " vmlogrdr: failed to register with "
2007-02-09 00:40:41 +03:00
" iucv driver \n " ) ;
goto out ;
}
2005-04-17 02:20:36 +04:00
ret = driver_register ( & vmlogrdr_driver ) ;
if ( ret ) {
printk ( KERN_ERR " vmlogrdr: failed to register driver. \n " ) ;
2007-02-09 00:40:41 +03:00
goto out_iucv ;
2005-04-17 02:20:36 +04:00
}
ret = driver_create_file ( & vmlogrdr_driver ,
& driver_attr_recording_status ) ;
if ( ret ) {
printk ( KERN_ERR " vmlogrdr: failed to add driver attribute. \n " ) ;
2007-02-09 00:40:41 +03:00
goto out_driver ;
2005-04-17 02:20:36 +04:00
}
2005-03-23 21:01:41 +03:00
vmlogrdr_class = class_create ( THIS_MODULE , " vmlogrdr " ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( vmlogrdr_class ) ) {
printk ( KERN_ERR " vmlogrdr: failed to create class. \n " ) ;
2007-02-09 00:40:41 +03:00
ret = PTR_ERR ( vmlogrdr_class ) ;
vmlogrdr_class = NULL ;
goto out_attr ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
2007-02-09 00:40:41 +03:00
out_attr :
2005-04-17 02:20:36 +04:00
driver_remove_file ( & vmlogrdr_driver , & driver_attr_recording_status ) ;
2007-02-09 00:40:41 +03:00
out_driver :
2005-04-17 02:20:36 +04:00
driver_unregister ( & vmlogrdr_driver ) ;
2007-02-09 00:40:41 +03:00
out_iucv :
iucv_unregister ( & vmlogrdr_iucv_handler , 1 ) ;
out :
2005-04-17 02:20:36 +04:00
return ret ;
}
2007-02-09 00:40:41 +03:00
static void vmlogrdr_unregister_driver ( void )
{
2005-03-23 21:01:41 +03:00
class_destroy ( vmlogrdr_class ) ;
2005-04-17 02:20:36 +04:00
vmlogrdr_class = NULL ;
driver_remove_file ( & vmlogrdr_driver , & driver_attr_recording_status ) ;
driver_unregister ( & vmlogrdr_driver ) ;
2007-02-09 00:40:41 +03:00
iucv_unregister ( & vmlogrdr_iucv_handler , 1 ) ;
2005-04-17 02:20:36 +04:00
}
2007-02-09 00:40:41 +03:00
static int vmlogrdr_register_device ( struct vmlogrdr_priv_t * priv )
{
2005-04-17 02:20:36 +04:00
struct device * dev ;
int ret ;
2006-03-24 14:15:31 +03:00
dev = kzalloc ( sizeof ( struct device ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( dev ) {
snprintf ( dev - > bus_id , BUS_ID_SIZE , " %s " ,
priv - > internal_name ) ;
dev - > bus = & iucv_bus ;
dev - > parent = iucv_root ;
dev - > driver = & vmlogrdr_driver ;
/*
* The release function could be called after the
* module has been unloaded . It ' s _only_ task is to
* free the struct . Therefore , we specify kfree ( )
* directly here . ( Probably a little bit obfuscating
* but legitime . . . ) .
*/
dev - > release = ( void ( * ) ( struct device * ) ) kfree ;
} else
return - ENOMEM ;
ret = device_register ( dev ) ;
if ( ret )
return ret ;
ret = sysfs_create_group ( & dev - > kobj , & vmlogrdr_attr_group ) ;
if ( ret ) {
device_unregister ( dev ) ;
return ret ;
}
2007-10-22 14:52:42 +04:00
priv - > class_device = device_create ( vmlogrdr_class , dev ,
MKDEV ( vmlogrdr_major ,
priv - > minor_num ) ,
" %s " , dev - > bus_id ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( priv - > class_device ) ) {
ret = PTR_ERR ( priv - > class_device ) ;
priv - > class_device = NULL ;
sysfs_remove_group ( & dev - > kobj , & vmlogrdr_attr_group ) ;
device_unregister ( dev ) ;
return ret ;
}
dev - > driver_data = priv ;
priv - > device = dev ;
return 0 ;
}
2007-02-09 00:40:41 +03:00
static int vmlogrdr_unregister_device ( struct vmlogrdr_priv_t * priv )
{
2007-10-22 14:52:42 +04:00
device_destroy ( vmlogrdr_class , MKDEV ( vmlogrdr_major , priv - > minor_num ) ) ;
2005-04-17 02:20:36 +04:00
if ( priv - > device ! = NULL ) {
sysfs_remove_group ( & priv - > device - > kobj , & vmlogrdr_attr_group ) ;
device_unregister ( priv - > device ) ;
priv - > device = NULL ;
}
return 0 ;
}
2007-02-09 00:40:41 +03:00
static int vmlogrdr_register_cdev ( dev_t dev )
{
2005-04-17 02:20:36 +04:00
int rc = 0 ;
vmlogrdr_cdev = cdev_alloc ( ) ;
if ( ! vmlogrdr_cdev ) {
return - ENOMEM ;
}
vmlogrdr_cdev - > owner = THIS_MODULE ;
vmlogrdr_cdev - > ops = & vmlogrdr_fops ;
vmlogrdr_cdev - > dev = dev ;
rc = cdev_add ( vmlogrdr_cdev , vmlogrdr_cdev - > dev , MAXMINOR ) ;
if ( ! rc )
return 0 ;
// cleanup: cdev is not fully registered, no cdev_del here!
kobject_put ( & vmlogrdr_cdev - > kobj ) ;
vmlogrdr_cdev = NULL ;
return rc ;
}
2007-02-09 00:40:41 +03:00
static void vmlogrdr_cleanup ( void )
{
2005-04-17 02:20:36 +04:00
int i ;
2007-02-09 00:40:41 +03:00
2005-04-17 02:20:36 +04:00
if ( vmlogrdr_cdev ) {
cdev_del ( vmlogrdr_cdev ) ;
vmlogrdr_cdev = NULL ;
}
for ( i = 0 ; i < MAXMINOR ; + + i ) {
vmlogrdr_unregister_device ( & sys_ser [ i ] ) ;
free_page ( ( unsigned long ) sys_ser [ i ] . buffer ) ;
}
vmlogrdr_unregister_driver ( ) ;
if ( vmlogrdr_major ) {
unregister_chrdev_region ( MKDEV ( vmlogrdr_major , 0 ) , MAXMINOR ) ;
vmlogrdr_major = 0 ;
}
}
2007-07-10 13:24:22 +04:00
static int __init vmlogrdr_init ( void )
2005-04-17 02:20:36 +04:00
{
int rc ;
int i ;
dev_t dev ;
if ( ! MACHINE_IS_VM ) {
printk ( KERN_ERR " vmlogrdr: not running under VM, "
" driver not loaded. \n " ) ;
return - ENODEV ;
}
recording_class_AB = vmlogrdr_get_recording_class_AB ( ) ;
rc = alloc_chrdev_region ( & dev , 0 , MAXMINOR , " vmlogrdr " ) ;
if ( rc )
return rc ;
vmlogrdr_major = MAJOR ( dev ) ;
rc = vmlogrdr_register_driver ( ) ;
if ( rc )
goto cleanup ;
for ( i = 0 ; i < MAXMINOR ; + + i ) {
sys_ser [ i ] . buffer = ( char * ) get_zeroed_page ( GFP_KERNEL ) ;
if ( ! sys_ser [ i ] . buffer ) {
2008-05-15 18:52:32 +04:00
rc = - ENOMEM ;
2005-04-17 02:20:36 +04:00
break ;
}
sys_ser [ i ] . current_position = sys_ser [ i ] . buffer ;
rc = vmlogrdr_register_device ( & sys_ser [ i ] ) ;
if ( rc )
break ;
}
if ( rc )
goto cleanup ;
rc = vmlogrdr_register_cdev ( dev ) ;
if ( rc )
goto cleanup ;
printk ( KERN_INFO " vmlogrdr: driver loaded \n " ) ;
return 0 ;
cleanup :
vmlogrdr_cleanup ( ) ;
printk ( KERN_ERR " vmlogrdr: driver not loaded. \n " ) ;
return rc ;
}
2007-07-10 13:24:22 +04:00
static void __exit vmlogrdr_exit ( void )
2005-04-17 02:20:36 +04:00
{
vmlogrdr_cleanup ( ) ;
printk ( KERN_INFO " vmlogrdr: driver unloaded \n " ) ;
return ;
}
module_init ( vmlogrdr_init ) ;
module_exit ( vmlogrdr_exit ) ;