diff mbox

[RFC,v2,7/9] vfio/pci: Intel IGD graphics support

Message ID 20160213001712.17724.1735.stgit@gimli.home
State New
Headers show

Commit Message

Alex Williamson Feb. 13, 2016, 12:17 a.m. UTC
In order to support device assignment of Intel IGD graphics, we need
support three quirks, which are enabled through vfio using device
specific regions.

The first quirk is to expose the OpRegion of the host.  This contains
a Video BIOS Table (VBT), that often provides graphics modes that are
useful for laptops.  We store the OpRegion data from vfio in a buffer
and pass it to SeaBIOS via fw_cfg.  The ASL Storage register (0xFC) is
made writable so the BIOS can use this register to inform the guest OS
of the allocated OpRegion in VM memory.  If we were to find a use for
any passthrough of the host OpRegion into the guest (ie. volatile
status bits and whatnot), we could use the ASLS register write as a
trigger to map the host OpRegion into the VM.

The second quirk is to copy some of the revisions and identification
fields from PCI config space on the host bridge into the guest.  Vfio
provides a separate region for providing read-only access to the host
bridge config space to make this possible.

The third and final quirk is to create an LPC/ISA bridge in the VM at
address 00:1f.0 and also populate some of its config space with host
data for revision and identification.  Vfio provides yet another
region for read-only access to this configuration space on the host.

For Broadwell and newer graphics and new enough guest graphics
drivers (at least in Windows), these latter two quirks supposedly
aren't necessary, but since we don't know what driver is running in
the guest, we currently enable these regardless of the graphics
revision.

Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
---
 hw/vfio/common.c              |    2 
 hw/vfio/pci-quirks.c          |  176 +++++++++++++++++++++++++++++++++++++++++
 hw/vfio/pci.c                 |   48 +++++++++++
 hw/vfio/pci.h                 |    8 ++
 include/hw/vfio/vfio-common.h |    2 
 trace-events                  |    4 +
 6 files changed, 238 insertions(+), 2 deletions(-)
diff mbox

Patch

diff --git a/hw/vfio/common.c b/hw/vfio/common.c
index 879a657..c201bee 100644
--- a/hw/vfio/common.c
+++ b/hw/vfio/common.c
@@ -493,7 +493,7 @@  static void vfio_listener_release(VFIOContainer *container)
     memory_listener_unregister(&container->listener);
 }
 
-static struct vfio_info_cap_header *
+struct vfio_info_cap_header *
 vfio_get_region_info_cap(struct vfio_region_info *info, uint16_t id)
 {
     struct vfio_info_cap_header *hdr;
diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c
index 49ecf11..f6e83b0 100644
--- a/hw/vfio/pci-quirks.c
+++ b/hw/vfio/pci-quirks.c
@@ -11,9 +11,11 @@ 
  */
 
 #include "qemu/osdep.h"
+#include "hw/nvram/fw_cfg.h"
 #include "pci.h"
 #include "trace.h"
 #include "qemu/range.h"
+#include "qemu/error-report.h"
 
 /* Use uin32_t for vendor & device so PCI_ANY_ID expands and cannot match hw */
 static bool vfio_pci_is(VFIOPCIDevice *vdev, uint32_t vendor, uint32_t device)
@@ -1203,3 +1205,177 @@  void vfio_setup_resetfn_quirk(VFIOPCIDevice *vdev)
         break;
     }
 }
