9ae3a8
From 81ee28858d0082b300184c9fcfc10480c67aa74d Mon Sep 17 00:00:00 2001
9ae3a8
From: Alex Williamson <alex.williamson@redhat.com>
9ae3a8
Date: Thu, 7 Aug 2014 21:03:08 +0200
9ae3a8
Subject: [PATCH 5/7] vfio-pci: Fix MSI-X masking performance
9ae3a8
9ae3a8
Message-id: <20140807210308.11689.93757.stgit@gimli.home>
9ae3a8
Patchwork-id: 60481
9ae3a8
O-Subject: [RHEL7.0/z qemu-kvm PATCH v2 4/6] vfio-pci: Fix MSI-X masking performance
9ae3a8
Bugzilla: 1098976
9ae3a8
RH-Acked-by: Bandan Das <bsd@redhat.com>
9ae3a8
RH-Acked-by: Amos Kong <akong@redhat.com>
9ae3a8
RH-Acked-by: Laszlo Ersek <lersek@redhat.com>
9ae3a8
9ae3a8
There are still old guests out there that over-exercise MSI-X masking.
9ae3a8
The current code completely sets-up and tears-down an MSI-X vector on
9ae3a8
the "use" and "release" callbacks.  While this is functional, it can
9ae3a8
slow an old guest to a crawl.  We can easily skip the KVM parts of
9ae3a8
this so that we keep the MSI route and irqfd setup.  We do however
9ae3a8
need to switch VFIO to trigger a different eventfd while masked.
9ae3a8
Actually, we have the option of continuing to use -1 to disable the
9ae3a8
trigger, but by using another EventNotifier we can allow the MSI-X
9ae3a8
core to emulate pending bits and re-fire the vector once unmasked.
9ae3a8
MSI code gets updated as well to use the same setup and teardown
9ae3a8
structures and functions.
9ae3a8
9ae3a8
Prior to this change, an igbvf assigned to a RHEL5 guest gets about
9ae3a8
20Mbps and 50 transactions/s with netperf (remote or VF->PF).  With
9ae3a8
this change, we get line rate and 3k transactions/s remote or 2Gbps
9ae3a8
and 6k+ transactions/s to the PF.  No significant change is expected
9ae3a8
for newer guests with more well behaved MSI-X support.
9ae3a8
9ae3a8
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
9ae3a8
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
9ae3a8
---
9ae3a8
 hw/misc/vfio.c | 233 ++++++++++++++++++++++++++++++++-------------------------
9ae3a8
 1 file changed, 131 insertions(+), 102 deletions(-)
9ae3a8
9ae3a8
diff --git a/hw/misc/vfio.c b/hw/misc/vfio.c
9ae3a8
index 5f0460a..bd37924 100644
9ae3a8
--- a/hw/misc/vfio.c
9ae3a8
+++ b/hw/misc/vfio.c
9ae3a8
@@ -120,6 +120,7 @@ typedef struct VFIOINTx {
9ae3a8
 
9ae3a8
 typedef struct VFIOMSIVector {
9ae3a8
     EventNotifier interrupt; /* eventfd triggered on interrupt */
9ae3a8
+    EventNotifier kvm_interrupt; /* eventfd triggered for KVM irqfd bypass */
9ae3a8
     struct VFIODevice *vdev; /* back pointer to device */
9ae3a8
     MSIMessage msg; /* cache the MSI message so we know when it changes */
9ae3a8
     int virq; /* KVM irqchip route for QEMU bypass */
9ae3a8
@@ -663,10 +664,11 @@ static int vfio_enable_vectors(VFIODevice *vdev, bool msix)
9ae3a8
     for (i = 0; i < vdev->nr_vectors; i++) {
9ae3a8
         if (!vdev->msi_vectors[i].use) {
9ae3a8
             fds[i] = -1;
9ae3a8
-            continue;
9ae3a8
+        } else if (vdev->msi_vectors[i].virq >= 0) {
9ae3a8
+            fds[i] = event_notifier_get_fd(&vdev->msi_vectors[i].kvm_interrupt);
9ae3a8
+        } else {
9ae3a8
+            fds[i] = event_notifier_get_fd(&vdev->msi_vectors[i].interrupt);
9ae3a8
         }
9ae3a8
-
9ae3a8
-        fds[i] = event_notifier_get_fd(&vdev->msi_vectors[i].interrupt);
9ae3a8
     }
9ae3a8
 
9ae3a8
     ret = ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, irq_set);
9ae3a8
@@ -676,6 +678,52 @@ static int vfio_enable_vectors(VFIODevice *vdev, bool msix)
9ae3a8
     return ret;
9ae3a8
 }
