Blob Blame History Raw
From: Sebastian Ott <sebott@linux.vnet.ibm.com>
Subject: s390/pci: improve pci hotplug
Patch-mainline: v4.13-rc1
Git-commit: 623bd44d3f277b7bbe16e0e091bd361e75964b5d
References: bnc#1066983, LTC#157731

Description:  PCI: fix hotplug related issues
Symptom:      After serveral cycles of hot-unplug/hotplug the affected
              function stops working or the kernel oopses.
Problem:      Hotplug notifications happen after the fact, meaning it's
              not possible to issue instructions to the function for
              cleanup purposes. Thus we never release the associated
              resources and at some point fail to allocate new ones.
Solution:     During clean up treat some errors as success (e.g. a
              function that's gone can't create an interrupt).
Reproduction: vmcp att pcif XXX to \* ;vmcp det pcif XXX #in a loop

Upstream-Description:

              s390/pci: improve pci hotplug

              PCI hotplug events basically notify about the new state of a
              function. Unfortunately some hypervisors implement hotplug
              events in a way where it is not clear what the new state of
              the function should be.

              Use clp_get_state to find the current state of the function
              and handle accordingly.

              Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com>
              Reviewed-by: Gerald Schaefer <gerald.schaefer@de.ibm.com>
              Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>


Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com>
Acked-by: Hannes Reinecke <hare@suse.com>
---
 arch/s390/include/asm/pci.h |    1 +
 arch/s390/pci/pci.c         |    9 +++++++++
 arch/s390/pci/pci_event.c   |   14 +++++++++++---
 3 files changed, 21 insertions(+), 3 deletions(-)

--- a/arch/s390/include/asm/pci.h
+++ b/arch/s390/include/asm/pci.h
@@ -158,6 +158,7 @@ extern const struct attribute_group *zpc
 ----------------------------------------------------------------------------- */
 /* Base stuff */
 int zpci_create_device(struct zpci_dev *);
+void zpci_remove_device(struct zpci_dev *zdev);
 int zpci_enable_device(struct zpci_dev *);
 int zpci_disable_device(struct zpci_dev *);
 void zpci_stop_device(struct zpci_dev *);
--- a/arch/s390/pci/pci.c
+++ b/arch/s390/pci/pci.c
@@ -855,6 +855,15 @@ void zpci_stop_device(struct zpci_dev *z
 }
 EXPORT_SYMBOL_GPL(zpci_stop_device);
 
+void zpci_remove_device(struct zpci_dev *zdev)
+{
+	if (!zdev->bus)
+		return;
+
+	pci_stop_root_bus(zdev->bus);
+	pci_remove_root_bus(zdev->bus);
+}
+
 int zpci_report_error(struct pci_dev *pdev,
 		      struct zpci_report_error_header *report)
 {
--- a/arch/s390/pci/pci_event.c
+++ b/arch/s390/pci/pci_event.c
@@ -74,6 +74,7 @@ static void __zpci_event_availability(st
 {
 	struct zpci_dev *zdev = get_zdev_by_fid(ccdf->fid);
 	struct pci_dev *pdev = NULL;
+	enum zpci_state state;
 	int ret;
 
 	if (zdev)
@@ -108,6 +109,8 @@ static void __zpci_event_availability(st
 			clp_add_pci_device(ccdf->fid, ccdf->fh, 0);
 		break;
 	case 0x0303: /* Deconfiguration requested */
+		if (!zdev)
+			break;
 		if (pdev)
 			pci_stop_and_remove_bus_device_locked(pdev);
 
@@ -121,7 +124,9 @@ static void __zpci_event_availability(st
 			zdev->state = ZPCI_FN_STATE_STANDBY;
 
 		break;
-	case 0x0304: /* Configured -> Standby */
+	case 0x0304: /* Configured -> Standby|Reserved */
+		if (!zdev)
+			break;
 		if (pdev) {
 			/* Give the driver a hint that the function is
 			 * already unusable. */
@@ -132,6 +137,10 @@ static void __zpci_event_availability(st
 		zdev->fh = ccdf->fh;
 		zpci_disable_device(zdev);
 		zdev->state = ZPCI_FN_STATE_STANDBY;
+		if (!clp_get_state(ccdf->fid, &state) &&
+		    state == ZPCI_FN_STATE_RESERVED) {
+			zpci_remove_device(zdev);
+		}
 		break;
 	case 0x0306: /* 0x308 or 0x302 for multiple devices */
 		clp_rescan_pci_devices();
@@ -139,8 +148,7 @@ static void __zpci_event_availability(st
 	case 0x0308: /* Standby -> Reserved */
 		if (!zdev)
 			break;
-		pci_stop_root_bus(zdev->bus);
-		pci_remove_root_bus(zdev->bus);
+		zpci_remove_device(zdev);
 		break;
 	default:
 		break;