162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Character device driver for writing z/VM *MONITOR service records.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright IBM Corp. 2006, 2009
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author(s): Melissa Howland <Melissa.Howland@us.ibm.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define KMSG_COMPONENT "monwriter"
1162306a36Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/moduleparam.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/errno.h>
1762306a36Sopenharmony_ci#include <linux/types.h>
1862306a36Sopenharmony_ci#include <linux/kernel.h>
1962306a36Sopenharmony_ci#include <linux/miscdevice.h>
2062306a36Sopenharmony_ci#include <linux/ctype.h>
2162306a36Sopenharmony_ci#include <linux/poll.h>
2262306a36Sopenharmony_ci#include <linux/mutex.h>
2362306a36Sopenharmony_ci#include <linux/slab.h>
2462306a36Sopenharmony_ci#include <linux/uaccess.h>
2562306a36Sopenharmony_ci#include <linux/io.h>
2662306a36Sopenharmony_ci#include <asm/ebcdic.h>
2762306a36Sopenharmony_ci#include <asm/appldata.h>
2862306a36Sopenharmony_ci#include <asm/monwriter.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define MONWRITE_MAX_DATALEN	4010
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic int mon_max_bufs = 255;
3362306a36Sopenharmony_cistatic int mon_buf_count;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistruct mon_buf {
3662306a36Sopenharmony_ci	struct list_head list;
3762306a36Sopenharmony_ci	struct monwrite_hdr hdr;
3862306a36Sopenharmony_ci	int diag_done;
3962306a36Sopenharmony_ci	char *data;
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistruct mon_private {
4362306a36Sopenharmony_ci	struct list_head list;
4462306a36Sopenharmony_ci	struct monwrite_hdr hdr;
4562306a36Sopenharmony_ci	size_t hdr_to_read;
4662306a36Sopenharmony_ci	size_t data_to_read;
4762306a36Sopenharmony_ci	struct mon_buf *current_buf;
4862306a36Sopenharmony_ci	struct mutex thread_mutex;
4962306a36Sopenharmony_ci};
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/*
5262306a36Sopenharmony_ci * helper functions
5362306a36Sopenharmony_ci */
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int monwrite_diag(struct monwrite_hdr *myhdr, char *buffer, int fcn)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct appldata_parameter_list *parm_list;
5862306a36Sopenharmony_ci	struct appldata_product_id *id;
5962306a36Sopenharmony_ci	int rc;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	id = kmalloc(sizeof(*id), GFP_KERNEL);
6262306a36Sopenharmony_ci	parm_list = kmalloc(sizeof(*parm_list), GFP_KERNEL);
6362306a36Sopenharmony_ci	rc = -ENOMEM;
6462306a36Sopenharmony_ci	if (!id || !parm_list)
6562306a36Sopenharmony_ci		goto out;
6662306a36Sopenharmony_ci	memcpy(id->prod_nr, "LNXAPPL", 7);
6762306a36Sopenharmony_ci	id->prod_fn = myhdr->applid;
6862306a36Sopenharmony_ci	id->record_nr = myhdr->record_num;
6962306a36Sopenharmony_ci	id->version_nr = myhdr->version;
7062306a36Sopenharmony_ci	id->release_nr = myhdr->release;
7162306a36Sopenharmony_ci	id->mod_lvl = myhdr->mod_level;
7262306a36Sopenharmony_ci	rc = appldata_asm(parm_list, id, fcn,
7362306a36Sopenharmony_ci			  (void *) buffer, myhdr->datalen);
7462306a36Sopenharmony_ci	if (rc <= 0)
7562306a36Sopenharmony_ci		goto out;
7662306a36Sopenharmony_ci	pr_err("Writing monitor data failed with rc=%i\n", rc);
7762306a36Sopenharmony_ci	rc = (rc == 5) ? -EPERM : -EINVAL;
7862306a36Sopenharmony_ciout:
7962306a36Sopenharmony_ci	kfree(id);
8062306a36Sopenharmony_ci	kfree(parm_list);
8162306a36Sopenharmony_ci	return rc;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic struct mon_buf *monwrite_find_hdr(struct mon_private *monpriv,
8562306a36Sopenharmony_ci					 struct monwrite_hdr *monhdr)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct mon_buf *entry, *next;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	list_for_each_entry_safe(entry, next, &monpriv->list, list)
9062306a36Sopenharmony_ci		if ((entry->hdr.mon_function == monhdr->mon_function ||
9162306a36Sopenharmony_ci		     monhdr->mon_function == MONWRITE_STOP_INTERVAL) &&
9262306a36Sopenharmony_ci		    entry->hdr.applid == monhdr->applid &&
9362306a36Sopenharmony_ci		    entry->hdr.record_num == monhdr->record_num &&
9462306a36Sopenharmony_ci		    entry->hdr.version == monhdr->version &&
9562306a36Sopenharmony_ci		    entry->hdr.release == monhdr->release &&
9662306a36Sopenharmony_ci		    entry->hdr.mod_level == monhdr->mod_level)
9762306a36Sopenharmony_ci			return entry;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	return NULL;
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic int monwrite_new_hdr(struct mon_private *monpriv)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct monwrite_hdr *monhdr = &monpriv->hdr;
10562306a36Sopenharmony_ci	struct mon_buf *monbuf;
10662306a36Sopenharmony_ci	int rc = 0;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	if (monhdr->datalen > MONWRITE_MAX_DATALEN ||
10962306a36Sopenharmony_ci	    monhdr->mon_function > MONWRITE_START_CONFIG ||
11062306a36Sopenharmony_ci	    monhdr->hdrlen != sizeof(struct monwrite_hdr))
11162306a36Sopenharmony_ci		return -EINVAL;
11262306a36Sopenharmony_ci	monbuf = NULL;
11362306a36Sopenharmony_ci	if (monhdr->mon_function != MONWRITE_GEN_EVENT)
11462306a36Sopenharmony_ci		monbuf = monwrite_find_hdr(monpriv, monhdr);
11562306a36Sopenharmony_ci	if (monbuf) {
11662306a36Sopenharmony_ci		if (monhdr->mon_function == MONWRITE_STOP_INTERVAL) {
11762306a36Sopenharmony_ci			monhdr->datalen = monbuf->hdr.datalen;
11862306a36Sopenharmony_ci			rc = monwrite_diag(monhdr, monbuf->data,
11962306a36Sopenharmony_ci					   APPLDATA_STOP_REC);
12062306a36Sopenharmony_ci			list_del(&monbuf->list);
12162306a36Sopenharmony_ci			mon_buf_count--;
12262306a36Sopenharmony_ci			kfree(monbuf->data);
12362306a36Sopenharmony_ci			kfree(monbuf);
12462306a36Sopenharmony_ci			monbuf = NULL;
12562306a36Sopenharmony_ci		}
12662306a36Sopenharmony_ci	} else if (monhdr->mon_function != MONWRITE_STOP_INTERVAL) {
12762306a36Sopenharmony_ci		if (mon_buf_count >= mon_max_bufs)
12862306a36Sopenharmony_ci			return -ENOSPC;
12962306a36Sopenharmony_ci		monbuf = kzalloc(sizeof(struct mon_buf), GFP_KERNEL);
13062306a36Sopenharmony_ci		if (!monbuf)
13162306a36Sopenharmony_ci			return -ENOMEM;
13262306a36Sopenharmony_ci		monbuf->data = kzalloc(monhdr->datalen,
13362306a36Sopenharmony_ci				       GFP_KERNEL | GFP_DMA);
13462306a36Sopenharmony_ci		if (!monbuf->data) {
13562306a36Sopenharmony_ci			kfree(monbuf);
13662306a36Sopenharmony_ci			return -ENOMEM;
13762306a36Sopenharmony_ci		}
13862306a36Sopenharmony_ci		monbuf->hdr = *monhdr;
13962306a36Sopenharmony_ci		list_add_tail(&monbuf->list, &monpriv->list);
14062306a36Sopenharmony_ci		if (monhdr->mon_function != MONWRITE_GEN_EVENT)
14162306a36Sopenharmony_ci			mon_buf_count++;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci	monpriv->current_buf = monbuf;
14462306a36Sopenharmony_ci	return rc;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic int monwrite_new_data(struct mon_private *monpriv)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	struct monwrite_hdr *monhdr = &monpriv->hdr;
15062306a36Sopenharmony_ci	struct mon_buf *monbuf = monpriv->current_buf;
15162306a36Sopenharmony_ci	int rc = 0;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	switch (monhdr->mon_function) {
15462306a36Sopenharmony_ci	case MONWRITE_START_INTERVAL:
15562306a36Sopenharmony_ci		if (!monbuf->diag_done) {
15662306a36Sopenharmony_ci			rc = monwrite_diag(monhdr, monbuf->data,
15762306a36Sopenharmony_ci					   APPLDATA_START_INTERVAL_REC);
15862306a36Sopenharmony_ci			monbuf->diag_done = 1;
15962306a36Sopenharmony_ci		}
16062306a36Sopenharmony_ci		break;
16162306a36Sopenharmony_ci	case MONWRITE_START_CONFIG:
16262306a36Sopenharmony_ci		if (!monbuf->diag_done) {
16362306a36Sopenharmony_ci			rc = monwrite_diag(monhdr, monbuf->data,
16462306a36Sopenharmony_ci					   APPLDATA_START_CONFIG_REC);
16562306a36Sopenharmony_ci			monbuf->diag_done = 1;
16662306a36Sopenharmony_ci		}
16762306a36Sopenharmony_ci		break;
16862306a36Sopenharmony_ci	case MONWRITE_GEN_EVENT:
16962306a36Sopenharmony_ci		rc = monwrite_diag(monhdr, monbuf->data,
17062306a36Sopenharmony_ci				   APPLDATA_GEN_EVENT_REC);
17162306a36Sopenharmony_ci		list_del(&monpriv->current_buf->list);
17262306a36Sopenharmony_ci		kfree(monpriv->current_buf->data);
17362306a36Sopenharmony_ci		kfree(monpriv->current_buf);
17462306a36Sopenharmony_ci		monpriv->current_buf = NULL;
17562306a36Sopenharmony_ci		break;
17662306a36Sopenharmony_ci	default:
17762306a36Sopenharmony_ci		/* monhdr->mon_function is checked in monwrite_new_hdr */
17862306a36Sopenharmony_ci		BUG();
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci	return rc;
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci/*
18462306a36Sopenharmony_ci * file operations
18562306a36Sopenharmony_ci */
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic int monwrite_open(struct inode *inode, struct file *filp)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	struct mon_private *monpriv;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	monpriv = kzalloc(sizeof(struct mon_private), GFP_KERNEL);
19262306a36Sopenharmony_ci	if (!monpriv)
19362306a36Sopenharmony_ci		return -ENOMEM;
19462306a36Sopenharmony_ci	INIT_LIST_HEAD(&monpriv->list);
19562306a36Sopenharmony_ci	monpriv->hdr_to_read = sizeof(monpriv->hdr);
19662306a36Sopenharmony_ci	mutex_init(&monpriv->thread_mutex);
19762306a36Sopenharmony_ci	filp->private_data = monpriv;
19862306a36Sopenharmony_ci	return nonseekable_open(inode, filp);
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic int monwrite_close(struct inode *inode, struct file *filp)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	struct mon_private *monpriv = filp->private_data;
20462306a36Sopenharmony_ci	struct mon_buf *entry, *next;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	list_for_each_entry_safe(entry, next, &monpriv->list, list) {
20762306a36Sopenharmony_ci		if (entry->hdr.mon_function != MONWRITE_GEN_EVENT)
20862306a36Sopenharmony_ci			monwrite_diag(&entry->hdr, entry->data,
20962306a36Sopenharmony_ci				      APPLDATA_STOP_REC);
21062306a36Sopenharmony_ci		mon_buf_count--;
21162306a36Sopenharmony_ci		list_del(&entry->list);
21262306a36Sopenharmony_ci		kfree(entry->data);
21362306a36Sopenharmony_ci		kfree(entry);
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci	kfree(monpriv);
21662306a36Sopenharmony_ci	return 0;
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic ssize_t monwrite_write(struct file *filp, const char __user *data,
22062306a36Sopenharmony_ci			      size_t count, loff_t *ppos)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	struct mon_private *monpriv = filp->private_data;
22362306a36Sopenharmony_ci	size_t len, written;
22462306a36Sopenharmony_ci	void *to;
22562306a36Sopenharmony_ci	int rc;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	mutex_lock(&monpriv->thread_mutex);
22862306a36Sopenharmony_ci	for (written = 0; written < count; ) {
22962306a36Sopenharmony_ci		if (monpriv->hdr_to_read) {
23062306a36Sopenharmony_ci			len = min(count - written, monpriv->hdr_to_read);
23162306a36Sopenharmony_ci			to = (char *) &monpriv->hdr +
23262306a36Sopenharmony_ci				sizeof(monpriv->hdr) - monpriv->hdr_to_read;
23362306a36Sopenharmony_ci			if (copy_from_user(to, data + written, len)) {
23462306a36Sopenharmony_ci				rc = -EFAULT;
23562306a36Sopenharmony_ci				goto out_error;
23662306a36Sopenharmony_ci			}
23762306a36Sopenharmony_ci			monpriv->hdr_to_read -= len;
23862306a36Sopenharmony_ci			written += len;
23962306a36Sopenharmony_ci			if (monpriv->hdr_to_read > 0)
24062306a36Sopenharmony_ci				continue;
24162306a36Sopenharmony_ci			rc = monwrite_new_hdr(monpriv);
24262306a36Sopenharmony_ci			if (rc)
24362306a36Sopenharmony_ci				goto out_error;
24462306a36Sopenharmony_ci			monpriv->data_to_read = monpriv->current_buf ?
24562306a36Sopenharmony_ci				monpriv->current_buf->hdr.datalen : 0;
24662306a36Sopenharmony_ci		}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci		if (monpriv->data_to_read) {
24962306a36Sopenharmony_ci			len = min(count - written, monpriv->data_to_read);
25062306a36Sopenharmony_ci			to = monpriv->current_buf->data +
25162306a36Sopenharmony_ci				monpriv->hdr.datalen - monpriv->data_to_read;
25262306a36Sopenharmony_ci			if (copy_from_user(to, data + written, len)) {
25362306a36Sopenharmony_ci				rc = -EFAULT;
25462306a36Sopenharmony_ci				goto out_error;
25562306a36Sopenharmony_ci			}
25662306a36Sopenharmony_ci			monpriv->data_to_read -= len;
25762306a36Sopenharmony_ci			written += len;
25862306a36Sopenharmony_ci			if (monpriv->data_to_read > 0)
25962306a36Sopenharmony_ci				continue;
26062306a36Sopenharmony_ci			rc = monwrite_new_data(monpriv);
26162306a36Sopenharmony_ci			if (rc)
26262306a36Sopenharmony_ci				goto out_error;
26362306a36Sopenharmony_ci		}
26462306a36Sopenharmony_ci		monpriv->hdr_to_read = sizeof(monpriv->hdr);
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci	mutex_unlock(&monpriv->thread_mutex);
26762306a36Sopenharmony_ci	return written;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ciout_error:
27062306a36Sopenharmony_ci	monpriv->data_to_read = 0;
27162306a36Sopenharmony_ci	monpriv->hdr_to_read = sizeof(struct monwrite_hdr);
27262306a36Sopenharmony_ci	mutex_unlock(&monpriv->thread_mutex);
27362306a36Sopenharmony_ci	return rc;
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic const struct file_operations monwrite_fops = {
27762306a36Sopenharmony_ci	.owner	 = THIS_MODULE,
27862306a36Sopenharmony_ci	.open	 = &monwrite_open,
27962306a36Sopenharmony_ci	.release = &monwrite_close,
28062306a36Sopenharmony_ci	.write	 = &monwrite_write,
28162306a36Sopenharmony_ci	.llseek  = noop_llseek,
28262306a36Sopenharmony_ci};
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_cistatic struct miscdevice mon_dev = {
28562306a36Sopenharmony_ci	.name	= "monwriter",
28662306a36Sopenharmony_ci	.fops	= &monwrite_fops,
28762306a36Sopenharmony_ci	.minor	= MISC_DYNAMIC_MINOR,
28862306a36Sopenharmony_ci};
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci/*
29162306a36Sopenharmony_ci * module init/exit
29262306a36Sopenharmony_ci */
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistatic int __init mon_init(void)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	if (!MACHINE_IS_VM)
29762306a36Sopenharmony_ci		return -ENODEV;
29862306a36Sopenharmony_ci	/*
29962306a36Sopenharmony_ci	 * misc_register() has to be the last action in module_init(), because
30062306a36Sopenharmony_ci	 * file operations will be available right after this.
30162306a36Sopenharmony_ci	 */
30262306a36Sopenharmony_ci	return misc_register(&mon_dev);
30362306a36Sopenharmony_ci}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_cistatic void __exit mon_exit(void)
30662306a36Sopenharmony_ci{
30762306a36Sopenharmony_ci	misc_deregister(&mon_dev);
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_cimodule_init(mon_init);
31162306a36Sopenharmony_cimodule_exit(mon_exit);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cimodule_param_named(max_bufs, mon_max_bufs, int, 0644);
31462306a36Sopenharmony_ciMODULE_PARM_DESC(max_bufs, "Maximum number of sample monitor data buffers "
31562306a36Sopenharmony_ci		 "that can be active at one time");
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ciMODULE_AUTHOR("Melissa Howland <Melissa.Howland@us.ibm.com>");
31862306a36Sopenharmony_ciMODULE_DESCRIPTION("Character device driver for writing z/VM "
31962306a36Sopenharmony_ci		   "APPLDATA monitor records.");
32062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
321