diff mbox series

[1/2] PCI: Add pci_host_bridge_handle_link_down() API to handle the PCI link down event

Message ID 20250221172309.120009-2-manivannan.sadhasivam@linaro.org (mailing list archive)
State Not Applicable
Headers show
Series PCI: Add support for handling link down event from host bridge drivers | expand

Commit Message

Manivannan Sadhasivam Feb. 21, 2025, 5:23 p.m. UTC
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(+)
diff mbox series

Patch

diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b6536ed599c3..36ffcd2a44a5 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -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 */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 47b31ad724fa..1c6f18a51bdd 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -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);
 };
 
 /*