Blame SOURCES/kvm-s390x-pci-Introduce-unplug-requests-and-split-unplug.patch

ae23c9
From db9158c9e7905a77c854b178140be79f5295bcdd Mon Sep 17 00:00:00 2001
ae23c9
From: Cornelia Huck <cohuck@redhat.com>
ae23c9
Date: Wed, 17 Apr 2019 13:57:33 +0100
ae23c9
Subject: [PATCH 16/24] s390x/pci: Introduce unplug requests and split unplug
ae23c9
 handler
ae23c9
ae23c9
RH-Author: Cornelia Huck <cohuck@redhat.com>
ae23c9
Message-id: <20190417135741.25297-17-cohuck@redhat.com>
ae23c9
Patchwork-id: 85799
ae23c9
O-Subject: [RHEL-8.1.0 qemu-kvm PATCH v2 16/24] s390x/pci: Introduce unplug requests and split unplug handler
ae23c9
Bugzilla: 1699070
ae23c9
RH-Acked-by: David Hildenbrand <david@redhat.com>
ae23c9
RH-Acked-by: Thomas Huth <thuth@redhat.com>
ae23c9
RH-Acked-by: Jens Freimann <jfreimann@redhat.com>
ae23c9
ae23c9
From: David Hildenbrand <david@redhat.com>
ae23c9
ae23c9
PCI on s390x is really weird and how it was modeled in QEMU might not have
ae23c9
been the right choice. Anyhow, right now it is the case that:
ae23c9
- Hotplugging a PCI device will silently create a zPCI device
ae23c9
  (if none is provided)
ae23c9
- Hotunplugging a zPCI device will unplug the PCI device (if any)
ae23c9
- Hotunplugging a PCI device will unplug also the zPCI device
ae23c9
As far as I can see, we can no longer change this behavior. But we
ae23c9
should fix it.
ae23c9
ae23c9
Both device types are handled via a single hotplug handler call. This
ae23c9
is problematic for various reasons:
ae23c9
1. Unplugging via the zPCI device allows to unplug devices that are not
ae23c9
   hot removable. (check performed in qdev_unplug()) - bad.
ae23c9
2. Hotplug handler chains are not possible for the unplug case. In the
ae23c9
   future, the machine might want to override hotplug handlers, to
ae23c9
   process device specific stuff and to then branch off to the actual
ae23c9
   hotplug handler. We need separate hotplug handler calls for both the
ae23c9
   PCI and zPCI device to make this work reliably. All other PCI
ae23c9
   implementations are already prepared to handle this correctly, only
ae23c9
   s390x is missing.
ae23c9
ae23c9
Therefore, introduce the unplug_request handler and properly perform
ae23c9
unplug checks by redirecting to the separate unplug_request handlers.
ae23c9
When finally unplugging, perform two separate hotplug_handler_unplug()
ae23c9
calls, first for the PCI device, followed by the zPCI device. This now
ae23c9
nicely splits unplugging paths for both devices.
ae23c9
ae23c9
The redirect part is a little hairy, as the user is allowed to trigger
ae23c9
unplug either via the PCI or the zPCI device. So redirect always to the
ae23c9
PCI unplug request handler first and remember if that check has been
ae23c9
performed in the zPCI device. Redirect then to the zPCI device unplug
ae23c9
request handler to perform the magic. Remembering that we already
ae23c9
checked the PCI device breaks the redirect loop.
ae23c9
ae23c9
Signed-off-by: David Hildenbrand <david@redhat.com>
ae23c9
Message-Id: <20190130155733.32742-5-david@redhat.com>
ae23c9
Reviewed-by: Collin Walling <walling@linux.ibm.com>
ae23c9
Signed-off-by: Cornelia Huck <cohuck@redhat.com>
ae23c9
(cherry picked from commit e0998fe8910435f0e818e5c9ac58d4d2d9144a98)
ae23c9
Signed-off-by: Cornelia Huck <cohuck@redhat.com>
ae23c9
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
ae23c9
ae23c9
Conflicts:
ae23c9
	hw/s390x/s390-pci-bus.c
ae23c9
  We don't have 6e92c70c3754 ("s390x/pci: add common function measurement
ae23c9
  block") downstream.
ae23c9
ae23c9
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
ae23c9
---
ae23c9
 hw/s390x/s390-pci-bus.c | 165 ++++++++++++++++++++++++++++++++----------------
ae23c9
 hw/s390x/s390-pci-bus.h |   1 +
ae23c9
 2 files changed, 113 insertions(+), 53 deletions(-)
