vmlogrdr.c 21.7 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4
/*
 *	character device driver for reading z/VM system service records
 *
 *
5
 *	Copyright IBM Corp. 2004, 2009
Linus Torvalds's avatar
Linus Torvalds committed
6 7 8 9 10 11
 *	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>
 *
 */
12 13 14 15

#define KMSG_COMPONENT "vmlogrdr"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt

Linus Torvalds's avatar
Linus Torvalds committed
16 17
#include <linux/module.h>
#include <linux/init.h>
18
#include <linux/slab.h>
Linus Torvalds's avatar
Linus Torvalds committed
19 20 21 22
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
Arun Sharma's avatar
Arun Sharma committed
23
#include <linux/atomic.h>
24
#include <linux/uaccess.h>
Linus Torvalds's avatar
Linus Torvalds committed
25 26 27
#include <asm/cpcmd.h>
#include <asm/debug.h>
#include <asm/ebcdic.h>
28
#include <net/iucv/iucv.h>
Linus Torvalds's avatar
Linus Torvalds committed
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
#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];
65
	struct iucv_path *path;
Linus Torvalds's avatar
Linus Torvalds committed
66 67
	int connection_established;
	int iucv_path_severed;
68
	struct iucv_message local_interrupt_buffer;
Linus Torvalds's avatar
Linus Torvalds committed
69 70 71 72 73 74 75 76 77 78
	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;
79
	struct device  *class_device;
Linus Torvalds's avatar
Linus Torvalds committed
80 81 82 83 84 85 86 87 88 89
	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 *);
Heiko Carstens's avatar
Heiko Carstens committed
90 91
static ssize_t vmlogrdr_read (struct file *filp, char __user *data,
			      size_t count, loff_t * ppos);
Linus Torvalds's avatar
Linus Torvalds committed
92

93
static const struct file_operations vmlogrdr_fops = {
Linus Torvalds's avatar
Linus Torvalds committed
94 95 96 97
	.owner   = THIS_MODULE,
	.open    = vmlogrdr_open,
	.release = vmlogrdr_release,
	.read    = vmlogrdr_read,
98
	.llseek  = no_llseek,
Linus Torvalds's avatar
Linus Torvalds committed
99 100 101
};


102 103
static void vmlogrdr_iucv_path_complete(struct iucv_path *, u8 *ipuser);
static void vmlogrdr_iucv_path_severed(struct iucv_path *, u8 *ipuser);
104 105
static void vmlogrdr_iucv_message_pending(struct iucv_path *,
					  struct iucv_message *);
Linus Torvalds's avatar
Linus Torvalds committed
106 107


108 109 110 111
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,
Linus Torvalds's avatar
Linus Torvalds committed
112 113 114
};


115 116
static DECLARE_WAIT_QUEUE_HEAD(conn_wait_queue);
static DECLARE_WAIT_QUEUE_HEAD(read_wait_queue);
Linus Torvalds's avatar
Linus Torvalds committed
117 118 119 120 121 122 123 124 125 126 127 128 129 130

