Skip to content

Commit

Permalink
Merge branch 'pci/cxl'
Browse files Browse the repository at this point in the history
- Lock the upstream bridge while using it to perform a Secondary Bus Reset
  (Dave Jiang)

- Return failure when attempting Secondary Bus Reset below a CXL Port that
  has SBR masked (Dave Jiang)

- Add a "cxl_bus" reset method that temporarily unmasks SBR (Dave Jiang)

- Add a warning if we reset a CXL type 3 memory device that was in use
  while being reset (Dave Jiang)

* pci/cxl:
  cxl: Add post-reset warning if reset results in loss of previously committed HDM decoders
  PCI/CXL: Add 'cxl_bus' reset method for devices below CXL Ports
  PCI/CXL: Fail bus reset if upstream CXL Port has SBR masked
  PCI: Lock upstream bridge for pci_reset_function()
  PCI/CXL: Move CXL Vendor ID to pci_ids.h
  • Loading branch information
bjorn-helgaas committed May 16, 2024
2 parents a6faf3f + 934edcd commit 83711a1
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 8 deletions.
35 changes: 32 additions & 3 deletions drivers/cxl/core/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ static int cxl_cdat_get_length(struct device *dev,
__le32 response[2];
int rc;

rc = pci_doe(doe_mb, PCI_DVSEC_VENDOR_ID_CXL,
rc = pci_doe(doe_mb, PCI_VENDOR_ID_CXL,
CXL_DOE_PROTOCOL_TABLE_ACCESS,
&request, sizeof(request),
&response, sizeof(response));
Expand Down Expand Up @@ -555,7 +555,7 @@ static int cxl_cdat_read_table(struct device *dev,
__le32 request = CDAT_DOE_REQ(entry_handle);
int rc;

rc = pci_doe(doe_mb, PCI_DVSEC_VENDOR_ID_CXL,
rc = pci_doe(doe_mb, PCI_VENDOR_ID_CXL,
CXL_DOE_PROTOCOL_TABLE_ACCESS,
&request, sizeof(request),
rsp, sizeof(*rsp) + remaining);
Expand Down Expand Up @@ -640,7 +640,7 @@ void read_cdat_data(struct cxl_port *port)
if (!pdev)
return;

doe_mb = pci_find_doe_mailbox(pdev, PCI_DVSEC_VENDOR_ID_CXL,
doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_CXL,
CXL_DOE_PROTOCOL_TABLE_ACCESS);
if (!doe_mb) {
dev_dbg(dev, "No CDAT mailbox\n");
Expand Down Expand Up @@ -1045,3 +1045,32 @@ long cxl_pci_get_latency(struct pci_dev *pdev)

return cxl_flit_size(pdev) * MEGA / bw;
}

static int __cxl_endpoint_decoder_reset_detected(struct device *dev, void *data)
{
struct cxl_port *port = data;
struct cxl_decoder *cxld;
struct cxl_hdm *cxlhdm;
void __iomem *hdm;
u32 ctrl;

if (!is_endpoint_decoder(dev))
return 0;

cxld = to_cxl_decoder(dev);
if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
return 0;

cxlhdm = dev_get_drvdata(&port->dev);
hdm = cxlhdm->regs.hdm_decoder;
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));

return !FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
}

bool cxl_endpoint_decoder_reset_detected(struct cxl_port *port)
{
return device_for_each_child(&port->dev, port,
__cxl_endpoint_decoder_reset_detected);
}
EXPORT_SYMBOL_NS_GPL(cxl_endpoint_decoder_reset_detected, CXL);
2 changes: 1 addition & 1 deletion drivers/cxl/core/regs.c
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ int cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_type type,
.resource = CXL_RESOURCE_NONE,
};

regloc = pci_find_dvsec_capability(pdev, PCI_DVSEC_VENDOR_ID_CXL,
regloc = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
CXL_DVSEC_REG_LOCATOR);
if (!regloc)
return -ENXIO;
Expand Down
2 changes: 2 additions & 0 deletions drivers/cxl/cxl.h
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,8 @@ void cxl_coordinates_combine(struct access_coordinate *out,
struct access_coordinate *c1,
struct access_coordinate *c2);

bool cxl_endpoint_decoder_reset_detected(struct cxl_port *port);

/*
* Unit test builds overrides this to __weak, find the 'strong' version
* of these symbols in tools/testing/cxl/.
Expand Down
1 change: 0 additions & 1 deletion drivers/cxl/cxlpci.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
* "DVSEC" redundancies removed. When obvious, abbreviations may be used.
*/
#define PCI_DVSEC_HEADER1_LENGTH_MASK GENMASK(31, 20)
#define PCI_DVSEC_VENDOR_ID_CXL 0x1E98

/* CXL 2.0 8.1.3: PCIe DVSEC for CXL Device */
#define CXL_DVSEC_PCIE_DEVICE 0
Expand Down
24 changes: 23 additions & 1 deletion drivers/cxl/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
cxlds->rcd = is_cxl_restricted(pdev);
cxlds->serial = pci_get_dsn(pdev);
cxlds->cxl_dvsec = pci_find_dvsec_capability(
pdev, PCI_DVSEC_VENDOR_ID_CXL, CXL_DVSEC_PCIE_DEVICE);
pdev, PCI_VENDOR_ID_CXL, CXL_DVSEC_PCIE_DEVICE);
if (!cxlds->cxl_dvsec)
dev_warn(&pdev->dev,
"Device DVSEC not present, skip CXL.mem init\n");
Expand Down Expand Up @@ -957,11 +957,33 @@ static void cxl_error_resume(struct pci_dev *pdev)
dev->driver ? "successful" : "failed");
}

