Linux CXL
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Srirangan Madhavan" <smadhavan@nvidia.com>,
	linux-cxl@vger.kernel.org, linux-pci@vger.kernel.org
Subject: Re: [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset
Date: Thu, 05 Nov 2026 19:44:27 +0000	[thread overview]
Message-ID: <sashiko-outbox-199975@kernel.org> (raw)
In-Reply-To: <20261105185917.2667656-14-smadhavan@nvidia.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] cxl: missed or cleared CXL_DECODER_F_NORMALIZED_ADDRESSING in HDM cache
- [High] PCI/CXL: cxl_reset_function() returns -ENOTTY on !probe safety check failures
- [High] cxl: stale global_ctrl cache and unserialized check in cxl_reset_function()
- [Low] PCI/CXL: split declaration and assignment of __free() in cxl_reset_quiesced()
--

commit 5c4d959ed1ab8877ba7835557e27e40030bf5a45
Author: Srirangan Madhavan <smadhavan@nvidia.com>

cxl: Validate and synchronize HDM ranges around reset

This commit validates and reserves enabled system-physical HDM ranges
around CXL reset, invalidating CPU caches before reset and again before
ending IOMMU exclusion while holding the global cxl_rwsem.region write
lock. It also rejects normalized-addressing and DVSEC-decoded memory
configurations and records Normalized Addressing in the HDM cache during
region discovery.

> diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
> index 9def4a7439c93..51df56377f7a9 100644
> --- a/drivers/cxl/core/hdm_state.c
> +++ b/drivers/cxl/core/hdm_state.c
[ ... ]
> +/**
> + * cxl_hdm_cache_dvsec_decode() - check for memory decoded by DVSEC ranges
> + * @slot: cache pointer owned by the device
> + *
> + * Return: true if the published state shows CXL.mem enabled while HDM
> + * decoders are globally disabled, so that the device decodes memory through
> + * its DVSEC ranges, which the cache does not describe.
> + */
> +bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot)
> +{
> +	struct cxl_hdm_info *info;
> +
> +	guard(rwsem_read)(&cxl_rwsem.dpa);
> +	info = *slot;
> +	return info && (info->dvsec_ctrl & PCI_DVSEC_CXL_MEM_ENABLE) &&
> +	       !(info->global_ctrl & CXL_HDM_DECODER_ENABLE);
> +}

[Severity: High]
As reported by Sashiko on v16 [1], can info->global_ctrl become out of sync
with hardware across devm_cxl_enable_hdm() and disable_hdm(), and can
checking cxl_hdm_cache_dvsec_decode() outside cxl_region_quiesce_lock()
race with concurrent endpoint probe or unbind?

In drivers/cxl/core/pci.c, neither devm_cxl_enable_hdm() nor its devres
teardown callback disable_hdm() updates pdev->hdm->global_ctrl:

drivers/cxl/core/pci.c:disable_hdm() {
	...
	global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
	writel(global_ctrl & ~CXL_HDM_DECODER_ENABLE,
	       hdm + CXL_HDM_DECODER_CTRL_OFFSET);
}

During cxl_endpoint_port_probe(), when firmware left
PCI_DVSEC_CXL_MEM_ENABLE clear at boot, cxl_hdm_decode_init() calls
devm_cxl_enable_hdm() (setting CXL_HDM_DECODER_ENABLE in MMIO without
updating pdev->hdm->global_ctrl) and then devm_cxl_enable_mem() (setting
PCI_DVSEC_CXL_MEM_ENABLE in pdev->hdm->dvsec_ctrl). Next,
devm_cxl_enumerate_decoders() calls cxl_settle_decoders(), which sleeps
for 20 ms before cxl_hdm_refresh_decoder() refreshes
pdev->hdm->global_ctrl. Throughout that 20 ms window, pdev->hdm has
PCI_DVSEC_CXL_MEM_ENABLE set in dvsec_ctrl and CXL_HDM_DECODER_ENABLE
clear in global_ctrl, so cxl_hdm_cache_dvsec_decode() falsely reports
DVSEC range decode mode.