/*
 * 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,
131
	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[0].priv_lock),
Linus Torvalds's avatar
Linus Torvalds committed
132 133 134 135 136 137 138 139
	  .autorecording  = 1,
	  .autopurge      = 1,
	},
	{ .system_service = "*ACCOUNT",
	  .internal_name  = "account",
	  .recording_name = "ACCOUNT",
	  .minor_num      = 1,
	  .buffer_free    = 1,
140
	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[1].priv_lock),
Linus Torvalds's avatar
Linus Torvalds committed
141 142 143 144 145 146 147 148
	  .autorecording  = 1,
	  .autopurge      = 1,
	},
	{ .system_service = "*SYMPTOM",
	  .internal_name  = "symptom",
	  .recording_name = "SYMPTOM",
	  .minor_num      = 2,
	  .buffer_free    = 1,
149
	  .priv_lock	  = __SPIN_LOCK_UNLOCKED(sys_ser[2].priv_lock),
Linus Torvalds's avatar
Linus Torvalds committed
150 151 152 153 154 155 156 157 158 159 160 161 162
	  .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;


163
static void vmlogrdr_iucv_path_complete(struct iucv_path *path, u8 *ipuser)
Linus Torvalds's avatar
Linus Torvalds committed
164
{
165 166
	struct vmlogrdr_priv_t * logptr = path->private;

Linus Torvalds's avatar
Linus Torvalds committed
167 168 169 170 171 172 173
	spin_lock(&logptr->priv_lock);
	logptr->connection_established = 1;
	spin_unlock(&logptr->priv_lock);
	wake_up(&conn_wait_queue);
}


174
static void vmlogrdr_iucv_path_severed(struct iucv_path *path, u8 *ipuser)
Linus Torvalds's avatar
Linus Torvalds committed
175
{
176 177
	struct vmlogrdr_priv_t * logptr = path->private;
	u8 reason = (u8) ipuser[8];
Linus Torvalds's avatar
Linus Torvalds committed
178

179
	pr_err("vmlogrdr: connection severed with reason %i\n", reason);
Linus Torvalds's avatar
Linus Torvalds committed
180

181 182 183 184
	iucv_path_sever(path, NULL);
	kfree(path);
	logptr->path = NULL;

Linus Torvalds's avatar
Linus Torvalds committed
185 186 187 188 189 190 191 192 193 194 195
	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);
}


196 197
static void vmlogrdr_iucv_message_pending(struct iucv_path *path,
					  struct iucv_message *msg)
Linus Torvalds's avatar
Linus Torvalds committed
198
{
199
	struct vmlogrdr_priv_t * logptr = path->private;
Linus Torvalds's avatar
Linus Torvalds committed
200 201 202 203 204 205 206

	/*
	 * 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);
207
	memcpy(&logptr->local_interrupt_buffer, msg, sizeof(*msg));
Linus Torvalds's avatar
Linus Torvalds committed
208 209 210 211 212 213
	atomic_inc(&logptr->receive_ready);
	spin_unlock(&logptr->priv_lock);
	wake_up_interruptible(&read_wait_queue);
}


214 215
static int vmlogrdr_get_recording_class_AB(void)
{
216
	static const char cp_command[] = "QUERY COMMAND RECORDING ";
Linus Torvalds's avatar
Linus Torvalds committed
217 218 219 220
	char cp_response[80];
	char *tail;
	int len,i;

221
	cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
Linus Torvalds's avatar
Linus Torvalds committed
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
	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;
}


243 244 245
static int vmlogrdr_recording(struct vmlogrdr_priv_t * logptr,
			      int action, int purge)
{
Linus Torvalds's avatar
Linus Torvalds committed
246 247 248 249

	char cp_command[80];
	char cp_response[160];
	char *onoff, *qid_string;
250
	int rc;
Linus Torvalds's avatar
Linus Torvalds committed
251

252
	onoff = ((action == 1) ? "ON" : "OFF");
Linus Torvalds's avatar
Linus Torvalds committed
253 254
	qid_string = ((recording_class_AB == 1) ? " QID * " : "");

255
	/*
Linus Torvalds's avatar
Linus Torvalds committed
256 257 258 259 260 261
	 * 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.
	 */
262 263 264
	if (purge && (action == 1)) {
		memset(cp_command, 0x00, sizeof(cp_command));
		memset(cp_response, 0x00, sizeof(cp_response));
Linus Torvalds's avatar
Linus Torvalds committed
265 266 267 268
		snprintf(cp_command, sizeof(cp_command),
			 "RECORDING %s PURGE %s",
			 logptr->recording_name,
			 qid_string);
269
		cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
Linus Torvalds's avatar
Linus Torvalds committed
270 271 272 273 274 275 276 277
	}

	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);
278
	cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