9ae3a8
 
9ae3a8
+static void vfio_add_kvm_msi_virq(VFIOMSIVector *vector, MSIMessage *msg,
9ae3a8
+                                  bool msix)
9ae3a8
+{
9ae3a8
+    int virq;
9ae3a8
+
9ae3a8
+    if ((msix && !VFIO_ALLOW_KVM_MSIX) ||
9ae3a8
+        (!msix && !VFIO_ALLOW_KVM_MSI) || !msg) {
9ae3a8
+        return;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    if (event_notifier_init(&vector->kvm_interrupt, 0)) {
9ae3a8
+        return;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    virq = kvm_irqchip_add_msi_route(kvm_state, *msg);
9ae3a8
+    if (virq < 0) {
9ae3a8
+        event_notifier_cleanup(&vector->kvm_interrupt);
9ae3a8
+        return;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    if (kvm_irqchip_add_irqfd_notifier(kvm_state, &vector->kvm_interrupt,
9ae3a8
+                                       virq) < 0) {
9ae3a8
+        kvm_irqchip_release_virq(kvm_state, virq);
9ae3a8
+        event_notifier_cleanup(&vector->kvm_interrupt);
9ae3a8
+        return;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    vector->msg = *msg;
9ae3a8
+    vector->virq = virq;
9ae3a8
+}
9ae3a8
+
9ae3a8
+static void vfio_remove_kvm_msi_virq(VFIOMSIVector *vector)
9ae3a8
+{
9ae3a8
+    kvm_irqchip_remove_irqfd_notifier(kvm_state, &vector->kvm_interrupt,
9ae3a8
+                                      vector->virq);
9ae3a8
+    kvm_irqchip_release_virq(kvm_state, vector->virq);
9ae3a8
+    vector->virq = -1;
9ae3a8
+    event_notifier_cleanup(&vector->kvm_interrupt);
9ae3a8
+}
9ae3a8
+
9ae3a8
+static void vfio_update_kvm_msi_virq(VFIOMSIVector *vector, MSIMessage msg)
9ae3a8
+{
9ae3a8
+    kvm_irqchip_update_msi_route(kvm_state, vector->virq, msg);
9ae3a8
+    vector->msg = msg;
9ae3a8
+}
9ae3a8
+
9ae3a8
 static int vfio_msix_vector_do_use(PCIDevice *pdev, unsigned int nr,
9ae3a8
                                    MSIMessage *msg, IOHandler *handler)
9ae3a8
 {
9ae3a8
@@ -688,30 +736,32 @@ static int vfio_msix_vector_do_use(PCIDevice *pdev, unsigned int nr,
9ae3a8
             vdev->host.function, nr);
9ae3a8
 
9ae3a8
     vector = &vdev->msi_vectors[nr];
9ae3a8
-    vector->vdev = vdev;
9ae3a8
-    vector->use = true;
9ae3a8
-
9ae3a8
-    msix_vector_use(pdev, nr);
9ae3a8
 
9ae3a8
-    if (event_notifier_init(&vector->interrupt, 0)) {
9ae3a8
-        error_report("vfio: Error: event_notifier_init failed");
9ae3a8
+    if (!vector->use) {
9ae3a8
+        vector->vdev = vdev;
9ae3a8
+        vector->virq = -1;
9ae3a8
+        if (event_notifier_init(&vector->interrupt, 0)) {
9ae3a8
+            error_report("vfio: Error: event_notifier_init failed");
9ae3a8
+        }
9ae3a8
+        vector->use = true;
9ae3a8
+        msix_vector_use(pdev, nr);
9ae3a8
     }
9ae3a8
 
9ae3a8
+    qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
+                        handler, NULL, vector);
9ae3a8
+
9ae3a8
     /*
9ae3a8
      * Attempt to enable route through KVM irqchip,
9ae3a8
      * default to userspace handling if unavailable.
9ae3a8
      */
9ae3a8
-    vector->virq = msg && VFIO_ALLOW_KVM_MSIX ?
9ae3a8
-                   kvm_irqchip_add_msi_route(kvm_state, *msg) : -1;
9ae3a8
-    if (vector->virq < 0 ||
9ae3a8
-        kvm_irqchip_add_irqfd_notifier(kvm_state, &vector->interrupt,
9ae3a8
-                                       vector->virq) < 0) {
9ae3a8
-        if (vector->virq >= 0) {
9ae3a8
-            kvm_irqchip_release_virq(kvm_state, vector->virq);
9ae3a8
-            vector->virq = -1;
9ae3a8
+    if (vector->virq >= 0) {
9ae3a8
+        if (!msg) {
9ae3a8
+            vfio_remove_kvm_msi_virq(vector);
9ae3a8
+        } else {
9ae3a8
+            vfio_update_kvm_msi_virq(vector, *msg);
9ae3a8
         }
9ae3a8
-        qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
-                            handler, NULL, vector);
9ae3a8
+    } else {
9ae3a8
+        vfio_add_kvm_msi_virq(vector, msg, true);
9ae3a8
     }
9ae3a8
 
9ae3a8
     /*
9ae3a8
@@ -742,7 +792,11 @@ static int vfio_msix_vector_do_use(PCIDevice *pdev, unsigned int nr,
9ae3a8
         irq_set->count = 1;
9ae3a8
         pfd = (int32_t *)&irq_set->data;
9ae3a8
 
9ae3a8
-        *pfd = event_notifier_get_fd(&vector->interrupt);
9ae3a8
+        if (vector->virq >= 0) {
9ae3a8
+            *pfd = event_notifier_get_fd(&vector->kvm_interrupt);
9ae3a8
+        } else {
9ae3a8
+            *pfd = event_notifier_get_fd(&vector->interrupt);
9ae3a8
+        }
9ae3a8
 
9ae3a8
         ret = ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, irq_set);
9ae3a8
         g_free(irq_set);
9ae3a8
@@ -764,50 +818,41 @@ static void vfio_msix_vector_release(PCIDevice *pdev, unsigned int nr)
9ae3a8
 {
9ae3a8
     VFIODevice *vdev = DO_UPCAST(VFIODevice, pdev, pdev);
9ae3a8
     VFIOMSIVector *vector = &vdev->msi_vectors[nr];
9ae3a8
-    int argsz;
9ae3a8
-    struct vfio_irq_set *irq_set;
9ae3a8
-    int32_t *pfd;
9ae3a8
 
9ae3a8
     DPRINTF("%s(%04x:%02x:%02x.%x) vector %d released\n", __func__,
9ae3a8
             vdev->host.domain, vdev->host.bus, vdev->host.slot,
9ae3a8
             vdev->host.function, nr);
9ae3a8
 
9ae3a8
     /*
9ae3a8
-     * XXX What's the right thing to do here?  This turns off the interrupt
9ae3a8
-     * completely, but do we really just want to switch the interrupt to
9ae3a8
-     * bouncing through userspace and let msix.c drop it?  Not sure.
9ae3a8
+     * There are still old guests that mask and unmask vectors on every
9ae3a8
+     * interrupt.  If we're using QEMU bypass with a KVM irqfd, leave all of
9ae3a8
+     * the KVM setup in place, simply switch VFIO to use the non-bypass
9ae3a8
+     * eventfd.  We'll then fire the interrupt through QEMU and the MSI-X
9ae3a8
+     * core will mask the interrupt and set pending bits, allowing it to
9ae3a8
+     * be re-asserted on unmask.  Nothing to do if already using QEMU mode.
9ae3a8
      */
9ae3a8
-    msix_vector_unuse(pdev, nr);
9ae3a8
-
9ae3a8
-    argsz = sizeof(*irq_set) + sizeof(*pfd);
9ae3a8
+    if (vector->virq >= 0) {
9ae3a8
+        int argsz;
9ae3a8
+        struct vfio_irq_set *irq_set;
9ae3a8
+        int32_t *pfd;
9ae3a8
 
9ae3a8
-    irq_set = g_malloc0(argsz);
9ae3a8
-    irq_set->argsz = argsz;
9ae3a8
-    irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
9ae3a8
-                     VFIO_IRQ_SET_ACTION_TRIGGER;
9ae3a8
-    irq_set->index = VFIO_PCI_MSIX_IRQ_INDEX;
9ae3a8
-    irq_set->start = nr;
9ae3a8
-    irq_set->count = 1;
9ae3a8
-    pfd = (int32_t *)&irq_set->data;
9ae3a8
+        argsz = sizeof(*irq_set) + sizeof(*pfd);
9ae3a8
 
9ae3a8
-    *pfd = -1;
9ae3a8
+        irq_set = g_malloc0(argsz);
9ae3a8
+        irq_set->argsz = argsz;
9ae3a8
+        irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
9ae3a8
+                         VFIO_IRQ_SET_ACTION_TRIGGER;
9ae3a8
+        irq_set->index = VFIO_PCI_MSIX_IRQ_INDEX;
9ae3a8
+        irq_set->start = nr;
9ae3a8
+        irq_set->count = 1;
9ae3a8
+        pfd = (int32_t *)&irq_set->data;
9ae3a8
 
9ae3a8
-    ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, irq_set);
9ae3a8
+        *pfd = event_notifier_get_fd(&vector->interrupt);
9ae3a8
 
9ae3a8
-    g_free(irq_set);
9ae3a8
+        ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, irq_set);
9ae3a8
 
9ae3a8
-    if (vector->virq < 0) {
9ae3a8
-        qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
-                            NULL, NULL, NULL);
9ae3a8
-    } else {
9ae3a8
-        kvm_irqchip_remove_irqfd_notifier(kvm_state, &vector->interrupt,
9ae3a8
-                                          vector->virq);
9ae3a8
-        kvm_irqchip_release_virq(kvm_state, vector->virq);
9ae3a8
-        vector->virq = -1;
9ae3a8
+        g_free(irq_set);
9ae3a8
     }
9ae3a8
-
9ae3a8
-    event_notifier_cleanup(&vector->interrupt);
9ae3a8
-    vector->use = false;
9ae3a8
 }
9ae3a8
 
9ae3a8
 static void vfio_enable_msix(VFIODevice *vdev)
9ae3a8
@@ -857,28 +902,28 @@ retry:
9ae3a8
         VFIOMSIVector *vector = &vdev->msi_vectors[i];
9ae3a8
 
9ae3a8
         vector->vdev = vdev;
9ae3a8
+        vector->virq = -1;
9ae3a8
         vector->use = true;
9ae3a8
 
9ae3a8
         if (event_notifier_init(&vector->interrupt, 0)) {
9ae3a8
             error_report("vfio: Error: event_notifier_init failed");
9ae3a8
         }
9ae3a8
 
9ae3a8
+        qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
+                            vfio_msi_interrupt, NULL, vector);
9ae3a8
+
9ae3a8
         vector->msg = msi_get_message(&vdev->pdev, i);
9ae3a8
 
9ae3a8
         /*
9ae3a8
          * Attempt to enable route through KVM irqchip,
9ae3a8
          * default to userspace handling if unavailable.
9ae3a8
          */
9ae3a8
-        vector->virq = VFIO_ALLOW_KVM_MSI ?
9ae3a8
-                       kvm_irqchip_add_msi_route(kvm_state, vector->msg) : -1;
9ae3a8
-        if (vector->virq < 0 ||
9ae3a8
-            kvm_irqchip_add_irqfd_notifier(kvm_state, &vector->interrupt,
9ae3a8
-                                           vector->virq) < 0) {
9ae3a8
-            qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
-                                vfio_msi_interrupt, NULL, vector);
9ae3a8
-        }
9ae3a8
+        vfio_add_kvm_msi_virq(vector, &vector->msg, false);
9ae3a8
     }
