1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * HMC Drive CD/DVD Device |
4 | * |
5 | * Copyright IBM Corp. 2013 |
6 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) |
7 | * |
8 | * This file provides a Linux "misc" character device for access to an |
9 | * assigned HMC drive CD/DVD-ROM. It works as follows: First create the |
10 | * device by calling hmcdrv_dev_init(). After open() a lseek(fd, 0, |
11 | * SEEK_END) indicates that a new FTP command follows (not needed on the |
12 | * first command after open). Then write() the FTP command ASCII string |
13 | * to it, e.g. "dir /" or "nls <directory>" or "get <filename>". At the |
14 | * end read() the response. |
15 | */ |
16 | |
17 | #define KMSG_COMPONENT "hmcdrv" |
18 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
19 | |
20 | #include <linux/kernel.h> |
21 | #include <linux/module.h> |
22 | #include <linux/slab.h> |
23 | #include <linux/fs.h> |
24 | #include <linux/cdev.h> |
25 | #include <linux/miscdevice.h> |
26 | #include <linux/device.h> |
27 | #include <linux/capability.h> |
28 | #include <linux/delay.h> |
29 | #include <linux/uaccess.h> |
30 | |
31 | #include "hmcdrv_dev.h" |
32 | #include "hmcdrv_ftp.h" |
33 | |
34 | /* If the following macro is defined, then the HMC device creates it's own |
35 | * separated device class (and dynamically assigns a major number). If not |
36 | * defined then the HMC device is assigned to the "misc" class devices. |
37 | * |
38 | #define HMCDRV_DEV_CLASS "hmcftp" |
39 | */ |
40 | |
41 | #define HMCDRV_DEV_NAME "hmcdrv" |
42 | #define HMCDRV_DEV_BUSY_DELAY 500 /* delay between -EBUSY trials in ms */ |
43 | #define HMCDRV_DEV_BUSY_RETRIES 3 /* number of retries on -EBUSY */ |
44 | |
45 | struct hmcdrv_dev_node { |
46 | |
47 | #ifdef HMCDRV_DEV_CLASS |
48 | struct cdev dev; /* character device structure */ |
49 | umode_t mode; /* mode of device node (unused, zero) */ |
50 | #else |
51 | struct miscdevice dev; /* "misc" device structure */ |
52 | #endif |
53 | |
54 | }; |
55 | |
56 | static int hmcdrv_dev_open(struct inode *inode, struct file *fp); |
57 | static int hmcdrv_dev_release(struct inode *inode, struct file *fp); |
58 | static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence); |
59 | static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf, |
60 | size_t len, loff_t *pos); |
61 | static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf, |
62 | size_t len, loff_t *pos); |
63 | static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset, |
64 | char __user *buf, size_t len); |
65 | |
66 | /* |
67 | * device operations |
68 | */ |
69 | static const struct file_operations hmcdrv_dev_fops = { |
70 | .open = hmcdrv_dev_open, |
71 | .llseek = hmcdrv_dev_seek, |
72 | .release = hmcdrv_dev_release, |
73 | .read = hmcdrv_dev_read, |
74 | .write = hmcdrv_dev_write, |
75 | }; |
76 | |
77 | static struct hmcdrv_dev_node hmcdrv_dev; /* HMC device struct (static) */ |
78 | |
79 | #ifdef HMCDRV_DEV_CLASS |
80 | |
81 | static struct class *hmcdrv_dev_class; /* device class pointer */ |
82 | static dev_t hmcdrv_dev_no; /* device number (major/minor) */ |
83 | |
84 | /** |
85 | * hmcdrv_dev_name() - provides a naming hint for a device node in /dev |
86 | * @dev: device for which the naming/mode hint is |
87 | * @mode: file mode for device node created in /dev |
88 | * |
89 | * See: devtmpfs.c, function devtmpfs_create_node() |
90 | * |
91 | * Return: recommended device file name in /dev |
92 | */ |
93 | static char *hmcdrv_dev_name(const struct device *dev, umode_t *mode) |
94 | { |
95 | char *nodename = NULL; |
96 | const char *devname = dev_name(dev); /* kernel device name */ |
97 | |
98 | if (devname) |
99 | nodename = kasprintf(GFP_KERNEL, "%s" , devname); |
100 | |
101 | /* on device destroy (rmmod) the mode pointer may be NULL |
102 | */ |
103 | if (mode) |
104 | *mode = hmcdrv_dev.mode; |
105 | |
106 | return nodename; |
107 | } |
108 | |
109 | #endif /* HMCDRV_DEV_CLASS */ |
110 | |
111 | /* |
112 | * open() |
113 | */ |
114 | static int hmcdrv_dev_open(struct inode *inode, struct file *fp) |
115 | { |
116 | int rc; |
117 | |
118 | /* check for non-blocking access, which is really unsupported |
119 | */ |
120 | if (fp->f_flags & O_NONBLOCK) |
121 | return -EINVAL; |
122 | |
123 | /* Because it makes no sense to open this device read-only (then a |
124 | * FTP command cannot be emitted), we respond with an error. |
125 | */ |
126 | if ((fp->f_flags & O_ACCMODE) == O_RDONLY) |
127 | return -EINVAL; |
128 | |
129 | /* prevent unloading this module as long as anyone holds the |
130 | * device file open - so increment the reference count here |
131 | */ |
132 | if (!try_module_get(THIS_MODULE)) |
133 | return -ENODEV; |
134 | |
135 | fp->private_data = NULL; /* no command yet */ |
136 | rc = hmcdrv_ftp_startup(); |
137 | if (rc) |
138 | module_put(THIS_MODULE); |
139 | |
140 | pr_debug("open file '/dev/%pD' with return code %d\n" , fp, 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/%pD'\n" , fp); |
150 | kfree(objp: 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(objp: 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(cmd: (char *) fp->private_data, |
232 | offset: *pos, buf: ubuf, len); |
233 | |
234 | pr_debug("read from file '/dev/%pD' at %lld returns %zd/%zu\n" , |
235 | fp, (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/%pD' at pos. %lld with length %zd\n" , |
252 | fp, (long long) *pos, len); |
253 | |
254 | if (!fp->private_data) { /* first expect a cmd write */ |
255 | fp->private_data = kmalloc(size: len + 1, GFP_KERNEL); |
256 | |
257 | if (!fp->private_data) |
258 | return -ENOMEM; |
259 | |
260 | if (!copy_from_user(to: fp->private_data, from: ubuf, n: len)) { |
261 | ((char *)fp->private_data)[len] = '\0'; |
262 | return len; |
263 | } |
264 | |
265 | kfree(objp: fp->private_data); |
266 | fp->private_data = NULL; |
267 | return -EFAULT; |
268 | } |
269 | |
270 | retlen = hmcdrv_dev_transfer(cmd: (char *) fp->private_data, |
271 | offset: *pos, buf: (char __user *) ubuf, len); |
272 | if (retlen > 0) |
273 | *pos += retlen; |
274 | |
275 | pr_debug("write to file '/dev/%pD' returned %zd\n" , fp, retlen); |
276 | |
277 | return retlen; |
278 | } |
279 | |
280 | /** |
281 | * hmcdrv_dev_init() - creates a HMC drive CD/DVD device |
282 | * |
283 | * This function creates a HMC drive CD/DVD kernel device and an associated |
284 | * device under /dev, using a dynamically allocated major number. |
285 | * |
286 | * Return: 0 on success, else an error code. |
287 | */ |
288 | int hmcdrv_dev_init(void) |
289 | { |
290 | int rc; |
291 | |
292 | #ifdef HMCDRV_DEV_CLASS |
293 | struct device *dev; |
294 | |
295 | rc = alloc_chrdev_region(&hmcdrv_dev_no, 0, 1, HMCDRV_DEV_NAME); |
296 | |
297 | if (rc) |
298 | goto out_err; |
299 | |
300 | cdev_init(&hmcdrv_dev.dev, &hmcdrv_dev_fops); |
301 | hmcdrv_dev.dev.owner = THIS_MODULE; |
302 | rc = cdev_add(&hmcdrv_dev.dev, hmcdrv_dev_no, 1); |
303 | |
304 | if (rc) |
305 | goto out_unreg; |
306 | |
307 | /* At this point the character device exists in the kernel (see |
308 | * /proc/devices), but not under /dev nor /sys/devices/virtual. So |
309 | * we have to create an associated class (see /sys/class). |
310 | */ |
311 | hmcdrv_dev_class = class_create(HMCDRV_DEV_CLASS); |
312 | |
313 | if (IS_ERR(hmcdrv_dev_class)) { |
314 | rc = PTR_ERR(hmcdrv_dev_class); |
315 | goto out_devdel; |
316 | } |
317 | |
318 | /* Finally a device node in /dev has to be established (as 'mkdev' |
319 | * does from the command line). Notice that assignment of a device |
320 | * node name/mode function is optional (only for mode != 0600). |
321 | */ |
322 | hmcdrv_dev.mode = 0; /* "unset" */ |
323 | hmcdrv_dev_class->devnode = hmcdrv_dev_name; |
324 | |
325 | dev = device_create(hmcdrv_dev_class, NULL, hmcdrv_dev_no, NULL, |
326 | "%s" , HMCDRV_DEV_NAME); |
327 | if (!IS_ERR(dev)) |
328 | return 0; |
329 | |
330 | rc = PTR_ERR(dev); |
331 | class_destroy(hmcdrv_dev_class); |
332 | hmcdrv_dev_class = NULL; |
333 | |
334 | out_devdel: |
335 | cdev_del(&hmcdrv_dev.dev); |
336 | |
337 | out_unreg: |
338 | unregister_chrdev_region(hmcdrv_dev_no, 1); |
339 | |
340 | out_err: |
341 | |
342 | #else /* !HMCDRV_DEV_CLASS */ |
343 | hmcdrv_dev.dev.minor = MISC_DYNAMIC_MINOR; |
344 | hmcdrv_dev.dev.name = HMCDRV_DEV_NAME; |
345 | hmcdrv_dev.dev.fops = &hmcdrv_dev_fops; |
346 | hmcdrv_dev.dev.mode = 0; /* finally produces 0600 */ |
347 | rc = misc_register(misc: &hmcdrv_dev.dev); |
348 | #endif /* HMCDRV_DEV_CLASS */ |
349 | |
350 | return rc; |
351 | } |
352 | |
353 | /** |
354 | * hmcdrv_dev_exit() - destroys a HMC drive CD/DVD device |
355 | */ |
356 | void hmcdrv_dev_exit(void) |
357 | { |
358 | #ifdef HMCDRV_DEV_CLASS |
359 | if (!IS_ERR_OR_NULL(hmcdrv_dev_class)) { |
360 | device_destroy(hmcdrv_dev_class, hmcdrv_dev_no); |
361 | class_destroy(hmcdrv_dev_class); |
362 | } |
363 | |
364 | cdev_del(&hmcdrv_dev.dev); |
365 | unregister_chrdev_region(hmcdrv_dev_no, 1); |
366 | #else /* !HMCDRV_DEV_CLASS */ |
367 | misc_deregister(misc: &hmcdrv_dev.dev); |
368 | #endif /* HMCDRV_DEV_CLASS */ |
369 | } |
370 | |