Linus Torvalds's avatar
Linus Torvalds committed
279 280 281 282
	/* 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
283
	 * 'Command complete'. So I use strstr rather then the strncmp.
Linus Torvalds's avatar
Linus Torvalds committed
284 285
	 */
	if (strstr(cp_response,"Command complete"))
286
		rc = 0;
Linus Torvalds's avatar
Linus Torvalds committed
287
	else
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
		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);
	}
Linus Torvalds's avatar
Linus Torvalds committed
303

304
	return rc;
Linus Torvalds's avatar
Linus Torvalds committed
305 306 307
}


308
static int vmlogrdr_open (struct inode *inode, struct file *filp)
Linus Torvalds's avatar
Linus Torvalds committed
309 310 311 312 313 314 315
{
	int dev_num = 0;
	struct vmlogrdr_priv_t * logptr = NULL;
	int connect_rc = 0;
	int ret;

	dev_num = iminor(inode);
316
	if (dev_num >= MAXMINOR)
Linus Torvalds's avatar
Linus Torvalds committed
317 318 319 320 321 322 323
		return -ENODEV;
	logptr = &sys_ser[dev_num];

	/*
	 * only allow for blocking reads to be open
	 */
	if (filp->f_flags & O_NONBLOCK)
324
		return -EOPNOTSUPP;
Linus Torvalds's avatar
Linus Torvalds committed
325 326 327 328 329 330 331

	/* 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;
	}
332 333 334
	logptr->dev_in_use = 1;
	logptr->connection_established = 0;
	logptr->iucv_path_severed = 0;
Linus Torvalds's avatar
Linus Torvalds committed
335 336
	atomic_set(&logptr->receive_ready, 0);
	logptr->buffer_free = 1;
337
	spin_unlock_bh(&logptr->priv_lock);
Linus Torvalds's avatar
Linus Torvalds committed
338 339 340 341 342

	/* set the file options */
	filp->private_data = logptr;

	/* start recording for this service*/
343
	if (logptr->autorecording) {
Linus Torvalds's avatar
Linus Torvalds committed
344
		ret = vmlogrdr_recording(logptr,1,logptr->autopurge);
345
		if (ret)
346
			pr_warn("vmlogrdr: failed to start recording automatically\n");
Linus Torvalds's avatar
Linus Torvalds committed
347 348 349
	}

	/* create connection to the system service */
350 351 352 353 354 355
	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);
Linus Torvalds's avatar
Linus Torvalds committed
356
	if (connect_rc) {
357 358 359
		pr_err("vmlogrdr: iucv connection to %s "
		       "failed with rc %i \n",
		       logptr->system_service, connect_rc);
360
		goto out_path;
Linus Torvalds's avatar
Linus Torvalds committed
361 362 363 364 365 366 367 368
	}

	/* 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));
369 370
	if (logptr->iucv_path_severed)
		goto out_record;
371 372
	nonseekable_open(inode, filp);
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
373

374
out_record:
Linus Torvalds's avatar
Linus Torvalds committed
375 376
	if (logptr->autorecording)
		vmlogrdr_recording(logptr,0,logptr->autopurge);
377 378 379 380
out_path:
	kfree(logptr->path);	/* kfree(NULL) is ok. */
	logptr->path = NULL;
out_dev:
Linus Torvalds's avatar
Linus Torvalds committed
381 382 383 384 385
	logptr->dev_in_use = 0;
	return -EIO;
}


386
static int vmlogrdr_release (struct inode *inode, struct file *filp)
Linus Torvalds's avatar
Linus Torvalds committed
387 388 389 390 391
{
	int ret;

	struct vmlogrdr_priv_t * logptr = filp->private_data;

392 393 394
	iucv_path_sever(logptr->path, NULL);
	kfree(logptr->path);
	logptr->path = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
395 396 397
	if (logptr->autorecording) {
		ret = vmlogrdr_recording(logptr,0,logptr->autopurge);
		if (ret)
398
			pr_warn("vmlogrdr: failed to stop recording automatically\n");
Linus Torvalds's avatar
Linus Torvalds committed
399 400 401 402 403 404 405
	}
	logptr->dev_in_use = 0;

	return 0;
}