Conversely, when the endpoint port or CXL driver unbinds, devres runs
clear_mem_enable() followed by disable_hdm(), which clears
CXL_HDM_DECODER_ENABLE in CXL_HDM_DECODER_CTRL_OFFSET without updating
pdev->hdm->global_ctrl. When a subsequent reset restores state from
pdev->hdm (in the later commits "PCI/CXL: Restore CXL state after PCI
reset" and "PCI/CXL: Flush host and device caches for CXL bus reset"),
cxl_hdm_restore_state() will write the stale snapshot->global_ctrl with
CXL_HDM_DECODER_ENABLE set back into hardware.

Additionally, cxl_reset_function() evaluates
cxl_hdm_cache_dvsec_decode(&pdev->hdm) in a standalone cxl_rwsem.dpa read
section before cxl_reset_execute() acquires cxl_region_quiesce_lock() and
calls cxl_hdm_cache_snapshot(&pdev->hdm) in cxl_reset_quiesced(), while
cxl_hdm_quiesce_prepare() never checks the captured snapshot for DVSEC
decode. Because endpoint port probe and unbind run under
device_lock(&port->dev) rather than device_lock(&pdev->dev) and modify
PCI_DVSEC_CXL_CTRL, CXL_HDM_DECODER_CTRL_OFFSET, and pdev->hdm without
holding cxl_rwsem.region, can the cached state change between
cxl_hdm_cache_dvsec_decode() and cxl_hdm_cache_snapshot()?

> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index 8cfc06df31340..b06dd9b901f0c 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
> @@ -3840,6 +3841,19 @@ int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
>  	if (IS_ERR(cxlrd))
>  		return PTR_ERR(cxlrd);
>  
> +	/*
> +	 * Translation setup may have marked the decoder as Normalized
> +	 * Addressing, after the PCI cache was filled. Record it for reset.
> +	 */
> +	if (cxled->cxld.config.flags & CXL_DECODER_F_NORMALIZED_ADDRESSING) {
> +		struct pci_dev *pdev __free(pci_dev_put) =
> +			cxl_port_get_uport_pci_dev(cxled_to_port(cxled));
> +
> +		if (pdev)
> +			cxl_hdm_cache_set_normalized(pdev,
> +						     cxled->cxld.config.id);
> +	}

[Severity: High]
As reported by Sashiko on v16 [1], can CXL_DECODER_F_NORMALIZED_ADDRESSING
still be missed or cleared in pdev->hdm, causing cxl_hdm_ranges_collect()
to treat a device-local normalized address range as a system physical
address range during reset?

There appear to be two paths where the cached decoder loses this flag:

1. In cxl_add_to_region(), get_cxl_root_decoder(cxled, &ctx) runs before
the check above. Inside get_cxl_root_decoder():

drivers/cxl/core/region.c:get_cxl_root_decoder() {
	...
	rc = cxl_root_setup_translation(cxl_root, ctx);
	if (rc) {
		...
		return ERR_PTR(rc);
	}

	cxlrd_dev = device_find_child(&cxl_root->port.dev, &ctx->hpa_range,
				      match_root_decoder);
	if (!cxlrd_dev) {
		...
		return ERR_PTR(-ENXIO);
	}
}

cxl_root_setup_translation() -> cxl_prm_setup_root() sets
CXL_DECODER_F_NORMALIZED_ADDRESSING on cxled->cxld.config.flags before
device_find_child(..., match_root_decoder) runs. If no root decoder window
matches (!cxlrd_dev), get_cxl_root_decoder() returns ERR_PTR(-ENXIO).
(Similarly, if cxl_prm_setup_root() detects Normalized Addressing via
hpa_range.start == cxled->dpa_res->start but fails translation before
setting the flag, get_cxl_root_decoder() returns ERR_PTR(-ENXIO).) Because
discover_region() treats errors from cxl_add_to_region() as non-fatal and
cxl_add_to_region() returns early on IS_ERR(cxlrd),
cxl_hdm_cache_set_normalized() is never called.

2. In devm_cxl_enumerate_decoders() -> init_hdm_decoder() ->
cxl_hdm_refresh_decoder() -> cxl_hdm_cache_refresh_decoder():

drivers/cxl/core/hdm_state.c:cxl_hdm_cache_refresh_decoder() {
	...
	*settings = (struct cxl_endpoint_decoder_settings) {
		.config.id = id,
	};
	if (cxld->config.flags & CXL_DECODER_F_ENABLE) {
		settings->config = cxld->config;
		settings->skip = cxled->skip;
	}
}

Because hardware registers do not have a Normalized Addressing bit,
overwriting settings->config from cxld->config clears any previously
recorded CXL_DECODER_F_NORMALIZED_ADDRESSING bit when the endpoint port
driver is rebound or if enumeration fails on a later decoder before
discover_region() runs.

If a CXL reset then runs, will cxl_hdm_ranges_collect() fail to reject the
decoder with -EOPNOTSUPP and pass the untranslated normalized DPA range to
request_mem_region() and cpu_cache_invalidate_memregion()?

> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> index 9ac5948f595b1..af8ec8db20f12 100644
> --- a/drivers/pci/cxl.c
> +++ b/drivers/pci/cxl.c
[ ... ]
> -static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
> +static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
> +	__must_hold(cxl_region_quiesce)
>  {
> -	u16 saved_ctrl2;
> +	struct cxl_hdm_info *snapshot __free(kfree) = NULL;
> +	struct cxl_pci_cache cache = {};
>  	int rc, rc2;
>  
> -	rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &saved_ctrl2);
> +	rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2,
> +				  &cache.saved_ctrl2);
>  	if (rc)
>  		return pcibios_err_to_errno(rc);
> -	if (PCI_POSSIBLE_ERROR(saved_ctrl2))
> +	if (PCI_POSSIBLE_ERROR(cache.saved_ctrl2))
>  		return -ENODEV;
>  
> -	rc = cxl_reset_disable_cache(pdev, dvsec, cap);
> +	snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);

