1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * HMC Drive FTP Services
4 *
5 * Copyright IBM Corp. 2013
6 * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
7 */
8
9 #define KMSG_COMPONENT "hmcdrv"
10 #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
11
12 #include <linux/kernel.h>
13 #include <linux/slab.h>
14 #include <linux/uaccess.h>
15 #include <linux/export.h>
16
17 #include <linux/ctype.h>
18 #include <linux/crc16.h>
19
20 #include <asm/machine.h>
21
22 #include "hmcdrv_ftp.h"
23 #include "hmcdrv_cache.h"
24 #include "sclp_ftp.h"
25 #include "diag_ftp.h"
26
27 /**
28 * struct hmcdrv_ftp_ops - HMC drive FTP operations
29 * @startup: startup function
30 * @shutdown: shutdown function
31 * @transfer: FTP transfer function
32 */
33 struct hmcdrv_ftp_ops {
34 int (*startup)(void);
35 void (*shutdown)(void);
36 ssize_t (*transfer)(const struct hmcdrv_ftp_cmdspec *ftp,
37 size_t *fsize);
38 };
39
40 static enum hmcdrv_ftp_cmdid hmcdrv_ftp_cmd_getid(const char *cmd, int len);
41 static int hmcdrv_ftp_parse(char *cmd, struct hmcdrv_ftp_cmdspec *ftp);
42
43 static const struct hmcdrv_ftp_ops *hmcdrv_ftp_funcs; /* current operations */
44 static DEFINE_MUTEX(hmcdrv_ftp_mutex); /* mutex for hmcdrv_ftp_funcs */
45 static unsigned hmcdrv_ftp_refcnt; /* start/shutdown reference counter */
46
47 /**
48 * hmcdrv_ftp_cmd_getid() - determine FTP command ID from a command string
49 * @cmd: FTP command string (NOT zero-terminated)
50 * @len: length of FTP command string in @cmd
51 */
hmcdrv_ftp_cmd_getid(const char * cmd,int len)52 static enum hmcdrv_ftp_cmdid hmcdrv_ftp_cmd_getid(const char *cmd, int len)
53 {
54 /* HMC FTP command descriptor */
55 struct hmcdrv_ftp_cmd_desc {
56 const char *str; /* command string */
57 enum hmcdrv_ftp_cmdid cmd; /* associated command as enum */
58 };
59
60 /* Description of all HMC drive FTP commands
61 *
62 * Notes:
63 * 1. Array size should be a prime number.
64 * 2. Do not change the order of commands in table (because the
65 * index is determined by CRC % ARRAY_SIZE).
66 * 3. Original command 'nlist' was renamed, else the CRC would
67 * collide with 'append' (see point 2).
68 */
69 static const struct hmcdrv_ftp_cmd_desc ftpcmds[7] = {
70
71 {.str = "get", /* [0] get (CRC = 0x68eb) */
72 .cmd = HMCDRV_FTP_GET},
73 {.str = "dir", /* [1] dir (CRC = 0x6a9e) */
74 .cmd = HMCDRV_FTP_DIR},
75 {.str = "delete", /* [2] delete (CRC = 0x53ae) */
76 .cmd = HMCDRV_FTP_DELETE},
77 {.str = "nls", /* [3] nls (CRC = 0xf87c) */
78 .cmd = HMCDRV_FTP_NLIST},
79 {.str = "put", /* [4] put (CRC = 0xac56) */
80 .cmd = HMCDRV_FTP_PUT},
81 {.str = "append", /* [5] append (CRC = 0xf56e) */
82 .cmd = HMCDRV_FTP_APPEND},
83 {.str = NULL} /* [6] unused */
84 };
85
86 const struct hmcdrv_ftp_cmd_desc *pdesc;
87
88 u16 crc = 0xffffU;
89
90 if (len == 0)
91 return HMCDRV_FTP_NOOP; /* error indiactor */
92
93 crc = crc16(crc, cmd, len);
94 pdesc = ftpcmds + (crc % ARRAY_SIZE(ftpcmds));
95 pr_debug("FTP command '%s' has CRC 0x%04x, at table pos. %lu\n",
96 cmd, crc, (crc % ARRAY_SIZE(ftpcmds)));
97
98 if (!pdesc->str || strncmp(pdesc->str, cmd, len))
99 return HMCDRV_FTP_NOOP;
100
101 pr_debug("FTP command '%s' found, with ID %d\n",
102 pdesc->str, pdesc->cmd);
103
104 return pdesc->cmd;
105 }
106
107 /**
108 * hmcdrv_ftp_parse() - HMC drive FTP command parser
109 * @cmd: FTP command string "<cmd> <filename>"
110 * @ftp: Pointer to FTP command specification buffer (output)
111 *
112 * Return: 0 on success, else a (negative) error code
113 */
hmcdrv_ftp_parse(char * cmd,struct hmcdrv_ftp_cmdspec * ftp)114 static int hmcdrv_ftp_parse(char *cmd, struct hmcdrv_ftp_cmdspec *ftp)
115 {
116 char *start;
117 int argc = 0;
118
119 ftp->id = HMCDRV_FTP_NOOP;
120 ftp->fname = NULL;
121
122 while (*cmd != '\0') {
123
124 while (isspace(*cmd))
125 ++cmd;
126
127 if (*cmd == '\0')
128 break;
129
130 start = cmd;
131
132 switch (argc) {
133 case 0: /* 1st argument (FTP command) */
134 while ((*cmd != '\0') && !isspace(*cmd))
135 ++cmd;
136 ftp->id = hmcdrv_ftp_cmd_getid(start, cmd - start);
137 break;
138 case 1: /* 2nd / last argument (rest of line) */
139 while ((*cmd != '\0') && !iscntrl(*cmd))
140 ++cmd;
141 ftp->fname = start;
142 fallthrough;
143 default:
144 *cmd = '\0';
145 break;
146 } /* switch */
147
148 ++argc;
149 } /* while */
150
151 if (!ftp->fname || (ftp->id == HMCDRV_FTP_NOOP))
152 return -EINVAL;
153
154 return 0;
155 }
156
157 /**
158 * hmcdrv_ftp_do() - perform a HMC drive FTP, with data from kernel-space
159 * @ftp: pointer to FTP command specification
160 *
161 * Return: number of bytes read/written or a negative error code
162 */
hmcdrv_ftp_do(const struct hmcdrv_ftp_cmdspec * ftp)163 ssize_t hmcdrv_ftp_do(const struct hmcdrv_ftp_cmdspec *ftp)
164 {
165 ssize_t len;
166
167 mutex_lock(&hmcdrv_ftp_mutex);
168
169 if (hmcdrv_ftp_funcs && hmcdrv_ftp_refcnt) {
170 pr_debug("starting transfer, cmd %d for '%s' at %lld with %zd bytes\n",
171 ftp->id, ftp->fname, (long long) ftp->ofs, ftp->len);
172 len = hmcdrv_cache_cmd(ftp, hmcdrv_ftp_funcs->transfer);
173 } else {
174 len = -ENXIO;
175 }
176
177 mutex_unlock(&hmcdrv_ftp_mutex);
178 return len;
179 }
180 EXPORT_SYMBOL(hmcdrv_ftp_do);
181
182 /**
183 * hmcdrv_ftp_probe() - probe for the HMC drive FTP service
184 *
185 * Return: 0 if service is available, else an (negative) error code
186 */
hmcdrv_ftp_probe(void)187 int hmcdrv_ftp_probe(void)
188 {
189 int rc;
190
191 struct hmcdrv_ftp_cmdspec ftp = {
192 .id = HMCDRV_FTP_NOOP,
193 .ofs = 0,
194 .fname = "",
195 .len = PAGE_SIZE
196 };
197
198 ftp.buf = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
199
200 if (!ftp.buf)
201 return -ENOMEM;
202
203 rc = hmcdrv_ftp_startup();
204
205 if (rc)
206 goto out;
207
208 rc = hmcdrv_ftp_do(&ftp);
209 hmcdrv_ftp_shutdown();
210
211 switch (rc) {
212 case -ENOENT: /* no such file/media or currently busy, */
213 case -EBUSY: /* but service seems to be available */
214 rc = 0;
215 break;
216 default: /* leave 'rc' as it is for [0, -EPERM, -E...] */
217 if (rc > 0)
218 rc = 0; /* clear length (success) */
219 break;
220 } /* switch */
221 out:
222 free_page((unsigned long) ftp.buf);
223 return rc;
224 }
225 EXPORT_SYMBOL(hmcdrv_ftp_probe);
226
227 /**
228 * hmcdrv_ftp_cmd() - Perform a HMC drive FTP, with data from user-space
229 *
230 * @cmd: FTP command string "<cmd> <filename>"
231 * @offset: file position to read/write
232 * @buf: user-space buffer for read/written directory/file
233 * @len: size of @buf (read/dir) or number of bytes to write
234 *
235 * This function must not be called before hmcdrv_ftp_startup() was called.
236 *
237 * Return: number of bytes read/written or a negative error code
238 */
hmcdrv_ftp_cmd(char __kernel * cmd,loff_t offset,char __user * buf,size_t len)239 ssize_t hmcdrv_ftp_cmd(char __kernel *cmd, loff_t offset,
240 char __user *buf, size_t len)
241 {
242 int order;
243
244 struct hmcdrv_ftp_cmdspec ftp = {.len = len, .ofs = offset};
245 ssize_t retlen = hmcdrv_ftp_parse(cmd, &ftp);
246
247 if (retlen)
248 return retlen;
249
250 order = get_order(ftp.len);
251 ftp.buf = (void *) __get_free_pages(GFP_KERNEL | GFP_DMA, order);
252
253 if (!ftp.buf)
254 return -ENOMEM;
255
256 switch (ftp.id) {
257 case HMCDRV_FTP_DIR:
258 case HMCDRV_FTP_NLIST:
259 case HMCDRV_FTP_GET:
260 retlen = hmcdrv_ftp_do(&ftp);
261
262 if ((retlen >= 0) &&
263 copy_to_user(buf, ftp.buf, retlen))
264 retlen = -EFAULT;
265 break;
266
267 case HMCDRV_FTP_PUT:
268 case HMCDRV_FTP_APPEND:
269 if (!copy_from_user(ftp.buf, buf, ftp.len))
270 retlen = hmcdrv_ftp_do(&ftp);
271 else
272 retlen = -EFAULT;
273 break;
274
275 case HMCDRV_FTP_DELETE:
276 retlen = hmcdrv_ftp_do(&ftp);
277 break;
278
279 default:
280 retlen = -EOPNOTSUPP;
281 break;
282 }
283
284 free_pages((unsigned long) ftp.buf, order);
285 return retlen;
286 }
287
288 /**
289 * hmcdrv_ftp_startup() - startup of HMC drive FTP functionality for a
290 * dedicated (owner) instance
291 *
292 * Return: 0 on success, else an (negative) error code
293 */
hmcdrv_ftp_startup(void)294 int hmcdrv_ftp_startup(void)
295 {
296 static const struct hmcdrv_ftp_ops hmcdrv_ftp_zvm = {
297 .startup = diag_ftp_startup,
298 .shutdown = diag_ftp_shutdown,
299 .transfer = diag_ftp_cmd
300 };
301
302 static const struct hmcdrv_ftp_ops hmcdrv_ftp_lpar = {
303 .startup = sclp_ftp_startup,
304 .shutdown = sclp_ftp_shutdown,
305 .transfer = sclp_ftp_cmd
306 };
307
308 int rc = 0;
309
310 mutex_lock(&hmcdrv_ftp_mutex); /* block transfers while start-up */
311
312 if (hmcdrv_ftp_refcnt == 0) {
313 if (machine_is_vm())
314 hmcdrv_ftp_funcs = &hmcdrv_ftp_zvm;
315 else if (machine_is_lpar() || machine_is_kvm())
316 hmcdrv_ftp_funcs = &hmcdrv_ftp_lpar;
317 else
318 rc = -EOPNOTSUPP;
319
320 if (hmcdrv_ftp_funcs)
321 rc = hmcdrv_ftp_funcs->startup();
322 }
323
324 if (!rc)
325 ++hmcdrv_ftp_refcnt;
326
327 mutex_unlock(&hmcdrv_ftp_mutex);
328 return rc;
329 }
330 EXPORT_SYMBOL(hmcdrv_ftp_startup);
331
332 /**
333 * hmcdrv_ftp_shutdown() - shutdown of HMC drive FTP functionality for a
334 * dedicated (owner) instance
335 */
hmcdrv_ftp_shutdown(void)336 void hmcdrv_ftp_shutdown(void)
337 {
338 mutex_lock(&hmcdrv_ftp_mutex);
339 --hmcdrv_ftp_refcnt;
340
341 if ((hmcdrv_ftp_refcnt == 0) && hmcdrv_ftp_funcs)
342 hmcdrv_ftp_funcs->shutdown();
343
344 mutex_unlock(&hmcdrv_ftp_mutex);
345 }
346 EXPORT_SYMBOL(hmcdrv_ftp_shutdown);
347