406 407
static int vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv)
{
Linus Torvalds's avatar
Linus Torvalds committed
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
	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.
		         */
428
			iucv_data_count = priv->local_interrupt_buffer.length;
Linus Torvalds's avatar
Linus Torvalds committed
429 430 431 432 433 434
			user_data_count = sizeof(int);
			temp = (int*)priv->buffer;
			*temp= iucv_data_count + sizeof(FENCE);
			buffer = priv->buffer + sizeof(int);
		}
		/*
435
		 * If the record is bigger than our buffer, we receive only
Linus Torvalds's avatar
Linus Torvalds committed
436 437 438 439
		 * a part of it. We can get the rest later.
		 */
		if (iucv_data_count > NET_BUFFER_SIZE)
			iucv_data_count = NET_BUFFER_SIZE;
440 441 442 443
		rc = iucv_message_receive(priv->path,
					  &priv->local_interrupt_buffer,
					  0, buffer, iucv_data_count,
					  &priv->residual_length);
Linus Torvalds's avatar
Linus Torvalds committed
444
		spin_unlock_bh(&priv->priv_lock);
445
		/* An rc of 5 indicates that the record was bigger than
Linus Torvalds's avatar
Linus Torvalds committed
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
		 * 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;
	}

	return rc;
}


475 476
static ssize_t vmlogrdr_read(struct file *filp, char __user *data,
			     size_t count, loff_t * ppos)
Linus Torvalds's avatar
Linus Torvalds committed
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
{
	int rc;
	struct vmlogrdr_priv_t * priv = filp->private_data;

	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;

	*ppos += count;
	priv->current_position += count;
	priv->remaining -= count;

	/* if all data has been transferred, set buffer free */
	if (priv->remaining == 0)
		priv->buffer_free = 1;

	return count;
}

508 509 510 511
static ssize_t vmlogrdr_autopurge_store(struct device * dev,
					struct device_attribute *attr,
					const char * buf, size_t count)
{
512
	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
Linus Torvalds's avatar
Linus Torvalds committed
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
	ssize_t ret = count;

	switch (buf[0]) {
	case '0':
		priv->autopurge=0;
		break;
	case '1':
		priv->autopurge=1;
		break;
	default:
		ret = -EINVAL;
	}
	return ret;
}


529 530 531 532
static ssize_t vmlogrdr_autopurge_show(struct device *dev,
				       struct device_attribute *attr,
				       char *buf)
{
533
	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
Linus Torvalds's avatar
Linus Torvalds committed
534 535 536 537 538 539 540 541
	return sprintf(buf, "%u\n", priv->autopurge);
}


static DEVICE_ATTR(autopurge, 0644, vmlogrdr_autopurge_show,
		   vmlogrdr_autopurge_store);


542 543 544 545
static ssize_t vmlogrdr_purge_store(struct device * dev,
				    struct device_attribute *attr,
				    const char * buf, size_t count)
{
Linus Torvalds's avatar
Linus Torvalds committed
546 547 548

	char cp_command[80];
	char cp_response[80];
549
	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
Linus Torvalds's avatar
Linus Torvalds committed
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572

	if (buf[0] != '1')
		return -EINVAL;

	memset(cp_command, 0x00, sizeof(cp_command));
	memset(cp_response, 0x00, sizeof(cp_response));

        /*
	 * The recording command needs to be called with option QID
	 * for guests that have previlege classes A or B.
	 * Other guests will not recognize the command and we have to
	 * issue the same command without the QID parameter.
	 */

	if (recording_class_AB)
		snprintf(cp_command, sizeof(cp_command),
			 "RECORDING %s PURGE QID * ",
			 priv->recording_name);
	else
		snprintf(cp_command, sizeof(cp_command),
			 "RECORDING %s PURGE ",
			 priv->recording_name);

573
	cpcmd(cp_command, cp_response, sizeof(cp_response), NULL);
Linus Torvalds's avatar
Linus Torvalds committed
574 575 576 577 578 579 580 581

	return count;
}


