diff options
Diffstat (limited to 'drivers/s390/char/vmcp.c')
-rw-r--r-- | drivers/s390/char/vmcp.c | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/drivers/s390/char/vmcp.c b/drivers/s390/char/vmcp.c new file mode 100644 index 000000000000..dfbbf235ca2b --- /dev/null +++ b/drivers/s390/char/vmcp.c | |||
@@ -0,0 +1,219 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2004,2005 IBM Corporation | ||
3 | * Interface implementation for communication with the v/VM control program | ||
4 | * Author(s): Christian Borntraeger <cborntra@de.ibm.com> | ||
5 | * | ||
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. | ||
10 | |||
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> | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/miscdevice.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <asm/cpcmd.h> | ||
20 | #include <asm/debug.h> | ||
21 | #include <asm/uaccess.h> | ||
22 | #include "vmcp.h" | ||
23 | |||
24 | MODULE_LICENSE("GPL"); | ||
25 | MODULE_AUTHOR("Christian Borntraeger <cborntra@de.ibm.com>"); | ||
26 | MODULE_DESCRIPTION("z/VM CP interface"); | ||
27 | |||
28 | static debug_info_t *vmcp_debug; | ||
29 | |||
30 | static int vmcp_open(struct inode *inode, struct file *file) | ||
31 | { | ||
32 | struct vmcp_session *session; | ||
33 | |||
34 | if (!capable(CAP_SYS_ADMIN)) | ||
35 | return -EPERM; | ||
36 | |||
37 | session = kmalloc(sizeof(*session), GFP_KERNEL); | ||
38 | if (!session) | ||
39 | return -ENOMEM; | ||
40 | session->bufsize = PAGE_SIZE; | ||
41 | session->response = NULL; | ||
42 | session->resp_size = 0; | ||
43 | init_MUTEX(&session->mutex); | ||
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 | |||
52 | session = (struct vmcp_session *)file->private_data; | ||
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 | ||
60 | vmcp_read(struct file *file, char __user * buff, size_t count, loff_t * ppos) | ||
61 | { | ||
62 | size_t tocopy; | ||
63 | struct vmcp_session *session; | ||
64 | |||
65 | session = (struct vmcp_session *)file->private_data; | ||
66 | if (down_interruptible(&session->mutex)) | ||
67 | return -ERESTARTSYS; | ||
68 | if (!session->response) { | ||
69 | up(&session->mutex); | ||
70 | return 0; | ||
71 | } | ||
72 | if (*ppos > session->resp_size) { | ||
73 | up(&session->mutex); | ||
74 | return 0; | ||
75 | } | ||
76 | tocopy = min(session->resp_size - (size_t) (*ppos), count); | ||
77 | tocopy = min(tocopy,session->bufsize - (size_t) (*ppos)); | ||
78 | |||
79 | if (copy_to_user(buff, session->response + (*ppos), tocopy)) { | ||
80 | up(&session->mutex); | ||
81 | return -EFAULT; | ||
82 | } | ||
83 | up(&session->mutex); | ||
84 | *ppos += tocopy; | ||
85 | return tocopy; | ||
86 | } | ||
87 | |||
88 | static ssize_t | ||
89 | vmcp_write(struct file *file, const char __user * buff, size_t count, | ||
90 | loff_t * ppos) | ||
91 | { | ||
92 | char *cmd; | ||
93 | struct vmcp_session *session; | ||
94 | |||
95 | if (count > 240) | ||
96 | return -EINVAL; | ||
97 | cmd = kmalloc(count + 1, GFP_KERNEL); | ||
98 | if (!cmd) | ||
99 | return -ENOMEM; | ||
100 | if (copy_from_user(cmd, buff, count)) { | ||
101 | kfree(cmd); | ||
102 | return -EFAULT; | ||
103 | } | ||
104 | cmd[count] = '\0'; | ||
105 | session = (struct vmcp_session *)file->private_data; | ||
106 | if (down_interruptible(&session->mutex)) | ||
107 | return -ERESTARTSYS; | ||
108 | if (!session->response) | ||
109 | session->response = (char *)__get_free_pages(GFP_KERNEL | ||
110 | | __GFP_REPEAT | GFP_DMA, | ||
111 | get_order(session->bufsize)); | ||
112 | if (!session->response) { | ||
113 | up(&session->mutex); | ||
114 | kfree(cmd); | ||
115 | return -ENOMEM; | ||
116 | } | ||
117 | debug_text_event(vmcp_debug, 1, cmd); | ||
118 | session->resp_size = cpcmd(cmd, session->response, | ||
119 | session->bufsize, | ||
120 | &session->resp_code); | ||
121 | up(&session->mutex); | ||
122 | kfree(cmd); | ||
123 | *ppos = 0; /* reset the file pointer after a command */ | ||
124 | return count; | ||
125 | } | ||
126 | |||
127 | |||
128 | /* | ||
129 | * These ioctls are available, as the semantics of the diagnose 8 call | ||
130 | * does not fit very well into a Linux call. Diagnose X'08' is described in | ||
131 | * CP Programming Services SC24-6084-00 | ||
132 | * | ||
133 | * VMCP_GETCODE: gives the CP return code back to user space | ||
134 | * VMCP_SETBUF: sets the response buffer for the next write call. diagnose 8 | ||
135 | * expects adjacent pages in real storage and to make matters worse, we | ||
136 | * dont know the size of the response. Therefore we default to PAGESIZE and | ||
137 | * let userspace to change the response size, if userspace expects a bigger | ||
138 | * response | ||
139 | */ | ||
140 | static long vmcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | ||
141 | { | ||
142 | struct vmcp_session *session; | ||
143 | int temp; | ||
144 | |||
145 | session = (struct vmcp_session *)file->private_data; | ||
146 | if (down_interruptible(&session->mutex)) | ||
147 | return -ERESTARTSYS; | ||
148 | switch (cmd) { | ||
149 | case VMCP_GETCODE: | ||
150 | temp = session->resp_code; | ||
151 | up(&session->mutex); | ||
152 | return put_user(temp, (int __user *)arg); | ||
153 | case VMCP_SETBUF: | ||
154 | free_pages((unsigned long)session->response, | ||
155 | get_order(session->bufsize)); | ||
156 | session->response=NULL; | ||
157 | temp = get_user(session->bufsize, (int __user *)arg); | ||
158 | if (get_order(session->bufsize) > 8) { | ||
159 | session->bufsize = PAGE_SIZE; | ||
160 | temp = -EINVAL; | ||
161 | } | ||
162 | up(&session->mutex); | ||
163 | return temp; | ||
164 | case VMCP_GETSIZE: | ||
165 | temp = session->resp_size; | ||
166 | up(&session->mutex); | ||
167 | return put_user(temp, (int __user *)arg); | ||
168 | default: | ||
169 | up(&session->mutex); | ||
170 | return -ENOIOCTLCMD; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | static struct file_operations vmcp_fops = { | ||
175 | .owner = THIS_MODULE, | ||
176 | .open = &vmcp_open, | ||
177 | .release = &vmcp_release, | ||
178 | .read = &vmcp_read, | ||
179 | .llseek = &no_llseek, | ||
180 | .write = &vmcp_write, | ||
181 | .unlocked_ioctl = &vmcp_ioctl, | ||
182 | .compat_ioctl = &vmcp_ioctl | ||
183 | }; | ||
184 | |||
185 | static struct miscdevice vmcp_dev = { | ||
186 | .name = "vmcp", | ||
187 | .minor = MISC_DYNAMIC_MINOR, | ||
188 | .fops = &vmcp_fops, | ||
189 | }; | ||
190 | |||
191 | static int __init vmcp_init(void) | ||
192 | { | ||
193 | int ret; | ||
194 | |||
195 | if (!MACHINE_IS_VM) { | ||
196 | printk(KERN_WARNING | ||
197 | "z/VM CP interface is only available under z/VM\n"); | ||
198 | return -ENODEV; | ||
199 | } | ||
200 | ret = misc_register(&vmcp_dev); | ||
201 | if (!ret) | ||
202 | printk(KERN_INFO "z/VM CP interface loaded\n"); | ||
203 | else | ||
204 | printk(KERN_WARNING | ||
205 | "z/VM CP interface not loaded. Could not register misc device.\n"); | ||
206 | vmcp_debug = debug_register("vmcp", 0, 1, 240); | ||
207 | debug_register_view(vmcp_debug, &debug_hex_ascii_view); | ||
208 | return ret; | ||
209 | } | ||
210 | |||
211 | static void __exit vmcp_exit(void) | ||
212 | { | ||
213 | WARN_ON(misc_deregister(&vmcp_dev) != 0); | ||
214 | debug_unregister(vmcp_debug); | ||
215 | printk(KERN_INFO "z/VM CP interface unloaded.\n"); | ||
216 | } | ||
217 | |||
218 | module_init(vmcp_init); | ||
219 | module_exit(vmcp_exit); | ||