NVDIMM Device and Persistent Memory development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Anisa Su" <anisa.su887@gmail.com>, linux-cxl@vger.kernel.org
Cc: nvdimm@lists.linux.dev
Subject: Re: [PATCH v1 12/14] cxl/region: Read existing extents on region creation
Date: Wed, 04 Nov 2026 23:36:43 +0000	[thread overview]
Message-ID: <sashiko-outbox-198302@kernel.org> (raw)
In-Reply-To: <20261104230445.66625-13-anisa.su@samsung.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] cxl/mbox: per-extent DPA filtering in cxl_read_extent_list() splits tag groups
- [High] cxl/mbox: cxl_release_recovered() releases already-onlined DPA capacity
- [High] cxl/region: race in cxl_region_add_existing_extents() on concurrent detach
- [Medium] cxl/mbox: missing end < start check before range_contains() in extent scan
--

commit 6931a3e5d551f78995bded312bdac1337c547e3c
Author: Ira Weiny <iweiny@kernel.org>

cxl/region: Read existing extents on region creation

This patch reads pre-existing accepted dynamic capacity extents from the
device via CXL_MBOX_OP_GET_DC_EXTENT_LIST when probing a cxl_dax_region. It
filters extents by the endpoint decoder's DPA range, stages and onlines
recovered tag groups without sending an Add response, and gates new extent
add events until the initial scan completes.

> diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c
> index c3236fe9e7c5e..a18b41ff5d232 100644
> --- a/drivers/cxl/core/mbox.c
> +++ b/drivers/cxl/core/mbox.c
[ ... ]
> @@ -1627,6 +1641,10 @@ static int cxl_realize_group(struct cxl_memdev_state *mds, const uuid_t *tag,
>  	mds->dc_chain.group = NULL;
>  	if (tag_group)
>  		discard_tag_group(tag_group);
> +err_release:
> +	/* A fresh group is simply left out of the Add response */
> +	if (existing)
> +		cxl_release_recovered(mds, tag, group);
>  	return rc;
>  }
>  
> @@ -1686,11 +1704,14 @@ static int cxl_validate_group(struct cxl_memdev_state *mds, const uuid_t *tag,
>   * list.
>   */
>  static int cxl_add_pending(struct cxl_memdev_state *mds,
> -			   struct list_head *pending)
> +			   struct list_head *pending, bool existing)
>  {
[ ... ]
> @@ -1727,11 +1748,13 @@ static int cxl_add_pending(struct cxl_memdev_state *mds,
>  		shareable = cxl_group_is_shareable(mds, &group);
>  
>  		if (cxl_validate_group(mds, &tag, &group, shareable)) {
> +			if (existing)
> +				cxl_release_recovered(mds, &tag, &group);
>  			drop_extent_group(&group);
>  			continue;
>  		}
>  
> -		cnt = cxl_realize_group(mds, &tag, &group, shareable);
> +		cnt = cxl_realize_group(mds, &tag, &group, shareable, existing);

[Severity: High]
Could calling cxl_release_recovered() on all validation and realization
failures release DPA capacity that is already online on the host?

During recovery (existing == true), cxl_add_pending() processes groups
sequentially and onlines valid groups into cxlr_dax->dc_extents. If the
device's extent list contains a subsequent group whose tag is already
committed (cxl_tag_already_committed() returning true in
cxl_validate_group()) or whose extent DPA range duplicates or overlaps an
already-onlined dc_extent:

drivers/cxl/core/extent.c:cxl_add_extent() {
	...
	switch (cxlr_dax_classify_extent(cxlr_dax, cxled, &ext_range)) {
	case CXL_EXT_DUPLICATE:
		...
		return -EEXIST;
	case CXL_EXT_OVERLAP:
		return -ENXIO;
	case CXL_EXT_NEW:
		break;
	}
}

both error paths invoke cxl_release_recovered(), which sends
CXL_MBOX_OP_RELEASE_DC for every extent in the failing group. Wouldn't that
release physical capacity on the device that backs an active, already-onlined
dc_extent on the host?

> @@ -2439,6 +2471,180 @@ int cxl_dev_dc_identify(struct cxl_mailbox *mbox,
>  }
>  EXPORT_SYMBOL_NS_GPL(cxl_dev_dc_identify, "CXL");
>  
> +/*
> + * Stage the device's extents inside @dpa_range on @existing.  Returns
> + * -EAGAIN if the list changes while it is being read.
> + */
> +static int cxl_read_extent_list(struct cxl_memdev_state *mds,
> +				const struct range *dpa_range,
> +				struct list_head *existing)
> +{
[ ... ]
> +		for (int i = 0; i < nr_returned ; i++) {
> +			struct cxl_extent *extent = &extents->extent[i];
> +			struct range ext_range;
> +
> +			ext_range.start = le64_to_cpu(extent->start_dpa);
> +			ext_range.end = ext_range.start +
> +					le64_to_cpu(extent->length) - 1;
> +			dev_dbg(dev, "Processing extent %d/%d %pra (%pUb) seq %u\n",
> +				current_index + i, total_expected, &ext_range,
> +				extent->uuid,
> +				le16_to_cpu(extent->shared_extn_seq));
> +			/* Another region's, or an unmapped partition's: not ours */
> +			if (!range_contains(dpa_range, &ext_range))
> +				continue;
> +			rc = stage_pending_extent(existing, extent);

[Severity: Medium]
What happens in cxl_read_extent_list() if the device returns a malformed
extent where extent->length is 0 or start_dpa + length - 1 wraps around u64
such that ext_range.end < ext_range.start?

Because range_contains(r1, r2) only checks r1->start <= r2->start &&
r1->end >= r2->end, cxl_extent_dc_partition() explicitly guards against
ext_range->end < ext_range->start before calling range_contains(). Without a
similar check here, an extent with ext_range.start >= dpa_range->start (even
in a higher DPA partition) and a wrapped ext_range.end <= dpa_range->end
passes range_contains(dpa_range, &ext_range) and is staged onto existing.

If that malformed extent carries a UUID matching a valid tag group on this
region, extract_tag_group() will merge it into that group in
cxl_add_pending(), causing cxl_validate_group() to fail and
cxl_release_recovered() to release the entire valid group via
CXL_MBOX_OP_RELEASE_DC. Should cxl_read_extent_list() check
ext_range.end < ext_range.start before calling range_contains()?

[Severity: High]
Also in cxl_read_extent_list(), can filtering individual extents by
dpa_range before assembling tag groups split a multi-extent tag group that
spans or straddles the endpoint decoder's DPA boundary?

When cxl_read_extent_list() skips individual extents with
!range_contains(dpa_range, &ext_range), only the in-range subset of extents
is staged onto existing. When __cxl_process_extent_list() subsequently calls
cxl_add_pending(mds, &existing, true), extract_tag_group() forms each group
from only that pre-filtered subset:

1. For a non-shareable group (or a shareable group whose in-range extents
   happen to carry shared_extn_seq 0..k-1), cxl_validate_group() and
   cxl_realize_group() will succeed and online a truncated tag group,
   committing the tag UUID host-wide so the remaining extents cannot be added
   elsewhere.
2. For a shareable group whose in-range subset is missing earlier sequence
   numbers, cxl_check_group_seq() fails and cxl_release_recovered() sends
   CXL_MBOX_OP_RELEASE_DC for only the in-range subset, violating CXL r4.0
   Section 8.2.10.9.9.4 which requires shared tag groups to be released as a
   whole.

Should all extents be staged first (or grouped by tag first) so that a tag
group only partially contained in dpa_range is detected as a whole?

> diff --git a/drivers/cxl/core/region_dax.c b/drivers/cxl/core/region_dax.c
> index 790176a8aaddc..816386301b80e 100644
> --- a/drivers/cxl/core/region_dax.c
> +++ b/drivers/cxl/core/region_dax.c
[ ... ]
> @@ -79,6 +81,15 @@ static struct cxl_dax_region *cxl_dax_region_alloc(struct cxl_region *cxlr)
>  	cxlr_dax->hpa_range.start = p->res->start;
>  	cxlr_dax->hpa_range.end = p->res->end;
>  
> +	/*
> +	 * DC regions are not interleaved; the probe-time scan uses this decoder
> +	 * and may outlive its attachment to the region, so hold a reference.
> +	 */
> +	if (cxlr->mode == CXL_PARTMODE_DYNAMIC_RAM)
> +		cxlr_dax->cxled = p->targets[0];
> +	if (cxlr_dax->cxled)
> +		get_device(&cxlr_dax->cxled->cxld.dev);
> +
>  	dev = &cxlr_dax->dev;
>  	cxlr_dax->cxlr = cxlr;
>  	cxlr->cxlr_dax = cxlr_dax;
> @@ -153,6 +164,42 @@ DEFINE_FREE(unpublish_cxl_dax_region, struct cxl_dax_region *,
>  		put_device(&_T->dev);
>  	})
>  
> +/*
> + * Runs from cxl_dax_region_probe().  The region's parameters may not be
> + * read here: a concurrent detach clears p->targets[] under cxl_rwsem.region
> + * before it waits for this probe.  Use the decoder recorded at allocation
> + * instead; cxlr_dax holds a reference on it.
> + */
> +int cxl_region_add_existing_extents(struct cxl_dax_region *cxlr_dax)
> +{
> +	struct cxl_endpoint_decoder *cxled = cxlr_dax->cxled;
> +	int rc;
> +
> +	rc = cxl_process_extent_list(cxled);

[Severity: High]
Can a concurrent region detach or reset while cxl_dax_region_probe() runs
cause cxl_region_add_existing_extents() to race with decoder reuse, memdev
teardown, or cache invalidation?

In cxl_decoder_detach(), __cxl_decoder_detach() runs under cxl_rwsem.region
write lock, disables the decoder, and clears cxled->cxld.region = NULL and
p->targets[0] = NULL before dropping cxl_rwsem.region and calling
device_release_driver(&detach->dev) -> cxlr_dax_unregister(), which waits on
device_lock(&cxlr_dax->dev) for cxl_dax_region_probe() to finish. Because
cxl_region_add_existing_extents() passes only cxled to
cxl_process_extent_list() and runs without cxl_rwsem.region held across the
scan, it looks like several races can occur once __cxl_decoder_detach()
drops cxl_rwsem.region:

1. If cxled is reattached to a new region (cxlr2 / cxlr_dax2) while the old
   cxlr_dax1 probe is in cxl_read_extent_list(), cxl_add_pending() ->
   cxl_realize_group() -> cxl_add_extent() -> cxl_validate_extent() resolves
   start_dpa via cxl_dpa_to_region(cxlmd, start_dpa, &cxled) to cxlr_dax2
   without checking that cxlr->cxlr_dax == cxlr_dax1. Because existing is
   true, cxl_validate_extent() bypasses the extents_scanned gate and
   online_tag_group() attaches devres actions to cxlr_dax2->dev before
   cxlr_dax2 has probed, causing really_probe() on cxlr_dax2 to fail with
   -EBUSY ("Resources present before probing") and leak the tag groups.
2. Similarly, cxled->dpa_res can be freed and reallocated to a different DPA
   range via cxl_dpa_free() / cxl_dpa_alloc() before
   __cxl_process_extent_list() (or one of its -EAGAIN retries) reads
   cxled->dpa_res under cxl_rwsem.dpa, causing it to scan and release
   extents in the new DPA range via cxl_release_recovered().
3. Once detach_target() clears cxled->cxld.region = NULL, a concurrent
   memdev/PCI unbind running cxld_unregister() -> cxl_decoder_detach() sees
   cxled->cxld.region == NULL and returns NULL without waiting for
   cxlr_dax1's probe. Holding get_device(&cxled->cxld.dev) only pins the
   decoder device, not cxlmd->cxlds, which cxl_memdev_shutdown() sets to
   NULL before cxl_pci devres frees mds while cxl_process_extent_list() is
   dereferencing cxled_to_mds(cxled).
4. During commit_store() reset (or cxl_decoder_detach()),
   cxlr_dax_unpublish() clears cxlr->cxlr_dax = NULL before waiting for
   cxl_dax_region_probe() (and in commit_store(), before
   cxl_region_decode_reset() invalidates CPU caches). When the in-flight
   scan sees !cxlr->cxlr_dax, cxl_validate_extent() returns -ENXIO and
   cxl_realize_group() calls cxl_release_recovered(), sending
   CXL_MBOX_OP_RELEASE_DC while CPU caches may still hold dirty lines or
   before the region teardown has finished.

Should cxl_region_add_existing_extents() pass cxlr_dax into the scan, verify
under cxl_rwsem.region and cxl_rwsem.dpa that cxled->cxld.region ==
cxlr_dax->cxlr and cxlr->cxlr_dax == cxlr_dax, and avoid calling
cxl_release_recovered() when aborting due to a concurrent detach/unpublish?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261104230445.66625-1-anisa.su@samsung.com?part=12

  reply	other threads:[~2026-11-04 23:36 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 23:02 [PATCH v1 00/14] DCD CXL Series: Dynamic Capacity extent handling Anisa Su
2026-11-04 23:02 ` [PATCH v1 01/14] cxl/core: Return endpoint decoder information from region search Anisa Su
2026-11-04 23:02 ` [PATCH v1 02/14] cxl/port: Add dynamic capacity options to endpoint decoder mode Anisa Su
2026-11-04 23:02 ` [PATCH v1 03/14] cxl/region: Add dynamic DAX region support Anisa Su
2026-11-04 23:21   ` sashiko-bot
2026-11-04 23:02 ` [PATCH v1 04/14] cxl/mem: Introduce DC event handlers Anisa Su
2026-11-04 23:18   ` sashiko-bot
2026-11-04 23:02 ` [PATCH v1 05/14] cxl/mem: Add 20 second timeout for stalled DC event chains Anisa Su
2026-11-04 23:13   ` sashiko-bot
2026-11-04 23:02 ` [PATCH v1 06/14] cxl/extent: Handle DC Add Capacity events Anisa Su
2026-11-04 23:34   ` sashiko-bot
2026-11-04 23:02 ` [PATCH v1 07/14] cxl/mem: Drop misaligned DCD extent groups Anisa Su
2026-11-04 23:02 ` [PATCH v1 08/14] cxl/extent: Validate DC extent partition Anisa Su
2026-11-04 23:02 ` [PATCH v1 09/14] cxl/mem: Enforce tag-group semantics Anisa Su
2026-11-04 23:02 ` [PATCH v1 10/14] cxl/extent: Enforce cross-region tag uniqueness Anisa Su
2026-11-04 23:17   ` sashiko-bot
2026-11-04 23:02 ` [PATCH v1 11/14] cxl/extent: Handle DC Release Capacity events Anisa Su
2026-11-04 23:28   ` sashiko-bot
2026-11-04 23:02 ` [PATCH v1 12/14] cxl/region: Read existing extents on region creation Anisa Su
2026-11-04 23:36   ` sashiko-bot [this message]
2026-11-04 23:02 ` [PATCH v1 13/14] cxl/mem: Trace Dynamic capacity Event Record Anisa Su
2026-11-04 23:02 ` [PATCH v1 14/14] Documentation/cxl: Document DCD extent handling Anisa Su

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=sashiko-outbox-198302@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=anisa.su887@gmail.com \
    --cc=linux-cxl@vger.kernel.org \
    --cc=nvdimm@lists.linux.dev \
    --cc=sashiko-reviews@lists.linux.dev \
    /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