diff options
author | Alex Williamson <alex.williamson@redhat.com> | 2016-02-23 02:02:39 +0300 |
---|---|---|
committer | Alex Williamson <alex.williamson@redhat.com> | 2016-02-23 02:10:09 +0300 |
commit | 28541d41c9e04cb2ddbf93facd1e376dd5613360 (patch) | |
tree | bfd5234a1dadc51d7f73cd69f36ebb71e7df224f /drivers/vfio/pci | |
parent | c7bb4cb40f89224dc55755178343728e30dd583a (diff) | |
download | linux-28541d41c9e04cb2ddbf93facd1e376dd5613360.tar.xz |
vfio/pci: Add infrastructure for additional device specific regions
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>
Diffstat (limited to 'drivers/vfio/pci')
-rw-r--r-- | drivers/vfio/pci/vfio_pci.c | 81 | ||||
-rw-r--r-- | drivers/vfio/pci/vfio_pci_private.h | 27 |
2 files changed, 103 insertions, 5 deletions
diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c index 4682207b1ac8..813a2e67aa0c 100644 --- a/drivers/vfio/pci/vfio_pci.c +++ b/drivers/vfio/pci/vfio_pci.c @@ -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)) { diff --git a/drivers/vfio/pci/vfio_pci_private.h b/drivers/vfio/pci/vfio_pci_private.h index 0e7394f8f69b..0710bda5ae2c 100644 --- a/drivers/vfio/pci/vfio_pci_private.h +++ b/drivers/vfio/pci/vfio_pci_private.h @@ -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 */ |