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