PCI/CXL: Add 'cxl_bus' reset method for devices below CXL Ports
authorDave Jiang <dave.jiang@intel.com>
Thu, 2 May 2024 16:57:33 +0000 (09:57 -0700)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 8 May 2024 18:25:43 +0000 (13:25 -0500)
By default Secondary Bus Reset (SBR) is masked for CXL Ports (see CXL r3.1,
sec 8.1.5.2).

Add cxl_reset_bus_function() (method "cxl_bus") to set the "Unmask SBR" bit
in the upstream CXL Port before performing the bus reset and restore the
original value afterwards.

This method allows the user to perform a bus reset on a CXL device without
needing to set the "Unmask SBR" bit via a user tool.

Link: https://lore.kernel.org/r/20240502165851.1948523-5-dave.jiang@intel.com
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
[bhelgaas: simplify commit log, invert condition to avoid negation]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
drivers/pci/pci.c
include/linux/pci.h

index 225cec964f251f49f0e90da2fa542cee5381a413..13605261a8810f28d5df8c0f1fbebe686f27e454 100644 (file)
@@ -4980,6 +4980,44 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
        return pci_parent_bus_reset(dev, probe);
 }
 
+static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
+{
+       struct pci_dev *bridge;
+       u16 dvsec, reg, val;
+       int rc;
+
+       bridge = pci_upstream_bridge(dev);
+       if (!bridge)
+               return -ENOTTY;
+
+       dvsec = cxl_port_dvsec(bridge);
+       if (!dvsec)
+               return -ENOTTY;
+
+       if (probe)
+               return 0;
+
+       rc = pci_read_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL, &reg);
+       if (rc)
+               return -ENOTTY;
+
+       if (reg & PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR) {
+               val = reg;
+       } else {
+               val = reg | PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR;
+               pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
+                                     val);
+       }
+
+       rc = pci_reset_bus_function(dev, probe);
+
+       if (reg != val)
+               pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
+                                     reg);
+
+       return rc;
+}
+
 void pci_dev_lock(struct pci_dev *dev)
 {
        /* block PM suspend, driver probe, etc. */
@@ -5064,6 +5102,7 @@ static const struct pci_reset_fn_method pci_reset_fn_methods[] = {
        { pci_af_flr, .name = "af_flr" },
        { pci_pm_reset, .name = "pm" },
        { pci_reset_bus_function, .name = "bus" },
+       { cxl_reset_bus_function, .name = "cxl_bus" },
 };
 
 static ssize_t reset_method_show(struct device *dev,
index e4e7b175af5477f3e4a43fa1991d9ff27a30c9f9..b06c1c0ec9bd4156cdf59e826ed0b4d0d16a20e8 100644 (file)
@@ -51,7 +51,7 @@
                               PCI_STATUS_PARITY)
 
 /* Number of reset methods used in pci_reset_fn_methods array in pci.c */
-#define PCI_NUM_RESET_METHODS 7
+#define PCI_NUM_RESET_METHODS 8
 
 #define PCI_RESET_PROBE                true
 #define PCI_RESET_DO_RESET     false