static DEVICE_ATTR(purge, 0200, NULL, vmlogrdr_purge_store);


582 583 584 585
static ssize_t vmlogrdr_autorecording_store(struct device *dev,
					    struct device_attribute *attr,
					    const char *buf, size_t count)
{
586
	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
Linus Torvalds's avatar
Linus Torvalds committed
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
	ssize_t ret = count;

	switch (buf[0]) {
	case '0':
		priv->autorecording=0;
		break;
	case '1':
		priv->autorecording=1;
		break;
	default:
		ret = -EINVAL;
	}
	return ret;
}


603 604 605 606
static ssize_t vmlogrdr_autorecording_show(struct device *dev,
					   struct device_attribute *attr,
					   char *buf)
{
607
	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
Linus Torvalds's avatar
Linus Torvalds committed
608 609 610 611 612 613 614 615
	return sprintf(buf, "%u\n", priv->autorecording);
}


static DEVICE_ATTR(autorecording, 0644, vmlogrdr_autorecording_show,
		   vmlogrdr_autorecording_store);


616 617 618 619
static ssize_t vmlogrdr_recording_store(struct device * dev,
					struct device_attribute *attr,
					const char * buf, size_t count)
{
620
	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
Linus Torvalds's avatar
Linus Torvalds committed
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
	ssize_t ret;

	switch (buf[0]) {
	case '0':
		ret = vmlogrdr_recording(priv,0,0);
		break;
	case '1':
		ret = vmlogrdr_recording(priv,1,0);
		break;
	default:
		ret = -EINVAL;
	}
	if (ret)
		return ret;
	else
		return count;

}


static DEVICE_ATTR(recording, 0200, NULL, vmlogrdr_recording_store);


644
static ssize_t recording_status_show(struct device_driver *driver, char *buf)
645
{
646
	static const char cp_command[] = "QUERY RECORDING ";
Linus Torvalds's avatar
Linus Torvalds committed
647 648
	int len;

649
	cpcmd(cp_command, buf, 4096, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
650 651 652
	len = strlen(buf);
	return len;
}
653
static DRIVER_ATTR_RO(recording_status);
654 655 656 657 658 659 660 661 662 663 664
static struct attribute *vmlogrdr_drv_attrs[] = {
	&driver_attr_recording_status.attr,
	NULL,
};
static struct attribute_group vmlogrdr_drv_attr_group = {
	.attrs = vmlogrdr_drv_attrs,
};
static const struct attribute_group *vmlogrdr_drv_attr_groups[] = {
	&vmlogrdr_drv_attr_group,
	NULL,
};
Linus Torvalds's avatar
Linus Torvalds committed
665 666 667 668 669 670 671 672

static struct attribute *vmlogrdr_attrs[] = {
	&dev_attr_autopurge.attr,
	&dev_attr_purge.attr,
	&dev_attr_autorecording.attr,
	&dev_attr_recording.attr,
	NULL,
};
673 674 675 676 677 678 679
static struct attribute_group vmlogrdr_attr_group = {
	.attrs = vmlogrdr_attrs,
};
static const struct attribute_group *vmlogrdr_attr_groups[] = {
	&vmlogrdr_attr_group,
	NULL,
};
Linus Torvalds's avatar
Linus Torvalds committed
680

681 682 683
static int vmlogrdr_pm_prepare(struct device *dev)
{
	int rc;
684
	struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev);
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699

	rc = 0;
	if (priv) {
		spin_lock_bh(&priv->priv_lock);
		if (priv->dev_in_use)
			rc = -EBUSY;
		spin_unlock_bh(&priv->priv_lock);
	}
	if (rc)
		pr_err("vmlogrdr: device %s is busy. Refuse to suspend.\n",
		       dev_name(dev));
	return rc;
}


