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