2005-06-26 01:55:32 +04:00
/*
2007-07-17 15:36:05 +04:00
* Copyright IBM Corp . 2004 , 2007
2006-09-20 17:59:24 +04:00
* Interface implementation for communication with the z / VM control program
2007-07-17 15:36:05 +04:00
* Author ( s ) : Christian Borntraeger < borntraeger @ de . ibm . com >
2005-06-26 01:55:32 +04:00
*
*
* 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
*/
2008-12-25 15:39:32 +03:00
# define KMSG_COMPONENT "vmcp"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
2005-06-26 01:55:32 +04:00
# 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 " ) ;
2007-07-17 15:36:05 +04:00
MODULE_AUTHOR ( " Christian Borntraeger <borntraeger@de.ibm.com> " ) ;
2005-06-26 01:55:32 +04:00
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 ;
2008-05-20 21:16:59 +04:00
2005-06-26 01:55:32 +04:00
session - > bufsize = PAGE_SIZE ;
session - > response = NULL ;
session - > resp_size = 0 ;
2007-07-17 15:36:05 +04:00
mutex_init ( & session - > mutex ) ;
2005-06-26 01:55:32 +04:00
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
2007-07-17 15:36:05 +04:00
vmcp_read ( struct file * file , char __user * buff , size_t count , loff_t * ppos )
2005-06-26 01:55:32 +04:00
{
2008-07-14 11:59:14 +04:00
ssize_t ret ;
size_t size ;
2005-06-26 01:55:32 +04:00
struct vmcp_session * session ;
2008-07-14 11:59:14 +04:00
session = file - > private_data ;
2007-07-17 15:36:05 +04:00
if ( mutex_lock_interruptible ( & session - > mutex ) )
2005-06-26 01:55:32 +04:00
return - ERESTARTSYS ;
if ( ! session - > response ) {
2007-07-17 15:36:05 +04:00
mutex_unlock ( & session - > mutex ) ;
2005-06-26 01:55:32 +04:00
return 0 ;
}
2008-07-14 11:59:14 +04:00
size = min_t ( size_t , session - > resp_size , session - > bufsize ) ;
ret = simple_read_from_buffer ( buff , count , ppos ,
session - > response , size ) ;
2005-06-26 01:55:32 +04:00
2007-07-17 15:36:05 +04:00
mutex_unlock ( & session - > mutex ) ;
2008-07-14 11:59:14 +04:00
return ret ;
2005-06-26 01:55:32 +04:00
}
static ssize_t
2007-07-17 15:36:05 +04:00
vmcp_write ( struct file * file , const char __user * buff , size_t count ,
loff_t * ppos )
2005-06-26 01:55:32 +04:00
{
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 ;
2007-07-17 15:36:05 +04:00
if ( mutex_lock_interruptible ( & session - > mutex ) ) {
2005-11-07 11:59:12 +03:00
kfree ( cmd ) ;
2005-06-26 01:55:32 +04:00
return - ERESTARTSYS ;
2005-11-07 11:59:12 +03:00
}
2005-06-26 01:55:32 +04:00
if ( ! session - > response )
session - > response = ( char * ) __get_free_pages ( GFP_KERNEL
2007-07-17 15:36:05 +04:00
| __GFP_REPEAT | GFP_DMA ,
2005-06-26 01:55:32 +04:00
get_order ( session - > bufsize ) ) ;
if ( ! session - > response ) {
2007-07-17 15:36:05 +04:00
mutex_unlock ( & session - > mutex ) ;
2005-06-26 01:55:32 +04:00
kfree ( cmd ) ;
return - ENOMEM ;
}
debug_text_event ( vmcp_debug , 1 , cmd ) ;
2007-07-17 15:36:05 +04:00
session - > resp_size = cpcmd ( cmd , session - > response , session - > bufsize ,
& session - > resp_code ) ;
mutex_unlock ( & session - > mutex ) ;
2005-06-26 01:55:32 +04:00
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 ;
2007-07-17 15:36:05 +04:00
if ( mutex_lock_interruptible ( & session - > mutex ) )
2005-06-26 01:55:32 +04:00
return - ERESTARTSYS ;
switch ( cmd ) {
case VMCP_GETCODE :
temp = session - > resp_code ;
2007-07-17 15:36:05 +04:00
mutex_unlock ( & session - > mutex ) ;
2005-06-26 01:55:32 +04:00
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 ;
}
2007-07-17 15:36:05 +04:00
mutex_unlock ( & session - > mutex ) ;
2005-06-26 01:55:32 +04:00
return temp ;
case VMCP_GETSIZE :
temp = session - > resp_size ;
2007-07-17 15:36:05 +04:00
mutex_unlock ( & session - > mutex ) ;
2005-06-26 01:55:32 +04:00
return put_user ( temp , ( int __user * ) arg ) ;
default :
2007-07-17 15:36:05 +04:00
mutex_unlock ( & session - > mutex ) ;
2005-06-26 01:55:32 +04:00
return - ENOIOCTLCMD ;
}
}
2007-02-12 11:55:34 +03:00
static const struct file_operations vmcp_fops = {
2005-06-26 01:55:32 +04:00
. owner = THIS_MODULE ,
2007-07-10 13:24:07 +04:00
. open = vmcp_open ,
. release = vmcp_release ,
. read = vmcp_read ,
. write = vmcp_write ,
. unlocked_ioctl = vmcp_ioctl ,
2007-07-17 15:36:05 +04:00
. compat_ioctl = vmcp_ioctl ,
2005-06-26 01:55:32 +04:00
} ;
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 ) {
2008-12-25 15:39:32 +03:00
pr_warning ( " The z/VM CP interface device driver cannot be "
" loaded without z/VM \n " ) ;
2005-06-26 01:55:32 +04:00
return - ENODEV ;
}
2008-07-14 11:59:43 +04:00
2005-06-26 01:55:33 +04:00
vmcp_debug = debug_register ( " vmcp " , 1 , 1 , 240 ) ;
2008-07-14 11:59:43 +04:00
if ( ! vmcp_debug )
2007-07-17 15:36:05 +04:00
return - ENOMEM ;
2008-07-14 11:59:43 +04:00
2007-07-17 15:36:05 +04:00
ret = debug_register_view ( vmcp_debug , & debug_hex_ascii_view ) ;
if ( ret ) {
debug_unregister ( vmcp_debug ) ;
return ret ;
}
2008-07-14 11:59:43 +04:00
2007-07-17 15:36:05 +04:00
ret = misc_register ( & vmcp_dev ) ;
if ( ret ) {
debug_unregister ( vmcp_debug ) ;
return ret ;
}
2008-07-14 11:59:43 +04:00
2007-07-17 15:36:05 +04:00
return 0 ;
2005-06-26 01:55:32 +04:00
}
static void __exit vmcp_exit ( void )
{
2007-07-17 15:36:05 +04:00
misc_deregister ( & vmcp_dev ) ;
2005-06-26 01:55:32 +04:00
debug_unregister ( vmcp_debug ) ;
}
module_init ( vmcp_init ) ;
module_exit ( vmcp_exit ) ;