18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *	character device driver for reading z/VM system service records
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci *	Copyright IBM Corp. 2004, 2009
78c2ecf20Sopenharmony_ci *	character device driver for reading z/VM system service records,
88c2ecf20Sopenharmony_ci *	Version 1.0
98c2ecf20Sopenharmony_ci *	Author(s): Xenia Tkatschow <xenia@us.ibm.com>
108c2ecf20Sopenharmony_ci *		   Stefan Weinhuber <wein@de.ibm.com>
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#define KMSG_COMPONENT "vmlogrdr"
158c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci#include <linux/init.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci#include <linux/errno.h>
218c2ecf20Sopenharmony_ci#include <linux/types.h>
228c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
238c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
248c2ecf20Sopenharmony_ci#include <linux/atomic.h>
258c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
268c2ecf20Sopenharmony_ci#include <asm/cpcmd.h>
278c2ecf20Sopenharmony_ci#include <asm/debug.h>
288c2ecf20Sopenharmony_ci#include <asm/ebcdic.h>
298c2ecf20Sopenharmony_ci#include <net/iucv/iucv.h>
308c2ecf20Sopenharmony_ci#include <linux/kmod.h>
318c2ecf20Sopenharmony_ci#include <linux/cdev.h>
328c2ecf20Sopenharmony_ci#include <linux/device.h>
338c2ecf20Sopenharmony_ci#include <linux/string.h>
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ciMODULE_AUTHOR
368c2ecf20Sopenharmony_ci	("(C) 2004 IBM Corporation by Xenia Tkatschow (xenia@us.ibm.com)\n"
378c2ecf20Sopenharmony_ci	 "                            Stefan Weinhuber (wein@de.ibm.com)");
388c2ecf20Sopenharmony_ciMODULE_DESCRIPTION ("Character device driver for reading z/VM "
398c2ecf20Sopenharmony_ci		    "system service records.");
408c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci/*
448c2ecf20Sopenharmony_ci * The size of the buffer for iucv data transfer is one page,
458c2ecf20Sopenharmony_ci * but in addition to the data we read from iucv we also
468c2ecf20Sopenharmony_ci * place an integer and some characters into that buffer,
478c2ecf20Sopenharmony_ci * so the maximum size for record data is a little less then
488c2ecf20Sopenharmony_ci * one page.
498c2ecf20Sopenharmony_ci */
508c2ecf20Sopenharmony_ci#define NET_BUFFER_SIZE	(PAGE_SIZE - sizeof(int) - sizeof(FENCE))
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/*
538c2ecf20Sopenharmony_ci * The elements that are concurrently accessed by bottom halves are
548c2ecf20Sopenharmony_ci * connection_established, iucv_path_severed, local_interrupt_buffer
558c2ecf20Sopenharmony_ci * and receive_ready. The first three can be protected by
568c2ecf20Sopenharmony_ci * priv_lock.  receive_ready is atomic, so it can be incremented and
578c2ecf20Sopenharmony_ci * decremented without holding a lock.
588c2ecf20Sopenharmony_ci * The variable dev_in_use needs to be protected by the lock, since
598c2ecf20Sopenharmony_ci * it's a flag used by open to make sure that the device is opened only
608c2ecf20Sopenharmony_ci * by one user at the same time.
618c2ecf20Sopenharmony_ci */
628c2ecf20Sopenharmony_cistruct vmlogrdr_priv_t {
638c2ecf20Sopenharmony_ci	char system_service[8];
648c2ecf20Sopenharmony_ci	char internal_name[8];
658c2ecf20Sopenharmony_ci	char recording_name[8];
668c2ecf20Sopenharmony_ci	struct iucv_path *path;
678c2ecf20Sopenharmony_ci	int connection_established;
688c2ecf20Sopenharmony_ci	int iucv_path_severed;
698c2ecf20Sopenharmony_ci	struct iucv_message local_interrupt_buffer;
708c2ecf20Sopenharmony_ci	atomic_t receive_ready;
718c2ecf20Sopenharmony_ci	int minor_num;
728c2ecf20Sopenharmony_ci	char * buffer;
738c2ecf20Sopenharmony_ci	char * current_position;
748c2ecf20Sopenharmony_ci	int remaining;
758c2ecf20Sopenharmony_ci	ulong residual_length;
768c2ecf20Sopenharmony_ci	int buffer_free;
778c2ecf20Sopenharmony_ci	int dev_in_use; /* 1: already opened, 0: not opened*/
788c2ecf20Sopenharmony_ci	spinlock_t priv_lock;
798c2ecf20Sopenharmony_ci	struct device  *device;
808c2ecf20Sopenharmony_ci	struct device  *class_device;
818c2ecf20Sopenharmony_ci	int autorecording;
828c2ecf20Sopenharmony_ci	int autopurge;
838c2ecf20Sopenharmony_ci};
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci/*
878c2ecf20Sopenharmony_ci * File operation structure for vmlogrdr devices
888c2ecf20Sopenharmony_ci */
898c2ecf20Sopenharmony_cistatic int vmlogrdr_open(struct inode *, struct file *);
908c2ecf20Sopenharmony_cistatic int vmlogrdr_release(struct inode *, struct file *);
918c2ecf20Sopenharmony_cistatic ssize_t vmlogrdr_read (struct file *filp, char __user *data,
928c2ecf20Sopenharmony_ci			      size_t count, loff_t * ppos);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic const struct file_operations vmlogrdr_fops = {
958c2ecf20Sopenharmony_ci	.owner   = THIS_MODULE,
968c2ecf20Sopenharmony_ci	.open    = vmlogrdr_open,
978c2ecf20Sopenharmony_ci	.release = vmlogrdr_release,
988c2ecf20Sopenharmony_ci	.read    = vmlogrdr_read,
998c2ecf20Sopenharmony_ci	.llseek  = no_llseek,
1008c2ecf20Sopenharmony_ci};
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic void vmlogrdr_iucv_path_complete(struct iucv_path *, u8 *ipuser);
1048c2ecf20Sopenharmony_cistatic void vmlogrdr_iucv_path_severed(struct iucv_path *, u8 *ipuser);
1058c2ecf20Sopenharmony_cistatic void vmlogrdr_iucv_message_pending(struct iucv_path *,
1068c2ecf20Sopenharmony_ci					  struct iucv_message *);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic struct iucv_handler vmlogrdr_iucv_handler = {
1108c2ecf20Sopenharmony_ci	.path_complete	 = vmlogrdr_iucv_path_complete,
1118c2ecf20Sopenharmony_ci	.path_severed	 = vmlogrdr_iucv_path_severed,
1128c2ecf20Sopenharmony_ci	.message_pending = vmlogrdr_iucv_message_pending,
1138c2ecf20Sopenharmony_ci};
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(conn_wait_queue);
1178c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(read_wait_queue);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci/*
1208c2ecf20Sopenharmony_ci * pointer to system service private structure
1218c2ecf20Sopenharmony_ci * minor number 0 --> logrec
1228c2ecf20Sopenharmony_ci * minor number 1 --> account
1238c2ecf20Sopenharmony_ci * minor number 2 --> symptom
1248c2ecf20Sopenharmony_ci */
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic struct vmlogrdr_priv_t sys_ser[] = {
1278c2ecf20Sopenharmony_ci	{ .system_service = "*LOGREC ",
1288c2ecf20Sopenharmony_ci	  .internal_name  = "logrec",
1298c2ecf20Sopenharmony_ci	  .recording_name = "EREP",
1308c2ecf20Sopenharmony_ci	  .minor_num      = 0,
1318c2ecf20Sopenharmony_ci	  .buffer_free    = 1,
1328c2ecf20Sopenharmony_ci	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[0].priv_lock),
1338c2ecf20Sopenharmony_ci	  .autorecording  = 1,
1348c2ecf20Sopenharmony_ci	  .autopurge      = 1,
1358c2ecf20Sopenharmony_ci	},
1368c2ecf20Sopenharmony_ci	{ .system_service = "*ACCOUNT",
1378c2ecf20Sopenharmony_ci	  .internal_name  = "account",
1388c2ecf20Sopenharmony_ci	  .recording_name = "ACCOUNT",
1398c2ecf20Sopenharmony_ci	  .minor_num      = 1,
1408c2ecf20Sopenharmony_ci	  .buffer_free    = 1,
1418c2ecf20Sopenharmony_ci	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[1].priv_lock),
1428c2ecf20Sopenharmony_ci	  .autorecording  = 1,
1438c2ecf20Sopenharmony_ci	  .autopurge      = 1,
1448c2ecf20Sopenharmony_ci	},
1458c2ecf20Sopenharmony_ci	{ .system_service = "*SYMPTOM",
1468c2ecf20Sopenharmony_ci	  .internal_name  = "symptom",
1478c2ecf20Sopenharmony_ci	  .recording_name = "SYMPTOM",
1488c2ecf20Sopenharmony_ci	  .minor_num      = 2,
1498c2ecf20Sopenharmony_ci	  .buffer_free    = 1,
1508c2ecf20Sopenharmony_ci	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[2].priv_lock),
1518c2ecf20Sopenharmony_ci	  .autorecording  = 1,
1528c2ecf20Sopenharmony_ci	  .autopurge      = 1,
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci};
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci#define MAXMINOR  ARRAY_SIZE(sys_ser)
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cistatic char FENCE[] = {"EOR"};
1598c2ecf20Sopenharmony_cistatic int vmlogrdr_major = 0;
1608c2ecf20Sopenharmony_cistatic struct cdev  *vmlogrdr_cdev = NULL;
1618c2ecf20Sopenharmony_cistatic int recording_class_AB;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic void vmlogrdr_iucv_path_complete(struct iucv_path *path, u8 *ipuser)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t * logptr = path->private;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	spin_lock(&logptr->priv_lock);
1698c2ecf20Sopenharmony_ci	logptr->connection_established = 1;
1708c2ecf20Sopenharmony_ci	spin_unlock(&logptr->priv_lock);
1718c2ecf20Sopenharmony_ci	wake_up(&conn_wait_queue);
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cistatic void vmlogrdr_iucv_path_severed(struct iucv_path *path, u8 *ipuser)
1768c2ecf20Sopenharmony_ci{
1778c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t * logptr = path->private;
1788c2ecf20Sopenharmony_ci	u8 reason = (u8) ipuser[8];
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	pr_err("vmlogrdr: connection severed with reason %i\n", reason);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	iucv_path_sever(path, NULL);
1838c2ecf20Sopenharmony_ci	kfree(path);
1848c2ecf20Sopenharmony_ci	logptr->path = NULL;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	spin_lock(&logptr->priv_lock);
1878c2ecf20Sopenharmony_ci	logptr->connection_established = 0;
1888c2ecf20Sopenharmony_ci	logptr->iucv_path_severed = 1;
1898c2ecf20Sopenharmony_ci	spin_unlock(&logptr->priv_lock);
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	wake_up(&conn_wait_queue);
1928c2ecf20Sopenharmony_ci	/* just in case we're sleeping waiting for a record */
1938c2ecf20Sopenharmony_ci	wake_up_interruptible(&read_wait_queue);
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic void vmlogrdr_iucv_message_pending(struct iucv_path *path,
1988c2ecf20Sopenharmony_ci					  struct iucv_message *msg)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t * logptr = path->private;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	/*
2038c2ecf20Sopenharmony_ci	 * This function is the bottom half so it should be quick.
2048c2ecf20Sopenharmony_ci	 * Copy the external interrupt data into our local eib and increment
2058c2ecf20Sopenharmony_ci	 * the usage count
2068c2ecf20Sopenharmony_ci	 */
2078c2ecf20Sopenharmony_ci	spin_lock(&logptr->priv_lock);
2088c2ecf20Sopenharmony_ci	memcpy(&logptr->local_interrupt_buffer, msg, sizeof(*msg));
2098c2ecf20Sopenharmony_ci	atomic_inc(&logptr->receive_ready);
2108c2ecf20Sopenharmony_ci	spin_unlock(&logptr->priv_lock);
2118c2ecf20Sopenharmony_ci	wake_up_interruptible(&read_wait_queue);
2128c2ecf20Sopenharmony_ci}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic int vmlogrdr_get_recording_class_AB(void)
2168c2ecf20Sopenharmony_ci{
2178c2ecf20Sopenharmony_ci	static const char cp_command[] = "QUERY COMMAND RECORDING ";
2188c2ecf20Sopenharmony_ci	char cp_response[80];
2198c2ecf20Sopenharmony_ci	char *tail;
2208c2ecf20Sopenharmony_ci	int len,i;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
2238c2ecf20Sopenharmony_ci	len = strnlen(cp_response,sizeof(cp_response));
2248c2ecf20Sopenharmony_ci	// now the parsing
2258c2ecf20Sopenharmony_ci	tail=strnchr(cp_response,len,'=');
2268c2ecf20Sopenharmony_ci	if (!tail)
2278c2ecf20Sopenharmony_ci		return 0;
2288c2ecf20Sopenharmony_ci	tail++;
2298c2ecf20Sopenharmony_ci	if (!strncmp("ANY",tail,3))
2308c2ecf20Sopenharmony_ci		return 1;
2318c2ecf20Sopenharmony_ci	if (!strncmp("NONE",tail,4))
2328c2ecf20Sopenharmony_ci		return 0;
2338c2ecf20Sopenharmony_ci	/*
2348c2ecf20Sopenharmony_ci	 * expect comma separated list of classes here, if one of them
2358c2ecf20Sopenharmony_ci	 * is A or B return 1 otherwise 0
2368c2ecf20Sopenharmony_ci	 */
2378c2ecf20Sopenharmony_ci        for (i=tail-cp_response; i<len; i++)
2388c2ecf20Sopenharmony_ci		if ( cp_response[i]=='A' || cp_response[i]=='B' )
2398c2ecf20Sopenharmony_ci			return 1;
2408c2ecf20Sopenharmony_ci	return 0;
2418c2ecf20Sopenharmony_ci}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_cistatic int vmlogrdr_recording(struct vmlogrdr_priv_t * logptr,
2458c2ecf20Sopenharmony_ci			      int action, int purge)
2468c2ecf20Sopenharmony_ci{
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	char cp_command[80];
2498c2ecf20Sopenharmony_ci	char cp_response[160];
2508c2ecf20Sopenharmony_ci	char *onoff, *qid_string;
2518c2ecf20Sopenharmony_ci	int rc;
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	onoff = ((action == 1) ? "ON" : "OFF");
2548c2ecf20Sopenharmony_ci	qid_string = ((recording_class_AB == 1) ? " QID * " : "");
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	/*
2578c2ecf20Sopenharmony_ci	 * The recording commands needs to be called with option QID
2588c2ecf20Sopenharmony_ci	 * for guests that have previlege classes A or B.
2598c2ecf20Sopenharmony_ci	 * Purging has to be done as separate step, because recording
2608c2ecf20Sopenharmony_ci	 * can't be switched on as long as records are on the queue.
2618c2ecf20Sopenharmony_ci	 * Doing both at the same time doesn't work.
2628c2ecf20Sopenharmony_ci	 */
2638c2ecf20Sopenharmony_ci	if (purge && (action == 1)) {
2648c2ecf20Sopenharmony_ci		memset(cp_command, 0x00, sizeof(cp_command));
2658c2ecf20Sopenharmony_ci		memset(cp_response, 0x00, sizeof(cp_response));
2668c2ecf20Sopenharmony_ci		snprintf(cp_command, sizeof(cp_command),
2678c2ecf20Sopenharmony_ci			 "RECORDING %s PURGE %s",
2688c2ecf20Sopenharmony_ci			 logptr->recording_name,
2698c2ecf20Sopenharmony_ci			 qid_string);
2708c2ecf20Sopenharmony_ci		cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	memset(cp_command, 0x00, sizeof(cp_command));
2748c2ecf20Sopenharmony_ci	memset(cp_response, 0x00, sizeof(cp_response));
2758c2ecf20Sopenharmony_ci	snprintf(cp_command, sizeof(cp_command), "RECORDING %s %s %s",
2768c2ecf20Sopenharmony_ci		logptr->recording_name,
2778c2ecf20Sopenharmony_ci		onoff,
2788c2ecf20Sopenharmony_ci		qid_string);
2798c2ecf20Sopenharmony_ci	cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
2808c2ecf20Sopenharmony_ci	/* The recording command will usually answer with 'Command complete'
2818c2ecf20Sopenharmony_ci	 * on success, but when the specific service was never connected
2828c2ecf20Sopenharmony_ci	 * before then there might be an additional informational message
2838c2ecf20Sopenharmony_ci	 * 'HCPCRC8072I Recording entry not found' before the
2848c2ecf20Sopenharmony_ci	 * 'Command complete'. So I use strstr rather then the strncmp.
2858c2ecf20Sopenharmony_ci	 */
2868c2ecf20Sopenharmony_ci	if (strstr(cp_response,"Command complete"))
2878c2ecf20Sopenharmony_ci		rc = 0;
2888c2ecf20Sopenharmony_ci	else
2898c2ecf20Sopenharmony_ci		rc = -EIO;
2908c2ecf20Sopenharmony_ci	/*
2918c2ecf20Sopenharmony_ci	 * If we turn recording off, we have to purge any remaining records
2928c2ecf20Sopenharmony_ci	 * afterwards, as a large number of queued records may impact z/VM
2938c2ecf20Sopenharmony_ci	 * performance.
2948c2ecf20Sopenharmony_ci	 */
2958c2ecf20Sopenharmony_ci	if (purge && (action == 0)) {
2968c2ecf20Sopenharmony_ci		memset(cp_command, 0x00, sizeof(cp_command));
2978c2ecf20Sopenharmony_ci		memset(cp_response, 0x00, sizeof(cp_response));
2988c2ecf20Sopenharmony_ci		snprintf(cp_command, sizeof(cp_command),
2998c2ecf20Sopenharmony_ci			 "RECORDING %s PURGE %s",
3008c2ecf20Sopenharmony_ci			 logptr->recording_name,
3018c2ecf20Sopenharmony_ci			 qid_string);
3028c2ecf20Sopenharmony_ci		cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
3038c2ecf20Sopenharmony_ci	}
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	return rc;
3068c2ecf20Sopenharmony_ci}
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_cistatic int vmlogrdr_open (struct inode *inode, struct file *filp)
3108c2ecf20Sopenharmony_ci{
3118c2ecf20Sopenharmony_ci	int dev_num = 0;
3128c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t * logptr = NULL;
3138c2ecf20Sopenharmony_ci	int connect_rc = 0;
3148c2ecf20Sopenharmony_ci	int ret;
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	dev_num = iminor(inode);
3178c2ecf20Sopenharmony_ci	if (dev_num >= MAXMINOR)
3188c2ecf20Sopenharmony_ci		return -ENODEV;
3198c2ecf20Sopenharmony_ci	logptr = &sys_ser[dev_num];
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	/*
3228c2ecf20Sopenharmony_ci	 * only allow for blocking reads to be open
3238c2ecf20Sopenharmony_ci	 */
3248c2ecf20Sopenharmony_ci	if (filp->f_flags & O_NONBLOCK)
3258c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	/* Besure this device hasn't already been opened */
3288c2ecf20Sopenharmony_ci	spin_lock_bh(&logptr->priv_lock);
3298c2ecf20Sopenharmony_ci	if (logptr->dev_in_use)	{
3308c2ecf20Sopenharmony_ci		spin_unlock_bh(&logptr->priv_lock);
3318c2ecf20Sopenharmony_ci		return -EBUSY;
3328c2ecf20Sopenharmony_ci	}
3338c2ecf20Sopenharmony_ci	logptr->dev_in_use = 1;
3348c2ecf20Sopenharmony_ci	logptr->connection_established = 0;
3358c2ecf20Sopenharmony_ci	logptr->iucv_path_severed = 0;
3368c2ecf20Sopenharmony_ci	atomic_set(&logptr->receive_ready, 0);
3378c2ecf20Sopenharmony_ci	logptr->buffer_free = 1;
3388c2ecf20Sopenharmony_ci	spin_unlock_bh(&logptr->priv_lock);
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	/* set the file options */
3418c2ecf20Sopenharmony_ci	filp->private_data = logptr;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	/* start recording for this service*/
3448c2ecf20Sopenharmony_ci	if (logptr->autorecording) {
3458c2ecf20Sopenharmony_ci		ret = vmlogrdr_recording(logptr,1,logptr->autopurge);
3468c2ecf20Sopenharmony_ci		if (ret)
3478c2ecf20Sopenharmony_ci			pr_warn("vmlogrdr: failed to start recording automatically\n");
3488c2ecf20Sopenharmony_ci	}
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	/* create connection to the system service */
3518c2ecf20Sopenharmony_ci	logptr->path = iucv_path_alloc(10, 0, GFP_KERNEL);
3528c2ecf20Sopenharmony_ci	if (!logptr->path)
3538c2ecf20Sopenharmony_ci		goto out_dev;
3548c2ecf20Sopenharmony_ci	connect_rc = iucv_path_connect(logptr->path, &vmlogrdr_iucv_handler,
3558c2ecf20Sopenharmony_ci				       logptr->system_service, NULL, NULL,
3568c2ecf20Sopenharmony_ci				       logptr);
3578c2ecf20Sopenharmony_ci	if (connect_rc) {
3588c2ecf20Sopenharmony_ci		pr_err("vmlogrdr: iucv connection to %s "
3598c2ecf20Sopenharmony_ci		       "failed with rc %i \n",
3608c2ecf20Sopenharmony_ci		       logptr->system_service, connect_rc);
3618c2ecf20Sopenharmony_ci		goto out_path;
3628c2ecf20Sopenharmony_ci	}
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	/* We've issued the connect and now we must wait for a
3658c2ecf20Sopenharmony_ci	 * ConnectionComplete or ConnectinSevered Interrupt
3668c2ecf20Sopenharmony_ci	 * before we can continue to process.
3678c2ecf20Sopenharmony_ci	 */
3688c2ecf20Sopenharmony_ci	wait_event(conn_wait_queue, (logptr->connection_established)
3698c2ecf20Sopenharmony_ci		   || (logptr->iucv_path_severed));
3708c2ecf20Sopenharmony_ci	if (logptr->iucv_path_severed)
3718c2ecf20Sopenharmony_ci		goto out_record;
3728c2ecf20Sopenharmony_ci	nonseekable_open(inode, filp);
3738c2ecf20Sopenharmony_ci	return 0;
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ciout_record:
3768c2ecf20Sopenharmony_ci	if (logptr->autorecording)
3778c2ecf20Sopenharmony_ci		vmlogrdr_recording(logptr,0,logptr->autopurge);
3788c2ecf20Sopenharmony_ciout_path:
3798c2ecf20Sopenharmony_ci	kfree(logptr->path);	/* kfree(NULL) is ok. */
3808c2ecf20Sopenharmony_ci	logptr->path = NULL;
3818c2ecf20Sopenharmony_ciout_dev:
3828c2ecf20Sopenharmony_ci	logptr->dev_in_use = 0;
3838c2ecf20Sopenharmony_ci	return -EIO;
3848c2ecf20Sopenharmony_ci}
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_cistatic int vmlogrdr_release (struct inode *inode, struct file *filp)
3888c2ecf20Sopenharmony_ci{
3898c2ecf20Sopenharmony_ci	int ret;
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t * logptr = filp->private_data;
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	iucv_path_sever(logptr->path, NULL);
3948c2ecf20Sopenharmony_ci	kfree(logptr->path);
3958c2ecf20Sopenharmony_ci	logptr->path = NULL;
3968c2ecf20Sopenharmony_ci	if (logptr->autorecording) {
3978c2ecf20Sopenharmony_ci		ret = vmlogrdr_recording(logptr,0,logptr->autopurge);
3988c2ecf20Sopenharmony_ci		if (ret)
3998c2ecf20Sopenharmony_ci			pr_warn("vmlogrdr: failed to stop recording automatically\n");
4008c2ecf20Sopenharmony_ci	}
4018c2ecf20Sopenharmony_ci	logptr->dev_in_use = 0;
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci	return 0;
4048c2ecf20Sopenharmony_ci}
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_cistatic int vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv)
4088c2ecf20Sopenharmony_ci{
4098c2ecf20Sopenharmony_ci	int rc, *temp;
4108c2ecf20Sopenharmony_ci	/* we need to keep track of two data sizes here:
4118c2ecf20Sopenharmony_ci	 * The number of bytes we need to receive from iucv and
4128c2ecf20Sopenharmony_ci	 * the total number of bytes we actually write into the buffer.
4138c2ecf20Sopenharmony_ci	 */
4148c2ecf20Sopenharmony_ci	int user_data_count, iucv_data_count;
4158c2ecf20Sopenharmony_ci	char * buffer;
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	if (atomic_read(&priv->receive_ready)) {
4188c2ecf20Sopenharmony_ci		spin_lock_bh(&priv->priv_lock);
4198c2ecf20Sopenharmony_ci		if (priv->residual_length){
4208c2ecf20Sopenharmony_ci			/* receive second half of a record */
4218c2ecf20Sopenharmony_ci			iucv_data_count = priv->residual_length;
4228c2ecf20Sopenharmony_ci			user_data_count = 0;
4238c2ecf20Sopenharmony_ci			buffer = priv->buffer;
4248c2ecf20Sopenharmony_ci		} else {
4258c2ecf20Sopenharmony_ci			/* receive a new record:
4268c2ecf20Sopenharmony_ci			 * We need to return the total length of the record
4278c2ecf20Sopenharmony_ci                         * + size of FENCE in the first 4 bytes of the buffer.
4288c2ecf20Sopenharmony_ci		         */
4298c2ecf20Sopenharmony_ci			iucv_data_count = priv->local_interrupt_buffer.length;
4308c2ecf20Sopenharmony_ci			user_data_count = sizeof(int);
4318c2ecf20Sopenharmony_ci			temp = (int*)priv->buffer;
4328c2ecf20Sopenharmony_ci			*temp= iucv_data_count + sizeof(FENCE);
4338c2ecf20Sopenharmony_ci			buffer = priv->buffer + sizeof(int);
4348c2ecf20Sopenharmony_ci		}
4358c2ecf20Sopenharmony_ci		/*
4368c2ecf20Sopenharmony_ci		 * If the record is bigger than our buffer, we receive only
4378c2ecf20Sopenharmony_ci		 * a part of it. We can get the rest later.
4388c2ecf20Sopenharmony_ci		 */
4398c2ecf20Sopenharmony_ci		if (iucv_data_count > NET_BUFFER_SIZE)
4408c2ecf20Sopenharmony_ci			iucv_data_count = NET_BUFFER_SIZE;
4418c2ecf20Sopenharmony_ci		rc = iucv_message_receive(priv->path,
4428c2ecf20Sopenharmony_ci					  &priv->local_interrupt_buffer,
4438c2ecf20Sopenharmony_ci					  0, buffer, iucv_data_count,
4448c2ecf20Sopenharmony_ci					  &priv->residual_length);
4458c2ecf20Sopenharmony_ci		spin_unlock_bh(&priv->priv_lock);
4468c2ecf20Sopenharmony_ci		/* An rc of 5 indicates that the record was bigger than
4478c2ecf20Sopenharmony_ci		 * the buffer, which is OK for us. A 9 indicates that the
4488c2ecf20Sopenharmony_ci		 * record was purged befor we could receive it.
4498c2ecf20Sopenharmony_ci		 */
4508c2ecf20Sopenharmony_ci		if (rc == 5)
4518c2ecf20Sopenharmony_ci			rc = 0;
4528c2ecf20Sopenharmony_ci		if (rc == 9)
4538c2ecf20Sopenharmony_ci			atomic_set(&priv->receive_ready, 0);
4548c2ecf20Sopenharmony_ci	} else {
4558c2ecf20Sopenharmony_ci		rc = 1;
4568c2ecf20Sopenharmony_ci	}
4578c2ecf20Sopenharmony_ci	if (!rc) {
4588c2ecf20Sopenharmony_ci		priv->buffer_free = 0;
4598c2ecf20Sopenharmony_ci 		user_data_count += iucv_data_count;
4608c2ecf20Sopenharmony_ci		priv->current_position = priv->buffer;
4618c2ecf20Sopenharmony_ci		if (priv->residual_length == 0){
4628c2ecf20Sopenharmony_ci			/* the whole record has been captured,
4638c2ecf20Sopenharmony_ci			 * now add the fence */
4648c2ecf20Sopenharmony_ci			atomic_dec(&priv->receive_ready);
4658c2ecf20Sopenharmony_ci			buffer = priv->buffer + user_data_count;
4668c2ecf20Sopenharmony_ci			memcpy(buffer, FENCE, sizeof(FENCE));
4678c2ecf20Sopenharmony_ci			user_data_count += sizeof(FENCE);
4688c2ecf20Sopenharmony_ci		}
4698c2ecf20Sopenharmony_ci		priv->remaining = user_data_count;
4708c2ecf20Sopenharmony_ci	}
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci	return rc;
4738c2ecf20Sopenharmony_ci}
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_cistatic ssize_t vmlogrdr_read(struct file *filp, char __user *data,
4778c2ecf20Sopenharmony_ci			     size_t count, loff_t * ppos)
4788c2ecf20Sopenharmony_ci{
4798c2ecf20Sopenharmony_ci	int rc;
4808c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t * priv = filp->private_data;
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_ci	while (priv->buffer_free) {
4838c2ecf20Sopenharmony_ci		rc = vmlogrdr_receive_data(priv);
4848c2ecf20Sopenharmony_ci		if (rc) {
4858c2ecf20Sopenharmony_ci			rc = wait_event_interruptible(read_wait_queue,
4868c2ecf20Sopenharmony_ci					atomic_read(&priv->receive_ready));
4878c2ecf20Sopenharmony_ci			if (rc)
4888c2ecf20Sopenharmony_ci				return rc;
4898c2ecf20Sopenharmony_ci		}
4908c2ecf20Sopenharmony_ci	}
4918c2ecf20Sopenharmony_ci	/* copy only up to end of record */
4928c2ecf20Sopenharmony_ci	if (count > priv->remaining)
4938c2ecf20Sopenharmony_ci		count = priv->remaining;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	if (copy_to_user(data, priv->current_position, count))
4968c2ecf20Sopenharmony_ci		return -EFAULT;
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_ci	*ppos += count;
4998c2ecf20Sopenharmony_ci	priv->current_position += count;
5008c2ecf20Sopenharmony_ci	priv->remaining -= count;
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_ci	/* if all data has been transferred, set buffer free */
5038c2ecf20Sopenharmony_ci	if (priv->remaining == 0)
5048c2ecf20Sopenharmony_ci		priv->buffer_free = 1;
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci	return count;
5078c2ecf20Sopenharmony_ci}
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_cistatic ssize_t vmlogrdr_autopurge_store(struct device * dev,
5108c2ecf20Sopenharmony_ci					struct device_attribute *attr,
5118c2ecf20Sopenharmony_ci					const char * buf, size_t count)
5128c2ecf20Sopenharmony_ci{
5138c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
5148c2ecf20Sopenharmony_ci	ssize_t ret = count;
5158c2ecf20Sopenharmony_ci
5168c2ecf20Sopenharmony_ci	switch (buf[0]) {
5178c2ecf20Sopenharmony_ci	case '0':
5188c2ecf20Sopenharmony_ci		priv->autopurge=0;
5198c2ecf20Sopenharmony_ci		break;
5208c2ecf20Sopenharmony_ci	case '1':
5218c2ecf20Sopenharmony_ci		priv->autopurge=1;
5228c2ecf20Sopenharmony_ci		break;
5238c2ecf20Sopenharmony_ci	default:
5248c2ecf20Sopenharmony_ci		ret = -EINVAL;
5258c2ecf20Sopenharmony_ci	}
5268c2ecf20Sopenharmony_ci	return ret;
5278c2ecf20Sopenharmony_ci}
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_cistatic ssize_t vmlogrdr_autopurge_show(struct device *dev,
5318c2ecf20Sopenharmony_ci				       struct device_attribute *attr,
5328c2ecf20Sopenharmony_ci				       char *buf)
5338c2ecf20Sopenharmony_ci{
5348c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
5358c2ecf20Sopenharmony_ci	return sprintf(buf, "%u\n", priv->autopurge);
5368c2ecf20Sopenharmony_ci}
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_cistatic DEVICE_ATTR(autopurge, 0644, vmlogrdr_autopurge_show,
5408c2ecf20Sopenharmony_ci		   vmlogrdr_autopurge_store);
5418c2ecf20Sopenharmony_ci
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_cistatic ssize_t vmlogrdr_purge_store(struct device * dev,
5448c2ecf20Sopenharmony_ci				    struct device_attribute *attr,
5458c2ecf20Sopenharmony_ci				    const char * buf, size_t count)
5468c2ecf20Sopenharmony_ci{
5478c2ecf20Sopenharmony_ci
5488c2ecf20Sopenharmony_ci	char cp_command[80];
5498c2ecf20Sopenharmony_ci	char cp_response[80];
5508c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	if (buf[0] != '1')
5538c2ecf20Sopenharmony_ci		return -EINVAL;
5548c2ecf20Sopenharmony_ci
5558c2ecf20Sopenharmony_ci	memset(cp_command, 0x00, sizeof(cp_command));
5568c2ecf20Sopenharmony_ci	memset(cp_response, 0x00, sizeof(cp_response));
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci        /*
5598c2ecf20Sopenharmony_ci	 * The recording command needs to be called with option QID
5608c2ecf20Sopenharmony_ci	 * for guests that have previlege classes A or B.
5618c2ecf20Sopenharmony_ci	 * Other guests will not recognize the command and we have to
5628c2ecf20Sopenharmony_ci	 * issue the same command without the QID parameter.
5638c2ecf20Sopenharmony_ci	 */
5648c2ecf20Sopenharmony_ci
5658c2ecf20Sopenharmony_ci	if (recording_class_AB)
5668c2ecf20Sopenharmony_ci		snprintf(cp_command, sizeof(cp_command),
5678c2ecf20Sopenharmony_ci			 "RECORDING %s PURGE QID * ",
5688c2ecf20Sopenharmony_ci			 priv->recording_name);
5698c2ecf20Sopenharmony_ci	else
5708c2ecf20Sopenharmony_ci		snprintf(cp_command, sizeof(cp_command),
5718c2ecf20Sopenharmony_ci			 "RECORDING %s PURGE ",
5728c2ecf20Sopenharmony_ci			 priv->recording_name);
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci	cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ci	return count;
5778c2ecf20Sopenharmony_ci}
5788c2ecf20Sopenharmony_ci
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_cistatic DEVICE_ATTR(purge, 0200, NULL, vmlogrdr_purge_store);
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_cistatic ssize_t vmlogrdr_autorecording_store(struct device *dev,
5848c2ecf20Sopenharmony_ci					    struct device_attribute *attr,
5858c2ecf20Sopenharmony_ci					    const char *buf, size_t count)
5868c2ecf20Sopenharmony_ci{
5878c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
5888c2ecf20Sopenharmony_ci	ssize_t ret = count;
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_ci	switch (buf[0]) {
5918c2ecf20Sopenharmony_ci	case '0':
5928c2ecf20Sopenharmony_ci		priv->autorecording=0;
5938c2ecf20Sopenharmony_ci		break;
5948c2ecf20Sopenharmony_ci	case '1':
5958c2ecf20Sopenharmony_ci		priv->autorecording=1;
5968c2ecf20Sopenharmony_ci		break;
5978c2ecf20Sopenharmony_ci	default:
5988c2ecf20Sopenharmony_ci		ret = -EINVAL;
5998c2ecf20Sopenharmony_ci	}
6008c2ecf20Sopenharmony_ci	return ret;
6018c2ecf20Sopenharmony_ci}
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_ci
6048c2ecf20Sopenharmony_cistatic ssize_t vmlogrdr_autorecording_show(struct device *dev,
6058c2ecf20Sopenharmony_ci					   struct device_attribute *attr,
6068c2ecf20Sopenharmony_ci					   char *buf)
6078c2ecf20Sopenharmony_ci{
6088c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
6098c2ecf20Sopenharmony_ci	return sprintf(buf, "%u\n", priv->autorecording);
6108c2ecf20Sopenharmony_ci}
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_ci
6138c2ecf20Sopenharmony_cistatic DEVICE_ATTR(autorecording, 0644, vmlogrdr_autorecording_show,
6148c2ecf20Sopenharmony_ci		   vmlogrdr_autorecording_store);
6158c2ecf20Sopenharmony_ci
6168c2ecf20Sopenharmony_ci
6178c2ecf20Sopenharmony_cistatic ssize_t vmlogrdr_recording_store(struct device * dev,
6188c2ecf20Sopenharmony_ci					struct device_attribute *attr,
6198c2ecf20Sopenharmony_ci					const char * buf, size_t count)
6208c2ecf20Sopenharmony_ci{
6218c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
6228c2ecf20Sopenharmony_ci	ssize_t ret;
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_ci	switch (buf[0]) {
6258c2ecf20Sopenharmony_ci	case '0':
6268c2ecf20Sopenharmony_ci		ret = vmlogrdr_recording(priv,0,0);
6278c2ecf20Sopenharmony_ci		break;
6288c2ecf20Sopenharmony_ci	case '1':
6298c2ecf20Sopenharmony_ci		ret = vmlogrdr_recording(priv,1,0);
6308c2ecf20Sopenharmony_ci		break;
6318c2ecf20Sopenharmony_ci	default:
6328c2ecf20Sopenharmony_ci		ret = -EINVAL;
6338c2ecf20Sopenharmony_ci	}
6348c2ecf20Sopenharmony_ci	if (ret)
6358c2ecf20Sopenharmony_ci		return ret;
6368c2ecf20Sopenharmony_ci	else
6378c2ecf20Sopenharmony_ci		return count;
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci}
6408c2ecf20Sopenharmony_ci
6418c2ecf20Sopenharmony_ci
6428c2ecf20Sopenharmony_cistatic DEVICE_ATTR(recording, 0200, NULL, vmlogrdr_recording_store);
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_ci
6458c2ecf20Sopenharmony_cistatic ssize_t recording_status_show(struct device_driver *driver, char *buf)
6468c2ecf20Sopenharmony_ci{
6478c2ecf20Sopenharmony_ci	static const char cp_command[] = "QUERY RECORDING ";
6488c2ecf20Sopenharmony_ci	int len;
6498c2ecf20Sopenharmony_ci
6508c2ecf20Sopenharmony_ci	cpcmd(cp_command, buf, 4096, NULL);
6518c2ecf20Sopenharmony_ci	len = strlen(buf);
6528c2ecf20Sopenharmony_ci	return len;
6538c2ecf20Sopenharmony_ci}
6548c2ecf20Sopenharmony_cistatic DRIVER_ATTR_RO(recording_status);
6558c2ecf20Sopenharmony_cistatic struct attribute *vmlogrdr_drv_attrs[] = {
6568c2ecf20Sopenharmony_ci	&driver_attr_recording_status.attr,
6578c2ecf20Sopenharmony_ci	NULL,
6588c2ecf20Sopenharmony_ci};
6598c2ecf20Sopenharmony_cistatic struct attribute_group vmlogrdr_drv_attr_group = {
6608c2ecf20Sopenharmony_ci	.attrs = vmlogrdr_drv_attrs,
6618c2ecf20Sopenharmony_ci};
6628c2ecf20Sopenharmony_cistatic const struct attribute_group *vmlogrdr_drv_attr_groups[] = {
6638c2ecf20Sopenharmony_ci	&vmlogrdr_drv_attr_group,
6648c2ecf20Sopenharmony_ci	NULL,
6658c2ecf20Sopenharmony_ci};
6668c2ecf20Sopenharmony_ci
6678c2ecf20Sopenharmony_cistatic struct attribute *vmlogrdr_attrs[] = {
6688c2ecf20Sopenharmony_ci	&dev_attr_autopurge.attr,
6698c2ecf20Sopenharmony_ci	&dev_attr_purge.attr,
6708c2ecf20Sopenharmony_ci	&dev_attr_autorecording.attr,
6718c2ecf20Sopenharmony_ci	&dev_attr_recording.attr,
6728c2ecf20Sopenharmony_ci	NULL,
6738c2ecf20Sopenharmony_ci};
6748c2ecf20Sopenharmony_cistatic struct attribute_group vmlogrdr_attr_group = {
6758c2ecf20Sopenharmony_ci	.attrs = vmlogrdr_attrs,
6768c2ecf20Sopenharmony_ci};
6778c2ecf20Sopenharmony_cistatic const struct attribute_group *vmlogrdr_attr_groups[] = {
6788c2ecf20Sopenharmony_ci	&vmlogrdr_attr_group,
6798c2ecf20Sopenharmony_ci	NULL,
6808c2ecf20Sopenharmony_ci};
6818c2ecf20Sopenharmony_ci
6828c2ecf20Sopenharmony_cistatic int vmlogrdr_pm_prepare(struct device *dev)
6838c2ecf20Sopenharmony_ci{
6848c2ecf20Sopenharmony_ci	int rc;
6858c2ecf20Sopenharmony_ci	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
6868c2ecf20Sopenharmony_ci
6878c2ecf20Sopenharmony_ci	rc = 0;
6888c2ecf20Sopenharmony_ci	if (priv) {
6898c2ecf20Sopenharmony_ci		spin_lock_bh(&priv->priv_lock);
6908c2ecf20Sopenharmony_ci		if (priv->dev_in_use)
6918c2ecf20Sopenharmony_ci			rc = -EBUSY;
6928c2ecf20Sopenharmony_ci		spin_unlock_bh(&priv->priv_lock);
6938c2ecf20Sopenharmony_ci	}
6948c2ecf20Sopenharmony_ci	if (rc)
6958c2ecf20Sopenharmony_ci		pr_err("vmlogrdr: device %s is busy. Refuse to suspend.\n",
6968c2ecf20Sopenharmony_ci		       dev_name(dev));
6978c2ecf20Sopenharmony_ci	return rc;
6988c2ecf20Sopenharmony_ci}
6998c2ecf20Sopenharmony_ci
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_cistatic const struct dev_pm_ops vmlogrdr_pm_ops = {
7028c2ecf20Sopenharmony_ci	.prepare = vmlogrdr_pm_prepare,
7038c2ecf20Sopenharmony_ci};
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_cistatic struct class *vmlogrdr_class;
7068c2ecf20Sopenharmony_cistatic struct device_driver vmlogrdr_driver = {
7078c2ecf20Sopenharmony_ci	.name = "vmlogrdr",
7088c2ecf20Sopenharmony_ci	.bus  = &iucv_bus,
7098c2ecf20Sopenharmony_ci	.pm = &vmlogrdr_pm_ops,
7108c2ecf20Sopenharmony_ci	.groups = vmlogrdr_drv_attr_groups,
7118c2ecf20Sopenharmony_ci};
7128c2ecf20Sopenharmony_ci
7138c2ecf20Sopenharmony_cistatic int vmlogrdr_register_driver(void)
7148c2ecf20Sopenharmony_ci{
7158c2ecf20Sopenharmony_ci	int ret;
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci	/* Register with iucv driver */
7188c2ecf20Sopenharmony_ci	ret = iucv_register(&vmlogrdr_iucv_handler, 1);
7198c2ecf20Sopenharmony_ci	if (ret)
7208c2ecf20Sopenharmony_ci		goto out;
7218c2ecf20Sopenharmony_ci
7228c2ecf20Sopenharmony_ci	ret = driver_register(&vmlogrdr_driver);
7238c2ecf20Sopenharmony_ci	if (ret)
7248c2ecf20Sopenharmony_ci		goto out_iucv;
7258c2ecf20Sopenharmony_ci
7268c2ecf20Sopenharmony_ci	vmlogrdr_class = class_create(THIS_MODULE, "vmlogrdr");
7278c2ecf20Sopenharmony_ci	if (IS_ERR(vmlogrdr_class)) {
7288c2ecf20Sopenharmony_ci		ret = PTR_ERR(vmlogrdr_class);
7298c2ecf20Sopenharmony_ci		vmlogrdr_class = NULL;
7308c2ecf20Sopenharmony_ci		goto out_driver;
7318c2ecf20Sopenharmony_ci	}
7328c2ecf20Sopenharmony_ci	return 0;
7338c2ecf20Sopenharmony_ci
7348c2ecf20Sopenharmony_ciout_driver:
7358c2ecf20Sopenharmony_ci	driver_unregister(&vmlogrdr_driver);
7368c2ecf20Sopenharmony_ciout_iucv:
7378c2ecf20Sopenharmony_ci	iucv_unregister(&vmlogrdr_iucv_handler, 1);
7388c2ecf20Sopenharmony_ciout:
7398c2ecf20Sopenharmony_ci	return ret;
7408c2ecf20Sopenharmony_ci}
7418c2ecf20Sopenharmony_ci
7428c2ecf20Sopenharmony_ci
7438c2ecf20Sopenharmony_cistatic void vmlogrdr_unregister_driver(void)
7448c2ecf20Sopenharmony_ci{
7458c2ecf20Sopenharmony_ci	class_destroy(vmlogrdr_class);
7468c2ecf20Sopenharmony_ci	vmlogrdr_class = NULL;
7478c2ecf20Sopenharmony_ci	driver_unregister(&vmlogrdr_driver);
7488c2ecf20Sopenharmony_ci	iucv_unregister(&vmlogrdr_iucv_handler, 1);
7498c2ecf20Sopenharmony_ci}
7508c2ecf20Sopenharmony_ci
7518c2ecf20Sopenharmony_ci
7528c2ecf20Sopenharmony_cistatic int vmlogrdr_register_device(struct vmlogrdr_priv_t *priv)
7538c2ecf20Sopenharmony_ci{
7548c2ecf20Sopenharmony_ci	struct device *dev;
7558c2ecf20Sopenharmony_ci	int ret;
7568c2ecf20Sopenharmony_ci
7578c2ecf20Sopenharmony_ci	dev = kzalloc(sizeof(struct device), GFP_KERNEL);
7588c2ecf20Sopenharmony_ci	if (dev) {
7598c2ecf20Sopenharmony_ci		dev_set_name(dev, "%s", priv->internal_name);
7608c2ecf20Sopenharmony_ci		dev->bus = &iucv_bus;
7618c2ecf20Sopenharmony_ci		dev->parent = iucv_root;
7628c2ecf20Sopenharmony_ci		dev->driver = &vmlogrdr_driver;
7638c2ecf20Sopenharmony_ci		dev->groups = vmlogrdr_attr_groups;
7648c2ecf20Sopenharmony_ci		dev_set_drvdata(dev, priv);
7658c2ecf20Sopenharmony_ci		/*
7668c2ecf20Sopenharmony_ci		 * The release function could be called after the
7678c2ecf20Sopenharmony_ci		 * module has been unloaded. It's _only_ task is to
7688c2ecf20Sopenharmony_ci		 * free the struct. Therefore, we specify kfree()
7698c2ecf20Sopenharmony_ci		 * directly here. (Probably a little bit obfuscating
7708c2ecf20Sopenharmony_ci		 * but legitime ...).
7718c2ecf20Sopenharmony_ci		 */
7728c2ecf20Sopenharmony_ci		dev->release = (void (*)(struct device *))kfree;
7738c2ecf20Sopenharmony_ci	} else
7748c2ecf20Sopenharmony_ci		return -ENOMEM;
7758c2ecf20Sopenharmony_ci	ret = device_register(dev);
7768c2ecf20Sopenharmony_ci	if (ret) {
7778c2ecf20Sopenharmony_ci		put_device(dev);
7788c2ecf20Sopenharmony_ci		return ret;
7798c2ecf20Sopenharmony_ci	}
7808c2ecf20Sopenharmony_ci
7818c2ecf20Sopenharmony_ci	priv->class_device = device_create(vmlogrdr_class, dev,
7828c2ecf20Sopenharmony_ci					   MKDEV(vmlogrdr_major,
7838c2ecf20Sopenharmony_ci						 priv->minor_num),
7848c2ecf20Sopenharmony_ci					   priv, "%s", dev_name(dev));
7858c2ecf20Sopenharmony_ci	if (IS_ERR(priv->class_device)) {
7868c2ecf20Sopenharmony_ci		ret = PTR_ERR(priv->class_device);
7878c2ecf20Sopenharmony_ci		priv->class_device=NULL;
7888c2ecf20Sopenharmony_ci		device_unregister(dev);
7898c2ecf20Sopenharmony_ci		return ret;
7908c2ecf20Sopenharmony_ci	}
7918c2ecf20Sopenharmony_ci	priv->device = dev;
7928c2ecf20Sopenharmony_ci	return 0;
7938c2ecf20Sopenharmony_ci}
7948c2ecf20Sopenharmony_ci
7958c2ecf20Sopenharmony_ci
7968c2ecf20Sopenharmony_cistatic int vmlogrdr_unregister_device(struct vmlogrdr_priv_t *priv)
7978c2ecf20Sopenharmony_ci{
7988c2ecf20Sopenharmony_ci	device_destroy(vmlogrdr_class, MKDEV(vmlogrdr_major, priv->minor_num));
7998c2ecf20Sopenharmony_ci	if (priv->device != NULL) {
8008c2ecf20Sopenharmony_ci		device_unregister(priv->device);
8018c2ecf20Sopenharmony_ci		priv->device=NULL;
8028c2ecf20Sopenharmony_ci	}
8038c2ecf20Sopenharmony_ci	return 0;
8048c2ecf20Sopenharmony_ci}
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_ci
8078c2ecf20Sopenharmony_cistatic int vmlogrdr_register_cdev(dev_t dev)
8088c2ecf20Sopenharmony_ci{
8098c2ecf20Sopenharmony_ci	int rc = 0;
8108c2ecf20Sopenharmony_ci	vmlogrdr_cdev = cdev_alloc();
8118c2ecf20Sopenharmony_ci	if (!vmlogrdr_cdev) {
8128c2ecf20Sopenharmony_ci		return -ENOMEM;
8138c2ecf20Sopenharmony_ci	}
8148c2ecf20Sopenharmony_ci	vmlogrdr_cdev->owner = THIS_MODULE;
8158c2ecf20Sopenharmony_ci	vmlogrdr_cdev->ops = &vmlogrdr_fops;
8168c2ecf20Sopenharmony_ci	rc = cdev_add(vmlogrdr_cdev, dev, MAXMINOR);
8178c2ecf20Sopenharmony_ci	if (!rc)
8188c2ecf20Sopenharmony_ci		return 0;
8198c2ecf20Sopenharmony_ci
8208c2ecf20Sopenharmony_ci	// cleanup: cdev is not fully registered, no cdev_del here!
8218c2ecf20Sopenharmony_ci	kobject_put(&vmlogrdr_cdev->kobj);
8228c2ecf20Sopenharmony_ci	vmlogrdr_cdev=NULL;
8238c2ecf20Sopenharmony_ci	return rc;
8248c2ecf20Sopenharmony_ci}
8258c2ecf20Sopenharmony_ci
8268c2ecf20Sopenharmony_ci
8278c2ecf20Sopenharmony_cistatic void vmlogrdr_cleanup(void)
8288c2ecf20Sopenharmony_ci{
8298c2ecf20Sopenharmony_ci        int i;
8308c2ecf20Sopenharmony_ci
8318c2ecf20Sopenharmony_ci	if (vmlogrdr_cdev) {
8328c2ecf20Sopenharmony_ci		cdev_del(vmlogrdr_cdev);
8338c2ecf20Sopenharmony_ci		vmlogrdr_cdev=NULL;
8348c2ecf20Sopenharmony_ci	}
8358c2ecf20Sopenharmony_ci	for (i=0; i < MAXMINOR; ++i ) {
8368c2ecf20Sopenharmony_ci		vmlogrdr_unregister_device(&sys_ser[i]);
8378c2ecf20Sopenharmony_ci		free_page((unsigned long)sys_ser[i].buffer);
8388c2ecf20Sopenharmony_ci	}
8398c2ecf20Sopenharmony_ci	vmlogrdr_unregister_driver();
8408c2ecf20Sopenharmony_ci	if (vmlogrdr_major) {
8418c2ecf20Sopenharmony_ci		unregister_chrdev_region(MKDEV(vmlogrdr_major, 0), MAXMINOR);
8428c2ecf20Sopenharmony_ci		vmlogrdr_major=0;
8438c2ecf20Sopenharmony_ci	}
8448c2ecf20Sopenharmony_ci}
8458c2ecf20Sopenharmony_ci
8468c2ecf20Sopenharmony_ci
8478c2ecf20Sopenharmony_cistatic int __init vmlogrdr_init(void)
8488c2ecf20Sopenharmony_ci{
8498c2ecf20Sopenharmony_ci	int rc;
8508c2ecf20Sopenharmony_ci	int i;
8518c2ecf20Sopenharmony_ci	dev_t dev;
8528c2ecf20Sopenharmony_ci
8538c2ecf20Sopenharmony_ci	if (! MACHINE_IS_VM) {
8548c2ecf20Sopenharmony_ci		pr_err("not running under VM, driver not loaded.\n");
8558c2ecf20Sopenharmony_ci		return -ENODEV;
8568c2ecf20Sopenharmony_ci	}
8578c2ecf20Sopenharmony_ci
8588c2ecf20Sopenharmony_ci        recording_class_AB = vmlogrdr_get_recording_class_AB();
8598c2ecf20Sopenharmony_ci
8608c2ecf20Sopenharmony_ci	rc = alloc_chrdev_region(&dev, 0, MAXMINOR, "vmlogrdr");
8618c2ecf20Sopenharmony_ci	if (rc)
8628c2ecf20Sopenharmony_ci		return rc;
8638c2ecf20Sopenharmony_ci	vmlogrdr_major = MAJOR(dev);
8648c2ecf20Sopenharmony_ci
8658c2ecf20Sopenharmony_ci	rc=vmlogrdr_register_driver();
8668c2ecf20Sopenharmony_ci	if (rc)
8678c2ecf20Sopenharmony_ci		goto cleanup;
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	for (i=0; i < MAXMINOR; ++i ) {
8708c2ecf20Sopenharmony_ci		sys_ser[i].buffer = (char *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
8718c2ecf20Sopenharmony_ci		if (!sys_ser[i].buffer) {
8728c2ecf20Sopenharmony_ci			rc = -ENOMEM;
8738c2ecf20Sopenharmony_ci			break;
8748c2ecf20Sopenharmony_ci		}
8758c2ecf20Sopenharmony_ci		sys_ser[i].current_position = sys_ser[i].buffer;
8768c2ecf20Sopenharmony_ci		rc=vmlogrdr_register_device(&sys_ser[i]);
8778c2ecf20Sopenharmony_ci		if (rc)
8788c2ecf20Sopenharmony_ci			break;
8798c2ecf20Sopenharmony_ci	}
8808c2ecf20Sopenharmony_ci	if (rc)
8818c2ecf20Sopenharmony_ci		goto cleanup;
8828c2ecf20Sopenharmony_ci
8838c2ecf20Sopenharmony_ci	rc = vmlogrdr_register_cdev(dev);
8848c2ecf20Sopenharmony_ci	if (rc)
8858c2ecf20Sopenharmony_ci		goto cleanup;
8868c2ecf20Sopenharmony_ci	return 0;
8878c2ecf20Sopenharmony_ci
8888c2ecf20Sopenharmony_cicleanup:
8898c2ecf20Sopenharmony_ci	vmlogrdr_cleanup();
8908c2ecf20Sopenharmony_ci	return rc;
8918c2ecf20Sopenharmony_ci}
8928c2ecf20Sopenharmony_ci
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_cistatic void __exit vmlogrdr_exit(void)
8958c2ecf20Sopenharmony_ci{
8968c2ecf20Sopenharmony_ci	vmlogrdr_cleanup();
8978c2ecf20Sopenharmony_ci	return;
8988c2ecf20Sopenharmony_ci}
8998c2ecf20Sopenharmony_ci
9008c2ecf20Sopenharmony_ci
9018c2ecf20Sopenharmony_cimodule_init(vmlogrdr_init);
9028c2ecf20Sopenharmony_cimodule_exit(vmlogrdr_exit);
903