RE: [RFC PATCH 3/7] vfio: add spimdev support

From: Tian, Kevin
Date: Wed Aug 01 2018 - 23:21:45 EST


> From: Kenneth Lee
> Sent: Wednesday, August 1, 2018 6:22 PM
>
> From: Kenneth Lee <liguozhu@xxxxxxxxxxxxx>
>
> SPIMDEV is "Share Parent IOMMU Mdev". It is a vfio-mdev. But differ from
> the general vfio-mdev:
>
> 1. It shares its parent's IOMMU.
> 2. There is no hardware resource attached to the mdev is created. The
> hardware resource (A `queue') is allocated only when the mdev is
> opened.

Alex has concern on doing so, as pointed out in:

https://www.spinics.net/lists/kvm/msg172652.html

resource allocation should be reserved at creation time.

>
> Currently only the vfio type-1 driver is updated to make it to be aware
> of.
>
> Signed-off-by: Kenneth Lee <liguozhu@xxxxxxxxxxxxx>
> Signed-off-by: Zaibo Xu <xuzaibo@xxxxxxxxxx>
> Signed-off-by: Zhou Wang <wangzhou1@xxxxxxxxxxxxx>
> ---
> drivers/vfio/Kconfig | 1 +
> drivers/vfio/Makefile | 1 +
> drivers/vfio/spimdev/Kconfig | 10 +
> drivers/vfio/spimdev/Makefile | 3 +
> drivers/vfio/spimdev/vfio_spimdev.c | 421
> ++++++++++++++++++++++++++++
> drivers/vfio/vfio_iommu_type1.c | 136 ++++++++-
> include/linux/vfio_spimdev.h | 95 +++++++
> include/uapi/linux/vfio_spimdev.h | 28 ++
> 8 files changed, 689 insertions(+), 6 deletions(-)
> create mode 100644 drivers/vfio/spimdev/Kconfig
> create mode 100644 drivers/vfio/spimdev/Makefile
> create mode 100644 drivers/vfio/spimdev/vfio_spimdev.c
> create mode 100644 include/linux/vfio_spimdev.h
> create mode 100644 include/uapi/linux/vfio_spimdev.h
>
> diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig
> index c84333eb5eb5..3719eba72ef1 100644
> --- a/drivers/vfio/Kconfig
> +++ b/drivers/vfio/Kconfig
> @@ -47,4 +47,5 @@ menuconfig VFIO_NOIOMMU
> source "drivers/vfio/pci/Kconfig"
> source "drivers/vfio/platform/Kconfig"
> source "drivers/vfio/mdev/Kconfig"
> +source "drivers/vfio/spimdev/Kconfig"
> source "virt/lib/Kconfig"
> diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile
> index de67c4725cce..28f3ef0cdce1 100644
> --- a/drivers/vfio/Makefile
> +++ b/drivers/vfio/Makefile
> @@ -9,3 +9,4 @@ obj-$(CONFIG_VFIO_SPAPR_EEH) += vfio_spapr_eeh.o
> obj-$(CONFIG_VFIO_PCI) += pci/
> obj-$(CONFIG_VFIO_PLATFORM) += platform/
> obj-$(CONFIG_VFIO_MDEV) += mdev/
> +obj-$(CONFIG_VFIO_SPIMDEV) += spimdev/
> diff --git a/drivers/vfio/spimdev/Kconfig b/drivers/vfio/spimdev/Kconfig
> new file mode 100644
> index 000000000000..1226301f9d0e
> --- /dev/null
> +++ b/drivers/vfio/spimdev/Kconfig
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0
> +config VFIO_SPIMDEV
> + tristate "Support for Share Parent IOMMU MDEV"
> + depends on VFIO_MDEV_DEVICE
> + help
> + Support for VFIO Share Parent IOMMU MDEV, which enable the
> kernel to
> + support for the light weight hardware accelerator framework,
> WrapDrive.
> +
> + To compile this as a module, choose M here: the module will be
> called
> + spimdev.
> diff --git a/drivers/vfio/spimdev/Makefile b/drivers/vfio/spimdev/Makefile
> new file mode 100644
> index 000000000000..d02fb69c37e4
> --- /dev/null
> +++ b/drivers/vfio/spimdev/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0
> +spimdev-y := spimdev.o
> +obj-$(CONFIG_VFIO_SPIMDEV) += vfio_spimdev.o
> diff --git a/drivers/vfio/spimdev/vfio_spimdev.c
> b/drivers/vfio/spimdev/vfio_spimdev.c
> new file mode 100644
> index 000000000000..1b6910c9d27d
> --- /dev/null
> +++ b/drivers/vfio/spimdev/vfio_spimdev.c
> @@ -0,0 +1,421 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +#include <linux/anon_inodes.h>
> +#include <linux/idr.h>
> +#include <linux/module.h>
> +#include <linux/poll.h>
> +#include <linux/vfio_spimdev.h>
> +
> +struct spimdev_mdev_state {
> + struct vfio_spimdev *spimdev;
> +};
> +
> +static struct class *spimdev_class;
> +static DEFINE_IDR(spimdev_idr);
> +
> +static int vfio_spimdev_dev_exist(struct device *dev, void *data)
> +{
> + return !strcmp(dev_name(dev), dev_name((struct device *)data));
> +}
> +
> +#ifdef CONFIG_IOMMU_SVA
> +static bool vfio_spimdev_is_valid_pasid(int pasid)
> +{
> + struct mm_struct *mm;
> +
> + mm = iommu_sva_find(pasid);
> + if (mm) {
> + mmput(mm);
> + return mm == current->mm;
> + }
> +
> + return false;
> +}
> +#endif
> +
> +/* Check if the device is a mediated device belongs to vfio_spimdev */
> +int vfio_spimdev_is_spimdev(struct device *dev)
> +{
> + struct mdev_device *mdev;
> + struct device *pdev;
> +
> + mdev = mdev_from_dev(dev);
> + if (!mdev)
> + return 0;
> +
> + pdev = mdev_parent_dev(mdev);
> + if (!pdev)
> + return 0;
> +
> + return class_for_each_device(spimdev_class, NULL, pdev,
> + vfio_spimdev_dev_exist);
> +}
> +EXPORT_SYMBOL_GPL(vfio_spimdev_is_spimdev);
> +
> +struct vfio_spimdev *vfio_spimdev_pdev_spimdev(struct device *dev)
> +{
> + struct device *class_dev;
> +
> + if (!dev)
> + return ERR_PTR(-EINVAL);
> +
> + class_dev = class_find_device(spimdev_class, NULL, dev,
> + (int(*)(struct device *, const void
> *))vfio_spimdev_dev_exist);
> + if (!class_dev)
> + return ERR_PTR(-ENODEV);
> +
> + return container_of(class_dev, struct vfio_spimdev, cls_dev);
> +}
> +EXPORT_SYMBOL_GPL(vfio_spimdev_pdev_spimdev);
> +
> +struct vfio_spimdev *mdev_spimdev(struct mdev_device *mdev)
> +{
> + struct device *pdev = mdev_parent_dev(mdev);
> +
> + return vfio_spimdev_pdev_spimdev(pdev);
> +}
> +EXPORT_SYMBOL_GPL(mdev_spimdev);
> +
> +static ssize_t iommu_type_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct vfio_spimdev *spimdev = vfio_spimdev_pdev_spimdev(dev);
> +
> + if (!spimdev)
> + return -ENODEV;
> +
> + return sprintf(buf, "%d\n", spimdev->iommu_type);
> +}
> +
> +static DEVICE_ATTR_RO(iommu_type);
> +
> +static ssize_t dma_flag_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct vfio_spimdev *spimdev = vfio_spimdev_pdev_spimdev(dev);
> +
> + if (!spimdev)
> + return -ENODEV;
> +
> + return sprintf(buf, "%d\n", spimdev->dma_flag);
> +}
> +
> +static DEVICE_ATTR_RO(dma_flag);
> +
> +/* mdev->dev_attr_groups */
> +static struct attribute *vfio_spimdev_attrs[] = {
> + &dev_attr_iommu_type.attr,
> + &dev_attr_dma_flag.attr,
> + NULL,
> +};
> +static const struct attribute_group vfio_spimdev_group = {
> + .name = VFIO_SPIMDEV_PDEV_ATTRS_GRP_NAME,
> + .attrs = vfio_spimdev_attrs,
> +};
> +const struct attribute_group *vfio_spimdev_groups[] = {
> + &vfio_spimdev_group,
> + NULL,
> +};
> +
> +/* default attributes for mdev->supported_type_groups, used by
> registerer*/
> +#define MDEV_TYPE_ATTR_RO_EXPORT(name) \
> + MDEV_TYPE_ATTR_RO(name); \
> + EXPORT_SYMBOL_GPL(mdev_type_attr_##name);
> +
> +#define DEF_SIMPLE_SPIMDEV_ATTR(_name, spimdev_member, format)
> \
> +static ssize_t _name##_show(struct kobject *kobj, struct device *dev, \
> + char *buf) \
> +{ \
> + struct vfio_spimdev *spimdev = vfio_spimdev_pdev_spimdev(dev);
> \
> + if (!spimdev) \
> + return -ENODEV; \
> + return sprintf(buf, format, spimdev->spimdev_member); \
> +} \
> +MDEV_TYPE_ATTR_RO_EXPORT(_name)
> +
> +DEF_SIMPLE_SPIMDEV_ATTR(flags, flags, "%d");
> +DEF_SIMPLE_SPIMDEV_ATTR(name, name, "%s"); /* this should be
> algorithm name, */
> + /* but you would not care if you have only one algorithm */
> +DEF_SIMPLE_SPIMDEV_ATTR(device_api, api_ver, "%s");
> +
> +/* this return total queue left, not mdev left */
> +static ssize_t
> +available_instances_show(struct kobject *kobj, struct device *dev, char
> *buf)
> +{
> + struct vfio_spimdev *spimdev = vfio_spimdev_pdev_spimdev(dev);
> +
> + return sprintf(buf, "%d",
> + spimdev->ops->get_available_instances(spimdev));
> +}
> +MDEV_TYPE_ATTR_RO_EXPORT(available_instances);
> +
> +static int vfio_spimdev_mdev_create(struct kobject *kobj,
> + struct mdev_device *mdev)
> +{
> + struct device *dev = mdev_dev(mdev);
> + struct device *pdev = mdev_parent_dev(mdev);
> + struct spimdev_mdev_state *mdev_state;
> + struct vfio_spimdev *spimdev = mdev_spimdev(mdev);
> +
> + if (!spimdev->ops->get_queue)
> + return -ENODEV;
> +
> + mdev_state = devm_kzalloc(dev, sizeof(struct
> spimdev_mdev_state),
> + GFP_KERNEL);
> + if (!mdev_state)
> + return -ENOMEM;
> + mdev_set_drvdata(mdev, mdev_state);
> + mdev_state->spimdev = spimdev;
> + dev->iommu_fwspec = pdev->iommu_fwspec;
> + get_device(pdev);
> + __module_get(spimdev->owner);
> +
> + return 0;
> +}
> +
> +static int vfio_spimdev_mdev_remove(struct mdev_device *mdev)
> +{
> + struct device *dev = mdev_dev(mdev);
> + struct device *pdev = mdev_parent_dev(mdev);
> + struct vfio_spimdev *spimdev = mdev_spimdev(mdev);
> +
> + put_device(pdev);
> + module_put(spimdev->owner);
> + dev->iommu_fwspec = NULL;
> + mdev_set_drvdata(mdev, NULL);
> +
> + return 0;
> +}
> +
> +/* Wake up the process who is waiting this queue */
> +void vfio_spimdev_wake_up(struct vfio_spimdev_queue *q)
> +{
> + wake_up(&q->wait);
> +}
> +EXPORT_SYMBOL_GPL(vfio_spimdev_wake_up);
> +
> +static int vfio_spimdev_q_file_open(struct inode *inode, struct file *file)
> +{
> + return 0;
> +}
> +
> +static int vfio_spimdev_q_file_release(struct inode *inode, struct file *file)
> +{
> + struct vfio_spimdev_queue *q =
> + (struct vfio_spimdev_queue *)file->private_data;
> + struct vfio_spimdev *spimdev = q->spimdev;
> + int ret;
> +
> + ret = spimdev->ops->put_queue(q);
> + if (ret) {
> + dev_err(spimdev->dev, "drv put queue fail (%d)!\n", ret);
> + return ret;
> + }
> +
> + put_device(mdev_dev(q->mdev));
> +
> + return 0;
> +}
> +
> +static long vfio_spimdev_q_file_ioctl(struct file *file, unsigned int cmd,
> + unsigned long arg)
> +{
> + struct vfio_spimdev_queue *q =
> + (struct vfio_spimdev_queue *)file->private_data;
> + struct vfio_spimdev *spimdev = q->spimdev;
> +
> + if (spimdev->ops->ioctl)
> + return spimdev->ops->ioctl(q, cmd, arg);
> +
> + dev_err(spimdev->dev, "ioctl cmd (%d) is not supported!\n", cmd);
> +
> + return -EINVAL;
> +}
> +
> +static int vfio_spimdev_q_file_mmap(struct file *file,
> + struct vm_area_struct *vma)
> +{
> + struct vfio_spimdev_queue *q =
> + (struct vfio_spimdev_queue *)file->private_data;
> + struct vfio_spimdev *spimdev = q->spimdev;
> +
> + if (spimdev->ops->mmap)
> + return spimdev->ops->mmap(q, vma);
> +
> + dev_err(spimdev->dev, "no driver mmap!\n");
> + return -EINVAL;
> +}
> +
> +static __poll_t vfio_spimdev_q_file_poll(struct file *file, poll_table *wait)
> +{
> + struct vfio_spimdev_queue *q =
> + (struct vfio_spimdev_queue *)file->private_data;
> + struct vfio_spimdev *spimdev = q->spimdev;
> +
> + poll_wait(file, &q->wait, wait);
> + if (spimdev->ops->is_q_updated(q))
> + return EPOLLIN | EPOLLRDNORM;
> +
> + return 0;
> +}
> +
> +static const struct file_operations spimdev_q_file_ops = {
> + .owner = THIS_MODULE,
> + .open = vfio_spimdev_q_file_open,
> + .unlocked_ioctl = vfio_spimdev_q_file_ioctl,
> + .release = vfio_spimdev_q_file_release,
> + .poll = vfio_spimdev_q_file_poll,
> + .mmap = vfio_spimdev_q_file_mmap,
> +};
> +
> +static long vfio_spimdev_mdev_get_queue(struct mdev_device *mdev,
> + struct vfio_spimdev *spimdev, unsigned long arg)
> +{
> + struct vfio_spimdev_queue *q;
> + int ret;
> +
> +#ifdef CONFIG_IOMMU_SVA
> + int pasid = arg;
> +
> + if (!vfio_spimdev_is_valid_pasid(pasid))
> + return -EINVAL;
> +#endif
> +
> + if (!spimdev->ops->get_queue)
> + return -EINVAL;
> +
> + ret = spimdev->ops->get_queue(spimdev, arg, &q);
> + if (ret < 0) {
> + dev_err(spimdev->dev, "get_queue failed\n");
> + return -ENODEV;
> + }
> +
> + ret = anon_inode_getfd("spimdev_q", &spimdev_q_file_ops,
> + q, O_CLOEXEC | O_RDWR);
> + if (ret < 0) {
> + dev_err(spimdev->dev, "getfd fail %d\n", ret);
> + goto err_with_queue;
> + }
> +
> + q->fd = ret;
> + q->spimdev = spimdev;
> + q->mdev = mdev;
> + q->container = arg;
> + init_waitqueue_head(&q->wait);
> + get_device(mdev_dev(mdev));
> +
> + return ret;
> +
> +err_with_queue:
> + spimdev->ops->put_queue(q);
> + return ret;
> +}
> +
> +static long vfio_spimdev_mdev_ioctl(struct mdev_device *mdev, unsigned
> int cmd,
> + unsigned long arg)
> +{
> + struct spimdev_mdev_state *mdev_state;
> + struct vfio_spimdev *spimdev;
> +
> + if (!mdev)
> + return -ENODEV;
> +
> + mdev_state = mdev_get_drvdata(mdev);
> + if (!mdev_state)
> + return -ENODEV;
> +
> + spimdev = mdev_state->spimdev;
> + if (!spimdev)
> + return -ENODEV;
> +
> + if (cmd == VFIO_SPIMDEV_CMD_GET_Q)
> + return vfio_spimdev_mdev_get_queue(mdev, spimdev, arg);
> +
> + dev_err(spimdev->dev,
> + "%s, ioctl cmd (0x%x) is not supported!\n", __func__, cmd);
> + return -EINVAL;
> +}
> +
> +static void vfio_spimdev_release(struct device *dev) { }
> +static void vfio_spimdev_mdev_release(struct mdev_device *mdev) { }
> +static int vfio_spimdev_mdev_open(struct mdev_device *mdev) { return
> 0; }
> +
> +/**
> + * vfio_spimdev_register - register a spimdev
> + * @spimdev: device structure
> + */
> +int vfio_spimdev_register(struct vfio_spimdev *spimdev)
> +{
> + int ret;
> + const char *drv_name;
> +
> + if (!spimdev->dev)
> + return -ENODEV;
> +
> + drv_name = dev_driver_string(spimdev->dev);
> + if (strstr(drv_name, "-")) {
> + pr_err("spimdev: parent driver name cannot include '-'!\n");
> + return -EINVAL;
> + }
> +
> + spimdev->dev_id = idr_alloc(&spimdev_idr, spimdev, 0, 0,
> GFP_KERNEL);
> + if (spimdev->dev_id < 0)
> + return spimdev->dev_id;
> +
> + atomic_set(&spimdev->ref, 0);
> + spimdev->cls_dev.parent = spimdev->dev;
> + spimdev->cls_dev.class = spimdev_class;
> + spimdev->cls_dev.release = vfio_spimdev_release;
> + dev_set_name(&spimdev->cls_dev, "%s", dev_name(spimdev-
> >dev));
> + ret = device_register(&spimdev->cls_dev);
> + if (ret)
> + return ret;
> +
> + spimdev->mdev_fops.owner = spimdev->owner;
> + spimdev->mdev_fops.dev_attr_groups =
> vfio_spimdev_groups;
> + WARN_ON(!spimdev->mdev_fops.supported_type_groups);
> + spimdev->mdev_fops.create =
> vfio_spimdev_mdev_create;
> + spimdev->mdev_fops.remove =
> vfio_spimdev_mdev_remove;
> + spimdev->mdev_fops.ioctl = vfio_spimdev_mdev_ioctl;
> + spimdev->mdev_fops.open =
> vfio_spimdev_mdev_open;
> + spimdev->mdev_fops.release =
> vfio_spimdev_mdev_release;
> +
> + ret = mdev_register_device(spimdev->dev, &spimdev->mdev_fops);
> + if (ret)
> + device_unregister(&spimdev->cls_dev);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(vfio_spimdev_register);
> +
> +/**
> + * vfio_spimdev_unregister - unregisters a spimdev
> + * @spimdev: device to unregister
> + *
> + * Unregister a miscellaneous device that wat previously successully
> registered
> + * with vfio_spimdev_register().
> + */
> +void vfio_spimdev_unregister(struct vfio_spimdev *spimdev)
> +{
> + mdev_unregister_device(spimdev->dev);
> + device_unregister(&spimdev->cls_dev);
> +}
> +EXPORT_SYMBOL_GPL(vfio_spimdev_unregister);
> +
> +static int __init vfio_spimdev_init(void)
> +{
> + spimdev_class = class_create(THIS_MODULE,
> VFIO_SPIMDEV_CLASS_NAME);
> + return PTR_ERR_OR_ZERO(spimdev_class);
> +}
> +
> +static __exit void vfio_spimdev_exit(void)
> +{
> + class_destroy(spimdev_class);
> + idr_destroy(&spimdev_idr);
> +}
> +
> +module_init(vfio_spimdev_init);
> +module_exit(vfio_spimdev_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Hisilicon Tech. Co., Ltd.");
> +MODULE_DESCRIPTION("VFIO Share Parent's IOMMU Mediated Device");
> diff --git a/drivers/vfio/vfio_iommu_type1.c
> b/drivers/vfio/vfio_iommu_type1.c
> index 3e5b17710a4f..0ec38a17c98c 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
> @@ -41,6 +41,7 @@
> #include <linux/notifier.h>
> #include <linux/dma-iommu.h>
> #include <linux/irqdomain.h>
> +#include <linux/vfio_spimdev.h>
>
> #define DRIVER_VERSION "0.2"
> #define DRIVER_AUTHOR "Alex Williamson
> <alex.williamson@xxxxxxxxxx>"
> @@ -89,6 +90,8 @@ struct vfio_dma {
> };
>
> struct vfio_group {
> + /* iommu_group of mdev's parent device */
> + struct iommu_group *parent_group;
> struct iommu_group *iommu_group;
> struct list_head next;
> };
> @@ -1327,6 +1330,109 @@ static bool vfio_iommu_has_sw_msi(struct
> iommu_group *group, phys_addr_t *base)
> return ret;
> }
>
> +/* return 0 if the device is not spimdev.
> + * return 1 if the device is spimdev, the data will be updated with parent
> + * device's group.
> + * return -errno if other error.
> + */
> +static int vfio_spimdev_type(struct device *dev, void *data)
> +{
> + struct iommu_group **group = data;
> + struct iommu_group *pgroup;
> + int (*spimdev_mdev)(struct device *dev);
> + struct device *pdev;
> + int ret = 1;
> +
> + /* vfio_spimdev module is not configurated */
> + spimdev_mdev = symbol_get(vfio_spimdev_is_spimdev);
> + if (!spimdev_mdev)
> + return 0;
> +
> + /* check if it belongs to vfio_spimdev device */
> + if (!spimdev_mdev(dev)) {
> + ret = 0;
> + goto get_exit;
> + }
> +
> + pdev = dev->parent;
> + pgroup = iommu_group_get(pdev);
> + if (!pgroup) {
> + ret = -ENODEV;
> + goto get_exit;
> + }
> +
> + if (group) {
> + /* check if all parent devices is the same */
> + if (*group && *group != pgroup)
> + ret = -ENODEV;
> + else
> + *group = pgroup;
> + }
> +
> + iommu_group_put(pgroup);
> +
> +get_exit:
> + symbol_put(vfio_spimdev_is_spimdev);
> +
> + return ret;
> +}
> +
> +/* return 0 or -errno */
> +static int vfio_spimdev_bus(struct device *dev, void *data)
> +{
> + struct bus_type **bus = data;
> +
> + if (!dev->bus)
> + return -ENODEV;
> +
> + /* ensure all devices has the same bus_type */
> + if (*bus && *bus != dev->bus)
> + return -EINVAL;
> +
> + *bus = dev->bus;
> + return 0;
> +}
> +
> +/* return 0 means it is not spi group, 1 means it is, or -EXXX for error */
> +static int vfio_iommu_type1_attach_spigroup(struct vfio_domain *domain,
> + struct vfio_group *group,
> + struct iommu_group
> *iommu_group)
> +{
> + int ret;
> + struct bus_type *pbus = NULL;
> + struct iommu_group *pgroup = NULL;
> +
> + ret = iommu_group_for_each_dev(iommu_group, &pgroup,
> + vfio_spimdev_type);
> + if (ret < 0)
> + goto out;
> + else if (ret > 0) {
> + domain->domain = iommu_group_share_domain(pgroup);
> + if (IS_ERR(domain->domain))
> + goto out;
> + ret = iommu_group_for_each_dev(pgroup, &pbus,
> + vfio_spimdev_bus);
> + if (ret < 0)
> + goto err_with_share_domain;
> +
> + if (pbus && iommu_capable(pbus,
> IOMMU_CAP_CACHE_COHERENCY))
> + domain->prot |= IOMMU_CACHE;
> +
> + group->parent_group = pgroup;
> + INIT_LIST_HEAD(&domain->group_list);
> + list_add(&group->next, &domain->group_list);
> +
> + return 1;
> + }
> +
> + return 0;
> +
> +err_with_share_domain:
> + iommu_group_unshare_domain(pgroup);
> +out:
> + return ret;
> +}
> +
> static int vfio_iommu_type1_attach_group(void *iommu_data,
> struct iommu_group
> *iommu_group)
> {
> @@ -1335,8 +1441,8 @@ static int vfio_iommu_type1_attach_group(void
> *iommu_data,
> struct vfio_domain *domain, *d;
> struct bus_type *bus = NULL, *mdev_bus;
> int ret;
> - bool resv_msi, msi_remap;
> - phys_addr_t resv_msi_base;
> + bool resv_msi = false, msi_remap;
> + phys_addr_t resv_msi_base = 0;
>
> mutex_lock(&iommu->lock);
>
> @@ -1373,6 +1479,14 @@ static int vfio_iommu_type1_attach_group(void
> *iommu_data,
> if (mdev_bus) {
> if ((bus == mdev_bus) && !iommu_present(bus)) {
> symbol_put(mdev_bus_type);
> +
> + ret = vfio_iommu_type1_attach_spigroup(domain,
> group,
> + iommu_group);
> + if (ret < 0)
> + goto out_free;
> + else if (ret > 0)
> + goto replay_check;
> +
> if (!iommu->external_domain) {
> INIT_LIST_HEAD(&domain->group_list);
> iommu->external_domain = domain;
> @@ -1451,12 +1565,13 @@ static int
> vfio_iommu_type1_attach_group(void *iommu_data,
>
> vfio_test_domain_fgsp(domain);
>
> +replay_check:
> /* replay mappings on new domains */
> ret = vfio_iommu_replay(iommu, domain);
> if (ret)
> goto out_detach;
>
> - if (resv_msi) {
> + if (!group->parent_group && resv_msi) {
> ret = iommu_get_msi_cookie(domain->domain,
> resv_msi_base);
> if (ret)
> goto out_detach;
> @@ -1471,7 +1586,10 @@ static int vfio_iommu_type1_attach_group(void
> *iommu_data,
> out_detach:
> iommu_detach_group(domain->domain, iommu_group);
> out_domain:
> - iommu_domain_free(domain->domain);
> + if (group->parent_group)
> + iommu_group_unshare_domain(group->parent_group);
> + else
> + iommu_domain_free(domain->domain);
> out_free:
> kfree(domain);
> kfree(group);
> @@ -1533,6 +1651,7 @@ static void
> vfio_iommu_type1_detach_group(void *iommu_data,
> struct vfio_iommu *iommu = iommu_data;
> struct vfio_domain *domain;
> struct vfio_group *group;
> + int ret;
>
> mutex_lock(&iommu->lock);
>
> @@ -1560,7 +1679,11 @@ static void
> vfio_iommu_type1_detach_group(void *iommu_data,
> if (!group)
> continue;
>
> - iommu_detach_group(domain->domain, iommu_group);
> + if (group->parent_group)
> + iommu_group_unshare_domain(group-
> >parent_group);
> + else
> + iommu_detach_group(domain->domain,
> iommu_group);
> +
> list_del(&group->next);
> kfree(group);
> /*
> @@ -1577,7 +1700,8 @@ static void
> vfio_iommu_type1_detach_group(void *iommu_data,
> else
>
> vfio_iommu_unmap_unpin_reaccount(iommu);
> }
> - iommu_domain_free(domain->domain);
> + if (!ret)
> + iommu_domain_free(domain->domain);
> list_del(&domain->next);
> kfree(domain);
> }
> diff --git a/include/linux/vfio_spimdev.h b/include/linux/vfio_spimdev.h
> new file mode 100644
> index 000000000000..f7e7d90013e1
> --- /dev/null
> +++ b/include/linux/vfio_spimdev.h
> @@ -0,0 +1,95 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +#ifndef __VFIO_SPIMDEV_H
> +#define __VFIO_SPIMDEV_H
> +
> +#include <linux/device.h>
> +#include <linux/iommu.h>
> +#include <linux/mdev.h>
> +#include <linux/vfio.h>
> +#include <uapi/linux/vfio_spimdev.h>
> +
> +struct vfio_spimdev_queue;
> +struct vfio_spimdev;
> +
> +/**
> + * struct vfio_spimdev_ops - WD device operations
> + * @get_queue: get a queue from the device according to algorithm
> + * @put_queue: free a queue to the device
> + * @is_q_updated: check whether the task is finished
> + * @mask_notify: mask the task irq of queue
> + * @mmap: mmap addresses of queue to user space
> + * @reset: reset the WD device
> + * @reset_queue: reset the queue
> + * @ioctl: ioctl for user space users of the queue
> + * @get_available_instances: get numbers of the queue remained
> + */
> +struct vfio_spimdev_ops {
> + int (*get_queue)(struct vfio_spimdev *spimdev, unsigned long arg,
> + struct vfio_spimdev_queue **q);
> + int (*put_queue)(struct vfio_spimdev_queue *q);
> + int (*is_q_updated)(struct vfio_spimdev_queue *q);
> + void (*mask_notify)(struct vfio_spimdev_queue *q, int
> event_mask);
> + int (*mmap)(struct vfio_spimdev_queue *q, struct vm_area_struct
> *vma);
> + int (*reset)(struct vfio_spimdev *spimdev);
> + int (*reset_queue)(struct vfio_spimdev_queue *q);
> + long (*ioctl)(struct vfio_spimdev_queue *q, unsigned int cmd,
> + unsigned long arg);
> + int (*get_available_instances)(struct vfio_spimdev *spimdev);
> +};
> +
> +struct vfio_spimdev_queue {
> + struct mutex mutex;
> + struct vfio_spimdev *spimdev;
> + int qid;
> + __u32 flags;
> + void *priv;
> + wait_queue_head_t wait;
> + struct mdev_device *mdev;
> + int fd;
> + int container;
> +#ifdef CONFIG_IOMMU_SVA
> + int pasid;
> +#endif
> +};
> +
> +struct vfio_spimdev {
> + const char *name;
> + int status;
> + atomic_t ref;
> + struct module *owner;
> + const struct vfio_spimdev_ops *ops;
> + struct device *dev;
> + struct device cls_dev;
> + bool is_vf;
> + u32 iommu_type;
> + u32 dma_flag;
> + u32 dev_id;
> + void *priv;
> + int flags;
> + const char *api_ver;
> + struct mdev_parent_ops mdev_fops;
> +};
> +
> +int vfio_spimdev_register(struct vfio_spimdev *spimdev);
> +void vfio_spimdev_unregister(struct vfio_spimdev *spimdev);
> +void vfio_spimdev_wake_up(struct vfio_spimdev_queue *q);
> +int vfio_spimdev_is_spimdev(struct device *dev);
> +struct vfio_spimdev *vfio_spimdev_pdev_spimdev(struct device *dev);
> +int vfio_spimdev_pasid_pri_check(int pasid);
> +int vfio_spimdev_get(struct device *dev);
> +int vfio_spimdev_put(struct device *dev);
> +struct vfio_spimdev *mdev_spimdev(struct mdev_device *mdev);
> +
> +extern struct mdev_type_attribute mdev_type_attr_flags;
> +extern struct mdev_type_attribute mdev_type_attr_name;
> +extern struct mdev_type_attribute mdev_type_attr_device_api;
> +extern struct mdev_type_attribute mdev_type_attr_available_instances;
> +#define VFIO_SPIMDEV_DEFAULT_MDEV_TYPE_ATTRS \
> + &mdev_type_attr_name.attr, \
> + &mdev_type_attr_device_api.attr, \
> + &mdev_type_attr_available_instances.attr, \
> + &mdev_type_attr_flags.attr
> +
> +#define _VFIO_SPIMDEV_REGION(vm_pgoff) (vm_pgoff & 0xf)
> +
> +#endif
> diff --git a/include/uapi/linux/vfio_spimdev.h
> b/include/uapi/linux/vfio_spimdev.h
> new file mode 100644
> index 000000000000..3435e5c345b4
> --- /dev/null
> +++ b/include/uapi/linux/vfio_spimdev.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +#ifndef _UAPIVFIO_SPIMDEV_H
> +#define _UAPIVFIO_SPIMDEV_H
> +
> +#include <linux/ioctl.h>
> +
> +#define VFIO_SPIMDEV_CLASS_NAME "spimdev"
> +
> +/* Device ATTRs in parent dev SYSFS DIR */
> +#define VFIO_SPIMDEV_PDEV_ATTRS_GRP_NAME "params"
> +
> +/* Parent device attributes */
> +#define SPIMDEV_IOMMU_TYPE "iommu_type"
> +#define SPIMDEV_DMA_FLAG "dma_flag"
> +
> +/* Maximum length of algorithm name string */
> +#define VFIO_SPIMDEV_ALG_NAME_SIZE 64
> +
> +/* the bits used in SPIMDEV_DMA_FLAG attributes */
> +#define VFIO_SPIMDEV_DMA_INVALID 0
> +#define VFIO_SPIMDEV_DMA_SINGLE_PROC_MAP 1
> +#define VFIO_SPIMDEV_DMA_MULTI_PROC_MAP 2
> +#define VFIO_SPIMDEV_DMA_SVM 4
> +#define VFIO_SPIMDEV_DMA_SVM_NO_FAULT 8
> +#define VFIO_SPIMDEV_DMA_PHY 16
> +
> +#define VFIO_SPIMDEV_CMD_GET_Q _IO('W', 1)
> +#endif
> --
> 2.17.1