]> Git Repo - linux.git/commitdiff
Merge branch 'pci/aw-reset-v5' into next
authorBjorn Helgaas <[email protected]>
Thu, 15 Aug 2013 20:41:33 +0000 (14:41 -0600)
committerBjorn Helgaas <[email protected]>
Thu, 15 Aug 2013 20:41:33 +0000 (14:41 -0600)
* pci/aw-reset-v5:
  PCI: Add pci_probe_reset_slot() and pci_probe_reset_bus()
  PCI: Remove aer_do_secondary_bus_reset()
  PCI: Tune secondary bus reset timing
  PCI: Wake-up devices before saving config space for reset
  PCI: Add pci_reset_slot() and pci_reset_bus()
  PCI: Split out pci_dev lock/unlock and save/restore
  PCI: Add slot reset option to pci_dev_reset()
  PCI: pciehp: Add reset_slot() method
  PCI: Add hotplug_slot_ops.reset_slot()
  PCI: Add pci_reset_bridge_secondary_bus()

1  2 
drivers/pci/pci.c
include/linux/pci.h

diff --combined drivers/pci/pci.c
index 7d6ce2ec04ec32c9d9c1bad793d41aa35ccef4be,7f89372483dd96d4655abc61aa5bbe0fb9068367..10e3c4e15178beeac370202e505ed7a806a4bff2
@@@ -22,6 -22,7 +22,7 @@@
  #include <linux/interrupt.h>
  #include <linux/device.h>
  #include <linux/pm_runtime.h>
+ #include <linux/pci_hotplug.h>
  #include <asm-generic/pci-bridge.h>
  #include <asm/setup.h>
  #include "pci.h"
@@@ -1992,7 -1993,7 +1993,7 @@@ static void pci_add_saved_cap(struct pc
  }
  
  /**
 - * pci_add_save_buffer - allocate buffer for saving given capability registers
 + * pci_add_cap_save_buffer - allocate buffer for saving given capability registers
   * @dev: the PCI device
   * @cap: the capability to allocate the buffer for
   * @size: requested size of the buffer
@@@ -2359,27 -2360,6 +2360,27 @@@ void pci_enable_acs(struct pci_dev *dev
        pci_write_config_word(dev, pos + PCI_ACS_CTRL, ctrl);
  }
  
 +static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags)
 +{
 +      int pos;
 +      u16 cap, ctrl;
 +
 +      pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ACS);
 +      if (!pos)
 +              return false;
 +
 +      /*
 +       * Except for egress control, capabilities are either required
 +       * or only required if controllable.  Features missing from the
 +       * capability field can therefore be assumed as hard-wired enabled.
 +       */
 +      pci_read_config_word(pdev, pos + PCI_ACS_CAP, &cap);
 +      acs_flags &= (cap | PCI_ACS_EC);
 +
 +      pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl);
 +      return (ctrl & acs_flags) == acs_flags;
 +}
 +
  /**
   * pci_acs_enabled - test ACS against required flags for a given device
   * @pdev: device to test
   *
   * Return true if the device supports the provided flags.  Automatically
   * filters out flags that are not implemented on multifunction devices.
 + *
 + * Note that this interface checks the effective ACS capabilities of the
 + * device rather than the actual capabilities.  For instance, most single
 + * function endpoints are not required to support ACS because they have no
 + * opportunity for peer-to-peer access.  We therefore return 'true'
 + * regardless of whether the device exposes an ACS capability.  This makes
 + * it much easier for callers of this function to ignore the actual type
 + * or topology of the device when testing ACS support.
   */
  bool pci_acs_enabled(struct pci_dev *pdev, u16 acs_flags)
  {
 -      int pos, ret;
 -      u16 ctrl;
 +      int ret;
  
        ret = pci_dev_specific_acs_enabled(pdev, acs_flags);
        if (ret >= 0)
                return ret > 0;
  
 +      /*
 +       * Conventional PCI and PCI-X devices never support ACS, either
 +       * effectively or actually.  The shared bus topology implies that
 +       * any device on the bus can receive or snoop DMA.
 +       */
        if (!pci_is_pcie(pdev))
                return false;
  
 -      /* Filter out flags not applicable to multifunction */
 -      if (pdev->multifunction)
 -              acs_flags &= (PCI_ACS_RR | PCI_ACS_CR |
 -                            PCI_ACS_EC | PCI_ACS_DT);
 -
 -      if (pci_pcie_type(pdev) == PCI_EXP_TYPE_DOWNSTREAM ||
 -          pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT ||
 -          pdev->multifunction) {
 -              pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ACS);
 -              if (!pos)
 -                      return false;
 +      switch (pci_pcie_type(pdev)) {
 +      /*
 +       * PCI/X-to-PCIe bridges are not specifically mentioned by the spec,
 +       * but since their primary inteface is PCI/X, we conservatively
 +       * handle them as we would a non-PCIe device.
 +       */
 +      case PCI_EXP_TYPE_PCIE_BRIDGE:
 +      /*
 +       * PCIe 3.0, 6.12.1 excludes ACS on these devices.  "ACS is never
 +       * applicable... must never implement an ACS Extended Capability...".
 +       * This seems arbitrary, but we take a conservative interpretation
 +       * of this statement.
 +       */
 +      case PCI_EXP_TYPE_PCI_BRIDGE:
 +      case PCI_EXP_TYPE_RC_EC:
 +              return false;
 +      /*
 +       * PCIe 3.0, 6.12.1.1 specifies that downstream and root ports should
 +       * implement ACS in order to indicate their peer-to-peer capabilities,
 +       * regardless of whether they are single- or multi-function devices.
 +       */
 +      case PCI_EXP_TYPE_DOWNSTREAM:
 +      case PCI_EXP_TYPE_ROOT_PORT:
 +              return pci_acs_flags_enabled(pdev, acs_flags);
 +      /*
 +       * PCIe 3.0, 6.12.1.2 specifies ACS capabilities that should be
 +       * implemented by the remaining PCIe types to indicate peer-to-peer
 +       * capabilities, but only when they are part of a multifunciton
 +       * device.  The footnote for section 6.12 indicates the specific
 +       * PCIe types included here.
 +       */
 +      case PCI_EXP_TYPE_ENDPOINT:
 +      case PCI_EXP_TYPE_UPSTREAM:
 +      case PCI_EXP_TYPE_LEG_END:
 +      case PCI_EXP_TYPE_RC_END:
 +              if (!pdev->multifunction)
 +                      break;
  
 -              pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl);
 -              if ((ctrl & acs_flags) != acs_flags)
 -                      return false;
 +              return pci_acs_flags_enabled(pdev, acs_flags);
        }
  
 +      /*
 +       * PCIe 3.0, 6.12.1.3 specifies no ACS capabilties are applicable
 +       * to single function devices with the exception of downstream ports.
 +       */
        return true;
  }
  
