powerpc/pci: Mask linkDown on resetting PCI bus
authorGavin Shan <gwshan@linux.vnet.ibm.com>
Thu, 24 Apr 2014 08:00:24 +0000 (18:00 +1000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Mon, 28 Apr 2014 07:34:53 +0000 (17:34 +1000)
The problem was initially reported by Wendy who tried pass through
IPR adapter, which was connected to PHB root port directly, to KVM
based guest. When doing that, pci_reset_bridge_secondary_bus() was
called by VFIO driver and linkDown was detected by the root port.
That caused all PEs to be frozen.

The patch fixes the issue by routing the reset for the secondary bus
of root port to underly firmware. For that, one more weak function
pci_reset_secondary_bus() is introduced so that the individual platforms
can override that and do specific reset for bridge's secondary bus.

Reported-by: Wendy Xiong <wenxiong@linux.vnet.ibm.com>
Signed-off-by: Gavin Shan <gwshan@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
arch/powerpc/include/asm/machdep.h
arch/powerpc/kernel/pci-common.c
arch/powerpc/platforms/powernv/eeh-ioda.c
arch/powerpc/platforms/powernv/pci-ioda.c
arch/powerpc/platforms/powernv/pci.h
drivers/pci/pci.c

index 5b6c03f1058f21c24a6402d05b8c19d6cd884983..240b137ce0cfdc19b72b69ff666e11537b5af802 100644 (file)
@@ -241,6 +241,9 @@ struct machdep_calls {
        /* Called during PCI resource reassignment */
        resource_size_t (*pcibios_window_alignment)(struct pci_bus *, unsigned long type);
 
+       /* Reset the secondary bus of bridge */
+       void  (*pcibios_reset_secondary_bus)(struct pci_dev *dev);
+
        /* Called to shutdown machine specific hardware not already controlled
         * by other drivers.
         */
index d9476c1fc9596132d27bc68d5795e2b6a3ee8a6a..f9ca5091840ca848403ba4baf6227ff24122b15b 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/string.h>
 #include <linux/init.h>
 #include <linux/bootmem.h>
+#include <linux/delay.h>
 #include <linux/export.h>
 #include <linux/of_address.h>
 #include <linux/of_pci.h>
@@ -120,6 +121,25 @@ resource_size_t pcibios_window_alignment(struct pci_bus *bus,
        return 1;
 }
 
+void pcibios_reset_secondary_bus(struct pci_dev *dev)
+{
+       u16 ctrl;
+
+       if (ppc_md.pcibios_reset_secondary_bus) {
+               ppc_md.pcibios_reset_secondary_bus(dev);
+               return;
+       }
+
+       pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &ctrl);
+       ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
+       pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl);
+       msleep(2);
+
+       ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
+       pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl);
+       ssleep(1);
+}
+
 static resource_size_t pcibios_io_size(const struct pci_controller *hose)
 {
 #ifdef CONFIG_PPC64
index 268cd46af8f1e57dee7f2f5c69225fa6653f3272..58ef80987eedab40705967ea745b4f628bc667e3 100644 (file)
@@ -474,6 +474,8 @@ static int ioda_eeh_bridge_reset(struct pci_dev *dev, int option)
 
 {
        struct device_node *dn = pci_device_to_OF_node(dev);
+       struct eeh_dev *edev = of_node_to_eeh_dev(dn);
+       int aer = edev ? edev->aer_cap : 0;
        u32 ctrl;
 
        pr_debug("%s: Reset PCI bus %04x:%02x with option %d\n",
@@ -483,24 +485,56 @@ static int ioda_eeh_bridge_reset(struct pci_dev *dev, int option)
        switch (option) {
        case EEH_RESET_FUNDAMENTAL:
        case EEH_RESET_HOT:
+               /* Don't report linkDown event */
+               if (aer) {
+                       eeh_ops->read_config(dn, aer + PCI_ERR_UNCOR_MASK,
+                                            4, &ctrl);
+                       ctrl |= PCI_ERR_UNC_SURPDN;
+                        eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_MASK,
+                                             4, ctrl);
+                }
+
                eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &ctrl);
                ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
                eeh_ops->write_config(dn, PCI_BRIDGE_CONTROL, 2, ctrl);
-
                msleep(EEH_PE_RST_HOLD_TIME);
+
                break;
        case EEH_RESET_DEACTIVATE:
                eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &ctrl);
                ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
                eeh_ops->write_config(dn, PCI_BRIDGE_CONTROL, 2, ctrl);
-
                msleep(EEH_PE_RST_SETTLE_TIME);
+
+               /* Continue reporting linkDown event */
+               if (aer) {
+                       eeh_ops->read_config(dn, aer + PCI_ERR_UNCOR_MASK,
+                                            4, &ctrl);
+                       ctrl &= ~PCI_ERR_UNC_SURPDN;
+                       eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_MASK,
+                                             4, ctrl);
+               }
+
                break;
        }
 
        return 0;
 }
 