9ae3a8
 
9ae3a8
+    /* Set interrupt type prior to possible interrupts */
9ae3a8
+    vdev->interrupt = VFIO_INT_MSI;
9ae3a8
+
9ae3a8
     ret = vfio_enable_vectors(vdev, false);
9ae3a8
     if (ret) {
9ae3a8
         if (ret < 0) {
9ae3a8
@@ -891,14 +936,10 @@ retry:
9ae3a8
         for (i = 0; i < vdev->nr_vectors; i++) {
9ae3a8
             VFIOMSIVector *vector = &vdev->msi_vectors[i];
9ae3a8
             if (vector->virq >= 0) {
9ae3a8
-                kvm_irqchip_remove_irqfd_notifier(kvm_state, &vector->interrupt,
9ae3a8
-                                                  vector->virq);
9ae3a8
-                kvm_irqchip_release_virq(kvm_state, vector->virq);
9ae3a8
-                vector->virq = -1;
9ae3a8
-            } else {
9ae3a8
-                qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
-                                    NULL, NULL, NULL);
9ae3a8
+                vfio_remove_kvm_msi_virq(vector);
9ae3a8
             }
9ae3a8
+            qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
+                                NULL, NULL, NULL);
9ae3a8
             event_notifier_cleanup(&vector->interrupt);
9ae3a8
         }