@@@ -3159,17 -3099,19 +3160,17 @@@ int pci_set_dma_seg_boundary(struct pci
  }
  EXPORT_SYMBOL(pci_set_dma_seg_boundary);
  
 -static int pcie_flr(struct pci_dev *dev, int probe)
 +/**
 + * pci_wait_for_pending_transaction - waits for pending transaction
 + * @dev: the PCI device to operate on
 + *
 + * Return 0 if transaction is pending 1 otherwise.
 + */
 +int pci_wait_for_pending_transaction(struct pci_dev *dev)
  {
        int i;
 -      u32 cap;
        u16 status;
  
 -      pcie_capability_read_dword(dev, PCI_EXP_DEVCAP, &cap);
 -      if (!(cap & PCI_EXP_DEVCAP_FLR))
 -              return -ENOTTY;
 -
 -      if (probe)
 -              return 0;
 -
        /* Wait for Transaction Pending bit clean */
        for (i = 0; i < 4; i++) {
                if (i)
  
                pcie_capability_read_word(dev, PCI_EXP_DEVSTA, &status);
                if (!(status & PCI_EXP_DEVSTA_TRPND))
 -                      goto clear;
 +                      return 1;
        }
  
 -      dev_err(&dev->dev, "transaction is not cleared; "
 -                      "proceeding with reset anyway\n");
 +      return 0;
 +}
 +EXPORT_SYMBOL(pci_wait_for_pending_transaction);
 +
 +static int pcie_flr(struct pci_dev *dev, int probe)
 +{
 +      u32 cap;
 +
 +      pcie_capability_read_dword(dev, PCI_EXP_DEVCAP, &cap);
 +      if (!(cap & PCI_EXP_DEVCAP_FLR))
 +              return -ENOTTY;
 +
 +      if (probe)
 +              return 0;
 +
 +      if (!pci_wait_for_pending_transaction(dev))
 +              dev_err(&dev->dev, "transaction is not cleared; proceeding with reset anyway\n");
  
 -clear:
        pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_BCR_FLR);
  
        msleep(100);
@@@ -3288,9 -3216,42 +3289,42 @@@ static int pci_pm_reset(struct pci_dev 
        return 0;
  }
  
