|  | // 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 | 
|  | */ | 
|  | 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 | 
|  | */ | 
|  | 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, | 
|  | .pm_event_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); | 
|  | } |