PCI/CXL: Fail bus reset if upstream CXL Port has SBR masked
authorDave Jiang <dave.jiang@intel.com>
Thu, 2 May 2024 16:57:32 +0000 (09:57 -0700)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 8 May 2024 18:25:36 +0000 (13:25 -0500)
Per CXL spec r3.1, sec 8.1.5.2, the Secondary Bus Reset (SBR) bit in the
Bridge Control register of a CXL port has no effect unless the "Unmask SBR"
bit is set.

Return -ENOTTY if we attempt a bus reset on a device below a CXL Port where
"Unmask SBR" is 0.  Otherwise, the bus reset would appear to have succeeded
even though setting the bridge SBR bit had no effect.

Link: https://lore.kernel.org/linux-cxl/20240220203956.GA1502351@bhelgaas/
Link: https://lore.kernel.org/r/20240502165851.1948523-4-dave.jiang@intel.com
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
[bhelgaas: simplify commit log and comments]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
drivers/pci/pci.c
include/uapi/linux/pci_regs.h

index c63142352844177365c6695dc964fd42e928ac69..225cec964f251f49f0e90da2fa542cee5381a413 100644 (file)
@@ -4928,10 +4928,52 @@ static int pci_dev_reset_slot_function(struct pci_dev *dev, bool probe)
        return pci_reset_hotplug_slot(dev->slot->hotplug, probe);
 }
 
+static u16 cxl_port_dvsec(struct pci_dev *dev)
+{
+       return pci_find_dvsec_capability(dev, PCI_VENDOR_ID_CXL,
+                                        PCI_DVSEC_CXL_PORT);
+}
+
+static bool cxl_sbr_masked(struct pci_dev *dev)
+{
+       u16 dvsec, reg;
+       int rc;
+
+       dvsec = cxl_port_dvsec(dev);
+       if (!dvsec)
+               return false;
+
+       rc = pci_read_config_word(dev, dvsec + PCI_DVSEC_CXL_PORT_CTL, &reg);
+       if (rc || PCI_POSSIBLE_ERROR(reg))
+               return false;
+
+       /*
+        * Per CXL spec r3.1, sec 8.1.5.2, when "Unmask SBR" is 0, the SBR
+        * bit in Bridge Control has no effect.  When 1, the Port generates
+        * hot reset when the SBR bit is set to 1.
+        */
+       if (reg & PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR)
+               return false;
+
+       return true;
+}
+
 static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
 {
+       struct pci_dev *bridge = pci_upstream_bridge(dev);
        int rc;
 
+       /*
+        * If "dev" is below a CXL port that has SBR control masked, SBR
+        * won't do anything, so return error.
+        */
+       if (bridge && cxl_sbr_masked(bridge)) {
+               if (probe)
+                       return 0;
+
+               return -ENOTTY;
+       }
+
        rc = pci_dev_reset_slot_function(dev, probe);
        if (rc != -ENOTTY)
                return rc;
index a39193213ff25ba24233fe28bc4977f38c294702..6024eb2e9a2f8dddd5bd321b025b59aed475c600 100644 (file)
 #define PCI_DOE_DATA_OBJECT_DISC_RSP_3_PROTOCOL                0x00ff0000
 #define PCI_DOE_DATA_OBJECT_DISC_RSP_3_NEXT_INDEX      0xff000000
 
+/* Compute Express Link (CXL r3.1, sec 8.1.5) */
+#define PCI_DVSEC_CXL_PORT                             3
+#define PCI_DVSEC_CXL_PORT_CTL                         0x0c
+#define PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR              0x00000001
+
 #endif /* LINUX_PCI_REGS_H */