vfio: ccw: handle ccw command request
authorDong Jia Shi <bjsdjshi@linux.vnet.ibm.com>
Fri, 17 Mar 2017 03:17:35 +0000 (04:17 +0100)
committerCornelia Huck <cornelia.huck@de.ibm.com>
Fri, 31 Mar 2017 10:55:07 +0000 (12:55 +0200)
We implement the basic ccw command handling infrastructure
here:
1. Translate the ccw commands.
2. Issue the translated ccw commands to the device.
3. Once we get the execution result, update the guest SCSW
   with it.

Acked-by: Pierre Morel <pmorel@linux.vnet.ibm.com>
Signed-off-by: Dong Jia Shi <bjsdjshi@linux.vnet.ibm.com>
Message-Id: <20170317031743.40128-9-bjsdjshi@linux.vnet.ibm.com>
Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
drivers/s390/cio/vfio_ccw_drv.c
drivers/s390/cio/vfio_ccw_ops.c
drivers/s390/cio/vfio_ccw_private.h

index b1f430aa9bdb001270cb8c47c9748c465d4cb3c2..b5971d3216977c1f6abb4c98f3a510b2be661e0f 100644 (file)
 #include <linux/init.h>
 #include <linux/device.h>
 #include <linux/slab.h>
+#include <linux/uuid.h>
+#include <linux/mdev.h>
 
 #include <asm/isc.h>
 
+#include "ioasm.h"
+#include "css.h"
 #include "vfio_ccw_private.h"
 
 /*
@@ -59,6 +63,109 @@ out_unlock:
        return ret;
 }
 
+static int doing_io(struct vfio_ccw_private *private, u32 intparm)
+{
+       return (private->intparm == intparm);
+}
+
+static int vfio_ccw_sch_io_helper(struct vfio_ccw_private *private)
+{
+       struct subchannel *sch;
+       union orb *orb;
+       int ccode;
+       __u8 lpm;
+       u32 intparm;
+
+       sch = private->sch;
+
+       orb = cp_get_orb(&private->cp, (u32)(addr_t)sch, sch->lpm);
+
+       /* Issue "Start Subchannel" */
+       ccode = ssch(sch->schid, orb);
+
+       switch (ccode) {
+       case 0:
+               /*
+                * Initialize device status information
+                */
+               sch->schib.scsw.cmd.actl |= SCSW_ACTL_START_PEND;
+               break;
+       case 1:         /* Status pending */
+       case 2:         /* Busy */
+               return -EBUSY;
+       case 3:         /* Device/path not operational */
+       {
+               lpm = orb->cmd.lpm;
+               if (lpm != 0)
+                       sch->lpm &= ~lpm;
+               else
+                       sch->lpm = 0;
+
+               if (cio_update_schib(sch))
+                       return -ENODEV;
+
+               return sch->lpm ? -EACCES : -ENODEV;
+       }
+       default:
+               return ccode;
+       }
+
+       intparm = (u32)(addr_t)sch;
+       private->intparm = 0;
+       wait_event(private->wait_q, doing_io(private, intparm));
+
+       if (scsw_is_solicited(&private->irb.scsw))
+               cp_update_scsw(&private->cp, &private->irb.scsw);
+
+       return 0;
+}
+
+/* Deal with the ccw command request from the userspace. */
+int vfio_ccw_sch_cmd_request(struct vfio_ccw_private *private)
+{
+       struct mdev_device *mdev = private->mdev;
+       union orb *orb;
+       union scsw *scsw = &private->scsw;
+       struct irb *irb = &private->irb;
+       struct ccw_io_region *io_region = &private->io_region;
+       int ret;
+
+       memcpy(scsw, io_region->scsw_area, sizeof(*scsw));
+
+       if (scsw->cmd.fctl & SCSW_FCTL_START_FUNC) {
+               orb = (union orb *)io_region->orb_area;
+
+               ret = cp_init(&private->cp, mdev_dev(mdev), orb);
+               if (ret)
+                       return ret;
+
+               ret = cp_prefetch(&private->cp);
+               if (ret) {
+                       cp_free(&private->cp);
+                       return ret;
+               }
+
+               /* Start channel program and wait for I/O interrupt. */
+               ret = vfio_ccw_sch_io_helper(private);
+               if (!ret) {
+                       /* Get irb info and copy it to irb_area. */
+                       memcpy(io_region->irb_area, irb, sizeof(*irb));
+               }
+
+               cp_free(&private->cp);
+       } else if (scsw->cmd.fctl & SCSW_FCTL_HALT_FUNC) {
+               /* XXX: Handle halt. */
+               ret = -EOPNOTSUPP;
+       } else if (scsw->cmd.fctl & SCSW_FCTL_CLEAR_FUNC) {
+               /* XXX: Handle clear. */
+               ret = -EOPNOTSUPP;
+       } else {
+               ret = -EOPNOTSUPP;
+       }
+
+       return ret;
+}
+
 /*
  * Sysfs interfaces
  */