+
+/*
+ * Intel IGD support
+ *
+ * We need to do a few things to support Intel Integrated Graphics Devices:
+ *  1) Expose the OpRegion if one is provided to us
+ *  2) Copy key PCI config space register values from the host bridge
+ *  3) Create an LPC/ISA bridge and do the same for it.
+ *
+ * Each of these is supported in vfio-pci through the use of device specific
+ * regions.  The main vfio-pci driver calls out to the init functions here
+ * for each of those found regions.  All three of these operations are
+ * necessary for SandyBridge, IvyBridge, ValleyView, and Haswell graphics.
+ * For newer versions of IGD, such as Broadwell and SkyLake, Intel supports
+ * an assignment mode without requiring 2) and 3).  Since we don't know what
+ * driver will be used in the guest, we don't do any filtering here but that
+ * may change at some point.
+ */
+int vfio_pci_igd_opregion_init(VFIOPCIDevice *vdev,
+                               struct vfio_region_info *region)
+{
+    DeviceClass *dc = DEVICE_GET_CLASS(vdev);
+    int ret;
+
+    if (vdev->pdev.qdev.hotplugged) {
+        return -EINVAL;
+    }
+
+    dc->hotpluggable = false;
+
+    vdev->igd_opregion = g_malloc0(region->size);
+    ret = pread(vdev->vbasedev.fd, vdev->igd_opregion,
+                region->size, region->offset);
+    if (ret != region->size) {
+        error_report("vfio: Error reading IGD OpRegion\n");
+        g_free(vdev->igd_opregion);
+        vdev->igd_opregion = NULL;
+        return -EINVAL;
+    }
+
+    fw_cfg_add_file(fw_cfg_find(), "etc/igd-opregion",
+                    vdev->igd_opregion, region->size);
+
+    trace_vfio_pci_igd_opregion_enabled(vdev->vbasedev.name);
+
+    return 0;
+}
+
+/* Define config register sets to copy from the host devices */
+typedef struct {
+    uint8_t offset;
+    uint8_t len;
+} IGDHostInfo;
+
+static const IGDHostInfo igd_host_bridge_infos[] = {
+    {PCI_REVISION_ID,         2},
+    {PCI_SUBSYSTEM_VENDOR_ID, 2},
+    {PCI_SUBSYSTEM_ID,        2},
+};
+
+static const IGDHostInfo igd_lpc_bridge_infos[] = {
+    {PCI_VENDOR_ID,           2},
+    {PCI_DEVICE_ID,           2},
+    {PCI_REVISION_ID,         2},
+    {PCI_SUBSYSTEM_VENDOR_ID, 2},
+    {PCI_SUBSYSTEM_ID,        2},
+};
+
+static int vfio_pci_igd_copy(VFIOPCIDevice *vdev, PCIDevice *pdev,
+                             struct vfio_region_info *region,
+                             const IGDHostInfo *list, int len)
+{
+    int i, ret;
+
+    for (i = 0; i < len; i++) {
+        ret = pread(vdev->vbasedev.fd, pdev->config + list[i].offset,
+                    list[i].len, region->offset + list[i].offset);
+        if (ret != list[i].len) {
+            error_report("IGD copy failed: %m\n");
+            return -errno;
+        }
+    }
+
+    return 0;
+}
+
+int vfio_pci_igd_host_init(VFIOPCIDevice *vdev,
+                           struct vfio_region_info *region)
+{
+    DeviceClass *dc = DEVICE_GET_CLASS(vdev);
+    PCIBus *bus;
+    PCIDevice *host_bridge;
+    int ret;
+
+    if (vdev->pdev.qdev.hotplugged) {
+        return -EINVAL;
+    }
+
+    dc->hotpluggable = false;
+
+    bus = pci_device_root_bus(&vdev->pdev);
+    host_bridge = pci_find_device(bus, 0, PCI_DEVFN(0, 0));
+
+    if (!host_bridge) {
+        error_report("Can't find host bridge");
+        return -ENODEV;
+    }
+
+    ret = vfio_pci_igd_copy(vdev, host_bridge, region, igd_host_bridge_infos,
+                            ARRAY_SIZE(igd_host_bridge_infos));
+    if (!ret) {
+        trace_vfio_pci_igd_host_bridge_enabled(vdev->vbasedev.name);
+    }
+
+    return ret;
+}
+
+static void vfio_pci_igd_lpc_bridge_realize(PCIDevice *pdev, Error **errp)
+{
+    if (pdev->devfn != PCI_DEVFN(0x1f, 0)) {
+        error_setg(errp, "VFIO dummy ISA/LPC bridge must have address 1f.0");
+        return;
+    }
+}
+
+static void vfio_pci_igd_lpc_bridge_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+    dc->desc = "VFIO dummy ISA/LPC bridge for IGD assignment";
+    dc->hotpluggable = false;
+    k->realize = vfio_pci_igd_lpc_bridge_realize;
+    k->class_id = PCI_CLASS_BRIDGE_ISA;
+}
+
+static TypeInfo vfio_pci_igd_lpc_bridge_info = {
+    .name = "vfio-pci-igd-lpc-bridge",
+    .parent = TYPE_PCI_DEVICE,
+    .class_init = vfio_pci_igd_lpc_bridge_class_init,
+};
+
+static void vfio_pci_igd_register_types(void)
+{
+    type_register_static(&vfio_pci_igd_lpc_bridge_info);
+}
+
+type_init(vfio_pci_igd_register_types)
+
+int vfio_pci_igd_lpc_init(VFIOPCIDevice *vdev,
+                           struct vfio_region_info *region)
+{
+    DeviceClass *dc = DEVICE_GET_CLASS(vdev);
+    PCIDevice *lpc_bridge;
+    int ret;
+
+    if (vdev->pdev.qdev.hotplugged) {
+        return -EINVAL;
+    }
+
+    dc->hotpluggable = false;
+
+    lpc_bridge = pci_create_simple(pci_device_root_bus(&vdev->pdev),
+                                   PCI_DEVFN(0x1f, 0),
+                                   "vfio-pci-igd-lpc-bridge");
+
+    ret = vfio_pci_igd_copy(vdev, lpc_bridge, region, igd_lpc_bridge_infos,
+                            ARRAY_SIZE(igd_lpc_bridge_infos));
+    if (!ret) {
+        trace_vfio_pci_igd_lpc_bridge_enabled(vdev->vbasedev.name);
+    }
+
+    return ret;
+}
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
index 8e20781..4c376a8 100644
--- a/hw/vfio/pci.c
+++ b/hw/vfio/pci.c
@@ -2123,6 +2123,53 @@  static int vfio_populate_device(VFIOPCIDevice *vdev)
         QLIST_INIT(&vdev->vga->region[QEMU_PCI_VGA_IO_HI].quirks);
     }
 
