PCI/CXL: Fail bus reset if upstream CXL Port has SBR masked

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>
This commit is contained in:
Dave Jiang 2024-05-02 09:57:32 -07:00 committed by Bjorn Helgaas
parent 7e89efc6e9
commit b1956e2d07
2 changed files with 47 additions and 0 deletions

View 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;

View File

@ -1148,4 +1148,9 @@
#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 */