static void cxl_reset_done(struct pci_dev *pdev)
{
struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
struct cxl_memdev *cxlmd = cxlds->cxlmd;
struct device *dev = &pdev->dev;

/*
* FLR does not expect to touch the HDM decoders and related
* registers. SBR, however, will wipe all device configurations.
* Issue a warning if there was an active decoder before the reset
* that no longer exists.
*/
guard(device)(&cxlmd->dev);
if (cxlmd->endpoint &&
cxl_endpoint_decoder_reset_detected(cxlmd->endpoint)) {
dev_crit(dev, "SBR happened without memory regions removal.\n");
dev_crit(dev, "System may be unstable if regions hosted system memory.\n");
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
}
}

static const struct pci_error_handlers cxl_error_handlers = {
.error_detected = cxl_error_detected,
.slot_reset = cxl_slot_reset,
.resume = cxl_error_resume,
.cor_error_detected = cxl_cor_error_detected,
.reset_done = cxl_reset_done,
};

static struct pci_driver cxl_pci_driver = {
Expand Down
4 changes: 4 additions & 0 deletions drivers/pci/access.c
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ void pci_cfg_access_lock(struct pci_dev *dev)
{
might_sleep();

lock_map_acquire(&dev->cfg_access_lock);

raw_spin_lock_irq(&pci_lock);
if (dev->block_cfg_access)
pci_wait_cfg(dev);
Expand Down Expand Up @@ -329,6 +331,8 @@ void pci_cfg_access_unlock(struct pci_dev *dev)
raw_spin_unlock_irqrestore(&pci_lock, flags);

wake_up_all(&pci_cfg_wait);

lock_map_release(&dev->cfg_access_lock);
}
EXPORT_SYMBOL_GPL(pci_cfg_access_unlock);

Expand Down
94 changes: 94 additions & 0 deletions drivers/pci/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -4879,6 +4879,7 @@ void __weak pcibios_reset_secondary_bus(struct pci_dev *dev)
*/
int pci_bridge_secondary_bus_reset(struct pci_dev *dev)
{
lock_map_assert_held(&dev->cfg_access_lock);
pcibios_reset_secondary_bus(dev);

return pci_bridge_wait_for_secondary_bus(dev, "bus reset");
Expand Down Expand Up @@ -4927,16 +4928,96 @@ 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;
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. */
Expand Down Expand Up @@ -5021,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,
Expand Down Expand Up @@ -5245,11 +5327,20 @@ void pci_init_reset_methods(struct pci_dev *dev)
*/
int pci_reset_function(struct pci_dev *dev)
{
struct pci_dev *bridge;
int rc;

if (!pci_reset_supported(dev))
return -ENOTTY;

/*
* If there's no upstream bridge, no locking is needed since there is
* no upstream bridge configuration to hold consistent.
*/
bridge = pci_upstream_bridge(dev);
if (bridge)
pci_dev_lock(bridge);

pci_dev_lock(dev);
pci_dev_save_and_disable(dev);

Expand All @@ -5258,6 +5349,9 @@ int pci_reset_function(struct pci_dev *dev)
pci_dev_restore(dev);
pci_dev_unlock(dev);

if (bridge)
pci_dev_unlock(bridge);

return rc;
}
EXPORT_SYMBOL_GPL(pci_reset_function);
Expand Down
3 changes: 3 additions & 0 deletions drivers/pci/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -2543,6 +2543,9 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
dev->dev.dma_mask = &dev->dma_mask;
dev->dev.dma_parms = &dev->dma_parms;
dev->dev.coherent_dma_mask = 0xffffffffull;
lockdep_register_key(&dev->cfg_access_key);
lockdep_init_map(&dev->cfg_access_lock, dev_name(&dev->dev),
&dev->cfg_access_key, 0);

dma_set_max_seg_size(&dev->dev, 65536);
dma_set_seg_boundary(&dev->dev, 0xffffffff);
Expand Down
2 changes: 1 addition & 1 deletion drivers/perf/cxl_pmu.c
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ static ssize_t cxl_pmu_event_sysfs_show(struct device *dev,

/* For CXL spec defined events */
#define CXL_PMU_EVENT_CXL_ATTR(_name, _gid, _msk) \
CXL_PMU_EVENT_ATTR(_name, PCI_DVSEC_VENDOR_ID_CXL, _gid, _msk)
CXL_PMU_EVENT_ATTR(_name, PCI_VENDOR_ID_CXL, _gid, _msk)

static struct attribute *cxl_pmu_event_attrs[] = {
CXL_PMU_EVENT_CXL_ATTR(clock_ticks, CXL_PMU_GID_CLOCK_TICKS, BIT(0)),
Expand Down
5 changes: 5 additions & 0 deletions include/linux/lockdep.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ extern void lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie);
.wait_type_inner = _wait_type, \
.lock_type = LD_LOCK_WAIT_OVERRIDE, }

#define lock_map_assert_held(l) \
lockdep_assert(lock_is_held(l) != LOCK_STATE_NOT_HELD)

#else /* !CONFIG_LOCKDEP */

static inline void lockdep_init_task(struct task_struct *task)
Expand Down Expand Up @@ -388,6 +391,8 @@ extern int lockdep_is_held(const void *);
#define DEFINE_WAIT_OVERRIDE_MAP(_name, _wait_type) \
struct lockdep_map __maybe_unused _name = {}

#define lock_map_assert_held(l) do { (void)(l); } while (0)

#endif /* !LOCKDEP */

#ifdef CONFIG_PROVE_LOCKING
Expand Down
4 changes: 3 additions & 1 deletion include/linux/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -413,6 +413,8 @@ struct pci_dev {
struct resource driver_exclusive_resource; /* driver exclusive resource ranges */

bool match_driver; /* Skip attaching driver */
struct lock_class_key cfg_access_key;
struct lockdep_map cfg_access_lock;

unsigned int transparent:1; /* Subtractive decode bridge */
unsigned int io_window:1; /* Bridge has I/O window */
Expand Down
2 changes: 2 additions & 0 deletions include/linux/pci_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -2607,6 +2607,8 @@

#define PCI_VENDOR_ID_ALIBABA 0x1ded

#define PCI_VENDOR_ID_CXL 0x1e98

#define PCI_VENDOR_ID_TEHUTI 0x1fc9
#define PCI_DEVICE_ID_TEHUTI_3009 0x3009
#define PCI_DEVICE_ID_TEHUTI_3010 0x3010
Expand Down
5 changes: 5 additions & 0 deletions include/uapi/linux/pci_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

0 comments on commit 83711a1

Please sign in to comment.