// SPDX-License-Identifier: GPL-2.0 /* * character device driver for reading z/VM system service records * * * Copyright IBM Corp. 2004, 2009 * 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> *
*/
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;
};
/* * 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);
}
cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
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;
}
staticint vmlogrdr_recording(struct vmlogrdr_priv_t * logptr, int action, int purge)
{
char cp_command[80]; char cp_response[160]; char *onoff, *qid_string; int rc;
/* * The recording commands needs to be called with option QID * for guests that have privilege 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 && (action == 1)) {
memset(cp_command, 0x00, sizeof(cp_command));
memset(cp_response, 0x00, sizeof(cp_response));
snprintf(cp_command, sizeof(cp_command), "RECORDING %s PURGE %s",
logptr->recording_name,
qid_string);
cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
}
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);
cpcmd(cp_command, cp_response, sizeof(cp_response), NULL); /* 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"))
rc = 0; else
rc = -EIO; /* * If we turn recording off, we have to purge any remaining records * afterwards, as a large number of queued records may impact z/VM * performance.
*/ if (purge && (action == 0)) {
memset(cp_command, 0x00, sizeof(cp_command));
memset(cp_response, 0x00, sizeof(cp_response));
snprintf(cp_command, sizeof(cp_command), "RECORDING %s PURGE %s",
logptr->recording_name,
qid_string);
cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
}
return rc;
}
staticint vmlogrdr_open (struct inode *inode, struct file *filp)
{ int dev_num = 0; struct vmlogrdr_priv_t * logptr = NULL; int connect_rc = 0; int ret;
/* * only allow for blocking reads to be open
*/ if (filp->f_flags & O_NONBLOCK) return -EOPNOTSUPP;
/* 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;
/* start recording for this service*/ if (logptr->autorecording) {
ret = vmlogrdr_recording(logptr,1,logptr->autopurge); if (ret)
pr_warn("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) {
pr_err("vmlogrdr: iucv connection to %s " "failed with rc %i \n",
logptr->internal_name, 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;
nonseekable_open(inode, filp); return 0;
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;
}
iucv_path_sever(logptr->path, NULL);
kfree(logptr->path);
logptr->path = NULL; if (logptr->autorecording) {
ret = vmlogrdr_recording(logptr,0,logptr->autopurge); if (ret)
pr_warn("vmlogrdr: failed to stop recording automatically\n");
}
logptr->dev_in_use = 0;
return 0;
}
staticint 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 than 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 than * 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;
}
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;
/* * The recording command needs to be called with option QID * for guests that have privilege classes A or B. * Other guests will not recognize the command and we have to * issue the same command without the QID parameter.
*/
staticint vmlogrdr_register_device(struct vmlogrdr_priv_t *priv)
{ struct device *dev; int ret;
dev = iucv_alloc_device(vmlogrdr_attr_groups, &vmlogrdr_driver,
priv, priv->internal_name); if (!dev) return -ENOMEM;
ret = device_register(dev); if (ret) {
put_device(dev); return ret;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.