From: Li Ming <ming.li@zohomail.com>
To: Dave Jiang <dave.jiang@intel.com>, linux-cxl@vger.kernel.org
Cc: dave@stgolabs.net, jic23@kernel.org, alison.schofield@intel.com,
icheng@nvidia.com
Subject: Re: [PATCH v3 3/3] cxl/port: Attach endpoints to a dport under the lock that pins it
Date: Thu, 1 Oct 2026 09:59:53 +0800 [thread overview]
Message-ID: <5c19ab8b-f369-448d-abcf-a80ff3bbaf9d@zohomail.com> (raw)
In-Reply-To: <20260930152311.4164036-4-dave.jiang@intel.com>
On 9/30/2026 11:23 PM, Dave Jiang wrote:
> 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>
>
> ---
> 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 336d7c0f5d98..b71fe3882212 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)
> {
> @@ -1705,13 +1708,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) {
> @@ -1728,7 +1733,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
> @@ -1736,7 +1741,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));
> @@ -1745,11 +1750,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,
> @@ -1757,7 +1776,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)) {
> @@ -1786,33 +1805,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
> @@ -1828,13 +1837,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)
> @@ -1863,7 +1887,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;
> @@ -1890,27 +1913,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;
prev parent reply other threads:[~2026-10-01 2:00 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 15:23 [PATCH v3 0/3] cxl: dport fixes from sashiko reports Dave Jiang
2026-09-30 15:23 ` [PATCH v3 1/3] cxl/port: Clear cached dport pointers when a dport is removed Dave Jiang
2026-09-30 22:56 ` Alison Schofield
2026-09-30 15:23 ` [PATCH v3 2/3] cxl/core: Hold the dport host lock across dport lookup and use Dave Jiang
2026-09-30 15:40 ` sashiko-bot
2026-09-30 22:59 ` Alison Schofield
2026-10-01 1:47 ` Li Ming
2026-09-30 15:23 ` [PATCH v3 3/3] cxl/port: Attach endpoints to a dport under the lock that pins it Dave Jiang
2026-09-30 23:00 ` Alison Schofield
2026-10-01 1:59 ` Li Ming [this message]
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=5c19ab8b-f369-448d-abcf-a80ff3bbaf9d@zohomail.com \
--to=ming.li@zohomail.com \
--cc=alison.schofield@intel.com \
--cc=dave.jiang@intel.com \
--cc=dave@stgolabs.net \
--cc=icheng@nvidia.com \
--cc=jic23@kernel.org \
--cc=linux-cxl@vger.kernel.org \
/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