+    if (vbasedev->num_regions > VFIO_PCI_NUM_REGIONS) {
+        for (i = VFIO_PCI_NUM_REGIONS; i < vbasedev->num_regions; i++) {
+            struct vfio_info_cap_header *hdr;
+            struct vfio_region_info_cap_type *type;
+
+            ret = vfio_get_region_info(vbasedev, i, &reg_info);
+            if (ret) {
+                continue;
+            }
+
+            hdr = vfio_get_region_info_cap(reg_info, VFIO_REGION_INFO_CAP_TYPE);
+            if (!hdr) {
+                g_free(reg_info);
+                continue;
+            }
+
+            type = container_of(hdr, struct vfio_region_info_cap_type, header);
+            if (type->type ==
+                (VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL) &&
+                type->subtype == VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION) {
+
+                ret = vfio_pci_igd_opregion_init(vdev, reg_info);
+                if (ret) {
+                    goto error;
+                }
+            } else if (type->type ==
+                (VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL) &&
+                type->subtype == VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG) {
+
+                ret = vfio_pci_igd_host_init(vdev, reg_info);
+                if (ret) {
+                    goto error;
+                }
+            } else if (type->type ==
+                (VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL) &&
+                type->subtype == VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG) {
+
+                ret = vfio_pci_igd_lpc_init(vdev, reg_info);
+                if (ret) {
+                    goto error;
+                }
+            }
+
+            g_free(reg_info);
+        }
+    }
+
     irq_info.index = VFIO_PCI_ERR_IRQ_INDEX;
 
     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_IRQ_INFO, &irq_info);
@@ -2557,6 +2604,7 @@  static void vfio_instance_finalize(Object *obj)
     vfio_bars_finalize(vdev);
     g_free(vdev->emulated_config_bits);
     g_free(vdev->rom);
+    g_free(vdev->igd_opregion);
     vfio_put_device(vdev);
     vfio_put_group(group);
 }
diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h
index b8a7189..b183251 100644
--- a/hw/vfio/pci.h
+++ b/hw/vfio/pci.h
@@ -115,6 +115,7 @@  typedef struct VFIOPCIDevice {
     int interrupt; /* Current interrupt type */
     VFIOBAR bars[PCI_NUM_REGIONS - 1]; /* No ROM */
     VFIOVGA *vga; /* 0xa0000, 0x3b0, 0x3c0 */
+    void *igd_opregion;
     PCIHostDeviceAddress host;
     EventNotifier err_notifier;
     EventNotifier req_notifier;
@@ -157,4 +158,11 @@  void vfio_bar_quirk_exit(VFIOPCIDevice *vdev, int nr);
 void vfio_bar_quirk_finalize(VFIOPCIDevice *vdev, int nr);
 void vfio_setup_resetfn_quirk(VFIOPCIDevice *vdev);
 
+int vfio_pci_igd_opregion_init(VFIOPCIDevice *vdev,
+                               struct vfio_region_info *region);
+int vfio_pci_igd_host_init(VFIOPCIDevice *vdev,
+                           struct vfio_region_info *region);
+int vfio_pci_igd_lpc_init(VFIOPCIDevice *vdev,
+                           struct vfio_region_info *region);
+
 #endif /* HW_VFIO_VFIO_PCI_H */
diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h
index 594905a..45ba6a3 100644
--- a/include/hw/vfio/vfio-common.h
+++ b/include/hw/vfio/vfio-common.h
@@ -146,6 +146,8 @@  int vfio_get_device(VFIOGroup *group, const char *name,
                     VFIODevice *vbasedev);
 int vfio_get_region_info(VFIODevice *vbasedev, int index,
                          struct vfio_region_info **info);
+struct vfio_info_cap_header *
+    vfio_get_region_info_cap(struct vfio_region_info *info, uint16_t id);
 
 extern const MemoryRegionOps vfio_region_ops;
 extern QLIST_HEAD(vfio_group_head, VFIOGroup) vfio_group_list;
diff --git a/trace-events b/trace-events
index c2f48af..3233dd1 100644
--- a/trace-events
+++ b/trace-events
@@ -1715,7 +1715,9 @@  vfio_quirk_ati_bonaire_reset_no_smc(const char *name) "%s"
 vfio_quirk_ati_bonaire_reset_timeout(const char *name) "%s"
 vfio_quirk_ati_bonaire_reset_done(const char *name) "%s"
 vfio_quirk_ati_bonaire_reset(const char *name) "%s"
-
+vfio_pci_igd_opregion_enabled(const char *name) "%s"
+vfio_pci_igd_host_bridge_enabled(const char *name) "%s"
+vfio_pci_igd_lpc_bridge_enabled(const char *name) "%s"
 
 # hw/vfio/vfio-common.c
 vfio_region_write(const char *name, int index, uint64_t addr, uint64_t data, unsigned size) " (%s:region%d+0x%"PRIx64", 0x%"PRIx64 ", %d)"