|
@@ -111,6 +111,7 @@ static inline bool vfio_pci_is_vga(struct pci_dev *pdev)
|
|
|
}
|
|
|
|
|
|
static void vfio_pci_try_bus_reset(struct vfio_pci_device *vdev);
|
|
|
+static void vfio_pci_disable(struct vfio_pci_device *vdev);
|
|
|
|
|
|
static int vfio_pci_enable(struct vfio_pci_device *vdev)
|
|
|
{
|
|
@@ -169,13 +170,26 @@ static int vfio_pci_enable(struct vfio_pci_device *vdev)
|
|
|
if (!vfio_vga_disabled() && vfio_pci_is_vga(pdev))
|
|
|
vdev->has_vga = true;
|
|
|
|
|
|
+
|
|
|
+ if (vfio_pci_is_vga(pdev) &&
|
|
|
+ pdev->vendor == PCI_VENDOR_ID_INTEL &&
|
|
|
+ IS_ENABLED(CONFIG_VFIO_PCI_IGD)) {
|
|
|
+ ret = vfio_pci_igd_init(vdev);
|
|
|
+ if (ret) {
|
|
|
+ dev_warn(&vdev->pdev->dev,
|
|
|
+ "Failed to setup Intel IGD regions\n");
|
|
|
+ vfio_pci_disable(vdev);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
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 +200,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++) {
|
|
@@ -421,6 +442,93 @@ static int vfio_pci_for_each_slot_or_bus(struct pci_dev *pdev,
|
|
|
return walk.ret;
|
|
|
}
|
|
|
|
|
|
+static int msix_sparse_mmap_cap(struct vfio_pci_device *vdev,
|
|
|
+ struct vfio_info_cap *caps)
|
|
|
+{
|
|
|
+ struct vfio_info_cap_header *header;
|
|
|
+ struct vfio_region_info_cap_sparse_mmap *sparse;
|
|
|
+ size_t end, size;
|
|
|
+ int nr_areas = 2, i = 0;
|
|
|
+
|
|
|
+ end = pci_resource_len(vdev->pdev, vdev->msix_bar);
|
|
|
+
|
|
|
+ /* If MSI-X table is aligned to the start or end, only one area */
|
|
|
+ if (((vdev->msix_offset & PAGE_MASK) == 0) ||
|
|
|
+ (PAGE_ALIGN(vdev->msix_offset + vdev->msix_size) >= end))
|
|
|
+ nr_areas = 1;
|
|
|
+
|
|
|
+ size = sizeof(*sparse) + (nr_areas * sizeof(*sparse->areas));
|
|
|
+
|
|
|
+ header = vfio_info_cap_add(caps, size,
|
|
|
+ VFIO_REGION_INFO_CAP_SPARSE_MMAP, 1);
|
|
|
+ if (IS_ERR(header))
|
|
|
+ return PTR_ERR(header);
|
|
|
+
|
|
|
+ sparse = container_of(header,
|
|
|
+ struct vfio_region_info_cap_sparse_mmap, header);
|
|
|
+ sparse->nr_areas = nr_areas;
|
|
|
+
|
|
|
+ if (vdev->msix_offset & PAGE_MASK) {
|
|
|
+ sparse->areas[i].offset = 0;
|
|
|
+ sparse->areas[i].size = vdev->msix_offset & PAGE_MASK;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (PAGE_ALIGN(vdev->msix_offset + vdev->msix_size) < end) {
|
|
|
+ sparse->areas[i].offset = PAGE_ALIGN(vdev->msix_offset +
|
|
|
+ vdev->msix_size);
|
|
|
+ sparse->areas[i].size = end - sparse->areas[i].offset;
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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)
|
|
|
{
|
|
@@ -443,7 +551,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) ?
|
|
@@ -452,6 +560,8 @@ static long vfio_pci_ioctl(void *device_data,
|
|
|
} else if (cmd == VFIO_DEVICE_GET_REGION_INFO) {
|
|
|
struct pci_dev *pdev = vdev->pdev;
|
|
|
struct vfio_region_info info;
|
|
|
+ struct vfio_info_cap caps = { .buf = NULL, .size = 0 };
|
|
|
+ int i, ret;
|
|
|
|
|
|
minsz = offsetofend(struct vfio_region_info, offset);
|
|
|
|
|
@@ -480,8 +590,15 @@ static long vfio_pci_ioctl(void *device_data,
|
|
|
VFIO_REGION_INFO_FLAG_WRITE;
|
|
|
if (IS_ENABLED(CONFIG_VFIO_PCI_MMAP) &&
|
|
|
pci_resource_flags(pdev, info.index) &
|
|
|
- IORESOURCE_MEM && info.size >= PAGE_SIZE)
|
|
|
+ IORESOURCE_MEM && info.size >= PAGE_SIZE) {
|
|
|
info.flags |= VFIO_REGION_INFO_FLAG_MMAP;
|
|
|
+ if (info.index == vdev->msix_bar) {
|
|
|
+ ret = msix_sparse_mmap_cap(vdev, &caps);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
break;
|
|
|
case VFIO_PCI_ROM_REGION_INDEX:
|
|
|
{
|
|
@@ -493,8 +610,14 @@ static long vfio_pci_ioctl(void *device_data,
|
|
|
|
|
|
/* Report the BAR size, not the ROM size */
|
|
|
info.size = pci_resource_len(pdev, info.index);
|
|
|
- if (!info.size)
|
|
|
- break;
|
|
|
+ if (!info.size) {
|
|
|
+ /* Shadow ROMs appear as PCI option ROMs */
|
|
|
+ if (pdev->resource[PCI_ROM_RESOURCE].flags &
|
|
|
+ IORESOURCE_ROM_SHADOW)
|
|
|
+ info.size = 0x20000;
|
|
|
+ else
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
/* Is it really there? */
|
|
|
io = pci_map_rom(pdev, &size);
|
|
@@ -518,7 +641,40 @@ 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) {
|
|
|
+ info.flags |= VFIO_REGION_INFO_FLAG_CAPS;
|
|
|
+ if (info.argsz < sizeof(info) + caps.size) {
|
|
|
+ info.argsz = sizeof(info) + caps.size;
|
|
|
+ info.cap_offset = 0;
|
|
|
+ } else {
|
|
|
+ vfio_info_cap_shift(&caps, sizeof(info));
|
|
|
+ if (copy_to_user((void __user *)arg +
|
|
|
+ sizeof(info), caps.buf,
|
|
|
+ caps.size)) {
|
|
|
+ kfree(caps.buf);
|
|
|
+ return -EFAULT;
|
|
|
+ }
|
|
|
+ info.cap_offset = sizeof(info);
|
|
|
+ }
|
|
|
+
|
|
|
+ kfree(caps.buf);
|
|
|
}
|
|
|
|
|
|
return copy_to_user((void __user *)arg, &info, minsz) ?
|
|
@@ -798,7 +954,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) {
|
|
@@ -815,6 +971,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;
|
|
@@ -997,6 +1157,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)) {
|