hmcdrv_ftp.c (8185B)
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 */ 31struct 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 38static enum hmcdrv_ftp_cmdid hmcdrv_ftp_cmd_getid(const char *cmd, int len); 39static int hmcdrv_ftp_parse(char *cmd, struct hmcdrv_ftp_cmdspec *ftp); 40 41static const struct hmcdrv_ftp_ops *hmcdrv_ftp_funcs; /* current operations */ 42static DEFINE_MUTEX(hmcdrv_ftp_mutex); /* mutex for hmcdrv_ftp_funcs */ 43static 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 */ 50static 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, 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 */ 112static 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(start, 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 */ 161ssize_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, hmcdrv_ftp_funcs->transfer); 171 } else { 172 len = -ENXIO; 173 } 174 175 mutex_unlock(&hmcdrv_ftp_mutex); 176 return len; 177} 178EXPORT_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 */ 185int 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 */ 219out: 220 free_page((unsigned long) ftp.buf); 221 return rc; 222} 223EXPORT_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 */ 237ssize_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); 244 245 if (retlen) 246 return retlen; 247 248 order = get_order(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(buf, ftp.buf, retlen)) 262 retlen = -EFAULT; 263 break; 264 265 case HMCDRV_FTP_PUT: 266 case HMCDRV_FTP_APPEND: 267 if (!copy_from_user(ftp.buf, buf, 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((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 */ 292int 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(&hmcdrv_ftp_mutex); 326 return rc; 327} 328EXPORT_SYMBOL(hmcdrv_ftp_startup); 329 330/** 331 * hmcdrv_ftp_shutdown() - shutdown of HMC drive FTP functionality for a 332 * dedicated (owner) instance 333 */ 334void 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(&hmcdrv_ftp_mutex); 343} 344EXPORT_SYMBOL(hmcdrv_ftp_shutdown);