9ae3a8
 
9ae3a8
@@ -910,11 +951,17 @@ retry:
9ae3a8
         }
9ae3a8
         vdev->nr_vectors = 0;
9ae3a8
 
9ae3a8
+        /*
9ae3a8
+         * Failing to setup MSI doesn't really fall within any specification.
9ae3a8
+         * Let's try leaving interrupts disabled and hope the guest figures
9ae3a8
+         * out to fall back to INTx for this device.
9ae3a8
+         */
9ae3a8
+        error_report("vfio: Error: Failed to enable MSI");
9ae3a8
+        vdev->interrupt = VFIO_INT_NONE;
9ae3a8
+
9ae3a8
         return;
9ae3a8
     }
9ae3a8
 
9ae3a8
-    vdev->interrupt = VFIO_INT_MSI;
9ae3a8
-
9ae3a8
     DPRINTF("%s(%04x:%02x:%02x.%x) Enabled %d MSI vectors\n", __func__,
9ae3a8
             vdev->host.domain, vdev->host.bus, vdev->host.slot,
9ae3a8
             vdev->host.function, vdev->nr_vectors);
9ae3a8
@@ -922,6 +969,20 @@ retry:
9ae3a8
 
9ae3a8
 static void vfio_disable_msi_common(VFIODevice *vdev)
