powerpc/eeh: Remove EEH PE for normal PCI hotplug
authorGavin Shan <shangw@linux.vnet.ibm.com>
Tue, 11 Sep 2012 19:16:17 +0000 (19:16 +0000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Tue, 18 Sep 2012 05:32:23 +0000 (15:32 +1000)
Function eeh_rmv_from_parent_pe() could be called by the path of
either normal PCI hotplug, or EEH recovery. For the former case,
we need purge the corresponding PE on removal of the associated
PE bus.

The patch tries to cover that by passing more information to function
pcibios_remove_pci_devices() so that we know if the corresponding PE
needs to be purged or be marked as "invalid".

Signed-off-by: Gavin Shan <shangw@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
arch/powerpc/include/asm/eeh.h
arch/powerpc/include/asm/pci-bridge.h
arch/powerpc/platforms/pseries/eeh.c
arch/powerpc/platforms/pseries/eeh_driver.c
arch/powerpc/platforms/pseries/eeh_pe.c
arch/powerpc/platforms/pseries/pci_dlpar.c
drivers/pci/hotplug/rpadlpar_core.c

index afeb40086fb2a3e03c39ceff93e3ab7286200438..b0ef73882b3847763117f7b0ccb6a123a227e9a5 100644 (file)
@@ -185,7 +185,7 @@ static inline void eeh_unlock(void)
 typedef void *(*eeh_traverse_func)(void *data, void *flag);
 int __devinit eeh_phb_pe_create(struct pci_controller *phb);
 int eeh_add_to_parent_pe(struct eeh_dev *edev);
-int eeh_rmv_from_parent_pe(struct eeh_dev *edev);
+int eeh_rmv_from_parent_pe(struct eeh_dev *edev, int purge_pe);
 void *eeh_pe_dev_traverse(struct eeh_pe *root,
                eeh_traverse_func fn, void *flag);
 void eeh_pe_restore_bars(struct eeh_pe *pe);
@@ -201,7 +201,7 @@ int eeh_dev_check_failure(struct eeh_dev *edev);
 void __init eeh_addr_cache_build(void);
 void eeh_add_device_tree_early(struct device_node *);
 void eeh_add_device_tree_late(struct pci_bus *);
-void eeh_remove_bus_device(struct pci_dev *);
+void eeh_remove_bus_device(struct pci_dev *, int);
 
 /**
  * EEH_POSSIBLE_ERROR() -- test for possible MMIO failure.
@@ -240,7 +240,7 @@ static inline void eeh_add_device_tree_early(struct device_node *dn) { }
 
 static inline void eeh_add_device_tree_late(struct pci_bus *bus) { }
 
-static inline void eeh_remove_bus_device(struct pci_dev *dev) { }
+static inline void eeh_remove_bus_device(struct pci_dev *dev, int purge_pe) { }
 
 static inline void eeh_lock(void) { }
 static inline void eeh_unlock(void) { }
index 973df4d9d3663ba41477f5417937af9d42814614..a059cb94952bad5f364fc6288bd8ec819a6d35c5 100644 (file)
@@ -192,6 +192,7 @@ static inline struct eeh_dev *of_node_to_eeh_dev(struct device_node *dn)
 extern struct pci_bus *pcibios_find_pci_bus(struct device_node *dn);
 
 /** Remove all of the PCI devices under this bus */
+extern void __pcibios_remove_pci_devices(struct pci_bus *bus, int purge_pe);
 extern void pcibios_remove_pci_devices(struct pci_bus *bus);
 
 /** Discover new pci devices under this bus, and add them */
index 18c168b752da2e24785ec1d5a6c0a879577df798..43f6ed4155640a07fd9238878991392720ef90f1 100644 (file)
@@ -817,6 +817,7 @@ EXPORT_SYMBOL_GPL(eeh_add_device_tree_late);
 /**
  * eeh_remove_device - Undo EEH setup for the indicated pci device
  * @dev: pci device to be removed
+ * @purge_pe: remove the PE or not
  *
  * This routine should be called when a device is removed from
  * a running system (e.g. by hotplug or dlpar).  It unregisters
@@ -824,7 +825,7 @@ EXPORT_SYMBOL_GPL(eeh_add_device_tree_late);
  * this device will no longer be detected after this call; thus,
  * i/o errors affecting this slot may leave this device unusable.
  */
-static void eeh_remove_device(struct pci_dev *dev)
+static void eeh_remove_device(struct pci_dev *dev, int purge_pe)
 {
        struct eeh_dev *edev;
 
@@ -843,7 +844,7 @@ static void eeh_remove_device(struct pci_dev *dev)
        dev->dev.archdata.edev = NULL;
        pci_dev_put(dev);
 
-       eeh_rmv_from_parent_pe(edev);
+       eeh_rmv_from_parent_pe(edev, purge_pe);
        eeh_addr_cache_rmv_dev(dev);
        eeh_sysfs_remove_device(dev);
 }
@@ -851,21 +852,22 @@ static void eeh_remove_device(struct pci_dev *dev)
 /**
  * eeh_remove_bus_device - Undo EEH setup for the indicated PCI device
  * @dev: PCI device
+ * @purge_pe: remove the corresponding PE or not
  *
  * This routine must be called when a device is removed from the
  * running system through hotplug or dlpar. The corresponding
  * PCI address cache will be removed.
  */
-void eeh_remove_bus_device(struct pci_dev *dev)
+void eeh_remove_bus_device(struct pci_dev *dev, int purge_pe)
 {
        struct pci_bus *bus = dev->subordinate;
        struct pci_dev *child, *tmp;
 
-       eeh_remove_device(dev);
+       eeh_remove_device(dev, purge_pe);
 
        if (bus && dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
                list_for_each_entry_safe(child, tmp, &bus->devices, bus_list)
-                        eeh_remove_bus_device(child);
+                        eeh_remove_bus_device(child, purge_pe);
        }
 }
 EXPORT_SYMBOL_GPL(eeh_remove_bus_device);
index 8370ce7d5931f0abff0b5d07d43e49208bd190c5..37c2cf7431338d579cddf42404a677978e7eebc1 100644 (file)
@@ -305,8 +305,14 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus)
        /* pcibios will clear the counter; save the value */
        cnt = pe->freeze_count;
 
