[PATCH v6 7/9] cxl: Move BI setup to endpoint port probe
From: Davidlohr Bueso
Date: Mon Jul 27 2026 - 15:38:13 EST
Move cxl_bi_setup (BI-ID allocation) from cxl_mem_probe() into
cxl_endpoint_port_probe(): the BI-ID and path enablement belong to
the endpoint port's lifetime, not the memdev driver's. The dealloc
devm action moves to the endpoint port accordingly.
Setup is safe in endpoint port probe context: the port probes
synchronously from cxl_mem_probe(), pinning the memdev state the
walk consumes, and the whole ancestor path already exists with BI
registers mapped (dports at dport-add time, the switch USP RT at
first-dport setup) because devm_cxl_enumerate_ports() completes
before the endpoint is created.
Dealloc is safe in endpoint devres context: both setup and dealloc
walk the endpoint's parent_dport topology rather than getting the
port by bus lookup - an ancestor teardown delists the parent port
before the endpoint's devres runs.
The topology walk is stable as parent_dport pointers are fixed at
port creation; ancestors cannot be reaped while holding this
memdev's cxl_ep; and their own teardown frees dports only after
the endpoint is gone.
The device state holds up as well: cxlmd->cxlds is nulled only
after cxl_memdev_unregister() has torn the endpoint down, and
delete_endpoint() clears cxlmd->endpoint only after the endpoint
devres has run.
With dealloc in the endpoint's devres, delete_endpoint() already
holds the parent port's device lock, so to avoid deadlocking, add a
per-port bi_lock, serializing only dports that share state (ie:
sibling nr_bi on a root port, the switch USP's BI RT).
Signed-off-by: Davidlohr Bueso <dave@xxxxxxxxxxxx>
---
drivers/cxl/core/pci.c | 57 +++++++++++++++++++----------------------
drivers/cxl/core/port.c | 1 +
drivers/cxl/cxl.h | 4 ++-
drivers/cxl/mem.c | 4 ---
drivers/cxl/port.c | 4 +++
5 files changed, 35 insertions(+), 35 deletions(-)
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 12cb50640f65..a87c2ad9ac53 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -1092,7 +1092,7 @@ static int __cxl_bi_ctrl_dport(struct cxl_dport *dport, bool enable)
u32 ctrl, value;
int rc;
- guard(device)(&port->dev);
+ guard(mutex)(&port->bi_lock);
if (!bi)
return -EINVAL;
@@ -1211,29 +1211,31 @@ static int cxl_bi_ctrl_endpoint_disable(struct cxl_dev_state *cxlds)
}
/*
- * devm teardown on cxl_mem unbind. Endpoint decoders may still be
- * committed here (cxl_workqueue tears them down asynchronously), but
- * memory access has been quiesced.
+ * devm teardown on endpoint port destruction. Endpoint decoders may
+ * still be committed here (cxl_workqueue tears them down
+ * asynchronously), but memory access has been quiesced.
*/
static void cxl_bi_dealloc(void *data)
{
- struct cxl_dev_state *cxlds = data;
- struct cxl_dport *dport_iter, *dport;
+ struct cxl_port *endpoint = data;
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+ struct cxl_dport *dport_iter;
struct cxl_port *port_iter;
- if (!dev_is_pci(cxlds->dev))
- return;
-
- struct cxl_port *port __free(put_cxl_port) =
- cxl_pci_find_port(to_pci_dev(cxlds->dev), &dport);
- if (!port || !cxlds->bi)
+ if (!cxlds->bi)
return;
scoped_guard(rwsem_read, &cxl_rwsem.region)
cxl_bi_ctrl_endpoint_disable(cxlds);
- port_iter = port;
- dport_iter = dport;
+ /*
+ * Walk the same parent_dport chain that enabled the path. A bus
+ * lookup cannot stand in for it: an ancestor-driven teardown
+ * delists the parent port before this devres action runs.
+ */
+ dport_iter = endpoint->parent_dport;
+ port_iter = dport_iter->port;
while (!is_cxl_root(port_iter)) {
int rc = cxl_bi_ctrl_dport_disable(dport_iter);
@@ -1288,12 +1290,13 @@ static int cxl_bi_enable_path(struct cxl_dev_state *cxlds,
return rc;
}
-int cxl_bi_setup(struct cxl_dev_state *cxlds)
+int cxl_bi_setup(struct cxl_port *endpoint)
{
- struct cxl_port *endpoint = cxlds->cxlmd->endpoint;
- struct cxl_dport *dport_iter, *dport;
+ struct cxl_memdev *cxlmd = to_cxl_memdev(endpoint->uport_dev);
+ struct cxl_dev_state *cxlds = cxlmd->cxlds;
+ struct cxl_dport *dport = endpoint->parent_dport;
+ struct cxl_dport *dport_iter;
struct cxl_port *port_iter;
- struct pci_dev *pdev;
int rc;
if (!dev_is_pci(cxlds->dev))
@@ -1303,18 +1306,12 @@ int cxl_bi_setup(struct cxl_dev_state *cxlds)
if (cxlds->rcd)
return 0;
- pdev = to_pci_dev(cxlds->dev);
- struct cxl_port *port __free(put_cxl_port) =
- cxl_pci_find_port(pdev, &dport);
-
- if (!port)
- return -EINVAL;
-
- if (!cxl_is_bi_capable(pdev, endpoint->regs.bi_decoder))
+ if (!cxl_is_bi_capable(to_pci_dev(cxlds->dev),
+ endpoint->regs.bi_decoder))
return 0;
/* walkup the topology twice, first to check, then to enable */
- port_iter = port;
+ port_iter = dport->port;
dport_iter = dport;
while (!is_cxl_root(port_iter)) {
/* check rp, dsp */
@@ -1338,11 +1335,11 @@ int cxl_bi_setup(struct cxl_dev_state *cxlds)
port_iter = dport_iter->port;
}
- rc = cxl_bi_enable_path(cxlds, port, dport);
+ rc = cxl_bi_enable_path(cxlds, dport->port, dport);
if (rc)
return rc;
- return devm_add_action_or_reset(&cxlds->cxlmd->dev,
- cxl_bi_dealloc, cxlds);
+ return devm_add_action_or_reset(&endpoint->dev, cxl_bi_dealloc,
+ endpoint);
}
EXPORT_SYMBOL_NS_GPL(cxl_bi_setup, "CXL");
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 4fce2188cd8c..57882f4c2d8e 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -772,6 +772,7 @@ static struct cxl_port *cxl_port_alloc(struct device *uport_dev,
xa_init(&port->dports);
xa_init(&port->endpoints);
xa_init(&port->regions);
+ mutex_init(&port->bi_lock);
port->component_reg_phys = CXL_RESOURCE_NONE;
device_initialize(dev);
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 5f4a11538bbf..ca7051641c47 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -594,6 +594,7 @@ struct cxl_dax_region {
* @decoder_ida: allocator for decoder ids
* @reg_map: component and ras register mapping parameters
* @regs: mapped component registers
+ * @bi_lock: serializes BI Decoder/RT state of this port's dports
* @nr_dports: number of entries in @dports
* @hdm_end: track last allocated HDM decoder instance for allocation ordering
* @commit_end: cursor to track highest committed decoder for commit ordering
@@ -616,6 +617,7 @@ struct cxl_port {
struct ida decoder_ida;
struct cxl_register_map reg_map;
struct cxl_component_regs regs;
+ struct mutex bi_lock;
int nr_dports;
int hdm_end;
int commit_end;
@@ -961,7 +963,7 @@ void cxl_coordinates_combine(struct access_coordinate *out,
struct access_coordinate *c2);
bool cxl_endpoint_decoder_reset_detected(struct cxl_port *port);
-int cxl_bi_setup(struct cxl_dev_state *cxlds);
+int cxl_bi_setup(struct cxl_port *endpoint);
struct cxl_dport *devm_cxl_add_dport_by_dev(struct cxl_port *port,
struct device *dport_dev);
diff --git a/drivers/cxl/mem.c b/drivers/cxl/mem.c
index ab28c20d7897..798e5c369cfc 100644
--- a/drivers/cxl/mem.c
+++ b/drivers/cxl/mem.c
@@ -175,10 +175,6 @@ static int cxl_mem_probe(struct device *dev)
if (rc)
dev_dbg(dev, "CXL memdev EDAC registration failed rc=%d\n", rc);
- rc = cxl_bi_setup(cxlds);
- if (rc)
- dev_dbg(dev, "BI setup failed rc=%d\n", rc);
-
/*
* The kernel may be operating out of CXL memory on this device,
* there is no spec defined way to determine whether this device
diff --git a/drivers/cxl/port.c b/drivers/cxl/port.c
index a527dd13fb68..22bd4254ba8d 100644
--- a/drivers/cxl/port.c
+++ b/drivers/cxl/port.c
@@ -180,6 +180,10 @@ static int cxl_endpoint_port_probe(struct cxl_port *port)
if (rc)
return rc;
+ rc = cxl_bi_setup(port);
+ if (rc)
+ dev_dbg(&port->dev, "BI setup failed rc=%d\n", rc);
+
/*
* With VH (CXL Virtual Host) topology the cxl_port::add_dport() method
* handles RAS setup for downstream ports. With RCH (CXL Restricted CXL
--
2.39.5