9ae3a8
 {
9ae3a8
+    int i;
9ae3a8
+
9ae3a8
+    for (i = 0; i < vdev->nr_vectors; i++) {
9ae3a8
+        VFIOMSIVector *vector = &vdev->msi_vectors[i];
9ae3a8
+        if (vdev->msi_vectors[i].use) {
9ae3a8
+            if (vector->virq >= 0) {
9ae3a8
+                vfio_remove_kvm_msi_virq(vector);
9ae3a8
+            }
9ae3a8
+            qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
+                                NULL, NULL, NULL);
9ae3a8
+            event_notifier_cleanup(&vector->interrupt);
9ae3a8
+        }
9ae3a8
+    }
9ae3a8
+
9ae3a8
     g_free(vdev->msi_vectors);
9ae3a8
     vdev->msi_vectors = NULL;
9ae3a8
     vdev->nr_vectors = 0;
9ae3a8
@@ -943,6 +1004,7 @@ static void vfio_disable_msix(VFIODevice *vdev)
9ae3a8
     for (i = 0; i < vdev->nr_vectors; i++) {
9ae3a8
         if (vdev->msi_vectors[i].use) {
9ae3a8
             vfio_msix_vector_release(&vdev->pdev, i);
9ae3a8
+            msix_vector_unuse(&vdev->pdev, i);
9ae3a8
         }
9ae3a8
     }