+       /*
+        * We don't remove the corresponding PE instances because
+        * we need the information afterwords. The attached EEH
+        * devices are expected to be attached soon when calling
+        * into pcibios_add_pci_devices().
+        */
        if (bus)
-               pcibios_remove_pci_devices(bus);
+               __pcibios_remove_pci_devices(bus, 0);
 
        /* Reset the pci controller. (Asserts RST#; resets config space).
         * Reconfigure bridges and devices. Don't try to bring the system
index 51fc56abb7a2e04c25c11af4e79be43cba3801ed..30b8d96ed54dc2a82d3eb0da8de72bbfe9905c78 100644 (file)
@@ -395,13 +395,14 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)
 /**
  * eeh_rmv_from_parent_pe - Remove one EEH device from the associated PE
  * @edev: EEH device
+ * @purge_pe: remove PE or not
  *
  * The PE hierarchy tree might be changed when doing PCI hotplug.
  * Also, the PCI devices or buses could be removed from the system
  * during EEH recovery. So we have to call the function remove the
  * corresponding PE accordingly if necessary.
  */
-int eeh_rmv_from_parent_pe(struct eeh_dev *edev)
+int eeh_rmv_from_parent_pe(struct eeh_dev *edev, int purge_pe)
 {
        struct eeh_pe *pe, *parent, *child;
        int cnt;
@@ -428,19 +429,29 @@ int eeh_rmv_from_parent_pe(struct eeh_dev *edev)
                if (pe->type & EEH_PE_PHB)
                        break;
 
-               if (list_empty(&pe->edevs)) {
-                       cnt = 0;
-                       list_for_each_entry(child, &pe->child_list, child) {
-                               if (!(pe->type & EEH_PE_INVALID)) {
-                                       cnt++;
-                                       break;
-                               }
+               if (purge_pe) {
+                       if (list_empty(&pe->edevs) &&
+                           list_empty(&pe->child_list)) {
+                               list_del(&pe->child);
+                               kfree(pe);
+                       } else {
+                               break;
                        }
+               } else {
+                       if (list_empty(&pe->edevs)) {
+                               cnt = 0;
+                               list_for_each_entry(child, &pe->child_list, child) {
+                                       if (!(pe->type & EEH_PE_INVALID)) {
+                                               cnt++;
+                                               break;
+                                       }
+                               }
 
-                       if (!cnt)
-                               pe->type |= EEH_PE_INVALID;
-                       else
-                               break;
+                               if (!cnt)
+                                       pe->type |= EEH_PE_INVALID;
+                               else
+                                       break;
+                       }
                }
 
                pe = parent;
index 3ccebc83dc021a23dfdbf66e1cf0f8d80ff59eb1..261a577a3dd2db2d3e651648368ec72ef52628fd 100644 (file)
@@ -65,27 +65,43 @@ pcibios_find_pci_bus(struct device_node *dn)
 EXPORT_SYMBOL_GPL(pcibios_find_pci_bus);
 
 /**
- * pcibios_remove_pci_devices - remove all devices under this bus
+ * __pcibios_remove_pci_devices - remove all devices under this bus
+ * @bus: the indicated PCI bus
+ * @purge_pe: destroy the PE on removal of PCI devices
  *
  * Remove all of the PCI devices under this bus both from the
  * linux pci device tree, and from the powerpc EEH address cache.
+ * By default, the corresponding PE will be destroied during the
+ * normal PCI hotplug path. For PCI hotplug during EEH recovery,
+ * the corresponding PE won't be destroied and deallocated.
  */
-void pcibios_remove_pci_devices(struct pci_bus *bus)
+void __pcibios_remove_pci_devices(struct pci_bus *bus, int purge_pe)
 {
-       struct pci_dev *dev, *tmp;
+       struct pci_dev *dev, *tmp;
        struct pci_bus *child_bus;
 
        /* First go down child busses */
        list_for_each_entry(child_bus, &bus->children, node)
-               pcibios_remove_pci_devices(child_bus);
+               __pcibios_remove_pci_devices(child_bus, purge_pe);
 
        pr_debug("PCI: Removing devices on bus %04x:%02x\n",
-                pci_domain_nr(bus),  bus->number);
+               pci_domain_nr(bus),  bus->number);
        list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) {
                pr_debug("     * Removing %s...\n", pci_name(dev));
-               eeh_remove_bus_device(dev);
-               pci_stop_and_remove_bus_device(dev);
-       }
+               eeh_remove_bus_device(dev, purge_pe);
+               pci_stop_and_remove_bus_device(dev);
+       }
+}
+
+/**
+ * pcibios_remove_pci_devices - remove all devices under this bus
+ *
+ * Remove all of the PCI devices under this bus both from the
+ * linux pci device tree, and from the powerpc EEH address cache.
+ */
+void pcibios_remove_pci_devices(struct pci_bus *bus)
+{
+       __pcibios_remove_pci_devices(bus, 1);
 }
 EXPORT_SYMBOL_GPL(pcibios_remove_pci_devices);
 
index 1e117c2a3cad032390fe9ab1a12745b6ce405313..b29e20b7862f168673b3ad980200dd3c80fff175 100644 (file)
@@ -388,7 +388,7 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn)
        /* Remove the EADS bridge device itself */
        BUG_ON(!bus->self);
        pr_debug("PCI: Now removing bridge device %s\n", pci_name(bus->self));
-       eeh_remove_bus_device(bus->self);
+       eeh_remove_bus_device(bus->self, true);
        pci_stop_and_remove_bus_device(bus->self);
 
        return 0;