162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright IBM Corp. 2004, 2010
462306a36Sopenharmony_ci * Interface implementation for communication with the z/VM control program
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Author(s): Christian Borntraeger <borntraeger@de.ibm.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * z/VMs CP offers the possibility to issue commands via the diagnose code 8
962306a36Sopenharmony_ci * this driver implements a character device that issues these commands and
1062306a36Sopenharmony_ci * returns the answer of CP.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * The idea of this driver is based on cpint from Neale Ferguson and #CP in CMS
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/fs.h>
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/compat.h>
1862306a36Sopenharmony_ci#include <linux/kernel.h>
1962306a36Sopenharmony_ci#include <linux/miscdevice.h>
2062306a36Sopenharmony_ci#include <linux/slab.h>
2162306a36Sopenharmony_ci#include <linux/uaccess.h>
2262306a36Sopenharmony_ci#include <linux/export.h>
2362306a36Sopenharmony_ci#include <linux/mutex.h>
2462306a36Sopenharmony_ci#include <linux/cma.h>
2562306a36Sopenharmony_ci#include <linux/mm.h>
2662306a36Sopenharmony_ci#include <asm/cpcmd.h>
2762306a36Sopenharmony_ci#include <asm/debug.h>
2862306a36Sopenharmony_ci#include <asm/vmcp.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct vmcp_session {
3162306a36Sopenharmony_ci	char *response;
3262306a36Sopenharmony_ci	unsigned int bufsize;
3362306a36Sopenharmony_ci	unsigned int cma_alloc : 1;
3462306a36Sopenharmony_ci	int resp_size;
3562306a36Sopenharmony_ci	int resp_code;
3662306a36Sopenharmony_ci	struct mutex mutex;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic debug_info_t *vmcp_debug;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic unsigned long vmcp_cma_size __initdata = CONFIG_VMCP_CMA_SIZE * 1024 * 1024;
4262306a36Sopenharmony_cistatic struct cma *vmcp_cma;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic int __init early_parse_vmcp_cma(char *p)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	if (!p)
4762306a36Sopenharmony_ci		return 1;
4862306a36Sopenharmony_ci	vmcp_cma_size = ALIGN(memparse(p, NULL), PAGE_SIZE);
4962306a36Sopenharmony_ci	return 0;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ciearly_param("vmcp_cma", early_parse_vmcp_cma);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_civoid __init vmcp_cma_reserve(void)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	if (!MACHINE_IS_VM)
5662306a36Sopenharmony_ci		return;
5762306a36Sopenharmony_ci	cma_declare_contiguous(0, vmcp_cma_size, 0, 0, 0, false, "vmcp", &vmcp_cma);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void vmcp_response_alloc(struct vmcp_session *session)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct page *page = NULL;
6362306a36Sopenharmony_ci	int nr_pages, order;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	order = get_order(session->bufsize);
6662306a36Sopenharmony_ci	nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT;
6762306a36Sopenharmony_ci	/*
6862306a36Sopenharmony_ci	 * For anything below order 3 allocations rely on the buddy
6962306a36Sopenharmony_ci	 * allocator. If such low-order allocations can't be handled
7062306a36Sopenharmony_ci	 * anymore the system won't work anyway.
7162306a36Sopenharmony_ci	 */
7262306a36Sopenharmony_ci	if (order > 2)
7362306a36Sopenharmony_ci		page = cma_alloc(vmcp_cma, nr_pages, 0, false);
7462306a36Sopenharmony_ci	if (page) {
7562306a36Sopenharmony_ci		session->response = (char *)page_to_virt(page);
7662306a36Sopenharmony_ci		session->cma_alloc = 1;
7762306a36Sopenharmony_ci		return;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci	session->response = (char *)__get_free_pages(GFP_KERNEL | __GFP_RETRY_MAYFAIL, order);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic void vmcp_response_free(struct vmcp_session *session)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	int nr_pages, order;
8562306a36Sopenharmony_ci	struct page *page;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	if (!session->response)
8862306a36Sopenharmony_ci		return;
8962306a36Sopenharmony_ci	order = get_order(session->bufsize);
9062306a36Sopenharmony_ci	nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT;
9162306a36Sopenharmony_ci	if (session->cma_alloc) {
9262306a36Sopenharmony_ci		page = virt_to_page(session->response);
9362306a36Sopenharmony_ci		cma_release(vmcp_cma, page, nr_pages);
9462306a36Sopenharmony_ci		session->cma_alloc = 0;
9562306a36Sopenharmony_ci	} else {
9662306a36Sopenharmony_ci		free_pages((unsigned long)session->response, order);
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci	session->response = NULL;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic int vmcp_open(struct inode *inode, struct file *file)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	struct vmcp_session *session;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
10662306a36Sopenharmony_ci		return -EPERM;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	session = kmalloc(sizeof(*session), GFP_KERNEL);
10962306a36Sopenharmony_ci	if (!session)
11062306a36Sopenharmony_ci		return -ENOMEM;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	session->bufsize = PAGE_SIZE;
11362306a36Sopenharmony_ci	session->response = NULL;
11462306a36Sopenharmony_ci	session->resp_size = 0;
11562306a36Sopenharmony_ci	mutex_init(&session->mutex);
11662306a36Sopenharmony_ci	file->private_data = session;
11762306a36Sopenharmony_ci	return nonseekable_open(inode, file);
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic int vmcp_release(struct inode *inode, struct file *file)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct vmcp_session *session;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	session = file->private_data;
12562306a36Sopenharmony_ci	file->private_data = NULL;
12662306a36Sopenharmony_ci	vmcp_response_free(session);
12762306a36Sopenharmony_ci	kfree(session);
12862306a36Sopenharmony_ci	return 0;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic ssize_t
13262306a36Sopenharmony_civmcp_read(struct file *file, char __user *buff, size_t count, loff_t *ppos)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	ssize_t ret;
13562306a36Sopenharmony_ci	size_t size;
13662306a36Sopenharmony_ci	struct vmcp_session *session;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	session = file->private_data;
13962306a36Sopenharmony_ci	if (mutex_lock_interruptible(&session->mutex))
14062306a36Sopenharmony_ci		return -ERESTARTSYS;
14162306a36Sopenharmony_ci	if (!session->response) {
14262306a36Sopenharmony_ci		mutex_unlock(&session->mutex);
14362306a36Sopenharmony_ci		return 0;
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci	size = min_t(size_t, session->resp_size, session->bufsize);
14662306a36Sopenharmony_ci	ret = simple_read_from_buffer(buff, count, ppos,
14762306a36Sopenharmony_ci					session->response, size);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	mutex_unlock(&session->mutex);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return ret;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic ssize_t
15562306a36Sopenharmony_civmcp_write(struct file *file, const char __user *buff, size_t count,
15662306a36Sopenharmony_ci	   loff_t *ppos)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	char *cmd;
15962306a36Sopenharmony_ci	struct vmcp_session *session;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (count > 240)
16262306a36Sopenharmony_ci		return -EINVAL;
16362306a36Sopenharmony_ci	cmd = memdup_user_nul(buff, count);
16462306a36Sopenharmony_ci	if (IS_ERR(cmd))
16562306a36Sopenharmony_ci		return PTR_ERR(cmd);
16662306a36Sopenharmony_ci	session = file->private_data;
16762306a36Sopenharmony_ci	if (mutex_lock_interruptible(&session->mutex)) {
16862306a36Sopenharmony_ci		kfree(cmd);
16962306a36Sopenharmony_ci		return -ERESTARTSYS;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci	if (!session->response)
17262306a36Sopenharmony_ci		vmcp_response_alloc(session);
17362306a36Sopenharmony_ci	if (!session->response) {
17462306a36Sopenharmony_ci		mutex_unlock(&session->mutex);
17562306a36Sopenharmony_ci		kfree(cmd);
17662306a36Sopenharmony_ci		return -ENOMEM;
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci	debug_text_event(vmcp_debug, 1, cmd);
17962306a36Sopenharmony_ci	session->resp_size = cpcmd(cmd, session->response, session->bufsize,
18062306a36Sopenharmony_ci				   &session->resp_code);
18162306a36Sopenharmony_ci	mutex_unlock(&session->mutex);
18262306a36Sopenharmony_ci	kfree(cmd);
18362306a36Sopenharmony_ci	*ppos = 0;		/* reset the file pointer after a command */
18462306a36Sopenharmony_ci	return count;
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci/*
18962306a36Sopenharmony_ci * These ioctls are available, as the semantics of the diagnose 8 call
19062306a36Sopenharmony_ci * does not fit very well into a Linux call. Diagnose X'08' is described in
19162306a36Sopenharmony_ci * CP Programming Services SC24-6084-00
19262306a36Sopenharmony_ci *
19362306a36Sopenharmony_ci * VMCP_GETCODE: gives the CP return code back to user space
19462306a36Sopenharmony_ci * VMCP_SETBUF: sets the response buffer for the next write call. diagnose 8
19562306a36Sopenharmony_ci * expects adjacent pages in real storage and to make matters worse, we
19662306a36Sopenharmony_ci * dont know the size of the response. Therefore we default to PAGESIZE and
19762306a36Sopenharmony_ci * let userspace to change the response size, if userspace expects a bigger
19862306a36Sopenharmony_ci * response
19962306a36Sopenharmony_ci */
20062306a36Sopenharmony_cistatic long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	struct vmcp_session *session;
20362306a36Sopenharmony_ci	int ret = -ENOTTY;
20462306a36Sopenharmony_ci	int __user *argp;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	session = file->private_data;
20762306a36Sopenharmony_ci	if (is_compat_task())
20862306a36Sopenharmony_ci		argp = compat_ptr(arg);
20962306a36Sopenharmony_ci	else
21062306a36Sopenharmony_ci		argp = (int __user *)arg;
21162306a36Sopenharmony_ci	if (mutex_lock_interruptible(&session->mutex))
21262306a36Sopenharmony_ci		return -ERESTARTSYS;
21362306a36Sopenharmony_ci	switch (cmd) {
21462306a36Sopenharmony_ci	case VMCP_GETCODE:
21562306a36Sopenharmony_ci		ret = put_user(session->resp_code, argp);
21662306a36Sopenharmony_ci		break;
21762306a36Sopenharmony_ci	case VMCP_SETBUF:
21862306a36Sopenharmony_ci		vmcp_response_free(session);
21962306a36Sopenharmony_ci		ret = get_user(session->bufsize, argp);
22062306a36Sopenharmony_ci		if (ret)
22162306a36Sopenharmony_ci			session->bufsize = PAGE_SIZE;
22262306a36Sopenharmony_ci		if (!session->bufsize || get_order(session->bufsize) > 8) {
22362306a36Sopenharmony_ci			session->bufsize = PAGE_SIZE;
22462306a36Sopenharmony_ci			ret = -EINVAL;
22562306a36Sopenharmony_ci		}
22662306a36Sopenharmony_ci		break;
22762306a36Sopenharmony_ci	case VMCP_GETSIZE:
22862306a36Sopenharmony_ci		ret = put_user(session->resp_size, argp);
22962306a36Sopenharmony_ci		break;
23062306a36Sopenharmony_ci	default:
23162306a36Sopenharmony_ci		break;
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci	mutex_unlock(&session->mutex);
23462306a36Sopenharmony_ci	return ret;
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic const struct file_operations vmcp_fops = {
23862306a36Sopenharmony_ci	.owner		= THIS_MODULE,
23962306a36Sopenharmony_ci	.open		= vmcp_open,
24062306a36Sopenharmony_ci	.release	= vmcp_release,
24162306a36Sopenharmony_ci	.read		= vmcp_read,
24262306a36Sopenharmony_ci	.write		= vmcp_write,
24362306a36Sopenharmony_ci	.unlocked_ioctl	= vmcp_ioctl,
24462306a36Sopenharmony_ci	.compat_ioctl	= vmcp_ioctl,
24562306a36Sopenharmony_ci	.llseek		= no_llseek,
24662306a36Sopenharmony_ci};
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_cistatic struct miscdevice vmcp_dev = {
24962306a36Sopenharmony_ci	.name	= "vmcp",
25062306a36Sopenharmony_ci	.minor	= MISC_DYNAMIC_MINOR,
25162306a36Sopenharmony_ci	.fops	= &vmcp_fops,
25262306a36Sopenharmony_ci};
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cistatic int __init vmcp_init(void)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	int ret;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	if (!MACHINE_IS_VM)
25962306a36Sopenharmony_ci		return 0;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	vmcp_debug = debug_register("vmcp", 1, 1, 240);
26262306a36Sopenharmony_ci	if (!vmcp_debug)
26362306a36Sopenharmony_ci		return -ENOMEM;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	ret = debug_register_view(vmcp_debug, &debug_hex_ascii_view);
26662306a36Sopenharmony_ci	if (ret) {
26762306a36Sopenharmony_ci		debug_unregister(vmcp_debug);
26862306a36Sopenharmony_ci		return ret;
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	ret = misc_register(&vmcp_dev);
27262306a36Sopenharmony_ci	if (ret)
27362306a36Sopenharmony_ci		debug_unregister(vmcp_debug);
27462306a36Sopenharmony_ci	return ret;
27562306a36Sopenharmony_ci}
27662306a36Sopenharmony_cidevice_initcall(vmcp_init);
277