Commit | Line | Data |
---|---|---|
8f933b10 RH |
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 | ||
c967e1df | 40 | static const struct hmcdrv_ftp_ops *hmcdrv_ftp_funcs; /* current operations */ |
8f933b10 RH |
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) | |
2ec50493 | 203 | goto out; |
8f933b10 RH |
204 | |
205 | rc = hmcdrv_ftp_do(&ftp); | |
8f933b10 RH |
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 */ | |
2ec50493 CJ |
218 | out: |
219 | free_page((unsigned long) ftp.buf); | |
8f933b10 RH |
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 | { | |
c967e1df | 293 | static const struct hmcdrv_ftp_ops hmcdrv_ftp_zvm = { |
8f933b10 RH |
294 | .startup = diag_ftp_startup, |
295 | .shutdown = diag_ftp_shutdown, | |
296 | .transfer = diag_ftp_cmd | |
297 | }; | |
298 | ||
c967e1df | 299 | static const struct hmcdrv_ftp_ops hmcdrv_ftp_lpar = { |
8f933b10 RH |
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); |