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