Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / s390 / cio / ccwreq.c
diff --git a/kernel/drivers/s390/cio/ccwreq.c b/kernel/drivers/s390/cio/ccwreq.c
new file mode 100644 (file)
index 0000000..79f5991
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+ *  Handling of internal CCW device requests.
+ *
+ *    Copyright IBM Corp. 2009, 2011
+ *    Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ */
+
+#define KMSG_COMPONENT "cio"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/types.h>
+#include <linux/err.h>
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+
+#include "io_sch.h"
+#include "cio.h"
+#include "device.h"
+#include "cio_debug.h"
+
+/**
+ * lpm_adjust - adjust path mask
+ * @lpm: path mask to adjust
+ * @mask: mask of available paths
+ *
+ * Shift @lpm right until @lpm and @mask have at least one bit in common or
+ * until @lpm is zero. Return the resulting lpm.
+ */
+int lpm_adjust(int lpm, int mask)
+{
+       while (lpm && ((lpm & mask) == 0))
+               lpm >>= 1;
+       return lpm;
+}
+
+/*
+ * Adjust path mask to use next path and reset retry count. Return resulting
+ * path mask.
+ */
+static u16 ccwreq_next_path(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+
+       if (!req->singlepath) {
+               req->mask = 0;
+               goto out;
+       }
+       req->retries    = req->maxretries;
+       req->mask       = lpm_adjust(req->mask >> 1, req->lpm);
+out:
+       return req->mask;
+}
+
+/*
+ * Clean up device state and report to callback.
+ */
+static void ccwreq_stop(struct ccw_device *cdev, int rc)
+{
+       struct ccw_request *req = &cdev->private->req;
+
+       if (req->done)
+               return;
+       req->done = 1;
+       ccw_device_set_timeout(cdev, 0);
+       memset(&cdev->private->irb, 0, sizeof(struct irb));
+       if (rc && rc != -ENODEV && req->drc)
+               rc = req->drc;
+       req->callback(cdev, req->data, rc);
+}
+
+/*
+ * (Re-)Start the operation until retries and paths are exhausted.
+ */
+static void ccwreq_do(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw1 *cp = req->cp;
+       int rc = -EACCES;
+
+       while (req->mask) {
+               if (req->retries-- == 0) {
+                       /* Retries exhausted, try next path. */
+                       ccwreq_next_path(cdev);
+                       continue;
+               }
+               /* Perform start function. */
+               memset(&cdev->private->irb, 0, sizeof(struct irb));
+               rc = cio_start(sch, cp, (u8) req->mask);
+               if (rc == 0) {
+                       /* I/O started successfully. */
+                       ccw_device_set_timeout(cdev, req->timeout);
+                       return;
+               }
+               if (rc == -ENODEV) {
+                       /* Permanent device error. */
+                       break;
+               }
+               if (rc == -EACCES) {
+                       /* Permant path error. */
+                       ccwreq_next_path(cdev);
+                       continue;
+               }
+               /* Temporary improper status. */
+               rc = cio_clear(sch);
+               if (rc)
+                       break;
+               return;
+       }
+       ccwreq_stop(cdev, rc);
+}
+
+/**
+ * ccw_request_start - perform I/O request
+ * @cdev: ccw device
+ *
+ * Perform the I/O request specified by cdev->req.
+ */
+void ccw_request_start(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+
+       if (req->singlepath) {
+               /* Try all paths twice to counter link flapping. */
+               req->mask = 0x8080;
+       } else
+               req->mask = req->lpm;
+
+       req->retries    = req->maxretries;
+       req->mask       = lpm_adjust(req->mask, req->lpm);
+       req->drc        = 0;
+       req->done       = 0;
+       req->cancel     = 0;
+       if (!req->mask)
+               goto out_nopath;
+       ccwreq_do(cdev);
+       return;
+
+out_nopath:
+       ccwreq_stop(cdev, -EACCES);
+}
+
+/**
+ * ccw_request_cancel - cancel running I/O request
+ * @cdev: ccw device
+ *
+ * Cancel the I/O request specified by cdev->req. Return non-zero if request
+ * has already finished, zero otherwise.
+ */
+int ccw_request_cancel(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+       int rc;
+
+       if (req->done)
+               return 1;
+       req->cancel = 1;
+       rc = cio_clear(sch);
+       if (rc)
+               ccwreq_stop(cdev, rc);
+       return 0;
+}
+
+/*
+ * Return the status of the internal I/O started on the specified ccw device.
+ * Perform BASIC SENSE if required.
+ */
+static enum io_status ccwreq_status(struct ccw_device *cdev, struct irb *lcirb)
+{
+       struct irb *irb = &cdev->private->irb;
+       struct cmd_scsw *scsw = &irb->scsw.cmd;
+       enum uc_todo todo;
+
+       /* Perform BASIC SENSE if needed. */
+       if (ccw_device_accumulate_and_sense(cdev, lcirb))
+               return IO_RUNNING;
+       /* Check for halt/clear interrupt. */
+       if (scsw->fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
+               return IO_KILLED;
+       /* Check for path error. */
+       if (scsw->cc == 3 || scsw->pno)
+               return IO_PATH_ERROR;
+       /* Handle BASIC SENSE data. */
+       if (irb->esw.esw0.erw.cons) {
+               CIO_TRACE_EVENT(2, "sensedata");
+               CIO_HEX_EVENT(2, &cdev->private->dev_id,
+                             sizeof(struct ccw_dev_id));
+               CIO_HEX_EVENT(2, &cdev->private->irb.ecw, SENSE_MAX_COUNT);
+               /* Check for command reject. */
+               if (irb->ecw[0] & SNS0_CMD_REJECT)
+                       return IO_REJECTED;
+               /* Ask the driver what to do */
+               if (cdev->drv && cdev->drv->uc_handler) {
+                       todo = cdev->drv->uc_handler(cdev, lcirb);
+                       CIO_TRACE_EVENT(2, "uc_response");
+                       CIO_HEX_EVENT(2, &todo, sizeof(todo));
+                       switch (todo) {
+                       case UC_TODO_RETRY:
+                               return IO_STATUS_ERROR;
+                       case UC_TODO_RETRY_ON_NEW_PATH:
+                               return IO_PATH_ERROR;
+                       case UC_TODO_STOP:
+                               return IO_REJECTED;
+                       default:
+                               return IO_STATUS_ERROR;
+                       }
+               }
+               /* Assume that unexpected SENSE data implies an error. */
+               return IO_STATUS_ERROR;
+       }
+       /* Check for channel errors. */
+       if (scsw->cstat != 0)
+               return IO_STATUS_ERROR;
+       /* Check for device errors. */
+       if (scsw->dstat & ~(DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+               return IO_STATUS_ERROR;
+       /* Check for final state. */
+       if (!(scsw->dstat & DEV_STAT_DEV_END))
+               return IO_RUNNING;
+       /* Check for other improper status. */
+       if (scsw->cc == 1 && (scsw->stctl & SCSW_STCTL_ALERT_STATUS))
+               return IO_STATUS_ERROR;
+       return IO_DONE;
+}
+
+/*
+ * Log ccw request status.
+ */
+static void ccwreq_log_status(struct ccw_device *cdev, enum io_status status)
+{
+       struct ccw_request *req = &cdev->private->req;
+       struct {
+               struct ccw_dev_id dev_id;
+               u16 retries;
+               u8 lpm;
+               u8 status;
+       }  __attribute__ ((packed)) data;
+       data.dev_id     = cdev->private->dev_id;
+       data.retries    = req->retries;
+       data.lpm        = (u8) req->mask;
+       data.status     = (u8) status;
+       CIO_TRACE_EVENT(2, "reqstat");
+       CIO_HEX_EVENT(2, &data, sizeof(data));
+}
+
+/**
+ * ccw_request_handler - interrupt handler for I/O request procedure.
+ * @cdev: ccw device
+ *
+ * Handle interrupt during I/O request procedure.
+ */
+void ccw_request_handler(struct ccw_device *cdev)
+{
+       struct irb *irb = this_cpu_ptr(&cio_irb);
+       struct ccw_request *req = &cdev->private->req;
+       enum io_status status;
+       int rc = -EOPNOTSUPP;
+
+       /* Check status of I/O request. */
+       status = ccwreq_status(cdev, irb);
+       if (req->filter)
+               status = req->filter(cdev, req->data, irb, status);
+       if (status != IO_RUNNING)
+               ccw_device_set_timeout(cdev, 0);
+       if (status != IO_DONE && status != IO_RUNNING)
+               ccwreq_log_status(cdev, status);
+       switch (status) {
+       case IO_DONE:
+               break;
+       case IO_RUNNING:
+               return;
+       case IO_REJECTED:
+               goto err;
+       case IO_PATH_ERROR:
+               goto out_next_path;
+       case IO_STATUS_ERROR:
+               goto out_restart;
+       case IO_KILLED:
+               /* Check if request was cancelled on purpose. */
+               if (req->cancel) {
+                       rc = -EIO;
+                       goto err;
+               }
+               goto out_restart;
+       }
+       /* Check back with request initiator. */
+       if (!req->check)
+               goto out;
+       switch (req->check(cdev, req->data)) {
+       case 0:
+               break;
+       case -EAGAIN:
+               goto out_restart;
+       case -EACCES:
+               goto out_next_path;
+       default:
+               goto err;
+       }
+out:
+       ccwreq_stop(cdev, 0);
+       return;
+
+out_next_path:
+       /* Try next path and restart I/O. */
+       if (!ccwreq_next_path(cdev)) {
+               rc = -EACCES;
+               goto err;
+       }
+out_restart:
+       /* Restart. */
+       ccwreq_do(cdev);
+       return;
+err:
+       ccwreq_stop(cdev, rc);
+}
+
+
+/**
+ * ccw_request_timeout - timeout handler for I/O request procedure
+ * @cdev: ccw device
+ *
+ * Handle timeout during I/O request procedure.
+ */
+void ccw_request_timeout(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+       int rc = -ENODEV, chp;
+
+       if (cio_update_schib(sch))
+               goto err;
+
+       for (chp = 0; chp < 8; chp++) {
+               if ((0x80 >> chp) & sch->schib.pmcw.lpum)
+                       pr_warning("%s: No interrupt was received within %lus "
+                                  "(CS=%02x, DS=%02x, CHPID=%x.%02x)\n",
+                                  dev_name(&cdev->dev), req->timeout / HZ,
+                                  scsw_cstat(&sch->schib.scsw),
+                                  scsw_dstat(&sch->schib.scsw),
+                                  sch->schid.cssid,
+                                  sch->schib.pmcw.chpid[chp]);
+       }
+
+       if (!ccwreq_next_path(cdev)) {
+               /* set the final return code for this request */
+               req->drc = -ETIME;
+       }
+       rc = cio_clear(sch);
+       if (rc)
+               goto err;
+       return;
+
+err:
+       ccwreq_stop(cdev, rc);
+}
+
+/**
+ * ccw_request_notoper - notoper handler for I/O request procedure
+ * @cdev: ccw device
+ *
+ * Handle notoper during I/O request procedure.
+ */
+void ccw_request_notoper(struct ccw_device *cdev)
+{
+       ccwreq_stop(cdev, -ENODEV);
+}