]>
Commit | Line | Data |
---|---|---|
6b979de3 | 1 | /* |
a53c8fab | 2 | * Copyright IBM Corp. 2004, 2010 |
45af3af8 | 3 | * Interface implementation for communication with the z/VM control program |
6b979de3 | 4 | * |
f73a2b03 | 5 | * Author(s): Christian Borntraeger <borntraeger@de.ibm.com> |
6b979de3 CB |
6 | * |
7 | * z/VMs CP offers the possibility to issue commands via the diagnose code 8 | |
8 | * this driver implements a character device that issues these commands and | |
9 | * returns the answer of CP. | |
f73a2b03 | 10 | * |
6b979de3 CB |
11 | * The idea of this driver is based on cpint from Neale Ferguson and #CP in CMS |
12 | */ | |
13 | ||
14 | #include <linux/fs.h> | |
15 | #include <linux/init.h> | |
048cd4e5 | 16 | #include <linux/compat.h> |
6b979de3 CB |
17 | #include <linux/kernel.h> |
18 | #include <linux/miscdevice.h> | |
5a0e3ad6 | 19 | #include <linux/slab.h> |
3a4c5d59 | 20 | #include <linux/export.h> |
8f3eabe3 | 21 | #include <asm/compat.h> |
6b979de3 CB |
22 | #include <asm/cpcmd.h> |
23 | #include <asm/debug.h> | |
7c0f6ba6 | 24 | #include <linux/uaccess.h> |
6b979de3 CB |
25 | #include "vmcp.h" |
26 | ||
6b979de3 CB |
27 | static debug_info_t *vmcp_debug; |
28 | ||
29 | static int vmcp_open(struct inode *inode, struct file *file) | |
30 | { | |
31 | struct vmcp_session *session; | |
32 | ||
33 | if (!capable(CAP_SYS_ADMIN)) | |
34 | return -EPERM; | |
35 | ||
36 | session = kmalloc(sizeof(*session), GFP_KERNEL); | |
37 | if (!session) | |
38 | return -ENOMEM; | |
6c111d88 | 39 | |
6b979de3 CB |
40 | session->bufsize = PAGE_SIZE; |
41 | session->response = NULL; | |
42 | session->resp_size = 0; | |
d9d119f1 | 43 | mutex_init(&session->mutex); |
6b979de3 CB |
44 | file->private_data = session; |
45 | return nonseekable_open(inode, file); | |
46 | } | |
47 | ||
48 | static int vmcp_release(struct inode *inode, struct file *file) | |
49 | { | |
50 | struct vmcp_session *session; | |
51 | ||
27e49945 | 52 | session = file->private_data; |
6b979de3 CB |
53 | file->private_data = NULL; |
54 | free_pages((unsigned long)session->response, get_order(session->bufsize)); | |
55 | kfree(session); | |
56 | return 0; | |
57 | } | |
58 | ||
59 | static ssize_t | |
d9d119f1 | 60 | vmcp_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) |
6b979de3 | 61 | { |
7785857a AM |
62 | ssize_t ret; |
63 | size_t size; | |
6b979de3 CB |
64 | struct vmcp_session *session; |
65 | ||
7785857a | 66 | session = file->private_data; |
d9d119f1 | 67 | if (mutex_lock_interruptible(&session->mutex)) |
6b979de3 CB |
68 | return -ERESTARTSYS; |
69 | if (!session->response) { | |
d9d119f1 | 70 | mutex_unlock(&session->mutex); |
6b979de3 CB |
71 | return 0; |
72 | } | |
7785857a AM |
73 | size = min_t(size_t, session->resp_size, session->bufsize); |
74 | ret = simple_read_from_buffer(buff, count, ppos, | |
75 | session->response, size); | |
6b979de3 | 76 | |
d9d119f1 | 77 | mutex_unlock(&session->mutex); |
7785857a AM |
78 | |
79 | return ret; | |
6b979de3 CB |
80 | } |
81 | ||
82 | static ssize_t | |
d9d119f1 CB |
83 | vmcp_write(struct file *file, const char __user *buff, size_t count, |
84 | loff_t *ppos) | |
6b979de3 CB |
85 | { |
86 | char *cmd; | |
87 | struct vmcp_session *session; | |
88 | ||
89 | if (count > 240) | |
90 | return -EINVAL; | |
16e5c1fc AV |
91 | cmd = memdup_user_nul(buff, count); |
92 | if (IS_ERR(cmd)) | |
93 | return PTR_ERR(cmd); | |
27e49945 | 94 | session = file->private_data; |
d9d119f1 | 95 | if (mutex_lock_interruptible(&session->mutex)) { |
a5da866f | 96 | kfree(cmd); |
6b979de3 | 97 | return -ERESTARTSYS; |
a5da866f | 98 | } |
6b979de3 CB |
99 | if (!session->response) |
100 | session->response = (char *)__get_free_pages(GFP_KERNEL | |
d9d119f1 | 101 | | __GFP_REPEAT | GFP_DMA, |
6b979de3 CB |
102 | get_order(session->bufsize)); |
103 | if (!session->response) { | |
d9d119f1 | 104 | mutex_unlock(&session->mutex); |
6b979de3 CB |
105 | kfree(cmd); |
106 | return -ENOMEM; | |
107 | } | |
108 | debug_text_event(vmcp_debug, 1, cmd); | |
d9d119f1 CB |
109 | session->resp_size = cpcmd(cmd, session->response, session->bufsize, |
110 | &session->resp_code); | |
111 | mutex_unlock(&session->mutex); | |
6b979de3 CB |
112 | kfree(cmd); |
113 | *ppos = 0; /* reset the file pointer after a command */ | |
114 | return count; | |
115 | } | |
116 | ||
117 | ||
118 | /* | |
119 | * These ioctls are available, as the semantics of the diagnose 8 call | |
120 | * does not fit very well into a Linux call. Diagnose X'08' is described in | |
121 | * CP Programming Services SC24-6084-00 | |
122 | * | |
123 | * VMCP_GETCODE: gives the CP return code back to user space | |
124 | * VMCP_SETBUF: sets the response buffer for the next write call. diagnose 8 | |
125 | * expects adjacent pages in real storage and to make matters worse, we | |
126 | * dont know the size of the response. Therefore we default to PAGESIZE and | |
127 | * let userspace to change the response size, if userspace expects a bigger | |
128 | * response | |
129 | */ | |
130 | static long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |
131 | { | |
132 | struct vmcp_session *session; | |
8f3eabe3 | 133 | int __user *argp; |
6b979de3 CB |
134 | int temp; |
135 | ||
27e49945 | 136 | session = file->private_data; |
8f3eabe3 HC |
137 | if (is_compat_task()) |
138 | argp = compat_ptr(arg); | |
139 | else | |
140 | argp = (int __user *)arg; | |
d9d119f1 | 141 | if (mutex_lock_interruptible(&session->mutex)) |
6b979de3 CB |
142 | return -ERESTARTSYS; |
143 | switch (cmd) { | |
144 | case VMCP_GETCODE: | |
145 | temp = session->resp_code; | |
d9d119f1 | 146 | mutex_unlock(&session->mutex); |
8f3eabe3 | 147 | return put_user(temp, argp); |
6b979de3 CB |
148 | case VMCP_SETBUF: |
149 | free_pages((unsigned long)session->response, | |
150 | get_order(session->bufsize)); | |
151 | session->response=NULL; | |
8f3eabe3 | 152 | temp = get_user(session->bufsize, argp); |
6b979de3 CB |
153 | if (get_order(session->bufsize) > 8) { |
154 | session->bufsize = PAGE_SIZE; | |
155 | temp = -EINVAL; | |
156 | } | |
d9d119f1 | 157 | mutex_unlock(&session->mutex); |
6b979de3 CB |
158 | return temp; |
159 | case VMCP_GETSIZE: | |
160 | temp = session->resp_size; | |
d9d119f1 | 161 | mutex_unlock(&session->mutex); |
8f3eabe3 | 162 | return put_user(temp, argp); |
6b979de3 | 163 | default: |
d9d119f1 | 164 | mutex_unlock(&session->mutex); |
6b979de3 CB |
165 | return -ENOIOCTLCMD; |
166 | } | |
167 | } | |
168 | ||
d54b1fdb | 169 | static const struct file_operations vmcp_fops = { |
6b979de3 | 170 | .owner = THIS_MODULE, |
d197e692 RD |
171 | .open = vmcp_open, |
172 | .release = vmcp_release, | |
173 | .read = vmcp_read, | |
174 | .write = vmcp_write, | |
175 | .unlocked_ioctl = vmcp_ioctl, | |
d9d119f1 | 176 | .compat_ioctl = vmcp_ioctl, |
6038f373 | 177 | .llseek = no_llseek, |
6b979de3 CB |
178 | }; |
179 | ||
180 | static struct miscdevice vmcp_dev = { | |
181 | .name = "vmcp", | |
182 | .minor = MISC_DYNAMIC_MINOR, | |
183 | .fops = &vmcp_fops, | |
184 | }; | |
185 | ||
186 | static int __init vmcp_init(void) | |
187 | { | |
188 | int ret; | |
189 | ||
f73a2b03 HC |
190 | if (!MACHINE_IS_VM) |
191 | return 0; | |
a44008f2 | 192 | |
66a464db | 193 | vmcp_debug = debug_register("vmcp", 1, 1, 240); |
a44008f2 | 194 | if (!vmcp_debug) |
d9d119f1 | 195 | return -ENOMEM; |
a44008f2 | 196 | |
d9d119f1 CB |
197 | ret = debug_register_view(vmcp_debug, &debug_hex_ascii_view); |
198 | if (ret) { | |
d9d119f1 CB |
199 | debug_unregister(vmcp_debug); |
200 | return ret; | |
201 | } | |
a44008f2 | 202 | |
d9d119f1 | 203 | ret = misc_register(&vmcp_dev); |
f73a2b03 | 204 | if (ret) |
d9d119f1 | 205 | debug_unregister(vmcp_debug); |
f73a2b03 | 206 | return ret; |
6b979de3 | 207 | } |
f73a2b03 | 208 | device_initcall(vmcp_init); |