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