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