| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * SCLP Event Type (ET) 7 - Diagnostic Test FTP Services, useable on LPAR |
| * |
| * Copyright IBM Corp. 2013 |
| * Author(s): Ralf Hoppe (rhoppe@de.ibm.com) |
| * |
| */ |
| |
| #define KMSG_COMPONENT "hmcdrv" |
| #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
| |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/wait.h> |
| #include <linux/string.h> |
| #include <linux/jiffies.h> |
| #include <asm/sysinfo.h> |
| #include <asm/ebcdic.h> |
| |
| #include "sclp.h" |
| #include "sclp_diag.h" |
| #include "sclp_ftp.h" |
| |
| static DECLARE_COMPLETION(sclp_ftp_rx_complete); |
| static u8 sclp_ftp_ldflg; |
| static u64 sclp_ftp_fsize; |
| static u64 sclp_ftp_length; |
| |
| /** |
| * sclp_ftp_txcb() - Diagnostic Test FTP services SCLP command callback |
| * @req: sclp request |
| * @data: pointer to struct completion |
| */ |
| static void sclp_ftp_txcb(struct sclp_req *req, void *data) |
| { |
| struct completion *completion = data; |
| |
| #ifdef DEBUG |
| pr_debug("SCLP (ET7) TX-IRQ, SCCB @ 0x%p: %*phN\n", |
| req->sccb, 24, req->sccb); |
| #endif |
| complete(completion); |
| } |
| |
| /** |
| * sclp_ftp_rxcb() - Diagnostic Test FTP services receiver event callback |
| * @evbuf: pointer to Diagnostic Test (ET7) event buffer |
| */ |
| static void sclp_ftp_rxcb(struct evbuf_header *evbuf) |
| { |
| struct sclp_diag_evbuf *diag = (struct sclp_diag_evbuf *) evbuf; |
| |
| /* |
| * Check for Diagnostic Test FTP Service |
| */ |
| if (evbuf->type != EVTYP_DIAG_TEST || |
| diag->route != SCLP_DIAG_FTP_ROUTE || |
| diag->mdd.ftp.pcx != SCLP_DIAG_FTP_XPCX || |
| evbuf->length < SCLP_DIAG_FTP_EVBUF_LEN) |
| return; |
| |
| #ifdef DEBUG |
| pr_debug("SCLP (ET7) RX-IRQ, Event @ 0x%p: %*phN\n", |
| evbuf, 24, evbuf); |
| #endif |
| |
| /* |
| * Because the event buffer is located in a page which is owned |
| * by the SCLP core, all data of interest must be copied. The |
| * error indication is in 'sclp_ftp_ldflg' |
| */ |
| sclp_ftp_ldflg = diag->mdd.ftp.ldflg; |
| sclp_ftp_fsize = diag->mdd.ftp.fsize; |
| sclp_ftp_length = diag->mdd.ftp.length; |
| |
| complete(&sclp_ftp_rx_complete); |
| } |
| |
| /** |
| * sclp_ftp_et7() - start a Diagnostic Test FTP Service SCLP request |
| * @ftp: pointer to FTP descriptor |
| * |
| * Return: 0 on success, else a (negative) error code |
| */ |
| static int sclp_ftp_et7(const struct hmcdrv_ftp_cmdspec *ftp) |
| { |
| struct completion completion; |
| struct sclp_diag_sccb *sccb; |
| struct sclp_req *req; |
| size_t len; |
| int rc; |
| |
| req = kzalloc(sizeof(*req), GFP_KERNEL); |
| sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); |
| if (!req || !sccb) { |
| rc = -ENOMEM; |
| goto out_free; |
| } |
| |
| sccb->hdr.length = SCLP_DIAG_FTP_EVBUF_LEN + |
| sizeof(struct sccb_header); |
| sccb->evbuf.hdr.type = EVTYP_DIAG_TEST; |
| sccb->evbuf.hdr.length = SCLP_DIAG_FTP_EVBUF_LEN; |
| sccb->evbuf.hdr.flags = 0; /* clear processed-buffer */ |
| sccb->evbuf.route = SCLP_DIAG_FTP_ROUTE; |
| sccb->evbuf.mdd.ftp.pcx = SCLP_DIAG_FTP_XPCX; |
| sccb->evbuf.mdd.ftp.srcflg = 0; |
| sccb->evbuf.mdd.ftp.pgsize = 0; |
| sccb->evbuf.mdd.ftp.asce = _ASCE_REAL_SPACE; |
| sccb->evbuf.mdd.ftp.ldflg = SCLP_DIAG_FTP_LDFAIL; |
| sccb->evbuf.mdd.ftp.fsize = 0; |
| sccb->evbuf.mdd.ftp.cmd = ftp->id; |
| sccb->evbuf.mdd.ftp.offset = ftp->ofs; |
| sccb->evbuf.mdd.ftp.length = ftp->len; |
| sccb->evbuf.mdd.ftp.bufaddr = virt_to_phys(ftp->buf); |
| |
| len = strlcpy(sccb->evbuf.mdd.ftp.fident, ftp->fname, |
| HMCDRV_FTP_FIDENT_MAX); |
| if (len >= HMCDRV_FTP_FIDENT_MAX) { |
| rc = -EINVAL; |
| goto out_free; |
| } |
| |
| req->command = SCLP_CMDW_WRITE_EVENT_DATA; |
| req->sccb = sccb; |
| req->status = SCLP_REQ_FILLED; |
| req->callback = sclp_ftp_txcb; |
| req->callback_data = &completion; |
| |
| init_completion(&completion); |
| |
| rc = sclp_add_request(req); |
| if (rc) |
| goto out_free; |
| |
| /* Wait for end of ftp sclp command. */ |
| wait_for_completion(&completion); |
| |
| #ifdef DEBUG |
| pr_debug("status of SCLP (ET7) request is 0x%04x (0x%02x)\n", |
| sccb->hdr.response_code, sccb->evbuf.hdr.flags); |
| #endif |
| |
| /* |
| * Check if sclp accepted the request. The data transfer runs |
| * asynchronously and the completion is indicated with an |
| * sclp ET7 event. |
| */ |
| if (req->status != SCLP_REQ_DONE || |
| (sccb->evbuf.hdr.flags & 0x80) == 0 || /* processed-buffer */ |
| (sccb->hdr.response_code & 0xffU) != 0x20U) { |
| rc = -EIO; |
| } |
| |
| out_free: |
| free_page((unsigned long) sccb); |
| kfree(req); |
| return rc; |
| } |
| |
| /** |
| * sclp_ftp_cmd() - executes a HMC related SCLP Diagnose (ET7) FTP command |
| * @ftp: pointer to FTP command specification |
| * @fsize: return of file size (or NULL if undesirable) |
| * |
| * Attention: Notice that this function is not reentrant - so the caller |
| * must ensure locking. |
| * |
| * Return: number of bytes read/written or a (negative) error code |
| */ |
| ssize_t sclp_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize) |
| { |
| ssize_t len; |
| #ifdef DEBUG |
| unsigned long start_jiffies; |
| |
| pr_debug("starting SCLP (ET7), cmd %d for '%s' at %lld with %zd bytes\n", |
| ftp->id, ftp->fname, (long long) ftp->ofs, ftp->len); |
| start_jiffies = jiffies; |
| #endif |
| |
| init_completion(&sclp_ftp_rx_complete); |
| |
| /* Start ftp sclp command. */ |
| len = sclp_ftp_et7(ftp); |
| if (len) |
| goto out_unlock; |
| |
| /* |
| * There is no way to cancel the sclp ET7 request, the code |
| * needs to wait unconditionally until the transfer is complete. |
| */ |
| wait_for_completion(&sclp_ftp_rx_complete); |
| |
| #ifdef DEBUG |
| pr_debug("completed SCLP (ET7) request after %lu ms (all)\n", |
| (jiffies - start_jiffies) * 1000 / HZ); |
| pr_debug("return code of SCLP (ET7) FTP Service is 0x%02x, with %lld/%lld bytes\n", |
| sclp_ftp_ldflg, sclp_ftp_length, sclp_ftp_fsize); |
| #endif |
| |
| switch (sclp_ftp_ldflg) { |
| case SCLP_DIAG_FTP_OK: |
| len = sclp_ftp_length; |
| if (fsize) |
| *fsize = sclp_ftp_fsize; |
| break; |
| case SCLP_DIAG_FTP_LDNPERM: |
| len = -EPERM; |
| break; |
| case SCLP_DIAG_FTP_LDRUNS: |
| len = -EBUSY; |
| break; |
| case SCLP_DIAG_FTP_LDFAIL: |
| len = -ENOENT; |
| break; |
| default: |
| len = -EIO; |
| break; |
| } |
| |
| out_unlock: |
| return len; |
| } |
| |
| /* |
| * ET7 event listener |
| */ |
| static struct sclp_register sclp_ftp_event = { |
| .send_mask = EVTYP_DIAG_TEST_MASK, /* want tx events */ |
| .receive_mask = EVTYP_DIAG_TEST_MASK, /* want rx events */ |
| .receiver_fn = sclp_ftp_rxcb, /* async callback (rx) */ |
| .state_change_fn = NULL, |
| }; |
| |
| /** |
| * sclp_ftp_startup() - startup of FTP services, when running on LPAR |
| */ |
| int sclp_ftp_startup(void) |
| { |
| #ifdef DEBUG |
| unsigned long info; |
| #endif |
| int rc; |
| |
| rc = sclp_register(&sclp_ftp_event); |
| if (rc) |
| return rc; |
| |
| #ifdef DEBUG |
| info = get_zeroed_page(GFP_KERNEL); |
| |
| if (info != 0) { |
| struct sysinfo_2_2_2 *info222 = (struct sysinfo_2_2_2 *)info; |
| |
| if (!stsi(info222, 2, 2, 2)) { /* get SYSIB 2.2.2 */ |
| info222->name[sizeof(info222->name) - 1] = '\0'; |
| EBCASC_500(info222->name, sizeof(info222->name) - 1); |
| pr_debug("SCLP (ET7) FTP Service working on LPAR %u (%s)\n", |
| info222->lpar_number, info222->name); |
| } |
| |
| free_page(info); |
| } |
| #endif /* DEBUG */ |
| return 0; |
| } |
| |
| /** |
| * sclp_ftp_shutdown() - shutdown of FTP services, when running on LPAR |
| */ |
| void sclp_ftp_shutdown(void) |
| { |
| sclp_unregister(&sclp_ftp_event); |
| } |