diff options
author | Ralf Hoppe <rhoppe@de.ibm.com> | 2013-04-08 03:52:57 -0400 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2014-09-25 04:52:02 -0400 |
commit | 8f933b1043e1e51f4776fc1ffe86752c7785fd4e (patch) | |
tree | 9549e4659cd027279b213bab4e735e1ccd5b15d7 /drivers/s390/char/hmcdrv_dev.c | |
parent | ea61a579ab87f1620b14777afc32cf3827f07bc8 (diff) |
s390/hmcdrv: HMC drive CD/DVD access
This device driver allows accessing a HMC drive CD/DVD-ROM.
It can be used in a LPAR and z/VM environment.
Reviewed-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Ralf Hoppe <rhoppe@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390/char/hmcdrv_dev.c')
-rw-r--r-- | drivers/s390/char/hmcdrv_dev.c | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/drivers/s390/char/hmcdrv_dev.c b/drivers/s390/char/hmcdrv_dev.c new file mode 100644 index 000000000000..0c5176179c17 --- /dev/null +++ b/drivers/s390/char/hmcdrv_dev.c | |||
@@ -0,0 +1,370 @@ | |||
1 | /* | ||
2 | * HMC Drive CD/DVD Device | ||
3 | * | ||
4 | * Copyright IBM Corp. 2013 | ||
5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) | ||
6 | * | ||
7 | * This file provides a Linux "misc" character device for access to an | ||
8 | * assigned HMC drive CD/DVD-ROM. It works as follows: First create the | ||
9 | * device by calling hmcdrv_dev_init(). After open() a lseek(fd, 0, | ||
10 | * SEEK_END) indicates that a new FTP command follows (not needed on the | ||
11 | * first command after open). Then write() the FTP command ASCII string | ||
12 | * to it, e.g. "dir /" or "nls <directory>" or "get <filename>". At the | ||
13 | * end read() the response. | ||
14 | */ | ||
15 | |||
16 | #define KMSG_COMPONENT "hmcdrv" | ||
17 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | ||
18 | |||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/slab.h> | ||
22 | #include <linux/fs.h> | ||
23 | #include <linux/cdev.h> | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/device.h> | ||
26 | #include <linux/capability.h> | ||
27 | #include <linux/delay.h> | ||
28 | #include <linux/uaccess.h> | ||
29 | |||
30 | #include "hmcdrv_dev.h" | ||
31 | #include "hmcdrv_ftp.h" | ||
32 | |||
33 | /* If the following macro is defined, then the HMC device creates it's own | ||
34 | * separated device class (and dynamically assigns a major number). If not | ||
35 | * defined then the HMC device is assigned to the "misc" class devices. | ||
36 | * | ||
37 | #define HMCDRV_DEV_CLASS "hmcftp" | ||
38 | */ | ||
39 | |||
40 | #define HMCDRV_DEV_NAME "hmcdrv" | ||
41 | #define HMCDRV_DEV_BUSY_DELAY 500 /* delay between -EBUSY trials in ms */ | ||
42 | #define HMCDRV_DEV_BUSY_RETRIES 3 /* number of retries on -EBUSY */ | ||
43 | |||
44 | struct hmcdrv_dev_node { | ||
45 | |||
46 | #ifdef HMCDRV_DEV_CLASS | ||
47 | struct cdev dev; /* character device structure */ | ||
48 | umode_t mode; /* mode of device node (unused, zero) */ | ||
49 | #else | ||
50 | struct miscdevice dev; /* "misc" device structure */ | ||
51 | #endif | ||
52 | |||
53 | }; | ||
54 | |||
55 | static int hmcdrv_dev_open(struct inode *inode, struct file *fp); | ||
56 | static int hmcdrv_dev_release(struct inode *inode, struct file *fp); | ||
57 | static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence); | ||
58 | static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf, | ||
59 | size_t len, loff_t *pos); | ||
60 | static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf, | ||
61 | size_t len, loff_t *pos); | ||
62 | static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset, | ||
63 | char __user *buf, size_t len); | ||
64 | |||
65 | /* | ||
66 | * device operations | ||
67 | */ | ||
68 | static const struct file_operations hmcdrv_dev_fops = { | ||
69 | .open = hmcdrv_dev_open, | ||
70 | .llseek = hmcdrv_dev_seek, | ||
71 | .release = hmcdrv_dev_release, | ||
72 | .read = hmcdrv_dev_read, | ||
73 | .write = hmcdrv_dev_write, | ||
74 | }; | ||
75 | |||
76 | static struct hmcdrv_dev_node hmcdrv_dev; /* HMC device struct (static) */ | ||
77 | |||
78 | #ifdef HMCDRV_DEV_CLASS | ||
79 | |||
80 | static struct class *hmcdrv_dev_class; /* device class pointer */ | ||
81 | static dev_t hmcdrv_dev_no; /* device number (major/minor) */ | ||
82 | |||
83 | /** | ||
84 | * hmcdrv_dev_name() - provides a naming hint for a device node in /dev | ||
85 | * @dev: device for which the naming/mode hint is | ||
86 | * @mode: file mode for device node created in /dev | ||
87 | * | ||
88 | * See: devtmpfs.c, function devtmpfs_create_node() | ||
89 | * | ||
90 | * Return: recommended device file name in /dev | ||
91 | */ | ||
92 | static char *hmcdrv_dev_name(struct device *dev, umode_t *mode) | ||
93 | { | ||
94 | char *nodename = NULL; | ||
95 | const char *devname = dev_name(dev); /* kernel device name */ | ||
96 | |||
97 | if (devname) | ||
98 | nodename = kasprintf(GFP_KERNEL, "%s", devname); | ||
99 | |||
100 | /* on device destroy (rmmod) the mode pointer may be NULL | ||
101 | */ | ||
102 | if (mode) | ||
103 | *mode = hmcdrv_dev.mode; | ||
104 | |||
105 | return nodename; | ||
106 | } | ||
107 | |||
108 | #endif /* HMCDRV_DEV_CLASS */ | ||
109 | |||
110 | /* | ||
111 | * open() | ||
112 | */ | ||
113 | static int hmcdrv_dev_open(struct inode *inode, struct file *fp) | ||
114 | { | ||
115 | int rc; | ||
116 | |||
117 | /* check for non-blocking access, which is really unsupported | ||
118 | */ | ||
119 | if (fp->f_flags & O_NONBLOCK) | ||
120 | return -EINVAL; | ||
121 | |||
122 | /* Because it makes no sense to open this device read-only (then a | ||
123 | * FTP command cannot be emitted), we respond with an error. | ||
124 | */ | ||
125 | if ((fp->f_flags & O_ACCMODE) == O_RDONLY) | ||
126 | return -EINVAL; | ||
127 | |||
128 | /* prevent unloading this module as long as anyone holds the | ||
129 | * device file open - so increment the reference count here | ||
130 | */ | ||
131 | if (!try_module_get(THIS_MODULE)) | ||
132 | return -ENODEV; | ||
133 | |||
134 | fp->private_data = NULL; /* no command yet */ | ||
135 | rc = hmcdrv_ftp_startup(); | ||
136 | if (rc) | ||
137 | module_put(THIS_MODULE); | ||
138 | |||
139 | pr_debug("open file '/dev/%s' with return code %d\n", | ||
140 | fp->f_dentry->d_name.name, rc); | ||
141 | return rc; | ||
142 | } | ||
143 | |||
144 | /* | ||
145 | * release() | ||
146 | */ | ||
147 | static int hmcdrv_dev_release(struct inode *inode, struct file *fp) | ||
148 | { | ||
149 | pr_debug("closing file '/dev/%s'\n", fp->f_dentry->d_name.name); | ||
150 | kfree(fp->private_data); | ||
151 | fp->private_data = NULL; | ||
152 | hmcdrv_ftp_shutdown(); | ||
153 | module_put(THIS_MODULE); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | /* | ||
158 | * lseek() | ||
159 | */ | ||
160 | static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence) | ||
161 | { | ||
162 | switch (whence) { | ||
163 | case SEEK_CUR: /* relative to current file position */ | ||
164 | pos += fp->f_pos; /* new position stored in 'pos' */ | ||
165 | break; | ||
166 | |||
167 | case SEEK_SET: /* absolute (relative to beginning of file) */ | ||
168 | break; /* SEEK_SET */ | ||
169 | |||
170 | /* We use SEEK_END as a special indicator for a SEEK_SET | ||
171 | * (set absolute position), combined with a FTP command | ||
172 | * clear. | ||
173 | */ | ||
174 | case SEEK_END: | ||
175 | if (fp->private_data) { | ||
176 | kfree(fp->private_data); | ||
177 | fp->private_data = NULL; | ||
178 | } | ||
179 | |||
180 | break; /* SEEK_END */ | ||
181 | |||
182 | default: /* SEEK_DATA, SEEK_HOLE: unsupported */ | ||
183 | return -EINVAL; | ||
184 | } | ||
185 | |||
186 | if (pos < 0) | ||
187 | return -EINVAL; | ||
188 | |||
189 | if (fp->f_pos != pos) | ||
190 | ++fp->f_version; | ||
191 | |||
192 | fp->f_pos = pos; | ||
193 | return pos; | ||
194 | } | ||
195 | |||
196 | /* | ||
197 | * transfer (helper function) | ||
198 | */ | ||
199 | static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset, | ||
200 | char __user *buf, size_t len) | ||
201 | { | ||
202 | ssize_t retlen; | ||
203 | unsigned trials = HMCDRV_DEV_BUSY_RETRIES; | ||
204 | |||
205 | do { | ||
206 | retlen = hmcdrv_ftp_cmd(cmd, offset, buf, len); | ||
207 | |||
208 | if (retlen != -EBUSY) | ||
209 | break; | ||
210 | |||
211 | msleep(HMCDRV_DEV_BUSY_DELAY); | ||
212 | |||
213 | } while (--trials > 0); | ||
214 | |||
215 | return retlen; | ||
216 | } | ||
217 | |||
218 | /* | ||
219 | * read() | ||
220 | */ | ||
221 | static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf, | ||
222 | size_t len, loff_t *pos) | ||
223 | { | ||
224 | ssize_t retlen; | ||
225 | |||
226 | if (((fp->f_flags & O_ACCMODE) == O_WRONLY) || | ||
227 | (fp->private_data == NULL)) { /* no FTP cmd defined ? */ | ||
228 | return -EBADF; | ||
229 | } | ||
230 | |||
231 | retlen = hmcdrv_dev_transfer((char *) fp->private_data, | ||
232 | *pos, ubuf, len); | ||
233 | |||
234 | pr_debug("read from file '/dev/%s' at %lld returns %zd/%zu\n", | ||
235 | fp->f_dentry->d_name.name, (long long) *pos, retlen, len); | ||
236 | |||
237 | if (retlen > 0) | ||
238 | *pos += retlen; | ||
239 | |||
240 | return retlen; | ||
241 | } | ||
242 | |||
243 | /* | ||
244 | * write() | ||
245 | */ | ||
246 | static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf, | ||
247 | size_t len, loff_t *pos) | ||
248 | { | ||
249 | ssize_t retlen; | ||
250 | |||
251 | pr_debug("writing file '/dev/%s' at pos. %lld with length %zd\n", | ||
252 | fp->f_dentry->d_name.name, (long long) *pos, len); | ||
253 | |||
254 | if (!fp->private_data) { /* first expect a cmd write */ | ||
255 | fp->private_data = kmalloc(len + 1, GFP_KERNEL); | ||
256 | |||
257 | if (!fp->private_data) | ||
258 | return -ENOMEM; | ||
259 | |||
260 | if (!copy_from_user(fp->private_data, ubuf, len)) { | ||
261 | ((char *)fp->private_data)[len] = '\0'; | ||
262 | return len; | ||
263 | } | ||
264 | |||
265 | kfree(fp->private_data); | ||
266 | fp->private_data = NULL; | ||
267 | return -EFAULT; | ||
268 | } | ||
269 | |||
270 | retlen = hmcdrv_dev_transfer((char *) fp->private_data, | ||
271 | *pos, (char __user *) ubuf, len); | ||
272 | if (retlen > 0) | ||
273 | *pos += retlen; | ||
274 | |||
275 | pr_debug("write to file '/dev/%s' returned %zd\n", | ||
276 | fp->f_dentry->d_name.name, retlen); | ||
277 | |||
278 | return retlen; | ||
279 | } | ||
280 | |||
281 | /** | ||
282 | * hmcdrv_dev_init() - creates a HMC drive CD/DVD device | ||
283 | * | ||
284 | * This function creates a HMC drive CD/DVD kernel device and an associated | ||
285 | * device under /dev, using a dynamically allocated major number. | ||
286 | * | ||
287 | * Return: 0 on success, else an error code. | ||
288 | */ | ||
289 | int hmcdrv_dev_init(void) | ||
290 | { | ||
291 | int rc; | ||
292 | |||
293 | #ifdef HMCDRV_DEV_CLASS | ||
294 | struct device *dev; | ||
295 | |||
296 | rc = alloc_chrdev_region(&hmcdrv_dev_no, 0, 1, HMCDRV_DEV_NAME); | ||
297 | |||
298 | if (rc) | ||
299 | goto out_err; | ||
300 | |||
301 | cdev_init(&hmcdrv_dev.dev, &hmcdrv_dev_fops); | ||
302 | hmcdrv_dev.dev.owner = THIS_MODULE; | ||
303 | rc = cdev_add(&hmcdrv_dev.dev, hmcdrv_dev_no, 1); | ||
304 | |||
305 | if (rc) | ||
306 | goto out_unreg; | ||
307 | |||
308 | /* At this point the character device exists in the kernel (see | ||
309 | * /proc/devices), but not under /dev nor /sys/devices/virtual. So | ||
310 | * we have to create an associated class (see /sys/class). | ||
311 | */ | ||
312 | hmcdrv_dev_class = class_create(THIS_MODULE, HMCDRV_DEV_CLASS); | ||
313 | |||
314 | if (IS_ERR(hmcdrv_dev_class)) { | ||
315 | rc = PTR_ERR(hmcdrv_dev_class); | ||
316 | goto out_devdel; | ||
317 | } | ||
318 | |||
319 | /* Finally a device node in /dev has to be established (as 'mkdev' | ||
320 | * does from the command line). Notice that assignment of a device | ||
321 | * node name/mode function is optional (only for mode != 0600). | ||
322 | */ | ||
323 | hmcdrv_dev.mode = 0; /* "unset" */ | ||
324 | hmcdrv_dev_class->devnode = hmcdrv_dev_name; | ||
325 | |||
326 | dev = device_create(hmcdrv_dev_class, NULL, hmcdrv_dev_no, NULL, | ||
327 | "%s", HMCDRV_DEV_NAME); | ||
328 | if (!IS_ERR(dev)) | ||
329 | return 0; | ||
330 | |||
331 | rc = PTR_ERR(dev); | ||
332 | class_destroy(hmcdrv_dev_class); | ||
333 | hmcdrv_dev_class = NULL; | ||
334 | |||
335 | out_devdel: | ||
336 | cdev_del(&hmcdrv_dev.dev); | ||
337 | |||
338 | out_unreg: | ||
339 | unregister_chrdev_region(hmcdrv_dev_no, 1); | ||
340 | |||
341 | out_err: | ||
342 | |||
343 | #else /* !HMCDRV_DEV_CLASS */ | ||
344 | hmcdrv_dev.dev.minor = MISC_DYNAMIC_MINOR; | ||
345 | hmcdrv_dev.dev.name = HMCDRV_DEV_NAME; | ||
346 | hmcdrv_dev.dev.fops = &hmcdrv_dev_fops; | ||
347 | hmcdrv_dev.dev.mode = 0; /* finally produces 0600 */ | ||
348 | rc = misc_register(&hmcdrv_dev.dev); | ||
349 | #endif /* HMCDRV_DEV_CLASS */ | ||
350 | |||
351 | return rc; | ||
352 | } | ||
353 | |||
354 | /** | ||
355 | * hmcdrv_dev_exit() - destroys a HMC drive CD/DVD device | ||
356 | */ | ||
357 | void hmcdrv_dev_exit(void) | ||
358 | { | ||
359 | #ifdef HMCDRV_DEV_CLASS | ||
360 | if (!IS_ERR_OR_NULL(hmcdrv_dev_class)) { | ||
361 | device_destroy(hmcdrv_dev_class, hmcdrv_dev_no); | ||
362 | class_destroy(hmcdrv_dev_class); | ||
363 | } | ||
364 | |||
365 | cdev_del(&hmcdrv_dev.dev); | ||
366 | unregister_chrdev_region(hmcdrv_dev_no, 1); | ||
367 | #else /* !HMCDRV_DEV_CLASS */ | ||
368 | misc_deregister(&hmcdrv_dev.dev); | ||
369 | #endif /* HMCDRV_DEV_CLASS */ | ||
370 | } | ||