xref: /linux/drivers/s390/char/hmcdrv_dev.c (revision 2a4c0c11c0193889446cdb6f1540cc2b9aff97dd)
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