- static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
+ /**
+  * 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)
  {
        u16 ctrl;
+       pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &ctrl);
+       ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
+       pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl);
+       /*
+        * PCI spec v3.0 7.6.4.2 requires minimum Trst of 1ms.  Double
+        * this to 2ms to ensure that we meet the minium requirement.
+        */
+       msleep(2);
+       ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
+       pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl);
+       /*
+        * Trhfa for conventional PCI is 2^25 clock cycles.
+        * Assuming a minimum 33MHz clock this results in a 1s
+        * delay before we can consider subordinate devices to
+        * be re-initialized.  PCIe has some ways to shorten this,
+        * but we don't make use of them yet.
+        */
+       ssleep(1);
+ }
+ EXPORT_SYMBOL_GPL(pci_reset_bridge_secondary_bus);
+ static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
+ {
        struct pci_dev *pdev;
  
        if (pci_is_root_bus(dev->bus) || dev->subordinate || !dev->bus->self)
        if (probe)
                return 0;
  
-       pci_read_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, &ctrl);
-       ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
-       pci_write_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, ctrl);
-       msleep(100);
-       ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
-       pci_write_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, ctrl);
-       msleep(100);
+       pci_reset_bridge_secondary_bus(dev->bus->self);
  
        return 0;
  }
  
+ static int pci_reset_hotplug_slot(struct hotplug_slot *hotplug, int probe)
+ {
+       int rc = -ENOTTY;
+       if (!hotplug || !try_module_get(hotplug->ops->owner))
+               return rc;
+       if (hotplug->ops->reset_slot)
+               rc = hotplug->ops->reset_slot(hotplug, probe);
+       module_put(hotplug->ops->owner);
+       return rc;
+ }
+ static int pci_dev_reset_slot_function(struct pci_dev *dev, int probe)
+ {
+       struct pci_dev *pdev;
+       if (dev->subordinate || !dev->slot)
+               return -ENOTTY;
+       list_for_each_entry(pdev, &dev->bus->devices, bus_list)
+               if (pdev != dev && pdev->slot == dev->slot)
+                       return -ENOTTY;
+       return pci_reset_hotplug_slot(dev->slot->hotplug, probe);
+ }
  static int __pci_dev_reset(struct pci_dev *dev, int probe)
  {
        int rc;
        if (rc != -ENOTTY)
                goto done;
  
+       rc = pci_dev_reset_slot_function(dev, probe);
+       if (rc != -ENOTTY)
+               goto done;
        rc = pci_parent_bus_reset(dev, probe);
  done:
        return rc;
  }
  
