xref: /linux/drivers/s390/char/monwriter.c (revision cdd5b5a9761fd66d17586e4f4ba6588c70e640ea)
16f05e69eSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
231b58088SMelissa Howland /*
331b58088SMelissa Howland  * Character device driver for writing z/VM *MONITOR service records.
431b58088SMelissa Howland  *
5fb78140cSGerald Schaefer  * Copyright IBM Corp. 2006, 2009
631b58088SMelissa Howland  *
731b58088SMelissa Howland  * Author(s): Melissa Howland <Melissa.Howland@us.ibm.com>
831b58088SMelissa Howland  */
931b58088SMelissa Howland 
101519c0c6SMelissa Howland #define KMSG_COMPONENT "monwriter"
111519c0c6SMelissa Howland #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
121519c0c6SMelissa Howland 
1331b58088SMelissa Howland #include <linux/module.h>
1431b58088SMelissa Howland #include <linux/moduleparam.h>
1531b58088SMelissa Howland #include <linux/init.h>
1631b58088SMelissa Howland #include <linux/errno.h>
1731b58088SMelissa Howland #include <linux/types.h>
1831b58088SMelissa Howland #include <linux/kernel.h>
1931b58088SMelissa Howland #include <linux/miscdevice.h>
2031b58088SMelissa Howland #include <linux/ctype.h>
2131b58088SMelissa Howland #include <linux/poll.h>
22cbea66d9SMelissa Howland #include <linux/mutex.h>
235a0e3ad6STejun Heo #include <linux/slab.h>
247c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
25*b378a982SHeiko Carstens #include <linux/io.h>
2631b58088SMelissa Howland #include <asm/ebcdic.h>
2731b58088SMelissa Howland #include <asm/appldata.h>
2831b58088SMelissa Howland #include <asm/monwriter.h>
2931b58088SMelissa Howland 
30524a237eSMelissa Howland #define MONWRITE_MAX_DATALEN	4010
3131b58088SMelissa Howland 
3231b58088SMelissa Howland static int mon_max_bufs = 255;
332d103d5aSMelissa Howland static int mon_buf_count;
3431b58088SMelissa Howland 
3531b58088SMelissa Howland struct mon_buf {
3631b58088SMelissa Howland 	struct list_head list;
3731b58088SMelissa Howland 	struct monwrite_hdr hdr;
3831b58088SMelissa Howland 	int diag_done;
3931b58088SMelissa Howland 	char *data;
4031b58088SMelissa Howland };
4131b58088SMelissa Howland 
4231b58088SMelissa Howland struct mon_private {
4331b58088SMelissa Howland 	struct list_head list;
4431b58088SMelissa Howland 	struct monwrite_hdr hdr;
4531b58088SMelissa Howland 	size_t hdr_to_read;
4631b58088SMelissa Howland 	size_t data_to_read;
4731b58088SMelissa Howland 	struct mon_buf *current_buf;
48cbea66d9SMelissa Howland 	struct mutex thread_mutex;
4931b58088SMelissa Howland };
5031b58088SMelissa Howland 
5131b58088SMelissa Howland /*
5231b58088SMelissa Howland  * helper functions
5331b58088SMelissa Howland  */
5431b58088SMelissa Howland 
monwrite_diag(struct monwrite_hdr * myhdr,char * buffer,int fcn)5531b58088SMelissa Howland static int monwrite_diag(struct monwrite_hdr *myhdr, char *buffer, int fcn)
5631b58088SMelissa Howland {
57c0f07ff9SMartin Schwidefsky 	struct appldata_parameter_list *parm_list;
58c0f07ff9SMartin Schwidefsky 	struct appldata_product_id *id;
5931b58088SMelissa Howland 	int rc;
6031b58088SMelissa Howland 
61c0f07ff9SMartin Schwidefsky 	id = kmalloc(sizeof(*id), GFP_KERNEL);
62c0f07ff9SMartin Schwidefsky 	parm_list = kmalloc(sizeof(*parm_list), GFP_KERNEL);
63c0f07ff9SMartin Schwidefsky 	rc = -ENOMEM;
64c0f07ff9SMartin Schwidefsky 	if (!id || !parm_list)
65c0f07ff9SMartin Schwidefsky 		goto out;
66c0f07ff9SMartin Schwidefsky 	memcpy(id->prod_nr, "LNXAPPL", 7);
67c0f07ff9SMartin Schwidefsky 	id->prod_fn = myhdr->applid;
68c0f07ff9SMartin Schwidefsky 	id->record_nr = myhdr->record_num;
69c0f07ff9SMartin Schwidefsky 	id->version_nr = myhdr->version;
70c0f07ff9SMartin Schwidefsky 	id->release_nr = myhdr->release;
71c0f07ff9SMartin Schwidefsky 	id->mod_lvl = myhdr->mod_level;
72c0f07ff9SMartin Schwidefsky 	rc = appldata_asm(parm_list, id, fcn,
73f689789aSMartin Schwidefsky 			  (void *) buffer, myhdr->datalen);
7431b58088SMelissa Howland 	if (rc <= 0)
75c0f07ff9SMartin Schwidefsky 		goto out;
761519c0c6SMelissa Howland 	pr_err("Writing monitor data failed with rc=%i\n", rc);
77c0f07ff9SMartin Schwidefsky 	rc = (rc == 5) ? -EPERM : -EINVAL;
78c0f07ff9SMartin Schwidefsky out:
79c0f07ff9SMartin Schwidefsky 	kfree(id);
80c0f07ff9SMartin Schwidefsky 	kfree(parm_list);
81c0f07ff9SMartin Schwidefsky 	return rc;
8231b58088SMelissa Howland }
8331b58088SMelissa Howland 
monwrite_find_hdr(struct mon_private * monpriv,struct monwrite_hdr * monhdr)844d284cacSHeiko Carstens static struct mon_buf *monwrite_find_hdr(struct mon_private *monpriv,
8531b58088SMelissa Howland 					 struct monwrite_hdr *monhdr)
8631b58088SMelissa Howland {
8731b58088SMelissa Howland 	struct mon_buf *entry, *next;
8831b58088SMelissa Howland 
8931b58088SMelissa Howland 	list_for_each_entry_safe(entry, next, &monpriv->list, list)
902c91971fSMelissa Howland 		if ((entry->hdr.mon_function == monhdr->mon_function ||
912c91971fSMelissa Howland 		     monhdr->mon_function == MONWRITE_STOP_INTERVAL) &&
922c91971fSMelissa Howland 		    entry->hdr.applid == monhdr->applid &&
9331b58088SMelissa Howland 		    entry->hdr.record_num == monhdr->record_num &&
9431b58088SMelissa Howland 		    entry->hdr.version == monhdr->version &&
9531b58088SMelissa Howland 		    entry->hdr.release == monhdr->release &&
9631b58088SMelissa Howland 		    entry->hdr.mod_level == monhdr->mod_level)
9731b58088SMelissa Howland 			return entry;
982c91971fSMelissa Howland 
9931b58088SMelissa Howland 	return NULL;
10031b58088SMelissa Howland }
10131b58088SMelissa Howland 
monwrite_new_hdr(struct mon_private * monpriv)10231b58088SMelissa Howland static int monwrite_new_hdr(struct mon_private *monpriv)
10331b58088SMelissa Howland {
10431b58088SMelissa Howland 	struct monwrite_hdr *monhdr = &monpriv->hdr;
10531b58088SMelissa Howland 	struct mon_buf *monbuf;
10637fa9975SHeiko Carstens 	int rc = 0;
10731b58088SMelissa Howland 
10831b58088SMelissa Howland 	if (monhdr->datalen > MONWRITE_MAX_DATALEN ||
10931b58088SMelissa Howland 	    monhdr->mon_function > MONWRITE_START_CONFIG ||
11031b58088SMelissa Howland 	    monhdr->hdrlen != sizeof(struct monwrite_hdr))
11131b58088SMelissa Howland 		return -EINVAL;
1122c91971fSMelissa Howland 	monbuf = NULL;
1132c91971fSMelissa Howland 	if (monhdr->mon_function != MONWRITE_GEN_EVENT)
11431b58088SMelissa Howland 		monbuf = monwrite_find_hdr(monpriv, monhdr);
11531b58088SMelissa Howland 	if (monbuf) {
11631b58088SMelissa Howland 		if (monhdr->mon_function == MONWRITE_STOP_INTERVAL) {
11731b58088SMelissa Howland 			monhdr->datalen = monbuf->hdr.datalen;
11831b58088SMelissa Howland 			rc = monwrite_diag(monhdr, monbuf->data,
11931b58088SMelissa Howland 					   APPLDATA_STOP_REC);
12031b58088SMelissa Howland 			list_del(&monbuf->list);
1212d103d5aSMelissa Howland 			mon_buf_count--;
12231b58088SMelissa Howland 			kfree(monbuf->data);
12331b58088SMelissa Howland 			kfree(monbuf);
12431b58088SMelissa Howland 			monbuf = NULL;
12531b58088SMelissa Howland 		}
1262c91971fSMelissa Howland 	} else if (monhdr->mon_function != MONWRITE_STOP_INTERVAL) {
1272d103d5aSMelissa Howland 		if (mon_buf_count >= mon_max_bufs)
12831b58088SMelissa Howland 			return -ENOSPC;
12931b58088SMelissa Howland 		monbuf = kzalloc(sizeof(struct mon_buf), GFP_KERNEL);
13031b58088SMelissa Howland 		if (!monbuf)
13131b58088SMelissa Howland 			return -ENOMEM;
132715d854bSMelissa Howland 		monbuf->data = kzalloc(monhdr->datalen,
13331b58088SMelissa Howland 				       GFP_KERNEL | GFP_DMA);
13431b58088SMelissa Howland 		if (!monbuf->data) {
13531b58088SMelissa Howland 			kfree(monbuf);
13631b58088SMelissa Howland 			return -ENOMEM;
13731b58088SMelissa Howland 		}
13831b58088SMelissa Howland 		monbuf->hdr = *monhdr;
13931b58088SMelissa Howland 		list_add_tail(&monbuf->list, &monpriv->list);
1402c91971fSMelissa Howland 		if (monhdr->mon_function != MONWRITE_GEN_EVENT)
1412d103d5aSMelissa Howland 			mon_buf_count++;
14231b58088SMelissa Howland 	}
14331b58088SMelissa Howland 	monpriv->current_buf = monbuf;
14437fa9975SHeiko Carstens 	return rc;
14531b58088SMelissa Howland }
14631b58088SMelissa Howland 
monwrite_new_data(struct mon_private * monpriv)14731b58088SMelissa Howland static int monwrite_new_data(struct mon_private *monpriv)
14831b58088SMelissa Howland {
14931b58088SMelissa Howland 	struct monwrite_hdr *monhdr = &monpriv->hdr;
15031b58088SMelissa Howland 	struct mon_buf *monbuf = monpriv->current_buf;
15131b58088SMelissa Howland 	int rc = 0;
15231b58088SMelissa Howland 
15331b58088SMelissa Howland 	switch (monhdr->mon_function) {
15431b58088SMelissa Howland 	case MONWRITE_START_INTERVAL:
15531b58088SMelissa Howland 		if (!monbuf->diag_done) {
15631b58088SMelissa Howland 			rc = monwrite_diag(monhdr, monbuf->data,
15731b58088SMelissa Howland 					   APPLDATA_START_INTERVAL_REC);
15831b58088SMelissa Howland 			monbuf->diag_done = 1;
15931b58088SMelissa Howland 		}
16031b58088SMelissa Howland 		break;
16131b58088SMelissa Howland 	case MONWRITE_START_CONFIG:
16231b58088SMelissa Howland 		if (!monbuf->diag_done) {
16331b58088SMelissa Howland 			rc = monwrite_diag(monhdr, monbuf->data,
16431b58088SMelissa Howland 					   APPLDATA_START_CONFIG_REC);
16531b58088SMelissa Howland 			monbuf->diag_done = 1;
16631b58088SMelissa Howland 		}
16731b58088SMelissa Howland 		break;
16831b58088SMelissa Howland 	case MONWRITE_GEN_EVENT:
16931b58088SMelissa Howland 		rc = monwrite_diag(monhdr, monbuf->data,
17031b58088SMelissa Howland 				   APPLDATA_GEN_EVENT_REC);
17131b58088SMelissa Howland 		list_del(&monpriv->current_buf->list);
17231b58088SMelissa Howland 		kfree(monpriv->current_buf->data);
17331b58088SMelissa Howland 		kfree(monpriv->current_buf);
17431b58088SMelissa Howland 		monpriv->current_buf = NULL;
17531b58088SMelissa Howland 		break;
17631b58088SMelissa Howland 	default:
17731b58088SMelissa Howland 		/* monhdr->mon_function is checked in monwrite_new_hdr */
17831b58088SMelissa Howland 		BUG();
17931b58088SMelissa Howland 	}
18031b58088SMelissa Howland 	return rc;
18131b58088SMelissa Howland }
18231b58088SMelissa Howland 
18331b58088SMelissa Howland /*
18431b58088SMelissa Howland  * file operations
18531b58088SMelissa Howland  */
18631b58088SMelissa Howland 
monwrite_open(struct inode * inode,struct file * filp)18731b58088SMelissa Howland static int monwrite_open(struct inode *inode, struct file *filp)
18831b58088SMelissa Howland {
18931b58088SMelissa Howland 	struct mon_private *monpriv;
19031b58088SMelissa Howland 
19131b58088SMelissa Howland 	monpriv = kzalloc(sizeof(struct mon_private), GFP_KERNEL);
19231b58088SMelissa Howland 	if (!monpriv)
19331b58088SMelissa Howland 		return -ENOMEM;
19431b58088SMelissa Howland 	INIT_LIST_HEAD(&monpriv->list);
19531b58088SMelissa Howland 	monpriv->hdr_to_read = sizeof(monpriv->hdr);
196cbea66d9SMelissa Howland 	mutex_init(&monpriv->thread_mutex);
19731b58088SMelissa Howland 	filp->private_data = monpriv;
19831b58088SMelissa Howland 	return nonseekable_open(inode, filp);
19931b58088SMelissa Howland }
20031b58088SMelissa Howland 
monwrite_close(struct inode * inode,struct file * filp)20131b58088SMelissa Howland static int monwrite_close(struct inode *inode, struct file *filp)
20231b58088SMelissa Howland {
20331b58088SMelissa Howland 	struct mon_private *monpriv = filp->private_data;
20431b58088SMelissa Howland 	struct mon_buf *entry, *next;
20531b58088SMelissa Howland 
20631b58088SMelissa Howland 	list_for_each_entry_safe(entry, next, &monpriv->list, list) {
20731b58088SMelissa Howland 		if (entry->hdr.mon_function != MONWRITE_GEN_EVENT)
20831b58088SMelissa Howland 			monwrite_diag(&entry->hdr, entry->data,
20931b58088SMelissa Howland 				      APPLDATA_STOP_REC);
2102d103d5aSMelissa Howland 		mon_buf_count--;
21131b58088SMelissa Howland 		list_del(&entry->list);
21231b58088SMelissa Howland 		kfree(entry->data);
21331b58088SMelissa Howland 		kfree(entry);
21431b58088SMelissa Howland 	}
21531b58088SMelissa Howland 	kfree(monpriv);
21631b58088SMelissa Howland 	return 0;
21731b58088SMelissa Howland }
21831b58088SMelissa Howland 
monwrite_write(struct file * filp,const char __user * data,size_t count,loff_t * ppos)21931b58088SMelissa Howland static ssize_t monwrite_write(struct file *filp, const char __user *data,
22031b58088SMelissa Howland 			      size_t count, loff_t *ppos)
22131b58088SMelissa Howland {
22231b58088SMelissa Howland 	struct mon_private *monpriv = filp->private_data;
22331b58088SMelissa Howland 	size_t len, written;
22431b58088SMelissa Howland 	void *to;
22531b58088SMelissa Howland 	int rc;
22631b58088SMelissa Howland 
227cbea66d9SMelissa Howland 	mutex_lock(&monpriv->thread_mutex);
22831b58088SMelissa Howland 	for (written = 0; written < count; ) {
22931b58088SMelissa Howland 		if (monpriv->hdr_to_read) {
23031b58088SMelissa Howland 			len = min(count - written, monpriv->hdr_to_read);
23131b58088SMelissa Howland 			to = (char *) &monpriv->hdr +
23231b58088SMelissa Howland 				sizeof(monpriv->hdr) - monpriv->hdr_to_read;
23331b58088SMelissa Howland 			if (copy_from_user(to, data + written, len)) {
23431b58088SMelissa Howland 				rc = -EFAULT;
23531b58088SMelissa Howland 				goto out_error;
23631b58088SMelissa Howland 			}
23731b58088SMelissa Howland 			monpriv->hdr_to_read -= len;
23831b58088SMelissa Howland 			written += len;
23931b58088SMelissa Howland 			if (monpriv->hdr_to_read > 0)
24031b58088SMelissa Howland 				continue;
24131b58088SMelissa Howland 			rc = monwrite_new_hdr(monpriv);
24231b58088SMelissa Howland 			if (rc)
24331b58088SMelissa Howland 				goto out_error;
24431b58088SMelissa Howland 			monpriv->data_to_read = monpriv->current_buf ?
24531b58088SMelissa Howland 				monpriv->current_buf->hdr.datalen : 0;
24631b58088SMelissa Howland 		}
24731b58088SMelissa Howland 
24831b58088SMelissa Howland 		if (monpriv->data_to_read) {
24931b58088SMelissa Howland 			len = min(count - written, monpriv->data_to_read);
25031b58088SMelissa Howland 			to = monpriv->current_buf->data +
25131b58088SMelissa Howland 				monpriv->hdr.datalen - monpriv->data_to_read;
25231b58088SMelissa Howland 			if (copy_from_user(to, data + written, len)) {
25331b58088SMelissa Howland 				rc = -EFAULT;
25431b58088SMelissa Howland 				goto out_error;
25531b58088SMelissa Howland 			}
25631b58088SMelissa Howland 			monpriv->data_to_read -= len;
25731b58088SMelissa Howland 			written += len;
25831b58088SMelissa Howland 			if (monpriv->data_to_read > 0)
25931b58088SMelissa Howland 				continue;
26031b58088SMelissa Howland 			rc = monwrite_new_data(monpriv);
26131b58088SMelissa Howland 			if (rc)
26231b58088SMelissa Howland 				goto out_error;
26331b58088SMelissa Howland 		}
26431b58088SMelissa Howland 		monpriv->hdr_to_read = sizeof(monpriv->hdr);
26531b58088SMelissa Howland 	}
266cbea66d9SMelissa Howland 	mutex_unlock(&monpriv->thread_mutex);
26731b58088SMelissa Howland 	return written;
26831b58088SMelissa Howland 
26931b58088SMelissa Howland out_error:
27031b58088SMelissa Howland 	monpriv->data_to_read = 0;
27131b58088SMelissa Howland 	monpriv->hdr_to_read = sizeof(struct monwrite_hdr);
272cbea66d9SMelissa Howland 	mutex_unlock(&monpriv->thread_mutex);
27331b58088SMelissa Howland 	return rc;
27431b58088SMelissa Howland }
27531b58088SMelissa Howland 
276d54b1fdbSArjan van de Ven static const struct file_operations monwrite_fops = {
27731b58088SMelissa Howland 	.owner	 = THIS_MODULE,
27831b58088SMelissa Howland 	.open	 = &monwrite_open,
27931b58088SMelissa Howland 	.release = &monwrite_close,
28031b58088SMelissa Howland 	.write	 = &monwrite_write,
2816038f373SArnd Bergmann 	.llseek  = noop_llseek,
28231b58088SMelissa Howland };
28331b58088SMelissa Howland 
28431b58088SMelissa Howland static struct miscdevice mon_dev = {
28531b58088SMelissa Howland 	.name	= "monwriter",
28631b58088SMelissa Howland 	.fops	= &monwrite_fops,
28731b58088SMelissa Howland 	.minor	= MISC_DYNAMIC_MINOR,
28831b58088SMelissa Howland };
28931b58088SMelissa Howland 
29031b58088SMelissa Howland /*
29131b58088SMelissa Howland  * module init/exit
29231b58088SMelissa Howland  */
29331b58088SMelissa Howland 
mon_init(void)29431b58088SMelissa Howland static int __init mon_init(void)
29531b58088SMelissa Howland {
296fb78140cSGerald Schaefer 	if (!MACHINE_IS_VM)
29731b58088SMelissa Howland 		return -ENODEV;
298801f97b7SGerald Schaefer 	/*
299801f97b7SGerald Schaefer 	 * misc_register() has to be the last action in module_init(), because
300801f97b7SGerald Schaefer 	 * file operations will be available right after this.
301801f97b7SGerald Schaefer 	 */
30221adcf11SPeter Oberparleiter 	return misc_register(&mon_dev);
30331b58088SMelissa Howland }
30431b58088SMelissa Howland 
mon_exit(void)30531b58088SMelissa Howland static void __exit mon_exit(void)
30631b58088SMelissa Howland {
307547415d5SAkinobu Mita 	misc_deregister(&mon_dev);
30831b58088SMelissa Howland }
30931b58088SMelissa Howland 
31031b58088SMelissa Howland module_init(mon_init);
31131b58088SMelissa Howland module_exit(mon_exit);
31231b58088SMelissa Howland 
31331b58088SMelissa Howland module_param_named(max_bufs, mon_max_bufs, int, 0644);
31431b58088SMelissa Howland MODULE_PARM_DESC(max_bufs, "Maximum number of sample monitor data buffers "
31531b58088SMelissa Howland 		 "that can be active at one time");
31631b58088SMelissa Howland 
31731b58088SMelissa Howland MODULE_AUTHOR("Melissa Howland <Melissa.Howland@us.ibm.com>");
31831b58088SMelissa Howland MODULE_DESCRIPTION("Character device driver for writing z/VM "
31931b58088SMelissa Howland 		   "APPLDATA monitor records.");
32031b58088SMelissa Howland MODULE_LICENSE("GPL");
321