700
static const struct dev_pm_ops vmlogrdr_pm_ops = {
701 702 703
	.prepare = vmlogrdr_pm_prepare,
};

704
static struct class *vmlogrdr_class;
Linus Torvalds's avatar
Linus Torvalds committed
705 706 707
static struct device_driver vmlogrdr_driver = {
	.name = "vmlogrdr",
	.bus  = &iucv_bus,
708
	.pm = &vmlogrdr_pm_ops,
709
	.groups = vmlogrdr_drv_attr_groups,
Linus Torvalds's avatar
Linus Torvalds committed
710 711
};

712 713
static int vmlogrdr_register_driver(void)
{
Linus Torvalds's avatar
Linus Torvalds committed
714 715
	int ret;

716 717
	/* Register with iucv driver */
	ret = iucv_register(&vmlogrdr_iucv_handler, 1);
718
	if (ret)
719 720
		goto out;

Linus Torvalds's avatar
Linus Torvalds committed
721
	ret = driver_register(&vmlogrdr_driver);
722
	if (ret)
723
		goto out_iucv;
Linus Torvalds's avatar
Linus Torvalds committed
724

725
	vmlogrdr_class = class_create(THIS_MODULE, "vmlogrdr");
Linus Torvalds's avatar
Linus Torvalds committed
726
	if (IS_ERR(vmlogrdr_class)) {
727 728
		ret = PTR_ERR(vmlogrdr_class);
		vmlogrdr_class = NULL;
729
		goto out_driver;
Linus Torvalds's avatar
Linus Torvalds committed
730 731 732
	}
	return 0;

733
out_driver:
Linus Torvalds's avatar
Linus Torvalds committed
734
	driver_unregister(&vmlogrdr_driver);
735 736 737
out_iucv:
	iucv_unregister(&vmlogrdr_iucv_handler, 1);
out:
Linus Torvalds's avatar
Linus Torvalds committed
738 739 740 741
	return ret;
}


742 743
static void vmlogrdr_unregister_driver(void)
{
744
	class_destroy(vmlogrdr_class);
Linus Torvalds's avatar
Linus Torvalds committed
745 746
	vmlogrdr_class = NULL;
	driver_unregister(&vmlogrdr_driver);
747
	iucv_unregister(&vmlogrdr_iucv_handler, 1);
Linus Torvalds's avatar
Linus Torvalds committed
748 749 750
}