ae23c9
ae23c9
diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c
ae23c9
index e3f576a..21419df 100644
ae23c9
--- a/hw/s390x/s390-pci-bus.c
ae23c9
+++ b/hw/s390x/s390-pci-bus.c
ae23c9
@@ -148,6 +148,22 @@ out:
ae23c9
     psccb->header.response_code = cpu_to_be16(rc);
ae23c9
 }
ae23c9
 
ae23c9
+static void s390_pci_perform_unplug(S390PCIBusDevice *pbdev)
ae23c9
+{
ae23c9
+    HotplugHandler *hotplug_ctrl;
ae23c9
+
ae23c9
+    /* Unplug the PCI device */
ae23c9
+    if (pbdev->pdev) {
ae23c9
+        hotplug_ctrl = qdev_get_hotplug_handler(DEVICE(pbdev->pdev));
ae23c9
+        hotplug_handler_unplug(hotplug_ctrl, DEVICE(pbdev->pdev),
ae23c9
+                               &error_abort);
ae23c9
+    }
ae23c9
+
ae23c9
+    /* Unplug the zPCI device */
ae23c9
+    hotplug_ctrl = qdev_get_hotplug_handler(DEVICE(pbdev));
ae23c9
+    hotplug_handler_unplug(hotplug_ctrl, DEVICE(pbdev), &error_abort);
ae23c9
+}
ae23c9
+
ae23c9
 void s390_pci_sclp_deconfigure(SCCB *sccb)
ae23c9
 {
ae23c9
     IoaCfgSccb *psccb = (IoaCfgSccb *)sccb;
ae23c9
@@ -179,7 +195,7 @@ void s390_pci_sclp_deconfigure(SCCB *sccb)
ae23c9
         rc = SCLP_RC_NORMAL_COMPLETION;
ae23c9
 
ae23c9
         if (pbdev->release_timer) {
ae23c9
-            qdev_unplug(DEVICE(pbdev->pdev), NULL);
ae23c9
+            s390_pci_perform_unplug(pbdev);
ae23c9
         }
ae23c9
     }
ae23c9
 out:
ae23c9
@@ -217,6 +233,24 @@ S390PCIBusDevice *s390_pci_find_dev_by_target(S390pciState *s,
ae23c9
     return NULL;
ae23c9
 }
ae23c9
 
ae23c9
+static S390PCIBusDevice *s390_pci_find_dev_by_pci(S390pciState *s,
ae23c9
+                                                  PCIDevice *pci_dev)
ae23c9
+{
ae23c9
+    S390PCIBusDevice *pbdev;
ae23c9
+
ae23c9
+    if (!pci_dev) {
ae23c9
+        return NULL;
ae23c9
+    }
ae23c9
+
ae23c9
+    QTAILQ_FOREACH(pbdev, &s->zpci_devs, link) {
ae23c9
+        if (pbdev->pdev == pci_dev) {
ae23c9
+            return pbdev;
ae23c9
+        }
ae23c9
+    }
ae23c9
+
ae23c9
+    return NULL;
ae23c9
+}
ae23c9
+
ae23c9
 S390PCIBusDevice *s390_pci_find_dev_by_idx(S390pciState *s, uint32_t idx)
ae23c9
 {
ae23c9
     return g_hash_table_lookup(s->zpci_table, &idx);
ae23c9
@@ -943,76 +977,100 @@ static void s390_pcihost_timer_cb(void *opaque)
ae23c9
     pbdev->state = ZPCI_FS_STANDBY;
ae23c9
     s390_pci_generate_plug_event(HP_EVENT_CONFIGURED_TO_STBRES,
ae23c9
                                  pbdev->fh, pbdev->fid);
ae23c9
-    qdev_unplug(DEVICE(pbdev), NULL);
ae23c9
+    s390_pci_perform_unplug(pbdev);
ae23c9
 }
ae23c9
 
ae23c9
 static void s390_pcihost_unplug(HotplugHandler *hotplug_dev, DeviceState *dev,
ae23c9
                                 Error **errp)