+ static void pci_dev_lock(struct pci_dev *dev)
+ {
+       pci_cfg_access_lock(dev);
+       /* block PM suspend, driver probe, etc. */
+       device_lock(&dev->dev);
+ }
+ static void pci_dev_unlock(struct pci_dev *dev)
+ {
+       device_unlock(&dev->dev);
+       pci_cfg_access_unlock(dev);
+ }
+ static void pci_dev_save_and_disable(struct pci_dev *dev)
+ {
+       /*
+        * Wake-up device prior to save.  PM registers default to D0 after
+        * reset and a simple register restore doesn't reliably return
+        * to a non-D0 state anyway.
+        */
+       pci_set_power_state(dev, PCI_D0);
+       pci_save_state(dev);
+       /*
+        * Disable the device by clearing the Command register, except for
+        * INTx-disable which is set.  This not only disables MMIO and I/O port
+        * BARs, but also prevents the device from being Bus Master, preventing
+        * DMA from the device including MSI/MSI-X interrupts.  For PCI 2.3
+        * compliant devices, INTx-disable prevents legacy interrupts.
+        */
+       pci_write_config_word(dev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
+ }
+ static void pci_dev_restore(struct pci_dev *dev)
+ {
+       pci_restore_state(dev);
+ }
  static int pci_dev_reset(struct pci_dev *dev, int probe)
  {
        int rc;
  
-       if (!probe) {
-               pci_cfg_access_lock(dev);
-               /* block PM suspend, driver probe, etc. */
-               device_lock(&dev->dev);
-       }
+       if (!probe)
+               pci_dev_lock(dev);
  
        rc = __pci_dev_reset(dev, probe);
  
-       if (!probe) {
-               device_unlock(&dev->dev);
-               pci_cfg_access_unlock(dev);
-       }
+       if (!probe)
+               pci_dev_unlock(dev);
        return rc;
  }
  /**
@@@ -3448,22 -3469,249 +3542,249 @@@ int pci_reset_function(struct pci_dev *
        if (rc)
                return rc;
  
-       pci_save_state(dev);
-       /*
-        * both INTx and MSI are disabled after the Interrupt Disable bit
-        * is set and the Bus Master bit is cleared.
-        */
-       pci_write_config_word(dev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
+       pci_dev_save_and_disable(dev);
  
        rc = pci_dev_reset(dev, 0);
  
-       pci_restore_state(dev);
+       pci_dev_restore(dev);
  
        return rc;
  }
  EXPORT_SYMBOL_GPL(pci_reset_function);
  
+ /* Lock devices from the top of the tree down */
+ static void pci_bus_lock(struct pci_bus *bus)
+ {
+       struct pci_dev *dev;
+       list_for_each_entry(dev, &bus->devices, bus_list) {
+               pci_dev_lock(dev);
+               if (dev->subordinate)
+                       pci_bus_lock(dev->subordinate);
+       }
+ }
+ /* Unlock devices from the bottom of the tree up */
+ static void pci_bus_unlock(struct pci_bus *bus)
+ {
+       struct pci_dev *dev;
+       list_for_each_entry(dev, &bus->devices, bus_list) {
+               if (dev->subordinate)
+                       pci_bus_unlock(dev->subordinate);
+               pci_dev_unlock(dev);
+       }
+ }
+ /* Lock devices from the top of the tree down */
+ static void pci_slot_lock(struct pci_slot *slot)
+ {
+       struct pci_dev *dev;
+       list_for_each_entry(dev, &slot->bus->devices, bus_list) {
+               if (!dev->slot || dev->slot != slot)
+                       continue;
+               pci_dev_lock(dev);
+               if (dev->subordinate)
+                       pci_bus_lock(dev->subordinate);
+       }
+ }
+ /* Unlock devices from the bottom of the tree up */
+ static void pci_slot_unlock(struct pci_slot *slot)
+ {
+       struct pci_dev *dev;
+       list_for_each_entry(dev, &slot->bus->devices, bus_list) {
+               if (!dev->slot || dev->slot != slot)
+                       continue;
+               if (dev->subordinate)
+                       pci_bus_unlock(dev->subordinate);
+               pci_dev_unlock(dev);
+       }
+ }
+ /* Save and disable devices from the top of the tree down */
+ static void pci_bus_save_and_disable(struct pci_bus *bus)
+ {
+       struct pci_dev *dev;
+       list_for_each_entry(dev, &bus->devices, bus_list) {
+               pci_dev_save_and_disable(dev);
+               if (dev->subordinate)
+                       pci_bus_save_and_disable(dev->subordinate);
+       }
+ }
+ /*
+  * Restore devices from top of the tree down - parent bridges need to be
+  * restored before we can get to subordinate devices.
+  */
+ static void pci_bus_restore(struct pci_bus *bus)
+ {
+       struct pci_dev *dev;
+       list_for_each_entry(dev, &bus->devices, bus_list) {
+               pci_dev_restore(dev);
+               if (dev->subordinate)
+                       pci_bus_restore(dev->subordinate);
+       }
+ }
+ /* Save and disable devices from the top of the tree down */
+ static void pci_slot_save_and_disable(struct pci_slot *slot)
+ {
+       struct pci_dev *dev;
+       list_for_each_entry(dev, &slot->bus->devices, bus_list) {
+               if (!dev->slot || dev->slot != slot)
+                       continue;
+               pci_dev_save_and_disable(dev);
+               if (dev->subordinate)
+                       pci_bus_save_and_disable(dev->subordinate);
+       }
+ }
+ /*
+  * Restore devices from top of the tree down - parent bridges need to be
+  * restored before we can get to subordinate devices.
+  */
+ static void pci_slot_restore(struct pci_slot *slot)
+ {
+       struct pci_dev *dev;
+       list_for_each_entry(dev, &slot->bus->devices, bus_list) {
+               if (!dev->slot || dev->slot != slot)
+                       continue;
+               pci_dev_restore(dev);
+               if (dev->subordinate)
+                       pci_bus_restore(dev->subordinate);
+       }
+ }
+ static int pci_slot_reset(struct pci_slot *slot, int probe)
+ {
+       int rc;
+       if (!slot)
+               return -ENOTTY;
+       if (!probe)
+               pci_slot_lock(slot);
+       might_sleep();
+       rc = pci_reset_hotplug_slot(slot->hotplug, probe);
+       if (!probe)
+               pci_slot_unlock(slot);
+       return rc;
+ }
+ /**
+  * pci_probe_reset_slot - probe whether a PCI slot can be reset
+  * @slot: PCI slot to probe
+  *
+  * Return 0 if slot can be reset, negative if a slot reset is not supported.
+  */
+ int pci_probe_reset_slot(struct pci_slot *slot)
+ {
+       return pci_slot_reset(slot, 1);
+ }
+ EXPORT_SYMBOL_GPL(pci_probe_reset_slot);
+ /**
+  * pci_reset_slot - reset a PCI slot
+  * @slot: PCI slot to reset
+  *
+  * A PCI bus may host multiple slots, each slot may support a reset mechanism
+  * independent of other slots.  For instance, some slots may support slot power
+  * control.  In the case of a 1:1 bus to slot architecture, this function may
+  * wrap the bus reset to avoid spurious slot related events such as hotplug.
+  * Generally a slot reset should be attempted before a bus reset.  All of the
+  * function of the slot and any subordinate buses behind the slot are reset
+  * through this function.  PCI config space of all devices in the slot and
+  * behind the slot is saved before and restored after reset.
+  *
+  * Return 0 on success, non-zero on error.
+  */
+ int pci_reset_slot(struct pci_slot *slot)
+ {
+       int rc;
+       rc = pci_slot_reset(slot, 1);
+       if (rc)
+               return rc;
+       pci_slot_save_and_disable(slot);
+       rc = pci_slot_reset(slot, 0);
+       pci_slot_restore(slot);
+       return rc;
+ }
+ EXPORT_SYMBOL_GPL(pci_reset_slot);
+ static int pci_bus_reset(struct pci_bus *bus, int probe)
+ {
+       if (!bus->self)
+               return -ENOTTY;
+       if (probe)
+               return 0;
+       pci_bus_lock(bus);
+       might_sleep();
+       pci_reset_bridge_secondary_bus(bus->self);
+       pci_bus_unlock(bus);
+       return 0;
+ }
+ /**
+  * pci_probe_reset_bus - probe whether a PCI bus can be reset
+  * @bus: PCI bus to probe
+  *
+  * Return 0 if bus can be reset, negative if a bus reset is not supported.
+  */
+ int pci_probe_reset_bus(struct pci_bus *bus)
+ {
+       return pci_bus_reset(bus, 1);
+ }
+ EXPORT_SYMBOL_GPL(pci_probe_reset_bus);
+ /**
+  * pci_reset_bus - reset a PCI bus
+  * @bus: top level PCI bus to reset
+  *
+  * Do a bus reset on the given bus and any subordinate buses, saving
+  * and restoring state of all devices.
+  *
+  * Return 0 on success, non-zero on error.
+  */
+ int pci_reset_bus(struct pci_bus *bus)
+ {
+       int rc;
+       rc = pci_bus_reset(bus, 1);
+       if (rc)
+               return rc;
+       pci_bus_save_and_disable(bus);
+       rc = pci_bus_reset(bus, 0);
+       pci_bus_restore(bus);
+       return rc;
+ }
+ EXPORT_SYMBOL_GPL(pci_reset_bus);
  /**
   * pcix_get_max_mmrbc - get PCI-X maximum designed memory read byte count
   * @dev: PCI device to query
diff --combined include/linux/pci.h
index e6470016c718b73fe6b4f82d083496b311da15db,daf40cd851df0871f4bd00eee435324f37e48988..408f047ad92995246405632b66b51ff06ba09363
@@@ -914,7 -914,6 +914,7 @@@ bool pci_check_and_unmask_intx(struct p
  void pci_msi_off(struct pci_dev *dev);
  int pci_set_dma_max_seg_size(struct pci_dev *dev, unsigned int size);
  int pci_set_dma_seg_boundary(struct pci_dev *dev, unsigned long mask);
 +int pci_wait_for_pending_transaction(struct pci_dev *dev);
  int pcix_get_max_mmrbc(struct pci_dev *dev);
  int pcix_get_mmrbc(struct pci_dev *dev);
  int pcix_set_mmrbc(struct pci_dev *dev, int mmrbc);
@@@ -925,6 -924,11 +925,11 @@@ int pcie_set_mps(struct pci_dev *dev, i
  int __pci_reset_function(struct pci_dev *dev);
  int __pci_reset_function_locked(struct pci_dev *dev);
  int pci_reset_function(struct pci_dev *dev);
+ int pci_probe_reset_slot(struct pci_slot *slot);
+ int pci_reset_slot(struct pci_slot *slot);
+ int pci_probe_reset_bus(struct pci_bus *bus);
+ int pci_reset_bus(struct pci_bus *bus);
+ void pci_reset_bridge_secondary_bus(struct pci_dev *dev);
  void pci_update_resource(struct pci_dev *dev, int resno);
  int __must_check pci_assign_resource(struct pci_dev *dev, int i);
  int __must_check pci_reassign_resource(struct pci_dev *dev, int i, resource_size_t add_size, resource_size_t align);
This page took 0.089045 seconds and 4 git commands to generate.