751 752
static int vmlogrdr_register_device(struct vmlogrdr_priv_t *priv)
{
Linus Torvalds's avatar
Linus Torvalds committed
753 754 755
	struct device *dev;
	int ret;

756
	dev = kzalloc(sizeof(struct device), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
757
	if (dev) {
758
		dev_set_name(dev, "%s", priv->internal_name);
Linus Torvalds's avatar
Linus Torvalds committed
759 760 761
		dev->bus = &iucv_bus;
		dev->parent = iucv_root;
		dev->driver = &vmlogrdr_driver;
762
		dev->groups = vmlogrdr_attr_groups;
763
		dev_set_drvdata(dev, priv);
Linus Torvalds's avatar
Linus Torvalds committed
764 765 766 767 768 769 770 771 772 773 774
		/*
		 * The release function could be called after the
		 * module has been unloaded. It's _only_ task is to
		 * free the struct. Therefore, we specify kfree()
		 * directly here. (Probably a little bit obfuscating
		 * but legitime ...).
		 */
		dev->release = (void (*)(struct device *))kfree;
	} else
		return -ENOMEM;
	ret = device_register(dev);
775 776
	if (ret) {
		put_device(dev);
Linus Torvalds's avatar
Linus Torvalds committed
777
		return ret;
778
	}
Linus Torvalds's avatar
Linus Torvalds committed
779

780 781 782 783
	priv->class_device = device_create(vmlogrdr_class, dev,
					   MKDEV(vmlogrdr_major,
						 priv->minor_num),
					   priv, "%s", dev_name(dev));
Linus Torvalds's avatar
Linus Torvalds committed
784 785 786 787 788 789 790 791 792 793 794
	if (IS_ERR(priv->class_device)) {
		ret = PTR_ERR(priv->class_device);
		priv->class_device=NULL;
		device_unregister(dev);
		return ret;
	}
	priv->device = dev;
	return 0;
}


795 796
static int vmlogrdr_unregister_device(struct vmlogrdr_priv_t *priv)
{
797
	device_destroy(vmlogrdr_class, MKDEV(vmlogrdr_major, priv->minor_num));
Linus Torvalds's avatar
Linus Torvalds committed
798 799 800 801 802 803 804 805
	if (priv->device != NULL) {
		device_unregister(priv->device);
		priv->device=NULL;
	}
	return 0;
}


806 807
static int vmlogrdr_register_cdev(dev_t dev)
{
Linus Torvalds's avatar
Linus Torvalds committed
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
	int rc = 0;
	vmlogrdr_cdev = cdev_alloc();
	if (!vmlogrdr_cdev) {
		return -ENOMEM;
	}
	vmlogrdr_cdev->owner = THIS_MODULE;
	vmlogrdr_cdev->ops = &vmlogrdr_fops;
	vmlogrdr_cdev->dev = dev;
	rc = cdev_add(vmlogrdr_cdev, vmlogrdr_cdev->dev, MAXMINOR);
	if (!rc)
		return 0;

	// cleanup: cdev is not fully registered, no cdev_del here!
	kobject_put(&vmlogrdr_cdev->kobj);
	vmlogrdr_cdev=NULL;
	return rc;
}


827 828
static void vmlogrdr_cleanup(void)
{
Linus Torvalds's avatar
Linus Torvalds committed
829
        int i;
830

Linus Torvalds's avatar
Linus Torvalds committed
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
	if (vmlogrdr_cdev) {
		cdev_del(vmlogrdr_cdev);
		vmlogrdr_cdev=NULL;
	}
	for (i=0; i < MAXMINOR; ++i ) {
		vmlogrdr_unregister_device(&sys_ser[i]);
		free_page((unsigned long)sys_ser[i].buffer);
	}
	vmlogrdr_unregister_driver();
	if (vmlogrdr_major) {
		unregister_chrdev_region(MKDEV(vmlogrdr_major, 0), MAXMINOR);
		vmlogrdr_major=0;
	}
}


847
static int __init vmlogrdr_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
848 849 850 851 852 853
{
	int rc;
	int i;
	dev_t dev;

	if (! MACHINE_IS_VM) {
854
		pr_err("not running under VM, driver not loaded.\n");
Linus Torvalds's avatar
Linus Torvalds committed
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
		return -ENODEV;
	}

        recording_class_AB = vmlogrdr_get_recording_class_AB();

	rc = alloc_chrdev_region(&dev, 0, MAXMINOR, "vmlogrdr");
	if (rc)
		return rc;
	vmlogrdr_major = MAJOR(dev);

	rc=vmlogrdr_register_driver();
	if (rc)
		goto cleanup;

	for (i=0; i < MAXMINOR; ++i ) {
870
		sys_ser[i].buffer = (char *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
Linus Torvalds's avatar
Linus Torvalds committed
871
		if (!sys_ser[i].buffer) {
872
			rc = -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
			break;
		}
		sys_ser[i].current_position = sys_ser[i].buffer;
		rc=vmlogrdr_register_device(&sys_ser[i]);
		if (rc)
			break;
	}
	if (rc)
		goto cleanup;

	rc = vmlogrdr_register_cdev(dev);
	if (rc)
		goto cleanup;
	return 0;

cleanup:
	vmlogrdr_cleanup();
	return rc;
}


894
static void __exit vmlogrdr_exit(void)
Linus Torvalds's avatar
Linus Torvalds committed
895 896 897 898 899 900 901 902
{
	vmlogrdr_cleanup();
	return;
}


module_init(vmlogrdr_init);
module_exit(vmlogrdr_exit);