From: Dave Jiang <dave.jiang@intel.com>
To: linux-cxl@vger.kernel.org
Cc: dave@stgolabs.net, jic23@kernel.org, alison.schofield@intel.com,
ming.li@zohomail.com, icheng@nvidia.com
Subject: [PATCH v4 3/3] cxl/port: Attach endpoints to a dport under the lock that pins it
Date: Thu, 1 Oct 2026 08:47:44 -0700 [thread overview]
Message-ID: <20261001154744.1095902-4-dave.jiang@intel.com> (raw)
In-Reply-To: <20261001154744.1095902-1-dave.jiang@intel.com>
devm_cxl_enumerate_ports() and add_port_attach_ep() look up or create a
dport under its port's device lock, drop the lock, then pass the dport
to cxl_add_ep(). add_ep() reads dport->port before taking the lock, and
cxl_gpf_port_setup() reads and writes the dport with no lock held.
A concurrent cxl_detach_ep() for another memdev below the same port can
free the dport in that window: it calls del_dports() under the port lock
when the last endpoint leaves. The enumerating memdev then dereferences
freed memory.
Attach the endpoint and set up GPF while the port lock from the lookup
is still held. Make add_ep() assert that lock instead of taking it.
Under the lock, a dport found in port->dports is still live, since
removal erases it from the xarray before freeing it.
Fixes: de516b40116e ("cxl/port: Record dport in endpoint references")
Reported-by: Li Ming <ming.li@zohomail.com>
Closes: https://lore.kernel.org/linux-cxl/0c68324c-cfc7-46b2-9344-24ae40e88923@zohomail.com/
Assisted-by: LLM
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
Reviewed-by: Alison Schofield <alison.schofield@intel.com>
---
Found by review, not from a failure report.
Tested in QEMU with cxl_test, PROVE_LOCKING and KASAN on: 6 module
load/unload cycles, 12 concurrent target_list readers, and each cycle 20
rounds of every memdev unbinding and rebinding in parallel. No KASAN
report, no WARNING, no lockdep splat; debug_locks stays 1. Both converted
paths run under this load, so the new device_lock_assert() in add_ep()
never fired.
Enumeration is unchanged against the same test at patch 2: 11 memdev,
8 port, 11 endpoint, 158 decoder after every load.
The races produce transient -EBUSY cxl_mem probe failures (424 with this
patch, 407 without) from the pending detach_work check in
cxl_mem_probe(). Memdevs left unbound rebind on the first retry.
---
drivers/cxl/core/port.c | 115 ++++++++++++++++++++++------------------
1 file changed, 62 insertions(+), 53 deletions(-)
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 4cc29433216d..25e582718c06 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -1349,7 +1349,7 @@ static int add_ep(struct cxl_ep *new)
{
struct cxl_port *port = new->dport->port;
- guard(device)(&port->dev);
+ device_lock_assert(&port->dev);
if (port->dead)
return -ENXIO;
@@ -1365,6 +1365,9 @@ static int add_ep(struct cxl_ep *new)
* Intermediate CXL ports are scanned based on the arrival of endpoints.
* When those endpoints depart the port can be destroyed once all
* endpoints that care about that port have been removed.
+ *
+ * Context: Caller must hold the device lock of @dport->port, which pins
+ * @dport, across the lookup of @dport and this call.
*/
static int cxl_add_ep(struct cxl_dport *dport, struct device *ep_dev)
{
@@ -1695,13 +1698,15 @@ static struct cxl_dport *probe_dport(struct cxl_port *port,
return drv->add_dport(port, dport_dev);
}
-static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
- struct cxl_port *parent_port,
- struct cxl_dport *parent_dport,
- struct device *uport_dev,
- struct device *dport_dev)
+static int devm_cxl_create_port(struct device *ep_dev,
+ struct cxl_port *parent_port,
+ struct cxl_dport *parent_dport,
+ struct device *uport_dev,
+ struct device *dport_dev)
{
resource_size_t component_reg_phys;
+ struct cxl_dport *dport;
+ int rc;
device_lock_assert(&parent_port->dev);
if (!parent_port->dev.driver) {
@@ -1718,7 +1723,7 @@ static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
port = devm_cxl_add_port(&parent_port->dev, uport_dev,
component_reg_phys, parent_dport);
if (IS_ERR(port))
- return ERR_CAST(port);
+ return PTR_ERR(port);
/*
* retry to make sure a port is found. a port device
@@ -1726,7 +1731,7 @@ static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
*/
port = find_cxl_port_by_uport(uport_dev);
if (!port)
- return ERR_PTR(-ENODEV);
+ return -ENODEV;
dev_dbg(ep_dev, "created port %s:%s\n",
dev_name(&port->dev), dev_name(port->uport_dev));
@@ -1735,11 +1740,25 @@ static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
* Port was created before right before this function is
* called. Signal the caller to deal with it.
*/
- return ERR_PTR(-EAGAIN);
+ return -EAGAIN;
}
+ /* @dport is only valid while @port's lock is held */
guard(device)(&port->dev);
- return probe_dport(port, dport_dev);
+ dport = probe_dport(port, dport_dev);
+ if (IS_ERR(dport))
+ return PTR_ERR(dport);
+
+ rc = cxl_add_ep(dport, ep_dev);
+ if (rc == -EBUSY) {
+ /*
+ * "can't" happen, but this error code means
+ * something to the caller, so translate it.
+ */
+ rc = -ENXIO;
+ }
+
+ return rc;
}
static int add_port_attach_ep(struct cxl_memdev *cxlmd,
@@ -1747,7 +1766,7 @@ static int add_port_attach_ep(struct cxl_memdev *cxlmd,
struct device *dport_dev)
{
struct device *dparent = grandparent(dport_dev);
- struct cxl_dport *dport, *parent_dport;
+ struct cxl_dport *parent_dport;
int rc;
if (is_cxl_host_bridge(dparent)) {
@@ -1776,33 +1795,23 @@ static int add_port_attach_ep(struct cxl_memdev *cxlmd,
return PTR_ERR(parent_dport);
}
- dport = devm_cxl_create_port(&cxlmd->dev, parent_port,
- parent_dport, uport_dev,
- dport_dev);
- if (IS_ERR(dport)) {
- /* Port or dport already exists, restart iteration */
- if (PTR_ERR(dport) == -EAGAIN || PTR_ERR(dport) == -EBUSY)
- return 0;
- return PTR_ERR(dport);
- }
+ rc = devm_cxl_create_port(&cxlmd->dev, parent_port,
+ parent_dport, uport_dev, dport_dev);
}
- rc = cxl_add_ep(dport, &cxlmd->dev);
- if (rc == -EBUSY) {
- /*
- * "can't" happen, but this error code means
- * something to the caller, so translate it.
- */
- rc = -ENXIO;
- }
+ /* Port or dport already exists, restart iteration */
+ if (rc == -EAGAIN || rc == -EBUSY)
+ return 0;
return rc;
}
-static struct cxl_dport *find_or_add_dport(struct cxl_port *port,
- struct device *dport_dev)
+static int find_or_add_dport_attach_ep(struct cxl_memdev *cxlmd,
+ struct cxl_port *port,
+ struct device *dport_dev)
{
struct cxl_dport *dport;
+ int rc;
/*
* The port is already visible in CXL hierarchy, but it may still
@@ -1818,13 +1827,28 @@ static struct cxl_dport *find_or_add_dport(struct cxl_port *port,
if (!dport) {
dport = probe_dport(port, dport_dev);
if (IS_ERR(dport))
- return dport;
+ return PTR_ERR(dport);
/* New dport added, restart iteration */
- return ERR_PTR(-EAGAIN);
+ return -EAGAIN;
}
- return dport;
+ /* @dport is only valid while @port's lock is held */
+ rc = cxl_add_ep(dport, &cxlmd->dev);
+
+ /*
+ * If the endpoint already exists in the port's list,
+ * that's ok, it was added on a previous pass.
+ * Otherwise, retry in add_port_attach_ep() after taking
+ * the parent_port lock as the current port may be being
+ * reaped.
+ */
+ if (rc && rc != -EBUSY)
+ return rc;
+
+ cxl_gpf_port_setup(dport);
+
+ return 0;
}
int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
@@ -1853,7 +1877,6 @@ int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
for (iter = dev; iter; iter = grandparent(iter)) {
struct device *dport_dev = grandparent(iter);
struct device *uport_dev;
- struct cxl_dport *dport;
if (is_cxl_host_bridge(dport_dev))
return 0;
@@ -1880,27 +1903,13 @@ int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
* RP port enumerated by cxl_acpi without dport will
* have the dport added here.
*/
- dport = find_or_add_dport(port, dport_dev);
- if (IS_ERR(dport)) {
- if (PTR_ERR(dport) == -EAGAIN)
- goto retry;
- return PTR_ERR(dport);
- }
-
- rc = cxl_add_ep(dport, &cxlmd->dev);
-
- /*
- * If the endpoint already exists in the port's list,
- * that's ok, it was added on a previous pass.
- * Otherwise, retry in add_port_attach_ep() after taking
- * the parent_port lock as the current port may be being
- * reaped.
- */
- if (rc && rc != -EBUSY)
+ rc = find_or_add_dport_attach_ep(cxlmd, port,
+ dport_dev);
+ if (rc == -EAGAIN)
+ goto retry;
+ if (rc)
return rc;
- cxl_gpf_port_setup(dport);
-
/* Any more ports to add between this one and the root? */
if (!dev_is_cxl_root_child(&port->dev))
continue;
--
2.54.0
next prev parent reply other threads:[~2026-10-01 15:47 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-01 15:47 [PATCH v4 0/3] cxl: dport fixes from sashiko reports Dave Jiang
2026-10-01 15:47 ` [PATCH v4 1/3] cxl/port: Clear cached dport pointers when a dport is removed Dave Jiang
2026-10-01 15:47 ` [PATCH v4 2/3] cxl/core: Hold the dport host lock across dport lookup and use Dave Jiang
2026-10-01 16:02 ` sashiko-bot
2026-10-01 17:36 ` Dave Jiang
2026-10-01 15:47 ` Dave Jiang [this message]
2026-10-09 20:52 ` [PATCH v4 3/3] cxl/port: Attach endpoints to a dport under the lock that pins it Alison Schofield
2026-10-09 20:42 ` [PATCH v4 0/3] cxl: dport fixes from sashiko reports Alison Schofield
2026-10-09 21:43 ` Dave Jiang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261001154744.1095902-4-dave.jiang@intel.com \
--to=dave.jiang@intel.com \
--cc=alison.schofield@intel.com \
--cc=dave@stgolabs.net \
--cc=icheng@nvidia.com \
--cc=jic23@kernel.org \
--cc=linux-cxl@vger.kernel.org \
--cc=ming.li@zohomail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox