xref: /OK3568_Linux_fs/kernel/drivers/s390/char/vmcp.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * Copyright IBM Corp. 2004, 2010
4*4882a593Smuzhiyun  * Interface implementation for communication with the z/VM control program
5*4882a593Smuzhiyun  *
6*4882a593Smuzhiyun  * Author(s): Christian Borntraeger <borntraeger@de.ibm.com>
7*4882a593Smuzhiyun  *
8*4882a593Smuzhiyun  * z/VMs CP offers the possibility to issue commands via the diagnose code 8
9*4882a593Smuzhiyun  * this driver implements a character device that issues these commands and
10*4882a593Smuzhiyun  * returns the answer of CP.
11*4882a593Smuzhiyun  *
12*4882a593Smuzhiyun  * The idea of this driver is based on cpint from Neale Ferguson and #CP in CMS
13*4882a593Smuzhiyun  */
14*4882a593Smuzhiyun 
15*4882a593Smuzhiyun #include <linux/fs.h>
16*4882a593Smuzhiyun #include <linux/init.h>
17*4882a593Smuzhiyun #include <linux/compat.h>
18*4882a593Smuzhiyun #include <linux/kernel.h>
19*4882a593Smuzhiyun #include <linux/miscdevice.h>
20*4882a593Smuzhiyun #include <linux/slab.h>
21*4882a593Smuzhiyun #include <linux/uaccess.h>
22*4882a593Smuzhiyun #include <linux/export.h>
23*4882a593Smuzhiyun #include <linux/mutex.h>
24*4882a593Smuzhiyun #include <linux/cma.h>
25*4882a593Smuzhiyun #include <linux/mm.h>
26*4882a593Smuzhiyun #include <asm/cpcmd.h>
27*4882a593Smuzhiyun #include <asm/debug.h>
28*4882a593Smuzhiyun #include <asm/vmcp.h>
29*4882a593Smuzhiyun 
30*4882a593Smuzhiyun struct vmcp_session {
31*4882a593Smuzhiyun 	char *response;
32*4882a593Smuzhiyun 	unsigned int bufsize;
33*4882a593Smuzhiyun 	unsigned int cma_alloc : 1;
34*4882a593Smuzhiyun 	int resp_size;
35*4882a593Smuzhiyun 	int resp_code;
36*4882a593Smuzhiyun 	struct mutex mutex;
37*4882a593Smuzhiyun };
38*4882a593Smuzhiyun 
39*4882a593Smuzhiyun static debug_info_t *vmcp_debug;
40*4882a593Smuzhiyun 
41*4882a593Smuzhiyun static unsigned long vmcp_cma_size __initdata = CONFIG_VMCP_CMA_SIZE * 1024 * 1024;
42*4882a593Smuzhiyun static struct cma *vmcp_cma;
43*4882a593Smuzhiyun 
early_parse_vmcp_cma(char * p)44*4882a593Smuzhiyun static int __init early_parse_vmcp_cma(char *p)
45*4882a593Smuzhiyun {
46*4882a593Smuzhiyun 	if (!p)
47*4882a593Smuzhiyun 		return 1;
48*4882a593Smuzhiyun 	vmcp_cma_size = ALIGN(memparse(p, NULL), PAGE_SIZE);
49*4882a593Smuzhiyun 	return 0;
50*4882a593Smuzhiyun }
51*4882a593Smuzhiyun early_param("vmcp_cma", early_parse_vmcp_cma);
52*4882a593Smuzhiyun 
vmcp_cma_reserve(void)53*4882a593Smuzhiyun void __init vmcp_cma_reserve(void)
54*4882a593Smuzhiyun {
55*4882a593Smuzhiyun 	if (!MACHINE_IS_VM)
56*4882a593Smuzhiyun 		return;
57*4882a593Smuzhiyun 	cma_declare_contiguous(0, vmcp_cma_size, 0, 0, 0, false, "vmcp", &vmcp_cma);
58*4882a593Smuzhiyun }
59*4882a593Smuzhiyun 
vmcp_response_alloc(struct vmcp_session * session)60*4882a593Smuzhiyun static void vmcp_response_alloc(struct vmcp_session *session)
61*4882a593Smuzhiyun {
62*4882a593Smuzhiyun 	struct page *page = NULL;
63*4882a593Smuzhiyun 	int nr_pages, order;
64*4882a593Smuzhiyun 
65*4882a593Smuzhiyun 	order = get_order(session->bufsize);
66*4882a593Smuzhiyun 	nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT;
67*4882a593Smuzhiyun 	/*
68*4882a593Smuzhiyun 	 * For anything below order 3 allocations rely on the buddy
69*4882a593Smuzhiyun 	 * allocator. If such low-order allocations can't be handled
70*4882a593Smuzhiyun 	 * anymore the system won't work anyway.
71*4882a593Smuzhiyun 	 */
72*4882a593Smuzhiyun 	if (order > 2)
73*4882a593Smuzhiyun 		page = cma_alloc(vmcp_cma, nr_pages, 0, GFP_KERNEL);
74*4882a593Smuzhiyun 	if (page) {
75*4882a593Smuzhiyun 		session->response = (char *)page_to_phys(page);
76*4882a593Smuzhiyun 		session->cma_alloc = 1;
77*4882a593Smuzhiyun 		return;
78*4882a593Smuzhiyun 	}
79*4882a593Smuzhiyun 	session->response = (char *)__get_free_pages(GFP_KERNEL | __GFP_RETRY_MAYFAIL, order);
80*4882a593Smuzhiyun }
81*4882a593Smuzhiyun 
vmcp_response_free(struct vmcp_session * session)82*4882a593Smuzhiyun static void vmcp_response_free(struct vmcp_session *session)
83*4882a593Smuzhiyun {
84*4882a593Smuzhiyun 	int nr_pages, order;
85*4882a593Smuzhiyun 	struct page *page;
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 	if (!session->response)
88*4882a593Smuzhiyun 		return;
89*4882a593Smuzhiyun 	order = get_order(session->bufsize);
90*4882a593Smuzhiyun 	nr_pages = ALIGN(session->bufsize, PAGE_SIZE) >> PAGE_SHIFT;
91*4882a593Smuzhiyun 	if (session->cma_alloc) {
92*4882a593Smuzhiyun 		page = phys_to_page((unsigned long)session->response);
93*4882a593Smuzhiyun 		cma_release(vmcp_cma, page, nr_pages);
94*4882a593Smuzhiyun 		session->cma_alloc = 0;
95*4882a593Smuzhiyun 	} else {
96*4882a593Smuzhiyun 		free_pages((unsigned long)session->response, order);
97*4882a593Smuzhiyun 	}
98*4882a593Smuzhiyun 	session->response = NULL;
99*4882a593Smuzhiyun }
100*4882a593Smuzhiyun 
vmcp_open(struct inode * inode,struct file * file)101*4882a593Smuzhiyun static int vmcp_open(struct inode *inode, struct file *file)
102*4882a593Smuzhiyun {
103*4882a593Smuzhiyun 	struct vmcp_session *session;
104*4882a593Smuzhiyun 
105*4882a593Smuzhiyun 	if (!capable(CAP_SYS_ADMIN))
106*4882a593Smuzhiyun 		return -EPERM;
107*4882a593Smuzhiyun 
108*4882a593Smuzhiyun 	session = kmalloc(sizeof(*session), GFP_KERNEL);
109*4882a593Smuzhiyun 	if (!session)
110*4882a593Smuzhiyun 		return -ENOMEM;
111*4882a593Smuzhiyun 
112*4882a593Smuzhiyun 	session->bufsize = PAGE_SIZE;
113*4882a593Smuzhiyun 	session->response = NULL;
114*4882a593Smuzhiyun 	session->resp_size = 0;
115*4882a593Smuzhiyun 	mutex_init(&session->mutex);
116*4882a593Smuzhiyun 	file->private_data = session;
117*4882a593Smuzhiyun 	return nonseekable_open(inode, file);
118*4882a593Smuzhiyun }
119*4882a593Smuzhiyun 
vmcp_release(struct inode * inode,struct file * file)120*4882a593Smuzhiyun static int vmcp_release(struct inode *inode, struct file *file)
121*4882a593Smuzhiyun {
122*4882a593Smuzhiyun 	struct vmcp_session *session;
123*4882a593Smuzhiyun 
124*4882a593Smuzhiyun 	session = file->private_data;
125*4882a593Smuzhiyun 	file->private_data = NULL;
126*4882a593Smuzhiyun 	vmcp_response_free(session);
127*4882a593Smuzhiyun 	kfree(session);
128*4882a593Smuzhiyun 	return 0;
129*4882a593Smuzhiyun }
130*4882a593Smuzhiyun 
131*4882a593Smuzhiyun static ssize_t
vmcp_read(struct file * file,char __user * buff,size_t count,loff_t * ppos)132*4882a593Smuzhiyun vmcp_read(struct file *file, char __user *buff, size_t count, loff_t *ppos)
133*4882a593Smuzhiyun {
134*4882a593Smuzhiyun 	ssize_t ret;
135*4882a593Smuzhiyun 	size_t size;
136*4882a593Smuzhiyun 	struct vmcp_session *session;
137*4882a593Smuzhiyun 
138*4882a593Smuzhiyun 	session = file->private_data;
139*4882a593Smuzhiyun 	if (mutex_lock_interruptible(&session->mutex))
140*4882a593Smuzhiyun 		return -ERESTARTSYS;
141*4882a593Smuzhiyun 	if (!session->response) {
142*4882a593Smuzhiyun 		mutex_unlock(&session->mutex);
143*4882a593Smuzhiyun 		return 0;
144*4882a593Smuzhiyun 	}
145*4882a593Smuzhiyun 	size = min_t(size_t, session->resp_size, session->bufsize);
146*4882a593Smuzhiyun 	ret = simple_read_from_buffer(buff, count, ppos,
147*4882a593Smuzhiyun 					session->response, size);
148*4882a593Smuzhiyun 
149*4882a593Smuzhiyun 	mutex_unlock(&session->mutex);
150*4882a593Smuzhiyun 
151*4882a593Smuzhiyun 	return ret;
152*4882a593Smuzhiyun }
153*4882a593Smuzhiyun 
154*4882a593Smuzhiyun static ssize_t
vmcp_write(struct file * file,const char __user * buff,size_t count,loff_t * ppos)155*4882a593Smuzhiyun vmcp_write(struct file *file, const char __user *buff, size_t count,
156*4882a593Smuzhiyun 	   loff_t *ppos)
157*4882a593Smuzhiyun {
158*4882a593Smuzhiyun 	char *cmd;
159*4882a593Smuzhiyun 	struct vmcp_session *session;
160*4882a593Smuzhiyun 
161*4882a593Smuzhiyun 	if (count > 240)
162*4882a593Smuzhiyun 		return -EINVAL;
163*4882a593Smuzhiyun 	cmd = memdup_user_nul(buff, count);
164*4882a593Smuzhiyun 	if (IS_ERR(cmd))
165*4882a593Smuzhiyun 		return PTR_ERR(cmd);
166*4882a593Smuzhiyun 	session = file->private_data;
167*4882a593Smuzhiyun 	if (mutex_lock_interruptible(&session->mutex)) {
168*4882a593Smuzhiyun 		kfree(cmd);
169*4882a593Smuzhiyun 		return -ERESTARTSYS;
170*4882a593Smuzhiyun 	}
171*4882a593Smuzhiyun 	if (!session->response)
172*4882a593Smuzhiyun 		vmcp_response_alloc(session);
173*4882a593Smuzhiyun 	if (!session->response) {
174*4882a593Smuzhiyun 		mutex_unlock(&session->mutex);
175*4882a593Smuzhiyun 		kfree(cmd);
176*4882a593Smuzhiyun 		return -ENOMEM;
177*4882a593Smuzhiyun 	}
178*4882a593Smuzhiyun 	debug_text_event(vmcp_debug, 1, cmd);
179*4882a593Smuzhiyun 	session->resp_size = cpcmd(cmd, session->response, session->bufsize,
180*4882a593Smuzhiyun 				   &session->resp_code);
181*4882a593Smuzhiyun 	mutex_unlock(&session->mutex);
182*4882a593Smuzhiyun 	kfree(cmd);
183*4882a593Smuzhiyun 	*ppos = 0;		/* reset the file pointer after a command */
184*4882a593Smuzhiyun 	return count;
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun 
187*4882a593Smuzhiyun 
188*4882a593Smuzhiyun /*
189*4882a593Smuzhiyun  * These ioctls are available, as the semantics of the diagnose 8 call
190*4882a593Smuzhiyun  * does not fit very well into a Linux call. Diagnose X'08' is described in
191*4882a593Smuzhiyun  * CP Programming Services SC24-6084-00
192*4882a593Smuzhiyun  *
193*4882a593Smuzhiyun  * VMCP_GETCODE: gives the CP return code back to user space
194*4882a593Smuzhiyun  * VMCP_SETBUF: sets the response buffer for the next write call. diagnose 8
195*4882a593Smuzhiyun  * expects adjacent pages in real storage and to make matters worse, we
196*4882a593Smuzhiyun  * dont know the size of the response. Therefore we default to PAGESIZE and
197*4882a593Smuzhiyun  * let userspace to change the response size, if userspace expects a bigger
198*4882a593Smuzhiyun  * response
199*4882a593Smuzhiyun  */
vmcp_ioctl(struct file * file,unsigned int cmd,unsigned long arg)200*4882a593Smuzhiyun static long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
201*4882a593Smuzhiyun {
202*4882a593Smuzhiyun 	struct vmcp_session *session;
203*4882a593Smuzhiyun 	int ret = -ENOTTY;
204*4882a593Smuzhiyun 	int __user *argp;
205*4882a593Smuzhiyun 
206*4882a593Smuzhiyun 	session = file->private_data;
207*4882a593Smuzhiyun 	if (is_compat_task())
208*4882a593Smuzhiyun 		argp = compat_ptr(arg);
209*4882a593Smuzhiyun 	else
210*4882a593Smuzhiyun 		argp = (int __user *)arg;
211*4882a593Smuzhiyun 	if (mutex_lock_interruptible(&session->mutex))
212*4882a593Smuzhiyun 		return -ERESTARTSYS;
213*4882a593Smuzhiyun 	switch (cmd) {
214*4882a593Smuzhiyun 	case VMCP_GETCODE:
215*4882a593Smuzhiyun 		ret = put_user(session->resp_code, argp);
216*4882a593Smuzhiyun 		break;
217*4882a593Smuzhiyun 	case VMCP_SETBUF:
218*4882a593Smuzhiyun 		vmcp_response_free(session);
219*4882a593Smuzhiyun 		ret = get_user(session->bufsize, argp);
220*4882a593Smuzhiyun 		if (ret)
221*4882a593Smuzhiyun 			session->bufsize = PAGE_SIZE;
222*4882a593Smuzhiyun 		if (!session->bufsize || get_order(session->bufsize) > 8) {
223*4882a593Smuzhiyun 			session->bufsize = PAGE_SIZE;
224*4882a593Smuzhiyun 			ret = -EINVAL;
225*4882a593Smuzhiyun 		}
226*4882a593Smuzhiyun 		break;
227*4882a593Smuzhiyun 	case VMCP_GETSIZE:
228*4882a593Smuzhiyun 		ret = put_user(session->resp_size, argp);
229*4882a593Smuzhiyun 		break;
230*4882a593Smuzhiyun 	default:
231*4882a593Smuzhiyun 		break;
232*4882a593Smuzhiyun 	}
233*4882a593Smuzhiyun 	mutex_unlock(&session->mutex);
234*4882a593Smuzhiyun 	return ret;
235*4882a593Smuzhiyun }
236*4882a593Smuzhiyun 
237*4882a593Smuzhiyun static const struct file_operations vmcp_fops = {
238*4882a593Smuzhiyun 	.owner		= THIS_MODULE,
239*4882a593Smuzhiyun 	.open		= vmcp_open,
240*4882a593Smuzhiyun 	.release	= vmcp_release,
241*4882a593Smuzhiyun 	.read		= vmcp_read,
242*4882a593Smuzhiyun 	.write		= vmcp_write,
243*4882a593Smuzhiyun 	.unlocked_ioctl	= vmcp_ioctl,
244*4882a593Smuzhiyun 	.compat_ioctl	= vmcp_ioctl,
245*4882a593Smuzhiyun 	.llseek		= no_llseek,
246*4882a593Smuzhiyun };
247*4882a593Smuzhiyun 
248*4882a593Smuzhiyun static struct miscdevice vmcp_dev = {
249*4882a593Smuzhiyun 	.name	= "vmcp",
250*4882a593Smuzhiyun 	.minor	= MISC_DYNAMIC_MINOR,
251*4882a593Smuzhiyun 	.fops	= &vmcp_fops,
252*4882a593Smuzhiyun };
253*4882a593Smuzhiyun 
vmcp_init(void)254*4882a593Smuzhiyun static int __init vmcp_init(void)
255*4882a593Smuzhiyun {
256*4882a593Smuzhiyun 	int ret;
257*4882a593Smuzhiyun 
258*4882a593Smuzhiyun 	if (!MACHINE_IS_VM)
259*4882a593Smuzhiyun 		return 0;
260*4882a593Smuzhiyun 
261*4882a593Smuzhiyun 	vmcp_debug = debug_register("vmcp", 1, 1, 240);
262*4882a593Smuzhiyun 	if (!vmcp_debug)
263*4882a593Smuzhiyun 		return -ENOMEM;
264*4882a593Smuzhiyun 
265*4882a593Smuzhiyun 	ret = debug_register_view(vmcp_debug, &debug_hex_ascii_view);
266*4882a593Smuzhiyun 	if (ret) {
267*4882a593Smuzhiyun 		debug_unregister(vmcp_debug);
268*4882a593Smuzhiyun 		return ret;
269*4882a593Smuzhiyun 	}
270*4882a593Smuzhiyun 
271*4882a593Smuzhiyun 	ret = misc_register(&vmcp_dev);
272*4882a593Smuzhiyun 	if (ret)
273*4882a593Smuzhiyun 		debug_unregister(vmcp_debug);
274*4882a593Smuzhiyun 	return ret;
275*4882a593Smuzhiyun }
276*4882a593Smuzhiyun device_initcall(vmcp_init);
277