ae23c9
 {
ae23c9
     S390pciState *s = S390_PCI_HOST_BRIDGE(hotplug_dev);
ae23c9
-    PCIDevice *pci_dev = NULL;
ae23c9
-    PCIBus *bus;
ae23c9
-    int32_t devfn;
ae23c9
     S390PCIBusDevice *pbdev = NULL;
ae23c9
 
ae23c9
+    if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
ae23c9
+        PCIDevice *pci_dev = PCI_DEVICE(dev);
ae23c9
+        PCIBus *bus;
ae23c9
+        int32_t devfn;
ae23c9
+
ae23c9
+        pbdev = s390_pci_find_dev_by_pci(s, PCI_DEVICE(dev));
ae23c9
+        g_assert(pbdev);
ae23c9
+
ae23c9
+        s390_pci_generate_plug_event(HP_EVENT_STANDBY_TO_RESERVED,
ae23c9
+                                     pbdev->fh, pbdev->fid);
ae23c9
+        bus = pci_get_bus(pci_dev);
ae23c9
+        devfn = pci_dev->devfn;
ae23c9
+        object_unparent(OBJECT(pci_dev));
ae23c9
+
ae23c9
+        s390_pci_msix_free(pbdev);
ae23c9
+        s390_pci_iommu_free(s, bus, devfn);
ae23c9
+        pbdev->pdev = NULL;
ae23c9
+        pbdev->state = ZPCI_FS_RESERVED;
ae23c9
+    } else if (object_dynamic_cast(OBJECT(dev), TYPE_S390_PCI_DEVICE)) {
ae23c9
+        pbdev = S390_PCI_DEVICE(dev);
ae23c9
+
ae23c9
+        if (pbdev->release_timer) {
ae23c9
+            timer_del(pbdev->release_timer);
ae23c9
+            timer_free(pbdev->release_timer);
ae23c9
+            pbdev->release_timer = NULL;
ae23c9
+        }
ae23c9
+        pbdev->fid = 0;
ae23c9
+        QTAILQ_REMOVE(&s->zpci_devs, pbdev, link);
ae23c9
+        g_hash_table_remove(s->zpci_table, &pbdev->idx);
ae23c9
+        object_unparent(OBJECT(pbdev));
ae23c9
+    }
ae23c9
+}
ae23c9
+
ae23c9
+static void s390_pcihost_unplug_request(HotplugHandler *hotplug_dev,
ae23c9
+                                        DeviceState *dev,
ae23c9
+                                        Error **errp)
ae23c9
+{
ae23c9
+    S390pciState *s = S390_PCI_HOST_BRIDGE(hotplug_dev);
ae23c9
+    S390PCIBusDevice *pbdev;
ae23c9
+
ae23c9
     if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_BRIDGE)) {
ae23c9
         error_setg(errp, "PCI bridge hot unplug currently not supported");
ae23c9
-        return;
ae23c9
     } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
ae23c9
-        pci_dev = PCI_DEVICE(dev);
ae23c9
-
ae23c9
-        QTAILQ_FOREACH(pbdev, &s->zpci_devs, link) {
ae23c9
-            if (pbdev->pdev == pci_dev) {
ae23c9
-                break;
ae23c9
-            }
ae23c9
-        }
ae23c9
-        assert(pbdev != NULL);
ae23c9
+        /*
ae23c9
+         * Redirect the unplug request to the zPCI device and remember that
ae23c9
+         * we've checked the PCI device already (to prevent endless recursion).
ae23c9
+         */
ae23c9
+        pbdev = s390_pci_find_dev_by_pci(s, PCI_DEVICE(dev));
ae23c9
+        g_assert(pbdev);
ae23c9
+        pbdev->pci_unplug_request_processed = true;
ae23c9
+        qdev_unplug(DEVICE(pbdev), errp);
ae23c9
     } else if (object_dynamic_cast(OBJECT(dev), TYPE_S390_PCI_DEVICE)) {
ae23c9
         pbdev = S390_PCI_DEVICE(dev);
ae23c9
-        pci_dev = pbdev->pdev;
ae23c9
-    } else {
ae23c9
-        g_assert_not_reached();
ae23c9
-    }
ae23c9
 
