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 */
hmcdrv_dev_open(struct inode * inode,struct file * fp)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 */
hmcdrv_dev_release(struct inode * inode,struct file * fp)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 */
hmcdrv_dev_seek(struct file * fp,loff_t pos,int whence)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 */
hmcdrv_dev_transfer(char __kernel * cmd,loff_t offset,char __user * buf,size_t len)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 */
hmcdrv_dev_read(struct file * fp,char __user * ubuf,size_t len,loff_t * pos)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 */
hmcdrv_dev_write(struct file * fp,const char __user * ubuf,size_t len,loff_t * pos)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 */
hmcdrv_dev_init(void)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 */
hmcdrv_dev_exit(void)243 void hmcdrv_dev_exit(void)
244 {
245 misc_deregister(&hmcdrv_dev.dev);
246 }
247