vmlogrdr.c

来自「linux 内核源代码」· C语言 代码 · 共 895 行 · 第 1/2 页

C
895
字号
/* * drivers/s390/char/vmlogrdr.c *	character device driver for reading z/VM system service records * * *	Copyright 2004 IBM Corporation *	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>#include <net/iucv/iucv.h>#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];	struct iucv_path *path;	int connection_established;	int iucv_path_severed;	struct iucv_message local_interrupt_buffer;	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;	struct device  *class_device;	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 *);static ssize_t vmlogrdr_read (struct file *filp, char __user *data,			      size_t count, loff_t * ppos);static const struct file_operations vmlogrdr_fops = {	.owner   = THIS_MODULE,	.open    = vmlogrdr_open,	.release = vmlogrdr_release,	.read    = vmlogrdr_read,};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 *);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,};static DECLARE_WAIT_QUEUE_HEAD(conn_wait_queue);static DECLARE_WAIT_QUEUE_HEAD(read_wait_queue);/* * 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,	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[0].priv_lock),	  .autorecording  = 1,	  .autopurge      = 1,	},	{ .system_service = "*ACCOUNT",	  .internal_name  = "account",	  .recording_name = "ACCOUNT",	  .minor_num      = 1,	  .buffer_free    = 1,	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[1].priv_lock),	  .autorecording  = 1,	  .autopurge      = 1,	},	{ .system_service = "*SYMPTOM",	  .internal_name  = "symptom",	  .recording_name = "SYMPTOM",	  .minor_num      = 2,	  .buffer_free    = 1,	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[2].priv_lock),	  .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;static void vmlogrdr_iucv_path_complete(struct iucv_path *path, u8 ipuser[16]){	struct vmlogrdr_priv_t * logptr = path->private;	spin_lock(&logptr->priv_lock);	logptr->connection_established = 1;	spin_unlock(&logptr->priv_lock);	wake_up(&conn_wait_queue);}static void vmlogrdr_iucv_path_severed(struct iucv_path *path, u8 ipuser[16]){	struct vmlogrdr_priv_t * logptr = path->private;	u8 reason = (u8) ipuser[8];	printk (KERN_ERR "vmlogrdr: connection severed with"		" reason %i\n", reason);	iucv_path_sever(path, NULL);	kfree(path);	logptr->path = NULL;	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);}static void vmlogrdr_iucv_message_pending(struct iucv_path *path,					  struct iucv_message *msg){	struct vmlogrdr_priv_t * logptr = path->private;	/*	 * 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);	memcpy(&logptr->local_interrupt_buffer, msg, sizeof(*msg));	atomic_inc(&logptr->receive_ready);	spin_unlock(&logptr->priv_lock);	wake_up_interruptible(&read_wait_queue);}static int vmlogrdr_get_recording_class_AB(void){	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);	cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);	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;}static int vmlogrdr_recording(struct vmlogrdr_priv_t * logptr,			      int action, int purge){	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);		cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);		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);	cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);	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;}static int vmlogrdr_open (struct inode *inode, struct file *filp){	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;	}	logptr->dev_in_use = 1;	logptr->connection_established = 0;	logptr->iucv_path_severed = 0;	atomic_set(&logptr->receive_ready, 0);	logptr->buffer_free = 1;	spin_unlock_bh(&logptr->priv_lock);	/* set the file options */	filp->private_data = logptr;	filp->f_op = &vmlogrdr_fops;	/* start recording for this service*/	if (logptr->autorecording) {		ret = vmlogrdr_recording(logptr,1,logptr->autopurge);		if (ret)			printk (KERN_WARNING "vmlogrdr: failed to start "				"recording automatically\n");	}	/* create connection to the system service */	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);	if (connect_rc) {		printk (KERN_ERR "vmlogrdr: iucv connection to %s "			"failed with rc %i \n", logptr->system_service,			connect_rc);		goto out_path;	}	/* 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));	if (logptr->iucv_path_severed)		goto out_record; 	return nonseekable_open(inode, filp);out_record:	if (logptr->autorecording)		vmlogrdr_recording(logptr,0,logptr->autopurge);out_path:	kfree(logptr->path);	/* kfree(NULL) is ok. */	logptr->path = NULL;out_dev:	logptr->dev_in_use = 0;	return -EIO;}static int vmlogrdr_release (struct inode *inode, struct file *filp){	int ret;	struct vmlogrdr_priv_t * logptr = filp->private_data;	iucv_path_sever(logptr->path, NULL);	kfree(logptr->path);	logptr->path = NULL;	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;}static int vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv){	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.		         */			iucv_data_count = priv->local_interrupt_buffer.length;			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;		rc = iucv_message_receive(priv->path,					  &priv->local_interrupt_buffer,					  0, buffer, iucv_data_count,					  &priv->residual_length);		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)

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?