vfio/pci: Intel IGD host and LCP bridge config space access
authorAlex Williamson <alex.williamson@redhat.com>
Mon, 22 Feb 2016 23:02:45 +0000 (16:02 -0700)
committerAlex Williamson <alex.williamson@redhat.com>
Mon, 22 Feb 2016 23:10:09 +0000 (16:10 -0700)
Provide read-only access to PCI config space of the PCI host bridge
and LPC bridge through device specific regions.  This may be used to
configure a VM with matching register contents to satisfy driver
requirements.  Providing this through the vfio file descriptor removes
an additional userspace requirement for access through pci-sysfs and
removes the CAP_SYS_ADMIN requirement that doesn't appear to apply to
the specific devices we're accessing.

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

index cb2624db37d893af72539f38444730c451d190b0..74a3752974945147c4c2f89dfd4dae7d7701a58c 100644 (file)
@@ -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)
 {
@@ -170,10 +171,16 @@ static int vfio_pci_enable(struct vfio_pci_device *vdev)
                vdev->has_vga = true;
 
 
-       if (vfio_pci_is_vga(pdev) && pdev->vendor == PCI_VENDOR_ID_INTEL) {
-               if (vfio_pci_igd_opregion_init(vdev) == 0)
-                       dev_info(&pdev->dev,
-                                "Intel IGD OpRegion support enabled\n");
+       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;
index 3b6a6f7b367bae35722b7a1de5a8e642a14d4a4e..6394b168ef29248fc1b0c6c875141a0d13f4c458 100644 (file)
@@ -55,7 +55,7 @@ static const struct vfio_pci_regops vfio_pci_igd_regops = {
        .release        = vfio_pci_igd_release,
 };
 
-int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev)
+static int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev)
 {
        __le32 *dwordp = (__le32 *)(vdev->vconfig + OPREGION_PCI_ADDR);
        u32 addr, size;
@@ -109,3 +109,172 @@ int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev)
 
        return ret;
 }
