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 */ 30struct 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 37static enum hmcdrv_ftp_cmdid hmcdrv_ftp_cmd_getid(const char *cmd, int len); 38static int hmcdrv_ftp_parse(char *cmd, struct hmcdrv_ftp_cmdspec *ftp); 39 40static struct hmcdrv_ftp_ops *hmcdrv_ftp_funcs; /* current operations */ 41static DEFINE_MUTEX(hmcdrv_ftp_mutex); /* mutex for hmcdrv_ftp_funcs */ 42static 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 */ 49static 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 */ 111static 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 */ 160ssize_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} 177EXPORT_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 */ 184int 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 */ 218out: 219 free_page((unsigned long) ftp.buf); 220 return rc; 221} 222EXPORT_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 */ 236ssize_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 */ 291int 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} 327EXPORT_SYMBOL(hmcdrv_ftp_startup); 328 329/** 330 * hmcdrv_ftp_shutdown() - shutdown of HMC drive FTP functionality for a 331 * dedicated (owner) instance 332 */ 333void 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} 343EXPORT_SYMBOL(hmcdrv_ftp_shutdown); 344