From 84cd8fc48478b5e67b3f1600717299e11430a67e Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 17 Mar 2017 04:17:33 +0100 Subject: vfio: ccw: register vfio_ccw to the mediated device framework To make vfio support subchannel devices, we need to leverage the mediated device framework to create a mediated device for the subchannel device. This registers the subchannel device to the mediated device framework during probe to enable mediated device creation. Reviewed-by: Pierre Morel Signed-off-by: Dong Jia Shi Message-Id: <20170317031743.40128-7-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_ops.c | 147 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 drivers/s390/cio/vfio_ccw_ops.c (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c new file mode 100644 index 000000000000..b8a2fed58f02 --- /dev/null +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -0,0 +1,147 @@ +/* + * Physical device callbacks for vfio_ccw + * + * Copyright IBM Corp. 2017 + * + * Author(s): Dong Jia Shi + * Xiao Feng Ren + */ + +#include +#include + +#include "vfio_ccw_private.h" + +static int vfio_ccw_mdev_notifier(struct notifier_block *nb, + unsigned long action, + void *data) +{ + struct vfio_ccw_private *private = + container_of(nb, struct vfio_ccw_private, nb); + + if (!private) + return NOTIFY_STOP; + + /* + * TODO: + * Vendor drivers MUST unpin pages in response to an + * invalidation. + */ + if (action == VFIO_IOMMU_NOTIFY_DMA_UNMAP) + return NOTIFY_BAD; + + return NOTIFY_DONE; +} + +static ssize_t name_show(struct kobject *kobj, struct device *dev, char *buf) +{ + return sprintf(buf, "I/O subchannel (Non-QDIO)\n"); +} +MDEV_TYPE_ATTR_RO(name); + +static ssize_t device_api_show(struct kobject *kobj, struct device *dev, + char *buf) +{ + return sprintf(buf, "%s\n", VFIO_DEVICE_API_CCW_STRING); +} +MDEV_TYPE_ATTR_RO(device_api); + +static ssize_t available_instances_show(struct kobject *kobj, + struct device *dev, char *buf) +{ + struct vfio_ccw_private *private = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", atomic_read(&private->avail)); +} +MDEV_TYPE_ATTR_RO(available_instances); + +static struct attribute *mdev_types_attrs[] = { + &mdev_type_attr_name.attr, + &mdev_type_attr_device_api.attr, + &mdev_type_attr_available_instances.attr, + NULL, +}; + +static struct attribute_group mdev_type_group = { + .name = "io", + .attrs = mdev_types_attrs, +}; + +struct attribute_group *mdev_type_groups[] = { + &mdev_type_group, + NULL, +}; + +static int vfio_ccw_mdev_create(struct kobject *kobj, struct mdev_device *mdev) +{ + struct vfio_ccw_private *private = + dev_get_drvdata(mdev_parent_dev(mdev)); + + if (atomic_dec_if_positive(&private->avail) < 0) + return -EPERM; + + private->mdev = mdev; + + return 0; +} + +static int vfio_ccw_mdev_remove(struct mdev_device *mdev) +{ + struct vfio_ccw_private *private; + struct subchannel *sch; + int ret; + + private = dev_get_drvdata(mdev_parent_dev(mdev)); + sch = private->sch; + ret = vfio_ccw_sch_quiesce(sch); + if (ret) + return ret; + ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch); + if (ret) + return ret; + + private->mdev = NULL; + atomic_inc(&private->avail); + + return 0; +} + +static int vfio_ccw_mdev_open(struct mdev_device *mdev) +{ + struct vfio_ccw_private *private = + dev_get_drvdata(mdev_parent_dev(mdev)); + unsigned long events = VFIO_IOMMU_NOTIFY_DMA_UNMAP; + + private->nb.notifier_call = vfio_ccw_mdev_notifier; + + return vfio_register_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY, + &events, &private->nb); +} + +void vfio_ccw_mdev_release(struct mdev_device *mdev) +{ + struct vfio_ccw_private *private = + dev_get_drvdata(mdev_parent_dev(mdev)); + + vfio_unregister_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY, + &private->nb); +} + +static const struct mdev_parent_ops vfio_ccw_mdev_ops = { + .owner = THIS_MODULE, + .supported_type_groups = mdev_type_groups, + .create = vfio_ccw_mdev_create, + .remove = vfio_ccw_mdev_remove, + .open = vfio_ccw_mdev_open, + .release = vfio_ccw_mdev_release, +}; + +int vfio_ccw_mdev_reg(struct subchannel *sch) +{ + return mdev_register_device(&sch->dev, &vfio_ccw_mdev_ops); +} + +void vfio_ccw_mdev_unreg(struct subchannel *sch) +{ + mdev_unregister_device(&sch->dev); +} -- cgit v1.2.3-55-g7522 From 060d2b5afcc4f9e2d61e2b059e648f569b8dba9a Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 17 Mar 2017 04:17:34 +0100 Subject: vfio: ccw: introduce ccw_io_region To provide user-space a set of interfaces to: 1. pass in a ccw program to perform an I/O operation. 2. read back I/O results of the completed I/O operations. We introduce an MMIO region for the vfio-ccw device here. This region is defined to content: 1. areas to store arguments that an ssch required. 2. areas to store the I/O results. Using pwrite/pread to the device on this region, a user-space program could write/read data to/from the vfio-ccw device. Reviewed-by: Pierre Morel Signed-off-by: Dong Jia Shi Message-Id: <20170317031743.40128-8-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_ops.c | 47 +++++++++++++++++++++++++++++++++++++ drivers/s390/cio/vfio_ccw_private.h | 4 ++++ include/uapi/linux/vfio_ccw.h | 24 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 include/uapi/linux/vfio_ccw.h (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index b8a2fed58f02..6c06805839d8 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -127,6 +127,51 @@ void vfio_ccw_mdev_release(struct mdev_device *mdev) &private->nb); } +static ssize_t vfio_ccw_mdev_read(struct mdev_device *mdev, + char __user *buf, + size_t count, + loff_t *ppos) +{ + struct vfio_ccw_private *private; + struct ccw_io_region *region; + + if (*ppos + count > sizeof(*region)) + return -EINVAL; + + private = dev_get_drvdata(mdev_parent_dev(mdev)); + if (!private) + return -ENODEV; + + region = &private->io_region; + if (copy_to_user(buf, (void *)region + *ppos, count)) + return -EFAULT; + + return count; +} + +static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + struct vfio_ccw_private *private; + struct ccw_io_region *region; + + if (*ppos + count > sizeof(*region)) + return -EINVAL; + + private = dev_get_drvdata(mdev_parent_dev(mdev)); + if (!private) + return -ENODEV; + + region = &private->io_region; + if (copy_from_user((void *)region + *ppos, buf, count)) + return -EFAULT; + region->ret_code = 0; + + return count; +} + static const struct mdev_parent_ops vfio_ccw_mdev_ops = { .owner = THIS_MODULE, .supported_type_groups = mdev_type_groups, @@ -134,6 +179,8 @@ static const struct mdev_parent_ops vfio_ccw_mdev_ops = { .remove = vfio_ccw_mdev_remove, .open = vfio_ccw_mdev_open, .release = vfio_ccw_mdev_release, + .read = vfio_ccw_mdev_read, + .write = vfio_ccw_mdev_write, }; int vfio_ccw_mdev_reg(struct subchannel *sch) diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 5afb3ba5c7b5..359e96ba9c6c 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -10,6 +10,8 @@ #ifndef _VFIO_CCW_PRIVATE_H_ #define _VFIO_CCW_PRIVATE_H_ +#include + #include "css.h" /** @@ -19,6 +21,7 @@ * @avail: available for creating a mediated device * @mdev: pointer to the mediated device * @nb: notifier for vfio events + * @io_region: MMIO region to input/output I/O arguments/results */ struct vfio_ccw_private { struct subchannel *sch; @@ -26,6 +29,7 @@ struct vfio_ccw_private { atomic_t avail; struct mdev_device *mdev; struct notifier_block nb; + struct ccw_io_region io_region; } __aligned(8); extern int vfio_ccw_mdev_reg(struct subchannel *sch); diff --git a/include/uapi/linux/vfio_ccw.h b/include/uapi/linux/vfio_ccw.h new file mode 100644 index 000000000000..34a7f6f9e065 --- /dev/null +++ b/include/uapi/linux/vfio_ccw.h @@ -0,0 +1,24 @@ +/* + * Interfaces for vfio-ccw + * + * Copyright IBM Corp. 2017 + * + * Author(s): Dong Jia Shi + */ + +#ifndef _VFIO_CCW_H_ +#define _VFIO_CCW_H_ + +#include + +struct ccw_io_region { +#define ORB_AREA_SIZE 12 + __u8 orb_area[ORB_AREA_SIZE]; +#define SCSW_AREA_SIZE 12 + __u8 scsw_area[SCSW_AREA_SIZE]; +#define IRB_AREA_SIZE 96 + __u8 irb_area[IRB_AREA_SIZE]; + __u32 ret_code; +} __packed; + +#endif -- cgit v1.2.3-55-g7522 From 4e149e431a2858b86b4f9c801b2a4dde50c929f9 Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 17 Mar 2017 04:17:35 +0100 Subject: vfio: ccw: handle ccw command request 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 Signed-off-by: Dong Jia Shi Message-Id: <20170317031743.40128-9-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_drv.c | 114 ++++++++++++++++++++++++++++++++++++ drivers/s390/cio/vfio_ccw_ops.c | 24 ++++++-- drivers/s390/cio/vfio_ccw_private.h | 14 +++++ 3 files changed, 148 insertions(+), 4 deletions(-) (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index b1f430aa9bdb..b5971d321697 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -11,9 +11,13 @@ #include #include #include +#include +#include #include +#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; diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 6c06805839d8..878c88239fc8 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -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; } diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 359e96ba9c6c..79e53378f212 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -10,9 +10,11 @@ #ifndef _VFIO_CCW_PRIVATE_H_ #define _VFIO_CCW_PRIVATE_H_ +#include #include #include "css.h" +#include "vfio_ccw_cp.h" /** * struct vfio_ccw_private @@ -22,6 +24,11 @@ * @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 -- cgit v1.2.3-55-g7522 From e01bcdd61320c91c826376e0a7dd96ef8e85dd18 Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 17 Mar 2017 04:17:36 +0100 Subject: vfio: ccw: realize VFIO_DEVICE_GET_REGION_INFO ioctl Introduce device information about vfio-ccw: VFIO_DEVICE_FLAGS_CCW. Realize VFIO_DEVICE_GET_REGION_INFO ioctl for vfio-ccw. Reviewed-by: Pierre Morel Signed-off-by: Dong Jia Shi Acked-by: Alex Williamson Message-Id: <20170317031743.40128-10-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_ops.c | 78 +++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/vfio.h | 11 ++++++ 2 files changed, 89 insertions(+) (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 878c88239fc8..f3300ddded3f 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -188,6 +188,83 @@ static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev, return count; } +static int vfio_ccw_mdev_get_device_info(struct vfio_device_info *info) +{ + info->flags = VFIO_DEVICE_FLAGS_CCW; + info->num_regions = VFIO_CCW_NUM_REGIONS; + info->num_irqs = 0; + + return 0; +} + +static int vfio_ccw_mdev_get_region_info(struct vfio_region_info *info, + u16 *cap_type_id, + void **cap_type) +{ + switch (info->index) { + case VFIO_CCW_CONFIG_REGION_INDEX: + info->offset = 0; + info->size = sizeof(struct ccw_io_region); + info->flags = VFIO_REGION_INFO_FLAG_READ + | VFIO_REGION_INFO_FLAG_WRITE; + return 0; + default: + return -EINVAL; + } +} + +static ssize_t vfio_ccw_mdev_ioctl(struct mdev_device *mdev, + unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + unsigned long minsz; + + switch (cmd) { + case VFIO_DEVICE_GET_INFO: + { + struct vfio_device_info info; + + minsz = offsetofend(struct vfio_device_info, num_irqs); + + if (copy_from_user(&info, (void __user *)arg, minsz)) + return -EFAULT; + + if (info.argsz < minsz) + return -EINVAL; + + ret = vfio_ccw_mdev_get_device_info(&info); + if (ret) + return ret; + + return copy_to_user((void __user *)arg, &info, minsz); + } + case VFIO_DEVICE_GET_REGION_INFO: + { + struct vfio_region_info info; + u16 cap_type_id = 0; + void *cap_type = NULL; + + minsz = offsetofend(struct vfio_region_info, offset); + + if (copy_from_user(&info, (void __user *)arg, minsz)) + return -EFAULT; + + if (info.argsz < minsz) + return -EINVAL; + + ret = vfio_ccw_mdev_get_region_info(&info, &cap_type_id, + &cap_type); + if (ret) + return ret; + + return copy_to_user((void __user *)arg, &info, minsz); + } + default: + return -ENOTTY; + } +} + static const struct mdev_parent_ops vfio_ccw_mdev_ops = { .owner = THIS_MODULE, .supported_type_groups = mdev_type_groups, @@ -197,6 +274,7 @@ static const struct mdev_parent_ops vfio_ccw_mdev_ops = { .release = vfio_ccw_mdev_release, .read = vfio_ccw_mdev_read, .write = vfio_ccw_mdev_write, + .ioctl = vfio_ccw_mdev_ioctl, }; int vfio_ccw_mdev_reg(struct subchannel *sch) diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index 61837890c132..3fd70ffe9d78 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -198,6 +198,7 @@ struct vfio_device_info { #define VFIO_DEVICE_FLAGS_PCI (1 << 1) /* vfio-pci device */ #define VFIO_DEVICE_FLAGS_PLATFORM (1 << 2) /* vfio-platform device */ #define VFIO_DEVICE_FLAGS_AMBA (1 << 3) /* vfio-amba device */ +#define VFIO_DEVICE_FLAGS_CCW (1 << 4) /* vfio-ccw device */ __u32 num_regions; /* Max region index + 1 */ __u32 num_irqs; /* Max IRQ index + 1 */ }; @@ -447,6 +448,16 @@ enum { VFIO_PCI_NUM_IRQS }; +/* + * The vfio-ccw bus driver makes use of the following fixed region. + * Unimplemented regions return a size of zero. + */ + +enum { + VFIO_CCW_CONFIG_REGION_INDEX, + VFIO_CCW_NUM_REGIONS +}; + /** * VFIO_DEVICE_GET_PCI_HOT_RESET_INFO - _IORW(VFIO_TYPE, VFIO_BASE + 12, * struct vfio_pci_hot_reset_info) -- cgit v1.2.3-55-g7522 From 83d1193a96dc78576c15f93cd70e0558763a85b3 Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 17 Mar 2017 04:17:37 +0100 Subject: vfio: ccw: realize VFIO_DEVICE_RESET ioctl Introduce VFIO_DEVICE_RESET ioctl for vfio-ccw to make it possible to hot-reset the device. We try to achieve a reset by first disabling the subchannel and then enabling it again: this should clear all state at the subchannel. Signed-off-by: Dong Jia Shi Message-Id: <20170317031743.40128-11-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_ops.c | 47 +++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 14 deletions(-) (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index f3300ddded3f..125818cdf305 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -12,6 +12,32 @@ #include "vfio_ccw_private.h" +static int vfio_ccw_mdev_reset(struct mdev_device *mdev) +{ + struct vfio_ccw_private *private; + struct subchannel *sch; + int ret; + + private = dev_get_drvdata(mdev_parent_dev(mdev)); + if (!private) + return -ENODEV; + + sch = private->sch; + /* + * TODO: + * In the cureent stage, some things like "no I/O running" and "no + * interrupt pending" are clear, but we are not sure what other state + * we need to care about. + * There are still a lot more instructions need to be handled. We + * should come back here later. + */ + ret = vfio_ccw_sch_quiesce(sch); + if (ret) + return ret; + + return cio_enable_subchannel(sch, (u32)(unsigned long)sch); +} + static int vfio_ccw_mdev_notifier(struct notifier_block *nb, unsigned long action, void *data) @@ -28,15 +54,11 @@ static int vfio_ccw_mdev_notifier(struct notifier_block *nb, */ 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)) + if (vfio_ccw_mdev_reset(private->mdev)) return NOTIFY_BAD; cp_free(&private->cp); @@ -100,16 +122,11 @@ static int vfio_ccw_mdev_create(struct kobject *kobj, struct mdev_device *mdev) static int vfio_ccw_mdev_remove(struct mdev_device *mdev) { - struct vfio_ccw_private *private; - struct subchannel *sch; + struct vfio_ccw_private *private = + dev_get_drvdata(mdev_parent_dev(mdev)); int ret; - private = dev_get_drvdata(mdev_parent_dev(mdev)); - sch = private->sch; - ret = vfio_ccw_sch_quiesce(sch); - if (ret) - return ret; - ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch); + ret = vfio_ccw_mdev_reset(mdev); if (ret) return ret; @@ -190,7 +207,7 @@ static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev, static int vfio_ccw_mdev_get_device_info(struct vfio_device_info *info) { - info->flags = VFIO_DEVICE_FLAGS_CCW; + info->flags = VFIO_DEVICE_FLAGS_CCW | VFIO_DEVICE_FLAGS_RESET; info->num_regions = VFIO_CCW_NUM_REGIONS; info->num_irqs = 0; @@ -260,6 +277,8 @@ static ssize_t vfio_ccw_mdev_ioctl(struct mdev_device *mdev, return copy_to_user((void __user *)arg, &info, minsz); } + case VFIO_DEVICE_RESET: + return vfio_ccw_mdev_reset(mdev); default: return -ENOTTY; } -- cgit v1.2.3-55-g7522 From 120e214e504fd6d3e33ec4b661193600b2faab95 Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 17 Mar 2017 04:17:38 +0100 Subject: vfio: ccw: realize VFIO_DEVICE_G(S)ET_IRQ_INFO ioctls Realize VFIO_DEVICE_GET_IRQ_INFO ioctl to retrieve VFIO_CCW_IO_IRQ information. Realize VFIO_DEVICE_SET_IRQS ioctl to set an eventfd fd for VFIO_CCW_IO_IRQ. Once a write operation to the ccw_io_region was performed, trigger a signal on this fd. Reviewed-by: Pierre Morel Signed-off-by: Dong Jia Shi Acked-by: Alex Williamson Message-Id: <20170317031743.40128-12-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_ops.c | 123 +++++++++++++++++++++++++++++++++++- drivers/s390/cio/vfio_ccw_private.h | 4 ++ include/uapi/linux/vfio.h | 10 ++- 3 files changed, 134 insertions(+), 3 deletions(-) (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 125818cdf305..1294c5347410 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -202,6 +202,9 @@ static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev, if (region->ret_code != 0) return region->ret_code; + if (private->io_trigger) + eventfd_signal(private->io_trigger, 1); + return count; } @@ -209,7 +212,7 @@ static int vfio_ccw_mdev_get_device_info(struct vfio_device_info *info) { info->flags = VFIO_DEVICE_FLAGS_CCW | VFIO_DEVICE_FLAGS_RESET; info->num_regions = VFIO_CCW_NUM_REGIONS; - info->num_irqs = 0; + info->num_irqs = VFIO_CCW_NUM_IRQS; return 0; } @@ -230,6 +233,83 @@ static int vfio_ccw_mdev_get_region_info(struct vfio_region_info *info, } } +int vfio_ccw_mdev_get_irq_info(struct vfio_irq_info *info) +{ + if (info->index != VFIO_CCW_IO_IRQ_INDEX) + return -EINVAL; + + info->count = 1; + info->flags = VFIO_IRQ_INFO_EVENTFD; + + return 0; +} + +static int vfio_ccw_mdev_set_irqs(struct mdev_device *mdev, + uint32_t flags, + void __user *data) +{ + struct vfio_ccw_private *private; + struct eventfd_ctx **ctx; + + if (!(flags & VFIO_IRQ_SET_ACTION_TRIGGER)) + return -EINVAL; + + private = dev_get_drvdata(mdev_parent_dev(mdev)); + if (!private) + return -ENODEV; + + ctx = &private->io_trigger; + + switch (flags & VFIO_IRQ_SET_DATA_TYPE_MASK) { + case VFIO_IRQ_SET_DATA_NONE: + { + if (*ctx) + eventfd_signal(*ctx, 1); + return 0; + } + case VFIO_IRQ_SET_DATA_BOOL: + { + uint8_t trigger; + + if (get_user(trigger, (uint8_t __user *)data)) + return -EFAULT; + + if (trigger && *ctx) + eventfd_signal(*ctx, 1); + return 0; + } + case VFIO_IRQ_SET_DATA_EVENTFD: + { + int32_t fd; + + if (get_user(fd, (int32_t __user *)data)) + return -EFAULT; + + if (fd == -1) { + if (*ctx) + eventfd_ctx_put(*ctx); + *ctx = NULL; + } else if (fd >= 0) { + struct eventfd_ctx *efdctx; + + efdctx = eventfd_ctx_fdget(fd); + if (IS_ERR(efdctx)) + return PTR_ERR(efdctx); + + if (*ctx) + eventfd_ctx_put(*ctx); + + *ctx = efdctx; + } else + return -EINVAL; + + return 0; + } + default: + return -EINVAL; + } +} + static ssize_t vfio_ccw_mdev_ioctl(struct mdev_device *mdev, unsigned int cmd, unsigned long arg) @@ -277,6 +357,47 @@ static ssize_t vfio_ccw_mdev_ioctl(struct mdev_device *mdev, return copy_to_user((void __user *)arg, &info, minsz); } + case VFIO_DEVICE_GET_IRQ_INFO: + { + struct vfio_irq_info info; + + minsz = offsetofend(struct vfio_irq_info, count); + + if (copy_from_user(&info, (void __user *)arg, minsz)) + return -EFAULT; + + if (info.argsz < minsz || info.index >= VFIO_CCW_NUM_IRQS) + return -EINVAL; + + ret = vfio_ccw_mdev_get_irq_info(&info); + if (ret) + return ret; + + if (info.count == -1) + return -EINVAL; + + return copy_to_user((void __user *)arg, &info, minsz); + } + case VFIO_DEVICE_SET_IRQS: + { + struct vfio_irq_set hdr; + size_t data_size; + void __user *data; + + minsz = offsetofend(struct vfio_irq_set, count); + + if (copy_from_user(&hdr, (void __user *)arg, minsz)) + return -EFAULT; + + ret = vfio_set_irqs_validate_and_prepare(&hdr, 1, + VFIO_CCW_NUM_IRQS, + &data_size); + if (ret) + return ret; + + data = (void __user *)(arg + minsz); + return vfio_ccw_mdev_set_irqs(mdev, hdr.flags, data); + } case VFIO_DEVICE_RESET: return vfio_ccw_mdev_reset(mdev); default: diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 79e53378f212..dddab52913ed 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -11,6 +11,7 @@ #define _VFIO_CCW_PRIVATE_H_ #include +#include #include #include "css.h" @@ -29,6 +30,7 @@ * @cp: channel program for the current I/O operation * @irb: irb info received from interrupt * @scsw: scsw info + * @io_trigger: eventfd ctx for signaling userspace I/O results */ struct vfio_ccw_private { struct subchannel *sch; @@ -43,6 +45,8 @@ struct vfio_ccw_private { struct channel_program cp; struct irb irb; union scsw scsw; + + struct eventfd_ctx *io_trigger; } __aligned(8); extern int vfio_ccw_mdev_reg(struct subchannel *sch); diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index 3fd70ffe9d78..ae461050661a 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -449,8 +449,9 @@ enum { }; /* - * The vfio-ccw bus driver makes use of the following fixed region. - * Unimplemented regions return a size of zero. + * The vfio-ccw bus driver makes use of the following fixed region and + * IRQ index mapping. Unimplemented regions return a size of zero. + * Unimplemented IRQ types return a count of zero. */ enum { @@ -458,6 +459,11 @@ enum { VFIO_CCW_NUM_REGIONS }; +enum { + VFIO_CCW_IO_IRQ_INDEX, + VFIO_CCW_NUM_IRQS +}; + /** * VFIO_DEVICE_GET_PCI_HOT_RESET_INFO - _IORW(VFIO_TYPE, VFIO_BASE + 12, * struct vfio_pci_hot_reset_info) -- cgit v1.2.3-55-g7522 From e5f84dbaea59b4f712dac428c337528b70e1c533 Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 17 Mar 2017 04:17:39 +0100 Subject: vfio: ccw: return I/O results asynchronously Introduce a singlethreaded workqueue to handle the I/O interrupts. With the work added to this queue, we store the I/O results to the io_region of the subchannel, then signal the userspace program to handle the results. Signed-off-by: Dong Jia Shi Message-Id: <20170317031743.40128-13-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_drv.c | 58 ++++++++++++++++++++++--------------- drivers/s390/cio/vfio_ccw_ops.c | 3 -- drivers/s390/cio/vfio_ccw_private.h | 7 ++--- 3 files changed, 37 insertions(+), 31 deletions(-) (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index b5971d321697..3e0a40ec8ade 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -20,6 +20,8 @@ #include "css.h" #include "vfio_ccw_private.h" +struct workqueue_struct *vfio_ccw_work_q; + /* * Helpers */ @@ -52,6 +54,7 @@ int vfio_ccw_sch_quiesce(struct subchannel *sch) spin_lock_irq(sch->lock); private->completion = NULL; + flush_workqueue(vfio_ccw_work_q); ret = cio_cancel_halt_clear(sch, &iretry); }; @@ -63,18 +66,12 @@ 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; @@ -89,7 +86,7 @@ static int vfio_ccw_sch_io_helper(struct vfio_ccw_private *private) * Initialize device status information */ sch->schib.scsw.cmd.actl |= SCSW_ACTL_START_PEND; - break; + return 0; case 1: /* Status pending */ case 2: /* Busy */ return -EBUSY; @@ -109,15 +106,26 @@ static int vfio_ccw_sch_io_helper(struct vfio_ccw_private *private) default: return ccode; } +} - intparm = (u32)(addr_t)sch; - private->intparm = 0; - wait_event(private->wait_q, doing_io(private, intparm)); +static void vfio_ccw_sch_io_todo(struct work_struct *work) +{ + struct vfio_ccw_private *private; + struct subchannel *sch; + struct irb *irb; - if (scsw_is_solicited(&private->irb.scsw)) - cp_update_scsw(&private->cp, &private->irb.scsw); + private = container_of(work, struct vfio_ccw_private, io_work); + irb = &private->irb; + sch = private->sch; - return 0; + if (scsw_is_solicited(&irb->scsw)) { + cp_update_scsw(&private->cp, &irb->scsw); + cp_free(&private->cp); + } + memcpy(private->io_region.irb_area, irb, sizeof(*irb)); + + if (private->io_trigger) + eventfd_signal(private->io_trigger, 1); } /* Deal with the ccw command request from the userspace. */ @@ -126,7 +134,6 @@ 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; @@ -147,12 +154,8 @@ int vfio_ccw_sch_cmd_request(struct vfio_ccw_private *private) /* 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); + if (!ret) + cp_free(&private->cp); } else if (scsw->cmd.fctl & SCSW_FCTL_HALT_FUNC) { /* XXX: Handle halt. */ ret = -EOPNOTSUPP; @@ -229,8 +232,8 @@ static void vfio_ccw_sch_irq(struct subchannel *sch) irb = this_cpu_ptr(&cio_irb); memcpy(&private->irb, irb, sizeof(*irb)); - private->intparm = (u32)(addr_t)sch; - wake_up(&private->wait_q); + + queue_work(vfio_ccw_work_q, &private->io_work); if (private->completion) complete(private->completion); @@ -269,7 +272,7 @@ static int vfio_ccw_sch_probe(struct subchannel *sch) if (ret) goto out_rm_group; - init_waitqueue_head(&private->wait_q); + INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo); atomic_set(&private->avail, 1); return 0; @@ -367,10 +370,16 @@ static int __init vfio_ccw_sch_init(void) { int ret; + vfio_ccw_work_q = create_singlethread_workqueue("vfio-ccw"); + if (!vfio_ccw_work_q) + return -ENOMEM; + isc_register(VFIO_CCW_ISC); ret = css_driver_register(&vfio_ccw_sch_driver); - if (ret) + if (ret) { isc_unregister(VFIO_CCW_ISC); + destroy_workqueue(vfio_ccw_work_q); + } return ret; } @@ -379,6 +388,7 @@ static void __exit vfio_ccw_sch_exit(void) { css_driver_unregister(&vfio_ccw_sch_driver); isc_unregister(VFIO_CCW_ISC); + destroy_workqueue(vfio_ccw_work_q); } module_init(vfio_ccw_sch_init); module_exit(vfio_ccw_sch_exit); diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 1294c5347410..d754d3d90574 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -202,9 +202,6 @@ static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev, if (region->ret_code != 0) return region->ret_code; - if (private->io_trigger) - eventfd_signal(private->io_trigger, 1); - return count; } diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index dddab52913ed..2503797fb153 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -12,6 +12,7 @@ #include #include +#include #include #include "css.h" @@ -25,12 +26,11 @@ * @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 * @io_trigger: eventfd ctx for signaling userspace I/O results + * @io_work: work for deferral process of I/O handling */ struct vfio_ccw_private { struct subchannel *sch; @@ -40,13 +40,12 @@ struct vfio_ccw_private { 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; struct eventfd_ctx *io_trigger; + struct work_struct io_work; } __aligned(8); extern int vfio_ccw_mdev_reg(struct subchannel *sch); -- cgit v1.2.3-55-g7522 From bbe37e4cb89702aa78e0f44618c5af7f9aaa33f6 Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 17 Mar 2017 04:17:40 +0100 Subject: vfio: ccw: introduce a finite state machine The current implementation doesn't check if the subchannel is in a proper device state when handling an event. Let's introduce a finite state machine to manage the state/event change. Signed-off-by: Dong Jia Shi Message-Id: <20170317031743.40128-14-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/Makefile | 2 +- drivers/s390/cio/vfio_ccw_drv.c | 116 +++----------------- drivers/s390/cio/vfio_ccw_fsm.c | 207 ++++++++++++++++++++++++++++++++++++ drivers/s390/cio/vfio_ccw_ops.c | 28 ++++- drivers/s390/cio/vfio_ccw_private.h | 41 ++++++- 5 files changed, 287 insertions(+), 107 deletions(-) create mode 100644 drivers/s390/cio/vfio_ccw_fsm.c (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/Makefile b/drivers/s390/cio/Makefile index b0586b223502..bdf47526038a 100644 --- a/drivers/s390/cio/Makefile +++ b/drivers/s390/cio/Makefile @@ -18,5 +18,5 @@ obj-$(CONFIG_CCWGROUP) += ccwgroup.o qdio-objs := qdio_main.o qdio_thinint.o qdio_debug.o qdio_setup.o obj-$(CONFIG_QDIO) += qdio.o -vfio_ccw-objs += vfio_ccw_drv.o vfio_ccw_cp.o vfio_ccw_ops.o +vfio_ccw-objs += vfio_ccw_drv.o vfio_ccw_cp.o vfio_ccw_ops.o vfio_ccw_fsm.o obj-$(CONFIG_VFIO_CCW) += vfio_ccw.o diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index 3e0a40ec8ade..e90dd43d2a55 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -60,54 +60,12 @@ int vfio_ccw_sch_quiesce(struct subchannel *sch) ret = cio_disable_subchannel(sch); } while (ret == -EBUSY); - out_unlock: + private->state = VFIO_CCW_STATE_NOT_OPER; spin_unlock_irq(sch->lock); return ret; } -static int vfio_ccw_sch_io_helper(struct vfio_ccw_private *private) -{ - struct subchannel *sch; - union orb *orb; - int ccode; - __u8 lpm; - - 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; - return 0; - 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; - } -} - static void vfio_ccw_sch_io_todo(struct work_struct *work) { struct vfio_ccw_private *private; @@ -126,47 +84,9 @@ static void vfio_ccw_sch_io_todo(struct work_struct *work) if (private->io_trigger) eventfd_signal(private->io_trigger, 1); -} - -/* 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 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) - 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; + if (private->mdev) + private->state = VFIO_CCW_STATE_IDLE; } /* @@ -223,20 +143,9 @@ 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)); - - queue_work(vfio_ccw_work_q, &private->io_work); - - if (private->completion) - complete(private->completion); + vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_INTERRUPT); } static int vfio_ccw_sch_probe(struct subchannel *sch) @@ -258,6 +167,7 @@ static int vfio_ccw_sch_probe(struct subchannel *sch) dev_set_drvdata(&sch->dev, private); spin_lock_irq(sch->lock); + private->state = VFIO_CCW_STATE_NOT_OPER; sch->isc = VFIO_CCW_ISC; ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch); spin_unlock_irq(sch->lock); @@ -274,6 +184,7 @@ static int vfio_ccw_sch_probe(struct subchannel *sch) INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo); atomic_set(&private->avail, 1); + private->state = VFIO_CCW_STATE_STANDBY; return 0; @@ -321,6 +232,7 @@ static void vfio_ccw_sch_shutdown(struct subchannel *sch) */ static int vfio_ccw_sch_event(struct subchannel *sch, int process) { + struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); unsigned long flags; spin_lock_irqsave(sch->lock, flags); @@ -331,16 +243,16 @@ static int vfio_ccw_sch_event(struct subchannel *sch, int process) goto out_unlock; if (cio_update_schib(sch)) { - /* Not operational. */ - css_sched_sch_todo(sch, SCH_TODO_UNREG); - - /* - * TODO: - * Probably we should send the machine check to the guest. - */ + vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_NOT_OPER); goto out_unlock; } + private = dev_get_drvdata(&sch->dev); + if (private->state == VFIO_CCW_STATE_NOT_OPER) { + private->state = private->mdev ? VFIO_CCW_STATE_IDLE : + VFIO_CCW_STATE_STANDBY; + } + out_unlock: spin_unlock_irqrestore(sch->lock, flags); diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c new file mode 100644 index 000000000000..55b6cc55758e --- /dev/null +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -0,0 +1,207 @@ +/* + * Finite state machine for vfio-ccw device handling + * + * Copyright IBM Corp. 2017 + * + * Author(s): Dong Jia Shi + */ + +#include +#include + +#include "ioasm.h" +#include "vfio_ccw_private.h" + +static int fsm_io_helper(struct vfio_ccw_private *private) +{ + struct subchannel *sch; + union orb *orb; + int ccode; + __u8 lpm; + unsigned long flags; + + sch = private->sch; + + spin_lock_irqsave(sch->lock, flags); + private->state = VFIO_CCW_STATE_BUSY; + spin_unlock_irqrestore(sch->lock, flags); + + 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; + return 0; + 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; + } +} + +static void fsm_notoper(struct vfio_ccw_private *private, + enum vfio_ccw_event event) +{ + struct subchannel *sch = private->sch; + + /* + * TODO: + * Probably we should send the machine check to the guest. + */ + css_sched_sch_todo(sch, SCH_TODO_UNREG); + private->state = VFIO_CCW_STATE_NOT_OPER; +} + +/* + * No operation action. + */ +static void fsm_nop(struct vfio_ccw_private *private, + enum vfio_ccw_event event) +{ +} + +static void fsm_io_error(struct vfio_ccw_private *private, + enum vfio_ccw_event event) +{ + pr_err("vfio-ccw: FSM: I/O request from state:%d\n", private->state); + private->io_region.ret_code = -EIO; +} + +static void fsm_io_busy(struct vfio_ccw_private *private, + enum vfio_ccw_event event) +{ + private->io_region.ret_code = -EBUSY; +} + +static void fsm_disabled_irq(struct vfio_ccw_private *private, + enum vfio_ccw_event event) +{ + struct subchannel *sch = private->sch; + + /* + * An interrupt in a disabled state means a previous disable was not + * successful - should not happen, but we try to disable again. + */ + cio_disable_subchannel(sch); +} + +/* + * Deal with the ccw command request from the userspace. + */ +static void fsm_io_request(struct vfio_ccw_private *private, + enum vfio_ccw_event event) +{ + union orb *orb; + union scsw *scsw = &private->scsw; + struct ccw_io_region *io_region = &private->io_region; + struct mdev_device *mdev = private->mdev; + + private->state = VFIO_CCW_STATE_BOXED; + + memcpy(scsw, io_region->scsw_area, sizeof(*scsw)); + + if (scsw->cmd.fctl & SCSW_FCTL_START_FUNC) { + orb = (union orb *)io_region->orb_area; + + io_region->ret_code = cp_init(&private->cp, mdev_dev(mdev), + orb); + if (io_region->ret_code) + goto err_out; + + io_region->ret_code = cp_prefetch(&private->cp); + if (io_region->ret_code) { + cp_free(&private->cp); + goto err_out; + } + + /* Start channel program and wait for I/O interrupt. */ + io_region->ret_code = fsm_io_helper(private); + if (io_region->ret_code) { + cp_free(&private->cp); + goto err_out; + } + return; + } else if (scsw->cmd.fctl & SCSW_FCTL_HALT_FUNC) { + /* XXX: Handle halt. */ + io_region->ret_code = -EOPNOTSUPP; + goto err_out; + } else if (scsw->cmd.fctl & SCSW_FCTL_CLEAR_FUNC) { + /* XXX: Handle clear. */ + io_region->ret_code = -EOPNOTSUPP; + goto err_out; + } + +err_out: + private->state = VFIO_CCW_STATE_IDLE; +} + +/* + * Got an interrupt for a normal io (state busy). + */ +static void fsm_irq(struct vfio_ccw_private *private, + enum vfio_ccw_event event) +{ + struct irb *irb; + + if (!private) + return; + + irb = this_cpu_ptr(&cio_irb); + memcpy(&private->irb, irb, sizeof(*irb)); + + queue_work(vfio_ccw_work_q, &private->io_work); + + if (private->completion) + complete(private->completion); +} + +/* + * Device statemachine + */ +fsm_func_t *vfio_ccw_jumptable[NR_VFIO_CCW_STATES][NR_VFIO_CCW_EVENTS] = { + [VFIO_CCW_STATE_NOT_OPER] = { + [VFIO_CCW_EVENT_NOT_OPER] = fsm_nop, + [VFIO_CCW_EVENT_IO_REQ] = fsm_io_error, + [VFIO_CCW_EVENT_INTERRUPT] = fsm_disabled_irq, + }, + [VFIO_CCW_STATE_STANDBY] = { + [VFIO_CCW_EVENT_NOT_OPER] = fsm_notoper, + [VFIO_CCW_EVENT_IO_REQ] = fsm_io_error, + [VFIO_CCW_EVENT_INTERRUPT] = fsm_irq, + }, + [VFIO_CCW_STATE_IDLE] = { + [VFIO_CCW_EVENT_NOT_OPER] = fsm_notoper, + [VFIO_CCW_EVENT_IO_REQ] = fsm_io_request, + [VFIO_CCW_EVENT_INTERRUPT] = fsm_irq, + }, + [VFIO_CCW_STATE_BOXED] = { + [VFIO_CCW_EVENT_NOT_OPER] = fsm_notoper, + [VFIO_CCW_EVENT_IO_REQ] = fsm_io_busy, + [VFIO_CCW_EVENT_INTERRUPT] = fsm_irq, + }, + [VFIO_CCW_STATE_BUSY] = { + [VFIO_CCW_EVENT_NOT_OPER] = fsm_notoper, + [VFIO_CCW_EVENT_IO_REQ] = fsm_io_busy, + [VFIO_CCW_EVENT_INTERRUPT] = fsm_irq, + }, +}; diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index d754d3d90574..b2e615404034 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -35,7 +35,11 @@ static int vfio_ccw_mdev_reset(struct mdev_device *mdev) if (ret) return ret; - return cio_enable_subchannel(sch, (u32)(unsigned long)sch); + ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch); + if (!ret) + private->state = VFIO_CCW_STATE_IDLE; + + return ret; } static int vfio_ccw_mdev_notifier(struct notifier_block *nb, @@ -112,10 +116,14 @@ static int vfio_ccw_mdev_create(struct kobject *kobj, struct mdev_device *mdev) struct vfio_ccw_private *private = dev_get_drvdata(mdev_parent_dev(mdev)); + if (private->state == VFIO_CCW_STATE_NOT_OPER) + return -ENODEV; + if (atomic_dec_if_positive(&private->avail) < 0) return -EPERM; private->mdev = mdev; + private->state = VFIO_CCW_STATE_IDLE; return 0; } @@ -126,10 +134,20 @@ static int vfio_ccw_mdev_remove(struct mdev_device *mdev) dev_get_drvdata(mdev_parent_dev(mdev)); int ret; + if (!private) + goto out; + + if ((private->state == VFIO_CCW_STATE_NOT_OPER) || + (private->state == VFIO_CCW_STATE_STANDBY)) + goto out; + ret = vfio_ccw_mdev_reset(mdev); if (ret) return ret; + private->state = VFIO_CCW_STATE_STANDBY; + +out: private->mdev = NULL; atomic_inc(&private->avail); @@ -193,14 +211,18 @@ static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev, private = dev_get_drvdata(mdev_parent_dev(mdev)); if (!private) return -ENODEV; + if (private->state != VFIO_CCW_STATE_IDLE) + return -EACCES; region = &private->io_region; if (copy_from_user((void *)region + *ppos, buf, count)) return -EFAULT; - region->ret_code = vfio_ccw_sch_cmd_request(private); - if (region->ret_code != 0) + vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_IO_REQ); + if (region->ret_code != 0) { + private->state = VFIO_CCW_STATE_IDLE; return region->ret_code; + } return count; } diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 2503797fb153..fc0f01c16ef9 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -21,6 +21,7 @@ /** * struct vfio_ccw_private * @sch: pointer to the subchannel + * @state: internal state of the device * @completion: synchronization helper of the I/O completion * @avail: available for creating a mediated device * @mdev: pointer to the mediated device @@ -34,6 +35,7 @@ */ struct vfio_ccw_private { struct subchannel *sch; + int state; struct completion *completion; atomic_t avail; struct mdev_device *mdev; @@ -52,6 +54,43 @@ 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); + +/* + * States of the device statemachine. + */ +enum vfio_ccw_state { + VFIO_CCW_STATE_NOT_OPER, + VFIO_CCW_STATE_STANDBY, + VFIO_CCW_STATE_IDLE, + VFIO_CCW_STATE_BOXED, + VFIO_CCW_STATE_BUSY, + /* last element! */ + NR_VFIO_CCW_STATES +}; + +/* + * Asynchronous events of the device statemachine. + */ +enum vfio_ccw_event { + VFIO_CCW_EVENT_NOT_OPER, + VFIO_CCW_EVENT_IO_REQ, + VFIO_CCW_EVENT_INTERRUPT, + /* last element! */ + NR_VFIO_CCW_EVENTS +}; + +/* + * Action called through jumptable. + */ +typedef void (fsm_func_t)(struct vfio_ccw_private *, enum vfio_ccw_event); +extern fsm_func_t *vfio_ccw_jumptable[NR_VFIO_CCW_STATES][NR_VFIO_CCW_EVENTS]; + +static inline void vfio_ccw_fsm_event(struct vfio_ccw_private *private, + int event) +{ + vfio_ccw_jumptable[private->state][event](private, event); +} + +extern struct workqueue_struct *vfio_ccw_work_q; #endif -- cgit v1.2.3-55-g7522 From c9c31b07bab5fee3ef0bf163afc11b1100eb10d4 Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Wed, 12 Apr 2017 11:08:15 +0200 Subject: vfio: ccw: remove unnecessary NULL checks of a pointer Remove several unnecessary checks for the @private pointer, since it can never be NULL in these places. Reported-by: Dan Carpenter Signed-off-by: Dong Jia Shi Message-Id: <20170412090816.79108-2-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_fsm.c | 6 +----- drivers/s390/cio/vfio_ccw_ops.c | 17 ----------------- 2 files changed, 1 insertion(+), 22 deletions(-) (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index 55b6cc55758e..80a0559cd7ce 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -161,12 +161,8 @@ err_out: static void fsm_irq(struct vfio_ccw_private *private, enum vfio_ccw_event event) { - struct irb *irb; + struct irb *irb = this_cpu_ptr(&cio_irb); - if (!private) - return; - - irb = this_cpu_ptr(&cio_irb); memcpy(&private->irb, irb, sizeof(*irb)); queue_work(vfio_ccw_work_q, &private->io_work); diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index b2e615404034..55d0c87e73c3 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -19,9 +19,6 @@ static int vfio_ccw_mdev_reset(struct mdev_device *mdev) int ret; private = dev_get_drvdata(mdev_parent_dev(mdev)); - if (!private) - return -ENODEV; - sch = private->sch; /* * TODO: @@ -49,9 +46,6 @@ static int vfio_ccw_mdev_notifier(struct notifier_block *nb, struct vfio_ccw_private *private = container_of(nb, struct vfio_ccw_private, nb); - if (!private) - return NOTIFY_STOP; - /* * Vendor drivers MUST unpin pages in response to an * invalidation. @@ -134,9 +128,6 @@ static int vfio_ccw_mdev_remove(struct mdev_device *mdev) dev_get_drvdata(mdev_parent_dev(mdev)); int ret; - if (!private) - goto out; - if ((private->state == VFIO_CCW_STATE_NOT_OPER) || (private->state == VFIO_CCW_STATE_STANDBY)) goto out; @@ -187,9 +178,6 @@ static ssize_t vfio_ccw_mdev_read(struct mdev_device *mdev, return -EINVAL; private = dev_get_drvdata(mdev_parent_dev(mdev)); - if (!private) - return -ENODEV; - region = &private->io_region; if (copy_to_user(buf, (void *)region + *ppos, count)) return -EFAULT; @@ -209,8 +197,6 @@ static ssize_t vfio_ccw_mdev_write(struct mdev_device *mdev, return -EINVAL; private = dev_get_drvdata(mdev_parent_dev(mdev)); - if (!private) - return -ENODEV; if (private->state != VFIO_CCW_STATE_IDLE) return -EACCES; @@ -274,9 +260,6 @@ static int vfio_ccw_mdev_set_irqs(struct mdev_device *mdev, return -EINVAL; private = dev_get_drvdata(mdev_parent_dev(mdev)); - if (!private) - return -ENODEV; - ctx = &private->io_trigger; switch (flags & VFIO_IRQ_SET_DATA_TYPE_MASK) { -- cgit v1.2.3-55-g7522 From 129cc19a94513081e9250323cd57e12ed48b3613 Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Wed, 12 Apr 2017 11:08:16 +0200 Subject: vfio: ccw: improve error handling for vfio_ccw_mdev_remove When vfio_ccw_mdev_reset fails during the remove process of the mdev, the current implementation simply returns. The failure indicates that the subchannel device is in a NOT_OPER state, thus the right thing to do should be removing the mdev. While we are at here, reverse the condition check to make the code more concise and readable. Signed-off-by: Dong Jia Shi Message-Id: <20170412090816.79108-3-bjsdjshi@linux.vnet.ibm.com> Signed-off-by: Cornelia Huck --- drivers/s390/cio/vfio_ccw_ops.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'drivers/s390/cio/vfio_ccw_ops.c') diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 55d0c87e73c3..e72abbc18ee3 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -126,19 +126,14 @@ static int vfio_ccw_mdev_remove(struct mdev_device *mdev) { struct vfio_ccw_private *private = dev_get_drvdata(mdev_parent_dev(mdev)); - int ret; - - if ((private->state == VFIO_CCW_STATE_NOT_OPER) || - (private->state == VFIO_CCW_STATE_STANDBY)) - goto out; - - ret = vfio_ccw_mdev_reset(mdev); - if (ret) - return ret; - private->state = VFIO_CCW_STATE_STANDBY; + if ((private->state != VFIO_CCW_STATE_NOT_OPER) && + (private->state != VFIO_CCW_STATE_STANDBY)) { + if (!vfio_ccw_mdev_reset(mdev)) + private->state = VFIO_CCW_STATE_STANDBY; + /* The state will be NOT_OPER on error. */ + } -out: private->mdev = NULL; atomic_inc(&private->avail); -- cgit v1.2.3-55-g7522