Ralf Hoppe | 8f933b1 | 2013-04-08 09:52:57 +0200 | [diff] [blame] | 1 | /* |
| 2 | * SCLP Event Type (ET) 7 - Diagnostic Test FTP Services, useable on LPAR |
| 3 | * |
| 4 | * Copyright IBM Corp. 2013 |
| 5 | * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) |
| 6 | * |
| 7 | */ |
| 8 | |
| 9 | #define KMSG_COMPONENT "hmcdrv" |
| 10 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
| 11 | |
| 12 | #include <linux/kernel.h> |
| 13 | #include <linux/mm.h> |
| 14 | #include <linux/slab.h> |
| 15 | #include <linux/io.h> |
| 16 | #include <linux/wait.h> |
| 17 | #include <linux/string.h> |
| 18 | #include <linux/jiffies.h> |
| 19 | #include <asm/sysinfo.h> |
| 20 | #include <asm/ebcdic.h> |
| 21 | |
| 22 | #include "sclp.h" |
| 23 | #include "sclp_diag.h" |
| 24 | #include "sclp_ftp.h" |
| 25 | |
| 26 | static DECLARE_COMPLETION(sclp_ftp_rx_complete); |
| 27 | static u8 sclp_ftp_ldflg; |
| 28 | static u64 sclp_ftp_fsize; |
| 29 | static u64 sclp_ftp_length; |
| 30 | |
| 31 | /** |
| 32 | * sclp_ftp_txcb() - Diagnostic Test FTP services SCLP command callback |
| 33 | */ |
| 34 | static void sclp_ftp_txcb(struct sclp_req *req, void *data) |
| 35 | { |
| 36 | struct completion *completion = data; |
| 37 | |
| 38 | #ifdef DEBUG |
| 39 | pr_debug("SCLP (ET7) TX-IRQ, SCCB @ 0x%p: %*phN\n", |
| 40 | req->sccb, 24, req->sccb); |
| 41 | #endif |
| 42 | complete(completion); |
| 43 | } |
| 44 | |
| 45 | /** |
| 46 | * sclp_ftp_rxcb() - Diagnostic Test FTP services receiver event callback |
| 47 | */ |
| 48 | static void sclp_ftp_rxcb(struct evbuf_header *evbuf) |
| 49 | { |
| 50 | struct sclp_diag_evbuf *diag = (struct sclp_diag_evbuf *) evbuf; |
| 51 | |
| 52 | /* |
| 53 | * Check for Diagnostic Test FTP Service |
| 54 | */ |
| 55 | if (evbuf->type != EVTYP_DIAG_TEST || |
| 56 | diag->route != SCLP_DIAG_FTP_ROUTE || |
| 57 | diag->mdd.ftp.pcx != SCLP_DIAG_FTP_XPCX || |
| 58 | evbuf->length < SCLP_DIAG_FTP_EVBUF_LEN) |
| 59 | return; |
| 60 | |
| 61 | #ifdef DEBUG |
| 62 | pr_debug("SCLP (ET7) RX-IRQ, Event @ 0x%p: %*phN\n", |
| 63 | evbuf, 24, evbuf); |
| 64 | #endif |
| 65 | |
| 66 | /* |
| 67 | * Because the event buffer is located in a page which is owned |
| 68 | * by the SCLP core, all data of interest must be copied. The |
| 69 | * error indication is in 'sclp_ftp_ldflg' |
| 70 | */ |
| 71 | sclp_ftp_ldflg = diag->mdd.ftp.ldflg; |
| 72 | sclp_ftp_fsize = diag->mdd.ftp.fsize; |
| 73 | sclp_ftp_length = diag->mdd.ftp.length; |
| 74 | |
| 75 | complete(&sclp_ftp_rx_complete); |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * sclp_ftp_et7() - start a Diagnostic Test FTP Service SCLP request |
| 80 | * @ftp: pointer to FTP descriptor |
| 81 | * |
| 82 | * Return: 0 on success, else a (negative) error code |
| 83 | */ |
| 84 | static int sclp_ftp_et7(const struct hmcdrv_ftp_cmdspec *ftp) |
| 85 | { |
| 86 | struct completion completion; |
| 87 | struct sclp_diag_sccb *sccb; |
| 88 | struct sclp_req *req; |
| 89 | size_t len; |
| 90 | int rc; |
| 91 | |
| 92 | req = kzalloc(sizeof(*req), GFP_KERNEL); |
| 93 | sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); |
| 94 | if (!req || !sccb) { |
| 95 | rc = -ENOMEM; |
| 96 | goto out_free; |
| 97 | } |
| 98 | |
| 99 | sccb->hdr.length = SCLP_DIAG_FTP_EVBUF_LEN + |
| 100 | sizeof(struct sccb_header); |
| 101 | sccb->evbuf.hdr.type = EVTYP_DIAG_TEST; |
| 102 | sccb->evbuf.hdr.length = SCLP_DIAG_FTP_EVBUF_LEN; |
| 103 | sccb->evbuf.hdr.flags = 0; /* clear processed-buffer */ |
| 104 | sccb->evbuf.route = SCLP_DIAG_FTP_ROUTE; |
| 105 | sccb->evbuf.mdd.ftp.pcx = SCLP_DIAG_FTP_XPCX; |
| 106 | sccb->evbuf.mdd.ftp.srcflg = 0; |
| 107 | sccb->evbuf.mdd.ftp.pgsize = 0; |
| 108 | sccb->evbuf.mdd.ftp.asce = _ASCE_REAL_SPACE; |
| 109 | sccb->evbuf.mdd.ftp.ldflg = SCLP_DIAG_FTP_LDFAIL; |
| 110 | sccb->evbuf.mdd.ftp.fsize = 0; |
| 111 | sccb->evbuf.mdd.ftp.cmd = ftp->id; |
| 112 | sccb->evbuf.mdd.ftp.offset = ftp->ofs; |
| 113 | sccb->evbuf.mdd.ftp.length = ftp->len; |
| 114 | sccb->evbuf.mdd.ftp.bufaddr = virt_to_phys(ftp->buf); |
| 115 | |
| 116 | len = strlcpy(sccb->evbuf.mdd.ftp.fident, ftp->fname, |
| 117 | HMCDRV_FTP_FIDENT_MAX); |
| 118 | if (len >= HMCDRV_FTP_FIDENT_MAX) { |
| 119 | rc = -EINVAL; |
| 120 | goto out_free; |
| 121 | } |
| 122 | |
| 123 | req->command = SCLP_CMDW_WRITE_EVENT_DATA; |
| 124 | req->sccb = sccb; |
| 125 | req->status = SCLP_REQ_FILLED; |
| 126 | req->callback = sclp_ftp_txcb; |
| 127 | req->callback_data = &completion; |
| 128 | |
| 129 | init_completion(&completion); |
| 130 | |
| 131 | rc = sclp_add_request(req); |
| 132 | if (rc) |
| 133 | goto out_free; |
| 134 | |
| 135 | /* Wait for end of ftp sclp command. */ |
| 136 | wait_for_completion(&completion); |
| 137 | |
| 138 | #ifdef DEBUG |
| 139 | pr_debug("status of SCLP (ET7) request is 0x%04x (0x%02x)\n", |
| 140 | sccb->hdr.response_code, sccb->evbuf.hdr.flags); |
| 141 | #endif |
| 142 | |
| 143 | /* |
| 144 | * Check if sclp accepted the request. The data transfer runs |
| 145 | * asynchronously and the completion is indicated with an |
| 146 | * sclp ET7 event. |
| 147 | */ |
| 148 | if (req->status != SCLP_REQ_DONE || |
| 149 | (sccb->evbuf.hdr.flags & 0x80) == 0 || /* processed-buffer */ |
| 150 | (sccb->hdr.response_code & 0xffU) != 0x20U) { |
| 151 | rc = -EIO; |
| 152 | } |
| 153 | |
| 154 | out_free: |
| 155 | free_page((unsigned long) sccb); |
| 156 | kfree(req); |
| 157 | return rc; |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * sclp_ftp_cmd() - executes a HMC related SCLP Diagnose (ET7) FTP command |
| 162 | * @ftp: pointer to FTP command specification |
| 163 | * @fsize: return of file size (or NULL if undesirable) |
| 164 | * |
| 165 | * Attention: Notice that this function is not reentrant - so the caller |
| 166 | * must ensure locking. |
| 167 | * |
| 168 | * Return: number of bytes read/written or a (negative) error code |
| 169 | */ |
| 170 | ssize_t sclp_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize) |
| 171 | { |
| 172 | ssize_t len; |
| 173 | #ifdef DEBUG |
| 174 | unsigned long start_jiffies; |
| 175 | |
| 176 | pr_debug("starting SCLP (ET7), cmd %d for '%s' at %lld with %zd bytes\n", |
| 177 | ftp->id, ftp->fname, (long long) ftp->ofs, ftp->len); |
| 178 | start_jiffies = jiffies; |
| 179 | #endif |
| 180 | |
| 181 | init_completion(&sclp_ftp_rx_complete); |
| 182 | |
| 183 | /* Start ftp sclp command. */ |
| 184 | len = sclp_ftp_et7(ftp); |
| 185 | if (len) |
| 186 | goto out_unlock; |
| 187 | |
| 188 | /* |
| 189 | * There is no way to cancel the sclp ET7 request, the code |
| 190 | * needs to wait unconditionally until the transfer is complete. |
| 191 | */ |
| 192 | wait_for_completion(&sclp_ftp_rx_complete); |
| 193 | |
| 194 | #ifdef DEBUG |
| 195 | pr_debug("completed SCLP (ET7) request after %lu ms (all)\n", |
| 196 | (jiffies - start_jiffies) * 1000 / HZ); |
| 197 | pr_debug("return code of SCLP (ET7) FTP Service is 0x%02x, with %lld/%lld bytes\n", |
| 198 | sclp_ftp_ldflg, sclp_ftp_length, sclp_ftp_fsize); |
| 199 | #endif |
| 200 | |
| 201 | switch (sclp_ftp_ldflg) { |
| 202 | case SCLP_DIAG_FTP_OK: |
| 203 | len = sclp_ftp_length; |
| 204 | if (fsize) |
| 205 | *fsize = sclp_ftp_fsize; |
| 206 | break; |
| 207 | case SCLP_DIAG_FTP_LDNPERM: |
| 208 | len = -EPERM; |
| 209 | break; |
| 210 | case SCLP_DIAG_FTP_LDRUNS: |
| 211 | len = -EBUSY; |
| 212 | break; |
| 213 | case SCLP_DIAG_FTP_LDFAIL: |
| 214 | len = -ENOENT; |
| 215 | break; |
| 216 | default: |
| 217 | len = -EIO; |
| 218 | break; |
| 219 | } |
| 220 | |
| 221 | out_unlock: |
| 222 | return len; |
| 223 | } |
| 224 | |
| 225 | /* |
| 226 | * ET7 event listener |
| 227 | */ |
| 228 | static struct sclp_register sclp_ftp_event = { |
| 229 | .send_mask = EVTYP_DIAG_TEST_MASK, /* want tx events */ |
| 230 | .receive_mask = EVTYP_DIAG_TEST_MASK, /* want rx events */ |
| 231 | .receiver_fn = sclp_ftp_rxcb, /* async callback (rx) */ |
| 232 | .state_change_fn = NULL, |
| 233 | .pm_event_fn = NULL, |
| 234 | }; |
| 235 | |
| 236 | /** |
| 237 | * sclp_ftp_startup() - startup of FTP services, when running on LPAR |
| 238 | */ |
| 239 | int sclp_ftp_startup(void) |
| 240 | { |
| 241 | #ifdef DEBUG |
| 242 | unsigned long info; |
| 243 | #endif |
| 244 | int rc; |
| 245 | |
| 246 | rc = sclp_register(&sclp_ftp_event); |
| 247 | if (rc) |
| 248 | return rc; |
| 249 | |
| 250 | #ifdef DEBUG |
| 251 | info = get_zeroed_page(GFP_KERNEL); |
| 252 | |
| 253 | if (info != 0) { |
| 254 | struct sysinfo_2_2_2 *info222 = (struct sysinfo_2_2_2 *)info; |
| 255 | |
| 256 | if (!stsi(info222, 2, 2, 2)) { /* get SYSIB 2.2.2 */ |
| 257 | info222->name[sizeof(info222->name) - 1] = '\0'; |
| 258 | EBCASC_500(info222->name, sizeof(info222->name) - 1); |
| 259 | pr_debug("SCLP (ET7) FTP Service working on LPAR %u (%s)\n", |
| 260 | info222->lpar_number, info222->name); |
| 261 | } |
| 262 | |
| 263 | free_page(info); |
| 264 | } |
| 265 | #endif /* DEBUG */ |
| 266 | return 0; |
| 267 | } |
| 268 | |
| 269 | /** |
| 270 | * sclp_ftp_shutdown() - shutdown of FTP services, when running on LPAR |
| 271 | */ |
| 272 | void sclp_ftp_shutdown(void) |
| 273 | { |
| 274 | sclp_unregister(&sclp_ftp_event); |
| 275 | } |