2005-06-25 14:55:32 -07:00
/*
* Copyright ( C ) 2004 , 2005 IBM Corporation
2006-09-20 15:59:24 +02:00
* Interface implementation for communication with the z / VM control program
2005-06-25 14:55:32 -07:00
* Author ( s ) : Christian Borntraeger < cborntra @ de . ibm . com >
*
*
* z / VMs CP offers the possibility to issue commands via the diagnose code 8
* this driver implements a character device that issues these commands and
* returns the answer of CP .
* The idea of this driver is based on cpint from Neale Ferguson and # CP in CMS
*/
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <asm/cpcmd.h>
# include <asm/debug.h>
# include <asm/uaccess.h>
# include "vmcp.h"
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Christian Borntraeger <cborntra@de.ibm.com> " ) ;
MODULE_DESCRIPTION ( " z/VM CP interface " ) ;
static debug_info_t * vmcp_debug ;
static int vmcp_open ( struct inode * inode , struct file * file )
{
struct vmcp_session * session ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
session = kmalloc ( sizeof ( * session ) , GFP_KERNEL ) ;
if ( ! session )
return - ENOMEM ;
session - > bufsize = PAGE_SIZE ;
session - > response = NULL ;
session - > resp_size = 0 ;
init_MUTEX ( & session - > mutex ) ;
file - > private_data = session ;
return nonseekable_open ( inode , file ) ;
}
static int vmcp_release ( struct inode * inode , struct file * file )
{
struct vmcp_session * session ;
session = ( struct vmcp_session * ) file - > private_data ;
file - > private_data = NULL ;
free_pages ( ( unsigned long ) session - > response , get_order ( session - > bufsize ) ) ;
kfree ( session ) ;
return 0 ;
}
static ssize_t
vmcp_read ( struct file * file , char __user * buff , size_t count , loff_t * ppos )
{
size_t tocopy ;
struct vmcp_session * session ;
session = ( struct vmcp_session * ) file - > private_data ;
if ( down_interruptible ( & session - > mutex ) )
return - ERESTARTSYS ;
if ( ! session - > response ) {
up ( & session - > mutex ) ;
return 0 ;
}
if ( * ppos > session - > resp_size ) {
up ( & session - > mutex ) ;
return 0 ;
}
tocopy = min ( session - > resp_size - ( size_t ) ( * ppos ) , count ) ;
tocopy = min ( tocopy , session - > bufsize - ( size_t ) ( * ppos ) ) ;
if ( copy_to_user ( buff , session - > response + ( * ppos ) , tocopy ) ) {
up ( & session - > mutex ) ;
return - EFAULT ;
}
up ( & session - > mutex ) ;
* ppos + = tocopy ;
return tocopy ;
}
static ssize_t
vmcp_write ( struct file * file , const char __user * buff , size_t count ,
loff_t * ppos )
{
char * cmd ;
struct vmcp_session * session ;
if ( count > 240 )
return - EINVAL ;
cmd = kmalloc ( count + 1 , GFP_KERNEL ) ;
if ( ! cmd )
return - ENOMEM ;
if ( copy_from_user ( cmd , buff , count ) ) {
kfree ( cmd ) ;
return - EFAULT ;
}
cmd [ count ] = ' \0 ' ;
session = ( struct vmcp_session * ) file - > private_data ;
2005-11-07 00:59:12 -08:00
if ( down_interruptible ( & session - > mutex ) ) {
kfree ( cmd ) ;
2005-06-25 14:55:32 -07:00
return - ERESTARTSYS ;
2005-11-07 00:59:12 -08:00
}
2005-06-25 14:55:32 -07:00
if ( ! session - > response )
session - > response = ( char * ) __get_free_pages ( GFP_KERNEL
| __GFP_REPEAT | GFP_DMA ,
get_order ( session - > bufsize ) ) ;
if ( ! session - > response ) {
up ( & session - > mutex ) ;
kfree ( cmd ) ;
return - ENOMEM ;
}
debug_text_event ( vmcp_debug , 1 , cmd ) ;
2005-07-27 11:45:06 -07:00
session - > resp_size = __cpcmd ( cmd , session - > response ,
session - > bufsize ,
& session - > resp_code ) ;
2005-06-25 14:55:32 -07:00
up ( & session - > mutex ) ;
kfree ( cmd ) ;
* ppos = 0 ; /* reset the file pointer after a command */
return count ;
}
/*
* These ioctls are available , as the semantics of the diagnose 8 call
* does not fit very well into a Linux call . Diagnose X ' 08 ' is described in
* CP Programming Services SC24 - 6084 - 00
*
* VMCP_GETCODE : gives the CP return code back to user space
* VMCP_SETBUF : sets the response buffer for the next write call . diagnose 8
* expects adjacent pages in real storage and to make matters worse , we
* dont know the size of the response . Therefore we default to PAGESIZE and
* let userspace to change the response size , if userspace expects a bigger
* response
*/
static long vmcp_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
{
struct vmcp_session * session ;
int temp ;
session = ( struct vmcp_session * ) file - > private_data ;
if ( down_interruptible ( & session - > mutex ) )
return - ERESTARTSYS ;
switch ( cmd ) {
case VMCP_GETCODE :
temp = session - > resp_code ;
up ( & session - > mutex ) ;
return put_user ( temp , ( int __user * ) arg ) ;
case VMCP_SETBUF :
free_pages ( ( unsigned long ) session - > response ,
get_order ( session - > bufsize ) ) ;
session - > response = NULL ;
temp = get_user ( session - > bufsize , ( int __user * ) arg ) ;
if ( get_order ( session - > bufsize ) > 8 ) {
session - > bufsize = PAGE_SIZE ;
temp = - EINVAL ;
}
up ( & session - > mutex ) ;
return temp ;
case VMCP_GETSIZE :
temp = session - > resp_size ;
up ( & session - > mutex ) ;
return put_user ( temp , ( int __user * ) arg ) ;
default :
up ( & session - > mutex ) ;
return - ENOIOCTLCMD ;
}
}
static struct file_operations vmcp_fops = {
. owner = THIS_MODULE ,
. open = & vmcp_open ,
. release = & vmcp_release ,
. read = & vmcp_read ,
. llseek = & no_llseek ,
. write = & vmcp_write ,
. unlocked_ioctl = & vmcp_ioctl ,
. compat_ioctl = & vmcp_ioctl
} ;
static struct miscdevice vmcp_dev = {
. name = " vmcp " ,
. minor = MISC_DYNAMIC_MINOR ,
. fops = & vmcp_fops ,
} ;
static int __init vmcp_init ( void )
{
int ret ;
if ( ! MACHINE_IS_VM ) {
printk ( KERN_WARNING
" z/VM CP interface is only available under z/VM \n " ) ;
return - ENODEV ;
}
ret = misc_register ( & vmcp_dev ) ;
if ( ! ret )
printk ( KERN_INFO " z/VM CP interface loaded \n " ) ;
else
printk ( KERN_WARNING
" z/VM CP interface not loaded. Could not register misc device. \n " ) ;
2005-06-25 14:55:33 -07:00
vmcp_debug = debug_register ( " vmcp " , 1 , 1 , 240 ) ;
2005-06-25 14:55:32 -07:00
debug_register_view ( vmcp_debug , & debug_hex_ascii_view ) ;
return ret ;
}
static void __exit vmcp_exit ( void )
{
WARN_ON ( misc_deregister ( & vmcp_dev ) ! = 0 ) ;
debug_unregister ( vmcp_debug ) ;
printk ( KERN_INFO " z/VM CP interface unloaded. \n " ) ;
}
module_init ( vmcp_init ) ;
module_exit ( vmcp_exit ) ;