* [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices
@ 2026-09-10 7:07 Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 01/12] cxl: Move HDM decoder programming helpers Srirangan Madhavan
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:07 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
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 Memory Clear, 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.
CXL Reset is exposed conservatively. The target must be a
single-function CXL Type 2 device with no enabled VFs, CXL.cache and
CXL.mem must be enabled, and CXL Reset Memory Clear must be supported.
The kernel must also have cached HDM state sufficient to validate affected
ranges and restore decoder programming.
For reset through the sysfs reset attribute, the generic PCI path calls
the driver's reset_prepare() callback when provided and disables PCI I/O,
memory decoding, and bus mastering. The CXL reset path then temporarily
reserves each affected HDM range exclusively for the duration of the
reset, synchronizes CPU and device caches, and blocks IOMMU translations
where applicable. It restores HDM decoder state and CXL Device DVSEC
Cache and Mem Enable controls before PCI operation resumes. The path does
not offline memory or revoke mappings; it relies on CXL.mem users retaining
conflicting iomem resources while access remains active.
Following Dan's v6 feedback, this uses the existing PCI reset_method
interface instead of adding a memdev-specific sysfs ABI.
v12 changes from v11 [2]:
- Reject cxl_reset and cxl_bus reset when the PF has enabled VFs,
including VFs enabled after reset-method probing.
- Require CXL Reset Memory Clear support and request memory clearing so
the generic PCI reset does not preserve tenant memory.
- Cache the CXL Device DVSEC control register during early PCI discovery
and restore its CXL.cache and CXL.mem enable bits after CXL Reset and
CXL bus reset.
- Initialize the early PCI HDM cache only from pci_bus_add_device(),
removing the later CXL core retry and its lifecycle locking.
- Reapply and verify a cached HDM decoder lock after decoder restoration
and commit, while leaving surviving locked committed state unchanged.
- Convert temporary early HDM-cache ownership to scoped cleanup.
- Document the reset-safety contract, including the iomem ownership
requirement and the limits around memory offlining and mapping
revocation.
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 CXL Reset before
falling back to broader bus reset methods.
Change Description:
-------------------
Patch 1: cxl: Move HDM decoder programming helpers
- Introduce a decoder settings snapshot collected under the DPA lock.
- Move low-level settings-based HDM programming into resource.c.
Patch 2: cxl: Make HDM commit helpers available to reset code
- Build generic HDM resource helpers under CONFIG_CXL_RESET.
- Export the lock and commit helpers needed when cxl_core is modular.
Patch 3: cxl: Share HDM decoder decode logic
- Share HDM decoder decode and validation between normal CXL core
enumeration and early PCI HDM cache setup.
- Decode zero-size committed decoder state without imposing caller policy.
Patch 4: cxl: 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 5: cxl: Cache endpoint decoder settings during PCI enumeration
- Snapshot endpoint HDM and CXL Device DVSEC control state during PCI
enumeration.
- Cache the HDM register locator as BAR-relative metadata and preserve raw
decoder state for reset restore.
- Keep early cache initialization in the PCI bus-add path only.
Patch 6: cxl: Add CXL Device Reset helper
- Add the internal DVSEC reset sequence.
- Disable CXL.cache, perform cache writeback where supported, initiate
CXL Reset with Memory Clear, and wait for completion.
Patch 7: cxl: Validate HDM ranges before CXL reset
- Collect enabled cached HDM ranges, reject busy ranges, and perform CPU
cache invalidation when a runtime backend is available.
Patch 8: PCI/CXL: Reject CXL Reset on multifunction devices
- Conservatively reject CXL Reset for multifunction devices and PFs with
enabled VFs.
Patch 9: cxl: Restore CXL state after PCI reset
- Restore cached global and per-decoder HDM state, decoder locks, and CXL
Device DVSEC protocol enables while the target IOMMU block is active.
Patch 10: PCI/CXL: Expose CXL Reset as a PCI reset method
- Add "cxl_reset" to the PCI reset-method table ahead of FLR.
Patch 11: Documentation/ABI: Document CXL Reset PCI reset method
- Document the reset method and its safety contract.
Patch 12: PCI/CXL: Restore CXL state after CXL bus reset
- Restore cached CXL HDM and Device DVSEC state after cxl_bus reset.
- Reject multifunction devices and PFs with enabled VFs.
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:
- Applied all 12 patches sequentially to a fresh v7.3-rc1 source tree
with CONFIG_CXL_BUS=m and CONFIG_CXL_RESET=y. Every intermediate patch
passed `make vmlinux modules` and `make M=tools/testing/cxl/`.
- Built and booted the series on an arm64 NVIDIA NVL72 system and
successfully exercised CXL Reset through the PCI reset sysfs ABI.
- Exercised cxl_bus reset with CXL state restoration on all four GPUs in
an NVL72 system; all four resets completed successfully.
- Earlier revision testing ran 100 iterations of cxl_reset.
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/20260902072804.665639-1-smadhavan@nvidia.com/
[3] https://lore.kernel.org/linux-cxl/20260804192958.1823952-1-smadhavan@nvidia.com/
[4] https://lore.kernel.org/linux-cxl/20260709010304.680422-1-smadhavan@nvidia.com/
[5] https://lore.kernel.org/linux-cxl/20260703220508.546528-1-smadhavan@nvidia.com/
Srirangan Madhavan (12):
cxl: Move HDM decoder programming helpers
cxl: Make HDM commit helpers available to reset code
cxl: Share HDM decoder decode logic
cxl: Cache decoder settings on PCI devices
cxl: Cache endpoint decoder settings during PCI enumeration
cxl: Add CXL Device Reset helper
cxl: Validate HDM ranges before CXL reset
PCI/CXL: Reject CXL Reset on multifunction devices
cxl: Restore CXL state after PCI reset
PCI/CXL: Expose CXL Reset as a PCI reset method
Documentation/ABI: Document CXL Reset PCI reset method
PCI/CXL: Restore CXL state after CXL bus reset
Documentation/ABI/testing/sysfs-bus-pci | 25 +
drivers/cxl/Kconfig | 4 +
drivers/cxl/core/Makefile | 2 +-
drivers/cxl/core/core.h | 8 +
drivers/cxl/core/hdm.c | 301 +++----
drivers/cxl/core/pci.c | 22 +-
drivers/cxl/core/port.c | 19 +
drivers/cxl/core/regs.c | 4 +
drivers/cxl/core/resource.c | 1400 +++++++++++++++++++++++++++++++
drivers/cxl/cxl.h | 41 -
drivers/pci/bus.c | 2 +
drivers/pci/pci.c | 8 +
drivers/pci/probe.c | 2 +
include/cxl/cxl.h | 120 +-
include/linux/pci.h | 8 +-
include/uapi/linux/pci_regs.h | 14 +
tools/testing/cxl/Kbuild | 1 -
17 files changed, 1769 insertions(+), 212 deletions(-)
create mode 100644 drivers/cxl/core/resource.c
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 01/12] cxl: Move HDM decoder programming helpers
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
@ 2026-09-10 7:07 ` Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 02/12] cxl: Make HDM commit helpers available to reset code Srirangan Madhavan
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:07 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Introduce struct cxl_decoder_settings as a snapshot of HDM decoder
programming state. Collect endpoint skip and switch target-list state in
hdm.c while holding the DPA lock, then pass the snapshot to generic commit
helpers.
Move the low-level settings-based register programming into resource.c so
later reset restore code can reuse it without depending on private decoder
types.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/Makefile | 1 +
drivers/cxl/core/core.h | 3 +
drivers/cxl/core/hdm.c | 159 +++++++++++-------------------------
drivers/cxl/core/resource.c | 112 +++++++++++++++++++++++++
drivers/cxl/cxl.h | 41 ----------
include/cxl/cxl.h | 65 ++++++++++++++-
tools/testing/cxl/Kbuild | 1 +
7 files changed, 227 insertions(+), 155 deletions(-)
create mode 100644 drivers/cxl/core/resource.c
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index ce7213818d3c..57995777af0d 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -6,6 +6,7 @@ ccflags-y += -I$(srctree)/drivers/cxl
CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
+cxl_core-y += resource.o
cxl_core-y += pmem.o
cxl_core-y += regs.o
cxl_core-y += memdev.o
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 35eaf636adc9..48fbc635975a 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -210,8 +210,11 @@ static inline void devm_cxl_dport_ras_setup(struct cxl_dport *dport) { }
int cxl_gpf_port_setup(struct cxl_dport *dport);
struct cxl_hdm;
+struct cxl_decoder_settings;
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm,
struct cxl_endpoint_dvsec_info *info);
+int cxl_commit_start(void __iomem *hdm, struct cxl_decoder_settings *settings);
+int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings);
int cxl_port_get_possible_dports(struct cxl_port *port);
#ifdef CONFIG_CXL_FEATURES
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 0c80b76a5f9b..70ffebd3e213 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;
@@ -679,122 +674,52 @@ 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)
+static u64 cxl_switch_get_target_list(struct cxl_switch_decoder *cxlsd)
{
- 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);
-}
+ struct cxl_decoder *cxld = &cxlsd->cxld;
+ u64 targets = 0;
+ int ways = min(cxld->interleave_ways, cxlsd->nr_targets);
-static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
-{
- struct cxl_dport **t = &cxlsd->target[0];
- int ways = cxlsd->cxld.interleave_ways;
-
- *tgt = FIELD_PREP(GENMASK(7, 0), t[0]->port_id);
- if (ways > 1)
- *tgt |= FIELD_PREP(GENMASK(15, 8), t[1]->port_id);
- if (ways > 2)
- *tgt |= FIELD_PREP(GENMASK(23, 16), t[2]->port_id);
- if (ways > 3)
- *tgt |= FIELD_PREP(GENMASK(31, 24), t[3]->port_id);
- if (ways > 4)
- *tgt |= FIELD_PREP(GENMASK_ULL(39, 32), t[4]->port_id);
- if (ways > 5)
- *tgt |= FIELD_PREP(GENMASK_ULL(47, 40), t[5]->port_id);
- if (ways > 6)
- *tgt |= FIELD_PREP(GENMASK_ULL(55, 48), t[6]->port_id);
- if (ways > 7)
- *tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
-}
+ /* target_map[] holds the raw list before target[] is resolved. */
+ for (int i = 0; i < ways && i < 8; i++) {
+ u8 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;
+ if (cxlsd->target[i])
+ port_id = cxlsd->target[i]->port_id;
+ else
+ port_id = cxld->target_map[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);
+ targets |= (u64)port_id << (i * 8);
}
- return -ETIMEDOUT;
+ return targets;
}
-static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
+static void cxl_decoder_snapshot(struct cxl_decoder *cxld,
+ struct cxl_decoder_settings *settings)
{
- int id = cxld->id;
- u64 base, size;
- u32 ctrl;
+ lockdep_assert_held(&cxl_rwsem.dpa);
- /* 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);
+ *settings = (struct cxl_decoder_settings) {
+ .id = cxld->id,
+ .hpa_range = cxld->hpa_range,
+ .interleave_ways = cxld->interleave_ways,
+ .interleave_granularity = cxld->interleave_granularity,
+ .target_type = cxld->target_type,
+ .flags = cxld->flags,
+ };
- 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_endpoint_decoder(&cxld->dev)) {
+ struct cxl_endpoint_decoder *cxled =
+ to_cxl_endpoint_decoder(&cxld->dev);
- if (is_switch_decoder(&cxld->dev)) {
+ settings->target_or_skip = cxled->skip;
+ } else 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);
+ settings->target_or_skip = cxl_switch_get_target_list(cxlsd);
}
-
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
}
static int cxl_decoder_commit(struct cxl_decoder *cxld)
@@ -802,7 +727,8 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
- int id = cxld->id, rc;
+ struct cxl_decoder_settings settings;
+ int id = cxld->id, rc = 0;
if (cxld->flags & CXL_DECODER_F_ENABLE)
return 0;
@@ -815,10 +741,6 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
return -EBUSY;
}
- /*
- * For endpoint decoders hosted on CXL memory devices that
- * support the sanitize operation, make sure sanitize is not in-flight.
- */
if (is_endpoint_decoder(&cxld->dev)) {
struct cxl_endpoint_decoder *cxled =
to_cxl_endpoint_decoder(&cxld->dev);
@@ -826,6 +748,10 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
struct cxl_memdev_state *mds =
to_cxl_memdev_state(cxlmd->cxlds);
+ /*
+ * For endpoint decoders hosted on CXL memory devices that
+ * support the sanitize operation, make sure sanitize is not in-flight.
+ */
if (mds && mds->security.sanitize_active) {
dev_dbg(&cxlmd->dev,
"attempted to commit %s during sanitize\n",
@@ -834,10 +760,17 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
}
}
- scoped_guard(rwsem_read, &cxl_rwsem.dpa)
- setup_hw_decoder(cxld, hdm);
+ scoped_guard(rwsem_read, &cxl_rwsem.dpa) {
+ cxl_decoder_snapshot(cxld, &settings);
+ rc = cxl_commit_start(hdm, &settings);
+ }
+ if (rc) {
+ dev_dbg(&port->dev, "%s: error %d committing decoder\n",
+ dev_name(&cxld->dev), rc);
+ return rc;
+ }
- rc = cxld_await_commit(hdm, cxld->id);
+ rc = cxl_commit_wait(hdm, &settings);
if (rc) {
dev_dbg(&port->dev, "%s: error %d committing decoder\n",
dev_name(&cxld->dev), rc);
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
new file mode 100644
index 000000000000..64e63114cfbf
--- /dev/null
+++ b/drivers/cxl/core/resource.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#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),
+};
+
+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 int setup_hw_decoder(void __iomem *hdm,
+ struct cxl_decoder_settings *settings)
+{
+ 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->target_or_skip;
+
+ 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));
+
+ return 0;
+}
+
+int cxl_commit_start(void __iomem *hdm, struct cxl_decoder_settings *settings)
+{
+ lockdep_assert_held(&cxl_rwsem.dpa);
+ return setup_hw_decoder(hdm, settings);
+}
+
+int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings)
+{
+ return cxld_await_commit(hdm, settings->id);
+}
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index cab8ce39f465..03ce50a6a06e 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -264,49 +264,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
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 802b143de83d..c09492af8fbd 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,68 @@ enum cxl_devtype {
CXL_DEVTYPE_CLASSMEM,
};
-struct device;
+struct cxl_region;
+
+enum cxl_decoder_type {
+ CXL_DECODER_DEVMEM = 2,
+ CXL_DECODER_HOSTONLYMEM = 3,
+};
+
+/**
+ * struct cxl_decoder_settings - CXL HDM decoder programming snapshot
+ * @id: decoder id
+ * @hpa_range: Host physical address range mapped by this decoder
+ * @target_or_skip: switch target list or endpoint skip register value
+ * @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
+ */
+struct cxl_decoder_settings {
+ int id;
+ struct range hpa_range;
+ u64 target_or_skip;
+ int interleave_ways;
+ int interleave_granularity;
+ enum cxl_decoder_type target_type;
+ unsigned long flags;
+};
+
+/*
+ * 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
+ * @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;
+ int id;
+ struct range hpa_range;
+ 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,
diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild
index 2be1df80fcc9..6c09932587ce 100644
--- a/tools/testing/cxl/Kbuild
+++ b/tools/testing/cxl/Kbuild
@@ -60,6 +60,7 @@ cxl_core-y += $(CXL_CORE_SRC)/memdev.o
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
cxl_core-y += $(CXL_CORE_SRC)/pci.o
cxl_core-y += $(CXL_CORE_SRC)/hdm.o
+cxl_core-y += $(CXL_CORE_SRC)/resource.o
cxl_core-y += $(CXL_CORE_SRC)/pmu.o
cxl_core-y += $(CXL_CORE_SRC)/cdat.o
cxl_core-$(CONFIG_TRACING) += $(CXL_CORE_SRC)/trace.o
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 02/12] cxl: Make HDM commit helpers available to reset code
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 01/12] cxl: Move HDM decoder programming helpers Srirangan Madhavan
@ 2026-09-10 7:07 ` Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 03/12] cxl: Share HDM decoder decode logic Srirangan Madhavan
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:07 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Build the generic HDM resource helpers under CONFIG_CXL_RESET so PCI
reset paths can use them when cxl_core is modular. Export the lock and
commit entry points consumed by cxl_core, and leave resource.o out of the
mock cxl_core module once it is built into the kernel.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/Kconfig | 4 ++++
drivers/cxl/core/Makefile | 2 +-
drivers/cxl/core/resource.c | 3 +++
tools/testing/cxl/Kbuild | 1 -
4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 80aeb0d556bd..9e6fb2290fed 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_RESET
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_RESET
+ bool
+
endif
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 57995777af0d..30470799e059 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,12 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
+obj-$(CONFIG_CXL_RESET) += resource.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
-cxl_core-y += resource.o
cxl_core-y += pmem.o
cxl_core-y += regs.o
cxl_core-y += memdev.o
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 64e63114cfbf..8d3f43640199 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -13,6 +13,7 @@ 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)
{
@@ -105,8 +106,10 @@ int cxl_commit_start(void __iomem *hdm, struct cxl_decoder_settings *settings)
lockdep_assert_held(&cxl_rwsem.dpa);
return setup_hw_decoder(hdm, settings);
}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start, "cxl_core");
int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings)
{
return cxld_await_commit(hdm, settings->id);
}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit_wait, "cxl_core");
diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild
index 6c09932587ce..2be1df80fcc9 100644
--- a/tools/testing/cxl/Kbuild
+++ b/tools/testing/cxl/Kbuild
@@ -60,7 +60,6 @@ cxl_core-y += $(CXL_CORE_SRC)/memdev.o
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
cxl_core-y += $(CXL_CORE_SRC)/pci.o
cxl_core-y += $(CXL_CORE_SRC)/hdm.o
-cxl_core-y += $(CXL_CORE_SRC)/resource.o
cxl_core-y += $(CXL_CORE_SRC)/pmu.o
cxl_core-y += $(CXL_CORE_SRC)/cdat.o
cxl_core-$(CONFIG_TRACING) += $(CXL_CORE_SRC)/trace.o
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 03/12] cxl: Share HDM decoder decode logic
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 01/12] cxl: Move HDM decoder programming helpers Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 02/12] cxl: Make HDM commit helpers available to reset code Srirangan Madhavan
@ 2026-09-10 7:07 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 04/12] cxl: Cache decoder settings on PCI devices Srirangan Madhavan
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:07 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan,
Fenghua Yu
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, range overflow, interleave, target type, and enable state in one
place before adding another HDM parser.
Keep caller-owned policy out of the decode helper. A committed zero-size
decoder now decodes successfully, while init_hdm_decoder() retains its
existing zero-size rejection.
Preserve endpoint DPA state ownership by using the decoded skip value as
a local input to devm_cxl_dpa_reserve(). The reservation helper updates
cxled->skip under cxl_rwsem.dpa.
Reported-by: Fenghua Yu <fenghua.yu@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/core.h | 4 ++
drivers/cxl/core/hdm.c | 75 +++++++++++++------------------------
drivers/cxl/core/port.c | 19 ++++++++++
drivers/cxl/core/resource.c | 43 +++++++++++++++++++++
4 files changed, 91 insertions(+), 50 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 48fbc635975a..5350c154d88e 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -157,6 +157,7 @@ long cxl_pci_get_latency(struct pci_dev *pdev);
int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c);
int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
struct access_coordinate *c);
+struct pci_dev *cxl_port_get_uport_pci_dev(struct cxl_port *port);
static inline struct device *port_to_host(struct cxl_port *port)
{
@@ -215,6 +216,9 @@ int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm,
struct cxl_endpoint_dvsec_info *info);
int cxl_commit_start(void __iomem *hdm, struct cxl_decoder_settings *settings);
int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings);
+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_port_get_possible_dports(struct cxl_port *port);
#ifdef CONFIG_CXL_FEATURES
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 70ffebd3e213..d621d827f59f 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -907,14 +907,11 @@ 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;
- u32 ctrl;
- union {
- u64 value;
- unsigned char target_id[8];
- } target_list;
+ u32 ctrl, tl_low, tl_high;
if (should_emulate_decoders(info))
return cxl_setup_hdm_decoder_from_dvsec(port, cxld, dpa_base,
@@ -927,35 +924,33 @@ 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);
+ tl_low = readl(hdm + CXL_HDM_DECODER0_TL_LOW(which));
+ tl_high = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(which));
+ rc = cxl_hdm_decode_decoder(&settings, which, ctrl, base, size,
+ ((u64)tl_high << 32) | tl_low, &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->hpa_range = settings.hpa_range;
+ cxld->interleave_ways = settings.interleave_ways;
+ cxld->interleave_granularity = settings.interleave_granularity;
+ cxld->target_type = settings.target_type;
+ cxld->flags = settings.flags;
+ 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,
- };
+ if (!cxled && cxld->interleave_ways > 8)
+ return -ENXIO;
/* 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,
@@ -995,33 +990,15 @@ 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;
for (i = 0; i < cxld->interleave_ways; i++)
- cxld->target_map[i] = target_list.target_id[i];
+ cxld->target_map[i] = i < 4 ?
+ (tl_low >> (i * 8)) & 0xff :
+ (tl_high >> ((i - 4) * 8)) & 0xff;
return 0;
}
@@ -1036,9 +1013,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 = settings.target_or_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/port.c b/drivers/cxl/core/port.c
index 625e4aa427db..5085574521c6 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -34,6 +34,25 @@
static DEFINE_IDA(cxl_port_ida);
static DEFINE_XARRAY(cxl_root_buses);
+struct pci_dev *cxl_port_get_uport_pci_dev(struct cxl_port *port)
+{
+ struct device *uport = port->uport_dev;
+ struct device *host;
+
+ if (is_cxl_memdev(uport)) {
+ struct cxl_memdev *cxlmd = to_cxl_memdev(uport);
+
+ host = cxlmd->dev.parent;
+ } else {
+ host = uport;
+ }
+
+ if (!host || !dev_is_pci(host))
+ return NULL;
+
+ return pci_dev_get(to_pci_dev(host));
+}
+
/*
* The terminal device in PCI is NULL and @platform_bus
* for platform devices (for cxl_test)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 8d3f43640199..e6aa55079c76 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -113,3 +113,46 @@ int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings)
return cxld_await_commit(hdm, settings->id);
}
EXPORT_SYMBOL_FOR_MODULES(cxl_commit_wait, "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,
+ .target_or_skip = 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;
+
+ 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");
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 04/12] cxl: Cache decoder settings on PCI devices
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (2 preceding siblings ...)
2026-09-10 7:07 ` [PATCH v12 03/12] cxl: Share HDM decoder decode logic Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 05/12] cxl: Cache endpoint decoder settings during PCI enumeration Srirangan Madhavan
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Add CXL core plumbing to refresh a PCI device HDM decoder cache when
decoders are enumerated, committed, or reset. PCI reset paths can use
this snapshot to restore HDM programming without walking CXL topology
during reset recovery.
The cache is populated by PCI-side discovery in a follow-on patch. Until
then, the CXL core update path is a no-op when no PCI HDM cache is
present.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/hdm.c | 68 +++++++++++++++++++++++++++++++++++++++++-
include/cxl/cxl.h | 12 ++++++++
include/linux/pci.h | 6 ++++
3 files changed, 85 insertions(+), 1 deletion(-)
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index d621d827f59f..0927036aed27 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -16,6 +16,9 @@
* for enumerating these registers and capabilities.
*/
+static void cxl_decoder_snapshot(struct cxl_decoder *cxld,
+ struct cxl_decoder_settings *settings);
+
static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld)
{
int rc;
@@ -84,6 +87,62 @@ static void parse_hdm_decoder_caps(struct cxl_hdm *cxlhdm)
cxlhdm->iw_cap_mask |= BIT(16);
}
+static bool __cxl_pci_hdm_decoder_count_match(struct pci_dev *pdev,
+ int decoder_count)
+{
+ struct cxl_hdm_info *info;
+ bool match = true;
+
+ down_read(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ if (info) {
+ if (info->decoder_count != decoder_count) {
+ pci_warn(pdev,
+ "CXL HDM cache decoder count mismatch: cached=%d hdm=%d\n",
+ info->decoder_count, decoder_count);
+ match = false;
+ }
+ }
+ up_read(&cxl_rwsem.dpa);
+
+ return match;
+}
+
+static bool cxl_pci_hdm_decoder_count_match(struct cxl_hdm *cxlhdm)
+{
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(cxlhdm->port);
+
+ if (!pdev)
+ return true;
+
+ return __cxl_pci_hdm_decoder_count_match(pdev, cxlhdm->decoder_count);
+}
+
+static void cxl_hdm_save_decoder_info(struct cxl_hdm *cxlhdm,
+ struct cxl_decoder *cxld)
+{
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(cxlhdm->port);
+ struct cxl_decoder_settings *settings;
+ struct cxl_hdm_info *info;
+
+ if (!pdev)
+ return;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ if (!info || cxld->id >= info->decoder_count)
+ return;
+
+ settings = &info->settings[cxld->id];
+ *settings = (struct cxl_decoder_settings) {
+ .id = cxld->id,
+ };
+ if (cxld->flags & CXL_DECODER_F_ENABLE)
+ cxl_decoder_snapshot(cxld, settings);
+}
+
static bool should_emulate_decoders(struct cxl_endpoint_dvsec_info *info)
{
struct cxl_hdm *cxlhdm;
@@ -778,6 +837,7 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
}
port->commit_end++;
cxld->flags |= CXL_DECODER_F_ENABLE;
+ cxl_hdm_save_decoder_info(cxlhdm, cxld);
return 0;
}
@@ -850,6 +910,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_save_decoder_info(cxlhdm, cxld);
/* Userspace is now responsible for reconfiguring this decoder */
if (is_endpoint_decoder(&cxld->dev)) {
@@ -1068,11 +1129,15 @@ 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);
+ if (!cxl_pci_hdm_decoder_count_match(cxlhdm))
+ return -ENXIO;
+
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)) {
@@ -1107,6 +1172,7 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
put_device(&cxld->dev);
return rc;
}
+ cxl_hdm_save_decoder_info(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 c09492af8fbd..ed5237df510f 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -133,6 +133,18 @@ struct cxl_regs {
);
};
+#define CXL_HDM_DECODER_MAX_COUNT 32
+
+/**
+ * struct cxl_hdm_info - PCI device HDM decoder programming cache
+ * @decoder_count: number of decoder settings entries
+ * @settings: cached per-decoder programming state
+ */
+struct cxl_hdm_info {
+ int decoder_count;
+ struct cxl_decoder_settings settings[CXL_HDM_DECODER_MAX_COUNT];
+};
+
struct cxl_reg_map {
bool valid;
int id;
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d31a8d107b1e..84d058b1b492 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -339,6 +339,9 @@ struct pcie_link_state;
struct pci_sriov;
struct pci_p2pdma;
struct rcec_ea;
+#ifdef CONFIG_CXL_RESET
+struct cxl_hdm_info;
+#endif
/* struct pci_dev - describes a PCI device
*
@@ -566,6 +569,9 @@ struct pci_dev {
#ifdef CONFIG_PCI_DOE
struct xarray doe_mbs; /* Data Object Exchange mailboxes */
#endif
+#ifdef CONFIG_CXL_RESET
+ 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 [flat|nested] 14+ messages in thread
* [PATCH v12 05/12] cxl: Cache endpoint decoder settings during PCI enumeration
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (3 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 04/12] cxl: Cache decoder settings on PCI devices Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 06/12] cxl: Add CXL Device Reset helper Srirangan Madhavan
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Populate pci_dev->hdm for CXL.mem functions from pci_bus_add_device(),
after final PCI fixups and state save but before driver binding. This
gives driver-free reset paths an early HDM snapshot while avoiding the
pre-resource-assignment window in PCI capability initialization.
Use the CXL Register Locator BAR Indicator to find the component register
BAR, reject unassigned, disabled, or zero memory BAR resources before
temporarily enabling Memory Space, and restore the original PCI_COMMAND
value before returning. Cache the CXL Device DVSEC control register with
the HDM state for reset recovery before a driver can alter it.
CXL core refreshes the cache as decoders are committed or reset, and keeps
the cached DVSEC control synchronized when CXL.mem is enabled or disabled.
Move the register helpers into the built-in CONFIG_CXL_RESET set so the
early cache path is available without cxl_core, and keep the cxl-test mock
core from building a duplicate regs.o.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/Makefile | 3 +-
drivers/cxl/core/core.h | 1 +
drivers/cxl/core/hdm.c | 45 ++---
drivers/cxl/core/pci.c | 22 ++-
drivers/cxl/core/regs.c | 4 +
drivers/cxl/core/resource.c | 336 ++++++++++++++++++++++++++++++++++++
drivers/pci/bus.c | 2 +
drivers/pci/probe.c | 2 +
include/cxl/cxl.h | 30 ++++
tools/testing/cxl/Kbuild | 1 -
10 files changed, 417 insertions(+), 29 deletions(-)
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 30470799e059..2335059f687a 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_RESET) += resource.o
+obj-$(CONFIG_CXL_RESET) += regs.o resource.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/core.h b/drivers/cxl/core/core.h
index 5350c154d88e..8dc33ea933c3 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -158,6 +158,7 @@ int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c);
int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
struct access_coordinate *c);
struct pci_dev *cxl_port_get_uport_pci_dev(struct cxl_port *port);
+int cxl_pci_get_device_dvsec_cap(struct pci_dev *pdev, int dvsec, u16 *cap);
static inline struct device *port_to_host(struct cxl_port *port)
{
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 0927036aed27..90a0fe6dff92 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -87,36 +87,26 @@ static void parse_hdm_decoder_caps(struct cxl_hdm *cxlhdm)
cxlhdm->iw_cap_mask |= BIT(16);
}
-static bool __cxl_pci_hdm_decoder_count_match(struct pci_dev *pdev,
- int decoder_count)
-{
- struct cxl_hdm_info *info;
- bool match = true;
-
- down_read(&cxl_rwsem.dpa);
- info = pdev->hdm;
- if (info) {
- if (info->decoder_count != decoder_count) {
- pci_warn(pdev,
- "CXL HDM cache decoder count mismatch: cached=%d hdm=%d\n",
- info->decoder_count, decoder_count);
- match = false;
- }
- }
- up_read(&cxl_rwsem.dpa);
-
- return match;
-}
-
static bool cxl_pci_hdm_decoder_count_match(struct cxl_hdm *cxlhdm)
{
struct pci_dev *pdev __free(pci_dev_put) =
cxl_port_get_uport_pci_dev(cxlhdm->port);
+ struct cxl_hdm_info *info;
if (!pdev)
return true;
- return __cxl_pci_hdm_decoder_count_match(pdev, cxlhdm->decoder_count);
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ if (!info)
+ return true;
+ if (info->decoder_count == cxlhdm->decoder_count)
+ return true;
+
+ pci_warn(pdev,
+ "CXL HDM cache decoder count mismatch: cached=%d hdm=%d\n",
+ info->decoder_count, cxlhdm->decoder_count);
+ return false;
}
static void cxl_hdm_save_decoder_info(struct cxl_hdm *cxlhdm,
@@ -126,10 +116,17 @@ static void cxl_hdm_save_decoder_info(struct cxl_hdm *cxlhdm,
cxl_port_get_uport_pci_dev(cxlhdm->port);
struct cxl_decoder_settings *settings;
struct cxl_hdm_info *info;
+ u32 global_ctrl = 0;
+ bool has_hdm_decoder;
if (!pdev)
return;
+ has_hdm_decoder = !!cxlhdm->regs.hdm_decoder;
+ if (has_hdm_decoder)
+ global_ctrl = readl(cxlhdm->regs.hdm_decoder +
+ CXL_HDM_DECODER_CTRL_OFFSET);
+
guard(rwsem_write)(&cxl_rwsem.dpa);
info = pdev->hdm;
if (!info || cxld->id >= info->decoder_count)
@@ -139,6 +136,10 @@ static void cxl_hdm_save_decoder_info(struct cxl_hdm *cxlhdm,
*settings = (struct cxl_decoder_settings) {
.id = cxld->id,
};
+
+ if (has_hdm_decoder)
+ info->global_ctrl = global_ctrl;
+
if (cxld->flags & CXL_DECODER_F_ENABLE)
cxl_decoder_snapshot(cxld, settings);
}
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 9d807c1a002c..0cd52753f5ef 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -180,6 +180,16 @@ int cxl_await_media_ready(struct cxl_dev_state *cxlds)
}
EXPORT_SYMBOL_NS_GPL(cxl_await_media_ready, "CXL");
+static void cxl_hdm_cache_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl)
+{
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ if (!pdev->hdm)
+ return;
+
+ pdev->hdm->dvsec_ctrl = ctrl;
+ pdev->hdm->dvsec_ctrl_valid = true;
+}
+
static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
@@ -191,8 +201,10 @@ static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
if (rc)
return pcibios_err_to_errno(rc);
- if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) == val)
+ if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) == val) {
+ cxl_hdm_cache_dvsec_ctrl(pdev, ctrl);
return 1;
+ }
ctrl &= ~PCI_DVSEC_CXL_MEM_ENABLE;
ctrl |= val;
@@ -200,6 +212,7 @@ static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
if (rc)
return pcibios_err_to_errno(rc);
+ cxl_hdm_cache_dvsec_ctrl(pdev, ctrl);
return 0;
}
@@ -274,9 +287,10 @@ int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds,
return -ENXIO;
}
- rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CAP, &cap);
- if (rc)
- return pcibios_err_to_errno(rc);
+ rc = cxl_pci_get_device_dvsec_cap(pdev, d, &cap);
+ if (rc < 0)
+ return rc;
+ d = rc;
if (!(cap & PCI_DVSEC_CXL_MEM_CAPABLE)) {
dev_dbg(dev, "Not MEM Capable\n");
diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c
index 20c2d9fbcfe7..989e79383b99 100644
--- a/drivers/cxl/core/regs.c
+++ b/drivers/cxl/core/regs.c
@@ -200,6 +200,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,
@@ -518,6 +519,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)
{
@@ -634,6 +636,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)
@@ -642,3 +645,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/resource.c b/drivers/cxl/core/resource.c
index e6aa55079c76..6d9f8fe14b16 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -2,9 +2,17 @@
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
#include <linux/delay.h>
#include <linux/bug.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.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 <cxl/pci.h>
#include "cxl.h"
#include "core.h"
@@ -156,3 +164,331 @@ 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;
+};
+
+static void cxl_pci_hdm_info_free(struct cxl_hdm_info *info)
+{
+ if (!info)
+ return;
+
+ kfree(info->decoder_state);
+ kfree(info);
+}
+
+DEFINE_FREE(cxl_pci_hdm_info, struct cxl_hdm_info *,
+ cxl_pci_hdm_info_free(_T))
+
+void pci_cxl_hdm_release(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *info = pdev->hdm;
+
+ pdev->hdm = NULL;
+ cxl_pci_hdm_info_free(info);
+}
+
+static bool cxl_pci_bar_usable(struct pci_dev *pdev, int bar)
+{
+ struct resource *res = &pdev->resource[bar];
+
+ if (!pci_resource_len(pdev, bar))
+ return false;
+ if (res->flags & (IORESOURCE_UNSET | IORESOURCE_DISABLED))
+ return false;
+ if (resource_type(res) != IORESOURCE_MEM)
+ return false;
+ if (!res->start || !res->end)
+ return false;
+
+ return true;
+}
+
+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;
+
+ if (!hdm_size)
+ return -EINVAL;
+
+ hdm_end = hdm_start + hdm_size - 1;
+ if (hdm_end < hdm_start)
+ return -EINVAL;
+
+ for (int i = 0; i < PCI_STD_NUM_BARS; i++) {
+ struct resource *res = &pdev->resource[i];
+
+ if (!cxl_pci_bar_usable(pdev, i))
+ continue;
+ if (hdm_start < res->start || hdm_end > res->end)
+ continue;
+
+ if (bar)
+ *bar = i;
+ if (offset)
+ *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;
+ 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, NULL);
+ if (rc) {
+ pci_err(pdev, "CXL HDM decoder %d has invalid configuration: %d\n",
+ id, rc);
+ return rc;
+ }
+ return 0;
+}
+
+int cxl_pci_get_device_dvsec_cap(struct pci_dev *pdev, int dvsec, u16 *cap)
+{
+ int rc;
+
+ if (!dvsec) {
+ 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);
+
+ return dvsec;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_pci_get_device_dvsec_cap, "cxl_core");
+
+DEFINE_FREE(cxl_hdm_iounmap, void __iomem *, if (_T) iounmap(_T))
+
+static bool cxl_pci_hdm_capable(struct pci_dev *pdev)
+{
+ u16 cap;
+ int dvsec;
+
+ dvsec = cxl_pci_get_device_dvsec_cap(pdev, 0, &cap);
+ if (dvsec < 0) {
+ if (dvsec != -ENOTTY)
+ pci_dbg(pdev,
+ "failed to read CXL DVSEC capability: %d\n",
+ dvsec);
+ return false;
+ }
+
+ if (!(cap & PCI_DVSEC_CXL_MEM_CAPABLE))
+ return false;
+
+ if (!FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap))
+ return false;
+
+ return true;
+}
+
+static int __cxl_pci_hdm_read_info(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ struct cxl_hdm_info *info)
+{
+ struct cxl_decoder_settings *settings;
+ int decoder_count;
+ int rc;
+
+ rc = cxl_setup_regs(map);
+ if (rc)
+ return rc;
+
+ if (!map->component_map.hdm_decoder.valid)
+ return -ENODEV;
+
+ void __iomem *hdm __free(cxl_hdm_iounmap) =
+ cxl_pci_hdm_map(pdev, map, info);
+ if (IS_ERR(hdm))
+ return PTR_ERR(no_free_ptr(hdm));
+
+ decoder_count = cxl_hdm_decoder_count(readl(hdm +
+ CXL_HDM_DECODER_CAP_OFFSET));
+ if (decoder_count < 0)
+ return decoder_count;
+
+ if (decoder_count > ARRAY_SIZE(info->settings))
+ return -ENXIO;
+
+ if (CXL_HDM_DECODER0_CTRL_OFFSET(decoder_count - 1) + 0x10 >
+ info->hdm_size) {
+ pci_err(pdev,
+ "CXL HDM decoder count exceeds mapped register block\n");
+ return -ENXIO;
+ }
+
+ 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)
+ return -ENOMEM;
+
+ 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)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_pci_hdm_read_info(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ struct cxl_hdm_info *info)
+{
+ bool restore_command;
+ u16 command;
+ int rc, rc2;
+
+ guard(pci_dev)(pdev);
+
+ rc = pci_read_config_word(pdev, PCI_COMMAND, &command);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ restore_command = !(command & PCI_COMMAND_MEMORY);
+ if (restore_command) {
+ rc = pci_write_config_word(pdev, PCI_COMMAND,
+ command | PCI_COMMAND_MEMORY);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ }
+
+ rc = __cxl_pci_hdm_read_info(pdev, map, info);
+
+ if (!restore_command)
+ return rc;
+
+ rc2 = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc2) {
+ rc2 = pcibios_err_to_errno(rc2);
+ pci_err(pdev,
+ "failed to restore PCI_COMMAND after CXL HDM cache init: %d\n",
+ rc2);
+ if (!rc)
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+static int __pci_cxl_hdm_init(struct pci_dev *pdev)
+{
+ struct cxl_register_map map = { 0 };
+ int dvsec;
+ int rc;
+
+ if (!cxl_pci_hdm_capable(pdev))
+ return -ENOTTY;
+
+ rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
+ if (rc)
+ return rc;
+
+ rc = cxl_pci_hdm_find_bar(pdev, map.resource, map.max_size, NULL, NULL);
+ if (rc)
+ return rc;
+
+ struct cxl_hdm_info *info __free(cxl_pci_hdm_info) =
+ kzalloc_obj(*info, GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ 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_CTRL,
+ &info->dvsec_ctrl);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ info->dvsec_ctrl_valid = true;
+
+ rc = cxl_pci_hdm_read_info(pdev, &map, info);
+ if (rc)
+ return rc;
+
+ pdev->hdm = no_free_ptr(info);
+
+ return 0;
+}
+
+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/bus.c b/drivers/pci/bus.c
index 655ed53436d3..5fd7bae8e786 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -18,6 +18,7 @@
#include <linux/pm_runtime.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
+#include <cxl/cxl.h>
#include "pci.h"
@@ -359,6 +360,7 @@ void pci_bus_add_device(struct pci_dev *dev)
/* Save config space for error recoverability */
pci_save_state(dev);
+ pci_cxl_hdm_init(dev);
/*
* Enable runtime PM, which potentially allows the device to
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 27008e2ea5af..13a2784b8fb8 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 = {
@@ -2484,6 +2485,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);
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index ed5237df510f..c8070d9cfbfb 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,
@@ -135,16 +136,45 @@ 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
+ * @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
+ * @dvsec_ctrl: cached CXL Device DVSEC control register
+ * @dvsec_ctrl_valid: @dvsec_ctrl contains valid state
+ * @decoder_state: cached raw per-decoder register state
* @settings: cached per-decoder programming state
*/
struct cxl_hdm_info {
int decoder_count;
+ int hdm_bar;
+ resource_size_t hdm_offset;
+ resource_size_t hdm_size;
+ u32 global_ctrl;
+ u16 dvsec_ctrl;
+ bool dvsec_ctrl_valid;
+ struct cxl_hdm_decoder_state *decoder_state;
struct cxl_decoder_settings settings[CXL_HDM_DECODER_MAX_COUNT];
};
+#ifdef CONFIG_CXL_RESET
+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;
int id;
diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild
index 2be1df80fcc9..e80500f457a9 100644
--- a/tools/testing/cxl/Kbuild
+++ b/tools/testing/cxl/Kbuild
@@ -55,7 +55,6 @@ obj-m += cxl_core.o
cxl_core-y := $(CXL_CORE_SRC)/port.o
cxl_core-y += $(CXL_CORE_SRC)/pmem.o
-cxl_core-y += $(CXL_CORE_SRC)/regs.o
cxl_core-y += $(CXL_CORE_SRC)/memdev.o
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
cxl_core-y += $(CXL_CORE_SRC)/pci.o
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 06/12] cxl: Add CXL Device Reset helper
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (4 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 05/12] cxl: Cache endpoint decoder settings during PCI enumeration Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 07/12] cxl: Validate HDM ranges before CXL reset Srirangan Madhavan
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
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 and CXL Reset Memory Clear in the CXL Device DVSEC.
The helper disables CXL.cache, performs cache writeback when supported,
initiates reset with Memory Clear enabled, 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/resource.c | 251 ++++++++++++++++++++++++++++++++++
include/cxl/cxl.h | 7 +
include/uapi/linux/pci_regs.h | 14 ++
3 files changed, 272 insertions(+)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 6d9f8fe14b16..9c1aa0800521 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -8,6 +8,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>
@@ -492,3 +494,252 @@ 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_get_dvsec(struct pci_dev *pdev, u16 *cap_out)
+{
+ int dvsec, rc;
+ u16 cap, ctrl;
+
+ dvsec = cxl_pci_get_device_dvsec_cap(pdev, 0, &cap);
+ if (dvsec < 0)
+ return dvsec;
+
+ if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_MEM_CAPABLE))
+ return -ENOTTY;
+
+ if (!(cap & PCI_DVSEC_CXL_RST_CAPABLE))
+ return -ENOTTY;
+ if (!(cap & PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE))
+ return -ENOTTY;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL, &ctrl);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ if (!(ctrl & PCI_DVSEC_CXL_CACHE_ENABLE) ||
+ !(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
+ return -ENOTTY;
+
+ *cap_out = cap;
+ return dvsec;
+}
+
+#define CXL_RESET_CTRL2_CMD_MASK \
+ (PCI_DVSEC_CXL_INIT_CACHE_WBI | PCI_DVSEC_CXL_INIT_CXL_RST)
+
+static int cxl_reset_read_ctrl2(struct pci_dev *pdev, int dvsec, 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 &= ~CXL_RESET_CTRL2_CMD_MASK;
+ return 0;
+}
+
+static int cxl_reset_write_ctrl2(struct pci_dev *pdev, int dvsec, u16 ctrl2)
+{
+ int rc;
+
+ 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_modify_ctrl2(struct pci_dev *pdev, int dvsec, u16 set,
+ u16 clear)
+{
+ u16 ctrl2;
+ int rc;
+
+ rc = cxl_reset_read_ctrl2(pdev, dvsec, &ctrl2);
+ if (rc)
+ return rc;
+
+ ctrl2 &= ~clear;
+ ctrl2 |= set;
+ return cxl_reset_write_ctrl2(pdev, dvsec, ctrl2);
+}
+
+static int cxl_reset_enable_cache(struct pci_dev *pdev, int dvsec)
+{
+ return cxl_reset_modify_ctrl2(pdev, dvsec, 0,
+ PCI_DVSEC_CXL_DISABLE_CACHING);
+}
+
+static int cxl_reset_initiate(struct pci_dev *pdev, int dvsec)
+{
+ return cxl_reset_modify_ctrl2(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CXL_RST |
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN, 0);
+}
+
+static int cxl_reset_wait_cache_wbi(struct pci_dev *pdev, int dvsec)
+{
+ unsigned long deadline;
+ u16 status2;
+ int rc;
+
+ rc = cxl_reset_modify_ctrl2(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CACHE_WBI, 0);
+ if (rc)
+ return rc;
+
+ deadline = jiffies + usecs_to_jiffies(CXL_CACHE_WBI_TIMEOUT_US);
+ do {
+ usleep_range(CXL_CACHE_WBI_POLL_US, CXL_CACHE_WBI_POLL_US + 1);
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (status2 != U16_MAX && (status2 & PCI_DVSEC_CXL_CACHE_INV))
+ return 0;
+ } while (time_before(jiffies, deadline));
+
+ return -ETIMEDOUT;
+}
+
+static int cxl_reset_disable_cache(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc, rc2;
+
+ rc = cxl_reset_modify_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_wait_cache_wbi(pdev, dvsec);
+ if (!rc)
+ return 0;
+
+ /*
+ * Restore the default CXL.cache policy after error paths that
+ * successfully set DISABLE_CACHING before reset completion.
+ */
+ 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;
+ bool final = false;
+ 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);
+ msleep(CXL_RESET_RRS_WAIT_MS);
+ deadline = jiffies + msecs_to_jiffies(timeout_ms -
+ CXL_RESET_RRS_WAIT_MS);
+
+ do {
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ if (status2 != U16_MAX) {
+ if (status2 & PCI_DVSEC_CXL_RST_ERR)
+ return -EIO;
+
+ if (status2 & PCI_DVSEC_CXL_RST_DONE)
+ return 0;
+ }
+
+ if (time_after_eq(jiffies, deadline)) {
+ if (final)
+ return -ETIMEDOUT;
+ final = true;
+ continue;
+ }
+
+ msleep(CXL_RESET_STATUS_POLL_MS);
+ } while (true);
+}
+
+static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ bool target_prepared = false;
+ int rc, rc2;
+
+ rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ if (rc)
+ return 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);
+ else
+ target_prepared = true;
+
+ if (!rc)
+ rc = cxl_reset_initiate(pdev, dvsec);
+ if (!rc)
+ rc = cxl_reset_wait_done(pdev, dvsec, cap);
+
+ 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;
+
+ if (target_prepared)
+ pci_dev_reset_iommu_done(pdev);
+ return rc;
+}
+
+int cxl_reset_function(struct pci_dev *pdev, bool probe)
+{
+ int dvsec;
+ u16 cap;
+
+ dvsec = cxl_reset_get_dvsec(pdev, &cap);
+ if (dvsec < 0)
+ return dvsec;
+
+ if (probe)
+ return 0;
+
+ return cxl_reset_execute(pdev, dvsec, cap);
+}
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index c8070d9cfbfb..856abbcd5f18 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>
/**
@@ -165,6 +166,7 @@ struct cxl_hdm_info {
#ifdef CONFIG_CXL_RESET
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)
{
@@ -173,6 +175,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 facaa324bd86..4f5942ebd29b 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1352,8 +1352,22 @@
#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_CACHE_ENABLE _BITUL(0)
#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 [flat|nested] 14+ messages in thread
* [PATCH v12 07/12] cxl: Validate HDM ranges before CXL reset
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (5 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 06/12] cxl: Add CXL Device Reset helper Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 08/12] PCI/CXL: Reject CXL Reset on multifunction devices Srirangan Madhavan
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Before reset, require cached HDM decoder state, collect enabled 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.
Reject CXL Reset when no cached HDM decoder state is available. The reset
path needs the cached address map to validate affected ranges and perform
CPU cache invalidation. Also reject normalized-addressing decoders for
now because the cached decoder range is not a system physical address.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/resource.c | 255 +++++++++++++++++++++++++++++++++++-
1 file changed, 254 insertions(+), 1 deletion(-)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 9c1aa0800521..7e4eee3e50c0 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -11,6 +11,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>
@@ -509,6 +511,229 @@ 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;
+ u64 len;
+ 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);
+ }
+}
+
+/*
+ * Bound the range twice: request_mem_region() takes resource_size_t while
+ * cpu_cache_invalidate_memregion() takes size_t, and the two differ on
+ * 32-bit builds with CONFIG_PHYS_ADDR_T_64BIT. range_len() can also reach
+ * RESOURCE_SIZE_MAX + 1 for a full-width range, and wraps to zero when
+ * resource_size_t is 64-bit, which the !len test catches.
+ */
+static int cxl_hdm_range_validate(struct pci_dev *pdev,
+ const struct range *hpa_range)
+{
+ u64 len = range_len(hpa_range);
+
+ if (!len)
+ return -EINVAL;
+
+ if (hpa_range->end > RESOURCE_SIZE_MAX) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource address size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (len > RESOURCE_SIZE_MAX) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (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_add(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev, const struct range *hpa_range)
+{
+ struct cxl_hdm_range *range;
+ int rc;
+
+ rc = cxl_hdm_range_validate(pdev, hpa_range);
+ if (rc)
+ return rc;
+
+ 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;
+ range->len = range_len(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;
+ int rc;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ 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;
+
+ /* A committed zero-size decoder maps no HPA. */
+ if (!range_len(&settings->hpa_range))
+ 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_request(struct cxl_hdm_range *range)
+{
+ struct pci_dev *pdev = range->pdev;
+ const struct range *hpa_range = &range->hpa_range;
+
+ range->res = request_mem_region(hpa_range->start, range->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;
+ int rc;
+
+ rc = cpu_cache_invalidate_memregion(hpa_range->start, range->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_get_dvsec(struct pci_dev *pdev, u16 *cap_out)
{
int dvsec, rc;
@@ -539,6 +764,20 @@ static int cxl_reset_get_dvsec(struct pci_dev *pdev, u16 *cap_out)
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;
+}
+
#define CXL_RESET_CTRL2_CMD_MASK \
(PCI_DVSEC_CXL_INIT_CACHE_WBI | PCI_DVSEC_CXL_INIT_CXL_RST)
@@ -731,7 +970,9 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
+ struct cxl_hdm_range_context range_ctx;
int dvsec;
+ int rc;
u16 cap;
dvsec = cxl_reset_get_dvsec(pdev, &cap);
@@ -741,5 +982,17 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (probe)
return 0;
- return cxl_reset_execute(pdev, dvsec, cap);
+ if (!cxl_reset_hdm_available(pdev))
+ return -ENOTTY;
+
+ 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, cap);
+ cxl_hdm_range_context_destroy(&range_ctx);
+ }
+
+ return rc;
}
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 08/12] PCI/CXL: Reject CXL Reset on multifunction devices
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (6 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 07/12] cxl: Validate HDM ranges before CXL reset Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 09/12] cxl: Restore CXL state after PCI reset Srirangan Madhavan
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
CXL Reset can be exposed as a PCI function reset method only when the
reset is function-scoped. PCI reset-method probing runs during
enumeration, so walking sibling functions there can race with functions
that have not been added to the bus yet.
Reject multifunction devices and devices with enabled VFs instead of
trying to infer reset scope from sibling state. Check both conditions on
each invocation because VF enablement can change after reset-method
probing. Systems that can prove a multifunction device has
function-scoped CXL Reset can be enabled later.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
---
drivers/cxl/core/resource.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 7e4eee3e50c0..ac0284fec51e 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -979,6 +979,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (dvsec < 0)
return dvsec;
+ if (pdev->multifunction || pci_num_vf(pdev))
+ return -ENOTTY;
+
if (probe)
return 0;
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 09/12] cxl: Restore CXL state after PCI reset
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (7 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 08/12] PCI/CXL: Reject CXL Reset on multifunction devices Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 10/12] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
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, restore the cached
CXL.cache and CXL.mem enable bits, and then run the normal PCI restore
callbacks.
If reset clears a previously locked decoder, restore and commit its
programming before reapplying the cached lock. Leave a locked committed
decoder unchanged when that state survives reset.
Keep the target IOMMU reset block active until CXL state 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/resource.c | 411 ++++++++++++++++++++++++++++++++++--
1 file changed, 396 insertions(+), 15 deletions(-)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index ac0284fec51e..e1d09f9042fa 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/memregion.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -83,6 +84,26 @@ 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_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 int setup_hw_decoder(void __iomem *hdm,
struct cxl_decoder_settings *settings)
{
@@ -272,6 +293,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)
{
@@ -284,6 +330,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,
@@ -496,6 +576,269 @@ 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);
}
+static int cxl_hdm_decoder_uncommit(struct pci_dev *pdev, void __iomem *hdm,
+ int id)
+{
+ u32 ctrl;
+ int rc;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return -EIO;
+
+ 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);
+ return -EBUSY;
+ }
+
+ pci_err(pdev, "CXL HDM decoder %d is locked and uncommitted\n",
+ id);
+ return -EIO;
+ }
+
+ 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 |
+ CXL_HDM_DECODER0_CTRL_LOCK);
+
+ 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));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+
+static int cxl_restore_dvsec_ctrl(struct pci_dev *pdev, u16 saved_ctrl)
+{
+ u16 mask = PCI_DVSEC_CXL_CACHE_ENABLE |
+ PCI_DVSEC_CXL_MEM_ENABLE;
+ u16 ctrl;
+ 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_CTRL, &ctrl);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ ctrl = (ctrl & ~mask) | (saved_ctrl & mask);
+ rc = pci_write_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL, ctrl);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ return 0;
+}
+
+static int cxl_restore_hdm_decoder(struct pci_dev *pdev,
+ struct cxl_hdm_decoder_state *state,
+ struct cxl_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ u32 ctrl;
+ int rc;
+
+ rc = cxl_hdm_decoder_uncommit(pdev, hdm, settings->id);
+ if (rc == -EBUSY)
+ return 0;
+ if (rc)
+ return rc;
+
+ cxl_restore_hdm_decoder_state(state, hdm, settings->id);
+
+ if (!(settings->flags & CXL_DECODER_F_ENABLE))
+ return 0;
+
+ scoped_guard(rwsem_read, &cxl_rwsem.dpa)
+ rc = cxl_commit_start(hdm, settings);
+ if (!rc)
+ rc = cxl_commit_wait(hdm, settings);
+ if (rc)
+ pci_err(pdev, "CXL HDM decoder %d restore failed: %d\n",
+ settings->id, rc);
+ else if (settings->flags & CXL_DECODER_F_LOCK) {
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(settings->id));
+ ctrl |= CXL_HDM_DECODER0_CTRL_LOCK;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(settings->id));
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(settings->id));
+ if (PCI_POSSIBLE_ERROR(ctrl) ||
+ !(ctrl & CXL_HDM_DECODER0_CTRL_LOCK)) {
+ pci_err(pdev,
+ "CXL HDM decoder %d failed to restore lock\n",
+ settings->id);
+ return -EIO;
+ }
+ }
+
+ return rc;
+}
+
+static struct cxl_hdm_info *cxl_snapshot_hdm(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *snap;
+ struct cxl_hdm_info *info;
+ size_t state_sz;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+
+ info = pdev->hdm;
+ if (!info)
+ return NULL;
+ if (info->decoder_count < 0 ||
+ info->decoder_count > CXL_HDM_DECODER_MAX_COUNT ||
+ (info->decoder_count && !info->decoder_state))
+ return ERR_PTR(-EINVAL);
+
+ state_sz = array_size(info->decoder_count, sizeof(*info->decoder_state));
+ snap = kzalloc(size_add(sizeof(*snap), state_sz), GFP_KERNEL);
+ if (!snap)
+ return ERR_PTR(-ENOMEM);
+
+ *snap = *info;
+ snap->decoder_state = (void *)(snap + 1);
+ memcpy(snap->decoder_state, info->decoder_state, state_sz);
+
+ return snap;
+}
+
+static void cxl_restore_pci_state_for_hdm_restore(struct pci_dev *pdev,
+ u16 *command)
+{
+ u32 saved_config = pdev->saved_config_space[PCI_COMMAND / 4];
+
+ pdev->saved_config_space[PCI_COMMAND / 4] &= ~PCI_COMMAND_MASTER;
+ pdev->saved_config_space[PCI_COMMAND / 4] |= PCI_COMMAND_INTX_DISABLE;
+ pci_restore_state(pdev);
+ pdev->saved_config_space[PCI_COMMAND / 4] = saved_config;
+ *command = saved_config & 0xffff;
+}
+
+static int cxl_restore_state(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *snap = cxl_snapshot_hdm(pdev);
+ bool restore_command = false;
+ void __iomem *hdm;
+ int first_rc = 0;
+ u16 command;
+ int rc;
+
+ if (!snap)
+ return 0;
+ if (IS_ERR(snap))
+ return PTR_ERR(snap);
+
+ rc = cxl_hdm_enable_mem(pdev, &command, &restore_command);
+ if (rc) {
+ kfree(snap);
+ return rc;
+ }
+
+ hdm = cxl_pci_hdm_ioremap_current(pdev, snap->hdm_bar,
+ snap->hdm_offset, snap->hdm_size);
+ if (IS_ERR(hdm)) {
+ first_rc = PTR_ERR(hdm);
+ } else {
+ /*
+ * Restore global HDM control before per-decoder commit. PCI
+ * config memory decoding is enabled for MMIO access, but bus
+ * mastering remains disabled until HDM restore completes.
+ */
+ writel(snap->global_ctrl, hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+
+ for (int i = 0; i < snap->decoder_count; i++) {
+ rc = cxl_restore_hdm_decoder(pdev,
+ &snap->decoder_state[i],
+ &snap->settings[i], hdm);
+ if (rc && !first_rc)
+ first_rc = rc;
+ }
+
+ /* Flush posted HDM writes before PCI_COMMAND can restore BME. */
+ readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ iounmap(hdm);
+ }
+
+ if (!first_rc && snap->dvsec_ctrl_valid) {
+ rc = cxl_restore_dvsec_ctrl(pdev, snap->dvsec_ctrl);
+ if (rc)
+ first_rc = rc;
+ }
+
+ if (restore_command) {
+ rc = cxl_hdm_restore_command(pdev, command);
+ if (rc && !first_rc)
+ first_rc = rc;
+ }
+
+ kfree(snap);
+ return first_rc;
+}
+
+static void cxl_reset_save_disabled_state(struct pci_dev *pdev)
+{
+ int rc;
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
+ if (rc) {
+ pci_warn(pdev, "failed to keep device disabled after CXL reset restore failure: %d\n",
+ pcibios_err_to_errno(rc));
+ return;
+ }
+
+ rc = pci_save_state(pdev);
+ if (rc)
+ pci_warn(pdev, "failed to save disabled state after CXL reset restore failure: %d\n",
+ rc);
+}
+
+static int cxl_reset_save_restored_state(struct pci_dev *pdev, u16 command)
+{
+ int rc;
+
+ rc = cxl_hdm_restore_command(pdev, command);
+ if (rc) {
+ cxl_reset_save_disabled_state(pdev);
+ return rc;
+ }
+
+ rc = pci_save_state(pdev);
+ if (rc) {
+ pci_warn(pdev, "failed to save restored state after CXL reset: %d\n",
+ rc);
+ cxl_reset_save_disabled_state(pdev);
+ }
+
+ return 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
@@ -523,6 +866,34 @@ struct cxl_hdm_range_context {
struct list_head ranges;
};
+static void cxl_pci_target_reset_done(struct pci_dev *pdev,
+ bool *target_prepared)
+{
+ if (!*target_prepared)
+ return;
+
+ pci_dev_reset_iommu_done(pdev);
+ *target_prepared = false;
+}
+
+static int cxl_pci_target_reset_prepare(struct pci_dev *pdev,
+ bool *target_prepared)
+{
+ 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;
+ }
+
+ *target_prepared = true;
+ return 0;
+}
+
static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
{
INIT_LIST_HEAD(&ctx->ranges);
@@ -934,26 +1305,21 @@ static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
} while (true);
}
-static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+static int cxl_reset_execute(struct pci_dev *pdev, bool *target_prepared,
+ bool *reset_initiated, int dvsec, u16 cap)
{
- bool target_prepared = false;
int rc, rc2;
rc = cxl_reset_disable_cache(pdev, dvsec, cap);
if (rc)
return 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);
- else
- target_prepared = true;
-
- if (!rc)
+ rc = cxl_pci_target_reset_prepare(pdev, target_prepared);
+ if (!rc) {
rc = cxl_reset_initiate(pdev, dvsec);
+ if (!rc)
+ *reset_initiated = true;
+ }
if (!rc)
rc = cxl_reset_wait_done(pdev, dvsec, cap);
@@ -963,14 +1329,14 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
else if (rc2)
rc = rc2;
- if (target_prepared)
- pci_dev_reset_iommu_done(pdev);
return rc;
}
int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
struct cxl_hdm_range_context range_ctx;
+ bool target_prepared = false;
+ bool reset_initiated = false;
int dvsec;
int rc;
u16 cap;
@@ -993,9 +1359,24 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
scoped_guard(rwsem_write, &cxl_rwsem.region) {
rc = cxl_hdm_ranges_prepare(&range_ctx, pdev);
if (!rc)
- rc = cxl_reset_execute(pdev, dvsec, cap);
+ rc = cxl_reset_execute(pdev, &target_prepared,
+ &reset_initiated, dvsec, cap);
+ if (!rc) {
+ u16 command;
+
+ cxl_restore_pci_state_for_hdm_restore(pdev, &command);
+ rc = cxl_restore_state(pdev);
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
+ else
+ rc = cxl_reset_save_restored_state(pdev,
+ command);
+ } else if (reset_initiated) {
+ cxl_reset_save_disabled_state(pdev);
+ }
cxl_hdm_range_context_destroy(&range_ctx);
}
+ cxl_pci_target_reset_done(pdev, &target_prepared);
return rc;
}
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 10/12] PCI/CXL: Expose CXL Reset as a PCI reset method
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (8 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 09/12] cxl: Restore CXL state after PCI reset Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 11/12] Documentation/ABI: Document CXL Reset " Srirangan Madhavan
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
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 and
CXL Reset Memory Clear 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 b2879a6be5f8..157f06c576e9 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -35,6 +35,7 @@
#include <linux/aer.h>
#include <linux/bitfield.h>
#include <linux/suspend.h>
+#include <cxl/cxl.h>
#include "pci.h"
DEFINE_MUTEX(pci_slot_mutex);
@@ -5145,6 +5146,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 84d058b1b492..074767c7afc4 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -52,7 +52,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 [flat|nested] 14+ messages in thread
* [PATCH v12 11/12] Documentation/ABI: Document CXL Reset PCI reset method
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (9 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 10/12] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 12/12] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
2026-09-10 7:31 ` [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
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
and its reset-safety contract. Describe generic PCI driver quiescing,
exclusive HDM range reservation, cache and IOMMU handling, function-scope
restrictions, and state restoration. Clarify that the method does not
offline memory or revoke mappings, and that it requests CXL Reset Memory
Clear.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
Documentation/ABI/testing/sysfs-bus-pci | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index 55ea1db749a1..248d4f427589 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -153,6 +153,31 @@ 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 and CXL Reset Memory Clear
+ support. The method is only listed for single-function devices,
+ and reset is rejected if the device has enabled VFs, so CXL Reset
+ cannot affect another PCI function.
+
+ When reset is triggered through the reset attribute, the generic
+ PCI reset path calls the device driver's reset_prepare() callback,
+ when provided, and disables PCI I/O, memory decoding, and bus
+ mastering before invoking this method.
+ The method then rejects the operation if cached HDM state is
+ unavailable or an affected HDM range cannot be exclusively
+ reserved. It does not offline memory or revoke mappings; CXL.mem
+ users are expected to retain conflicting iomem resources while
+ access to an affected range remains active.
+
+ Before issuing CXL Reset, the kernel invalidates CPU caches for
+ affected HDM ranges where supported, disables CXL.cache, initiates
+ cache write-back where supported, and blocks IOMMU translations
+ where applicable. After reset, it restores PCI config state needed
+ to access HDM MMIO, cached HDM decoder state, and the CXL Device
+ DVSEC Cache and Mem Enable controls before completing PCI reset
+ recovery. "cxl_reset" requests CXL Reset Memory Clear so that a
+ successful generic PCI reset does not preserve tenant memory.
+
What: /sys/bus/pci/devices/.../reset
Date: July 2009
Contact: Michael S. Tsirkin <mst@redhat.com>
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v12 12/12] PCI/CXL: Restore CXL state after CXL bus reset
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (10 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 11/12] Documentation/ABI: Document CXL Reset " Srirangan Madhavan
@ 2026-09-10 7:08 ` Srirangan Madhavan
2026-09-10 7:31 ` [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:08 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
CXL bus reset can clear HDM decoder programming and CXL Device DVSEC
protocol enables. Reuse the common CXL state restore path after a
successful cxl_bus reset while the IOMMU reset block remains active.
Restore PCI config state first so BARs are valid before restoring cached
CXL state. If restoration fails, save a disabled PCI command state so
later PCI restore does not reopen bus mastering over invalid decoder
state.
Reject cxl_bus reset for multifunction devices and PFs with enabled VFs,
matching cxl_reset.
Reported-by: Alex Williamson <alex.williamson@redhat.com>
Tested-by: Richard Cheng <icheng@nvidia.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/resource.c | 18 ++++++++++++++++++
drivers/pci/pci.c | 6 ++++++
include/cxl/cxl.h | 6 ++++++
3 files changed, 30 insertions(+)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index e1d09f9042fa..d3491e20c1ca 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -894,6 +894,24 @@ static int cxl_pci_target_reset_prepare(struct pci_dev *pdev,
return 0;
}
+int cxl_restore_state_after_pci_reset(struct pci_dev *pdev)
+{
+ u16 command;
+ int rc;
+
+ device_lock_assert(&pdev->dev);
+ guard(rwsem_write)(&cxl_rwsem.region);
+
+ cxl_restore_pci_state_for_hdm_restore(pdev, &command);
+ rc = cxl_restore_state(pdev);
+ if (rc) {
+ cxl_reset_save_disabled_state(pdev);
+ return rc;
+ }
+
+ return cxl_reset_save_restored_state(pdev, command);
+}
+
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 157f06c576e9..a3ba4c28c62b 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5027,6 +5027,9 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (!dvsec)
return -ENOTTY;
+ if (dev->multifunction || pci_num_vf(dev))
+ return -ENOTTY;
+
if (probe)
return 0;
@@ -5054,6 +5057,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_state_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 856abbcd5f18..a02792969e0a 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -166,6 +166,7 @@ struct cxl_hdm_info {
#ifdef CONFIG_CXL_RESET
void pci_cxl_hdm_init(struct pci_dev *pdev);
void pci_cxl_hdm_release(struct pci_dev *pdev);
+int cxl_restore_state_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)
@@ -176,6 +177,11 @@ static inline void pci_cxl_hdm_release(struct pci_dev *pdev)
{
}
+static inline int cxl_restore_state_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 [flat|nested] 14+ messages in thread
* Re: [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (11 preceding siblings ...)
2026-09-10 7:08 ` [PATCH v12 12/12] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
@ 2026-09-10 7:31 ` Srirangan Madhavan
12 siblings, 0 replies; 14+ messages in thread
From: Srirangan Madhavan @ 2026-09-10 7:31 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra
On 9/10/26 12:07 AM, Srirangan Madhavan wrote:
> v12 changes from v11 [2]:
> - Reject cxl_reset and cxl_bus reset when the PF has enabled VFs,
> including VFs enabled after reset-method probing.
> - Require CXL Reset Memory Clear support and request memory clearing so
> the generic PCI reset does not preserve tenant memory.
> - Cache the CXL Device DVSEC control register during early PCI discovery
> and restore its CXL.cache and CXL.mem enable bits after CXL Reset and
> CXL bus reset.
> - Initialize the early PCI HDM cache only from pci_bus_add_device(),
> removing the later CXL core retry and its lifecycle locking.
> - Reapply and verify a cached HDM decoder lock after decoder restoration
> and commit, while leaving surviving locked committed state unchanged.
> - Convert temporary early HDM-cache ownership to scoped cleanup.
> - Document the reset-safety contract, including the iomem ownership
> requirement and the limits around memory offlining and mapping
> revocation.
Hi Bjorn, Dave, and CXL maintainers
Based on the review feedback so far, I believe the overall design is in
reasonable shape. I have addressed the feedback received so far in v12.
The series passes the kernel/module build checks and both cxl_reset and
cxl_bus testing on a target system.
I would appreciate any guidance on remaining issues or additional
changes needed to move the series further toward acceptance.
--
Regards,
Srirangan
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-09-10 7:31 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-10 7:07 [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 01/12] cxl: Move HDM decoder programming helpers Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 02/12] cxl: Make HDM commit helpers available to reset code Srirangan Madhavan
2026-09-10 7:07 ` [PATCH v12 03/12] cxl: Share HDM decoder decode logic Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 04/12] cxl: Cache decoder settings on PCI devices Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 05/12] cxl: Cache endpoint decoder settings during PCI enumeration Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 06/12] cxl: Add CXL Device Reset helper Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 07/12] cxl: Validate HDM ranges before CXL reset Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 08/12] PCI/CXL: Reject CXL Reset on multifunction devices Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 09/12] cxl: Restore CXL state after PCI reset Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 10/12] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 11/12] Documentation/ABI: Document CXL Reset " Srirangan Madhavan
2026-09-10 7:08 ` [PATCH v12 12/12] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
2026-09-10 7:31 ` [PATCH v12 00/12] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®