@@ -113,12 +220,18 @@ static struct attribute_group vfio_subchannel_attr_group = {
 static void vfio_ccw_sch_irq(struct subchannel *sch)
 {
        struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev);
+       struct irb *irb;
 
        inc_irq_stat(IRQIO_CIO);
 
        if (!private)
                return;
 
+       irb = this_cpu_ptr(&cio_irb);
+       memcpy(&private->irb, irb, sizeof(*irb));
+       private->intparm = (u32)(addr_t)sch;
+       wake_up(&private->wait_q);
+
        if (private->completion)
                complete(private->completion);
 }
@@ -156,6 +269,7 @@ static int vfio_ccw_sch_probe(struct subchannel *sch)
        if (ret)
                goto out_rm_group;
 
+       init_waitqueue_head(&private->wait_q);
        atomic_set(&private->avail, 1);
 
        return 0;
index 6c06805839d88e262f89089b265cc587327a8e2a..878c88239fc802dd6a56b2d5e4387d1b247fe3ef 100644 (file)
@@ -23,12 +23,25 @@ static int vfio_ccw_mdev_notifier(struct notifier_block *nb,
                return NOTIFY_STOP;
 
        /*
-        * TODO:
         * Vendor drivers MUST unpin pages in response to an
         * invalidation.
         */
-       if (action == VFIO_IOMMU_NOTIFY_DMA_UNMAP)
-               return NOTIFY_BAD;
+       if (action == VFIO_IOMMU_NOTIFY_DMA_UNMAP) {
+               struct vfio_iommu_type1_dma_unmap *unmap = data;
+               struct subchannel *sch = private->sch;
+
+               if (!cp_iova_pinned(&private->cp, unmap->iova))
+                       return NOTIFY_OK;
+
+               if (vfio_ccw_sch_quiesce(sch))
+                       return NOTIFY_BAD;
+
+               if (cio_enable_subchannel(sch, (u32)(unsigned long)sch))
+                       return NOTIFY_BAD;
+
+               cp_free(&private->cp);
+               return NOTIFY_OK;
+       }
 
        return NOTIFY_DONE;
 }
@@ -167,7 +180,10 @@ static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev,
        region = &private->io_region;
        if (copy_from_user((void *)region + *ppos, buf, count))
                return -EFAULT;
-       region->ret_code = 0;
+
+       region->ret_code = vfio_ccw_sch_cmd_request(private);
+       if (region->ret_code != 0)
+               return region->ret_code;
 
        return count;
 }
index 359e96ba9c6c8d8abe076ad882ca9691bd151ab4..79e53378f212191fa2057d61982478f9c80589ff 100644 (file)
 #ifndef _VFIO_CCW_PRIVATE_H_
 #define _VFIO_CCW_PRIVATE_H_
 
+#include <linux/completion.h>
 #include <linux/vfio_ccw.h>
 
 #include "css.h"
+#include "vfio_ccw_cp.h"
 
 /**
  * struct vfio_ccw_private
  * @mdev: pointer to the mediated device
  * @nb: notifier for vfio events
  * @io_region: MMIO region to input/output I/O arguments/results
+ * @wait_q: wait for interrupt
+ * @intparm: record current interrupt parameter, used for wait interrupt
+ * @cp: channel program for the current I/O operation
+ * @irb: irb info received from interrupt
+ * @scsw: scsw info
  */
 struct vfio_ccw_private {
        struct subchannel       *sch;
@@ -30,11 +37,18 @@ struct vfio_ccw_private {
        struct mdev_device      *mdev;
        struct notifier_block   nb;
        struct ccw_io_region    io_region;
+
+       wait_queue_head_t       wait_q;
+       u32                     intparm;
+       struct channel_program  cp;
+       struct irb              irb;
+       union scsw              scsw;
 } __aligned(8);
 
 extern int vfio_ccw_mdev_reg(struct subchannel *sch);
 extern void vfio_ccw_mdev_unreg(struct subchannel *sch);
 
 extern int vfio_ccw_sch_quiesce(struct subchannel *sch);
+extern int vfio_ccw_sch_cmd_request(struct vfio_ccw_private *private);
 
 #endif