diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2013-08-15 22:41:33 +0200 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2013-08-15 22:41:33 +0200 |
commit | 7d8c4a2c5ae6d76f1142fb052d698b3c40ce518c (patch) | |
tree | e5f4d7779bd2e6b4e9ccc3e73d5c40bb537513e8 /drivers/pci/pci.c | |
parent | Merge branch 'pci/vipul-chelsio-reset-v2' into next (diff) | |
parent | PCI: Add pci_probe_reset_slot() and pci_probe_reset_bus() (diff) | |
download | linux-7d8c4a2c5ae6d76f1142fb052d698b3c40ce518c.tar.xz linux-7d8c4a2c5ae6d76f1142fb052d698b3c40ce518c.zip |
Merge branch 'pci/aw-reset-v5' into next
* 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()
Diffstat (limited to 'drivers/pci/pci.c')
-rw-r--r-- | drivers/pci/pci.c | 373 |
1 files changed, 347 insertions, 26 deletions
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 7d6ce2ec04ec..10e3c4e15178 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -22,6 +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" @@ -3288,9 +3289,42 @@ static int pci_pm_reset(struct pci_dev *dev, int probe) 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) @@ -3303,18 +3337,40 @@ static int pci_parent_bus_reset(struct pci_dev *dev, int probe) 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; @@ -3337,27 +3393,65 @@ static int __pci_dev_reset(struct pci_dev *dev, int probe) 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 +3542,249 @@ int pci_reset_function(struct pci_dev *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 |