+void pnv_pci_reset_secondary_bus(struct pci_dev *dev)
+{
+       struct pci_controller *hose;
+
+       if (pci_is_root_bus(dev->bus)) {
+               hose = pci_bus_to_host(dev->bus);
+               ioda_eeh_root_reset(hose, EEH_RESET_HOT);
+               ioda_eeh_root_reset(hose, EEH_RESET_DEACTIVATE);
+       } else {
+               ioda_eeh_bridge_reset(dev, EEH_RESET_HOT);
+               ioda_eeh_bridge_reset(dev, EEH_RESET_DEACTIVATE);
+       }
+}
+
 /**
  * ioda_eeh_reset - Reset the indicated PE
  * @pe: EEH PE
index 98824aa991731882cca87f806f464198f356ff26..a179ff00be3e09476d78d1749b9c166c306bc246 100644 (file)
@@ -1386,6 +1386,7 @@ void __init pnv_pci_init_ioda_phb(struct device_node *np,
        ppc_md.pcibios_fixup = pnv_pci_ioda_fixup;
        ppc_md.pcibios_enable_device_hook = pnv_pci_enable_device_hook;
        ppc_md.pcibios_window_alignment = pnv_pci_window_alignment;
+       ppc_md.pcibios_reset_secondary_bus = pnv_pci_reset_secondary_bus;
        pci_add_flags(PCI_REASSIGN_ALL_RSRC);
 
        /* Reset IODA tables to a clean state */
index 39ec6978e8092e852dfe3b28c3a371ebb9f97b18..34a09740aad3d21bdf9393c421c4815935225047 100644 (file)
@@ -204,5 +204,6 @@ extern void pnv_pci_init_ioda_hub(struct device_node *np);
 extern void pnv_pci_init_ioda2_phb(struct device_node *np);
 extern void pnv_pci_ioda_tce_invalidate(struct iommu_table *tbl,
                                        __be64 *startp, __be64 *endp, bool rm);
+extern void pnv_pci_reset_secondary_bus(struct pci_dev *dev);
 
 #endif /* __POWERNV_PCI_H */
index 7325d43bf030ce65d5f386f6aeeeb3bfa4d5c482..633382d227f41ce00ebd2b2f612b7b36f84247df 100644 (file)
@@ -3167,14 +3167,7 @@ static int pci_pm_reset(struct pci_dev *dev, int probe)
        return 0;
 }
 
-/**
- * pci_reset_bridge_secondary_bus - Reset the secondary bus on a PCI bridge.
- * @dev: Bridge device
- *
- * Use the bridge control register to assert reset on the secondary bus.
- * Devices on the secondary bus are left in power-on state.
- */
-void pci_reset_bridge_secondary_bus(struct pci_dev *dev)
+void __weak pcibios_reset_secondary_bus(struct pci_dev *dev)
 {
        u16 ctrl;
 
@@ -3199,6 +3192,18 @@ void pci_reset_bridge_secondary_bus(struct pci_dev *dev)
         */
        ssleep(1);
 }
+
+/**
+ * pci_reset_bridge_secondary_bus - Reset the secondary bus on a PCI bridge.
+ * @dev: Bridge device
+ *
+ * Use the bridge control register to assert reset on the secondary bus.
+ * Devices on the secondary bus are left in power-on state.
+ */
+void pci_reset_bridge_secondary_bus(struct pci_dev *dev)
+{
+       pcibios_reset_secondary_bus(dev);
+}
 EXPORT_SYMBOL_GPL(pci_reset_bridge_secondary_bus);
 
 static int pci_parent_bus_reset(struct pci_dev *dev, int probe)