@@ -706,6 +706,40 @@ void pci_free_host_bridge(struct pci_host_bridge *bridge)
}
EXPORT_SYMBOL(pci_free_host_bridge);
+void pci_host_bridge_handle_link_down(struct pci_host_bridge *bridge)
+{
+ struct pci_bus *bus = bridge->bus;
+ struct device *dev = &bridge->dev;
+ struct pci_dev *child, *tmp;
+ int ret;
+
+ pci_lock_rescan_remove();
+
+ /* Knock the devices off root bus since we cannot access them */
+ dev_warn(dev, "Removing devices from root bus due to link down\n");
+ list_for_each_entry_safe(child, tmp, &bus->devices, bus_list)
+ pci_stop_and_remove_bus_device(child);
+
+ /* Now retrain the link in a vendor specific way to bring it back */
+ if (bus->ops->retrain_link) {
+ dev_info(dev, "Starting link retraining\n");
+ ret = bus->ops->retrain_link(bus);
+ if (ret) {
+ dev_err(dev, "Failed to retrain the link\n");
+ pci_unlock_rescan_remove();
+ return;
+ }
+ dev_info(dev, "Link retraining completed\n");
+ } else {
+ dev_warn(dev, "retrain_link() callback not implemented!\n");
+ }
+
+ /* Finally, rescan the bus to bring the devices back */
+ pci_rescan_bus(bus);
+ pci_unlock_rescan_remove();
+}
+EXPORT_SYMBOL(pci_host_bridge_handle_link_down);
+
/* Indexed by PCI_X_SSTATUS_FREQ (secondary bus mode and frequency) */
static const unsigned char pcix_bus_speed[] = {
PCI_SPEED_UNKNOWN, /* 0 */
@@ -637,6 +637,7 @@ struct pci_host_bridge *pci_alloc_host_bridge(size_t priv);
struct pci_host_bridge *devm_pci_alloc_host_bridge(struct device *dev,
size_t priv);
void pci_free_host_bridge(struct pci_host_bridge *bridge);
+void pci_host_bridge_handle_link_down(struct pci_host_bridge *bridge);
struct pci_host_bridge *pci_find_host_bridge(struct pci_bus *bus);
void pci_set_host_bridge_release(struct pci_host_bridge *bridge,
@@ -804,6 +805,7 @@ struct pci_ops {
void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where);
int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);
+ int (*retrain_link)(struct pci_bus *bus);
};
/*
The PCI link, when down, needs to be retrained to bring it back. But that cannot be done in a generic way as link retrain procedure is specific to host bridges. So add a new API pci_host_bridge_handle_link_down() that could be called by the host bridge drivers when the link goes down. The API will remove all the devices from the root bus since there is no way the PCI core/drivers can access them and then calls the bus specific 'retrain_link()' callback if available. This callback is supposed to be implemented by the host bridge drivers to retrain the link in a platform specific way. Once that succeeds, the API will rescan the bus to bring the devices back. Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> --- drivers/pci/probe.c | 34 ++++++++++++++++++++++++++++++++++ include/linux/pci.h | 2 ++ 2 files changed, 36 insertions(+)