Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / s390 / char / diag_ftp.c
diff --git a/kernel/drivers/s390/char/diag_ftp.c b/kernel/drivers/s390/char/diag_ftp.c
new file mode 100644 (file)
index 0000000..9388963
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ *    DIAGNOSE X'2C4' instruction based HMC FTP services, useable on z/VM
+ *
+ *    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/irq.h>
+#include <linux/wait.h>
+#include <linux/string.h>
+#include <asm/ctl_reg.h>
+
+#include "hmcdrv_ftp.h"
+#include "diag_ftp.h"
+
+/* DIAGNOSE X'2C4' return codes in Ry */
+#define DIAG_FTP_RET_OK        0 /* HMC FTP started successfully */
+#define DIAG_FTP_RET_EBUSY     4 /* HMC FTP service currently busy */
+#define DIAG_FTP_RET_EIO       8 /* HMC FTP service I/O error */
+/* and an artificial extension */
+#define DIAG_FTP_RET_EPERM     2 /* HMC FTP service privilege error */
+
+/* FTP service status codes (after INTR at guest real location 133) */
+#define DIAG_FTP_STAT_OK       0U /* request completed successfully */
+#define DIAG_FTP_STAT_PGCC     4U /* program check condition */
+#define DIAG_FTP_STAT_PGIOE    8U /* paging I/O error */
+#define DIAG_FTP_STAT_TIMEOUT  12U /* timeout */
+#define DIAG_FTP_STAT_EBASE    16U /* base of error codes from SCLP */
+#define DIAG_FTP_STAT_LDFAIL   (DIAG_FTP_STAT_EBASE + 1U) /* failed */
+#define DIAG_FTP_STAT_LDNPERM  (DIAG_FTP_STAT_EBASE + 2U) /* not allowed */
+#define DIAG_FTP_STAT_LDRUNS   (DIAG_FTP_STAT_EBASE + 3U) /* runs */
+#define DIAG_FTP_STAT_LDNRUNS  (DIAG_FTP_STAT_EBASE + 4U) /* not runs */
+
+/**
+ * struct diag_ftp_ldfpl - load file FTP parameter list (LDFPL)
+ * @bufaddr: real buffer address (at 4k boundary)
+ * @buflen: length of buffer
+ * @offset: dir/file offset
+ * @intparm: interruption parameter (unused)
+ * @transferred: bytes transferred
+ * @fsize: file size, filled on GET
+ * @failaddr: failing address
+ * @spare: padding
+ * @fident: file name - ASCII
+ */
+struct diag_ftp_ldfpl {
+       u64 bufaddr;
+       u64 buflen;
+       u64 offset;
+       u64 intparm;
+       u64 transferred;
+       u64 fsize;
+       u64 failaddr;
+       u64 spare;
+       u8 fident[HMCDRV_FTP_FIDENT_MAX];
+} __packed;
+
+static DECLARE_COMPLETION(diag_ftp_rx_complete);
+static int diag_ftp_subcode;
+
+/**
+ * diag_ftp_handler() - FTP services IRQ handler
+ * @extirq: external interrupt (sub-) code
+ * @param32: 32-bit interruption parameter from &struct diag_ftp_ldfpl
+ * @param64: unused (for 64-bit interrupt parameters)
+ */
+static void diag_ftp_handler(struct ext_code extirq,
+                            unsigned int param32,
+                            unsigned long param64)
+{
+       if ((extirq.subcode >> 8) != 8)
+               return; /* not a FTP services sub-code */
+
+       inc_irq_stat(IRQEXT_FTP);
+       diag_ftp_subcode = extirq.subcode & 0xffU;
+       complete(&diag_ftp_rx_complete);
+}
+
+/**
+ * diag_ftp_2c4() - DIAGNOSE X'2C4' service call
+ * @fpl: pointer to prepared LDFPL
+ * @cmd: FTP command to be executed
+ *
+ * Performs a DIAGNOSE X'2C4' call with (input/output) FTP parameter list
+ * @fpl and FTP function code @cmd. In case of an error the function does
+ * nothing and returns an (negative) error code.
+ *
+ * Notes:
+ * 1. This function only initiates a transfer, so the caller must wait
+ *    for completion (asynchronous execution).
+ * 2. The FTP parameter list @fpl must be aligned to a double-word boundary.
+ * 3. fpl->bufaddr must be a real address, 4k aligned
+ */
+static int diag_ftp_2c4(struct diag_ftp_ldfpl *fpl,
+                       enum hmcdrv_ftp_cmdid cmd)
+{
+       int rc;
+
+       asm volatile(
+               "       diag    %[addr],%[cmd],0x2c4\n"
+               "0:     j       2f\n"
+               "1:     la      %[rc],%[err]\n"
+               "2:\n"
+               EX_TABLE(0b, 1b)
+               : [rc] "=d" (rc), "+m" (*fpl)
+               : [cmd] "0" (cmd), [addr] "d" (virt_to_phys(fpl)),
+                 [err] "i" (DIAG_FTP_RET_EPERM)
+               : "cc");
+
+       switch (rc) {
+       case DIAG_FTP_RET_OK:
+               return 0;
+       case DIAG_FTP_RET_EBUSY:
+               return -EBUSY;
+       case DIAG_FTP_RET_EPERM:
+               return -EPERM;
+       case DIAG_FTP_RET_EIO:
+       default:
+               return -EIO;
+       }
+}
+
+/**
+ * diag_ftp_cmd() - executes a DIAG X'2C4' FTP command, targeting a HMC
+ * @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 diag_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize)
+{
+       struct diag_ftp_ldfpl *ldfpl;
+       ssize_t len;
+#ifdef DEBUG
+       unsigned long start_jiffies;
+
+       pr_debug("starting DIAG X'2C4' on '%s', requesting %zd bytes\n",
+                ftp->fname, ftp->len);
+       start_jiffies = jiffies;
+#endif
+       init_completion(&diag_ftp_rx_complete);
+
+       ldfpl = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
+       if (!ldfpl) {
+               len = -ENOMEM;
+               goto out;
+       }
+
+       len = strlcpy(ldfpl->fident, ftp->fname, sizeof(ldfpl->fident));
+       if (len >= HMCDRV_FTP_FIDENT_MAX) {
+               len = -EINVAL;
+               goto out_free;
+       }
+
+       ldfpl->transferred = 0;
+       ldfpl->fsize = 0;
+       ldfpl->offset = ftp->ofs;
+       ldfpl->buflen = ftp->len;
+       ldfpl->bufaddr = virt_to_phys(ftp->buf);
+
+       len = diag_ftp_2c4(ldfpl, ftp->id);
+       if (len)
+               goto out_free;
+
+       /*
+        * There is no way to cancel the running diag X'2C4', the code
+        * needs to wait unconditionally until the transfer is complete.
+        */
+       wait_for_completion(&diag_ftp_rx_complete);
+
+#ifdef DEBUG
+       pr_debug("completed DIAG X'2C4' after %lu ms\n",
+                (jiffies - start_jiffies) * 1000 / HZ);
+       pr_debug("status of DIAG X'2C4' is %u, with %lld/%lld bytes\n",
+                diag_ftp_subcode, ldfpl->transferred, ldfpl->fsize);
+#endif
+
+       switch (diag_ftp_subcode) {
+       case DIAG_FTP_STAT_OK: /* success */
+               len = ldfpl->transferred;
+               if (fsize)
+                       *fsize = ldfpl->fsize;
+               break;
+       case DIAG_FTP_STAT_LDNPERM:
+               len = -EPERM;
+               break;
+       case DIAG_FTP_STAT_LDRUNS:
+               len = -EBUSY;
+               break;
+       case DIAG_FTP_STAT_LDFAIL:
+               len = -ENOENT; /* no such file or media */
+               break;
+       default:
+               len = -EIO;
+               break;
+       }
+
+out_free:
+       free_page((unsigned long) ldfpl);
+out:
+       return len;
+}
+
+/**
+ * diag_ftp_startup() - startup of FTP services, when running on z/VM
+ *
+ * Return: 0 on success, else an (negative) error code
+ */
+int diag_ftp_startup(void)
+{
+       int rc;
+
+       rc = register_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler);
+       if (rc)
+               return rc;
+
+       ctl_set_bit(0, 63 - 22);
+       return 0;
+}
+
+/**
+ * diag_ftp_shutdown() - shutdown of FTP services, when running on z/VM
+ */
+void diag_ftp_shutdown(void)
+{
+       ctl_clear_bit(0, 63 - 22);
+       unregister_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler);
+}