Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / s390 / char / sclp_ftp.c
diff --git a/kernel/drivers/s390/char/sclp_ftp.c b/kernel/drivers/s390/char/sclp_ftp.c
new file mode 100644 (file)
index 0000000..6561cc5
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ *    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);
+}