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 pr_fmt(fmt) "hmcdrv: " 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 #define HMCDRV_DEV_NAME "hmcdrv" 34 #define HMCDRV_DEV_BUSY_DELAY 500 /* delay between -EBUSY trials in ms */ 35 #define HMCDRV_DEV_BUSY_RETRIES 3 /* number of retries on -EBUSY */ 36 37 struct hmcdrv_dev_node { 38 struct miscdevice dev; /* "misc" device structure */ 39 }; 40 41 static int hmcdrv_dev_open(struct inode *inode, struct file *fp); 42 static int hmcdrv_dev_release(struct inode *inode, struct file *fp); 43 static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence); 44 static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf, 45 size_t len, loff_t *pos); 46 static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf, 47 size_t len, loff_t *pos); 48 static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset, 49 char __user *buf, size_t len); 50 51 /* 52 * device operations 53 */ 54 static const struct file_operations hmcdrv_dev_fops = { 55 .open = hmcdrv_dev_open, 56 .llseek = hmcdrv_dev_seek, 57 .release = hmcdrv_dev_release, 58 .read = hmcdrv_dev_read, 59 .write = hmcdrv_dev_write, 60 }; 61 62 static struct hmcdrv_dev_node hmcdrv_dev; /* HMC device struct (static) */ 63 64 /* 65 * open() 66 */ 67 static int hmcdrv_dev_open(struct inode *inode, struct file *fp) 68 { 69 int rc; 70 71 /* check for non-blocking access, which is really unsupported 72 */ 73 if (fp->f_flags & O_NONBLOCK) 74 return -EINVAL; 75 76 /* Because it makes no sense to open this device read-only (then a 77 * FTP command cannot be emitted), we respond with an error. 78 */ 79 if ((fp->f_flags & O_ACCMODE) == O_RDONLY) 80 return -EINVAL; 81 82 /* prevent unloading this module as long as anyone holds the 83 * device file open - so increment the reference count here 84 */ 85 if (!try_module_get(THIS_MODULE)) 86 return -ENODEV; 87 88 fp->private_data = NULL; /* no command yet */ 89 rc = hmcdrv_ftp_startup(); 90 if (rc) 91 module_put(THIS_MODULE); 92 93 pr_debug("open file '/dev/%pD' with return code %d\n", fp, rc); 94 return rc; 95 } 96 97 /* 98 * release() 99 */ 100 static int hmcdrv_dev_release(struct inode *inode, struct file *fp) 101 { 102 pr_debug("closing file '/dev/%pD'\n", fp); 103 kfree(fp->private_data); 104 fp->private_data = NULL; 105 hmcdrv_ftp_shutdown(); 106 module_put(THIS_MODULE); 107 return 0; 108 } 109 110 /* 111 * lseek() 112 */ 113 static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence) 114 { 115 switch (whence) { 116 case SEEK_CUR: /* relative to current file position */ 117 pos += fp->f_pos; /* new position stored in 'pos' */ 118 break; 119 120 case SEEK_SET: /* absolute (relative to beginning of file) */ 121 break; /* SEEK_SET */ 122 123 /* We use SEEK_END as a special indicator for a SEEK_SET 124 * (set absolute position), combined with a FTP command 125 * clear. 126 */ 127 case SEEK_END: 128 if (fp->private_data) { 129 kfree(fp->private_data); 130 fp->private_data = NULL; 131 } 132 133 break; /* SEEK_END */ 134 135 default: /* SEEK_DATA, SEEK_HOLE: unsupported */ 136 return -EINVAL; 137 } 138 139 if (pos < 0) 140 return -EINVAL; 141 142 fp->f_pos = pos; 143 return pos; 144 } 145 146 /* 147 * transfer (helper function) 148 */ 149 static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset, 150 char __user *buf, size_t len) 151 { 152 ssize_t retlen; 153 unsigned trials = HMCDRV_DEV_BUSY_RETRIES; 154 155 do { 156 retlen = hmcdrv_ftp_cmd(cmd, offset, buf, len); 157 158 if (retlen != -EBUSY) 159 break; 160 161 msleep(HMCDRV_DEV_BUSY_DELAY); 162 163 } while (--trials > 0); 164 165 return retlen; 166 } 167 168 /* 169 * read() 170 */ 171 static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf, 172 size_t len, loff_t *pos) 173 { 174 ssize_t retlen; 175 176 if (((fp->f_flags & O_ACCMODE) == O_WRONLY) || 177 (fp->private_data == NULL)) { /* no FTP cmd defined ? */ 178 return -EBADF; 179 } 180 181 retlen = hmcdrv_dev_transfer((char *) fp->private_data, 182 *pos, ubuf, len); 183 184 pr_debug("read from file '/dev/%pD' at %lld returns %zd/%zu\n", 185 fp, (long long) *pos, retlen, len); 186 187 if (retlen > 0) 188 *pos += retlen; 189 190 return retlen; 191 } 192 193 /* 194 * write() 195 */ 196 static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf, 197 size_t len, loff_t *pos) 198 { 199 ssize_t retlen; 200 void *pdata; 201 202 pr_debug("writing file '/dev/%pD' at pos. %lld with length %zd\n", 203 fp, (long long) *pos, len); 204 205 if (!fp->private_data) { /* first expect a cmd write */ 206 pdata = memdup_user_nul(ubuf, len); 207 if (IS_ERR(pdata)) 208 return PTR_ERR(pdata); 209 fp->private_data = pdata; 210 return len; 211 } 212 213 retlen = hmcdrv_dev_transfer((char *) fp->private_data, 214 *pos, (char __user *) ubuf, len); 215 if (retlen > 0) 216 *pos += retlen; 217 218 pr_debug("write to file '/dev/%pD' returned %zd\n", fp, retlen); 219 220 return retlen; 221 } 222 223 /** 224 * hmcdrv_dev_init() - creates a HMC drive CD/DVD device 225 * 226 * This function creates a HMC drive CD/DVD kernel device and an associated 227 * device under /dev, using a dynamically allocated major number. 228 * 229 * Return: 0 on success, else an error code. 230 */ 231 int hmcdrv_dev_init(void) 232 { 233 hmcdrv_dev.dev.minor = MISC_DYNAMIC_MINOR; 234 hmcdrv_dev.dev.name = HMCDRV_DEV_NAME; 235 hmcdrv_dev.dev.fops = &hmcdrv_dev_fops; 236 hmcdrv_dev.dev.mode = 0; /* finally produces 0600 */ 237 return misc_register(&hmcdrv_dev.dev); 238 } 239 240 /** 241 * hmcdrv_dev_exit() - destroys a HMC drive CD/DVD device 242 */ 243 void hmcdrv_dev_exit(void) 244 { 245 misc_deregister(&hmcdrv_dev.dev); 246 } 247