* [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices
@ 2026-07-03 22:04 Srirangan Madhavan
2026-07-03 22:04 ` [PATCH v8 01/10] cxl: Split decoder programming into a reusable helper Srirangan Madhavan
` (12 more replies)
0 siblings, 13 replies; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:04 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Hi folks!
This series adds CXL Reset support for CXL Type 2 devices through the
existing PCI reset_method ABI. The reset sequence follows the CXL 4.0
specification [1], including CXL.cache disable, optional cache
writeback, CXL Reset initiation, ResetComplete polling, and ResetError
reporting.
The userspace ABI is the existing PCI reset interface:
/sys/bus/pci/devices/.../reset_method
/sys/bus/pci/devices/.../reset
Userspace can select "cxl_reset" in reset_method and then trigger reset
through the existing reset attribute.
Following Dan's v6 feedback, this replaces the proposed memdev sysfs ABI
with the existing PCI reset_method interface.
v8 changes from v7 [2]:
- Drop the PCI helper export patch.
- Drop the multi-function sibling coordination patch. cxl_reset is only
exposed as a function-scoped reset method when the CXL reset scope is
limited to the target function.
- Keep the reset-scope discovery needed to reject non-function-scoped
CXL resets.
- Cache HDM location as BAR-relative metadata instead of keeping an
enum-time MMIO mapping.
- Restore HDM through a temporary mapping based on the current BAR
assignment after PCI config state is restored.
- Cache raw HDM decoder register state so uncommitted decoders can be
restored as uncommitted, while committed decoders are recommitted
through the normal HDM commit flow.
- Share HDM decoder decode and validation between normal CXL core
enumeration and early PCI HDM caching.
- Put cxl_reset ahead of FLR in reset_method priority because FLR does
not reset CXL.cache or CXL.mem protocol state.
Motivation:
-----------
- Type 2 devices need a CXL-specific reset mechanism beyond existing PCI
reset methods.
- FLR does not reset CXL.cache or CXL.mem protocol state. CXL Reset is
the architectural reset mechanism for those protocols.
- The PCI reset_method ABI lets userspace select this narrower CXL reset
before falling back to broader bus reset methods.
Change Description:
-------------------
Patch 1: cxl/hdm: Split decoder programming into a reusable helper
- Move shared decoder settings to include/cxl/cxl.h.
- Factor low-level HDM register programming into cxl_commit().
Patch 2: cxl/hdm: Cache decoder settings on PCI devices
- Cache CXL core HDM decoder settings in pci_dev->hdm.
- Refresh the cache as decoders are enumerated, committed, or reset.
Patch 3: cxl/hdm: Share HDM decoder decode logic
- Share HDM decoder decode and validation between normal CXL core
enumeration and early PCI HDM cache setup.
Patch 4: cxl/hdm: Cache endpoint decoder settings during PCI enumeration
- Snapshot endpoint HDM state during PCI capability initialization.
- Cache the HDM register locator as BAR-relative metadata.
- Cache raw decoder register state in addition to committed decoder
settings.
Patch 5: PCI/CXL: Add CXL Device Reset helper
- Add the internal DVSEC reset sequence.
- Disable CXL.cache, perform cache writeback where supported, initiate
CXL Reset, and wait for completion.
Patch 6: PCI/CXL: Validate HDM ranges before CXL reset
- Collect enabled cached HDM ranges.
- Reject reset if affected ranges are busy.
- Invalidate CPU caches when runtime cache-invalidation support is
available, otherwise continue after warning.
Patch 7: PCI/CXL: Discover the CXL reset scope
- Discover whether CXL Reset is function-scoped using the Non-CXL
Function Map and CXL cache/mem capability bits.
Patch 8: cxl/pci: Restore CXL HDM state after PCI reset
- Restore cached global and per-decoder HDM state after reset.
- Re-map HDM registers from the current BAR assignment during restore.
- Replay raw decoder state for uncommitted decoders and recommit
decoders that were committed before reset.
Patch 9: PCI/CXL: Expose CXL Reset as a PCI reset method
- Add "cxl_reset" to the PCI reset_method table for Type 2 reset-capable
CXL devices.
- Prioritize cxl_reset ahead of FLR.
Patch 10: Documentation/ABI: Document CXL Reset PCI reset method
- Document the new reset_method value and reset behavior.
The CPU cache invalidation step depends on
cpu_cache_invalidate_memregion() support for the affected address ranges.
If no runtime provider is available, the kernel emits a warning and
continues after the affected HDM ranges have been reserved.
Example:
echo cxl_reset > /sys/bus/pci/devices/0000:bb:dd.f/reset_method
echo 1 > /sys/bus/pci/devices/0000:bb:dd.f/reset
Testing:
- Ran 100 iterations of cxl_reset through the PCI reset sysfs ABI on a
CXL Type 2 device. All iterations completed successfully and
ResetComplete was observed.
- Exercised cxl_bus reset separately with an add-on HDM restore patch.
References:
[1] https://computeexpresslink.org/wp-content/uploads/2026/02/CXL-Specification_rev4p0_ver1p0_2026February26_clean_evalcopy_v2.pdf
[2] https://lore.kernel.org/linux-cxl/20260623032453.3404772-1-smadhavan@nvidia.com/
[3] https://lore.kernel.org/linux-cxl/20260306080026.116789-1-smadhavan@nvidia.com/
Srirangan Madhavan (10):
cxl/hdm: Split decoder programming into a reusable helper
cxl/hdm: Cache decoder settings on PCI devices
cxl/hdm: Share HDM decoder decode logic
cxl/hdm: Cache endpoint decoder settings during PCI enumeration
PCI/CXL: Add CXL Device Reset helper
PCI/CXL: Validate HDM ranges before CXL reset
PCI/CXL: Discover the CXL reset scope
cxl/pci: Restore CXL HDM state after PCI reset
PCI/CXL: Expose CXL Reset as a PCI reset method
Documentation/ABI: Document CXL Reset PCI reset method
Documentation/ABI/testing/sysfs-bus-pci | 14 +
drivers/cxl/Kconfig | 4 +
drivers/cxl/core/Makefile | 2 +-
drivers/cxl/core/hdm.c | 257 ++---
drivers/cxl/core/region.c | 6 +-
drivers/cxl/core/regs.c | 4 +
drivers/cxl/core/reset.c | 1354 +++++++++++++++++++++++
drivers/cxl/cxl.h | 43 -
drivers/pci/pci.c | 2 +
drivers/pci/probe.c | 3 +
include/cxl/cxl.h | 102 +-
include/linux/pci.h | 8 +-
include/uapi/linux/pci_regs.h | 15 +
tools/testing/cxl/test/cxl.c | 10 +-
14 files changed, 1615 insertions(+), 209 deletions(-)
create mode 100644 drivers/cxl/core/reset.c
base-commit: 90cf2e0d702c8a132ccbe72e7687f33c04c14658
--
2.43.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v8 01/10] cxl: Split decoder programming into a reusable helper
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
@ 2026-07-03 22:04 ` Srirangan Madhavan
2026-07-07 0:22 ` Dave Jiang
2026-07-03 22:05 ` [PATCH v8 02/10] cxl: Cache decoder settings on PCI devices Srirangan Madhavan
` (11 subsequent siblings)
12 siblings, 1 reply; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:04 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Move common HDM decoder settings to include/cxl/cxl.h and route the
register programming sequence through cxl_commit(). This lets reset code
restore cached HDM state without depending on private cxl_core types while
keeping hdm.c in charge of the existing commit policy checks.
Build the low-level HDM helper under CONFIG_CXL_HDM so it is available even
when cxl_core is modular.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/Kconfig | 4 ++
drivers/cxl/core/Makefile | 1 +
drivers/cxl/core/hdm.c | 122 ++++-------------------------------
drivers/cxl/core/region.c | 6 +-
drivers/cxl/core/reset.c | 118 +++++++++++++++++++++++++++++++++
drivers/cxl/cxl.h | 43 ------------
include/cxl/cxl.h | 55 +++++++++++++++-
tools/testing/cxl/test/cxl.c | 10 +--
8 files changed, 197 insertions(+), 162 deletions(-)
create mode 100644 drivers/cxl/core/reset.c
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 80aeb0d556bd..87d719ea1e14 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -6,6 +6,7 @@ menuconfig CXL_BUS
select FW_UPLOAD
select PCI_DOE
select FIRMWARE_TABLE
+ select CXL_HDM
select NUMA_KEEP_MEMINFO if NUMA_MEMBLKS
select FWCTL if CXL_FEATURES
help
@@ -243,4 +244,7 @@ config CXL_ATL
depends on CXL_REGION
depends on ACPI_PRMT && AMD_NB
+config CXL_HDM
+ bool
+
endif
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index ce7213818d3c..dc075cee0450 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
+obj-$(CONFIG_CXL_HDM) += reset.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 0c80b76a5f9b..fa978c297546 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -16,11 +16,6 @@
* for enumerating these registers and capabilities.
*/
-struct cxl_rwsem cxl_rwsem = {
- .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
- .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
-};
-
static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld)
{
int rc;
@@ -255,11 +250,11 @@ static void __cxl_dpa_release(struct cxl_endpoint_decoder *cxled)
lockdep_assert_held_write(&cxl_rwsem.dpa);
/* save @skip_start, before @res is released */
- skip_start = res->start - cxled->skip;
+ skip_start = res->start - cxled->cxld.skip;
__release_region(&cxlds->dpa_res, res->start, resource_size(res));
- if (cxled->skip)
- release_skip(cxlds, skip_start, cxled->skip);
- cxled->skip = 0;
+ if (cxled->cxld.skip)
+ release_skip(cxlds, skip_start, cxled->cxld.skip);
+ cxled->cxld.skip = 0;
cxled->dpa_res = NULL;
put_device(&cxled->cxld.dev);
port->hdm_end--;
@@ -388,7 +383,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
return -EBUSY;
}
cxled->dpa_res = res;
- cxled->skip = skipped;
+ cxled->cxld.skip = skipped;
/*
* When allocating new capacity, ->part is already set, when
@@ -679,35 +674,6 @@ int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
}
-static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
-{
- u16 eig;
- u8 eiw;
-
- /*
- * Input validation ensures these warns never fire, but otherwise
- * suppress unititalized variable usage warnings.
- */
- if (WARN_ONCE(ways_to_eiw(cxld->interleave_ways, &eiw),
- "invalid interleave_ways: %d\n", cxld->interleave_ways))
- return;
- if (WARN_ONCE(granularity_to_eig(cxld->interleave_granularity, &eig),
- "invalid interleave_granularity: %d\n",
- cxld->interleave_granularity))
- return;
-
- u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
- u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
- *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
-}
-
-static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
-{
- u32p_replace_bits(ctrl,
- !!(cxld->target_type == CXL_DECODER_HOSTONLYMEM),
- CXL_HDM_DECODER0_CTRL_HOSTONLY);
-}
-
static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
{
struct cxl_dport **t = &cxlsd->target[0];
@@ -730,73 +696,6 @@ static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
*tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
}
-/*
- * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set
- * committed or error within 10ms, but just be generous with 20ms to account for
- * clock skew and other marginal behavior
- */
-#define COMMIT_TIMEOUT_MS 20
-static int cxld_await_commit(void __iomem *hdm, int id)
-{
- u32 ctrl;
- int i;
-
- for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
- ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
- return -EIO;
- }
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
- return 0;
- fsleep(1000);
- }
-
- return -ETIMEDOUT;
-}
-
-static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
-{
- int id = cxld->id;
- u64 base, size;
- u32 ctrl;
-
- /* common decoder settings */
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
- cxld_set_interleave(cxld, &ctrl);
- cxld_set_type(cxld, &ctrl);
- base = cxld->hpa_range.start;
- size = range_len(&cxld->hpa_range);
-
- writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
- writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
- writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
- writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
-
- if (is_switch_decoder(&cxld->dev)) {
- struct cxl_switch_decoder *cxlsd =
- to_cxl_switch_decoder(&cxld->dev);
- void __iomem *tl_hi = hdm + CXL_HDM_DECODER0_TL_HIGH(id);
- void __iomem *tl_lo = hdm + CXL_HDM_DECODER0_TL_LOW(id);
- u64 targets;
-
- cxlsd_set_targets(cxlsd, &targets);
- writel(upper_32_bits(targets), tl_hi);
- writel(lower_32_bits(targets), tl_lo);
- } else {
- struct cxl_endpoint_decoder *cxled =
- to_cxl_endpoint_decoder(&cxld->dev);
- void __iomem *sk_hi = hdm + CXL_HDM_DECODER0_SKIP_HIGH(id);
- void __iomem *sk_lo = hdm + CXL_HDM_DECODER0_SKIP_LOW(id);
-
- writel(upper_32_bits(cxled->skip), sk_hi);
- writel(lower_32_bits(cxled->skip), sk_lo);
- }
-
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
-}
-
static int cxl_decoder_commit(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
@@ -834,17 +733,20 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
}
}
- scoped_guard(rwsem_read, &cxl_rwsem.dpa)
- setup_hw_decoder(cxld, hdm);
+ if (is_switch_decoder(&cxld->dev)) {
+ struct cxl_switch_decoder *cxlsd =
+ to_cxl_switch_decoder(&cxld->dev);
+
+ cxlsd_set_targets(cxlsd, &cxld->targets);
+ }
- rc = cxld_await_commit(hdm, cxld->id);
+ rc = cxl_commit(&cxld->settings, hdm);
if (rc) {
dev_dbg(&port->dev, "%s: error %d committing decoder\n",
dev_name(&cxld->dev), rc);
return rc;
}
port->commit_end++;
- cxld->flags |= CXL_DECODER_F_ENABLE;
return 0;
}
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index ce99f0650764..00f79b4862cb 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -2954,9 +2954,9 @@ static int poison_by_decoder(struct device *dev, void *arg)
cxlds = cxlmd->cxlds;
mode = cxlds->part[cxled->part].mode;
- if (cxled->skip) {
- offset = cxled->dpa_res->start - cxled->skip;
- length = cxled->skip;
+ if (cxled->cxld.skip) {
+ offset = cxled->dpa_res->start - cxled->cxld.skip;
+ length = cxled->cxld.skip;
rc = cxl_mem_get_poison(cxlmd, offset, length, NULL);
if (rc == -EFAULT && mode == CXL_PARTMODE_RAM)
rc = 0;
diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
new file mode 100644
index 000000000000..14f024098e82
--- /dev/null
+++ b/drivers/cxl/core/reset.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2026 NVIDIA Corporation. All rights reserved. */
+#include <linux/delay.h>
+#include <linux/bug.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+
+#include "cxl.h"
+#include "core.h"
+
+struct cxl_rwsem cxl_rwsem = {
+ .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
+ .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
+};
+EXPORT_SYMBOL_FOR_MODULES(cxl_rwsem, "cxl_core");
+
+static void cxld_set_interleave(struct cxl_decoder_settings *settings, u32 *ctrl)
+{
+ u16 eig;
+ u8 eiw;
+
+ /*
+ * Input validation ensures these warns never fire, but otherwise
+ * suppress uninitialized variable usage warnings.
+ */
+ if (WARN_ONCE(ways_to_eiw(settings->interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n",
+ settings->interleave_ways))
+ return;
+ if (WARN_ONCE(granularity_to_eig(settings->interleave_granularity, &eig),
+ "invalid interleave_granularity: %d\n",
+ settings->interleave_granularity))
+ return;
+
+ u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
+ u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
+ *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
+}
+
+static void cxld_set_type(struct cxl_decoder_settings *settings, u32 *ctrl)
+{
+ u32p_replace_bits(ctrl,
+ !!(settings->target_type == CXL_DECODER_HOSTONLYMEM),
+ CXL_HDM_DECODER0_CTRL_HOSTONLY);
+}
+
+/*
+ * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set
+ * committed or error within 10ms, but just be generous with 20ms to account for
+ * clock skew and other marginal behavior.
+ */
+#define COMMIT_TIMEOUT_MS 20
+static int cxld_await_commit(void __iomem *hdm, int id)
+{
+ u32 ctrl;
+ int i;
+
+ for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
+ ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ return -EIO;
+ }
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
+ return 0;
+ fsleep(1000);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static void setup_hw_decoder(struct cxl_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ int id = settings->id;
+ u64 target_or_skip;
+ u64 base, size;
+ u32 ctrl;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ cxld_set_interleave(settings, &ctrl);
+ cxld_set_type(settings, &ctrl);
+ base = settings->hpa_range.start;
+ size = range_len(&settings->hpa_range);
+ target_or_skip = settings->targets;
+
+ writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
+ writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
+ writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ /* Target-list and endpoint-skip registers alias the same slot. */
+ writel(upper_32_bits(target_or_skip),
+ hdm + CXL_HDM_DECODER0_TL_HIGH(id));
+ writel(lower_32_bits(target_or_skip),
+ hdm + CXL_HDM_DECODER0_TL_LOW(id));
+
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+
+int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm)
+{
+ int rc;
+
+ scoped_guard(rwsem_read, &cxl_rwsem.dpa) {
+ setup_hw_decoder(settings, hdm);
+ }
+
+ rc = cxld_await_commit(hdm, settings->id);
+ if (rc)
+ return rc;
+
+ settings->flags |= CXL_DECODER_F_ENABLE;
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit, "cxl_core");
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index f43abd1903ce..b433262ab256 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -252,49 +252,8 @@ int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
#define CXL_DECODER_F_NORMALIZED_ADDRESSING BIT(6)
#define CXL_DECODER_F_RESET_MASK (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK)
-enum cxl_decoder_type {
- CXL_DECODER_DEVMEM = 2,
- CXL_DECODER_HOSTONLYMEM = 3,
-};
-
-/*
- * Current specification goes up to 8, double that seems a reasonable
- * software max for the foreseeable future
- */
-#define CXL_DECODER_MAX_INTERLEAVE 16
-
#define CXL_QOS_CLASS_INVALID -1
-/**
- * struct cxl_decoder - Common CXL HDM Decoder Attributes
- * @dev: this decoder's device
- * @id: kernel device name id
- * @hpa_range: Host physical address range mapped by this decoder
- * @interleave_ways: number of cxl_dports in this decode
- * @interleave_granularity: data stride per dport
- * @target_type: accelerator vs expander (type2 vs type3) selector
- * @region: currently assigned region for this decoder
- * @flags: memory type capabilities and locking
- * @target_map: cached copy of hardware port-id list, available at init
- * before all @dport objects have been instantiated. While
- * dport id is 8bit, CFMWS interleave targets are 32bits.
- * @commit: device/decoder-type specific callback to commit settings to hw
- * @reset: device/decoder-type specific callback to reset hw settings
-*/
-struct cxl_decoder {
- struct device dev;
- int id;
- struct range hpa_range;
- int interleave_ways;
- int interleave_granularity;
- enum cxl_decoder_type target_type;
- struct cxl_region *region;
- unsigned long flags;
- u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
- int (*commit)(struct cxl_decoder *cxld);
- void (*reset)(struct cxl_decoder *cxld);
-};
-
/*
* Track whether this decoder is free for userspace provisioning, reserved for
* region autodiscovery, whether it is started connecting (awaiting other
@@ -310,7 +269,6 @@ enum cxl_decoder_state {
* struct cxl_endpoint_decoder - Endpoint / SPA to DPA decoder
* @cxld: base cxl_decoder_object
* @dpa_res: actively claimed DPA span of this decoder
- * @skip: offset into @dpa_res where @cxld.hpa_range maps
* @state: autodiscovery state
* @part: partition index this decoder maps
* @pos: interleave position in @cxld.region
@@ -318,7 +276,6 @@ enum cxl_decoder_state {
struct cxl_endpoint_decoder {
struct cxl_decoder cxld;
struct resource *dpa_res;
- resource_size_t skip;
enum cxl_decoder_state state;
int part;
int pos;
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 016c74fb747c..757d916a6330 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -5,8 +5,10 @@
#ifndef __CXL_CXL_H__
#define __CXL_CXL_H__
+#include <linux/device.h>
#include <linux/node.h>
#include <linux/ioport.h>
+#include <linux/range.h>
#include <cxl/mailbox.h>
/**
@@ -23,7 +25,56 @@ enum cxl_devtype {
CXL_DEVTYPE_CLASSMEM,
};
-struct device;
+struct cxl_region;
+
+enum cxl_decoder_type {
+ CXL_DECODER_DEVMEM = 2,
+ CXL_DECODER_HOSTONLYMEM = 3,
+};
+
+/*
+ * Current specification goes up to 8, double that seems a reasonable
+ * software max for the foreseeable future
+ */
+#define CXL_DECODER_MAX_INTERLEAVE 16
+
+/**
+ * struct cxl_decoder - Common CXL HDM Decoder Attributes
+ * @dev: this decoder's device
+ * @id: kernel device name id
+ * @hpa_range: Host physical address range mapped by this decoder
+ * @skip: offset into @dpa_res where @cxld.hpa_range maps (endpoint)
+ * @targets: interleave position to dport mapping (switch)
+ * @interleave_ways: number of cxl_dports in this decode
+ * @interleave_granularity: data stride per dport
+ * @target_type: accelerator vs expander (type2 vs type3) selector
+ * @flags: memory type capabilities and locking
+ * @region: currently assigned region for this decoder
+ * @target_map: cached copy of hardware port-id list, available at init
+ * before all @dport objects have been instantiated. While
+ * dport id is 8bit, CFMWS interleave targets are 32bits.
+ * @commit: device/decoder-type specific callback to commit settings to hw
+ * @reset: device/decoder-type specific callback to reset hw settings
+ */
+struct cxl_decoder {
+ struct device dev;
+
+ struct_group_tagged(cxl_decoder_settings, settings, int id;
+ struct range hpa_range;
+ union {
+ u64 skip;
+ u64 targets;
+ };
+ int interleave_ways;
+ int interleave_granularity;
+ enum cxl_decoder_type target_type;
+ unsigned long flags;
+ );
+ struct cxl_region *region;
+ u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
+ int (*commit)(struct cxl_decoder *cxld);
+ void (*reset)(struct cxl_decoder *cxld);
+};
/*
* Using struct_group() allows for per register-block-type helper routines,
@@ -70,6 +121,8 @@ struct cxl_regs {
);
};
+int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm);
+
struct cxl_reg_map {
bool valid;
int id;
diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index 296516eecfd6..4db5071aa25a 100644
--- a/tools/testing/cxl/test/cxl.c
+++ b/tools/testing/cxl/test/cxl.c
@@ -840,11 +840,11 @@ static int cxld_registry_restore(struct cxl_decoder *cxld,
dbg_cxld(port, "restore", &td->cxled.cxld);
cxld_copy(cxld, &td->cxled.cxld);
cxled->state = td->cxled.state;
- cxled->skip = td->cxled.skip;
+ cxld->skip = td->cxled.cxld.skip;
if (range_len(&td->dpa_range)) {
rc = devm_cxl_dpa_reserve(cxled, td->dpa_range.start,
range_len(&td->dpa_range),
- td->cxled.skip);
+ td->cxled.cxld.skip);
if (rc) {
init_disabled_mock_decoder(cxld);
return rc;
@@ -882,7 +882,7 @@ static void __cxld_registry_save(struct cxl_test_decoder *td,
cxld_copy(&td->cxled.cxld, cxld);
td->cxled.state = cxled->state;
- td->cxled.skip = cxled->skip;
+ td->cxled.cxld.skip = cxled->cxld.skip;
if (!(cxld->flags & CXL_DECODER_F_ENABLE)) {
td->dpa_range.start = 0;
@@ -970,7 +970,7 @@ static void mock_decoder_reset(struct cxl_decoder *cxld)
to_cxl_endpoint_decoder(&cxld->dev);
cxled->state = CXL_DECODER_STATE_MANUAL;
- cxled->skip = 0;
+ cxled->cxld.skip = 0;
}
if (decoder_reset_preserve_registry)
dev_dbg(port->uport_dev, "decoder%d: skip registry update\n",
@@ -1021,7 +1021,7 @@ static void init_disabled_mock_decoder(struct cxl_decoder *cxld)
to_cxl_endpoint_decoder(&cxld->dev);
cxled->state = CXL_DECODER_STATE_MANUAL;
- cxled->skip = 0;
+ cxled->cxld.skip = 0;
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 02/10] cxl: Cache decoder settings on PCI devices
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-07-03 22:04 ` [PATCH v8 01/10] cxl: Split decoder programming into a reusable helper Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 03/10] cxl: Share HDM decoder decode logic Srirangan Madhavan
` (10 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Cache CXL core's HDM decoder settings in pci_dev->hdm as decoders are
enumerated, committed, or reset. PCI reset paths can use this snapshot to
restore HDM programming without walking CXL topology during reset recovery.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/hdm.c | 81 +++++++++++++++++++++++++++++++++++++++++-
include/cxl/cxl.h | 12 +++++++
include/linux/pci.h | 6 ++++
3 files changed, 98 insertions(+), 1 deletion(-)
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index fa978c297546..83cda63f76a5 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -84,6 +84,76 @@ static void parse_hdm_decoder_caps(struct cxl_hdm *cxlhdm)
cxlhdm->iw_cap_mask |= BIT(16);
}
+static void clear_hdm_info(void *data)
+{
+ struct pci_dev *pdev = data;
+
+ WRITE_ONCE(pdev->hdm, NULL);
+}
+
+static int devm_cxl_pci_setup_hdm_info(struct cxl_hdm *cxlhdm)
+{
+ struct cxl_port *port = cxlhdm->port;
+ struct cxl_hdm_info *info;
+ struct pci_dev *pdev;
+ struct device *uport;
+
+ if (is_cxl_endpoint(port)) {
+ struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev);
+
+ uport = cxlmd->dev.parent;
+ } else {
+ uport = port->uport_dev;
+ }
+
+ if (!dev_is_pci(uport))
+ return 0;
+
+ pdev = to_pci_dev(uport);
+ info = devm_kzalloc(&pdev->dev,
+ struct_size(info, settings, cxlhdm->decoder_count),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->decoder_count = cxlhdm->decoder_count;
+ WRITE_ONCE(pdev->hdm, info);
+
+ return devm_add_action_or_reset(&pdev->dev, clear_hdm_info, pdev);
+}
+
+static void cxl_hdm_info_set_decoder(struct cxl_hdm *cxlhdm,
+ struct cxl_decoder *cxld)
+{
+ struct cxl_port *port = cxlhdm->port;
+ struct cxl_hdm_info *info;
+ struct pci_dev *pdev;
+ struct device *uport;
+
+ if (is_cxl_endpoint(port)) {
+ struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev);
+
+ uport = cxlmd->dev.parent;
+ } else {
+ uport = port->uport_dev;
+ }
+
+ if (!dev_is_pci(uport))
+ return;
+
+ pdev = to_pci_dev(uport);
+ info = READ_ONCE(pdev->hdm);
+ if (!info || cxld->id >= info->decoder_count)
+ return;
+
+ if (cxld->flags & CXL_DECODER_F_ENABLE)
+ info->settings[cxld->id] = cxld->settings;
+ else
+ info->settings[cxld->id] = (struct cxl_decoder_settings) {
+ .id = cxld->id,
+ };
+}
+
static bool should_emulate_decoders(struct cxl_endpoint_dvsec_info *info)
{
struct cxl_hdm *cxlhdm;
@@ -747,6 +817,7 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
return rc;
}
port->commit_end++;
+ cxl_hdm_info_set_decoder(cxlhdm, cxld);
return 0;
}
@@ -819,6 +890,7 @@ static void cxl_decoder_reset(struct cxl_decoder *cxld)
writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
cxld->flags &= ~CXL_DECODER_F_ENABLE;
+ cxl_hdm_info_set_decoder(cxlhdm, cxld);
/* Userspace is now responsible for reconfiguring this decoder */
if (is_endpoint_decoder(&cxld->dev)) {
@@ -989,6 +1061,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
lo = readl(hdm + CXL_HDM_DECODER0_TL_LOW(which));
hi = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(which));
target_list.value = (hi << 32) + lo;
+ cxld->targets = target_list.value;
for (i = 0; i < cxld->interleave_ways; i++)
cxld->target_map[i] = target_list.target_id[i];
@@ -1062,11 +1135,16 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
struct cxl_port *port = cxlhdm->port;
int i;
u64 dpa_base = 0;
+ int rc;
cxl_settle_decoders(cxlhdm);
+ rc = devm_cxl_pci_setup_hdm_info(cxlhdm);
+ if (rc)
+ return rc;
+
for (i = 0; i < cxlhdm->decoder_count; i++) {
- int rc, target_count = cxlhdm->target_count;
+ int target_count = cxlhdm->target_count;
struct cxl_decoder *cxld;
if (is_cxl_endpoint(port)) {
@@ -1101,6 +1179,7 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
put_device(&cxld->dev);
return rc;
}
+ cxl_hdm_info_set_decoder(cxlhdm, cxld);
rc = add_hdm_decoder(port, cxld);
if (rc) {
dev_warn(&port->dev,
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 757d916a6330..84924ca06e52 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -121,6 +121,18 @@ struct cxl_regs {
);
};
+/**
+ * struct cxl_hdm_info - PCI device HDM decoder programming cache
+ * @decoder_count: number of decoder settings entries
+ * @regs: mapped CXL component registers for this HDM decoder block
+ * @settings: cached per-decoder programming state
+ */
+struct cxl_hdm_info {
+ int decoder_count;
+ struct cxl_component_regs regs;
+ struct cxl_decoder_settings settings[] __counted_by(decoder_count);
+};
+
int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm);
struct cxl_reg_map {
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 2c4454583c11..7db2daf8597c 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -334,6 +334,9 @@ struct pcie_link_state;
struct pci_sriov;
struct pci_p2pdma;
struct rcec_ea;
+#ifdef CONFIG_CXL_HDM
+struct cxl_hdm_info;
+#endif
/* struct pci_dev - describes a PCI device
*
@@ -563,6 +566,9 @@ struct pci_dev {
#ifdef CONFIG_PCI_DOE
struct xarray doe_mbs; /* Data Object Exchange mailboxes */
#endif
+#ifdef CONFIG_CXL_HDM
+ struct cxl_hdm_info *hdm; /* CXL HDM decoder reset state */
+#endif
#ifdef CONFIG_PCI_NPEM
struct npem *npem; /* Native PCIe Enclosure Management */
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 03/10] cxl: Share HDM decoder decode logic
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-07-03 22:04 ` [PATCH v8 01/10] cxl: Split decoder programming into a reusable helper Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 02/10] cxl: Cache decoder settings on PCI devices Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 04/10] cxl: Cache endpoint decoder settings during PCI enumeration Srirangan Madhavan
` (9 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Move HDM decoder register decoding into a helper shared by normal CXL
core enumeration and early PCI HDM cache setup. This keeps validation of
base, size, interleave, target type, and enable state in one place before
adding another HDM parser.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/hdm.c | 60 ++++++++++------------------------------
drivers/cxl/core/reset.c | 45 ++++++++++++++++++++++++++++++
include/cxl/cxl.h | 3 ++
3 files changed, 63 insertions(+), 45 deletions(-)
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 83cda63f76a5..bd1d92e5add2 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -968,35 +968,28 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
lo = readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(which));
hi = readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(which));
size = (hi << 32) + lo;
- committed = !!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED);
+ lo = readl(hdm + CXL_HDM_DECODER0_TL_LOW(which));
+ hi = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(which));
+ target_list.value = (hi << 32) + lo;
+ rc = cxl_hdm_decode_decoder(&settings, which, ctrl, base, size,
+ target_list.value, &committed);
+ if (rc) {
+ dev_warn(&port->dev,
+ "decoder%d.%d: Invalid decoder configuration (ctrl: %#x): %d\n",
+ port->id, cxld->id, ctrl, rc);
+ return rc;
+ }
+
cxld->commit = cxl_decoder_commit;
cxld->reset = cxl_decoder_reset;
-
- if (!committed)
- size = 0;
- if (base == U64_MAX || size == U64_MAX) {
- dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n",
- port->id, cxld->id);
- return -ENXIO;
- }
+ cxld->settings = settings;
+ size = range_len(&cxld->hpa_range);
if (info)
cxled = to_cxl_endpoint_decoder(&cxld->dev);
- cxld->hpa_range = (struct range) {
- .start = base,
- .end = base + size - 1,
- };
/* decoders are enabled if committed */
if (committed) {
- cxld->flags |= CXL_DECODER_F_ENABLE;
- if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
- cxld->flags |= CXL_DECODER_F_LOCK;
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- else
- cxld->target_type = CXL_DECODER_DEVMEM;
-
guard(rwsem_write)(&cxl_rwsem.region);
if (cxld->id != cxl_num_decoders_committed(port)) {
dev_warn(&port->dev,
@@ -1036,32 +1029,11 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
}
}
- rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
- &cxld->interleave_ways);
- if (rc) {
- dev_warn(&port->dev,
- "decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
- port->id, cxld->id, ctrl);
- return rc;
- }
- rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
- &cxld->interleave_granularity);
- if (rc) {
- dev_warn(&port->dev,
- "decoder%d.%d: Invalid interleave granularity (ctrl: %#x)\n",
- port->id, cxld->id, ctrl);
- return rc;
- }
-
dev_dbg(&port->dev, "decoder%d.%d: range: %#llx-%#llx iw: %d ig: %d\n",
port->id, cxld->id, cxld->hpa_range.start, cxld->hpa_range.end,
cxld->interleave_ways, cxld->interleave_granularity);
if (!cxled) {
- lo = readl(hdm + CXL_HDM_DECODER0_TL_LOW(which));
- hi = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(which));
- target_list.value = (hi << 32) + lo;
- cxld->targets = target_list.value;
for (i = 0; i < cxld->interleave_ways; i++)
cxld->target_map[i] = target_list.target_id[i];
@@ -1078,9 +1050,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
port->id, cxld->id, size, cxld->interleave_ways);
return -ENXIO;
}
- lo = readl(hdm + CXL_HDM_DECODER0_SKIP_LOW(which));
- hi = readl(hdm + CXL_HDM_DECODER0_SKIP_HIGH(which));
- skip = (hi << 32) + lo;
+ skip = cxld->skip;
rc = devm_cxl_dpa_reserve(cxled, *dpa_base + skip, dpa_size, skip);
if (rc) {
dev_err(&port->dev,
diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
index 14f024098e82..4c977fc47f8d 100644
--- a/drivers/cxl/core/reset.c
+++ b/drivers/cxl/core/reset.c
@@ -116,3 +116,48 @@ int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm)
return 0;
}
EXPORT_SYMBOL_FOR_MODULES(cxl_commit, "cxl_core");
+
+int cxl_hdm_decode_decoder(struct cxl_decoder_settings *settings, int id,
+ u32 ctrl, u64 base, u64 size, u64 target_or_skip,
+ bool *committed)
+{
+ bool enabled = FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
+ int rc;
+
+ *settings = (struct cxl_decoder_settings) {
+ .id = id,
+ .targets = target_or_skip,
+ .target_type = FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl) ?
+ CXL_DECODER_HOSTONLYMEM : CXL_DECODER_DEVMEM,
+ };
+
+ if (committed)
+ *committed = enabled;
+ if (!enabled)
+ size = 0;
+ if (base == U64_MAX || size == U64_MAX ||
+ (size && base > U64_MAX - (size - 1)))
+ return -ENXIO;
+ if (enabled && !size)
+ return -ENXIO;
+
+ settings->hpa_range = (struct range) {
+ .start = base,
+ .end = base + size - 1,
+ };
+ if (enabled) {
+ settings->flags = CXL_DECODER_F_ENABLE;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
+ settings->flags |= CXL_DECODER_F_LOCK;
+ }
+
+ rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
+ &settings->interleave_ways);
+ if (rc)
+ return rc;
+
+ return eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK,
+ ctrl),
+ &settings->interleave_granularity);
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_decode_decoder, "cxl_core");
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 84924ca06e52..80839517eabf 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -133,6 +133,9 @@ struct cxl_hdm_info {
struct cxl_decoder_settings settings[] __counted_by(decoder_count);
};
+int cxl_hdm_decode_decoder(struct cxl_decoder_settings *settings, int id,
+ u32 ctrl, u64 base, u64 size, u64 target_or_skip,
+ bool *committed);
int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm);
struct cxl_reg_map {
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 04/10] cxl: Cache endpoint decoder settings during PCI enumeration
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (2 preceding siblings ...)
2026-07-03 22:05 ` [PATCH v8 03/10] cxl: Share HDM decoder decode logic Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-07 5:30 ` Richard Cheng
2026-07-03 22:05 ` [PATCH v8 05/10] cxl: Add CXL Device Reset helper Srirangan Madhavan
` (8 subsequent siblings)
12 siblings, 1 reply; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Populate pci_dev->hdm from PCI capability initialization for CXL.mem
functions. If Memory Space Enable is clear, temporarily set it while
reading HDM MMIO and restore the original PCI_COMMAND value before
returning. This gives driver-free reset paths an early HDM snapshot.
CXL core later reuses and refreshes the same cache. Move the register
helpers into the built-in CONFIG_CXL_HDM set so the early cache path is
available without cxl_core.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/Makefile | 3 +-
drivers/cxl/core/hdm.c | 58 ++++----
drivers/cxl/core/regs.c | 4 +
drivers/cxl/core/reset.c | 288 ++++++++++++++++++++++++++++++++++++++
drivers/pci/probe.c | 3 +
include/cxl/cxl.h | 31 +++-
6 files changed, 349 insertions(+), 38 deletions(-)
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index dc075cee0450..69cf2ea7ee74 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
-obj-$(CONFIG_CXL_HDM) += reset.o
+obj-$(CONFIG_CXL_HDM) += regs.o reset.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
@@ -8,7 +8,6 @@ CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
cxl_core-y += pmem.o
-cxl_core-y += regs.o
cxl_core-y += memdev.o
cxl_core-y += mbox.o
cxl_core-y += pci.o
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index bd1d92e5add2..7a1ade846c9c 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -84,18 +84,9 @@ static void parse_hdm_decoder_caps(struct cxl_hdm *cxlhdm)
cxlhdm->iw_cap_mask |= BIT(16);
}
-static void clear_hdm_info(void *data)
-{
- struct pci_dev *pdev = data;
-
- WRITE_ONCE(pdev->hdm, NULL);
-}
-
-static int devm_cxl_pci_setup_hdm_info(struct cxl_hdm *cxlhdm)
+static struct pci_dev *cxl_hdm_to_pci_dev(struct cxl_hdm *cxlhdm)
{
struct cxl_port *port = cxlhdm->port;
- struct cxl_hdm_info *info;
- struct pci_dev *pdev;
struct device *uport;
if (is_cxl_endpoint(port)) {
@@ -107,42 +98,42 @@ static int devm_cxl_pci_setup_hdm_info(struct cxl_hdm *cxlhdm)
}
if (!dev_is_pci(uport))
- return 0;
+ return NULL;
- pdev = to_pci_dev(uport);
- info = devm_kzalloc(&pdev->dev,
- struct_size(info, settings, cxlhdm->decoder_count),
- GFP_KERNEL);
- if (!info)
- return -ENOMEM;
+ return to_pci_dev(uport);
+}
+
+static int devm_cxl_pci_setup_hdm_info(struct cxl_hdm *cxlhdm)
+{
+ struct cxl_hdm_info *info;
+ struct pci_dev *pdev;
- info->decoder_count = cxlhdm->decoder_count;
- WRITE_ONCE(pdev->hdm, info);
+ pdev = cxl_hdm_to_pci_dev(cxlhdm);
+ if (!pdev)
+ return 0;
- return devm_add_action_or_reset(&pdev->dev, clear_hdm_info, pdev);
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ if (info) {
+ if (info->decoder_count != cxlhdm->decoder_count)
+ return -ENXIO;
+ }
+
+ return 0;
}
static void cxl_hdm_info_set_decoder(struct cxl_hdm *cxlhdm,
struct cxl_decoder *cxld)
{
- struct cxl_port *port = cxlhdm->port;
struct cxl_hdm_info *info;
struct pci_dev *pdev;
- struct device *uport;
-
- if (is_cxl_endpoint(port)) {
- struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev);
-
- uport = cxlmd->dev.parent;
- } else {
- uport = port->uport_dev;
- }
- if (!dev_is_pci(uport))
+ pdev = cxl_hdm_to_pci_dev(cxlhdm);
+ if (!pdev)
return;
- pdev = to_pci_dev(uport);
- info = READ_ONCE(pdev->hdm);
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
if (!info || cxld->id >= info->decoder_count)
return;
@@ -948,6 +939,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
{
struct cxl_endpoint_decoder *cxled = NULL;
u64 size, base, skip, dpa_size, lo, hi;
+ struct cxl_decoder_settings settings;
bool committed;
u32 remainder;
int i, rc;
diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c
index 93710cf4f0a6..040b0304f63c 100644
--- a/drivers/cxl/core/regs.c
+++ b/drivers/cxl/core/regs.c
@@ -199,6 +199,7 @@ void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr,
return ret_val;
}
+EXPORT_SYMBOL_NS_GPL(devm_cxl_iomap_block, "CXL");
int cxl_map_component_regs(const struct cxl_register_map *map,
struct cxl_component_regs *regs,
@@ -517,6 +518,7 @@ u16 cxl_rcrb_to_aer(struct device *dev, resource_size_t rcrb)
return offset;
}
+EXPORT_SYMBOL_NS_GPL(cxl_rcrb_to_aer, "CXL");
static resource_size_t cxl_rcrb_to_linkcap(struct device *dev, struct cxl_dport *dport)
{
@@ -633,6 +635,7 @@ resource_size_t __rcrb_to_component(struct device *dev, struct cxl_rcrb_info *ri
return component_reg_phys;
}
+EXPORT_SYMBOL_NS_GPL(__rcrb_to_component, "CXL");
resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
struct cxl_dport *dport)
@@ -641,3 +644,4 @@ resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
return CXL_RESOURCE_NONE;
return __rcrb_to_component(dev, &dport->rcrb, CXL_RCRB_UPSTREAM);
}
+EXPORT_SYMBOL_NS_GPL(cxl_rcd_component_reg_phys, "CXL");
diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
index 4c977fc47f8d..97b72cc67b6b 100644
--- a/drivers/cxl/core/reset.c
+++ b/drivers/cxl/core/reset.c
@@ -2,9 +2,16 @@
/* Copyright(c) 2026 NVIDIA Corporation. All rights reserved. */
#include <linux/delay.h>
#include <linux/bug.h>
+#include <linux/bitfield.h>
#include <linux/errno.h>
#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include <cxlpci.h>
#include "cxl.h"
#include "core.h"
@@ -161,3 +168,284 @@ int cxl_hdm_decode_decoder(struct cxl_decoder_settings *settings, int id,
&settings->interleave_granularity);
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_decode_decoder, "cxl_core");
+
+struct cxl_hdm_decoder_state {
+ u32 ctrl;
+ u32 base_low;
+ u32 base_high;
+ u32 size_low;
+ u32 size_high;
+ u32 target_low;
+ u32 target_high;
+};
+
+void pci_cxl_hdm_release(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *info;
+
+ scoped_guard(rwsem_write, &cxl_rwsem.dpa) {
+ info = pdev->hdm;
+ pdev->hdm = NULL;
+ }
+ if (!info)
+ return;
+
+ kfree(info->decoder_state);
+ kfree(info);
+}
+
+static int cxl_pci_hdm_find_bar(struct pci_dev *pdev, resource_size_t hdm_start,
+ resource_size_t hdm_size, int *bar,
+ resource_size_t *offset)
+{
+ resource_size_t hdm_end = hdm_start + hdm_size - 1;
+
+ for (int i = 0; i < PCI_STD_NUM_BARS; i++) {
+ struct resource *res = &pdev->resource[i];
+
+ if (!pci_resource_len(pdev, i))
+ continue;
+ if (resource_type(res) != IORESOURCE_MEM)
+ continue;
+ if (hdm_start < res->start || hdm_end > res->end)
+ continue;
+
+ *bar = i;
+ *offset = hdm_start - res->start;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+static void __iomem *cxl_pci_hdm_map(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ struct cxl_hdm_info *info)
+{
+ struct cxl_reg_map *hdm_map = &map->component_map.hdm_decoder;
+ resource_size_t hdm_start;
+ void __iomem *hdm;
+ int rc;
+
+ hdm_start = map->resource + hdm_map->offset;
+ info->hdm_size = hdm_map->size;
+
+ rc = cxl_pci_hdm_find_bar(pdev, hdm_start, info->hdm_size,
+ &info->hdm_bar, &info->hdm_offset);
+ if (rc)
+ return ERR_PTR(rc);
+
+ hdm = ioremap(hdm_start, info->hdm_size);
+ if (!hdm) {
+ pci_err(pdev, "failed to map CXL HDM decoder registers\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return hdm;
+}
+
+static void cxl_pci_hdm_read_decoder_state(struct cxl_hdm_decoder_state *state,
+ void __iomem *hdm, int id)
+{
+ state->ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ state->base_low = readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ state->base_high = readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
+ state->size_low = readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ state->size_high = readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
+ state->target_low = readl(hdm + CXL_HDM_DECODER0_TL_LOW(id));
+ state->target_high = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(id));
+}
+
+static int cxl_pci_hdm_read_decoder(struct pci_dev *pdev,
+ struct cxl_hdm_decoder_state *state,
+ struct cxl_decoder_settings *settings,
+ void __iomem *hdm, int id)
+{
+ u64 target_or_skip, base, size;
+ bool committed;
+ int rc;
+
+ cxl_pci_hdm_read_decoder_state(state, hdm, id);
+
+ base = ((u64)state->base_high << 32) | state->base_low;
+ size = ((u64)state->size_high << 32) | state->size_low;
+ target_or_skip = ((u64)state->target_high << 32) | state->target_low;
+
+ rc = cxl_hdm_decode_decoder(settings, id, state->ctrl, base, size,
+ target_or_skip, &committed);
+ if (rc) {
+ pci_err(pdev, "CXL HDM decoder %d has invalid configuration: %d\n",
+ id, rc);
+ return rc;
+ }
+ if (!committed)
+ return 0;
+
+ return 0;
+}
+
+static int cxl_pci_hdm_capable(struct pci_dev *pdev)
+{
+ u16 cap;
+ int dvsec;
+ int rc;
+
+ dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!dvsec)
+ return -ENOTTY;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, &cap);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ if (!(cap & PCI_DVSEC_CXL_MEM_CAPABLE))
+ return -ENOTTY;
+
+ return 0;
+}
+
+static int __pci_cxl_hdm_init(struct pci_dev *pdev)
+{
+ struct cxl_decoder_settings *settings;
+ struct cxl_register_map map = { 0 };
+ struct cxl_hdm_info *info;
+ void __iomem *hdm = NULL;
+ bool restore_command = false;
+ bool allocated_info = false;
+ int decoder_count;
+ u16 command;
+ int rc;
+
+ scoped_guard(rwsem_read, &cxl_rwsem.dpa) {
+ info = pdev->hdm;
+ if (info)
+ return 0;
+ }
+
+ rc = cxl_pci_hdm_capable(pdev);
+ if (rc)
+ return rc;
+
+ rc = pci_read_config_word(pdev, PCI_COMMAND, &command);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ if (!(command & PCI_COMMAND_MEMORY))
+ restore_command = true;
+
+ if (restore_command) {
+ rc = pci_write_config_word(pdev, PCI_COMMAND,
+ command | PCI_COMMAND_MEMORY);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ }
+
+ if (!info) {
+ info = kzalloc_obj(*info, GFP_KERNEL);
+ if (!info)
+ goto err_nomem;
+ allocated_info = true;
+ }
+
+ rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
+ if (rc)
+ goto out_restore_command;
+
+ rc = cxl_setup_regs(&map);
+ if (rc)
+ goto out_restore_command;
+
+ if (!map.component_map.hdm_decoder.valid) {
+ rc = -ENODEV;
+ goto out_restore_command;
+ }
+
+ hdm = cxl_pci_hdm_map(pdev, &map, info);
+ if (IS_ERR(hdm)) {
+ rc = PTR_ERR(hdm);
+ hdm = NULL;
+ goto out_restore_command;
+ }
+
+ decoder_count = cxl_hdm_decoder_count(readl(hdm +
+ CXL_HDM_DECODER_CAP_OFFSET));
+ if (decoder_count < 0) {
+ rc = decoder_count;
+ goto out_unmap;
+ }
+
+ if (decoder_count > CXL_HDM_DECODER_MAX_COUNT) {
+ rc = -ENXIO;
+ goto out_unmap;
+ }
+
+ if (info->decoder_count && info->decoder_count != decoder_count) {
+ rc = -ENXIO;
+ goto out_unmap;
+ }
+
+ info->decoder_count = decoder_count;
+ info->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ info->decoder_state = kcalloc(decoder_count,
+ sizeof(*info->decoder_state),
+ GFP_KERNEL);
+ if (!info->decoder_state) {
+ rc = -ENOMEM;
+ goto out_unmap;
+ }
+
+ settings = info->settings;
+ for (int i = 0; i < info->decoder_count; i++) {
+ rc = cxl_pci_hdm_read_decoder(pdev, &info->decoder_state[i],
+ &settings[i], hdm, i);
+ if (rc)
+ goto out_unmap;
+ }
+
+ if (restore_command) {
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc)
+ goto out_restore_failed;
+ }
+
+ scoped_guard(rwsem_write, &cxl_rwsem.dpa) {
+ if (pdev->hdm)
+ goto out_unmap;
+ pdev->hdm = info;
+ }
+ iounmap(hdm);
+ return 0;
+
+out_restore_failed:
+ rc = pcibios_err_to_errno(rc);
+ goto out_unmap;
+err_nomem:
+ rc = -ENOMEM;
+ goto out_restore_command;
+out_unmap:
+ if (hdm)
+ iounmap(hdm);
+out_restore_command:
+ if (allocated_info) {
+ kfree(info->decoder_state);
+ kfree(info);
+ }
+ if (restore_command) {
+ int rc2;
+
+ rc2 = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc2 && !rc)
+ rc = pcibios_err_to_errno(rc2);
+ }
+ return rc;
+}
+
+void pci_cxl_hdm_init(struct pci_dev *pdev)
+{
+ int rc;
+
+ rc = __pci_cxl_hdm_init(pdev);
+ if (rc && rc != -ENOTTY && rc != -ENODEV)
+ pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
+}
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc..2fd186468498 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -24,6 +24,7 @@
#include <linux/pm_runtime.h>
#include <linux/bitfield.h>
#include <trace/events/pci.h>
+#include <cxl/cxl.h>
#include "pci.h"
static struct resource busn_resource = {
@@ -2489,6 +2490,7 @@ static void pci_release_dev(struct device *dev)
struct pci_dev *pci_dev;
pci_dev = to_pci_dev(dev);
+ pci_cxl_hdm_release(pci_dev);
pci_release_capabilities(pci_dev);
pci_release_of_node(pci_dev);
pcibios_release_device(pci_dev);
@@ -2679,6 +2681,7 @@ static void pci_init_capabilities(struct pci_dev *dev)
pci_rebar_init(dev); /* Resizable BAR */
pci_dev3_init(dev); /* Device 3 capabilities */
pci_ide_init(dev); /* Link Integrity and Data Encryption */
+ pci_cxl_hdm_init(dev); /* CXL HDM Decoder Capability */
pcie_report_downtraining(dev);
pci_init_reset_methods(dev);
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 80839517eabf..2215fe1c3f78 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -26,6 +26,7 @@ enum cxl_devtype {
};
struct cxl_region;
+struct pci_dev;
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
@@ -121,22 +122,46 @@ struct cxl_regs {
);
};
+#define CXL_HDM_DECODER_MAX_COUNT 32
+
+struct cxl_hdm_decoder_state;
+
/**
* struct cxl_hdm_info - PCI device HDM decoder programming cache
* @decoder_count: number of decoder settings entries
- * @regs: mapped CXL component registers for this HDM decoder block
+ * @hdm_bar: BAR containing the HDM decoder registers
+ * @hdm_offset: HDM decoder register offset relative to @hdm_bar
+ * @hdm_size: HDM decoder register resource size
+ * @global_ctrl: cached HDM decoder global control register
+ * @decoder_state: cached raw per-decoder register state
* @settings: cached per-decoder programming state
*/
struct cxl_hdm_info {
int decoder_count;
- struct cxl_component_regs regs;
- struct cxl_decoder_settings settings[] __counted_by(decoder_count);
+ int hdm_bar;
+ resource_size_t hdm_offset;
+ resource_size_t hdm_size;
+ u32 global_ctrl;
+ struct cxl_hdm_decoder_state *decoder_state;
+ struct cxl_decoder_settings settings[CXL_HDM_DECODER_MAX_COUNT];
};
int cxl_hdm_decode_decoder(struct cxl_decoder_settings *settings, int id,
u32 ctrl, u64 base, u64 size, u64 target_or_skip,
bool *committed);
int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm);
+#ifdef CONFIG_CXL_HDM
+void pci_cxl_hdm_init(struct pci_dev *pdev);
+void pci_cxl_hdm_release(struct pci_dev *pdev);
+#else
+static inline void pci_cxl_hdm_init(struct pci_dev *pdev)
+{
+}
+
+static inline void pci_cxl_hdm_release(struct pci_dev *pdev)
+{
+}
+#endif
struct cxl_reg_map {
bool valid;
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 05/10] cxl: Add CXL Device Reset helper
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (3 preceding siblings ...)
2026-07-03 22:05 ` [PATCH v8 04/10] cxl: Cache endpoint decoder settings during PCI enumeration Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 06/10] cxl: Validate HDM ranges before CXL reset Srirangan Madhavan
` (7 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Add an internal CXL Device Reset helper for Type 2 functions that advertise
CXL Reset in the CXL Device DVSEC. The helper disables CXL.cache, performs
cache writeback when supported, initiates reset with Memory Clear disabled,
waits for completion, and re-enables CXL.cache on exit.
Leave the helper unregistered until range validation and reset-scope
validation are in place.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/reset.c | 224 ++++++++++++++++++++++++++++++++++
include/cxl/cxl.h | 7 ++
include/uapi/linux/pci_regs.h | 14 +++
3 files changed, 245 insertions(+)
diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
index 97b72cc67b6b..7a9b1ecfadcf 100644
--- a/drivers/cxl/core/reset.c
+++ b/drivers/cxl/core/reset.c
@@ -7,6 +7,8 @@
#include <linux/export.h>
#include <linux/io.h>
#include <linux/ioport.h>
+#include <linux/iommu.h>
+#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -449,3 +451,225 @@ void pci_cxl_hdm_init(struct pci_dev *pdev)
if (rc && rc != -ENOTTY && rc != -ENODEV)
pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
}
+
+/*
+ * CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
+ * Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
+ * initiating CXL Reset, then limits software to CXL Status2 access until
+ * reset completion, timeout, or error.
+ */
+#define CXL_RESET_RRS_WAIT_MS 100
+#define CXL_RESET_STATUS_POLL_MS 20
+static const u32 cxl_reset_timeout_ms[] = {
+ 10, 100, 1000, 10000, 100000,
+};
+
+#define CXL_CACHE_WBI_TIMEOUT_US 100000
+#define CXL_CACHE_WBI_POLL_US 100
+
+static int cxl_reset_dvsec(struct pci_dev *pdev)
+{
+ int dvsec, rc;
+ u16 cap;
+
+ dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!dvsec)
+ return -ENOTTY;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, &cap);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ if ((cap & (PCI_DVSEC_CXL_CACHE_CAPABLE |
+ PCI_DVSEC_CXL_MEM_CAPABLE)) !=
+ (PCI_DVSEC_CXL_CACHE_CAPABLE | PCI_DVSEC_CXL_MEM_CAPABLE))
+ return -ENOTTY;
+
+ if (!(cap & PCI_DVSEC_CXL_RST_CAPABLE))
+ return -ENOTTY;
+
+ return dvsec;
+}
+
+static int cxl_reset_update_ctrl2(struct pci_dev *pdev, int dvsec, u16 set,
+ u16 clear)
+{
+ u16 cmd = PCI_DVSEC_CXL_INIT_CACHE_WBI | PCI_DVSEC_CXL_INIT_CXL_RST;
+ u16 ctrl2;
+ int rc;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ ctrl2 &= ~cmd;
+ ctrl2 |= set;
+ ctrl2 &= ~clear;
+
+ rc = pci_write_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ return 0;
+}
+
+static int cxl_reset_enable_cache(struct pci_dev *pdev, int dvsec)
+{
+ return cxl_reset_update_ctrl2(pdev, dvsec, 0,
+ PCI_DVSEC_CXL_DISABLE_CACHING);
+}
+
+static int cxl_reset_disable_cache(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int remaining_us = CXL_CACHE_WBI_TIMEOUT_US;
+ u16 status2;
+ int rc, rc2;
+
+ rc = cxl_reset_update_ctrl2(pdev, dvsec,
+ PCI_DVSEC_CXL_DISABLE_CACHING, 0);
+ if (rc)
+ return rc;
+
+ if (!(cap & PCI_DVSEC_CXL_CACHE_WBI_CAPABLE))
+ return 0;
+
+ rc = cxl_reset_update_ctrl2(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CACHE_WBI, 0);
+ if (rc)
+ goto err_enable_cache;
+
+ do {
+ usleep_range(CXL_CACHE_WBI_POLL_US, CXL_CACHE_WBI_POLL_US + 1);
+ remaining_us -= CXL_CACHE_WBI_POLL_US;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ goto err_enable_cache;
+ }
+ } while (!(status2 & PCI_DVSEC_CXL_CACHE_INV) && remaining_us > 0);
+
+ if (!(status2 & PCI_DVSEC_CXL_CACHE_INV)) {
+ rc = -ETIMEDOUT;
+ goto err_enable_cache;
+ }
+
+ return 0;
+
+err_enable_cache:
+ /*
+ * DISABLE_CACHING can be rolled back here. INIT_CACHE_WBI is
+ * self-clearing on completion, so leave any in-flight writeback alone.
+ */
+ rc2 = cxl_reset_enable_cache(pdev, dvsec);
+ if (rc2)
+ pci_warn(pdev, "failed to re-enable CXL caching: %d\n", rc2);
+ return rc;
+}
+
+static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ unsigned long deadline;
+ u32 timeout_ms;
+ u16 status2;
+ int idx, rc;
+
+ idx = FIELD_GET(PCI_DVSEC_CXL_RST_TIMEOUT, cap);
+ if (idx >= ARRAY_SIZE(cxl_reset_timeout_ms)) {
+ int last = ARRAY_SIZE(cxl_reset_timeout_ms) - 1;
+
+ pci_warn(pdev,
+ "unknown CXL reset timeout encoding %d; using %u ms\n",
+ idx, cxl_reset_timeout_ms[last]);
+ idx = last;
+ }
+
+ timeout_ms = max_t(u32, cxl_reset_timeout_ms[idx],
+ CXL_RESET_RRS_WAIT_MS);
+ deadline = jiffies + msecs_to_jiffies(timeout_ms);
+ msleep(CXL_RESET_RRS_WAIT_MS);
+
+ do {
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc || status2 == U16_MAX)
+ goto not_ready;
+
+ if (status2 & PCI_DVSEC_CXL_RST_ERR)
+ return -EIO;
+
+ if (status2 & PCI_DVSEC_CXL_RST_DONE)
+ return 0;
+
+not_ready:
+ if (time_after_eq(jiffies, deadline))
+ return -ETIMEDOUT;
+
+ msleep(CXL_RESET_STATUS_POLL_MS);
+ } while (true);
+}
+
+static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
+{
+ bool cache_disabled = false;
+ u16 cap;
+ int rc;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, &cap);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ if (!pci_wait_for_pending_transaction(pdev))
+ pci_err(pdev, "timed out waiting for pending transactions\n");
+
+ rc = pci_dev_reset_iommu_prepare(pdev);
+ if (rc) {
+ pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
+ return rc;
+ }
+
+ rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ if (rc)
+ goto out;
+ cache_disabled = true;
+
+ rc = cxl_reset_update_ctrl2(pdev, dvsec, PCI_DVSEC_CXL_INIT_CXL_RST,
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN);
+ if (rc)
+ goto out;
+
+ rc = cxl_reset_wait_done(pdev, dvsec, cap);
+ if (rc)
+ goto out;
+
+out:
+ if (cache_disabled) {
+ int rc2;
+
+ rc2 = cxl_reset_enable_cache(pdev, dvsec);
+ if (rc2 && rc)
+ pci_warn(pdev, "failed to re-enable CXL caching: %d\n",
+ rc2);
+ else if (rc2)
+ rc = rc2;
+ }
+
+ pci_dev_reset_iommu_done(pdev);
+ return rc;
+}
+
+int cxl_reset_function(struct pci_dev *pdev, bool probe)
+{
+ int dvsec;
+
+ dvsec = cxl_reset_dvsec(pdev);
+ if (dvsec < 0)
+ return dvsec;
+
+ if (probe)
+ return 0;
+
+ return cxl_reset_execute(pdev, dvsec);
+}
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 2215fe1c3f78..de58f484b7d9 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -9,6 +9,7 @@
#include <linux/node.h>
#include <linux/ioport.h>
#include <linux/range.h>
+#include <linux/errno.h>
#include <cxl/mailbox.h>
/**
@@ -153,6 +154,7 @@ int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm);
#ifdef CONFIG_CXL_HDM
void pci_cxl_hdm_init(struct pci_dev *pdev);
void pci_cxl_hdm_release(struct pci_dev *pdev);
+int cxl_reset_function(struct pci_dev *pdev, bool probe);
#else
static inline void pci_cxl_hdm_init(struct pci_dev *pdev)
{
@@ -161,6 +163,11 @@ static inline void pci_cxl_hdm_init(struct pci_dev *pdev)
static inline void pci_cxl_hdm_release(struct pci_dev *pdev)
{
}
+
+static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
+{
+ return -ENOTTY;
+}
#endif
struct cxl_reg_map {
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
index 14f634ab9350..194ae56b4404 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1349,10 +1349,24 @@
/* CXL r4.0, 8.1.3: PCIe DVSEC for CXL Device */
#define PCI_DVSEC_CXL_DEVICE 0
#define PCI_DVSEC_CXL_CAP 0xA
+#define PCI_DVSEC_CXL_CACHE_CAPABLE _BITUL(0)
#define PCI_DVSEC_CXL_MEM_CAPABLE _BITUL(2)
#define PCI_DVSEC_CXL_HDM_COUNT __GENMASK(5, 4)
+#define PCI_DVSEC_CXL_CACHE_WBI_CAPABLE _BITUL(6)
+#define PCI_DVSEC_CXL_RST_CAPABLE _BITUL(7)
+#define PCI_DVSEC_CXL_RST_TIMEOUT __GENMASK(10, 8)
+#define PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE _BITUL(11)
#define PCI_DVSEC_CXL_CTRL 0xC
#define PCI_DVSEC_CXL_MEM_ENABLE _BITUL(2)
+#define PCI_DVSEC_CXL_CTRL2 0x10
+#define PCI_DVSEC_CXL_DISABLE_CACHING _BITUL(0)
+#define PCI_DVSEC_CXL_INIT_CACHE_WBI _BITUL(1)
+#define PCI_DVSEC_CXL_INIT_CXL_RST _BITUL(2)
+#define PCI_DVSEC_CXL_RST_MEM_CLR_EN _BITUL(3)
+#define PCI_DVSEC_CXL_STATUS2 0x12
+#define PCI_DVSEC_CXL_CACHE_INV _BITUL(0)
+#define PCI_DVSEC_CXL_RST_DONE _BITUL(1)
+#define PCI_DVSEC_CXL_RST_ERR _BITUL(2)
#define PCI_DVSEC_CXL_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10))
#define PCI_DVSEC_CXL_RANGE_SIZE_LOW(i) (0x1C + (i * 0x10))
#define PCI_DVSEC_CXL_MEM_INFO_VALID _BITUL(0)
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 06/10] cxl: Validate HDM ranges before CXL reset
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (4 preceding siblings ...)
2026-07-03 22:05 ` [PATCH v8 05/10] cxl: Add CXL Device Reset helper Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 07/10] PCI/CXL: Discover the CXL reset scope Srirangan Madhavan
` (6 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Before reset, collect enabled cached HDM decoder ranges and reserve them
with request_mem_region(). This rejects reset while affected CXL memory is
busy and keeps the validation stable through reset.
If CPU cache invalidation support is available, invalidate the affected
ranges before reset. If the runtime backend is unavailable, continue after
the range reservation succeeds.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/reset.c | 240 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 239 insertions(+), 1 deletion(-)
diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
index 7a9b1ecfadcf..fb741b66e502 100644
--- a/drivers/cxl/core/reset.c
+++ b/drivers/cxl/core/reset.c
@@ -10,6 +10,8 @@
#include <linux/iommu.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/memregion.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -467,6 +469,231 @@ static const u32 cxl_reset_timeout_ms[] = {
#define CXL_CACHE_WBI_TIMEOUT_US 100000
#define CXL_CACHE_WBI_POLL_US 100
+struct cxl_hdm_range {
+ struct list_head list;
+ struct pci_dev *pdev;
+ struct range hpa_range;
+ struct resource *res;
+};
+
+struct cxl_hdm_range_context {
+ struct list_head ranges;
+};
+
+static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
+{
+ INIT_LIST_HEAD(&ctx->ranges);
+}
+
+static void cxl_hdm_range_context_destroy(struct cxl_hdm_range_context *ctx)
+{
+ struct cxl_hdm_range *range, *next;
+
+ list_for_each_entry_safe(range, next, &ctx->ranges, list) {
+ list_del(&range->list);
+ if (range->res)
+ release_mem_region(range->hpa_range.start,
+ resource_size(range->res));
+ kfree(range);
+ }
+}
+
+static int cxl_hdm_range_add(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev, const struct range *hpa_range)
+{
+ struct cxl_hdm_range *range;
+
+ if (hpa_range->end < hpa_range->start)
+ return -EINVAL;
+
+ list_for_each_entry(range, &ctx->ranges, list)
+ if (range->hpa_range.start == hpa_range->start &&
+ range->hpa_range.end == hpa_range->end)
+ return 0;
+
+ range = kzalloc_obj(*range);
+ if (!range)
+ return -ENOMEM;
+
+ range->pdev = pdev;
+ range->hpa_range = *hpa_range;
+ list_add_tail(&range->list, &ctx->ranges);
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_collect(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *info = READ_ONCE(pdev->hdm);
+ int rc;
+
+ if (!info) {
+ pci_err(pdev, "CXL HDM decoder state unavailable\n");
+ return -ENXIO;
+ }
+
+ for (int i = 0; i < info->decoder_count; i++) {
+ struct cxl_decoder_settings *settings = &info->settings[i];
+
+ if (!(settings->flags & CXL_DECODER_F_ENABLE))
+ continue;
+
+ if (settings->flags & CXL_DECODER_F_NORMALIZED_ADDRESSING) {
+ pci_err(pdev,
+ "CXL reset does not support normalized address decoders\n");
+ return -EOPNOTSUPP;
+ }
+
+ rc = cxl_hdm_range_add(ctx, pdev, &settings->hpa_range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_range_len(struct pci_dev *pdev,
+ const struct range *hpa_range, u64 *len)
+{
+ if (sizeof(resource_size_t) < sizeof(hpa_range->start) &&
+ (hpa_range->start > (resource_size_t)~0ULL ||
+ hpa_range->end > (resource_size_t)~0ULL)) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource address size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (hpa_range->end < hpa_range->start)
+ return -EINVAL;
+
+ if (!hpa_range->start && hpa_range->end == U64_MAX) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ *len = range_len(hpa_range);
+ if (sizeof(resource_size_t) < sizeof(*len) &&
+ *len > (resource_size_t)~0ULL) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (sizeof(size_t) < sizeof(*len) && *len > SIZE_MAX) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds cache flush size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_range_request(struct cxl_hdm_range *range)
+{
+ struct pci_dev *pdev = range->pdev;
+ const struct range *hpa_range = &range->hpa_range;
+ u64 len;
+ int rc;
+
+ rc = cxl_hdm_range_len(pdev, hpa_range, &len);
+ if (rc)
+ return rc;
+
+ range->res = request_mem_region(hpa_range->start, len, "cxl_reset");
+ if (!range->res) {
+ pci_err(pdev,
+ "cannot reset while CXL memory range is busy [%#llx-%#llx]\n",
+ hpa_range->start, hpa_range->end);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_request(struct cxl_hdm_range_context *ctx)
+{
+ struct cxl_hdm_range *range;
+ int rc;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ list_for_each_entry(range, &ctx->ranges, list) {
+ rc = cxl_hdm_range_request(range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_range_flush_cache(struct cxl_hdm_range *range)
+{
+ struct pci_dev *pdev = range->pdev;
+ const struct range *hpa_range = &range->hpa_range;
+ u64 len;
+ int rc;
+
+ rc = cxl_hdm_range_len(pdev, hpa_range, &len);
+ if (rc)
+ return rc;
+
+ rc = cpu_cache_invalidate_memregion(hpa_range->start, len);
+ if (rc)
+ pci_err(pdev,
+ "failed to invalidate CPU cache [%#llx-%#llx]: %d\n",
+ hpa_range->start, hpa_range->end, rc);
+
+ return rc;
+}
+
+static int cxl_hdm_ranges_flush_cpu_caches(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev)
+{
+ struct cxl_hdm_range *range;
+ int rc;
+
+ if (list_empty(&ctx->ranges))
+ return 0;
+
+ if (!cpu_cache_has_invalidate_memregion()) {
+ pci_warn(pdev,
+ "CPU cache synchronization unavailable; continuing without cache invalidation\n");
+ return 0;
+ }
+
+ list_for_each_entry(range, &ctx->ranges, list) {
+ rc = cxl_hdm_range_flush_cache(range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_prepare(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev)
+{
+ int rc;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ rc = cxl_hdm_ranges_collect(ctx, pdev);
+ if (rc)
+ return rc;
+
+ rc = cxl_hdm_ranges_request(ctx);
+ if (rc)
+ return rc;
+
+ return cxl_hdm_ranges_flush_cpu_caches(ctx, pdev);
+}
+
static int cxl_reset_dvsec(struct pci_dev *pdev)
{
int dvsec, rc;
@@ -662,7 +889,9 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
+ struct cxl_hdm_range_context range_ctx;
int dvsec;
+ int rc;
dvsec = cxl_reset_dvsec(pdev);
if (dvsec < 0)
@@ -671,5 +900,14 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (probe)
return 0;
- return cxl_reset_execute(pdev, dvsec);
+ cxl_hdm_range_context_init(&range_ctx);
+
+ scoped_guard(rwsem_write, &cxl_rwsem.region) {
+ rc = cxl_hdm_ranges_prepare(&range_ctx, pdev);
+ if (!rc)
+ rc = cxl_reset_execute(pdev, dvsec);
+ }
+
+ cxl_hdm_range_context_destroy(&range_ctx);
+ return rc;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 07/10] PCI/CXL: Discover the CXL reset scope
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (5 preceding siblings ...)
2026-07-03 22:05 ` [PATCH v8 06/10] cxl: Validate HDM ranges before CXL reset Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-07 1:45 ` Richard Cheng
2026-07-03 22:05 ` [PATCH v8 08/10] cxl: Restore CXL HDM state after PCI reset Srirangan Madhavan
` (5 subsequent siblings)
12 siblings, 1 reply; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Add reset context support to validate that CXL Reset is function-scoped
before advertising it as a PCI reset method. Use the Non-CXL Function
Map, ARI/devfn rules, and CXL.cache/mem capability bits to reject reset
when another same-scope function would also be affected.
If the Function Map cannot be read, warn and conservatively treat all
candidate same-scope functions as CXL-capable for scope validation.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/reset.c | 153 +++++++++++++++++++++++++++++++++-
include/uapi/linux/pci_regs.h | 1 +
2 files changed, 152 insertions(+), 2 deletions(-)
diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
index fb741b66e502..8e80e7588046 100644
--- a/drivers/cxl/core/reset.c
+++ b/drivers/cxl/core/reset.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2026 NVIDIA Corporation. All rights reserved. */
+#include <linux/bitmap.h>
#include <linux/delay.h>
#include <linux/bug.h>
#include <linux/bitfield.h>
@@ -469,6 +470,21 @@ static const u32 cxl_reset_timeout_ms[] = {
#define CXL_CACHE_WBI_TIMEOUT_US 100000
#define CXL_CACHE_WBI_POLL_US 100
+/* CXL r4.0 sec 8.1.4 defines 256 bits of Non-CXL Function Map. */
+#define CXL_RESET_MAX_FUNCTIONS 256
+#define CXL_RESET_FUNCTION_MAP_REGS (CXL_RESET_MAX_FUNCTIONS / 32)
+
+struct cxl_reset_context {
+ struct pci_dev *target;
+};
+
+struct cxl_reset_walk_context {
+ struct cxl_reset_context *ctx;
+ DECLARE_BITMAP(non_cxl_func_map, CXL_RESET_MAX_FUNCTIONS);
+ bool ari;
+ int rc;
+};
+
struct cxl_hdm_range {
struct list_head list;
struct pci_dev *pdev;
@@ -480,6 +496,137 @@ struct cxl_hdm_range_context {
struct list_head ranges;
};
+static void cxl_reset_context_init(struct cxl_reset_context *ctx,
+ struct pci_dev *pdev)
+{
+ *ctx = (struct cxl_reset_context) {
+ .target = pdev,
+ };
+}
+
+static void cxl_reset_read_non_cxl_func_map(struct pci_dev *pdev,
+ unsigned long *map)
+{
+ u32 words[CXL_RESET_FUNCTION_MAP_REGS];
+ int dvsec, reg;
+
+ bitmap_zero(map, CXL_RESET_MAX_FUNCTIONS);
+
+ dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_FUNCTION_MAP);
+ if (!dvsec)
+ return;
+
+ for (reg = 0; reg < CXL_RESET_FUNCTION_MAP_REGS; reg++) {
+ int rc;
+
+ rc = pci_read_config_dword(pdev,
+ dvsec + PCI_DVSEC_CXL_FUNCTION_MAP_REG +
+ reg * sizeof(u32), &words[reg]);
+ if (rc) {
+ pci_warn(pdev,
+ "failed to read Non-CXL Function Map; treating same-scope functions as CXL\n");
+ bitmap_zero(map, CXL_RESET_MAX_FUNCTIONS);
+ return;
+ }
+ }
+
+ bitmap_from_arr32(map, words, CXL_RESET_MAX_FUNCTIONS);
+}
+
+static int cxl_reset_func_map_bit(struct pci_dev *sibling, bool ari)
+{
+ if (ari)
+ return sibling->devfn;
+
+ /*
+ * Without ARI, the Function Map is organized as 32 device slots per
+ * conventional 3-bit function number.
+ */
+ return PCI_FUNC(sibling->devfn) * 32 + PCI_SLOT(sibling->devfn);
+}
+
+static int cxl_reset_read_cxl_cap(struct pci_dev *pdev, u16 *cap)
+{
+ int dvsec, rc;
+
+ dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!dvsec)
+ return -ENODEV;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, cap);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ pci_warn(pdev, "failed to read CXL capability: %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_reset_has_cache_or_mem(struct pci_dev *pdev)
+{
+ u16 cap;
+ int rc;
+
+ rc = cxl_reset_read_cxl_cap(pdev, &cap);
+ if (rc == -ENODEV)
+ return 0;
+ if (rc)
+ return rc;
+
+ return !!(cap & (PCI_DVSEC_CXL_CACHE_CAPABLE |
+ PCI_DVSEC_CXL_MEM_CAPABLE));
+}
+
+static int cxl_reset_validate_function_scope(struct pci_dev *sibling,
+ void *data)
+{
+ struct cxl_reset_walk_context *wctx = data;
+ struct cxl_reset_context *ctx = wctx->ctx;
+ struct pci_dev *pdev = ctx->target;
+ int fn, rc;
+
+ if (sibling == pdev)
+ return 0;
+
+ if (sibling->bus != pdev->bus)
+ return 0;
+
+ if (!wctx->ari && PCI_SLOT(sibling->devfn) != PCI_SLOT(pdev->devfn))
+ return 0;
+
+ fn = cxl_reset_func_map_bit(sibling, wctx->ari);
+ if (test_bit(fn, wctx->non_cxl_func_map))
+ return 0;
+
+ rc = cxl_reset_has_cache_or_mem(sibling);
+ if (rc < 0) {
+ wctx->rc = rc;
+ return rc;
+ }
+ if (!rc)
+ return 0;
+
+ wctx->rc = -ENOTTY;
+ return wctx->rc;
+}
+
+static int cxl_reset_validate_function_scoped(struct cxl_reset_context *ctx)
+{
+ struct pci_dev *pdev = ctx->target;
+ struct cxl_reset_walk_context wctx = {
+ .ctx = ctx,
+ .ari = pci_ari_enabled(pdev->bus),
+ };
+
+ cxl_reset_read_non_cxl_func_map(pdev, wctx.non_cxl_func_map);
+ pci_walk_bus(pdev->bus, cxl_reset_validate_function_scope, &wctx);
+
+ return wctx.rc;
+}
+
static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
{
INIT_LIST_HEAD(&ctx->ranges);
@@ -890,6 +1037,7 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
struct cxl_hdm_range_context range_ctx;
+ struct cxl_reset_context ctx;
int dvsec;
int rc;
@@ -897,8 +1045,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (dvsec < 0)
return dvsec;
+ cxl_reset_context_init(&ctx, pdev);
if (probe)
- return 0;
+ return cxl_reset_validate_function_scoped(&ctx);
cxl_hdm_range_context_init(&range_ctx);
@@ -907,7 +1056,7 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (!rc)
rc = cxl_reset_execute(pdev, dvsec);
}
-
+out:
cxl_hdm_range_context_destroy(&range_ctx);
return rc;
}
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
index 194ae56b4404..7fc1d34fcce7 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1380,6 +1380,7 @@
/* CXL r4.0, 8.1.4: Non-CXL Function Map DVSEC */
#define PCI_DVSEC_CXL_FUNCTION_MAP 2
+#define PCI_DVSEC_CXL_FUNCTION_MAP_REG 0x0C
/* CXL r4.0, 8.1.5: Extensions DVSEC for Ports */
#define PCI_DVSEC_CXL_PORT 3
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 08/10] cxl: Restore CXL HDM state after PCI reset
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (6 preceding siblings ...)
2026-07-03 22:05 ` [PATCH v8 07/10] PCI/CXL: Discover the CXL reset scope Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-07 1:34 ` Richard Cheng
2026-07-03 22:05 ` [PATCH v8 09/10] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
` (4 subsequent siblings)
12 siblings, 1 reply; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
After CXL reset, restore PCI config state enough to reach HDM MMIO,
restore cached global and per-decoder HDM state, and then run the normal
PCI restore callbacks.
Keep the target IOMMU reset block active until HDM restore completes so
Bus Master Enable cannot reopen DMA before decoder state is valid.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/hdm.c | 4 +
drivers/cxl/core/reset.c | 320 +++++++++++++++++++++++++++++++++++++--
2 files changed, 310 insertions(+), 14 deletions(-)
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 7a1ade846c9c..96f0de889073 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -137,6 +137,10 @@ static void cxl_hdm_info_set_decoder(struct cxl_hdm *cxlhdm,
if (!info || cxld->id >= info->decoder_count)
return;
+ if (cxlhdm->regs.hdm_decoder)
+ info->global_ctrl = readl(cxlhdm->regs.hdm_decoder +
+ CXL_HDM_DECODER_CTRL_OFFSET);
+
if (cxld->flags & CXL_DECODER_F_ENABLE)
info->settings[cxld->id] = cxld->settings;
else
diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
index 8e80e7588046..cfb9b5101c28 100644
--- a/drivers/cxl/core/reset.c
+++ b/drivers/cxl/core/reset.c
@@ -83,6 +83,21 @@ static int cxld_await_commit(void __iomem *hdm, int id)
return -ETIMEDOUT;
}
+static int cxld_await_uncommit(void __iomem *hdm, int id)
+{
+ u32 ctrl;
+ int i;
+
+ for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (!FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
+ return 0;
+ fsleep(1000);
+ }
+
+ return -ETIMEDOUT;
+}
+
static void setup_hw_decoder(struct cxl_decoder_settings *settings,
void __iomem *hdm)
{
@@ -92,6 +107,8 @@ static void setup_hw_decoder(struct cxl_decoder_settings *settings,
u32 ctrl;
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ ctrl &= ~(CXL_HDM_DECODER0_CTRL_COMMIT |
+ CXL_HDM_DECODER0_CTRL_COMMIT_ERROR);
cxld_set_interleave(settings, &ctrl);
cxld_set_type(settings, &ctrl);
base = settings->hpa_range.start;
@@ -249,6 +266,31 @@ static void __iomem *cxl_pci_hdm_map(struct pci_dev *pdev,
return hdm;
}
+static void __iomem *cxl_pci_hdm_ioremap_current(struct pci_dev *pdev,
+ int bar,
+ resource_size_t offset,
+ resource_size_t size)
+{
+ resource_size_t hdm_start, bar_len;
+ void __iomem *hdm;
+
+ if (bar < 0 || bar >= PCI_STD_NUM_BARS || !size)
+ return ERR_PTR(-EINVAL);
+
+ bar_len = pci_resource_len(pdev, bar);
+ if (!bar_len || offset > bar_len || size > bar_len - offset)
+ return ERR_PTR(-ENODEV);
+
+ hdm_start = pci_resource_start(pdev, bar) + offset;
+ hdm = ioremap(hdm_start, size);
+ if (!hdm) {
+ pci_err(pdev, "failed to remap CXL HDM decoder registers\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return hdm;
+}
+
static void cxl_pci_hdm_read_decoder_state(struct cxl_hdm_decoder_state *state,
void __iomem *hdm, int id)
{
@@ -261,6 +303,40 @@ static void cxl_pci_hdm_read_decoder_state(struct cxl_hdm_decoder_state *state,
state->target_high = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(id));
}
+static int cxl_hdm_enable_mem(struct pci_dev *pdev, u16 *command,
+ bool *restore_command)
+{
+ int rc;
+
+ *restore_command = false;
+
+ rc = pci_read_config_word(pdev, PCI_COMMAND, command);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ if (*command & PCI_COMMAND_MEMORY)
+ return 0;
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND,
+ *command | PCI_COMMAND_MEMORY);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ *restore_command = true;
+ return 0;
+}
+
+static int cxl_hdm_restore_command(struct pci_dev *pdev, u16 command)
+{
+ int rc;
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ return 0;
+}
+
static int cxl_pci_hdm_read_decoder(struct pci_dev *pdev,
struct cxl_hdm_decoder_state *state,
struct cxl_decoder_settings *settings,
@@ -455,6 +531,164 @@ void pci_cxl_hdm_init(struct pci_dev *pdev)
pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
}
+static int cxl_hdm_decoder_uncommit(struct pci_dev *pdev, void __iomem *hdm,
+ int id, bool *locked_committed)
+{
+ u32 ctrl;
+ int rc;
+
+ *locked_committed = false;
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK) {
+ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) {
+ pci_dbg(pdev,
+ "CXL HDM decoder %d retained locked committed state\n",
+ id);
+ *locked_committed = true;
+ return 0;
+ }
+
+ pci_err(pdev, "CXL HDM decoder %d is locked\n", id);
+ return -EBUSY;
+ }
+
+ if (!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED))
+ return 0;
+
+ ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+
+ rc = cxld_await_uncommit(hdm, id);
+ if (rc)
+ pci_err(pdev, "CXL HDM decoder %d uncommit failed: %d\n",
+ id, rc);
+
+ return rc;
+}
+
+static void cxl_restore_hdm_decoder_state(struct cxl_hdm_decoder_state *state,
+ void __iomem *hdm, int id)
+{
+ u32 ctrl = state->ctrl;
+
+ ctrl &= ~(CXL_HDM_DECODER0_CTRL_COMMIT |
+ CXL_HDM_DECODER0_CTRL_COMMITTED |
+ CXL_HDM_DECODER0_CTRL_COMMIT_ERROR);
+
+ writel(state->base_high, hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
+ writel(state->base_low, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ writel(state->size_high, hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
+ writel(state->size_low, hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ writel(state->target_high, hdm + CXL_HDM_DECODER0_TL_HIGH(id));
+ writel(state->target_low, hdm + CXL_HDM_DECODER0_TL_LOW(id));
+ /* Ensure raw decoder fields are visible before restoring control. */
+ wmb();
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+
+static int cxl_restore_hdm_decoder(struct pci_dev *pdev,
+ struct cxl_hdm_decoder_state *state,
+ struct cxl_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ bool locked_committed;
+ int rc;
+
+ rc = cxl_hdm_decoder_uncommit(pdev, hdm, settings->id,
+ &locked_committed);
+ if (rc)
+ return rc;
+ if (locked_committed)
+ return 0;
+
+ cxl_restore_hdm_decoder_state(state, hdm, settings->id);
+
+ if (!(settings->flags & CXL_DECODER_F_ENABLE))
+ return 0;
+
+ rc = cxl_commit(settings, hdm);
+ if (rc)
+ pci_err(pdev, "CXL HDM decoder %d restore failed: %d\n",
+ settings->id, rc);
+
+ return rc;
+}
+
+static int cxl_restore_hdm(struct pci_dev *pdev)
+{
+ struct cxl_hdm_decoder_state *decoder_state __free(kfree) = NULL;
+ struct cxl_decoder_settings *settings __free(kfree) = NULL;
+ struct cxl_hdm_info *info;
+ resource_size_t hdm_offset;
+ resource_size_t hdm_size;
+ void __iomem *hdm;
+ int decoder_count;
+ int first_rc = 0;
+ u32 global_ctrl;
+ bool restore_command = false;
+ u16 command;
+ int hdm_bar;
+ int rc;
+
+ scoped_guard(rwsem_read, &cxl_rwsem.dpa) {
+ info = pdev->hdm;
+ if (!info)
+ return 0;
+
+ decoder_count = info->decoder_count;
+ hdm_bar = info->hdm_bar;
+ hdm_offset = info->hdm_offset;
+ hdm_size = info->hdm_size;
+ global_ctrl = info->global_ctrl;
+ settings = kmemdup_array(info->settings, decoder_count,
+ sizeof(*settings), GFP_KERNEL);
+ if (!settings)
+ return -ENOMEM;
+ decoder_state = kmemdup_array(info->decoder_state,
+ decoder_count,
+ sizeof(*decoder_state),
+ GFP_KERNEL);
+ if (!decoder_state)
+ return -ENOMEM;
+ }
+
+ rc = cxl_hdm_enable_mem(pdev, &command, &restore_command);
+ if (rc)
+ return rc;
+
+ hdm = cxl_pci_hdm_ioremap_current(pdev, hdm_bar, hdm_offset, hdm_size);
+ if (IS_ERR(hdm)) {
+ first_rc = PTR_ERR(hdm);
+ goto out_restore_command;
+ }
+
+ /*
+ * Restore global HDM control before per-decoder commit. PCI config
+ * memory decoding is enabled for MMIO access, but IOMMU reset blocks
+ * remain active until HDM restore completes.
+ */
+ writel(global_ctrl, hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+
+ for (int i = 0; i < decoder_count; i++) {
+ int rc;
+
+ rc = cxl_restore_hdm_decoder(pdev, &decoder_state[i],
+ &settings[i], hdm);
+ if (rc && !first_rc)
+ first_rc = rc;
+ }
+
+ iounmap(hdm);
+out_restore_command:
+ if (restore_command) {
+ rc = cxl_hdm_restore_command(pdev, command);
+ if (rc && !first_rc)
+ first_rc = rc;
+ }
+
+ return first_rc;
+}
+
/*
* CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
* Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
@@ -476,6 +710,7 @@ static const u32 cxl_reset_timeout_ms[] = {
struct cxl_reset_context {
struct pci_dev *target;
+ bool target_prepared;
};
struct cxl_reset_walk_context {
@@ -627,6 +862,38 @@ static int cxl_reset_validate_function_scoped(struct cxl_reset_context *ctx)
return wctx.rc;
}
+static void cxl_pci_target_reset_done(struct cxl_reset_context *ctx)
+{
+ if (!ctx->target_prepared)
+ return;
+
+ pci_dev_reset_iommu_done(ctx->target);
+ ctx->target_prepared = false;
+}
+
+static int cxl_pci_target_reset_prepare(struct cxl_reset_context *ctx)
+{
+ struct pci_dev *pdev = ctx->target;
+ int rc;
+
+ if (!pci_wait_for_pending_transaction(pdev))
+ pci_err(pdev, "timed out waiting for pending transactions\n");
+
+ rc = pci_dev_reset_iommu_prepare(pdev);
+ if (rc) {
+ pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
+ return rc;
+ }
+
+ ctx->target_prepared = true;
+ return 0;
+}
+
+static int cxl_restore_hdm_decoders(struct cxl_reset_context *ctx)
+{
+ return cxl_restore_hdm(ctx->target);
+}
+
static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
{
INIT_LIST_HEAD(&ctx->ranges);
@@ -672,9 +939,11 @@ static int cxl_hdm_range_add(struct cxl_hdm_range_context *ctx,
static int cxl_hdm_ranges_collect(struct cxl_hdm_range_context *ctx,
struct pci_dev *pdev)
{
- struct cxl_hdm_info *info = READ_ONCE(pdev->hdm);
+ struct cxl_hdm_info *info;
int rc;
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
if (!info) {
pci_err(pdev, "CXL HDM decoder state unavailable\n");
return -ENXIO;
@@ -824,8 +1093,9 @@ static int cxl_hdm_ranges_flush_cpu_caches(struct cxl_hdm_range_context *ctx,
}
static int cxl_hdm_ranges_prepare(struct cxl_hdm_range_context *ctx,
- struct pci_dev *pdev)
+ struct cxl_reset_context *reset_ctx)
{
+ struct pci_dev *pdev = reset_ctx->target;
int rc;
lockdep_assert_held_write(&cxl_rwsem.region);
@@ -866,6 +1136,25 @@ static int cxl_reset_dvsec(struct pci_dev *pdev)
return dvsec;
}
+static bool cxl_reset_hdm_available(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *info;
+
+ /*
+ * pdev->hdm is owned by the PCI device and released with pci_dev, so
+ * reset-method probes and reset requests can test availability without
+ * a CXL driver bound to the device.
+ */
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ return info && info->hdm_size;
+}
+
+static bool cxl_reset_scope_hdm_available(struct cxl_reset_context *ctx)
+{
+ return cxl_reset_hdm_available(ctx->target);
+}
+
static int cxl_reset_update_ctrl2(struct pci_dev *pdev, int dvsec, u16 set,
u16 clear)
{
@@ -995,18 +1284,9 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
if (rc)
return pcibios_err_to_errno(rc);
- if (!pci_wait_for_pending_transaction(pdev))
- pci_err(pdev, "timed out waiting for pending transactions\n");
-
- rc = pci_dev_reset_iommu_prepare(pdev);
- if (rc) {
- pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
- return rc;
- }
-
rc = cxl_reset_disable_cache(pdev, dvsec, cap);
if (rc)
- goto out;
+ return rc;
cache_disabled = true;
rc = cxl_reset_update_ctrl2(pdev, dvsec, PCI_DVSEC_CXL_INIT_CXL_RST,
@@ -1030,7 +1310,6 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
rc = rc2;
}
- pci_dev_reset_iommu_done(pdev);
return rc;
}
@@ -1051,11 +1330,24 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
cxl_hdm_range_context_init(&range_ctx);
+ if (!cxl_reset_scope_hdm_available(&ctx)) {
+ rc = -ENOTTY;
+ goto out;
+ }
+
+ rc = cxl_pci_target_reset_prepare(&ctx);
+ if (rc)
+ goto out;
+
scoped_guard(rwsem_write, &cxl_rwsem.region) {
- rc = cxl_hdm_ranges_prepare(&range_ctx, pdev);
+ rc = cxl_hdm_ranges_prepare(&range_ctx, &ctx);
if (!rc)
rc = cxl_reset_execute(pdev, dvsec);
+ if (!rc)
+ rc = cxl_restore_hdm_decoders(&ctx);
}
+
+ cxl_pci_target_reset_done(&ctx);
out:
cxl_hdm_range_context_destroy(&range_ctx);
return rc;
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 09/10] PCI/CXL: Expose CXL Reset as a PCI reset method
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (7 preceding siblings ...)
2026-07-03 22:05 ` [PATCH v8 08/10] cxl: Restore CXL HDM state after PCI reset Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 10/10] Documentation/ABI: Document CXL Reset " Srirangan Madhavan
` (3 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Add the CXL Reset helper to the PCI reset-method table so userspace can
select it through the existing reset_method ABI.
Advertise the method for Type 2 CXL devices that report CXL Reset
support in the CXL Device DVSEC and pass the function-scope check. Reset
execution requires cached HDM decoder state for the target so affected
ranges can be validated and HDM programming can be restored. If that
state is unavailable at reset time, return -ENOTTY so PCI can try the
next reset method.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/pci/pci.c | 2 ++
include/linux/pci.h | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index d34266651ad0..7521e705f718 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -33,6 +33,7 @@
#include <asm/dma.h>
#include <linux/aer.h>
#include <linux/bitfield.h>
+#include <cxl/cxl.h>
#include "pci.h"
DEFINE_MUTEX(pci_slot_mutex);
@@ -5060,6 +5061,7 @@ const struct pci_reset_fn_method pci_reset_fn_methods[] = {
{ },
{ pci_dev_specific_reset, .name = "device_specific" },
{ pci_dev_acpi_reset, .name = "acpi" },
+ { cxl_reset_function, .name = "cxl_reset" },
{ pcie_reset_flr, .name = "flr" },
{ pci_af_flr, .name = "af_flr" },
{ pci_pm_reset, .name = "pm" },
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 7db2daf8597c..7d6f1adab27c 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -51,7 +51,7 @@
PCI_STATUS_PARITY)
/* Number of reset methods used in pci_reset_fn_methods array in pci.c */
-#define PCI_NUM_RESET_METHODS 8
+#define PCI_NUM_RESET_METHODS 9
#define PCI_RESET_PROBE true
#define PCI_RESET_DO_RESET false
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v8 10/10] Documentation/ABI: Document CXL Reset PCI reset method
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (8 preceding siblings ...)
2026-07-03 22:05 ` [PATCH v8 09/10] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
@ 2026-07-03 22:05 ` Srirangan Madhavan
2026-07-03 22:08 ` [PATCH RFC] PCI/CXL: Restore HDM state after CXL bus reset Srirangan Madhavan
` (2 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:05 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Document the "cxl_reset" PCI reset_method value for Type 2 CXL devices.
CXL Reset is device scoped, requires affected memory to be idle,
invalidates CPU caches, restores cached HDM decoder state, and does not
request Memory Clear.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
Documentation/ABI/testing/sysfs-bus-pci | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index b767db2c52cb..5ab46a4340ae 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -153,6 +153,20 @@ Description:
"default" enables all supported reset methods in the
default ordering.
+ If present, "cxl_reset" selects CXL Reset for CXL Type 2
+ devices that advertise CXL Reset support and whose reset scope is
+ limited to the selected PCI function. The method is not listed
+ when CXL Reset would affect another CXL.cache or CXL.mem function.
+
+ Before issuing CXL Reset, the kernel quiesces the PCI function,
+ rejects the operation if cached HDM state is unavailable or
+ affected CXL memory is busy, invalidates CPU caches for enabled
+ HDM ranges, disables CXL.cache, and initiates cache write-back
+ where supported. After reset, the kernel restores PCI config
+ state needed to access HDM MMIO, restores cached HDM decoder
+ state, and completes PCI reset recovery. "cxl_reset" does not
+ request CXL Reset Memory Clear.
+
What: /sys/bus/pci/devices/.../reset
Date: July 2009
Contact: Michael S. Tsirkin <mst@redhat.com>
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH RFC] PCI/CXL: Restore HDM state after CXL bus reset
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (9 preceding siblings ...)
2026-07-03 22:05 ` [PATCH v8 10/10] Documentation/ABI: Document CXL Reset " Srirangan Madhavan
@ 2026-07-03 22:08 ` Srirangan Madhavan
2026-07-06 23:13 ` [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Dave Jiang
2026-07-07 5:41 ` Richard Cheng
12 siblings, 0 replies; 18+ messages in thread
From: Srirangan Madhavan @ 2026-07-03 22:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
CXL bus reset can clear HDM global control and decoder programming.
Restore cached HDM state after a successful cxl_bus reset while the
IOMMU reset block remains active.
This intentionally restores only the HDM component decoder state cached
by the CXL reset support. It does not attempt to restore CXL Device DVSEC
range/control registers.
This was exercised with cxl_bus reset on a CXL Type 2 device. Testing
confirmed that CXL Device DVSEC range/control state is a separate restore
class from HDM component decoder state.
This depends on the CXL reset series because it reuses the HDM state cache
and restore helper introduced there
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/reset.c | 5 +++++
drivers/pci/pci.c | 3 +++
include/cxl/cxl.h | 6 ++++++
3 files changed, 14 insertions(+)
diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
index cfb9b5101c28..b889687d69d8 100644
--- a/drivers/cxl/core/reset.c
+++ b/drivers/cxl/core/reset.c
@@ -894,6 +894,11 @@ static int cxl_restore_hdm_decoders(struct cxl_reset_context *ctx)
return cxl_restore_hdm(ctx->target);
}
+int cxl_restore_hdm_after_pci_reset(struct pci_dev *pdev)
+{
+ return cxl_restore_hdm(pdev);
+}
+
static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
{
INIT_LIST_HEAD(&ctx->ranges);
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 7521e705f718..88fa3117e0e7 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -4972,6 +4972,9 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
reg);
+ if (!rc)
+ rc = cxl_restore_hdm_after_pci_reset(dev);
+
pci_dev_reset_iommu_done(dev);
return rc;
}
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index de58f484b7d9..c1019e1b3b27 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -154,6 +154,7 @@ int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm);
#ifdef CONFIG_CXL_HDM
void pci_cxl_hdm_init(struct pci_dev *pdev);
void pci_cxl_hdm_release(struct pci_dev *pdev);
+int cxl_restore_hdm_after_pci_reset(struct pci_dev *pdev);
int cxl_reset_function(struct pci_dev *pdev, bool probe);
#else
static inline void pci_cxl_hdm_init(struct pci_dev *pdev)
@@ -164,6 +165,11 @@ static inline void pci_cxl_hdm_release(struct pci_dev *pdev)
{
}
+static inline int cxl_restore_hdm_after_pci_reset(struct pci_dev *pdev)
+{
+ return 0;
+}
+
static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
return -ENOTTY;
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (10 preceding siblings ...)
2026-07-03 22:08 ` [PATCH RFC] PCI/CXL: Restore HDM state after CXL bus reset Srirangan Madhavan
@ 2026-07-06 23:13 ` Dave Jiang
2026-07-07 5:41 ` Richard Cheng
12 siblings, 0 replies; 18+ messages in thread
From: Dave Jiang @ 2026-07-06 23:13 UTC (permalink / raw)
To: Srirangan Madhavan, Alison Schofield, Bjorn Helgaas, Dan Williams,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra
On 7/3/26 3:04 PM, Srirangan Madhavan wrote:
> Hi folks!
>
> This series adds CXL Reset support for CXL Type 2 devices through the
> existing PCI reset_method ABI. The reset sequence follows the CXL 4.0
> specification [1], including CXL.cache disable, optional cache
> writeback, CXL Reset initiation, ResetComplete polling, and ResetError
> reporting.
>
> The userspace ABI is the existing PCI reset interface:
>
> /sys/bus/pci/devices/.../reset_method
> /sys/bus/pci/devices/.../reset
>
> Userspace can select "cxl_reset" in reset_method and then trigger reset
> through the existing reset attribute.
>
> Following Dan's v6 feedback, this replaces the proposed memdev sysfs ABI
> with the existing PCI reset_method interface.
>
> v8 changes from v7 [2]:
> - Drop the PCI helper export patch.
> - Drop the multi-function sibling coordination patch. cxl_reset is only
> exposed as a function-scoped reset method when the CXL reset scope is
> limited to the target function.
> - Keep the reset-scope discovery needed to reject non-function-scoped
> CXL resets.
> - Cache HDM location as BAR-relative metadata instead of keeping an
> enum-time MMIO mapping.
> - Restore HDM through a temporary mapping based on the current BAR
> assignment after PCI config state is restored.
> - Cache raw HDM decoder register state so uncommitted decoders can be
> restored as uncommitted, while committed decoders are recommitted
> through the normal HDM commit flow.
> - Share HDM decoder decode and validation between normal CXL core
> enumeration and early PCI HDM caching.
> - Put cxl_reset ahead of FLR in reset_method priority because FLR does
> not reset CXL.cache or CXL.mem protocol state.
>
> Motivation:
> -----------
> - Type 2 devices need a CXL-specific reset mechanism beyond existing PCI
> reset methods.
>
> - FLR does not reset CXL.cache or CXL.mem protocol state. CXL Reset is
> the architectural reset mechanism for those protocols.
>
> - The PCI reset_method ABI lets userspace select this narrower CXL reset
> before falling back to broader bus reset methods.
>
> Change Description:
> -------------------
>
> Patch 1: cxl/hdm: Split decoder programming into a reusable helper
> - Move shared decoder settings to include/cxl/cxl.h.
> - Factor low-level HDM register programming into cxl_commit().
>
> Patch 2: cxl/hdm: Cache decoder settings on PCI devices
> - Cache CXL core HDM decoder settings in pci_dev->hdm.
> - Refresh the cache as decoders are enumerated, committed, or reset.
>
> Patch 3: cxl/hdm: Share HDM decoder decode logic
> - Share HDM decoder decode and validation between normal CXL core
> enumeration and early PCI HDM cache setup.
>
> Patch 4: cxl/hdm: Cache endpoint decoder settings during PCI enumeration
> - Snapshot endpoint HDM state during PCI capability initialization.
> - Cache the HDM register locator as BAR-relative metadata.
> - Cache raw decoder register state in addition to committed decoder
> settings.
>
> Patch 5: PCI/CXL: Add CXL Device Reset helper
> - Add the internal DVSEC reset sequence.
> - Disable CXL.cache, perform cache writeback where supported, initiate
> CXL Reset, and wait for completion.
>
> Patch 6: PCI/CXL: Validate HDM ranges before CXL reset
> - Collect enabled cached HDM ranges.
> - Reject reset if affected ranges are busy.
> - Invalidate CPU caches when runtime cache-invalidation support is
> available, otherwise continue after warning.
>
> Patch 7: PCI/CXL: Discover the CXL reset scope
> - Discover whether CXL Reset is function-scoped using the Non-CXL
> Function Map and CXL cache/mem capability bits.
>
> Patch 8: cxl/pci: Restore CXL HDM state after PCI reset
> - Restore cached global and per-decoder HDM state after reset.
> - Re-map HDM registers from the current BAR assignment during restore.
> - Replay raw decoder state for uncommitted decoders and recommit
> decoders that were committed before reset.
>
> Patch 9: PCI/CXL: Expose CXL Reset as a PCI reset method
> - Add "cxl_reset" to the PCI reset_method table for Type 2 reset-capable
> CXL devices.
> - Prioritize cxl_reset ahead of FLR.
>
> Patch 10: Documentation/ABI: Document CXL Reset PCI reset method
> - Document the new reset_method value and reset behavior.
>
> The CPU cache invalidation step depends on
> cpu_cache_invalidate_memregion() support for the affected address ranges.
> If no runtime provider is available, the kernel emits a warning and
> continues after the affected HDM ranges have been reserved.
>
> Example:
>
> echo cxl_reset > /sys/bus/pci/devices/0000:bb:dd.f/reset_method
> echo 1 > /sys/bus/pci/devices/0000:bb:dd.f/reset
>
> Testing:
> - Ran 100 iterations of cxl_reset through the PCI reset sysfs ABI on a
> CXL Type 2 device. All iterations completed successfully and
> ResetComplete was observed.
> - Exercised cxl_bus reset separately with an add-on HDM restore patch.
>
> References:
> [1] https://computeexpresslink.org/wp-content/uploads/2026/02/CXL-Specification_rev4p0_ver1p0_2026February26_clean_evalcopy_v2.pdf
> [2] https://lore.kernel.org/linux-cxl/20260623032453.3404772-1-smadhavan@nvidia.com/
> [3] https://lore.kernel.org/linux-cxl/20260306080026.116789-1-smadhavan@nvidia.com/
>
> Srirangan Madhavan (10):
> cxl/hdm: Split decoder programming into a reusable helper
> cxl/hdm: Cache decoder settings on PCI devices
> cxl/hdm: Share HDM decoder decode logic
> cxl/hdm: Cache endpoint decoder settings during PCI enumeration
> PCI/CXL: Add CXL Device Reset helper
> PCI/CXL: Validate HDM ranges before CXL reset
> PCI/CXL: Discover the CXL reset scope
> cxl/pci: Restore CXL HDM state after PCI reset
> PCI/CXL: Expose CXL Reset as a PCI reset method
> Documentation/ABI: Document CXL Reset PCI reset method
>
> Documentation/ABI/testing/sysfs-bus-pci | 14 +
> drivers/cxl/Kconfig | 4 +
> drivers/cxl/core/Makefile | 2 +-
> drivers/cxl/core/hdm.c | 257 ++---
> drivers/cxl/core/region.c | 6 +-
> drivers/cxl/core/regs.c | 4 +
> drivers/cxl/core/reset.c | 1354 +++++++++++++++++++++++
> drivers/cxl/cxl.h | 43 -
> drivers/pci/pci.c | 2 +
> drivers/pci/probe.c | 3 +
> include/cxl/cxl.h | 102 +-
> include/linux/pci.h | 8 +-
> include/uapi/linux/pci_regs.h | 15 +
> tools/testing/cxl/test/cxl.c | 10 +-
> 14 files changed, 1615 insertions(+), 209 deletions(-)
> create mode 100644 drivers/cxl/core/reset.c
>
> base-commit: 90cf2e0d702c8a132ccbe72e7687f33c04c14658
Hi Srirangan,
I am not able to apply the patch series using b4 with the above base commit.
b4 picks up the last RFC patch as the first patch and fails to apply. However, skipping it b4 is still having trouble. Can you push a public git branch somewhere please? Also in the future the last RFC patch should be labeled 11/11. Thanks!
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v8 01/10] cxl: Split decoder programming into a reusable helper
2026-07-03 22:04 ` [PATCH v8 01/10] cxl: Split decoder programming into a reusable helper Srirangan Madhavan
@ 2026-07-07 0:22 ` Dave Jiang
0 siblings, 0 replies; 18+ messages in thread
From: Dave Jiang @ 2026-07-07 0:22 UTC (permalink / raw)
To: Srirangan Madhavan, Alison Schofield, Bjorn Helgaas, Dan Williams,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson, Dan Williams,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra
On 7/3/26 3:04 PM, Srirangan Madhavan wrote:
> Move common HDM decoder settings to include/cxl/cxl.h and route the
> register programming sequence through cxl_commit(). This lets reset code
> restore cached HDM state without depending on private cxl_core types while
> keeping hdm.c in charge of the existing commit policy checks.
>
> Build the low-level HDM helper under CONFIG_CXL_HDM so it is available even
> when cxl_core is modular.
Maybe this patch would be cleaner to review if there's a prep patch that move things that needs to be moved without the additional changes as well.
>
> Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
> ---
> drivers/cxl/Kconfig | 4 ++
> drivers/cxl/core/Makefile | 1 +
> drivers/cxl/core/hdm.c | 122 ++++-------------------------------
> drivers/cxl/core/region.c | 6 +-
> drivers/cxl/core/reset.c | 118 +++++++++++++++++++++++++++++++++
> drivers/cxl/cxl.h | 43 ------------
> include/cxl/cxl.h | 55 +++++++++++++++-
> tools/testing/cxl/test/cxl.c | 10 +--
> 8 files changed, 197 insertions(+), 162 deletions(-)
> create mode 100644 drivers/cxl/core/reset.c
>
> diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
> index 80aeb0d556bd..87d719ea1e14 100644
> --- a/drivers/cxl/Kconfig
> +++ b/drivers/cxl/Kconfig
> @@ -6,6 +6,7 @@ menuconfig CXL_BUS
> select FW_UPLOAD
> select PCI_DOE
> select FIRMWARE_TABLE
> + select CXL_HDM
> select NUMA_KEEP_MEMINFO if NUMA_MEMBLKS
> select FWCTL if CXL_FEATURES
> help
> @@ -243,4 +244,7 @@ config CXL_ATL
> depends on CXL_REGION
> depends on ACPI_PRMT && AMD_NB
>
> +config CXL_HDM
> + bool
> +
> endif
> diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
> index ce7213818d3c..dc075cee0450 100644
> --- a/drivers/cxl/core/Makefile
> +++ b/drivers/cxl/core/Makefile
> @@ -1,5 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
> obj-$(CONFIG_CXL_BUS) += cxl_core.o
> +obj-$(CONFIG_CXL_HDM) += reset.o
> obj-$(CONFIG_CXL_SUSPEND) += suspend.o
>
> ccflags-y += -I$(srctree)/drivers/cxl
> diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
> index 0c80b76a5f9b..fa978c297546 100644
> --- a/drivers/cxl/core/hdm.c
> +++ b/drivers/cxl/core/hdm.c
> @@ -16,11 +16,6 @@
> * for enumerating these registers and capabilities.
> */
>
> -struct cxl_rwsem cxl_rwsem = {
> - .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
> - .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
> -};
> -
> static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld)
> {
> int rc;
> @@ -255,11 +250,11 @@ static void __cxl_dpa_release(struct cxl_endpoint_decoder *cxled)
> lockdep_assert_held_write(&cxl_rwsem.dpa);
>
> /* save @skip_start, before @res is released */
> - skip_start = res->start - cxled->skip;
> + skip_start = res->start - cxled->cxld.skip;
> __release_region(&cxlds->dpa_res, res->start, resource_size(res));
> - if (cxled->skip)
> - release_skip(cxlds, skip_start, cxled->skip);
> - cxled->skip = 0;
> + if (cxled->cxld.skip)
> + release_skip(cxlds, skip_start, cxled->cxld.skip);
> + cxled->cxld.skip = 0;
> cxled->dpa_res = NULL;
> put_device(&cxled->cxld.dev);
> port->hdm_end--;
> @@ -388,7 +383,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
> return -EBUSY;
> }
> cxled->dpa_res = res;
> - cxled->skip = skipped;
> + cxled->cxld.skip = skipped;
>
> /*
> * When allocating new capacity, ->part is already set, when
> @@ -679,35 +674,6 @@ int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
> return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
> }
>
> -static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
> -{
> - u16 eig;
> - u8 eiw;
> -
> - /*
> - * Input validation ensures these warns never fire, but otherwise
> - * suppress unititalized variable usage warnings.
> - */
> - if (WARN_ONCE(ways_to_eiw(cxld->interleave_ways, &eiw),
> - "invalid interleave_ways: %d\n", cxld->interleave_ways))
> - return;
> - if (WARN_ONCE(granularity_to_eig(cxld->interleave_granularity, &eig),
> - "invalid interleave_granularity: %d\n",
> - cxld->interleave_granularity))
> - return;
> -
> - u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
> - u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
> - *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
> -}
> -
> -static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
> -{
> - u32p_replace_bits(ctrl,
> - !!(cxld->target_type == CXL_DECODER_HOSTONLYMEM),
> - CXL_HDM_DECODER0_CTRL_HOSTONLY);
> -}
> -
> static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
> {
> struct cxl_dport **t = &cxlsd->target[0];
> @@ -730,73 +696,6 @@ static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
> *tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
> }
>
> -/*
> - * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set
> - * committed or error within 10ms, but just be generous with 20ms to account for
> - * clock skew and other marginal behavior
> - */
> -#define COMMIT_TIMEOUT_MS 20
> -static int cxld_await_commit(void __iomem *hdm, int id)
> -{
> - u32 ctrl;
> - int i;
> -
> - for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
> - ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> - if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
> - ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
> - writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> - return -EIO;
> - }
> - if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
> - return 0;
> - fsleep(1000);
> - }
> -
> - return -ETIMEDOUT;
> -}
> -
> -static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
> -{
> - int id = cxld->id;
> - u64 base, size;
> - u32 ctrl;
> -
> - /* common decoder settings */
> - ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
> - cxld_set_interleave(cxld, &ctrl);
> - cxld_set_type(cxld, &ctrl);
> - base = cxld->hpa_range.start;
> - size = range_len(&cxld->hpa_range);
> -
> - writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
> - writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
> - writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
> - writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
> -
> - if (is_switch_decoder(&cxld->dev)) {
> - struct cxl_switch_decoder *cxlsd =
> - to_cxl_switch_decoder(&cxld->dev);
> - void __iomem *tl_hi = hdm + CXL_HDM_DECODER0_TL_HIGH(id);
> - void __iomem *tl_lo = hdm + CXL_HDM_DECODER0_TL_LOW(id);
> - u64 targets;
> -
> - cxlsd_set_targets(cxlsd, &targets);
> - writel(upper_32_bits(targets), tl_hi);
> - writel(lower_32_bits(targets), tl_lo);
> - } else {
> - struct cxl_endpoint_decoder *cxled =
> - to_cxl_endpoint_decoder(&cxld->dev);
> - void __iomem *sk_hi = hdm + CXL_HDM_DECODER0_SKIP_HIGH(id);
> - void __iomem *sk_lo = hdm + CXL_HDM_DECODER0_SKIP_LOW(id);
> -
> - writel(upper_32_bits(cxled->skip), sk_hi);
> - writel(lower_32_bits(cxled->skip), sk_lo);
> - }
> -
> - writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> -}
> -
> static int cxl_decoder_commit(struct cxl_decoder *cxld)
> {
> struct cxl_port *port = to_cxl_port(cxld->dev.parent);
> @@ -834,17 +733,20 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
> }
> }
>
> - scoped_guard(rwsem_read, &cxl_rwsem.dpa)
> - setup_hw_decoder(cxld, hdm);
> + if (is_switch_decoder(&cxld->dev)) {
> + struct cxl_switch_decoder *cxlsd =
> + to_cxl_switch_decoder(&cxld->dev);
> +
> + cxlsd_set_targets(cxlsd, &cxld->targets);
> + }
>
> - rc = cxld_await_commit(hdm, cxld->id);
> + rc = cxl_commit(&cxld->settings, hdm);
> if (rc) {
> dev_dbg(&port->dev, "%s: error %d committing decoder\n",
> dev_name(&cxld->dev), rc);
> return rc;
> }
> port->commit_end++;
> - cxld->flags |= CXL_DECODER_F_ENABLE;
>
> return 0;
> }
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index ce99f0650764..00f79b4862cb 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
> @@ -2954,9 +2954,9 @@ static int poison_by_decoder(struct device *dev, void *arg)
> cxlds = cxlmd->cxlds;
> mode = cxlds->part[cxled->part].mode;
>
> - if (cxled->skip) {
> - offset = cxled->dpa_res->start - cxled->skip;
> - length = cxled->skip;
> + if (cxled->cxld.skip) {
> + offset = cxled->dpa_res->start - cxled->cxld.skip;
> + length = cxled->cxld.skip;
> rc = cxl_mem_get_poison(cxlmd, offset, length, NULL);
> if (rc == -EFAULT && mode == CXL_PARTMODE_RAM)
> rc = 0;
> diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
> new file mode 100644
> index 000000000000..14f024098e82
> --- /dev/null
> +++ b/drivers/cxl/core/reset.c
While I can't come up with a better name for this file, putting decoder bits in reset.c just doesn't seem right. There has to be a better name to represent what's in this file. Especially against CONFIG_CXL_HDM. Maybe hdm_lib.c and CONFIG_CXL_HDM_LIB? idk....
> @@ -0,0 +1,118 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright(c) 2026 NVIDIA Corporation. All rights reserved. */
> +#include <linux/delay.h>
> +#include <linux/bug.h>
> +#include <linux/errno.h>
> +#include <linux/export.h>
> +#include <linux/kernel.h>
> +
> +#include "cxl.h"
> +#include "core.h"
> +
> +struct cxl_rwsem cxl_rwsem = {
> + .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
> + .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
> +};
> +EXPORT_SYMBOL_FOR_MODULES(cxl_rwsem, "cxl_core");
> +
> +static void cxld_set_interleave(struct cxl_decoder_settings *settings, u32 *ctrl)
> +{
> + u16 eig;
> + u8 eiw;
> +
> + /*
> + * Input validation ensures these warns never fire, but otherwise
> + * suppress uninitialized variable usage warnings.
> + */
> + if (WARN_ONCE(ways_to_eiw(settings->interleave_ways, &eiw),
> + "invalid interleave_ways: %d\n",
> + settings->interleave_ways))
> + return;
> + if (WARN_ONCE(granularity_to_eig(settings->interleave_granularity, &eig),
> + "invalid interleave_granularity: %d\n",
> + settings->interleave_granularity))
> + return;
> +
> + u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
> + u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
> + *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
> +}
> +
> +static void cxld_set_type(struct cxl_decoder_settings *settings, u32 *ctrl)
> +{
> + u32p_replace_bits(ctrl,
> + !!(settings->target_type == CXL_DECODER_HOSTONLYMEM),
> + CXL_HDM_DECODER0_CTRL_HOSTONLY);
> +}
> +
> +/*
> + * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set
> + * committed or error within 10ms, but just be generous with 20ms to account for
> + * clock skew and other marginal behavior.
> + */
> +#define COMMIT_TIMEOUT_MS 20
> +static int cxld_await_commit(void __iomem *hdm, int id)
> +{
> + u32 ctrl;
> + int i;
> +
> + for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
> + ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> + if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
> + ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
> + writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> + return -EIO;
> + }
> + if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
> + return 0;
> + fsleep(1000);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +static void setup_hw_decoder(struct cxl_decoder_settings *settings,
> + void __iomem *hdm)
> +{
> + int id = settings->id;
> + u64 target_or_skip;
> + u64 base, size;
> + u32 ctrl;
> +
> + ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> + cxld_set_interleave(settings, &ctrl);
> + cxld_set_type(settings, &ctrl);
> + base = settings->hpa_range.start;
> + size = range_len(&settings->hpa_range);
> + target_or_skip = settings->targets;
> +
> + writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
> + writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
> + writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
> + writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
> + /* Target-list and endpoint-skip registers alias the same slot. */
> + writel(upper_32_bits(target_or_skip),
> + hdm + CXL_HDM_DECODER0_TL_HIGH(id));
> + writel(lower_32_bits(target_or_skip),
> + hdm + CXL_HDM_DECODER0_TL_LOW(id));
> +
> + writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> +}
> +
> +int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm)
> +{
> + int rc;
> +
> + scoped_guard(rwsem_read, &cxl_rwsem.dpa) {
> + setup_hw_decoder(settings, hdm);
> + }
> +
> + rc = cxld_await_commit(hdm, settings->id);
> + if (rc)
> + return rc;
> +
> + settings->flags |= CXL_DECODER_F_ENABLE;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_FOR_MODULES(cxl_commit, "cxl_core");
> diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
> index f43abd1903ce..b433262ab256 100644
> --- a/drivers/cxl/cxl.h
> +++ b/drivers/cxl/cxl.h
> @@ -252,49 +252,8 @@ int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
> #define CXL_DECODER_F_NORMALIZED_ADDRESSING BIT(6)
> #define CXL_DECODER_F_RESET_MASK (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK)
>
> -enum cxl_decoder_type {
> - CXL_DECODER_DEVMEM = 2,
> - CXL_DECODER_HOSTONLYMEM = 3,
> -};
> -
> -/*
> - * Current specification goes up to 8, double that seems a reasonable
> - * software max for the foreseeable future
> - */
> -#define CXL_DECODER_MAX_INTERLEAVE 16
> -
> #define CXL_QOS_CLASS_INVALID -1
>
> -/**
> - * struct cxl_decoder - Common CXL HDM Decoder Attributes
> - * @dev: this decoder's device
> - * @id: kernel device name id
> - * @hpa_range: Host physical address range mapped by this decoder
> - * @interleave_ways: number of cxl_dports in this decode
> - * @interleave_granularity: data stride per dport
> - * @target_type: accelerator vs expander (type2 vs type3) selector
> - * @region: currently assigned region for this decoder
> - * @flags: memory type capabilities and locking
> - * @target_map: cached copy of hardware port-id list, available at init
> - * before all @dport objects have been instantiated. While
> - * dport id is 8bit, CFMWS interleave targets are 32bits.
> - * @commit: device/decoder-type specific callback to commit settings to hw
> - * @reset: device/decoder-type specific callback to reset hw settings
> -*/
> -struct cxl_decoder {
> - struct device dev;
> - int id;
> - struct range hpa_range;
> - int interleave_ways;
> - int interleave_granularity;
> - enum cxl_decoder_type target_type;
> - struct cxl_region *region;
> - unsigned long flags;
> - u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
> - int (*commit)(struct cxl_decoder *cxld);
> - void (*reset)(struct cxl_decoder *cxld);
> -};
> -
> /*
> * Track whether this decoder is free for userspace provisioning, reserved for
> * region autodiscovery, whether it is started connecting (awaiting other
> @@ -310,7 +269,6 @@ enum cxl_decoder_state {
> * struct cxl_endpoint_decoder - Endpoint / SPA to DPA decoder
> * @cxld: base cxl_decoder_object
> * @dpa_res: actively claimed DPA span of this decoder
> - * @skip: offset into @dpa_res where @cxld.hpa_range maps
> * @state: autodiscovery state
> * @part: partition index this decoder maps
> * @pos: interleave position in @cxld.region
> @@ -318,7 +276,6 @@ enum cxl_decoder_state {
> struct cxl_endpoint_decoder {
> struct cxl_decoder cxld;
> struct resource *dpa_res;
> - resource_size_t skip;
> enum cxl_decoder_state state;
> int part;
> int pos;
> diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
> index 016c74fb747c..757d916a6330 100644
> --- a/include/cxl/cxl.h
> +++ b/include/cxl/cxl.h
> @@ -5,8 +5,10 @@
> #ifndef __CXL_CXL_H__
> #define __CXL_CXL_H__
>
> +#include <linux/device.h>
> #include <linux/node.h>
> #include <linux/ioport.h>
> +#include <linux/range.h>
> #include <cxl/mailbox.h>
>
> /**
> @@ -23,7 +25,56 @@ enum cxl_devtype {
> CXL_DEVTYPE_CLASSMEM,
> };
>
> -struct device;
> +struct cxl_region;
> +
> +enum cxl_decoder_type {
> + CXL_DECODER_DEVMEM = 2,
> + CXL_DECODER_HOSTONLYMEM = 3,
> +};
> +
> +/*
> + * Current specification goes up to 8, double that seems a reasonable
> + * software max for the foreseeable future
> + */
> +#define CXL_DECODER_MAX_INTERLEAVE 16
> +
> +/**
> + * struct cxl_decoder - Common CXL HDM Decoder Attributes
> + * @dev: this decoder's device
> + * @id: kernel device name id
> + * @hpa_range: Host physical address range mapped by this decoder
> + * @skip: offset into @dpa_res where @cxld.hpa_range maps (endpoint)
> + * @targets: interleave position to dport mapping (switch)
> + * @interleave_ways: number of cxl_dports in this decode
> + * @interleave_granularity: data stride per dport
> + * @target_type: accelerator vs expander (type2 vs type3) selector
> + * @flags: memory type capabilities and locking
> + * @region: currently assigned region for this decoder
> + * @target_map: cached copy of hardware port-id list, available at init
> + * before all @dport objects have been instantiated. While
> + * dport id is 8bit, CFMWS interleave targets are 32bits.
> + * @commit: device/decoder-type specific callback to commit settings to hw
> + * @reset: device/decoder-type specific callback to reset hw settings
> + */
> +struct cxl_decoder {
> + struct device dev;
> +
> + struct_group_tagged(cxl_decoder_settings, settings, int id;
> + struct range hpa_range;
> + union {
> + u64 skip;
> + u64 targets;
> + };
I don't like the moving of 'skip' from cxled to cxld just for convenience. It takes away of using skip intentionally with an endpoint decoder. Not only that, the type is changed. Same with targets.
DJ
> + int interleave_ways;
> + int interleave_granularity;
> + enum cxl_decoder_type target_type;
> + unsigned long flags;
> + );
> + struct cxl_region *region;
> + u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
> + int (*commit)(struct cxl_decoder *cxld);
> + void (*reset)(struct cxl_decoder *cxld);
> +};
>
> /*
> * Using struct_group() allows for per register-block-type helper routines,
> @@ -70,6 +121,8 @@ struct cxl_regs {
> );
> };
>
> +int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm);
> +
> struct cxl_reg_map {
> bool valid;
> int id;
> diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
> index 296516eecfd6..4db5071aa25a 100644
> --- a/tools/testing/cxl/test/cxl.c
> +++ b/tools/testing/cxl/test/cxl.c
> @@ -840,11 +840,11 @@ static int cxld_registry_restore(struct cxl_decoder *cxld,
> dbg_cxld(port, "restore", &td->cxled.cxld);
> cxld_copy(cxld, &td->cxled.cxld);
> cxled->state = td->cxled.state;
> - cxled->skip = td->cxled.skip;
> + cxld->skip = td->cxled.cxld.skip;
> if (range_len(&td->dpa_range)) {
> rc = devm_cxl_dpa_reserve(cxled, td->dpa_range.start,
> range_len(&td->dpa_range),
> - td->cxled.skip);
> + td->cxled.cxld.skip);
> if (rc) {
> init_disabled_mock_decoder(cxld);
> return rc;
> @@ -882,7 +882,7 @@ static void __cxld_registry_save(struct cxl_test_decoder *td,
>
> cxld_copy(&td->cxled.cxld, cxld);
> td->cxled.state = cxled->state;
> - td->cxled.skip = cxled->skip;
> + td->cxled.cxld.skip = cxled->cxld.skip;
>
> if (!(cxld->flags & CXL_DECODER_F_ENABLE)) {
> td->dpa_range.start = 0;
> @@ -970,7 +970,7 @@ static void mock_decoder_reset(struct cxl_decoder *cxld)
> to_cxl_endpoint_decoder(&cxld->dev);
>
> cxled->state = CXL_DECODER_STATE_MANUAL;
> - cxled->skip = 0;
> + cxled->cxld.skip = 0;
> }
> if (decoder_reset_preserve_registry)
> dev_dbg(port->uport_dev, "decoder%d: skip registry update\n",
> @@ -1021,7 +1021,7 @@ static void init_disabled_mock_decoder(struct cxl_decoder *cxld)
> to_cxl_endpoint_decoder(&cxld->dev);
>
> cxled->state = CXL_DECODER_STATE_MANUAL;
> - cxled->skip = 0;
> + cxled->cxld.skip = 0;
> }
> }
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v8 08/10] cxl: Restore CXL HDM state after PCI reset
2026-07-03 22:05 ` [PATCH v8 08/10] cxl: Restore CXL HDM state after PCI reset Srirangan Madhavan
@ 2026-07-07 1:34 ` Richard Cheng
0 siblings, 0 replies; 18+ messages in thread
From: Richard Cheng @ 2026-07-07 1:34 UTC (permalink / raw)
To: Srirangan Madhavan
Cc: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel, Alex Williamson, vsethi,
alwilliamson, Dan Williams, Sai Yashwanth Reddy Kancherla,
Vishal Aslot, Manish Honap, Jiandi An, linux-tegra
On Fri, Jul 03, 2026 at 10:05:06PM +0800, Srirangan Madhavan wrote:
> After CXL reset, restore PCI config state enough to reach HDM MMIO,
> restore cached global and per-decoder HDM state, and then run the normal
> PCI restore callbacks.
>
> Keep the target IOMMU reset block active until HDM restore completes so
> Bus Master Enable cannot reopen DMA before decoder state is valid.
>
> Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
> ---
> drivers/cxl/core/hdm.c | 4 +
> drivers/cxl/core/reset.c | 320 +++++++++++++++++++++++++++++++++++++--
> 2 files changed, 310 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
> index 7a1ade846c9c..96f0de889073 100644
> --- a/drivers/cxl/core/hdm.c
> +++ b/drivers/cxl/core/hdm.c
> @@ -137,6 +137,10 @@ static void cxl_hdm_info_set_decoder(struct cxl_hdm *cxlhdm,
> if (!info || cxld->id >= info->decoder_count)
> return;
>
> + if (cxlhdm->regs.hdm_decoder)
> + info->global_ctrl = readl(cxlhdm->regs.hdm_decoder +
> + CXL_HDM_DECODER_CTRL_OFFSET);
> +
> if (cxld->flags & CXL_DECODER_F_ENABLE)
> info->settings[cxld->id] = cxld->settings;
> else
> diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
> index 8e80e7588046..cfb9b5101c28 100644
> --- a/drivers/cxl/core/reset.c
> +++ b/drivers/cxl/core/reset.c
> @@ -83,6 +83,21 @@ static int cxld_await_commit(void __iomem *hdm, int id)
> return -ETIMEDOUT;
> }
>
> +static int cxld_await_uncommit(void __iomem *hdm, int id)
> +{
> + u32 ctrl;
> + int i;
> +
> + for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
> + ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> + if (!FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
> + return 0;
> + fsleep(1000);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> static void setup_hw_decoder(struct cxl_decoder_settings *settings,
> void __iomem *hdm)
> {
> @@ -92,6 +107,8 @@ static void setup_hw_decoder(struct cxl_decoder_settings *settings,
> u32 ctrl;
>
> ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> + ctrl &= ~(CXL_HDM_DECODER0_CTRL_COMMIT |
> + CXL_HDM_DECODER0_CTRL_COMMIT_ERROR);
> cxld_set_interleave(settings, &ctrl);
> cxld_set_type(settings, &ctrl);
> base = settings->hpa_range.start;
> @@ -249,6 +266,31 @@ static void __iomem *cxl_pci_hdm_map(struct pci_dev *pdev,
> return hdm;
> }
>
> +static void __iomem *cxl_pci_hdm_ioremap_current(struct pci_dev *pdev,
> + int bar,
> + resource_size_t offset,
> + resource_size_t size)
> +{
> + resource_size_t hdm_start, bar_len;
> + void __iomem *hdm;
> +
> + if (bar < 0 || bar >= PCI_STD_NUM_BARS || !size)
> + return ERR_PTR(-EINVAL);
> +
> + bar_len = pci_resource_len(pdev, bar);
> + if (!bar_len || offset > bar_len || size > bar_len - offset)
> + return ERR_PTR(-ENODEV);
> +
> + hdm_start = pci_resource_start(pdev, bar) + offset;
> + hdm = ioremap(hdm_start, size);
> + if (!hdm) {
> + pci_err(pdev, "failed to remap CXL HDM decoder registers\n");
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + return hdm;
> +}
> +
> static void cxl_pci_hdm_read_decoder_state(struct cxl_hdm_decoder_state *state,
> void __iomem *hdm, int id)
> {
> @@ -261,6 +303,40 @@ static void cxl_pci_hdm_read_decoder_state(struct cxl_hdm_decoder_state *state,
> state->target_high = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(id));
> }
>
> +static int cxl_hdm_enable_mem(struct pci_dev *pdev, u16 *command,
> + bool *restore_command)
> +{
> + int rc;
> +
> + *restore_command = false;
> +
> + rc = pci_read_config_word(pdev, PCI_COMMAND, command);
> + if (rc)
> + return pcibios_err_to_errno(rc);
> +
> + if (*command & PCI_COMMAND_MEMORY)
> + return 0;
> +
> + rc = pci_write_config_word(pdev, PCI_COMMAND,
> + *command | PCI_COMMAND_MEMORY);
> + if (rc)
> + return pcibios_err_to_errno(rc);
> +
> + *restore_command = true;
> + return 0;
> +}
> +
> +static int cxl_hdm_restore_command(struct pci_dev *pdev, u16 command)
> +{
> + int rc;
> +
> + rc = pci_write_config_word(pdev, PCI_COMMAND, command);
> + if (rc)
> + return pcibios_err_to_errno(rc);
> +
> + return 0;
> +}
> +
> static int cxl_pci_hdm_read_decoder(struct pci_dev *pdev,
> struct cxl_hdm_decoder_state *state,
> struct cxl_decoder_settings *settings,
> @@ -455,6 +531,164 @@ void pci_cxl_hdm_init(struct pci_dev *pdev)
> pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
> }
>
> +static int cxl_hdm_decoder_uncommit(struct pci_dev *pdev, void __iomem *hdm,
> + int id, bool *locked_committed)
> +{
> + u32 ctrl;
> + int rc;
> +
> + *locked_committed = false;
> + ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> + if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK) {
> + if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) {
> + pci_dbg(pdev,
> + "CXL HDM decoder %d retained locked committed state\n",
> + id);
> + *locked_committed = true;
> + return 0;
> + }
> +
> + pci_err(pdev, "CXL HDM decoder %d is locked\n", id);
> + return -EBUSY;
> + }
> +
> + if (!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED))
> + return 0;
> +
> + ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
> + writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> +
> + rc = cxld_await_uncommit(hdm, id);
> + if (rc)
> + pci_err(pdev, "CXL HDM decoder %d uncommit failed: %d\n",
> + id, rc);
> +
> + return rc;
> +}
> +
> +static void cxl_restore_hdm_decoder_state(struct cxl_hdm_decoder_state *state,
> + void __iomem *hdm, int id)
> +{
> + u32 ctrl = state->ctrl;
> +
> + ctrl &= ~(CXL_HDM_DECODER0_CTRL_COMMIT |
> + CXL_HDM_DECODER0_CTRL_COMMITTED |
> + CXL_HDM_DECODER0_CTRL_COMMIT_ERROR);
> +
> + writel(state->base_high, hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
> + writel(state->base_low, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
> + writel(state->size_high, hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
> + writel(state->size_low, hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
> + writel(state->target_high, hdm + CXL_HDM_DECODER0_TL_HIGH(id));
> + writel(state->target_low, hdm + CXL_HDM_DECODER0_TL_LOW(id));
> + /* Ensure raw decoder fields are visible before restoring control. */
> + wmb();
> + writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> +}
> +
> +static int cxl_restore_hdm_decoder(struct pci_dev *pdev,
> + struct cxl_hdm_decoder_state *state,
> + struct cxl_decoder_settings *settings,
> + void __iomem *hdm)
> +{
> + bool locked_committed;
> + int rc;
> +
> + rc = cxl_hdm_decoder_uncommit(pdev, hdm, settings->id,
> + &locked_committed);
> + if (rc)
> + return rc;
> + if (locked_committed)
> + return 0;
> +
> + cxl_restore_hdm_decoder_state(state, hdm, settings->id);
> +
> + if (!(settings->flags & CXL_DECODER_F_ENABLE))
> + return 0;
> +
> + rc = cxl_commit(settings, hdm);
> + if (rc)
> + pci_err(pdev, "CXL HDM decoder %d restore failed: %d\n",
> + settings->id, rc);
> +
> + return rc;
> +}
> +
> +static int cxl_restore_hdm(struct pci_dev *pdev)
> +{
> + struct cxl_hdm_decoder_state *decoder_state __free(kfree) = NULL;
> + struct cxl_decoder_settings *settings __free(kfree) = NULL;
> + struct cxl_hdm_info *info;
> + resource_size_t hdm_offset;
> + resource_size_t hdm_size;
> + void __iomem *hdm;
> + int decoder_count;
> + int first_rc = 0;
> + u32 global_ctrl;
> + bool restore_command = false;
> + u16 command;
> + int hdm_bar;
> + int rc;
> +
> + scoped_guard(rwsem_read, &cxl_rwsem.dpa) {
> + info = pdev->hdm;
> + if (!info)
> + return 0;
> +
> + decoder_count = info->decoder_count;
> + hdm_bar = info->hdm_bar;
> + hdm_offset = info->hdm_offset;
> + hdm_size = info->hdm_size;
> + global_ctrl = info->global_ctrl;
> + settings = kmemdup_array(info->settings, decoder_count,
> + sizeof(*settings), GFP_KERNEL);
> + if (!settings)
> + return -ENOMEM;
> + decoder_state = kmemdup_array(info->decoder_state,
> + decoder_count,
> + sizeof(*decoder_state),
> + GFP_KERNEL);
> + if (!decoder_state)
> + return -ENOMEM;
> + }
> +
> + rc = cxl_hdm_enable_mem(pdev, &command, &restore_command);
> + if (rc)
> + return rc;
> +
> + hdm = cxl_pci_hdm_ioremap_current(pdev, hdm_bar, hdm_offset, hdm_size);
> + if (IS_ERR(hdm)) {
> + first_rc = PTR_ERR(hdm);
> + goto out_restore_command;
> + }
> +
> + /*
> + * Restore global HDM control before per-decoder commit. PCI config
> + * memory decoding is enabled for MMIO access, but IOMMU reset blocks
> + * remain active until HDM restore completes.
> + */
> + writel(global_ctrl, hdm + CXL_HDM_DECODER_CTRL_OFFSET);
> +
> + for (int i = 0; i < decoder_count; i++) {
> + int rc;
> +
> + rc = cxl_restore_hdm_decoder(pdev, &decoder_state[i],
> + &settings[i], hdm);
> + if (rc && !first_rc)
> + first_rc = rc;
> + }
> +
> + iounmap(hdm);
> +out_restore_command:
> + if (restore_command) {
> + rc = cxl_hdm_restore_command(pdev, command);
> + if (rc && !first_rc)
> + first_rc = rc;
> + }
> +
> + return first_rc;
> +}
> +
> /*
> * CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
> * Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
> @@ -476,6 +710,7 @@ static const u32 cxl_reset_timeout_ms[] = {
>
> struct cxl_reset_context {
> struct pci_dev *target;
> + bool target_prepared;
> };
>
> struct cxl_reset_walk_context {
> @@ -627,6 +862,38 @@ static int cxl_reset_validate_function_scoped(struct cxl_reset_context *ctx)
> return wctx.rc;
> }
>
> +static void cxl_pci_target_reset_done(struct cxl_reset_context *ctx)
> +{
> + if (!ctx->target_prepared)
> + return;
> +
> + pci_dev_reset_iommu_done(ctx->target);
> + ctx->target_prepared = false;
> +}
> +
> +static int cxl_pci_target_reset_prepare(struct cxl_reset_context *ctx)
> +{
> + struct pci_dev *pdev = ctx->target;
> + int rc;
> +
> + if (!pci_wait_for_pending_transaction(pdev))
> + pci_err(pdev, "timed out waiting for pending transactions\n");
> +
> + rc = pci_dev_reset_iommu_prepare(pdev);
> + if (rc) {
> + pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
> + return rc;
> + }
> +
> + ctx->target_prepared = true;
> + return 0;
> +}
> +
> +static int cxl_restore_hdm_decoders(struct cxl_reset_context *ctx)
> +{
> + return cxl_restore_hdm(ctx->target);
> +}
> +
> static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
> {
> INIT_LIST_HEAD(&ctx->ranges);
> @@ -672,9 +939,11 @@ static int cxl_hdm_range_add(struct cxl_hdm_range_context *ctx,
> static int cxl_hdm_ranges_collect(struct cxl_hdm_range_context *ctx,
> struct pci_dev *pdev)
> {
> - struct cxl_hdm_info *info = READ_ONCE(pdev->hdm);
> + struct cxl_hdm_info *info;
> int rc;
>
> + guard(rwsem_read)(&cxl_rwsem.dpa);
> + info = pdev->hdm;
> if (!info) {
> pci_err(pdev, "CXL HDM decoder state unavailable\n");
> return -ENXIO;
> @@ -824,8 +1093,9 @@ static int cxl_hdm_ranges_flush_cpu_caches(struct cxl_hdm_range_context *ctx,
> }
>
> static int cxl_hdm_ranges_prepare(struct cxl_hdm_range_context *ctx,
> - struct pci_dev *pdev)
> + struct cxl_reset_context *reset_ctx)
> {
> + struct pci_dev *pdev = reset_ctx->target;
> int rc;
>
> lockdep_assert_held_write(&cxl_rwsem.region);
> @@ -866,6 +1136,25 @@ static int cxl_reset_dvsec(struct pci_dev *pdev)
> return dvsec;
> }
>
> +static bool cxl_reset_hdm_available(struct pci_dev *pdev)
> +{
> + struct cxl_hdm_info *info;
> +
> + /*
> + * pdev->hdm is owned by the PCI device and released with pci_dev, so
> + * reset-method probes and reset requests can test availability without
> + * a CXL driver bound to the device.
> + */
> + guard(rwsem_read)(&cxl_rwsem.dpa);
> + info = pdev->hdm;
> + return info && info->hdm_size;
> +}
> +
> +static bool cxl_reset_scope_hdm_available(struct cxl_reset_context *ctx)
> +{
> + return cxl_reset_hdm_available(ctx->target);
> +}
> +
> static int cxl_reset_update_ctrl2(struct pci_dev *pdev, int dvsec, u16 set,
> u16 clear)
> {
> @@ -995,18 +1284,9 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
> if (rc)
> return pcibios_err_to_errno(rc);
>
> - if (!pci_wait_for_pending_transaction(pdev))
> - pci_err(pdev, "timed out waiting for pending transactions\n");
> -
> - rc = pci_dev_reset_iommu_prepare(pdev);
> - if (rc) {
> - pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
> - return rc;
> - }
> -
> rc = cxl_reset_disable_cache(pdev, dvsec, cap);
> if (rc)
> - goto out;
> + return rc;
> cache_disabled = true;
>
> rc = cxl_reset_update_ctrl2(pdev, dvsec, PCI_DVSEC_CXL_INIT_CXL_RST,
> @@ -1030,7 +1310,6 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
> rc = rc2;
> }
>
> - pci_dev_reset_iommu_done(pdev);
> return rc;
> }
>
> @@ -1051,11 +1330,24 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
>
> cxl_hdm_range_context_init(&range_ctx);
>
> + if (!cxl_reset_scope_hdm_available(&ctx)) {
> + rc = -ENOTTY;
> + goto out;
> + }
> +
> + rc = cxl_pci_target_reset_prepare(&ctx);
> + if (rc)
> + goto out;
> +
> scoped_guard(rwsem_write, &cxl_rwsem.region) {
> - rc = cxl_hdm_ranges_prepare(&range_ctx, pdev);
> + rc = cxl_hdm_ranges_prepare(&range_ctx, &ctx);
> if (!rc)
> rc = cxl_reset_execute(pdev, dvsec);
> + if (!rc)
> + rc = cxl_restore_hdm_decoders(&ctx);
> }
> +
> + cxl_pci_target_reset_done(&ctx);
> out:
> cxl_hdm_range_context_destroy(&range_ctx);
> return rc;
> --
> 2.43.0
>
Hi Srirangan,
I have some questions.
On failure of cxl_restore_hdm_decoders(), cxl_pci_target_reset_done() still runs and lifts the IOMMU reset
block, after which pci_dev_restore() re-enables Bus Master. That leaves DMA reopened over decoders that were
never restored, the opposite of what you wrote in commit message. If restore fails, the device should be left
in a safe state, etc IOMMU block held, device kept disabled, etc, rather than resumed.
What's the intended recovery when restore returns non-zero ?
--Richard
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v8 07/10] PCI/CXL: Discover the CXL reset scope
2026-07-03 22:05 ` [PATCH v8 07/10] PCI/CXL: Discover the CXL reset scope Srirangan Madhavan
@ 2026-07-07 1:45 ` Richard Cheng
0 siblings, 0 replies; 18+ messages in thread
From: Richard Cheng @ 2026-07-07 1:45 UTC (permalink / raw)
To: Srirangan Madhavan
Cc: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel, Alex Williamson, vsethi,
alwilliamson, Dan Williams, Sai Yashwanth Reddy Kancherla,
Vishal Aslot, Manish Honap, Jiandi An, linux-tegra
On Fri, Jul 03, 2026 at 10:05:05PM +0800, Srirangan Madhavan wrote:
> Add reset context support to validate that CXL Reset is function-scoped
> before advertising it as a PCI reset method. Use the Non-CXL Function
> Map, ARI/devfn rules, and CXL.cache/mem capability bits to reject reset
> when another same-scope function would also be affected.
>
> If the Function Map cannot be read, warn and conservatively treat all
> candidate same-scope functions as CXL-capable for scope validation.
>
> Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
> ---
> drivers/cxl/core/reset.c | 153 +++++++++++++++++++++++++++++++++-
> include/uapi/linux/pci_regs.h | 1 +
> 2 files changed, 152 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
> index fb741b66e502..8e80e7588046 100644
> --- a/drivers/cxl/core/reset.c
> +++ b/drivers/cxl/core/reset.c
> @@ -1,5 +1,6 @@
> // SPDX-License-Identifier: GPL-2.0-only
> /* Copyright(c) 2026 NVIDIA Corporation. All rights reserved. */
> +#include <linux/bitmap.h>
> #include <linux/delay.h>
> #include <linux/bug.h>
> #include <linux/bitfield.h>
> @@ -469,6 +470,21 @@ static const u32 cxl_reset_timeout_ms[] = {
> #define CXL_CACHE_WBI_TIMEOUT_US 100000
> #define CXL_CACHE_WBI_POLL_US 100
>
> +/* CXL r4.0 sec 8.1.4 defines 256 bits of Non-CXL Function Map. */
> +#define CXL_RESET_MAX_FUNCTIONS 256
> +#define CXL_RESET_FUNCTION_MAP_REGS (CXL_RESET_MAX_FUNCTIONS / 32)
> +
> +struct cxl_reset_context {
> + struct pci_dev *target;
> +};
> +
> +struct cxl_reset_walk_context {
> + struct cxl_reset_context *ctx;
> + DECLARE_BITMAP(non_cxl_func_map, CXL_RESET_MAX_FUNCTIONS);
> + bool ari;
> + int rc;
> +};
> +
> struct cxl_hdm_range {
> struct list_head list;
> struct pci_dev *pdev;
> @@ -480,6 +496,137 @@ struct cxl_hdm_range_context {
> struct list_head ranges;
> };
>
> +static void cxl_reset_context_init(struct cxl_reset_context *ctx,
> + struct pci_dev *pdev)
> +{
> + *ctx = (struct cxl_reset_context) {
> + .target = pdev,
> + };
> +}
> +
> +static void cxl_reset_read_non_cxl_func_map(struct pci_dev *pdev,
> + unsigned long *map)
> +{
> + u32 words[CXL_RESET_FUNCTION_MAP_REGS];
> + int dvsec, reg;
> +
> + bitmap_zero(map, CXL_RESET_MAX_FUNCTIONS);
> +
> + dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
> + PCI_DVSEC_CXL_FUNCTION_MAP);
> + if (!dvsec)
> + return;
> +
> + for (reg = 0; reg < CXL_RESET_FUNCTION_MAP_REGS; reg++) {
> + int rc;
> +
> + rc = pci_read_config_dword(pdev,
> + dvsec + PCI_DVSEC_CXL_FUNCTION_MAP_REG +
> + reg * sizeof(u32), &words[reg]);
> + if (rc) {
> + pci_warn(pdev,
> + "failed to read Non-CXL Function Map; treating same-scope functions as CXL\n");
> + bitmap_zero(map, CXL_RESET_MAX_FUNCTIONS);
> + return;
> + }
> + }
> +
> + bitmap_from_arr32(map, words, CXL_RESET_MAX_FUNCTIONS);
> +}
> +
> +static int cxl_reset_func_map_bit(struct pci_dev *sibling, bool ari)
> +{
> + if (ari)
> + return sibling->devfn;
> +
> + /*
> + * Without ARI, the Function Map is organized as 32 device slots per
> + * conventional 3-bit function number.
> + */
> + return PCI_FUNC(sibling->devfn) * 32 + PCI_SLOT(sibling->devfn);
> +}
> +
> +static int cxl_reset_read_cxl_cap(struct pci_dev *pdev, u16 *cap)
> +{
> + int dvsec, rc;
> +
> + dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
> + PCI_DVSEC_CXL_DEVICE);
> + if (!dvsec)
> + return -ENODEV;
> +
> + rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, cap);
> + if (rc) {
> + rc = pcibios_err_to_errno(rc);
> + pci_warn(pdev, "failed to read CXL capability: %d\n", rc);
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int cxl_reset_has_cache_or_mem(struct pci_dev *pdev)
> +{
> + u16 cap;
> + int rc;
> +
> + rc = cxl_reset_read_cxl_cap(pdev, &cap);
> + if (rc == -ENODEV)
> + return 0;
> + if (rc)
> + return rc;
> +
> + return !!(cap & (PCI_DVSEC_CXL_CACHE_CAPABLE |
> + PCI_DVSEC_CXL_MEM_CAPABLE));
> +}
> +
> +static int cxl_reset_validate_function_scope(struct pci_dev *sibling,
> + void *data)
> +{
> + struct cxl_reset_walk_context *wctx = data;
> + struct cxl_reset_context *ctx = wctx->ctx;
> + struct pci_dev *pdev = ctx->target;
> + int fn, rc;
> +
> + if (sibling == pdev)
> + return 0;
> +
> + if (sibling->bus != pdev->bus)
> + return 0;
> +
> + if (!wctx->ari && PCI_SLOT(sibling->devfn) != PCI_SLOT(pdev->devfn))
> + return 0;
> +
> + fn = cxl_reset_func_map_bit(sibling, wctx->ari);
> + if (test_bit(fn, wctx->non_cxl_func_map))
> + return 0;
> +
> + rc = cxl_reset_has_cache_or_mem(sibling);
> + if (rc < 0) {
> + wctx->rc = rc;
> + return rc;
> + }
> + if (!rc)
> + return 0;
> +
cxl_reset_has_cache_or_mem() treats a sibling with no CXL device DVSEC as non-CXL, but according to
spec, it places the DVSEC only on Function 0, the sibling function can participate under fn0's DVSEC
with no DVSEC of their own. The non-CXL Function Map should be authoritative for classifying siblings
rather than a per-sibling DVSEC read.
What do you think ?
--Richard
> + wctx->rc = -ENOTTY;
> + return wctx->rc;
> +}
> +
> +static int cxl_reset_validate_function_scoped(struct cxl_reset_context *ctx)
> +{
> + struct pci_dev *pdev = ctx->target;
> + struct cxl_reset_walk_context wctx = {
> + .ctx = ctx,
> + .ari = pci_ari_enabled(pdev->bus),
> + };
> +
> + cxl_reset_read_non_cxl_func_map(pdev, wctx.non_cxl_func_map);
> + pci_walk_bus(pdev->bus, cxl_reset_validate_function_scope, &wctx);
> +
> + return wctx.rc;
> +}
> +
> static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
> {
> INIT_LIST_HEAD(&ctx->ranges);
> @@ -890,6 +1037,7 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)
> int cxl_reset_function(struct pci_dev *pdev, bool probe)
> {
> struct cxl_hdm_range_context range_ctx;
> + struct cxl_reset_context ctx;
> int dvsec;
> int rc;
>
> @@ -897,8 +1045,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
> if (dvsec < 0)
> return dvsec;
>
> + cxl_reset_context_init(&ctx, pdev);
> if (probe)
> - return 0;
> + return cxl_reset_validate_function_scoped(&ctx);
>
This only runs on the PCI_RESET_PROBE path, but pci_init_reset_methods() runs during pci_device_add(),
before sibling functions of a multi-function device are on pdev->bus.
Thus pci_walk_bus() at probe time walks on an incomplete sibling set and passes, the DO_RESET path never
re-validates.
A multi-function Type-2 device would advertise and perform cxl_reset even with a live CXL sibling.
Can the scope be recheck at reset time or gate on siblings actually being present ?
> cxl_hdm_range_context_init(&range_ctx);
>
> @@ -907,7 +1056,7 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
> if (!rc)
> rc = cxl_reset_execute(pdev, dvsec);
> }
> -
> +out:
> cxl_hdm_range_context_destroy(&range_ctx);
> return rc;
> }
> diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
> index 194ae56b4404..7fc1d34fcce7 100644
> --- a/include/uapi/linux/pci_regs.h
> +++ b/include/uapi/linux/pci_regs.h
> @@ -1380,6 +1380,7 @@
>
> /* CXL r4.0, 8.1.4: Non-CXL Function Map DVSEC */
> #define PCI_DVSEC_CXL_FUNCTION_MAP 2
> +#define PCI_DVSEC_CXL_FUNCTION_MAP_REG 0x0C
>
> /* CXL r4.0, 8.1.5: Extensions DVSEC for Ports */
> #define PCI_DVSEC_CXL_PORT 3
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v8 04/10] cxl: Cache endpoint decoder settings during PCI enumeration
2026-07-03 22:05 ` [PATCH v8 04/10] cxl: Cache endpoint decoder settings during PCI enumeration Srirangan Madhavan
@ 2026-07-07 5:30 ` Richard Cheng
0 siblings, 0 replies; 18+ messages in thread
From: Richard Cheng @ 2026-07-07 5:30 UTC (permalink / raw)
To: Srirangan Madhavan
Cc: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel, Alex Williamson, vsethi,
alwilliamson, Dan Williams, Sai Yashwanth Reddy Kancherla,
Vishal Aslot, Manish Honap, Jiandi An, linux-tegra
On Fri, Jul 03, 2026 at 10:05:02PM +0800, Srirangan Madhavan wrote:
> Populate pci_dev->hdm from PCI capability initialization for CXL.mem
> functions. If Memory Space Enable is clear, temporarily set it while
> reading HDM MMIO and restore the original PCI_COMMAND value before
> returning. This gives driver-free reset paths an early HDM snapshot.
>
> CXL core later reuses and refreshes the same cache. Move the register
> helpers into the built-in CONFIG_CXL_HDM set so the early cache path is
> available without cxl_core.
>
> Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
> ---
> drivers/cxl/core/Makefile | 3 +-
> drivers/cxl/core/hdm.c | 58 ++++----
> drivers/cxl/core/regs.c | 4 +
> drivers/cxl/core/reset.c | 288 ++++++++++++++++++++++++++++++++++++++
> drivers/pci/probe.c | 3 +
> include/cxl/cxl.h | 31 +++-
> 6 files changed, 349 insertions(+), 38 deletions(-)
>
> diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
> index dc075cee0450..69cf2ea7ee74 100644
> --- a/drivers/cxl/core/Makefile
> +++ b/drivers/cxl/core/Makefile
> @@ -1,6 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
> obj-$(CONFIG_CXL_BUS) += cxl_core.o
> -obj-$(CONFIG_CXL_HDM) += reset.o
> +obj-$(CONFIG_CXL_HDM) += regs.o reset.o
> obj-$(CONFIG_CXL_SUSPEND) += suspend.o
>
> ccflags-y += -I$(srctree)/drivers/cxl
> @@ -8,7 +8,6 @@ CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
>
> cxl_core-y := port.o
> cxl_core-y += pmem.o
> -cxl_core-y += regs.o
> cxl_core-y += memdev.o
> cxl_core-y += mbox.o
> cxl_core-y += pci.o
> diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
> index bd1d92e5add2..7a1ade846c9c 100644
> --- a/drivers/cxl/core/hdm.c
> +++ b/drivers/cxl/core/hdm.c
> @@ -84,18 +84,9 @@ static void parse_hdm_decoder_caps(struct cxl_hdm *cxlhdm)
> cxlhdm->iw_cap_mask |= BIT(16);
> }
>
> -static void clear_hdm_info(void *data)
> -{
> - struct pci_dev *pdev = data;
> -
> - WRITE_ONCE(pdev->hdm, NULL);
> -}
> -
> -static int devm_cxl_pci_setup_hdm_info(struct cxl_hdm *cxlhdm)
> +static struct pci_dev *cxl_hdm_to_pci_dev(struct cxl_hdm *cxlhdm)
> {
> struct cxl_port *port = cxlhdm->port;
> - struct cxl_hdm_info *info;
> - struct pci_dev *pdev;
> struct device *uport;
>
> if (is_cxl_endpoint(port)) {
> @@ -107,42 +98,42 @@ static int devm_cxl_pci_setup_hdm_info(struct cxl_hdm *cxlhdm)
> }
>
> if (!dev_is_pci(uport))
> - return 0;
> + return NULL;
>
> - pdev = to_pci_dev(uport);
> - info = devm_kzalloc(&pdev->dev,
> - struct_size(info, settings, cxlhdm->decoder_count),
> - GFP_KERNEL);
> - if (!info)
> - return -ENOMEM;
> + return to_pci_dev(uport);
> +}
> +
> +static int devm_cxl_pci_setup_hdm_info(struct cxl_hdm *cxlhdm)
> +{
> + struct cxl_hdm_info *info;
> + struct pci_dev *pdev;
>
> - info->decoder_count = cxlhdm->decoder_count;
> - WRITE_ONCE(pdev->hdm, info);
> + pdev = cxl_hdm_to_pci_dev(cxlhdm);
> + if (!pdev)
> + return 0;
>
> - return devm_add_action_or_reset(&pdev->dev, clear_hdm_info, pdev);
> + guard(rwsem_read)(&cxl_rwsem.dpa);
> + info = pdev->hdm;
> + if (info) {
> + if (info->decoder_count != cxlhdm->decoder_count)
> + return -ENXIO;
> + }
> +
> + return 0;
> }
>
> static void cxl_hdm_info_set_decoder(struct cxl_hdm *cxlhdm,
> struct cxl_decoder *cxld)
> {
> - struct cxl_port *port = cxlhdm->port;
> struct cxl_hdm_info *info;
> struct pci_dev *pdev;
> - struct device *uport;
> -
> - if (is_cxl_endpoint(port)) {
> - struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev);
> -
> - uport = cxlmd->dev.parent;
> - } else {
> - uport = port->uport_dev;
> - }
>
> - if (!dev_is_pci(uport))
> + pdev = cxl_hdm_to_pci_dev(cxlhdm);
> + if (!pdev)
> return;
>
> - pdev = to_pci_dev(uport);
> - info = READ_ONCE(pdev->hdm);
> + guard(rwsem_write)(&cxl_rwsem.dpa);
> + info = pdev->hdm;
> if (!info || cxld->id >= info->decoder_count)
> return;
>
> @@ -948,6 +939,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
> {
> struct cxl_endpoint_decoder *cxled = NULL;
> u64 size, base, skip, dpa_size, lo, hi;
> + struct cxl_decoder_settings settings;
> bool committed;
> u32 remainder;
> int i, rc;
> diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c
> index 93710cf4f0a6..040b0304f63c 100644
> --- a/drivers/cxl/core/regs.c
> +++ b/drivers/cxl/core/regs.c
> @@ -199,6 +199,7 @@ void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr,
>
> return ret_val;
> }
> +EXPORT_SYMBOL_NS_GPL(devm_cxl_iomap_block, "CXL");
>
> int cxl_map_component_regs(const struct cxl_register_map *map,
> struct cxl_component_regs *regs,
> @@ -517,6 +518,7 @@ u16 cxl_rcrb_to_aer(struct device *dev, resource_size_t rcrb)
>
> return offset;
> }
> +EXPORT_SYMBOL_NS_GPL(cxl_rcrb_to_aer, "CXL");
>
> static resource_size_t cxl_rcrb_to_linkcap(struct device *dev, struct cxl_dport *dport)
> {
> @@ -633,6 +635,7 @@ resource_size_t __rcrb_to_component(struct device *dev, struct cxl_rcrb_info *ri
>
> return component_reg_phys;
> }
> +EXPORT_SYMBOL_NS_GPL(__rcrb_to_component, "CXL");
>
> resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
> struct cxl_dport *dport)
> @@ -641,3 +644,4 @@ resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
> return CXL_RESOURCE_NONE;
> return __rcrb_to_component(dev, &dport->rcrb, CXL_RCRB_UPSTREAM);
> }
> +EXPORT_SYMBOL_NS_GPL(cxl_rcd_component_reg_phys, "CXL");
> diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
> index 4c977fc47f8d..97b72cc67b6b 100644
> --- a/drivers/cxl/core/reset.c
> +++ b/drivers/cxl/core/reset.c
> @@ -2,9 +2,16 @@
> /* Copyright(c) 2026 NVIDIA Corporation. All rights reserved. */
> #include <linux/delay.h>
> #include <linux/bug.h>
> +#include <linux/bitfield.h>
> #include <linux/errno.h>
> #include <linux/export.h>
> +#include <linux/io.h>
> +#include <linux/ioport.h>
> #include <linux/kernel.h>
> +#include <linux/pci.h>
> +#include <linux/slab.h>
> +
> +#include <cxlpci.h>
>
> #include "cxl.h"
> #include "core.h"
> @@ -161,3 +168,284 @@ int cxl_hdm_decode_decoder(struct cxl_decoder_settings *settings, int id,
> &settings->interleave_granularity);
> }
> EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_decode_decoder, "cxl_core");
> +
> +struct cxl_hdm_decoder_state {
> + u32 ctrl;
> + u32 base_low;
> + u32 base_high;
> + u32 size_low;
> + u32 size_high;
> + u32 target_low;
> + u32 target_high;
> +};
> +
> +void pci_cxl_hdm_release(struct pci_dev *pdev)
> +{
> + struct cxl_hdm_info *info;
> +
> + scoped_guard(rwsem_write, &cxl_rwsem.dpa) {
> + info = pdev->hdm;
> + pdev->hdm = NULL;
> + }
> + if (!info)
> + return;
> +
> + kfree(info->decoder_state);
> + kfree(info);
> +}
> +
> +static int cxl_pci_hdm_find_bar(struct pci_dev *pdev, resource_size_t hdm_start,
> + resource_size_t hdm_size, int *bar,
> + resource_size_t *offset)
> +{
> + resource_size_t hdm_end = hdm_start + hdm_size - 1;
> +
> + for (int i = 0; i < PCI_STD_NUM_BARS; i++) {
> + struct resource *res = &pdev->resource[i];
> +
> + if (!pci_resource_len(pdev, i))
> + continue;
> + if (resource_type(res) != IORESOURCE_MEM)
> + continue;
> + if (hdm_start < res->start || hdm_end > res->end)
> + continue;
> +
> + *bar = i;
> + *offset = hdm_start - res->start;
> + return 0;
> + }
> +
> + return -ENODEV;
> +}
> +
> +static void __iomem *cxl_pci_hdm_map(struct pci_dev *pdev,
> + struct cxl_register_map *map,
> + struct cxl_hdm_info *info)
> +{
> + struct cxl_reg_map *hdm_map = &map->component_map.hdm_decoder;
> + resource_size_t hdm_start;
> + void __iomem *hdm;
> + int rc;
> +
> + hdm_start = map->resource + hdm_map->offset;
> + info->hdm_size = hdm_map->size;
> +
> + rc = cxl_pci_hdm_find_bar(pdev, hdm_start, info->hdm_size,
> + &info->hdm_bar, &info->hdm_offset);
> + if (rc)
> + return ERR_PTR(rc);
> +
> + hdm = ioremap(hdm_start, info->hdm_size);
> + if (!hdm) {
> + pci_err(pdev, "failed to map CXL HDM decoder registers\n");
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + return hdm;
> +}
> +
> +static void cxl_pci_hdm_read_decoder_state(struct cxl_hdm_decoder_state *state,
> + void __iomem *hdm, int id)
> +{
> + state->ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
> + state->base_low = readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
> + state->base_high = readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
> + state->size_low = readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
> + state->size_high = readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
> + state->target_low = readl(hdm + CXL_HDM_DECODER0_TL_LOW(id));
> + state->target_high = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(id));
> +}
> +
> +static int cxl_pci_hdm_read_decoder(struct pci_dev *pdev,
> + struct cxl_hdm_decoder_state *state,
> + struct cxl_decoder_settings *settings,
> + void __iomem *hdm, int id)
> +{
> + u64 target_or_skip, base, size;
> + bool committed;
> + int rc;
> +
> + cxl_pci_hdm_read_decoder_state(state, hdm, id);
> +
> + base = ((u64)state->base_high << 32) | state->base_low;
> + size = ((u64)state->size_high << 32) | state->size_low;
> + target_or_skip = ((u64)state->target_high << 32) | state->target_low;
> +
> + rc = cxl_hdm_decode_decoder(settings, id, state->ctrl, base, size,
> + target_or_skip, &committed);
> + if (rc) {
> + pci_err(pdev, "CXL HDM decoder %d has invalid configuration: %d\n",
> + id, rc);
> + return rc;
> + }
> + if (!committed)
> + return 0;
> +
> + return 0;
> +}
> +
> +static int cxl_pci_hdm_capable(struct pci_dev *pdev)
> +{
> + u16 cap;
> + int dvsec;
> + int rc;
> +
> + dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
> + PCI_DVSEC_CXL_DEVICE);
> + if (!dvsec)
> + return -ENOTTY;
> +
> + rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, &cap);
> + if (rc)
> + return pcibios_err_to_errno(rc);
> +
> + if (!(cap & PCI_DVSEC_CXL_MEM_CAPABLE))
> + return -ENOTTY;
> +
> + return 0;
> +}
> +
> +static int __pci_cxl_hdm_init(struct pci_dev *pdev)
> +{
> + struct cxl_decoder_settings *settings;
> + struct cxl_register_map map = { 0 };
> + struct cxl_hdm_info *info;
> + void __iomem *hdm = NULL;
> + bool restore_command = false;
> + bool allocated_info = false;
> + int decoder_count;
> + u16 command;
> + int rc;
> +
> + scoped_guard(rwsem_read, &cxl_rwsem.dpa) {
> + info = pdev->hdm;
> + if (info)
> + return 0;
> + }
> +
> + rc = cxl_pci_hdm_capable(pdev);
> + if (rc)
> + return rc;
> +
> + rc = pci_read_config_word(pdev, PCI_COMMAND, &command);
> + if (rc)
> + return pcibios_err_to_errno(rc);
> +
> + if (!(command & PCI_COMMAND_MEMORY))
> + restore_command = true;
> +
> + if (restore_command) {
> + rc = pci_write_config_word(pdev, PCI_COMMAND,
> + command | PCI_COMMAND_MEMORY);
> + if (rc)
> + return pcibios_err_to_errno(rc);
> + }
> +
> + if (!info) {
> + info = kzalloc_obj(*info, GFP_KERNEL);
> + if (!info)
> + goto err_nomem;
> + allocated_info = true;
> + }
> +
> + rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
> + if (rc)
> + goto out_restore_command;
> +
> + rc = cxl_setup_regs(&map);
> + if (rc)
> + goto out_restore_command;
> +
> + if (!map.component_map.hdm_decoder.valid) {
> + rc = -ENODEV;
> + goto out_restore_command;
> + }
> +
> + hdm = cxl_pci_hdm_map(pdev, &map, info);
> + if (IS_ERR(hdm)) {
> + rc = PTR_ERR(hdm);
> + hdm = NULL;
> + goto out_restore_command;
> + }
Hi Srirangan,
Just a question by inspection, I didn't run the hot-remove path to test whether
this can happen or not.
pci_cxl_hdm_init() is hooked into pci_init_capabilities(), which runs inside pci_device_add(), so at bus-scan
time, before pci_assign_unassigned_*() do the assinging of BARs, __pci_cxl_hdm_init() then sets
PCI_COMMAND_MEMORY and ioremaps the register block.
For hot-add via pciehp or arm64/DT where kernel assigns BARs after the scan, the target BAR can still read 0
when the hook runs. In that state pci_resource_start() is just offset, and the code enables memory decode and ioremap
it, which is exactly what pci_enable_device() refuses to do on an IORESOURCE_UNSET BAR, this might fails the platform.
Do you think it would be safer to defer the HDM caching to a point where source are guaranteed assigned ?
and if it stays at enumeration time, it should at least bail on IORESOURCE_UNSET/!res->start and use Register Locator BIR
to pick the BAR.
--Richard
> +
> + decoder_count = cxl_hdm_decoder_count(readl(hdm +
> + CXL_HDM_DECODER_CAP_OFFSET));
> + if (decoder_count < 0) {
> + rc = decoder_count;
> + goto out_unmap;
> + }
> +
> + if (decoder_count > CXL_HDM_DECODER_MAX_COUNT) {
> + rc = -ENXIO;
> + goto out_unmap;
> + }
> +
> + if (info->decoder_count && info->decoder_count != decoder_count) {
> + rc = -ENXIO;
> + goto out_unmap;
> + }
> +
> + info->decoder_count = decoder_count;
> + info->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
> + info->decoder_state = kcalloc(decoder_count,
> + sizeof(*info->decoder_state),
> + GFP_KERNEL);
> + if (!info->decoder_state) {
> + rc = -ENOMEM;
> + goto out_unmap;
> + }
> +
> + settings = info->settings;
> + for (int i = 0; i < info->decoder_count; i++) {
> + rc = cxl_pci_hdm_read_decoder(pdev, &info->decoder_state[i],
> + &settings[i], hdm, i);
> + if (rc)
> + goto out_unmap;
> + }
> +
> + if (restore_command) {
> + rc = pci_write_config_word(pdev, PCI_COMMAND, command);
> + if (rc)
> + goto out_restore_failed;
> + }
> +
> + scoped_guard(rwsem_write, &cxl_rwsem.dpa) {
> + if (pdev->hdm)
> + goto out_unmap;
> + pdev->hdm = info;
> + }
> + iounmap(hdm);
> + return 0;
> +
> +out_restore_failed:
> + rc = pcibios_err_to_errno(rc);
> + goto out_unmap;
> +err_nomem:
> + rc = -ENOMEM;
> + goto out_restore_command;
> +out_unmap:
> + if (hdm)
> + iounmap(hdm);
> +out_restore_command:
> + if (allocated_info) {
> + kfree(info->decoder_state);
> + kfree(info);
> + }
> + if (restore_command) {
> + int rc2;
> +
> + rc2 = pci_write_config_word(pdev, PCI_COMMAND, command);
> + if (rc2 && !rc)
> + rc = pcibios_err_to_errno(rc2);
> + }
> + return rc;
> +}
> +
> +void pci_cxl_hdm_init(struct pci_dev *pdev)
> +{
> + int rc;
> +
> + rc = __pci_cxl_hdm_init(pdev);
> + if (rc && rc != -ENOTTY && rc != -ENODEV)
> + pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
> +}
> diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
> index b63cd0c310bc..2fd186468498 100644
> --- a/drivers/pci/probe.c
> +++ b/drivers/pci/probe.c
> @@ -24,6 +24,7 @@
> #include <linux/pm_runtime.h>
> #include <linux/bitfield.h>
> #include <trace/events/pci.h>
> +#include <cxl/cxl.h>
> #include "pci.h"
>
> static struct resource busn_resource = {
> @@ -2489,6 +2490,7 @@ static void pci_release_dev(struct device *dev)
> struct pci_dev *pci_dev;
>
> pci_dev = to_pci_dev(dev);
> + pci_cxl_hdm_release(pci_dev);
> pci_release_capabilities(pci_dev);
> pci_release_of_node(pci_dev);
> pcibios_release_device(pci_dev);
> @@ -2679,6 +2681,7 @@ static void pci_init_capabilities(struct pci_dev *dev)
> pci_rebar_init(dev); /* Resizable BAR */
> pci_dev3_init(dev); /* Device 3 capabilities */
> pci_ide_init(dev); /* Link Integrity and Data Encryption */
> + pci_cxl_hdm_init(dev); /* CXL HDM Decoder Capability */
>
> pcie_report_downtraining(dev);
> pci_init_reset_methods(dev);
> diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
> index 80839517eabf..2215fe1c3f78 100644
> --- a/include/cxl/cxl.h
> +++ b/include/cxl/cxl.h
> @@ -26,6 +26,7 @@ enum cxl_devtype {
> };
>
> struct cxl_region;
> +struct pci_dev;
>
> enum cxl_decoder_type {
> CXL_DECODER_DEVMEM = 2,
> @@ -121,22 +122,46 @@ struct cxl_regs {
> );
> };
>
> +#define CXL_HDM_DECODER_MAX_COUNT 32
> +
> +struct cxl_hdm_decoder_state;
> +
> /**
> * struct cxl_hdm_info - PCI device HDM decoder programming cache
> * @decoder_count: number of decoder settings entries
> - * @regs: mapped CXL component registers for this HDM decoder block
> + * @hdm_bar: BAR containing the HDM decoder registers
> + * @hdm_offset: HDM decoder register offset relative to @hdm_bar
> + * @hdm_size: HDM decoder register resource size
> + * @global_ctrl: cached HDM decoder global control register
> + * @decoder_state: cached raw per-decoder register state
> * @settings: cached per-decoder programming state
> */
> struct cxl_hdm_info {
> int decoder_count;
> - struct cxl_component_regs regs;
> - struct cxl_decoder_settings settings[] __counted_by(decoder_count);
> + int hdm_bar;
> + resource_size_t hdm_offset;
> + resource_size_t hdm_size;
> + u32 global_ctrl;
> + struct cxl_hdm_decoder_state *decoder_state;
> + struct cxl_decoder_settings settings[CXL_HDM_DECODER_MAX_COUNT];
> };
>
> int cxl_hdm_decode_decoder(struct cxl_decoder_settings *settings, int id,
> u32 ctrl, u64 base, u64 size, u64 target_or_skip,
> bool *committed);
> int cxl_commit(struct cxl_decoder_settings *settings, void __iomem *hdm);
> +#ifdef CONFIG_CXL_HDM
> +void pci_cxl_hdm_init(struct pci_dev *pdev);
> +void pci_cxl_hdm_release(struct pci_dev *pdev);
> +#else
> +static inline void pci_cxl_hdm_init(struct pci_dev *pdev)
> +{
> +}
> +
> +static inline void pci_cxl_hdm_release(struct pci_dev *pdev)
> +{
> +}
> +#endif
>
> struct cxl_reg_map {
> bool valid;
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (11 preceding siblings ...)
2026-07-06 23:13 ` [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Dave Jiang
@ 2026-07-07 5:41 ` Richard Cheng
12 siblings, 0 replies; 18+ messages in thread
From: Richard Cheng @ 2026-07-07 5:41 UTC (permalink / raw)
To: Srirangan Madhavan
Cc: Alison Schofield, Bjorn Helgaas, Dan Williams, Dave Jiang,
Davidlohr Bueso, Ira Weiny, Jonathan Cameron, Vishal Verma,
linux-cxl, linux-pci, linux-kernel, Alex Williamson, vsethi,
alwilliamson, Dan Williams, Sai Yashwanth Reddy Kancherla,
Vishal Aslot, Manish Honap, Jiandi An, linux-tegra
On Fri, Jul 03, 2026 at 10:04:58PM +0800, Srirangan Madhavan wrote:
> Hi folks!
>
> This series adds CXL Reset support for CXL Type 2 devices through the
> existing PCI reset_method ABI. The reset sequence follows the CXL 4.0
> specification [1], including CXL.cache disable, optional cache
> writeback, CXL Reset initiation, ResetComplete polling, and ResetError
> reporting.
>
> The userspace ABI is the existing PCI reset interface:
>
> /sys/bus/pci/devices/.../reset_method
> /sys/bus/pci/devices/.../reset
>
> Userspace can select "cxl_reset" in reset_method and then trigger reset
> through the existing reset attribute.
>
> Following Dan's v6 feedback, this replaces the proposed memdev sysfs ABI
> with the existing PCI reset_method interface.
>
> v8 changes from v7 [2]:
> - Drop the PCI helper export patch.
> - Drop the multi-function sibling coordination patch. cxl_reset is only
> exposed as a function-scoped reset method when the CXL reset scope is
> limited to the target function.
> - Keep the reset-scope discovery needed to reject non-function-scoped
> CXL resets.
> - Cache HDM location as BAR-relative metadata instead of keeping an
> enum-time MMIO mapping.
> - Restore HDM through a temporary mapping based on the current BAR
> assignment after PCI config state is restored.
> - Cache raw HDM decoder register state so uncommitted decoders can be
> restored as uncommitted, while committed decoders are recommitted
> through the normal HDM commit flow.
> - Share HDM decoder decode and validation between normal CXL core
> enumeration and early PCI HDM caching.
> - Put cxl_reset ahead of FLR in reset_method priority because FLR does
> not reset CXL.cache or CXL.mem protocol state.
>
Hi Srirangan,
Thanks for this, I have some questions and suggestions in your patches,
but overall I let codex ran 260 cxl_reset on a machine with 2 CXL-capable
GPUs, exposed as Type 2 device, and 0 failures for this series, at least
on my HW it works.
Let me summarize what I did
- 100 sequential resets on each GPU, 200 in totoal, all rc=0, device healthy after each.
- 20 rounds of concurrent reset on both GPUs, plus 40 iterations alternating reset_method: 0 failures.
- No WARN/BUG/soft-lockup/refcount, no leaked in /proc/iomem afterward, DVSEC CTRL2/STATUS2 stable across pre/post
I might run more test with KASAN when I have time, but so far it looks good functionally.
Thanks for your work.
Best regards,
Richard Cheng
> Motivation:
> -----------
> - Type 2 devices need a CXL-specific reset mechanism beyond existing PCI
> reset methods.
>
> - FLR does not reset CXL.cache or CXL.mem protocol state. CXL Reset is
> the architectural reset mechanism for those protocols.
>
> - The PCI reset_method ABI lets userspace select this narrower CXL reset
> before falling back to broader bus reset methods.
>
> Change Description:
> -------------------
>
> Patch 1: cxl/hdm: Split decoder programming into a reusable helper
> - Move shared decoder settings to include/cxl/cxl.h.
> - Factor low-level HDM register programming into cxl_commit().
>
> Patch 2: cxl/hdm: Cache decoder settings on PCI devices
> - Cache CXL core HDM decoder settings in pci_dev->hdm.
> - Refresh the cache as decoders are enumerated, committed, or reset.
>
> Patch 3: cxl/hdm: Share HDM decoder decode logic
> - Share HDM decoder decode and validation between normal CXL core
> enumeration and early PCI HDM cache setup.
>
> Patch 4: cxl/hdm: Cache endpoint decoder settings during PCI enumeration
> - Snapshot endpoint HDM state during PCI capability initialization.
> - Cache the HDM register locator as BAR-relative metadata.
> - Cache raw decoder register state in addition to committed decoder
> settings.
>
> Patch 5: PCI/CXL: Add CXL Device Reset helper
> - Add the internal DVSEC reset sequence.
> - Disable CXL.cache, perform cache writeback where supported, initiate
> CXL Reset, and wait for completion.
>
> Patch 6: PCI/CXL: Validate HDM ranges before CXL reset
> - Collect enabled cached HDM ranges.
> - Reject reset if affected ranges are busy.
> - Invalidate CPU caches when runtime cache-invalidation support is
> available, otherwise continue after warning.
>
> Patch 7: PCI/CXL: Discover the CXL reset scope
> - Discover whether CXL Reset is function-scoped using the Non-CXL
> Function Map and CXL cache/mem capability bits.
>
> Patch 8: cxl/pci: Restore CXL HDM state after PCI reset
> - Restore cached global and per-decoder HDM state after reset.
> - Re-map HDM registers from the current BAR assignment during restore.
> - Replay raw decoder state for uncommitted decoders and recommit
> decoders that were committed before reset.
>
> Patch 9: PCI/CXL: Expose CXL Reset as a PCI reset method
> - Add "cxl_reset" to the PCI reset_method table for Type 2 reset-capable
> CXL devices.
> - Prioritize cxl_reset ahead of FLR.
>
> Patch 10: Documentation/ABI: Document CXL Reset PCI reset method
> - Document the new reset_method value and reset behavior.
>
> The CPU cache invalidation step depends on
> cpu_cache_invalidate_memregion() support for the affected address ranges.
> If no runtime provider is available, the kernel emits a warning and
> continues after the affected HDM ranges have been reserved.
>
> Example:
>
> echo cxl_reset > /sys/bus/pci/devices/0000:bb:dd.f/reset_method
> echo 1 > /sys/bus/pci/devices/0000:bb:dd.f/reset
>
> Testing:
> - Ran 100 iterations of cxl_reset through the PCI reset sysfs ABI on a
> CXL Type 2 device. All iterations completed successfully and
> ResetComplete was observed.
> - Exercised cxl_bus reset separately with an add-on HDM restore patch.
>
> References:
> [1] https://computeexpresslink.org/wp-content/uploads/2026/02/CXL-Specification_rev4p0_ver1p0_2026February26_clean_evalcopy_v2.pdf
> [2] https://lore.kernel.org/linux-cxl/20260623032453.3404772-1-smadhavan@nvidia.com/
> [3] https://lore.kernel.org/linux-cxl/20260306080026.116789-1-smadhavan@nvidia.com/
>
> Srirangan Madhavan (10):
> cxl/hdm: Split decoder programming into a reusable helper
> cxl/hdm: Cache decoder settings on PCI devices
> cxl/hdm: Share HDM decoder decode logic
> cxl/hdm: Cache endpoint decoder settings during PCI enumeration
> PCI/CXL: Add CXL Device Reset helper
> PCI/CXL: Validate HDM ranges before CXL reset
> PCI/CXL: Discover the CXL reset scope
> cxl/pci: Restore CXL HDM state after PCI reset
> PCI/CXL: Expose CXL Reset as a PCI reset method
> Documentation/ABI: Document CXL Reset PCI reset method
>
> Documentation/ABI/testing/sysfs-bus-pci | 14 +
> drivers/cxl/Kconfig | 4 +
> drivers/cxl/core/Makefile | 2 +-
> drivers/cxl/core/hdm.c | 257 ++---
> drivers/cxl/core/region.c | 6 +-
> drivers/cxl/core/regs.c | 4 +
> drivers/cxl/core/reset.c | 1354 +++++++++++++++++++++++
> drivers/cxl/cxl.h | 43 -
> drivers/pci/pci.c | 2 +
> drivers/pci/probe.c | 3 +
> include/cxl/cxl.h | 102 +-
> include/linux/pci.h | 8 +-
> include/uapi/linux/pci_regs.h | 15 +
> tools/testing/cxl/test/cxl.c | 10 +-
> 14 files changed, 1615 insertions(+), 209 deletions(-)
> create mode 100644 drivers/cxl/core/reset.c
>
> base-commit: 90cf2e0d702c8a132ccbe72e7687f33c04c14658
> --
> 2.43.0
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-07-07 5:42 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-03 22:04 [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-07-03 22:04 ` [PATCH v8 01/10] cxl: Split decoder programming into a reusable helper Srirangan Madhavan
2026-07-07 0:22 ` Dave Jiang
2026-07-03 22:05 ` [PATCH v8 02/10] cxl: Cache decoder settings on PCI devices Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 03/10] cxl: Share HDM decoder decode logic Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 04/10] cxl: Cache endpoint decoder settings during PCI enumeration Srirangan Madhavan
2026-07-07 5:30 ` Richard Cheng
2026-07-03 22:05 ` [PATCH v8 05/10] cxl: Add CXL Device Reset helper Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 06/10] cxl: Validate HDM ranges before CXL reset Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 07/10] PCI/CXL: Discover the CXL reset scope Srirangan Madhavan
2026-07-07 1:45 ` Richard Cheng
2026-07-03 22:05 ` [PATCH v8 08/10] cxl: Restore CXL HDM state after PCI reset Srirangan Madhavan
2026-07-07 1:34 ` Richard Cheng
2026-07-03 22:05 ` [PATCH v8 09/10] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
2026-07-03 22:05 ` [PATCH v8 10/10] Documentation/ABI: Document CXL Reset " Srirangan Madhavan
2026-07-03 22:08 ` [PATCH RFC] PCI/CXL: Restore HDM state after CXL bus reset Srirangan Madhavan
2026-07-06 23:13 ` [PATCH v8 00/10] PCI/CXL: Add CXL reset support for Type 2 devices Dave Jiang
2026-07-07 5:41 ` Richard Cheng
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox