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