vfio/pci: Add infrastructure for additional device specific regions
authorAlex Williamson <alex.williamson@redhat.com>
Mon, 22 Feb 2016 23:02:39 +0000 (16:02 -0700)
committerAlex Williamson <alex.williamson@redhat.com>
Mon, 22 Feb 2016 23:10:09 +0000 (16:10 -0700)
Add support for additional regions with indexes started after the
already defined fixed regions.  Device specific code can register
these regions with the new vfio_pci_register_dev_region() function.
The ops structure per region currently only includes read/write
access and a release function, allowing automatic cleanup when the
device is closed.  mmap support is only missing here because it's
not needed by the first user queued for this support.

Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
drivers/vfio/pci/vfio_pci.c
drivers/vfio/pci/vfio_pci_private.h

index 4682207b1ac8542dbeee14d268fae76ee7959e8f..813a2e67aa0ce42bb2cbcd585508d44bc013d130 100644 (file)
@@ -175,7 +175,7 @@ static int vfio_pci_enable(struct vfio_pci_device *vdev)
 static void vfio_pci_disable(struct vfio_pci_device *vdev)
 {
        struct pci_dev *pdev = vdev->pdev;
-       int bar;
+       int i, bar;
 
        /* Stop the device from further DMA */
        pci_clear_master(pdev);
@@ -186,6 +186,13 @@ static void vfio_pci_disable(struct vfio_pci_device *vdev)
 
        vdev->virq_disabled = false;
 
+       for (i = 0; i < vdev->num_regions; i++)
+               vdev->region[i].ops->release(vdev, &vdev->region[i]);
+
+       vdev->num_regions = 0;
+       kfree(vdev->region);
+       vdev->region = NULL; /* don't krealloc a freed pointer */
+
        vfio_config_free(vdev);
 
        for (bar = PCI_STD_RESOURCES; bar <= PCI_STD_RESOURCE_END; bar++) {
@@ -463,6 +470,51 @@ static int msix_sparse_mmap_cap(struct vfio_pci_device *vdev,
        return 0;
 }
 
+static int region_type_cap(struct vfio_pci_device *vdev,
+                          struct vfio_info_cap *caps,
+                          unsigned int type, unsigned int subtype)
+{
+       struct vfio_info_cap_header *header;
+       struct vfio_region_info_cap_type *cap;
+
+       header = vfio_info_cap_add(caps, sizeof(*cap),
+                                  VFIO_REGION_INFO_CAP_TYPE, 1);
+       if (IS_ERR(header))
+               return PTR_ERR(header);
+
+       cap = container_of(header, struct vfio_region_info_cap_type, header);
+       cap->type = type;
+       cap->subtype = subtype;
+
+       return 0;
+}
+
+int vfio_pci_register_dev_region(struct vfio_pci_device *vdev,
+                                unsigned int type, unsigned int subtype,
+                                const struct vfio_pci_regops *ops,
+                                size_t size, u32 flags, void *data)
+{
+       struct vfio_pci_region *region;
+
+       region = krealloc(vdev->region,
+                         (vdev->num_regions + 1) * sizeof(*region),
+                         GFP_KERNEL);
+       if (!region)
+               return -ENOMEM;
+
+       vdev->region = region;
+       vdev->region[vdev->num_regions].type = type;
+       vdev->region[vdev->num_regions].subtype = subtype;
+       vdev->region[vdev->num_regions].ops = ops;
+       vdev->region[vdev->num_regions].size = size;
+       vdev->region[vdev->num_regions].flags = flags;
+       vdev->region[vdev->num_regions].data = data;
+
+       vdev->num_regions++;
+
+       return 0;
+}
+
 static long vfio_pci_ioctl(void *device_data,
                           unsigned int cmd, unsigned long arg)
 {
@@ -485,7 +537,7 @@ static long vfio_pci_ioctl(void *device_data,
                if (vdev->reset_works)
                        info.flags |= VFIO_DEVICE_FLAGS_RESET;
 
-               info.num_regions = VFIO_PCI_NUM_REGIONS;
+               info.num_regions = VFIO_PCI_NUM_REGIONS + vdev->num_regions;
                info.num_irqs = VFIO_PCI_NUM_IRQS;
 
                return copy_to_user((void __user *)arg, &info, minsz);
@@ -494,7 +546,7 @@ static long vfio_pci_ioctl(void *device_data,
                struct pci_dev *pdev = vdev->pdev;
                struct vfio_region_info info;
                struct vfio_info_cap caps = { .buf = NULL, .size = 0 };
-               int ret;
+               int i, ret;
 
                minsz = offsetofend(struct vfio_region_info, offset);
 
@@ -568,7 +620,21 @@ static long vfio_pci_ioctl(void *device_data,
 
                        break;
                default:
-                       return -EINVAL;
+                       if (info.index >=
+                           VFIO_PCI_NUM_REGIONS + vdev->num_regions)
+                               return -EINVAL;
+
+                       i = info.index - VFIO_PCI_NUM_REGIONS;
+
+                       info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index);
+                       info.size = vdev->region[i].size;
+                       info.flags = vdev->region[i].flags;
+
+                       ret = region_type_cap(vdev, &caps,
+                                             vdev->region[i].type,
+                                             vdev->region[i].subtype);
+                       if (ret)
+                               return ret;
                }
 
                if (caps.size) {
@@ -866,7 +932,7 @@ static ssize_t vfio_pci_rw(void *device_data, char __user *buf,
        unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos);
        struct vfio_pci_device *vdev = device_data;
 
-       if (index >= VFIO_PCI_NUM_REGIONS)
+       if (index >= VFIO_PCI_NUM_REGIONS + vdev->num_regions)
                return -EINVAL;
 
        switch (index) {
@@ -883,6 +949,10 @@ static ssize_t vfio_pci_rw(void *device_data, char __user *buf,
 
        case VFIO_PCI_VGA_REGION_INDEX:
                return vfio_pci_vga_rw(vdev, buf, count, ppos, iswrite);
+       default:
+               index -= VFIO_PCI_NUM_REGIONS;
+               return vdev->region[index].ops->rw(vdev, buf,
+                                                  count, ppos, iswrite);
        }
 
        return -EINVAL;
@@ -1065,6 +1135,7 @@ static void vfio_pci_remove(struct pci_dev *pdev)
                return;
 
        vfio_iommu_group_put(pdev->dev.iommu_group, &pdev->dev);
+       kfree(vdev->region);
        kfree(vdev);
 
        if (vfio_pci_is_vga(pdev)) {
index 0e7394f8f69bca32bd60f05bb013cbb4eea1dbc2..0710bda5ae2c2e103e266e806dbb066fca320a16 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/mutex.h>
 #include <linux/pci.h>
 #include <linux/irqbypass.h>
+#include <linux/types.h>
 
 #ifndef VFIO_PCI_PRIVATE_H
 #define VFIO_PCI_PRIVATE_H
@@ -33,6 +34,25 @@ struct vfio_pci_irq_ctx {
        struct irq_bypass_producer      producer;
 };
 
+struct vfio_pci_device;
+struct vfio_pci_region;
+
+struct vfio_pci_regops {
+       size_t  (*rw)(struct vfio_pci_device *vdev, char __user *buf,
+                     size_t count, loff_t *ppos, bool iswrite);
+       void    (*release)(struct vfio_pci_device *vdev,
+                          struct vfio_pci_region *region);
+};
+
+struct vfio_pci_region {
+       u32                             type;
+       u32                             subtype;
+       const struct vfio_pci_regops    *ops;
+       void                            *data;
+       size_t                          size;
+       u32                             flags;
+};
+
 struct vfio_pci_device {
        struct pci_dev          *pdev;
        void __iomem            *barmap[PCI_STD_RESOURCE_END + 1];
@@ -45,6 +65,8 @@ struct vfio_pci_device {
        struct vfio_pci_irq_ctx *ctx;
        int                     num_ctx;
        int                     irq_type;
+       int                     num_regions;
+       struct vfio_pci_region  *region;
        u8                      msi_qmax;
        u8                      msix_bar;
        u16                     msix_size;
@@ -91,4 +113,9 @@ extern void vfio_pci_uninit_perm_bits(void);
 
 extern int vfio_config_init(struct vfio_pci_device *vdev);
 extern void vfio_config_free(struct vfio_pci_device *vdev);
+
+extern int vfio_pci_register_dev_region(struct vfio_pci_device *vdev,
+                                       unsigned int type, unsigned int subtype,
+                                       const struct vfio_pci_regops *ops,
+                                       size_t size, u32 flags, void *data);
 #endif /* VFIO_PCI_PRIVATE_H */