Ralf Hoppe | 8f933b1 | 2013-04-08 09:52:57 +0200 | [diff] [blame] | 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 | |
Al Viro | a455589 | 2014-10-21 20:11:25 -0400 | [diff] [blame] | 139 | pr_debug("open file '/dev/%pD' with return code %d\n", fp, rc); |
Ralf Hoppe | 8f933b1 | 2013-04-08 09:52:57 +0200 | [diff] [blame] | 140 | return rc; |
| 141 | } |
| 142 | |
| 143 | /* |
| 144 | * release() |
| 145 | */ |
| 146 | static int hmcdrv_dev_release(struct inode *inode, struct file *fp) |
| 147 | { |
Al Viro | a455589 | 2014-10-21 20:11:25 -0400 | [diff] [blame] | 148 | pr_debug("closing file '/dev/%pD'\n", fp); |
Ralf Hoppe | 8f933b1 | 2013-04-08 09:52:57 +0200 | [diff] [blame] | 149 | kfree(fp->private_data); |
| 150 | fp->private_data = NULL; |
| 151 | hmcdrv_ftp_shutdown(); |
| 152 | module_put(THIS_MODULE); |
| 153 | return 0; |
| 154 | } |
| 155 | |
| 156 | /* |
| 157 | * lseek() |
| 158 | */ |
| 159 | static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence) |
| 160 | { |
| 161 | switch (whence) { |
| 162 | case SEEK_CUR: /* relative to current file position */ |
| 163 | pos += fp->f_pos; /* new position stored in 'pos' */ |
| 164 | break; |
| 165 | |
| 166 | case SEEK_SET: /* absolute (relative to beginning of file) */ |
| 167 | break; /* SEEK_SET */ |
| 168 | |
| 169 | /* We use SEEK_END as a special indicator for a SEEK_SET |
| 170 | * (set absolute position), combined with a FTP command |
| 171 | * clear. |
| 172 | */ |
| 173 | case SEEK_END: |
| 174 | if (fp->private_data) { |
| 175 | kfree(fp->private_data); |
| 176 | fp->private_data = NULL; |
| 177 | } |
| 178 | |
| 179 | break; /* SEEK_END */ |
| 180 | |
| 181 | default: /* SEEK_DATA, SEEK_HOLE: unsupported */ |
| 182 | return -EINVAL; |
| 183 | } |
| 184 | |
| 185 | if (pos < 0) |
| 186 | return -EINVAL; |
| 187 | |
| 188 | if (fp->f_pos != pos) |
| 189 | ++fp->f_version; |
| 190 | |
| 191 | fp->f_pos = pos; |
| 192 | return pos; |
| 193 | } |
| 194 | |
| 195 | /* |
| 196 | * transfer (helper function) |
| 197 | */ |
| 198 | static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset, |
| 199 | char __user *buf, size_t len) |
| 200 | { |
| 201 | ssize_t retlen; |
| 202 | unsigned trials = HMCDRV_DEV_BUSY_RETRIES; |
| 203 | |
| 204 | do { |
| 205 | retlen = hmcdrv_ftp_cmd(cmd, offset, buf, len); |
| 206 | |
| 207 | if (retlen != -EBUSY) |
| 208 | break; |
| 209 | |
| 210 | msleep(HMCDRV_DEV_BUSY_DELAY); |
| 211 | |
| 212 | } while (--trials > 0); |
| 213 | |
| 214 | return retlen; |
| 215 | } |
| 216 | |
| 217 | /* |
| 218 | * read() |
| 219 | */ |
| 220 | static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf, |
| 221 | size_t len, loff_t *pos) |
| 222 | { |
| 223 | ssize_t retlen; |
| 224 | |
| 225 | if (((fp->f_flags & O_ACCMODE) == O_WRONLY) || |
| 226 | (fp->private_data == NULL)) { /* no FTP cmd defined ? */ |
| 227 | return -EBADF; |
| 228 | } |
| 229 | |
| 230 | retlen = hmcdrv_dev_transfer((char *) fp->private_data, |
| 231 | *pos, ubuf, len); |
| 232 | |
Al Viro | a455589 | 2014-10-21 20:11:25 -0400 | [diff] [blame] | 233 | pr_debug("read from file '/dev/%pD' at %lld returns %zd/%zu\n", |
| 234 | fp, (long long) *pos, retlen, len); |
Ralf Hoppe | 8f933b1 | 2013-04-08 09:52:57 +0200 | [diff] [blame] | 235 | |
| 236 | if (retlen > 0) |
| 237 | *pos += retlen; |
| 238 | |
| 239 | return retlen; |
| 240 | } |
| 241 | |
| 242 | /* |
| 243 | * write() |
| 244 | */ |
| 245 | static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf, |
| 246 | size_t len, loff_t *pos) |
| 247 | { |
| 248 | ssize_t retlen; |
| 249 | |
Al Viro | a455589 | 2014-10-21 20:11:25 -0400 | [diff] [blame] | 250 | pr_debug("writing file '/dev/%pD' at pos. %lld with length %zd\n", |
| 251 | fp, (long long) *pos, len); |
Ralf Hoppe | 8f933b1 | 2013-04-08 09:52:57 +0200 | [diff] [blame] | 252 | |
| 253 | if (!fp->private_data) { /* first expect a cmd write */ |
| 254 | fp->private_data = kmalloc(len + 1, GFP_KERNEL); |
| 255 | |
| 256 | if (!fp->private_data) |
| 257 | return -ENOMEM; |
| 258 | |
| 259 | if (!copy_from_user(fp->private_data, ubuf, len)) { |
| 260 | ((char *)fp->private_data)[len] = '\0'; |
| 261 | return len; |
| 262 | } |
| 263 | |
| 264 | kfree(fp->private_data); |
| 265 | fp->private_data = NULL; |
| 266 | return -EFAULT; |
| 267 | } |
| 268 | |
| 269 | retlen = hmcdrv_dev_transfer((char *) fp->private_data, |
| 270 | *pos, (char __user *) ubuf, len); |
| 271 | if (retlen > 0) |
| 272 | *pos += retlen; |
| 273 | |
Al Viro | a455589 | 2014-10-21 20:11:25 -0400 | [diff] [blame] | 274 | pr_debug("write to file '/dev/%pD' returned %zd\n", fp, retlen); |
Ralf Hoppe | 8f933b1 | 2013-04-08 09:52:57 +0200 | [diff] [blame] | 275 | |
| 276 | return retlen; |
| 277 | } |
| 278 | |
| 279 | /** |
| 280 | * hmcdrv_dev_init() - creates a HMC drive CD/DVD device |
| 281 | * |
| 282 | * This function creates a HMC drive CD/DVD kernel device and an associated |
| 283 | * device under /dev, using a dynamically allocated major number. |
| 284 | * |
| 285 | * Return: 0 on success, else an error code. |
| 286 | */ |
| 287 | int hmcdrv_dev_init(void) |
| 288 | { |
| 289 | int rc; |
| 290 | |
| 291 | #ifdef HMCDRV_DEV_CLASS |
| 292 | struct device *dev; |
| 293 | |
| 294 | rc = alloc_chrdev_region(&hmcdrv_dev_no, 0, 1, HMCDRV_DEV_NAME); |
| 295 | |
| 296 | if (rc) |
| 297 | goto out_err; |
| 298 | |
| 299 | cdev_init(&hmcdrv_dev.dev, &hmcdrv_dev_fops); |
| 300 | hmcdrv_dev.dev.owner = THIS_MODULE; |
| 301 | rc = cdev_add(&hmcdrv_dev.dev, hmcdrv_dev_no, 1); |
| 302 | |
| 303 | if (rc) |
| 304 | goto out_unreg; |
| 305 | |
| 306 | /* At this point the character device exists in the kernel (see |
| 307 | * /proc/devices), but not under /dev nor /sys/devices/virtual. So |
| 308 | * we have to create an associated class (see /sys/class). |
| 309 | */ |
| 310 | hmcdrv_dev_class = class_create(THIS_MODULE, HMCDRV_DEV_CLASS); |
| 311 | |
| 312 | if (IS_ERR(hmcdrv_dev_class)) { |
| 313 | rc = PTR_ERR(hmcdrv_dev_class); |
| 314 | goto out_devdel; |
| 315 | } |
| 316 | |
| 317 | /* Finally a device node in /dev has to be established (as 'mkdev' |
| 318 | * does from the command line). Notice that assignment of a device |
| 319 | * node name/mode function is optional (only for mode != 0600). |
| 320 | */ |
| 321 | hmcdrv_dev.mode = 0; /* "unset" */ |
| 322 | hmcdrv_dev_class->devnode = hmcdrv_dev_name; |
| 323 | |
| 324 | dev = device_create(hmcdrv_dev_class, NULL, hmcdrv_dev_no, NULL, |
| 325 | "%s", HMCDRV_DEV_NAME); |
| 326 | if (!IS_ERR(dev)) |
| 327 | return 0; |
| 328 | |
| 329 | rc = PTR_ERR(dev); |
| 330 | class_destroy(hmcdrv_dev_class); |
| 331 | hmcdrv_dev_class = NULL; |
| 332 | |
| 333 | out_devdel: |
| 334 | cdev_del(&hmcdrv_dev.dev); |
| 335 | |
| 336 | out_unreg: |
| 337 | unregister_chrdev_region(hmcdrv_dev_no, 1); |
| 338 | |
| 339 | out_err: |
| 340 | |
| 341 | #else /* !HMCDRV_DEV_CLASS */ |
| 342 | hmcdrv_dev.dev.minor = MISC_DYNAMIC_MINOR; |
| 343 | hmcdrv_dev.dev.name = HMCDRV_DEV_NAME; |
| 344 | hmcdrv_dev.dev.fops = &hmcdrv_dev_fops; |
| 345 | hmcdrv_dev.dev.mode = 0; /* finally produces 0600 */ |
| 346 | rc = misc_register(&hmcdrv_dev.dev); |
| 347 | #endif /* HMCDRV_DEV_CLASS */ |
| 348 | |
| 349 | return rc; |
| 350 | } |
| 351 | |
| 352 | /** |
| 353 | * hmcdrv_dev_exit() - destroys a HMC drive CD/DVD device |
| 354 | */ |
| 355 | void hmcdrv_dev_exit(void) |
| 356 | { |
| 357 | #ifdef HMCDRV_DEV_CLASS |
| 358 | if (!IS_ERR_OR_NULL(hmcdrv_dev_class)) { |
| 359 | device_destroy(hmcdrv_dev_class, hmcdrv_dev_no); |
| 360 | class_destroy(hmcdrv_dev_class); |
| 361 | } |
| 362 | |
| 363 | cdev_del(&hmcdrv_dev.dev); |
| 364 | unregister_chrdev_region(hmcdrv_dev_no, 1); |
| 365 | #else /* !HMCDRV_DEV_CLASS */ |
| 366 | misc_deregister(&hmcdrv_dev.dev); |
| 367 | #endif /* HMCDRV_DEV_CLASS */ |
| 368 | } |