+
+static size_t vfio_pci_igd_cfg_rw(struct vfio_pci_device *vdev,
+                                 char __user *buf, size_t count, loff_t *ppos,
+                                 bool iswrite)
+{
+       unsigned int i = VFIO_PCI_OFFSET_TO_INDEX(*ppos) - VFIO_PCI_NUM_REGIONS;
+       struct pci_dev *pdev = vdev->region[i].data;
+       loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK;
+       size_t size;
+       int ret;
+
+       if (pos >= vdev->region[i].size || iswrite)
+               return -EINVAL;
+
+       size = count = min(count, (size_t)(vdev->region[i].size - pos));
+
+       if ((pos & 1) && size) {
+               u8 val;
+
+               ret = pci_user_read_config_byte(pdev, pos, &val);
+               if (ret)
+                       return pcibios_err_to_errno(ret);
+
+               if (copy_to_user(buf + count - size, &val, 1))
+                       return -EFAULT;
+
+               pos++;
+               size--;
+       }
+
+       if ((pos & 3) && size > 2) {
+               u16 val;
+
+               ret = pci_user_read_config_word(pdev, pos, &val);
+               if (ret)
+                       return pcibios_err_to_errno(ret);
+
+               val = cpu_to_le16(val);
+               if (copy_to_user(buf + count - size, &val, 2))
+                       return -EFAULT;
+
+               pos += 2;
+               size -= 2;
+       }
+
+       while (size > 3) {
+               u32 val;
+
+               ret = pci_user_read_config_dword(pdev, pos, &val);
+               if (ret)
+                       return pcibios_err_to_errno(ret);
+
+               val = cpu_to_le32(val);
+               if (copy_to_user(buf + count - size, &val, 4))
+                       return -EFAULT;
+
+               pos += 4;
+               size -= 4;
+       }
+
+       while (size >= 2) {
+               u16 val;
+
+               ret = pci_user_read_config_word(pdev, pos, &val);
+               if (ret)
+                       return pcibios_err_to_errno(ret);
+
+               val = cpu_to_le16(val);
+               if (copy_to_user(buf + count - size, &val, 2))
+                       return -EFAULT;
+
+               pos += 2;
+               size -= 2;
+       }
+
+       while (size) {
+               u8 val;
+
+               ret = pci_user_read_config_byte(pdev, pos, &val);
+               if (ret)
+                       return pcibios_err_to_errno(ret);
+
+               if (copy_to_user(buf + count - size, &val, 1))
+                       return -EFAULT;
+
+               pos++;
+               size--;
+       }
+
+       *ppos += count;
+
+       return count;
+}
+
+static void vfio_pci_igd_cfg_release(struct vfio_pci_device *vdev,
+                                    struct vfio_pci_region *region)
+{
+       struct pci_dev *pdev = region->data;
+
+       pci_dev_put(pdev);
+}
+
+static const struct vfio_pci_regops vfio_pci_igd_cfg_regops = {
+       .rw             = vfio_pci_igd_cfg_rw,
+       .release        = vfio_pci_igd_cfg_release,
+};
+
+static int vfio_pci_igd_cfg_init(struct vfio_pci_device *vdev)
+{
+       struct pci_dev *host_bridge, *lpc_bridge;
+       int ret;
+
+       host_bridge = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
+       if (!host_bridge)
+               return -ENODEV;
+
+       if (host_bridge->vendor != PCI_VENDOR_ID_INTEL ||
+           host_bridge->class != (PCI_CLASS_BRIDGE_HOST << 8)) {
+               pci_dev_put(host_bridge);
+               return -EINVAL;
+       }
+
+       ret = vfio_pci_register_dev_region(vdev,
+               PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE,
+               VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG,
+               &vfio_pci_igd_cfg_regops, host_bridge->cfg_size,
+               VFIO_REGION_INFO_FLAG_READ, host_bridge);
+       if (ret) {
+               pci_dev_put(host_bridge);
+               return ret;
+       }
+
+       lpc_bridge = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x1f, 0));
+       if (!lpc_bridge)
+               return -ENODEV;
+
+       if (lpc_bridge->vendor != PCI_VENDOR_ID_INTEL ||
+           lpc_bridge->class != (PCI_CLASS_BRIDGE_ISA << 8)) {
+               pci_dev_put(lpc_bridge);
+               return -EINVAL;
+       }
+
+       ret = vfio_pci_register_dev_region(vdev,
+               PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE,
+               VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG,
+               &vfio_pci_igd_cfg_regops, lpc_bridge->cfg_size,
+               VFIO_REGION_INFO_FLAG_READ, lpc_bridge);
+       if (ret) {
+               pci_dev_put(lpc_bridge);
+               return ret;
+       }
+
+       return 0;
+}
+
+int vfio_pci_igd_init(struct vfio_pci_device *vdev)
+{
+       int ret;
+
+       ret = vfio_pci_igd_opregion_init(vdev);
+       if (ret)
+               return ret;
+
+       ret = vfio_pci_igd_cfg_init(vdev);
+       if (ret)
+               return ret;
+
+       return 0;
+}
index 19f7699ac69956b729d858956420acc48f346147..8a7d546d18a0d91be62a6836df508d38cdf57b3a 100644 (file)
@@ -123,9 +123,9 @@ extern int vfio_pci_register_dev_region(struct vfio_pci_device *vdev,
                                        const struct vfio_pci_regops *ops,
                                        size_t size, u32 flags, void *data);
 #ifdef CONFIG_VFIO_PCI_IGD
-extern int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev);
+extern int vfio_pci_igd_init(struct vfio_pci_device *vdev);
 #else
-static inline int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev)
+static inline int vfio_pci_igd_init(struct vfio_pci_device *vdev)
 {
        return -ENODEV;
 }
index e622982dbc53ad5213e9483df344ff0ab748f9e7..255a2113f53cb3845b9fb8a2b06827199af2052c 100644 (file)
@@ -283,7 +283,10 @@ struct vfio_region_info_cap_type {
 #define VFIO_REGION_TYPE_PCI_VENDOR_TYPE       (1 << 31)
 #define VFIO_REGION_TYPE_PCI_VENDOR_MASK       (0xffff)
 
+/* 8086 Vendor sub-types */
 #define VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION (1)
+#define VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG (2)
+#define VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG  (3)
 
 /**
  * VFIO_DEVICE_GET_IRQ_INFO - _IOWR(VFIO_TYPE, VFIO_BASE + 9,