[Severity: Low]
This isn't a bug, but per the Cleanup API conventions, should snapshot be
defined and initialized in a single statement at its point of assignment:

	struct cxl_hdm_info *snapshot __free(kfree) =
		cxl_hdm_cache_snapshot(&pdev->hdm);

rather than being declared with = NULL at the top of cxl_reset_quiesced()?

[ ... ]
> @@ -463,5 +522,17 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
>  	if (probe)
>  		return 0;
>  
> +	/* The cache is owned by @pdev and does not require a bound CXL driver. */
> +	if (!pdev->hdm)
> +		return -ENOTTY;
> +
> +	/* Memory decoded through DVSEC ranges has no HPA ranges to quiesce. */
> +	if (cxl_hdm_cache_dvsec_decode(&pdev->hdm))
> +		return -ENOTTY;
> +
> +	if (!cpu_cache_has_invalidate_memregion() &&
> +	    !IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST))
> +		return -ENOTTY;
> +
>  	return cxl_reset_execute(pdev, dvsec, cap);
>  }

[Severity: High]
When probe is false and CXL.cache and CXL.mem are enabled on the device,
does returning -ENOTTY from these precondition checks cause
__pci_reset_function_locked() to fall back to other PCI reset methods
(such as FLR or PM reset) instead of refusing the reset?

During PCI enumeration, pci_init_reset_methods() calls cxl_reset_function()
with probe == true, which returns 0 before reaching these checks and
records cxl_reset in dev->reset_methods[] ahead of flr and pm (when wired
up by commit "PCI/CXL: Expose CXL Reset as a PCI reset method"). Later,
when __pci_reset_function_locked() runs with PCI_RESET_DO_RESET:

drivers/pci/pci.c:__pci_reset_function_locked() {
	...
	rc = method->reset_fn(dev, PCI_RESET_DO_RESET);
	if (!rc)
		return 0;

	pci_dbg(dev, "%s failed with %d\n", method->name, rc);
	if (rc != -ENOTTY)
		return rc;
}

Because -ENOTTY tells __pci_reset_function_locked() to try the next reset
method in dev->reset_methods[], failing any of these checks will proceed to
reset the active CXL.cache / CXL.mem function via FLR or PM reset without
reserving HPA ranges, flushing CPU or device caches, or restoring CXL
state.

Returning -ENOTTY here also makes the locked -ENXIO checks and pci_err()
diagnostics in cxl_hdm_cache_snapshot(), cxl_hdm_quiesce_prepare(), and
cxl_reset_quiesced() unreachable from cxl_reset_function(), while checking
!pdev->hdm without holding cxl_rwsem.dpa. Should these checks run under
cxl_region_quiesce_lock() and return non-ENOTTY errors (such as -ENXIO and
-EOPNOTSUPP, matching cxl_hdm_quiesce_prepare() and pci_cxl_reset_prepare()
in commit "PCI/CXL: Flush host and device caches for CXL bus reset") so the
reset is aborted?

[1] https://lore.kernel.org/r/20261029145703.2185231-14-smadhavan@nvidia.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=13

  reply	other threads:[~2026-11-05 19:44 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-11-05 18:58 ` [PATCH v17 01/19] cxl: Clarify decoder interleave limit Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
2026-11-05 19:12   ` sashiko-bot
2026-11-10 13:36   ` Li Ming
2026-11-05 18:59 ` [PATCH v17 03/19] cxl: Move decoder declarations to shared header Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 04/19] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 05/19] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 06/19] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 07/19] cxl: Share HDM decoder register unpacking Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 08/19] cxl: Move cxl_rwsem into built-in code Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
2026-11-05 19:17   ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 10/19] cxl: Build register block helpers into the kernel Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
2026-11-05 19:30   ` sashiko-bot
2026-11-10 14:12   ` Li Ming
2026-11-11  3:54     ` Li Ming
2026-11-05 18:59 ` [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
2026-11-10 14:29   ` Li Ming
2026-11-11 10:12   ` Lucero Palau, Alejandro
2026-11-05 18:59 ` [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
2026-11-05 19:44   ` sashiko-bot [this message]
2026-11-05 18:59 ` [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
2026-11-05 19:33   ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
2026-11-05 19:34   ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 16/19] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
2026-11-05 19:43   ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 18/19] PCI/CXL: Flush host and device caches for " Srirangan Madhavan
2026-11-05 19:56   ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 19/19] cxl: Add KUnit checks for HDM decoder unpacking Srirangan Madhavan
2026-11-05 19:31 ` [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Alison Schofield

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-199975@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-cxl@vger.kernel.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=smadhavan@nvidia.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