ae23c9
-    switch (pbdev->state) {
ae23c9
-    case ZPCI_FS_RESERVED:
ae23c9
-        goto out;
ae23c9
-    case ZPCI_FS_STANDBY:
ae23c9
-        break;
ae23c9
-    default:
ae23c9
-        if (pbdev->release_timer) {
ae23c9
+        /*
ae23c9
+         * If unplug was initially requested for the zPCI device, we
ae23c9
+         * first have to redirect to the PCI device, which will in return
ae23c9
+         * redirect back to us after performing its checks (if the request
ae23c9
+         * is not blocked, e.g. because it's a PCI bridge).
ae23c9
+         */
ae23c9
+        if (pbdev->pdev && !pbdev->pci_unplug_request_processed) {
ae23c9
+            qdev_unplug(DEVICE(pbdev->pdev), errp);
ae23c9
             return;
ae23c9
         }
ae23c9
-        s390_pci_generate_plug_event(HP_EVENT_DECONFIGURE_REQUEST,
ae23c9
-                                     pbdev->fh, pbdev->fid);
ae23c9
-        pbdev->release_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
ae23c9
-                                            s390_pcihost_timer_cb,
ae23c9
-                                            pbdev);
ae23c9
-        timer_mod(pbdev->release_timer,
ae23c9
-                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + HOT_UNPLUG_TIMEOUT);
ae23c9
-        return;
ae23c9
-    }
ae23c9
+        pbdev->pci_unplug_request_processed = false;
ae23c9
 
ae23c9
-    if (pbdev->release_timer) {
ae23c9
-        timer_del(pbdev->release_timer);
ae23c9
-        timer_free(pbdev->release_timer);
ae23c9
-        pbdev->release_timer = NULL;
ae23c9
+        switch (pbdev->state) {
ae23c9
+        case ZPCI_FS_STANDBY:
ae23c9
+        case ZPCI_FS_RESERVED:
ae23c9
+            s390_pci_perform_unplug(pbdev);
ae23c9
+            break;
ae23c9
+        default:
ae23c9
+            if (pbdev->release_timer) {
ae23c9
+                return;
ae23c9
+            }
ae23c9
+            s390_pci_generate_plug_event(HP_EVENT_DECONFIGURE_REQUEST,
ae23c9
+                                         pbdev->fh, pbdev->fid);
ae23c9
+            pbdev->release_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
ae23c9
+                                                s390_pcihost_timer_cb, pbdev);
ae23c9
+            timer_mod(pbdev->release_timer,
ae23c9
+                    qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + HOT_UNPLUG_TIMEOUT);
ae23c9
+        }
ae23c9
+    } else {
ae23c9
+        g_assert_not_reached();
ae23c9
     }
ae23c9
-
ae23c9
-    s390_pci_generate_plug_event(HP_EVENT_STANDBY_TO_RESERVED,
ae23c9
-                                 pbdev->fh, pbdev->fid);
ae23c9
-    bus = pci_get_bus(pci_dev);
ae23c9
-    devfn = pci_dev->devfn;
ae23c9
-    object_unparent(OBJECT(pci_dev));
ae23c9
-    s390_pci_msix_free(pbdev);
ae23c9
-    s390_pci_iommu_free(s, bus, devfn);
ae23c9
-    pbdev->pdev = NULL;
ae23c9
-    pbdev->state = ZPCI_FS_RESERVED;
ae23c9
-out:
ae23c9
-    pbdev->fid = 0;
ae23c9
-    QTAILQ_REMOVE(&s->zpci_devs, pbdev, link);
ae23c9
-    g_hash_table_remove(s->zpci_table, &pbdev->idx);
ae23c9
-    object_unparent(OBJECT(pbdev));
ae23c9
 }
ae23c9
 
ae23c9
 static void s390_pci_enumerate_bridge(PCIBus *bus, PCIDevice *pdev,
ae23c9
@@ -1062,6 +1120,7 @@ static void s390_pcihost_class_init(ObjectClass *klass, void *data)
ae23c9
     dc->realize = s390_pcihost_realize;
ae23c9
     hc->pre_plug = s390_pcihost_pre_plug;
ae23c9
     hc->plug = s390_pcihost_plug;
ae23c9
+    hc->unplug_request = s390_pcihost_unplug_request;
ae23c9
     hc->unplug = s390_pcihost_unplug;
ae23c9
     msi_nonbroken = true;
ae23c9
 }
ae23c9
diff --git a/hw/s390x/s390-pci-bus.h b/hw/s390x/s390-pci-bus.h
ae23c9
index 1f7f9b5..7684658 100644
ae23c9
--- a/hw/s390x/s390-pci-bus.h
ae23c9
+++ b/hw/s390x/s390-pci-bus.h
ae23c9
@@ -308,6 +308,7 @@ struct S390PCIBusDevice {
ae23c9
     IndAddr *summary_ind;
ae23c9
     IndAddr *indicator;
ae23c9
     QEMUTimer *release_timer;
ae23c9
+    bool pci_unplug_request_processed;
ae23c9
     QTAILQ_ENTRY(S390PCIBusDevice) link;
ae23c9
 };
ae23c9
 
ae23c9
-- 
ae23c9
1.8.3.1
ae23c9