9ae3a8
 
9ae3a8
@@ -958,30 +1020,7 @@ static void vfio_disable_msix(VFIODevice *vdev)
9ae3a8
 
9ae3a8
 static void vfio_disable_msi(VFIODevice *vdev)
9ae3a8
 {
9ae3a8
-    int i;
9ae3a8
-
9ae3a8
     vfio_disable_irqindex(vdev, VFIO_PCI_MSI_IRQ_INDEX);
9ae3a8
-
9ae3a8
-    for (i = 0; i < vdev->nr_vectors; i++) {
9ae3a8
-        VFIOMSIVector *vector = &vdev->msi_vectors[i];
9ae3a8
-
9ae3a8
-        if (!vector->use) {
9ae3a8
-            continue;
9ae3a8
-        }
9ae3a8
-
9ae3a8
-        if (vector->virq >= 0) {
9ae3a8
-            kvm_irqchip_remove_irqfd_notifier(kvm_state,
9ae3a8
-                                              &vector->interrupt, vector->virq);
9ae3a8
-            kvm_irqchip_release_virq(kvm_state, vector->virq);
9ae3a8
-            vector->virq = -1;
9ae3a8
-        } else {
9ae3a8
-            qemu_set_fd_handler(event_notifier_get_fd(&vector->interrupt),
9ae3a8
-                                NULL, NULL, NULL);
9ae3a8
-        }
9ae3a8
-
9ae3a8
-        event_notifier_cleanup(&vector->interrupt);
9ae3a8
-    }
9ae3a8
-
9ae3a8
     vfio_disable_msi_common(vdev);
9ae3a8
 
9ae3a8
     DPRINTF("%s(%04x:%02x:%02x.%x)\n", __func__, vdev->host.domain,
9ae3a8
@@ -1001,17 +1040,7 @@ static void vfio_update_msi(VFIODevice *vdev)
9ae3a8
         }
9ae3a8
 
9ae3a8
         msg = msi_get_message(&vdev->pdev, i);
9ae3a8
-
9ae3a8
-        if (msg.address != vector->msg.address ||
9ae3a8
-            msg.data != vector->msg.data) {
9ae3a8
-
9ae3a8
-            DPRINTF("%s(%04x:%02x:%02x.%x) MSI vector %d changed\n",
9ae3a8
-                    __func__, vdev->host.domain, vdev->host.bus,
9ae3a8
-                    vdev->host.slot, vdev->host.function, i);
9ae3a8
-
9ae3a8
-            kvm_irqchip_update_msi_route(kvm_state, vector->virq, msg);
9ae3a8
-            vector->msg = msg;
9ae3a8
-        }
9ae3a8
+        vfio_update_kvm_msi_virq(vector, msg);
9ae3a8
     }
9ae3a8
 }
9ae3a8
 
9ae3a8
-- 
9ae3a8
1.8.3.1
9ae3a8