* [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices
@ 2026-10-11 2:14 Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 01/16] cxl: Drop stale decoder interleave limit comment Srirangan Madhavan
` (15 more replies)
0 siblings, 16 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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 for Type 2 devices through the existing PCI
reset_method and reset sysfs attributes. It disables CXL.cache, performs
supported cache writeback, initiates CXL Reset with Memory Clear, and
restores cached HDM decoder and CXL Device DVSEC state before PCI use
resumes. Active HDM ranges are reserved and CPU caches invalidated before
and after reset. The method remains limited to eligible single-function
devices with both CXL.cache and CXL.mem enabled.
The reset sequence follows the CXL 4.0 specification [3], including
CXL.cache disable, optional cache writeback, CXL Reset Memory Clear,
ResetComplete polling, and ResetError reporting. Sections 9.7.2 and 9.7.3
define the completion timeout encodings and reset access restrictions.
The userspace ABI is the existing PCI reset interface:
/sys/bus/pci/devices/.../reset_method
/sys/bus/pci/devices/.../reset
Userspace selects "cxl_reset" in reset_method and triggers the reset
through reset. Following Dan's v6 feedback, this uses the PCI interface
instead of adding a memdev-specific sysfs ABI.
For sysfs reset, the generic PCI path invokes the driver's reset_prepare()
callback when provided and disables PCI I/O, memory decoding, and bus
mastering. The CXL path reserves affected HDM ranges exclusively and
blocks IOMMU translations where applicable. It does not offline memory
or revoke mappings; it relies on CXL.mem users retaining conflicting
iomem resources while access remains active.
Changes from v14 [1]:
- Build HDM register helpers directly from CXL_BUS and move cxl_rwsem
with the first built-in cache/state users, following Dave's reference.
- Split PCI-facing reset and discovery code from the generic HDM register
and state helpers, following review of the CXL/PCI boundary.
- Rework decoder settings and cache refresh so the HDM layer does not
depend on PCI devices, while retaining the tools/testing/cxl build.
- Skip PCI HDM cache checks and refresh for DVSEC-emulated decoders,
and retain cached global control when register reads return all ones.
- Share DVSEC HDM-count validation, cache the Device DVSEC offset for
restoration, and report every PCI_COMMAND restore failure during cache init.
- Reject DVSEC-only memory decode, whose ranges are not in the HDM cache.
- Use the documented region-quiesce API with explicit lock annotations.
- Capture the private HDM snapshot with range preparation and use that
same snapshot for restoration.
- Reject CXL reset and CXL bus reset requiring state restoration when no
PCI state has been saved, before changing the device.
- Fold the separate HDM range-overflow check into register unpacking and
reduce the production changes to 15 patches. A separate final patch adds
seven HDM decoder checks run separately from the watermark hooks under
tools/testing/cxl/.
The CXL.cache/CXL.mem eligibility policy is unchanged from v14.
CXL Reset requires a registered CPU cache clean/invalidate provider
and matching firmware, such as the separate SMCCC provider series [2].
Without that provider, cxl_reset can be advertised while execution returns
-ENOTTY. The mock regression suite uses its test-only invalidation bypass.
The cxl_test module checks HDM range boundaries, zero-size and
uncommitted state, lock and target-type decoding, and 12-way interleave.
All seven checks and all 16 ndctl CXL regression tests passed on an ARM64
target with 4 KiB pages, with a separate ndctl test log-collection race
fix.
[1] https://lore.kernel.org/linux-cxl/20261001092227.3004747-1-smadhavan@nvidia.com/
[2] https://lore.kernel.org/all/20260910082142.1522865-1-smadhavan@nvidia.com/T/
[3] https://computeexpresslink.org/wp-content/uploads/2026/02/CXL-Specification_rev4p0_ver1p0_2026February26_clean_evalcopy_v2.pdf
Srirangan Madhavan (16):
cxl: Drop stale decoder interleave limit comment
cxl: Share CXL port upstream PCI device lookup
cxl: Move decoder declarations to shared header
cxl: Embed decoder configuration in a standalone structure
cxl: Introduce endpoint HDM decoder settings
cxl: Move HDM decoder helpers to built-in code
cxl: Share HDM decoder register unpacking
cxl: Refresh cached PCI HDM decoder settings
PCI/CXL: Cache endpoint HDM state during PCI enumeration
PCI/CXL: Add CXL Device Reset sequencing
cxl: Validate and synchronize HDM ranges around reset
PCI/CXL: Reject reset with unsafe function scope
PCI/CXL: Restore CXL state after PCI reset
PCI/CXL: Expose CXL Reset as a PCI reset method
PCI/CXL: Restore CXL state after CXL bus reset
tools/testing/cxl: Add HDM decoder range checks
MAINTAINERS | 1 +
drivers/cxl/Kconfig | 5 +
drivers/cxl/acpi.c | 26 +-
drivers/cxl/core/Makefile | 2 +-
drivers/cxl/core/atl.c | 4 +-
drivers/cxl/core/core.h | 15 +
drivers/cxl/core/hdm.c | 282 +++++------
drivers/cxl/core/hdm_regs.c | 455 ++++++++++++++++++
drivers/cxl/core/hdm_state.c | 367 +++++++++++++++
drivers/cxl/core/pci.c | 111 ++++-
drivers/cxl/core/port.c | 68 ++-
drivers/cxl/core/region.c | 102 ++--
drivers/cxl/core/regs.c | 9 +
drivers/cxl/cxl.h | 46 +-
drivers/cxl/pci.c | 2 +-
drivers/cxl/port.c | 4 +-
drivers/pci/Makefile | 1 +
drivers/pci/bus.c | 1 +
drivers/pci/cxl.c | 747 ++++++++++++++++++++++++++++++
drivers/pci/pci.c | 18 +
drivers/pci/pci.h | 29 ++
drivers/pci/probe.c | 1 +
include/cxl/cxl.h | 59 ++-
include/cxl/hdm.h | 105 +++++
include/cxl/pci.h | 13 +
include/linux/pci.h | 8 +-
include/uapi/linux/pci_regs.h | 14 +
tools/testing/cxl/Kbuild | 1 -
tools/testing/cxl/cxl_core_test.c | 78 ++++
tools/testing/cxl/cxl_core_test.h | 8 +
tools/testing/cxl/test/cxl.c | 163 +++----
31 files changed, 2343 insertions(+), 402 deletions(-)
create mode 100644 drivers/cxl/core/hdm_regs.c
create mode 100644 drivers/cxl/core/hdm_state.c
create mode 100644 drivers/pci/cxl.c
create mode 100644 include/cxl/hdm.h
create mode 100644 tools/testing/cxl/cxl_core_test.h
base-commit: fef22d37d47043b63e4007f2f5fe762fa21fae0c
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 01/16] cxl: Drop stale decoder interleave limit comment
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 02/16] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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,
Jonathan Cameron, Li Ming
CXL 2.0 added 16-way memory interleaving, making the comment that the
specification supports only 8-way interleaving stale. Remove the comment
without changing CXL_DECODER_MAX_INTERLEAVE.
Suggested-by: Jonathan Cameron <jic23@kernel.org>
Link: https://lore.kernel.org/linux-cxl/20260912003057.3db9335c@jic23-hlaptop/
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
---
drivers/cxl/cxl.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 3ef0810ab86b..9e0500d56866 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -269,10 +269,6 @@ enum cxl_decoder_type {
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
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 02/16] cxl: Share CXL port upstream PCI device lookup
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 01/16] cxl: Drop stale decoder interleave limit comment Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 03/16] cxl: Move decoder declarations to shared header Srirangan Madhavan
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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,
Jonathan Cameron, Li Ming
read_cdat_data() resolves a CXL port's upstream device to its backing PCI
device. HDM cache updates need the same mapping.
Factor the lookup into cxl_port_get_uport_pci_dev() and return a referenced
PCI device to make caller ownership explicit.
Use automatic reference cleanup in read_cdat_data(), as suggested by
Li Ming, so early returns release the PCI device reference.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
---
drivers/cxl/core/core.h | 1 +
drivers/cxl/core/pci.c | 16 ++--------------
drivers/cxl/core/port.c | 26 ++++++++++++++++++++++++++
3 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 35eaf636adc9..983d7690c3a5 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)
{
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 9d807c1a002c..c3c41efae5fd 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -564,26 +564,14 @@ static unsigned char cdat_checksum(void *buf, size_t size)
*/
void read_cdat_data(struct cxl_port *port)
{
- struct device *uport = port->uport_dev;
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(port);
struct device *dev = &port->dev;
struct pci_doe_mb *doe_mb;
- struct pci_dev *pdev = NULL;
- struct cxl_memdev *cxlmd;
struct cdat_doe_rsp *buf;
size_t table_length, length;
int rc;
- if (is_cxl_memdev(uport)) {
- struct device *host;
-
- cxlmd = to_cxl_memdev(uport);
- host = cxlmd->dev.parent;
- if (dev_is_pci(host))
- pdev = to_pci_dev(host);
- } else if (dev_is_pci(uport)) {
- pdev = to_pci_dev(uport);
- }
-
if (!pdev)
return;
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 625e4aa427db..5dc2815d82d8 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -34,6 +34,32 @@
static DEFINE_IDA(cxl_port_ida);
static DEFINE_XARRAY(cxl_root_buses);
+/**
+ * cxl_port_get_uport_pci_dev - get the PCI device for a port's upstream device
+ * @port: CXL port to map to a PCI device
+ *
+ * Return: A referenced PCI device, or NULL if the upstream device is not PCI.
+ * The caller must release the returned reference with pci_dev_put().
+ */
+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)
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 03/16] cxl: Move decoder declarations to shared header
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 01/16] cxl: Drop stale decoder interleave limit comment Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 02/16] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 04/16] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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,
Jonathan Cameron, Li Ming
Move the common decoder declarations to include/cxl/cxl.h for use by
shared decoder settings. Preserve their layout and documentation.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
---
drivers/cxl/cxl.h | 37 -------------------------------------
include/cxl/cxl.h | 41 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 40 insertions(+), 38 deletions(-)
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 9e0500d56866..44ff3334648c 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -264,45 +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,
-};
-
-#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..69941f7fdb76 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,44 @@ enum cxl_devtype {
CXL_DEVTYPE_CLASSMEM,
};
-struct device;
+struct cxl_region;
+
+enum cxl_decoder_type {
+ CXL_DECODER_DEVMEM = 2,
+ CXL_DECODER_HOSTONLYMEM = 3,
+};
+
+#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
+ * @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);
+};
/*
* Using struct_group() allows for per register-block-type helper routines,
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 04/16] cxl: Embed decoder configuration in a standalone structure
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (2 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 03/16] cxl: Move decoder declarations to shared header Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 05/16] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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,
Li Ming
Define cxl_decoder_config independently and embed it in cxl_decoder. Update
decoder field accesses without changing behavior, so reset snapshots can
reuse the configuration type without struct_group_tagged().
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
---
drivers/cxl/acpi.c | 26 +++---
drivers/cxl/core/atl.c | 4 +-
drivers/cxl/core/hdm.c | 114 ++++++++++++-------------
drivers/cxl/core/pci.c | 8 +-
drivers/cxl/core/port.c | 42 +++++-----
drivers/cxl/core/region.c | 102 +++++++++++-----------
drivers/cxl/cxl.h | 2 +-
drivers/cxl/pci.c | 2 +-
drivers/cxl/port.c | 4 +-
include/cxl/cxl.h | 29 +++++--
tools/testing/cxl/test/cxl.c | 158 +++++++++++++++++------------------
11 files changed, 249 insertions(+), 242 deletions(-)
diff --git a/drivers/cxl/acpi.c b/drivers/cxl/acpi.c
index 3b818adbd38b..692a4e067678 100644
--- a/drivers/cxl/acpi.c
+++ b/drivers/cxl/acpi.c
@@ -109,16 +109,16 @@ static int cxl_parse_cxims(union acpi_subtable_headers *header, void *arg,
return rc;
/* Does this CXIMS entry apply to the given CXL Window? */
- if (hbig != cxld->interleave_granularity)
+ if (hbig != cxld->config.interleave_granularity)
return 0;
/* IW 1,3 do not use xormaps and skip this parsing entirely */
- if (is_power_of_2(cxld->interleave_ways))
+ if (is_power_of_2(cxld->config.interleave_ways))
/* 2, 4, 8, 16 way */
- nr_maps = ilog2(cxld->interleave_ways);
+ nr_maps = ilog2(cxld->config.interleave_ways);
else
/* 6, 12 way */
- nr_maps = ilog2(cxld->interleave_ways / 3);
+ nr_maps = ilog2(cxld->config.interleave_ways / 3);
if (cxims->nr_xormaps < nr_maps) {
dev_dbg(dev, "CXIMS nr_xormaps[%d] expected[%d]\n",
@@ -363,7 +363,7 @@ static int add_or_reset_cxl_resource(struct resource *parent, struct resource *r
static void cxl_setup_extended_linear_cache(struct cxl_root_decoder *cxlrd)
{
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
- struct range *hpa = &cxld->hpa_range;
+ struct range *hpa = &cxld->config.hpa_range;
resource_size_t size = range_len(hpa);
resource_size_t start = hpa->start;
resource_size_t cache_size;
@@ -436,13 +436,13 @@ static int __cxl_parse_cfmws(struct acpi_cedt_cfmws *cfmws,
return PTR_ERR(cxlrd);
cxld = &cxlrd->cxlsd.cxld;
- cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions);
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->hpa_range = (struct range) {
+ cxld->config.flags = cfmws_to_decoder_flags(cfmws->restrictions);
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.hpa_range = (struct range) {
.start = cfmws->base_hpa,
.end = cfmws->base_hpa + cfmws->window_size - 1,
};
- cxld->interleave_ways = ways;
+ cxld->config.interleave_ways = ways;
for (i = 0; i < ways; i++)
cxld->target_map[i] = cfmws->interleave_targets[i];
/*
@@ -451,7 +451,7 @@ static int __cxl_parse_cfmws(struct acpi_cedt_cfmws *cfmws,
*/
if (ways == 1)
ig = CXL_DECODER_MIN_GRANULARITY;
- cxld->interleave_granularity = ig;
+ cxld->config.interleave_granularity = ig;
if (cfmws->interleave_arithmetic == ACPI_CEDT_CFMWS_ARITHMETIC_XOR) {
if (ways != 1 && ways != 3) {
@@ -752,7 +752,7 @@ static int add_root_nvdimm_bridge(struct device *match, void *data)
return 0;
cxld = to_cxl_decoder(match);
- if (!(cxld->flags & CXL_DECODER_F_PMEM))
+ if (!(cxld->config.flags & CXL_DECODER_F_PMEM))
return 0;
cxl_nvb = devm_cxl_add_nvdimm_bridge(host, root_port);
@@ -869,8 +869,8 @@ static int pair_cxl_resource(struct device *dev, void *data)
struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev);
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct resource res = {
- .start = cxld->hpa_range.start,
- .end = cxld->hpa_range.end,
+ .start = cxld->config.hpa_range.start,
+ .end = cxld->config.hpa_range.end,
.flags = IORESOURCE_MEM,
};
diff --git a/drivers/cxl/core/atl.c b/drivers/cxl/core/atl.c
index 310668786189..07ce7c50c47a 100644
--- a/drivers/cxl/core/atl.c
+++ b/drivers/cxl/core/atl.c
@@ -172,8 +172,8 @@ static int cxl_prm_setup_root(struct cxl_root *cxl_root, void *data)
*
* Indicate that Normalized Addressing is enabled.
*/
- cxld->flags |= CXL_DECODER_F_LOCK;
- cxld->flags |= CXL_DECODER_F_NORMALIZED_ADDRESSING;
+ cxld->config.flags |= CXL_DECODER_F_LOCK;
+ cxld->config.flags |= CXL_DECODER_F_NORMALIZED_ADDRESSING;
ctx->hpa_range = hpa_range;
ctx->interleave_ways = ways;
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 39fe283cbc74..9e05032a5426 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -384,11 +384,11 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
if (cxled->dpa_res) {
dev_dbg(dev, "decoder%d.%d: existing allocation %pr assigned\n",
- port->id, cxled->cxld.id, cxled->dpa_res);
+ port->id, cxled->cxld.config.id, cxled->dpa_res);
return -EBUSY;
}
- if (port->hdm_end + 1 != cxled->cxld.id) {
+ if (port->hdm_end + 1 != cxled->cxld.config.id) {
/*
* Assumes alloc and commit order is always in hardware instance
* order per expectations from 8.2.5.12.20 Committing Decoder
@@ -396,7 +396,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
* decoder[m+1] commit start.
*/
dev_dbg(dev, "decoder%d.%d: expected decoder%d.%d\n", port->id,
- cxled->cxld.id, port->id, port->hdm_end + 1);
+ cxled->cxld.config.id, port->id, port->hdm_end + 1);
return -EBUSY;
}
@@ -409,7 +409,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
dev_name(&cxled->cxld.dev));
if (IS_ERR(res)) {
dev_dbg(dev, "decoder%d.%d: failed to reserve allocation\n",
- port->id, cxled->cxld.id);
+ port->id, cxled->cxld.config.id);
if (skipped)
release_skip(cxlds, base - skipped, skipped);
return PTR_ERR(res);
@@ -432,7 +432,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
/* Empty decoders may not be contained by a partition boundary */
if (cxled->part < 0 && resource_size(res))
dev_warn(dev, "decoder%d.%d: %pr does not map any partition\n",
- port->id, cxled->cxld.id, res);
+ port->id, cxled->cxld.config.id, res);
port->hdm_end++;
get_device(&cxled->cxld.dev);
@@ -580,11 +580,11 @@ int cxl_dpa_free(struct cxl_endpoint_decoder *cxled)
dev_name(&cxled->cxld.region->dev));
return -EBUSY;
}
- if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) {
+ if (cxled->cxld.config.flags & CXL_DECODER_F_ENABLE) {
dev_dbg(dev, "decoder enabled\n");
return -EBUSY;
}
- if (cxled->cxld.id != port->hdm_end) {
+ if (cxled->cxld.config.id != port->hdm_end) {
dev_dbg(dev, "expected decoder%d.%d\n", port->id,
port->hdm_end);
return -EBUSY;
@@ -603,7 +603,7 @@ int cxl_dpa_set_part(struct cxl_endpoint_decoder *cxled,
int part;
guard(rwsem_write)(&cxl_rwsem.dpa);
- if (cxled->cxld.flags & CXL_DECODER_F_ENABLE)
+ if (cxled->cxld.config.flags & CXL_DECODER_F_ENABLE)
return -EBUSY;
for (part = 0; part < cxlds->nr_partitions; part++)
@@ -641,7 +641,7 @@ static int __cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
return -EBUSY;
}
- if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) {
+ if (cxled->cxld.config.flags & CXL_DECODER_F_ENABLE) {
dev_dbg(dev, "decoder enabled\n");
return -EBUSY;
}
@@ -716,12 +716,12 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
* 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))
+ if (WARN_ONCE(ways_to_eiw(cxld->config.interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n", cxld->config.interleave_ways))
return;
- if (WARN_ONCE(granularity_to_eig(cxld->interleave_granularity, &eig),
+ if (WARN_ONCE(granularity_to_eig(cxld->config.interleave_granularity, &eig),
"invalid interleave_granularity: %d\n",
- cxld->interleave_granularity))
+ cxld->config.interleave_granularity))
return;
u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
@@ -732,14 +732,14 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
{
u32p_replace_bits(ctrl,
- !!(cxld->target_type == CXL_DECODER_HOSTONLYMEM),
+ !!(cxld->config.target_type == CXL_DECODER_HOSTONLYMEM),
CXL_HDM_DECODER0_CTRL_HOSTONLY);
}
static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
{
struct cxl_dport **t = &cxlsd->target[0];
- int ways = cxlsd->cxld.interleave_ways;
+ int ways = cxlsd->cxld.config.interleave_ways;
*tgt = FIELD_PREP(GENMASK(7, 0), t[0]->port_id);
if (ways > 1)
@@ -786,16 +786,16 @@ static int cxld_await_commit(void __iomem *hdm, int id)
static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
{
- int id = cxld->id;
+ int id = cxld->config.id;
u64 base, size;
u32 ctrl;
/* common decoder settings */
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->config.id));
cxld_set_interleave(cxld, &ctrl);
cxld_set_type(cxld, &ctrl);
- base = cxld->hpa_range.start;
- size = range_len(&cxld->hpa_range);
+ base = cxld->config.hpa_range.start;
+ size = range_len(&cxld->config.hpa_range);
writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
@@ -830,9 +830,9 @@ 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;
+ int id = cxld->config.id, rc;
- if (cxld->flags & CXL_DECODER_F_ENABLE)
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE)
return 0;
if (cxl_num_decoders_committed(port) != id) {
@@ -865,14 +865,14 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
scoped_guard(rwsem_read, &cxl_rwsem.dpa)
setup_hw_decoder(cxld, hdm);
- rc = cxld_await_commit(hdm, cxld->id);
+ rc = cxld_await_commit(hdm, cxld->config.id);
if (rc) {
dev_dbg(&port->dev, "%s: error %d committing decoder\n",
dev_name(&cxld->dev), rc);
return rc;
}
port->commit_end++;
- cxld->flags |= CXL_DECODER_F_ENABLE;
+ cxld->config.flags |= CXL_DECODER_F_ENABLE;
return 0;
}
@@ -886,8 +886,8 @@ static int commit_reap(struct device *dev, void *data)
return 0;
cxld = to_cxl_decoder(dev);
- if (port->commit_end == cxld->id &&
- ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
+ if (port->commit_end == cxld->config.id &&
+ ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)) {
port->commit_end--;
dev_dbg(&port->dev, "reap: %s commit_end: %d\n",
dev_name(&cxld->dev), port->commit_end);
@@ -919,13 +919,13 @@ static void cxl_decoder_reset(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;
+ int id = cxld->config.id;
u32 ctrl;
- if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)
return;
- if (cxld->flags & CXL_DECODER_F_LOCK)
+ if (cxld->config.flags & CXL_DECODER_F_LOCK)
return;
if (port->commit_end == id)
@@ -944,7 +944,7 @@ static void cxl_decoder_reset(struct cxl_decoder *cxld)
writel(0, hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
- cxld->flags &= ~CXL_DECODER_F_ENABLE;
+ cxld->config.flags &= ~CXL_DECODER_F_ENABLE;
/* Userspace is now responsible for reconfiguring this decoder */
if (is_endpoint_decoder(&cxld->dev)) {
@@ -971,23 +971,23 @@ static int cxl_setup_hdm_decoder_from_dvsec(
if (!len)
return -ENOENT;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
cxld->commit = NULL;
cxld->reset = NULL;
- cxld->hpa_range = info->dvsec_range[which];
+ cxld->config.hpa_range = info->dvsec_range[which];
/*
* Set the emulated decoder as locked pending additional support to
* change the range registers at run time.
*/
- cxld->flags |= CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
- port->commit_end = cxld->id;
+ cxld->config.flags |= CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
+ port->commit_end = cxld->config.id;
rc = devm_cxl_dpa_reserve(cxled, *dpa_base, len, 0);
if (rc) {
dev_err(&port->dev,
"decoder%d.%d: Failed to reserve DPA range %#llx - %#llx: %d\n",
- port->id, cxld->id, *dpa_base, *dpa_base + len - 1, rc);
+ port->id, cxld->config.id, *dpa_base, *dpa_base + len - 1, rc);
return rc;
}
*dpa_base += len;
@@ -1030,36 +1030,36 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
size = 0;
if (base == U64_MAX || size == U64_MAX) {
dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n",
- port->id, cxld->id);
+ port->id, cxld->config.id);
return -ENXIO;
}
if (info)
cxled = to_cxl_endpoint_decoder(&cxld->dev);
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + size - 1,
};
/* decoders are enabled if committed */
if (committed) {
- cxld->flags |= CXL_DECODER_F_ENABLE;
+ cxld->config.flags |= CXL_DECODER_F_ENABLE;
if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
- cxld->flags |= CXL_DECODER_F_LOCK;
+ cxld->config.flags |= CXL_DECODER_F_LOCK;
if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
else
- cxld->target_type = CXL_DECODER_DEVMEM;
+ cxld->config.target_type = CXL_DECODER_DEVMEM;
guard(rwsem_write)(&cxl_rwsem.region);
- if (cxld->id != cxl_num_decoders_committed(port)) {
+ if (cxld->config.id != cxl_num_decoders_committed(port)) {
dev_warn(&port->dev,
"decoder%d.%d: Committed out of order\n",
- port->id, cxld->id);
+ port->id, cxld->config.id);
return -ENXIO;
}
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
} else {
if (cxled) {
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
@@ -1070,46 +1070,46 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
* more precision.
*/
if (cxlds->type == CXL_DEVTYPE_CLASSMEM)
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
else
- cxld->target_type = CXL_DECODER_DEVMEM;
+ cxld->config.target_type = CXL_DECODER_DEVMEM;
} else {
/* To be overridden by region type at commit time */
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
}
if (!FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl) &&
- cxld->target_type == CXL_DECODER_HOSTONLYMEM) {
+ cxld->config.target_type == CXL_DECODER_HOSTONLYMEM) {
ctrl |= CXL_HDM_DECODER0_CTRL_HOSTONLY;
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);
+ &cxld->config.interleave_ways);
if (rc) {
dev_warn(&port->dev,
"decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
- port->id, cxld->id, ctrl);
+ port->id, cxld->config.id, ctrl);
return rc;
}
rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
- &cxld->interleave_granularity);
+ &cxld->config.interleave_granularity);
if (rc) {
dev_warn(&port->dev,
"decoder%d.%d: Invalid interleave granularity (ctrl: %#x)\n",
- port->id, cxld->id, ctrl);
+ port->id, cxld->config.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);
+ port->id, cxld->config.id, cxld->config.hpa_range.start, cxld->config.hpa_range.end,
+ cxld->config.interleave_ways, cxld->config.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++)
+ for (i = 0; i < cxld->config.interleave_ways; i++)
cxld->target_map[i] = target_list.target_id[i];
return 0;
@@ -1118,11 +1118,11 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
if (!committed)
return 0;
- dpa_size = div_u64_rem(size, cxld->interleave_ways, &remainder);
+ dpa_size = div_u64_rem(size, cxld->config.interleave_ways, &remainder);
if (remainder) {
dev_err(&port->dev,
"decoder%d.%d: invalid committed configuration size: %#llx ways: %d\n",
- port->id, cxld->id, size, cxld->interleave_ways);
+ port->id, cxld->config.id, size, cxld->config.interleave_ways);
return -ENXIO;
}
lo = readl(hdm + CXL_HDM_DECODER0_SKIP_LOW(which));
@@ -1132,7 +1132,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
if (rc) {
dev_err(&port->dev,
"decoder%d.%d: Failed to reserve DPA range %#llx - %#llx: %d\n",
- port->id, cxld->id, *dpa_base,
+ port->id, cxld->config.id, *dpa_base,
*dpa_base + dpa_size + skip - 1, rc);
return rc;
}
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index c3c41efae5fd..33b21c8dcb21 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -231,10 +231,10 @@ static int dvsec_range_allowed(struct device *dev, const void *arg)
cxld = to_cxl_decoder(dev);
- if (!(cxld->flags & CXL_DECODER_F_RAM))
+ if (!(cxld->config.flags & CXL_DECODER_F_RAM))
return 0;
- return range_contains(&cxld->hpa_range, dev_range);
+ return range_contains(&cxld->config.hpa_range, dev_range);
}
static void disable_hdm(void *_cxlhdm)
@@ -668,12 +668,12 @@ static int __cxl_endpoint_decoder_reset_detected(struct device *dev, void *data)
return 0;
cxld = to_cxl_decoder(dev);
- if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)
return 0;
cxlhdm = dev_get_drvdata(&port->dev);
hdm = cxlhdm->regs.hdm_decoder;
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->config.id));
return !FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
}
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 5dc2815d82d8..84374873f3d0 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -129,7 +129,7 @@ static ssize_t start_show(struct device *dev, struct device_attribute *attr,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%#llx\n", cxld->hpa_range.start);
+ return sysfs_emit(buf, "%#llx\n", cxld->config.hpa_range.start);
}
static DEVICE_ATTR_ADMIN_RO(start);
@@ -138,7 +138,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%#llx\n", range_len(&cxld->hpa_range));
+ return sysfs_emit(buf, "%#llx\n", range_len(&cxld->config.hpa_range));
}
static DEVICE_ATTR_RO(size);
@@ -149,7 +149,7 @@ static ssize_t name##_show(struct device *dev, \
struct cxl_decoder *cxld = to_cxl_decoder(dev); \
\
return sysfs_emit(buf, "%s\n", \
- (cxld->flags & (flag)) ? "1" : "0"); \
+ (cxld->config.flags & (flag)) ? "1" : "0"); \
} \
static DEVICE_ATTR_RO(name)
@@ -164,7 +164,7 @@ static ssize_t target_type_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- switch (cxld->target_type) {
+ switch (cxld->config.target_type) {
case CXL_DECODER_DEVMEM:
return sysfs_emit(buf, "accelerator\n");
case CXL_DECODER_HOSTONLYMEM:
@@ -180,14 +180,14 @@ static ssize_t emit_target_list(struct cxl_switch_decoder *cxlsd, char *buf)
ssize_t offset = 0;
int i, rc = 0;
- for (i = 0; i < cxld->interleave_ways; i++) {
+ for (i = 0; i < cxld->config.interleave_ways; i++) {
struct cxl_dport *dport = cxlsd->target[i];
struct cxl_dport *next = NULL;
if (!dport)
break;
- if (i + 1 < cxld->interleave_ways)
+ if (i + 1 < cxld->config.interleave_ways)
next = cxlsd->target[i + 1];
rc = sysfs_emit_at(buf, offset, "%d%s", dport->port_id,
next ? "," : "");
@@ -314,7 +314,7 @@ static ssize_t interleave_granularity_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%d\n", cxld->interleave_granularity);
+ return sysfs_emit(buf, "%d\n", cxld->config.interleave_granularity);
}
static DEVICE_ATTR_RO(interleave_granularity);
@@ -324,7 +324,7 @@ static ssize_t interleave_ways_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%d\n", cxld->interleave_ways);
+ return sysfs_emit(buf, "%d\n", cxld->config.interleave_ways);
}
static DEVICE_ATTR_RO(interleave_ways);
@@ -368,14 +368,14 @@ static bool can_create_pmem(struct cxl_root_decoder *cxlrd)
{
unsigned long flags = CXL_DECODER_F_TYPE3 | CXL_DECODER_F_PMEM;
- return (cxlrd->cxlsd.cxld.flags & flags) == flags;
+ return (cxlrd->cxlsd.cxld.config.flags & flags) == flags;
}
static bool can_create_ram(struct cxl_root_decoder *cxlrd)
{
unsigned long flags = CXL_DECODER_F_TYPE3 | CXL_DECODER_F_RAM;
- return (cxlrd->cxlsd.cxld.flags & flags) == flags;
+ return (cxlrd->cxlsd.cxld.config.flags & flags) == flags;
}
static umode_t cxl_root_decoder_visible(struct kobject *kobj, struct attribute *a, int n)
@@ -450,7 +450,7 @@ static void __cxl_decoder_release(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- ida_free(&port->decoder_ida, cxld->id);
+ ida_free(&port->decoder_ida, cxld->config.id);
put_device(&port->dev);
}
@@ -1646,7 +1646,7 @@ static int update_decoder_targets(struct device *dev, void *data)
cxld = &cxlsd->cxld;
guard(rwsem_write)(&cxl_rwsem.region);
- for (i = 0; i < cxld->interleave_ways; i++) {
+ for (i = 0; i < cxld->config.interleave_ways; i++) {
if (cxld->target_map[i] == dport->port_id) {
cxlsd->target[i] = dport;
dev_dbg(dev, "dport%d found in target list, index %d\n",
@@ -1952,7 +1952,7 @@ static int decoder_populate_targets(struct cxl_switch_decoder *cxlsd,
return 0;
guard(rwsem_write)(&cxl_rwsem.region);
- for (i = 0; i < cxlsd->cxld.interleave_ways; i++) {
+ for (i = 0; i < cxlsd->cxld.config.interleave_ways; i++) {
struct cxl_dport *dport = find_dport(port, cxld->target_map[i]);
if (!dport) {
@@ -1988,7 +1988,7 @@ static int cxl_decoder_init(struct cxl_port *port, struct cxl_decoder *cxld)
/* need parent to stick around to release the id */
get_device(&port->dev);
- cxld->id = rc;
+ cxld->config.id = rc;
dev = &cxld->dev;
device_initialize(dev);
@@ -1998,10 +1998,10 @@ static int cxl_decoder_init(struct cxl_port *port, struct cxl_decoder *cxld)
dev->bus = &cxl_bus_type;
/* Pre initialize an "empty" decoder */
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = PAGE_SIZE;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->hpa_range = (struct range) {
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = PAGE_SIZE;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -2172,7 +2172,7 @@ int cxl_decoder_add_locked(struct cxl_decoder *cxld)
if (WARN_ON_ONCE(IS_ERR(cxld)))
return PTR_ERR(cxld);
- if (cxld->interleave_ways < 1)
+ if (cxld->config.interleave_ways < 1)
return -EINVAL;
dev = &cxld->dev;
@@ -2182,14 +2182,14 @@ int cxl_decoder_add_locked(struct cxl_decoder *cxld)
struct cxl_switch_decoder *cxlsd = to_cxl_switch_decoder(dev);
rc = decoder_populate_targets(cxlsd, port);
- if (rc && (cxld->flags & CXL_DECODER_F_ENABLE)) {
+ if (rc && (cxld->config.flags & CXL_DECODER_F_ENABLE)) {
dev_err(&port->dev,
"Failed to populate active decoder targets\n");
return rc;
}
}
- rc = dev_set_name(dev, "decoder%d.%d", port->id, cxld->id);
+ rc = dev_set_name(dev, "decoder%d.%d", port->id, cxld->config.id);
if (rc)
return rc;
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index bb51e67f25f5..8cfc06df3134 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -502,8 +502,8 @@ static int set_interleave_ways(struct cxl_region *cxlr, int val)
* Even for x3, x6, and x12 interleaves the region interleave must be a
* power of 2 multiple of the host bridge interleave.
*/
- if (!is_power_of_2(val / cxld->interleave_ways) ||
- (val % cxld->interleave_ways)) {
+ if (!is_power_of_2(val / cxld->config.interleave_ways) ||
+ (val % cxld->config.interleave_ways)) {
dev_dbg(&cxlr->dev, "invalid interleave: %d\n", val);
return -EINVAL;
}
@@ -580,7 +580,7 @@ static int set_interleave_granularity(struct cxl_region *cxlr, int val)
* with a granularity greater than the root interleave result in invalid
* DPA translations (invalid to support).
*/
- if (cxld->interleave_ways > 1 && val != cxld->interleave_granularity)
+ if (cxld->config.interleave_ways > 1 && val != cxld->config.interleave_granularity)
return -EINVAL;
lockdep_assert_held_write(&cxl_rwsem.region);
@@ -880,7 +880,7 @@ static int check_commit_order(struct device *dev, void *data)
* order shutdown has occurred, block further allocations until
* that is resolved
*/
- if (((cxld->flags & CXL_DECODER_F_ENABLE) == 0))
+ if (((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0))
return -EBUSY;
return 0;
}
@@ -896,7 +896,7 @@ static int match_free_decoder(struct device *dev, const void *data)
cxld = to_cxl_decoder(dev);
- if (cxld->id != port->commit_end + 1)
+ if (cxld->config.id != port->commit_end + 1)
return 0;
if (cxld->region) {
@@ -942,7 +942,7 @@ static int match_auto_decoder(struct device *dev, const void *data)
return 0;
cxld = to_cxl_decoder(dev);
- r = &cxld->hpa_range;
+ r = &cxld->config.hpa_range;
if (spa_maps_hpa(p, r))
return 1;
@@ -1003,10 +1003,10 @@ static bool auto_order_ok(struct cxl_port *port, struct cxl_region *cxlr_iter,
* starting address has the lesser id.
*/
dev_dbg(&cxld->dev, "check for HPA violation %s:%d < %s:%d\n",
- dev_name(&cxld->dev), cxld->id,
- dev_name(&cxld_iter->dev), cxld_iter->id);
+ dev_name(&cxld->dev), cxld->config.id,
+ dev_name(&cxld_iter->dev), cxld_iter->config.id);
- if (cxld_iter->id > cxld->id)
+ if (cxld_iter->config.id > cxld->config.id)
return true;
return false;
@@ -1137,11 +1137,11 @@ static int cxl_rr_assign_decoder(struct cxl_port *port, struct cxl_region *cxlr,
*/
dev_WARN_ONCE(&cxlr->dev,
port == cxled_to_port(cxled) &&
- cxld->target_type != cxlr->type,
+ cxld->config.target_type != cxlr->type,
"%s:%s mismatch decoder type %d -> %d\n",
dev_name(&cxled_to_memdev(cxled)->dev),
- dev_name(&cxld->dev), cxld->target_type, cxlr->type);
- cxld->target_type = cxlr->type;
+ dev_name(&cxld->dev), cxld->config.target_type, cxlr->type);
+ cxld->config.target_type = cxlr->type;
cxl_rr->decoder = cxld;
return 0;
}
@@ -1162,12 +1162,12 @@ static void cxl_region_setup_flags(struct cxl_region *cxlr,
set_bit(CXL_REGION_F_LOCK, &cxlr->flags);
}
- if (cxld->flags & CXL_DECODER_F_LOCK) {
+ if (cxld->config.flags & CXL_DECODER_F_LOCK) {
set_bit(CXL_REGION_F_LOCK, &cxlr->flags);
clear_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
}
- if (cxld->flags & CXL_DECODER_F_NORMALIZED_ADDRESSING)
+ if (cxld->config.flags & CXL_DECODER_F_NORMALIZED_ADDRESSING)
set_bit(CXL_REGION_F_NORMALIZED_ADDRESSING, &cxlr->flags);
}
@@ -1483,7 +1483,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
distance *= cxl_rr_iter->nr_targets;
iter = to_cxl_port(iter->dev.parent);
} while (!is_cxl_root(iter));
- distance *= cxlrd->cxlsd.cxld.interleave_ways;
+ distance *= cxlrd->cxlsd.cxld.config.interleave_ways;
for (i = 0; i < cxl_rr->nr_targets_set; i++)
if (ep->dport == cxlsd->target[i]) {
@@ -1505,7 +1505,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
* root IG != region IG.
*/
parent_ig = p->interleave_granularity;
- parent_iw = cxlrd->cxlsd.cxld.interleave_ways;
+ parent_iw = cxlrd->cxlsd.cxld.config.interleave_ways;
/*
* For purposes of address bit routing, use power-of-2 math for
* switch ports.
@@ -1518,8 +1518,8 @@ static int cxl_port_setup_targets(struct cxl_port *port,
parent_rr = cxl_rr_load(parent_port, cxlr);
parent_cxld = parent_rr->decoder;
- parent_ig = parent_cxld->interleave_granularity;
- parent_iw = parent_cxld->interleave_ways;
+ parent_ig = parent_cxld->config.interleave_granularity;
+ parent_iw = parent_cxld->config.interleave_ways;
}
rc = granularity_to_eig(parent_ig, &peig);
@@ -1575,10 +1575,10 @@ static int cxl_port_setup_targets(struct cxl_port *port,
}
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
- if (cxld->interleave_ways != iw ||
- (iw > 1 && cxld->interleave_granularity != ig) ||
- !spa_maps_hpa(p, &cxld->hpa_range) ||
- ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
+ if (cxld->config.interleave_ways != iw ||
+ (iw > 1 && cxld->config.interleave_granularity != ig) ||
+ !spa_maps_hpa(p, &cxld->config.hpa_range) ||
+ ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)) {
dev_err(&cxlr->dev,
"%s:%s %s expected iw: %d ig: %d %pr\n",
dev_name(port->uport_dev), dev_name(&port->dev),
@@ -1586,10 +1586,10 @@ static int cxl_port_setup_targets(struct cxl_port *port,
dev_err(&cxlr->dev,
"%s:%s %s got iw: %d ig: %d state: %s %#llx:%#llx\n",
dev_name(port->uport_dev), dev_name(&port->dev),
- __func__, cxld->interleave_ways,
- cxld->interleave_granularity,
- str_enabled_disabled(cxld->flags & CXL_DECODER_F_ENABLE),
- cxld->hpa_range.start, cxld->hpa_range.end);
+ __func__, cxld->config.interleave_ways,
+ cxld->config.interleave_granularity,
+ str_enabled_disabled(cxld->config.flags & CXL_DECODER_F_ENABLE),
+ cxld->config.hpa_range.start, cxld->config.hpa_range.end);
return -ENXIO;
}
} else {
@@ -1602,9 +1602,9 @@ static int cxl_port_setup_targets(struct cxl_port *port,
return rc;
}
- cxld->interleave_ways = iw;
- cxld->interleave_granularity = ig;
- cxld->hpa_range = (struct range) {
+ cxld->config.interleave_ways = iw;
+ cxld->config.interleave_granularity = ig;
+ cxld->config.hpa_range = (struct range) {
.start = p->res->start,
.end = p->res->end,
};
@@ -1657,7 +1657,7 @@ static void cxl_port_reset_targets(struct cxl_port *port,
cxl_rr->nr_targets_set = 0;
cxld = cxl_rr->decoder;
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -1804,7 +1804,7 @@ static int cxl_region_attach_position(struct cxl_region *cxlr,
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_switch_decoder *cxlsd = &cxlrd->cxlsd;
struct cxl_decoder *cxld = &cxlsd->cxld;
- int iw = cxld->interleave_ways;
+ int iw = cxld->config.interleave_ways;
struct cxl_port *iter;
int rc;
@@ -1902,7 +1902,7 @@ static int match_switch_decoder_by_range(struct device *dev,
return 0;
cxlsd = to_cxl_switch_decoder(dev);
- r1 = &cxlsd->cxld.hpa_range;
+ r1 = &cxlsd->cxld.config.hpa_range;
if (is_root_decoder(dev))
return range_contains(r1, r2);
@@ -1929,7 +1929,7 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
return rc;
}
cxlsd = to_cxl_switch_decoder(dev);
- *ways = cxlsd->cxld.interleave_ways;
+ *ways = cxlsd->cxld.config.interleave_ways;
for (int i = 0; i < *ways; i++) {
if (cxlsd->target[i] == port->parent_dport) {
@@ -2105,10 +2105,10 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
- if (cxled->cxld.target_type != cxlr->type) {
+ if (cxled->cxld.config.target_type != cxlr->type) {
dev_dbg(&cxlr->dev, "%s:%s type mismatch: %d vs %d\n",
dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
- cxled->cxld.target_type, cxlr->type);
+ cxled->cxld.config.target_type, cxlr->type);
return -ENXIO;
}
@@ -2194,9 +2194,9 @@ static int cxl_region_attach(struct cxl_region *cxlr,
cxl_region_shared_upstream_bandwidth_update(cxlr);
}
- cxled->cxld.interleave_ways = p->interleave_ways;
- cxled->cxld.interleave_granularity = p->interleave_granularity;
- cxled->cxld.hpa_range = (struct range) {
+ cxled->cxld.config.interleave_ways = p->interleave_ways;
+ cxled->cxld.config.interleave_granularity = p->interleave_granularity;
+ cxled->cxld.config.hpa_range = (struct range) {
.start = p->res->start,
.end = p->res->end,
};
@@ -2320,7 +2320,7 @@ __cxl_decoder_detach(struct cxl_region *cxlr,
}
p->targets[cxled->pos] = NULL;
p->nr_targets--;
- cxled->cxld.hpa_range = (struct range) {
+ cxled->cxld.config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -2992,7 +2992,7 @@ static int poison_by_decoder(struct device *dev, void *arg)
}
/* Iterate until commit_end is reached */
- if (cxled->cxld.id == ctx->port->commit_end) {
+ if (cxled->cxld.config.id == ctx->port->commit_end) {
ctx->offset = cxled->dpa_res->end + 1;
ctx->part = cxled->part;
return 1;
@@ -3315,10 +3315,10 @@ static u64 restore_parent(u64 addr, u64 pos, u64 gran, u64 ways)
static u64 unaligned_dpa_to_hpa(struct cxl_decoder *cxld,
struct cxl_region_params *p, int pos, u64 dpa)
{
- int ways_port = p->interleave_ways / cxld->interleave_ways;
+ int ways_port = p->interleave_ways / cxld->config.interleave_ways;
int gran_port = p->interleave_granularity;
- int gran_hb = cxld->interleave_granularity;
- int ways_hb = cxld->interleave_ways;
+ int gran_hb = cxld->config.interleave_granularity;
+ int ways_hb = cxld->config.interleave_ways;
int pos_port, pos_hb, gran_shift;
u64 hpa_port = 0;
@@ -3365,7 +3365,7 @@ static bool region_is_unaligned_mod3(struct cxl_region *cxlr)
struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
- int hbiw = cxld->interleave_ways;
+ int hbiw = cxld->config.interleave_ways;
u64 rem;
if (is_power_of_2(hbiw))
@@ -3480,8 +3480,8 @@ static int unaligned_region_offset_to_dpa_result(struct cxl_region *cxlr,
* a dpa_offset independent of the target device and then enumerate
* and test that dpa_offset against each candidate endpoint decoder.
*/
- gran = cxld->interleave_granularity;
- interleave_width = gran * cxld->interleave_ways;
+ gran = cxld->config.interleave_granularity;
+ interleave_width = gran * cxld->config.interleave_ways;
interleave_index = div64_u64(offset, interleave_width);
gran_offset = do_div(tmp, gran);
@@ -3581,7 +3581,7 @@ static int match_root_decoder(struct device *dev, const void *data)
return 0;
cxlrd = to_cxl_root_decoder(dev);
- r1 = &cxlrd->cxlsd.cxld.hpa_range;
+ r1 = &cxlrd->cxlsd.cxld.config.hpa_range;
return range_contains(r1, r2);
}
@@ -3786,7 +3786,7 @@ static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd,
do {
cxlr = __create_region(cxlrd, cxlds->part[part].mode,
atomic_read(&cxlrd->region_id),
- cxled->cxld.target_type);
+ cxled->cxld.config.target_type);
} while (IS_ERR(cxlr) && PTR_ERR(cxlr) == -EBUSY);
if (IS_ERR(cxlr)) {
@@ -3829,9 +3829,9 @@ int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
ctx = (struct cxl_region_context) {
.cxled = cxled,
- .hpa_range = cxled->cxld.hpa_range,
- .interleave_ways = cxled->cxld.interleave_ways,
- .interleave_granularity = cxled->cxld.interleave_granularity,
+ .hpa_range = cxled->cxld.config.hpa_range,
+ .interleave_ways = cxled->cxld.config.interleave_ways,
+ .interleave_granularity = cxled->cxld.config.interleave_granularity,
};
struct cxl_root_decoder *cxlrd __free(put_cxl_root_decoder) =
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 44ff3334648c..688350c7c5fb 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -281,7 +281,7 @@ enum cxl_decoder_state {
* struct cxl_endpoint_decoder - Endpoint / SPA to DPA decoder
* @cxld: base cxl_decoder_object
* @dpa_res: actively claimed DPA span of this decoder
- * @skip: offset into @dpa_res where @cxld.hpa_range maps
+ * @skip: offset into @dpa_res where @cxld.config.hpa_range maps
* @state: autodiscovery state
* @part: partition index this decoder maps
* @pos: interleave position in @cxld.region
diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c
index c7c91e8dc51d..9a0cbcd230b2 100644
--- a/drivers/cxl/pci.c
+++ b/drivers/cxl/pci.c
@@ -966,7 +966,7 @@ static int cxl_endpoint_decoder_clear_reset_flags(struct device *dev, void *data
return 0;
cxled = to_cxl_endpoint_decoder(dev);
- cxled->cxld.flags &= ~CXL_DECODER_F_RESET_MASK;
+ cxled->cxld.config.flags &= ~CXL_DECODER_F_RESET_MASK;
return 0;
}
diff --git a/drivers/cxl/port.c b/drivers/cxl/port.c
index c12fd0b89883..b369d3e3876d 100644
--- a/drivers/cxl/port.c
+++ b/drivers/cxl/port.c
@@ -40,7 +40,7 @@ static int discover_region(struct device *dev, void *unused)
return 0;
cxled = to_cxl_endpoint_decoder(dev);
- if ((cxled->cxld.flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxled->cxld.config.flags & CXL_DECODER_F_ENABLE) == 0)
return 0;
if (cxled->state != CXL_DECODER_STATE_AUTO)
@@ -56,7 +56,7 @@ static int discover_region(struct device *dev, void *unused)
rc = cxl_add_to_region(cxled);
if (rc)
dev_dbg(dev, "failed to add to region: %#llx-%#llx\n",
- cxled->cxld.hpa_range.start, cxled->cxld.hpa_range.end);
+ cxled->cxld.config.hpa_range.start, cxled->cxld.config.hpa_range.end);
return 0;
}
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 69941f7fdb76..9af01e1f45ba 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -35,15 +35,31 @@ enum cxl_decoder_type {
#define CXL_DECODER_MAX_INTERLEAVE 16
/**
- * struct cxl_decoder - Common CXL HDM Decoder Attributes
- * @dev: this decoder's device
+ * struct cxl_decoder_config - common CXL decoder configuration and state
* @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
+ *
+ * This is not a complete snapshot of the hardware registers. Switch target
+ * lists and endpoint DPA skip values are not tracked in this common structure.
+ */
+struct cxl_decoder_config {
+ int id;
+ struct range hpa_range;
+ int interleave_ways;
+ int interleave_granularity;
+ enum cxl_decoder_type target_type;
+ unsigned long flags;
+};
+
+/**
+ * struct cxl_decoder - Common CXL HDM Decoder Attributes
+ * @dev: this decoder's device
+ * @config: common decoder configuration and state
+ * @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.
@@ -52,13 +68,8 @@ enum cxl_decoder_type {
*/
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_decoder_config config;
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);
diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index 2921f1111870..034d3a923b2c 100644
--- a/tools/testing/cxl/test/cxl.c
+++ b/tools/testing/cxl/test/cxl.c
@@ -773,7 +773,7 @@ static int map_targets(struct device *dev, void *data)
* id into the low bits.
*
* The key is formed as:
- * ((unsigned long)port->uport_dev << 4) | cxld->id
+ * ((unsigned long)port->uport_dev << 4) | cxld->config.id
*
* The low bits hold the decoder id (which must fit in 4 bits) while
* the remaining bits identify the upstream port. This key is only used
@@ -783,9 +783,9 @@ static unsigned long cxld_registry_index(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- dev_WARN_ONCE(&port->dev, cxld->id >= 16,
- "decoder id:%d out of range\n", cxld->id);
- return (((unsigned long)port->uport_dev) << 4) | cxld->id;
+ dev_WARN_ONCE(&port->dev, cxld->config.id >= 16,
+ "decoder id:%d out of range\n", cxld->config.id);
+ return (((unsigned long)port->uport_dev) << 4) | cxld->config.id;
}
struct cxl_test_decoder {
@@ -801,14 +801,15 @@ static struct cxl_test_decoder *cxld_registry_find(struct cxl_decoder *cxld)
return xa_load(&decoder_registry, cxld_registry_index(cxld));
}
-#define dbg_cxld(port, msg, cxld) \
- do { \
- struct cxl_decoder *___d = (cxld); \
- dev_dbg((port)->uport_dev, \
+#define dbg_cxld(port, msg, cxld) \
+ do { \
+ struct cxl_decoder *___d = (cxld); \
+ const struct cxl_decoder_config *___c = &___d->config; \
+ dev_dbg((port)->uport_dev, \
"decoder%d: %s range: %#llx-%#llx iw: %d ig: %d flags: %#lx\n", \
- ___d->id, msg, ___d->hpa_range.start, \
- ___d->hpa_range.end + 1, ___d->interleave_ways, \
- ___d->interleave_granularity, ___d->flags); \
+ ___c->id, msg, ___c->hpa_range.start, \
+ ___c->hpa_range.end + 1, ___c->interleave_ways, \
+ ___c->interleave_granularity, ___c->flags); \
} while (0)
static int mock_decoder_commit(struct cxl_decoder *cxld);
@@ -817,12 +818,7 @@ static void init_disabled_mock_decoder(struct cxl_decoder *cxld);
static void cxld_copy(struct cxl_decoder *a, struct cxl_decoder *b)
{
- a->id = b->id;
- a->hpa_range = b->hpa_range;
- a->interleave_ways = b->interleave_ways;
- a->interleave_granularity = b->interleave_granularity;
- a->target_type = b->target_type;
- a->flags = b->flags;
+ a->config = b->config;
a->commit = mock_decoder_commit;
a->reset = mock_decoder_reset;
}
@@ -847,7 +843,7 @@ static int cxld_registry_restore(struct cxl_decoder *cxld,
struct cxl_switch_decoder *cxlsd =
to_cxl_switch_decoder(&cxld->dev);
- if (!(td->cxlsd.cxld.flags & CXL_DECODER_F_ENABLE))
+ if (!(td->cxlsd.cxld.config.flags & CXL_DECODER_F_ENABLE))
return 0;
dbg_cxld(port, "restore", &td->cxlsd.cxld);
@@ -860,13 +856,13 @@ static int cxld_registry_restore(struct cxl_decoder *cxld,
cxld->target_map[i] = td->cxlsd.cxld.target_map[i];
}
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
} else {
struct cxl_endpoint_decoder *cxled =
to_cxl_endpoint_decoder(&cxld->dev);
- if (!(td->cxled.cxld.flags & CXL_DECODER_F_ENABLE))
+ if (!(td->cxled.cxld.config.flags & CXL_DECODER_F_ENABLE))
return 0;
dbg_cxld(port, "restore", &td->cxled.cxld);
@@ -881,7 +877,7 @@ static int cxld_registry_restore(struct cxl_decoder *cxld,
init_disabled_mock_decoder(cxld);
return rc;
}
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
}
return 0;
@@ -915,7 +911,7 @@ static void __cxld_registry_save(struct cxl_test_decoder *td,
td->cxled.state = cxled->state;
td->cxled.skip = cxled->skip;
- if (!(cxld->flags & CXL_DECODER_F_ENABLE)) {
+ if (!(cxld->config.flags & CXL_DECODER_F_ENABLE)) {
td->dpa_range.start = 0;
td->dpa_range.end = -1;
} else if (cxled->dpa_res) {
@@ -952,9 +948,9 @@ static void cxld_registry_update(struct cxl_decoder *cxld)
static int mock_decoder_commit(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- int id = cxld->id;
+ int id = cxld->config.id;
- if (cxld->flags & CXL_DECODER_F_ENABLE)
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE)
return 0;
dev_dbg(&port->dev, "%s commit\n", dev_name(&cxld->dev));
@@ -967,7 +963,7 @@ static int mock_decoder_commit(struct cxl_decoder *cxld)
}
port->commit_end++;
- cxld->flags |= CXL_DECODER_F_ENABLE;
+ cxld->config.flags |= CXL_DECODER_F_ENABLE;
if (is_endpoint_decoder(&cxld->dev)) {
struct cxl_endpoint_decoder *cxled =
to_cxl_endpoint_decoder(&cxld->dev);
@@ -982,9 +978,9 @@ static int mock_decoder_commit(struct cxl_decoder *cxld)
static void mock_decoder_reset(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- int id = cxld->id;
+ int id = cxld->config.id;
- if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)
return;
dev_dbg(&port->dev, "%s reset\n", dev_name(&cxld->dev));
@@ -994,7 +990,7 @@ static void mock_decoder_reset(struct cxl_decoder *cxld)
dev_dbg(&port->dev,
"%s: out of order reset, expected decoder%d.%d\n",
dev_name(&cxld->dev), port->id, port->commit_end);
- cxld->flags &= ~CXL_DECODER_F_ENABLE;
+ cxld->config.flags &= ~CXL_DECODER_F_ENABLE;
if (is_endpoint_decoder(&cxld->dev)) {
struct cxl_endpoint_decoder *cxled =
@@ -1005,7 +1001,7 @@ static void mock_decoder_reset(struct cxl_decoder *cxld)
}
if (decoder_reset_preserve_registry)
dev_dbg(port->uport_dev, "decoder%d: skip registry update\n",
- cxld->id);
+ cxld->config.id);
else
cxld_registry_update(cxld);
}
@@ -1030,12 +1026,12 @@ static struct cxl_test_decoder *cxld_registry_new(struct cxl_decoder *cxld)
static void init_disabled_mock_decoder(struct cxl_decoder *cxld)
{
- cxld->hpa_range.start = 0;
- cxld->hpa_range.end = -1;
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = 0;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->flags = 0;
+ cxld->config.hpa_range.start = 0;
+ cxld->config.hpa_range.end = -1;
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = 0;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.flags = 0;
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
@@ -1058,14 +1054,14 @@ static void init_disabled_mock_decoder(struct cxl_decoder *cxld)
static void default_mock_decoder(struct cxl_decoder *cxld)
{
- cxld->hpa_range = (struct range){
+ cxld->config.hpa_range = (struct range){
.start = 0,
.end = -1,
};
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = 256;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = 256;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
@@ -1080,7 +1076,7 @@ static int match_decoder_by_index(struct device *dev, const void *data)
if (!is_switch_decoder(dev))
return 0;
cxld = to_cxl_decoder(dev);
- return cxld->id == target_id;
+ return cxld->config.id == target_id;
}
/*
@@ -1093,11 +1089,11 @@ static void size_zero_mock_decoder_ep(struct cxl_decoder *cxld, u64 base)
{
struct cxl_endpoint_decoder *cxled = to_cxl_endpoint_decoder(&cxld->dev);
- cxld->hpa_range = DEFINE_RANGE(base, base - 1);
- cxld->interleave_ways = 2;
- cxld->interleave_granularity = 4096;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->flags = CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
+ cxld->config.hpa_range = DEFINE_RANGE(base, base - 1);
+ cxld->config.interleave_ways = 2;
+ cxld->config.interleave_granularity = 4096;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
cxled->state = CXL_DECODER_STATE_AUTO;
/* decoder[0] reserved [0, size/2), empty decoders sit at that watermark */
devm_cxl_dpa_reserve(cxled, mock_auto_region_size / 2, 0, 0);
@@ -1108,11 +1104,11 @@ static void size_zero_mock_decoder_ep(struct cxl_decoder *cxld, u64 base)
static void size_zero_mock_decoder_sw(struct cxl_decoder *cxld, u64 base,
int level)
{
- cxld->flags = CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->interleave_ways = level == 0 ? 2 : 1;
- cxld->interleave_granularity = 4096;
- cxld->hpa_range = DEFINE_RANGE(base, base - 1);
+ cxld->config.flags = CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.interleave_ways = level == 0 ? 2 : 1;
+ cxld->config.interleave_granularity = 4096;
+ cxld->config.hpa_range = DEFINE_RANGE(base, base - 1);
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
}
@@ -1147,7 +1143,7 @@ static enum cxld_init_type get_decoder_init_type(struct cxl_decoder *cxld,
* See 'cxl list -BMPu -m cxl_mem.0,cxl_mem.4'
*/
if (!is_endpoint_decoder(&cxld->dev) || !hb0 || pdev->id % 4 ||
- pdev->id > 4 || cxld->id > (mock_zero_size_decoders ? 2 : 0))
+ pdev->id > 4 || cxld->config.id > (mock_zero_size_decoders ? 2 : 0))
return MOCK_DECODER_INIT_DEFAULT;
return type2_test ? MOCK_DECODER_INIT_TYPE2_AUTO :
@@ -1159,9 +1155,9 @@ static bool mock_decoder_handle_saved(struct cxl_decoder *cxld, struct cxl_test_
bool enabled;
if (is_switch_decoder(&cxld->dev))
- enabled = td->cxlsd.cxld.flags & CXL_DECODER_F_ENABLE;
+ enabled = td->cxlsd.cxld.config.flags & CXL_DECODER_F_ENABLE;
else
- enabled = td->cxled.cxld.flags & CXL_DECODER_F_ENABLE;
+ enabled = td->cxled.cxld.config.flags & CXL_DECODER_F_ENABLE;
if (enabled)
return !cxld_registry_restore(cxld, td);
@@ -1183,19 +1179,19 @@ static void mock_init_hdm_type2_cxled(struct cxl_endpoint_decoder *cxled,
int id = 0;
base = window->base_hpa;
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + mock_auto_region_size - 1,
};
- cxld->interleave_ways = 1;
- eig_to_granularity(window->granularity, &cxld->interleave_granularity);
- cxld->target_type = CXL_DECODER_DEVMEM;
- cxld->flags = CXL_DECODER_F_ENABLE;
+ cxld->config.interleave_ways = 1;
+ eig_to_granularity(window->granularity, &cxld->config.interleave_granularity);
+ cxld->config.target_type = CXL_DECODER_DEVMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE;
cxled->state = CXL_DECODER_STATE_AUTO;
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
devm_cxl_dpa_reserve(cxled, 0,
- mock_auto_region_size / cxld->interleave_ways, 0);
+ mock_auto_region_size / cxld->config.interleave_ways, 0);
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
@@ -1216,13 +1212,13 @@ static void mock_init_hdm_type2_cxled(struct cxl_endpoint_decoder *cxled,
cxlsd = to_cxl_switch_decoder(dev);
cxld = &cxlsd->cxld;
- cxld->target_type = CXL_DECODER_DEVMEM;
- cxld->flags = CXL_DECODER_F_ENABLE;
+ cxld->config.target_type = CXL_DECODER_DEVMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE;
root_port->commit_end = 0;
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = 4096;
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = 4096;
cxld->target_map[0] = dport->port_id;
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + mock_auto_region_size - 1,
};
@@ -1258,7 +1254,7 @@ static void mock_init_hdm_type3_cxled(struct cxl_endpoint_decoder *cxled,
int i;
/* Simulate missing cxl_mem.4 configuration */
- if (hb0 && pdev->id == 4 && cxld->id == 0 && fail_autoassemble) {
+ if (hb0 && pdev->id == 4 && cxld->config.id == 0 && fail_autoassemble) {
default_mock_decoder(cxld);
return;
}
@@ -1274,25 +1270,25 @@ static void mock_init_hdm_type3_cxled(struct cxl_endpoint_decoder *cxled,
* points at a zero-size decoder, exercising the zero-size
* reservation and poison-by-endpoint code paths.
*/
- if (cxld->id == 1 || cxld->id == 2) {
+ if (cxld->config.id == 1 || cxld->config.id == 2) {
size_zero_mock_decoder_ep(cxld, base);
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
WARN_ON_ONCE(!cxld_registry_new(cxld));
return;
}
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + mock_auto_region_size - 1,
};
- cxld->interleave_ways = 2;
- eig_to_granularity(window->granularity, &cxld->interleave_granularity);
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->flags = CXL_DECODER_F_ENABLE;
+ cxld->config.interleave_ways = 2;
+ eig_to_granularity(window->granularity, &cxld->config.interleave_granularity);
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE;
cxled->state = CXL_DECODER_STATE_AUTO;
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
devm_cxl_dpa_reserve(cxled, 0,
- mock_auto_region_size / cxld->interleave_ways, 0);
+ mock_auto_region_size / cxld->config.interleave_ways, 0);
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
@@ -1326,19 +1322,19 @@ static void mock_init_hdm_type3_cxled(struct cxl_endpoint_decoder *cxled,
cxlsd->cxld.target_map[0] = dport->port_id;
}
cxld = &cxlsd->cxld;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->flags = CXL_DECODER_F_ENABLE;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE;
iter->commit_end = 0;
/*
* Switch targets 2 endpoints, while host bridge targets
* one root port
*/
if (i == 0)
- cxld->interleave_ways = 2;
+ cxld->config.interleave_ways = 2;
else
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = 4096;
- cxld->hpa_range = (struct range) {
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = 4096;
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + mock_auto_region_size - 1,
};
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 05/16] cxl: Introduce endpoint HDM decoder settings
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (3 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 04/16] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 06/16] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Keep common decoder configuration in cxl_decoder_config and represent
endpoint DPA skip in cxl_endpoint_decoder_settings. Share range
programming, but keep endpoint skip and switch target-list writes in
their respective commit paths.
Separate commit initiation from completion waiting so reset restoration
can reuse register programming without changing normal DPA-lock policy.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=20f9084b5b763238e2f18f629916bbbb46ec9f7d
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
drivers/cxl/core/core.h | 8 ++++
drivers/cxl/core/hdm.c | 96 ++++++++++++++++++++++++++++-------------
include/cxl/hdm.h | 19 ++++++++
3 files changed, 93 insertions(+), 30 deletions(-)
create mode 100644 include/cxl/hdm.h
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 983d7690c3a5..fb14b222f3ca 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -7,6 +7,8 @@
#include <cxl/mailbox.h>
#include <linux/rwsem.h>
+struct cxl_endpoint_decoder_settings;
+
extern const struct device_type cxl_nvdimm_bridge_type;
extern const struct device_type cxl_nvdimm_type;
extern const struct device_type cxl_pmu_type;
@@ -139,6 +141,12 @@ struct cxl_rwsem {
extern struct cxl_rwsem cxl_rwsem;
+void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm);
+void cxl_commit_start_switch(const struct cxl_decoder_config *config,
+ u64 targets, void __iomem *hdm);
+int cxld_await_commit(void __iomem *hdm, int id);
+
int cxl_memdev_init(void);
void cxl_memdev_exit(void);
void cxl_mbox_init(void);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 9e05032a5426..7d3254a712e4 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -3,6 +3,7 @@
#include <linux/seq_file.h>
#include <linux/device.h>
#include <linux/delay.h>
+#include <cxl/hdm.h>
#include "cxlmem.h"
#include "core.h"
@@ -707,7 +708,7 @@ 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 void cxld_set_interleave(const struct cxl_decoder_config *config, u32 *ctrl)
{
u16 eig;
u8 eiw;
@@ -716,12 +717,12 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
* Input validation ensures these warns never fire, but otherwise
* suppress unititalized variable usage warnings.
*/
- if (WARN_ONCE(ways_to_eiw(cxld->config.interleave_ways, &eiw),
- "invalid interleave_ways: %d\n", cxld->config.interleave_ways))
+ if (WARN_ONCE(ways_to_eiw(config->interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n", config->interleave_ways))
return;
- if (WARN_ONCE(granularity_to_eig(cxld->config.interleave_granularity, &eig),
+ if (WARN_ONCE(granularity_to_eig(config->interleave_granularity, &eig),
"invalid interleave_granularity: %d\n",
- cxld->config.interleave_granularity))
+ config->interleave_granularity))
return;
u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
@@ -729,10 +730,10 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
*ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
}
-static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
+static void cxld_set_type(const struct cxl_decoder_config *config, u32 *ctrl)
{
u32p_replace_bits(ctrl,
- !!(cxld->config.target_type == CXL_DECODER_HOSTONLYMEM),
+ !!(config->target_type == CXL_DECODER_HOSTONLYMEM),
CXL_HDM_DECODER0_CTRL_HOSTONLY);
}
@@ -764,7 +765,7 @@ static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
* clock skew and other marginal behavior
*/
#define COMMIT_TIMEOUT_MS 20
-static int cxld_await_commit(void __iomem *hdm, int id)
+int cxld_await_commit(void __iomem *hdm, int id)
{
u32 ctrl;
int i;
@@ -784,45 +785,80 @@ static int cxld_await_commit(void __iomem *hdm, int id)
return -ETIMEDOUT;
}
-static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
+static u32 cxl_hdm_write_range(void __iomem *hdm,
+ const struct cxl_decoder_config *config)
{
- int id = cxld->config.id;
- u64 base, size;
+ u64 size = range_len(&config->hpa_range);
+ u64 base = config->hpa_range.start;
+ int id = config->id;
u32 ctrl;
- /* common decoder settings */
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->config.id));
- cxld_set_interleave(cxld, &ctrl);
- cxld_set_type(cxld, &ctrl);
- base = cxld->config.hpa_range.start;
- size = range_len(&cxld->config.hpa_range);
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ cxld_set_interleave(config, &ctrl);
+ cxld_set_type(config, &ctrl);
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));
+ return ctrl;
+}
+
+void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ int id = settings->config.id;
+ u32 ctrl;
+
+ lockdep_assert_held(&cxl_rwsem.dpa);
+ ctrl = cxl_hdm_write_range(hdm, &settings->config);
+ writel(upper_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
+ writel(lower_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+
+void cxl_commit_start_switch(const struct cxl_decoder_config *config,
+ u64 targets, void __iomem *hdm)
+{
+ int id = config->id;
+ u32 ctrl;
+
+ lockdep_assert_held(&cxl_rwsem.dpa);
+ ctrl = cxl_hdm_write_range(hdm, config);
+ writel(upper_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_HIGH(id));
+ writel(lower_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_LOW(id));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+
+static void
+cxl_endpoint_decoder_snapshot(struct cxl_endpoint_decoder *cxled,
+ struct cxl_endpoint_decoder_settings *settings)
+{
+ lockdep_assert_held(&cxl_rwsem.dpa);
+
+ *settings = (struct cxl_endpoint_decoder_settings) {
+ .config = cxled->cxld.config,
+ .skip = cxled->skip,
+ };
+}
+
+static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
+{
+ struct cxl_endpoint_decoder_settings settings;
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);
+ cxl_commit_start_switch(&cxld->config, targets, hdm);
+ return;
}
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ cxl_endpoint_decoder_snapshot(to_cxl_endpoint_decoder(&cxld->dev),
+ &settings);
+ cxl_commit_start_endpoint(&settings, hdm);
}
static int cxl_decoder_commit(struct cxl_decoder *cxld)
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
new file mode 100644
index 000000000000..219b9bb67440
--- /dev/null
+++ b/include/cxl/hdm.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#ifndef __CXL_HDM_H__
+#define __CXL_HDM_H__
+
+#include <linux/types.h>
+#include <cxl/cxl.h>
+
+/**
+ * struct cxl_endpoint_decoder_settings - endpoint HDM decoder programming
+ * @config: common decoder configuration
+ * @skip: length of DPA space skipped before this decoder's DPA mapping
+ */
+struct cxl_endpoint_decoder_settings {
+ struct cxl_decoder_config config;
+ u64 skip;
+};
+
+#endif /* __CXL_HDM_H__ */
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 06/16] cxl: Move HDM decoder helpers to built-in code
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (4 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 05/16] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 07/16] cxl: Share HDM decoder register unpacking Srirangan Madhavan
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
The PCI core will restore HDM decoders after a reset, possibly while no
CXL driver is loaded, so it cannot call into cxl_core.
Move the HDM register programming helpers from hdm.c to hdm_regs.c and
build that file into the kernel whenever CXL_BUS is enabled, including
when CXL_BUS is modular. Export only the symbols that cxl_core uses.
Keep the DPA lock assertion in the normal decoder programming caller so
the register helpers do not depend on cxl_rwsem. Leave cxl_rwsem in
cxl_core until built-in cache management needs it.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=e0b476ea99567fb5aa416eac0cf61bca20724880
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/Makefile | 1 +
drivers/cxl/core/hdm.c | 102 +------------------------------
drivers/cxl/core/hdm_regs.c | 118 ++++++++++++++++++++++++++++++++++++
3 files changed, 121 insertions(+), 100 deletions(-)
create mode 100644 drivers/cxl/core/hdm_regs.c
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index ce7213818d3c..6703c1729396 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
+obj-$(subst m,y,$(CONFIG_CXL_BUS)) += hdm_regs.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 7d3254a712e4..47cf49bcd2e5 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -708,35 +708,6 @@ int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
}
-static void cxld_set_interleave(const struct cxl_decoder_config *config, u32 *ctrl)
-{
- u16 eig;
- u8 eiw;
-
- /*
- * Input validation ensures these warns never fire, but otherwise
- * suppress unititalized variable usage warnings.
- */
- if (WARN_ONCE(ways_to_eiw(config->interleave_ways, &eiw),
- "invalid interleave_ways: %d\n", config->interleave_ways))
- return;
- if (WARN_ONCE(granularity_to_eig(config->interleave_granularity, &eig),
- "invalid interleave_granularity: %d\n",
- config->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(const struct cxl_decoder_config *config, u32 *ctrl)
-{
- u32p_replace_bits(ctrl,
- !!(config->target_type == CXL_DECODER_HOSTONLYMEM),
- CXL_HDM_DECODER0_CTRL_HOSTONLY);
-}
-
static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
{
struct cxl_dport **t = &cxlsd->target[0];
@@ -759,77 +730,6 @@ static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
*tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
}
-/*
- * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set
- * committed or error within 10ms, but just be generous with 20ms to account for
- * clock skew and other marginal behavior
- */
-#define COMMIT_TIMEOUT_MS 20
-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 u32 cxl_hdm_write_range(void __iomem *hdm,
- const struct cxl_decoder_config *config)
-{
- u64 size = range_len(&config->hpa_range);
- u64 base = config->hpa_range.start;
- int id = config->id;
- u32 ctrl;
-
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
- cxld_set_interleave(config, &ctrl);
- cxld_set_type(config, &ctrl);
-
- 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));
- return ctrl;
-}
-
-void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
- void __iomem *hdm)
-{
- int id = settings->config.id;
- u32 ctrl;
-
- lockdep_assert_held(&cxl_rwsem.dpa);
- ctrl = cxl_hdm_write_range(hdm, &settings->config);
- writel(upper_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
- writel(lower_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
-}
-
-void cxl_commit_start_switch(const struct cxl_decoder_config *config,
- u64 targets, void __iomem *hdm)
-{
- int id = config->id;
- u32 ctrl;
-
- lockdep_assert_held(&cxl_rwsem.dpa);
- ctrl = cxl_hdm_write_range(hdm, config);
- writel(upper_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_HIGH(id));
- writel(lower_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_LOW(id));
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
-}
-
static void
cxl_endpoint_decoder_snapshot(struct cxl_endpoint_decoder *cxled,
struct cxl_endpoint_decoder_settings *settings)
@@ -846,6 +746,8 @@ static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
{
struct cxl_endpoint_decoder_settings settings;
+ lockdep_assert_held(&cxl_rwsem.dpa);
+
if (is_switch_decoder(&cxld->dev)) {
struct cxl_switch_decoder *cxlsd =
to_cxl_switch_decoder(&cxld->dev);
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
new file mode 100644
index 000000000000..53d2f236e49c
--- /dev/null
+++ b/drivers/cxl/core/hdm_regs.c
@@ -0,0 +1,118 @@
+// 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/hdm.h>
+
+#include "cxl.h"
+#include "core.h"
+
+/*
+ * HDM decoder register helpers are built in whenever CXL_BUS is enabled,
+ * including when cxl_core is modular. Callers provide any required locking.
+ */
+
+static void cxld_set_interleave(const struct cxl_decoder_config *config, u32 *ctrl)
+{
+ u16 eig;
+ u8 eiw;
+
+ /*
+ * Input validation ensures these warns never fire, but otherwise
+ * suppress unititalized variable usage warnings.
+ */
+ if (WARN_ONCE(ways_to_eiw(config->interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n", config->interleave_ways))
+ return;
+ if (WARN_ONCE(granularity_to_eig(config->interleave_granularity, &eig),
+ "invalid interleave_granularity: %d\n",
+ config->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(const struct cxl_decoder_config *config, u32 *ctrl)
+{
+ u32p_replace_bits(ctrl,
+ !!(config->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
+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;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxld_await_commit, "cxl_core");
+
+static u32 cxl_hdm_write_range(void __iomem *hdm,
+ const struct cxl_decoder_config *config)
+{
+ u64 size = range_len(&config->hpa_range);
+ u64 base = config->hpa_range.start;
+ int id = config->id;
+ u32 ctrl;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ cxld_set_interleave(config, &ctrl);
+ cxld_set_type(config, &ctrl);
+
+ 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));
+ return ctrl;
+}
+
+void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ int id = settings->config.id;
+ u32 ctrl;
+
+ ctrl = cxl_hdm_write_range(hdm, &settings->config);
+ writel(upper_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
+ writel(lower_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start_endpoint, "cxl_core");
+
+void cxl_commit_start_switch(const struct cxl_decoder_config *config,
+ u64 targets, void __iomem *hdm)
+{
+ int id = config->id;
+ u32 ctrl;
+
+ ctrl = cxl_hdm_write_range(hdm, config);
+ writel(upper_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_HIGH(id));
+ writel(lower_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_LOW(id));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start_switch, "cxl_core");
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 07/16] cxl: Share HDM decoder register unpacking
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (5 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 06/16] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 08/16] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Factor common HDM register parsing into cxl_hdm_unpack_decoder().
Validate range and interleave parameters in locals before publishing the
decoder configuration, including enable and lock flags. Reject nonzero
ranges whose end overflows the address space while preserving the
zero-size range representation.
Read switch target lists and endpoint DPA skip in their respective
branches of init_hdm_decoder().
Preserve the decoder ID allocated by cxl_decoder_init() when publishing
the parsed configuration.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=7d6ed7faf7d8e14abce8ae23902d5323738868d2
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
drivers/cxl/core/core.h | 3 ++
drivers/cxl/core/hdm.c | 54 +++++++++-------------------------
drivers/cxl/core/hdm_regs.c | 58 +++++++++++++++++++++++++++++++++++++
3 files changed, 74 insertions(+), 41 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index fb14b222f3ca..d69864b25656 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -147,6 +147,9 @@ void cxl_commit_start_switch(const struct cxl_decoder_config *config,
u64 targets, void __iomem *hdm);
int cxld_await_commit(void __iomem *hdm, int id);
+int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
+ u32 ctrl, u64 base, u64 size);
+
int cxl_memdev_init(void);
void cxl_memdev_exit(void);
void cxl_mbox_init(void);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 47cf49bcd2e5..1a19078e6c6c 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -939,8 +939,8 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
u64 *dpa_base, struct cxl_endpoint_dvsec_info *info)
{
struct cxl_endpoint_decoder *cxled = NULL;
+ struct cxl_decoder_config config;
u64 size, base, skip, dpa_size, lo, hi;
- bool committed;
u32 remainder;
int i, rc;
u32 ctrl;
@@ -960,35 +960,24 @@ 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);
+ rc = cxl_hdm_unpack_decoder(&config, cxld->config.id, ctrl, base, size);
+ if (rc) {
+ dev_warn(&port->dev,
+ "decoder%d.%d: Invalid decoder configuration (ctrl: %#x): %d\n",
+ port->id, cxld->config.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->config.id);
- return -ENXIO;
- }
+ cxld->config = config;
+ size = range_len(&cxld->config.hpa_range);
if (info)
cxled = to_cxl_endpoint_decoder(&cxld->dev);
- cxld->config.hpa_range = (struct range) {
- .start = base,
- .end = base + size - 1,
- };
/* decoders are enabled if committed */
- if (committed) {
- cxld->config.flags |= CXL_DECODER_F_ENABLE;
- if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
- cxld->config.flags |= CXL_DECODER_F_LOCK;
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
- cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
- else
- cxld->config.target_type = CXL_DECODER_DEVMEM;
-
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE) {
guard(rwsem_write)(&cxl_rwsem.region);
if (cxld->config.id != cxl_num_decoders_committed(port)) {
dev_warn(&port->dev,
@@ -1022,23 +1011,6 @@ 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->config.interleave_ways);
- if (rc) {
- dev_warn(&port->dev,
- "decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
- port->id, cxld->config.id, ctrl);
- return rc;
- }
- rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
- &cxld->config.interleave_granularity);
- if (rc) {
- dev_warn(&port->dev,
- "decoder%d.%d: Invalid interleave granularity (ctrl: %#x)\n",
- port->id, cxld->config.id, ctrl);
- return rc;
- }
-
dev_dbg(&port->dev, "decoder%d.%d: range: %#llx-%#llx iw: %d ig: %d\n",
port->id, cxld->config.id, cxld->config.hpa_range.start, cxld->config.hpa_range.end,
cxld->config.interleave_ways, cxld->config.interleave_granularity);
@@ -1053,7 +1025,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
return 0;
}
- if (!committed)
+ if (!(cxld->config.flags & CXL_DECODER_F_ENABLE))
return 0;
dpa_size = div_u64_rem(size, cxld->config.interleave_ways, &remainder);
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index 53d2f236e49c..b6ee4becde8d 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -116,3 +116,61 @@ void cxl_commit_start_switch(const struct cxl_decoder_config *config,
writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
}
EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start_switch, "cxl_core");
+
+int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
+ u32 ctrl, u64 base, u64 size)
+{
+ bool committed = FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
+ enum cxl_decoder_type target_type = 0;
+ int interleave_granularity;
+ int interleave_ways;
+ unsigned long flags = 0;
+ struct range hpa_range;
+ u64 end;
+ int rc;
+
+ if (!committed)
+ size = 0;
+ if (base == U64_MAX || size == U64_MAX)
+ return -ENXIO;
+ if (size && check_add_overflow(base, size - 1, &end))
+ return -ENXIO;
+ if (!size)
+ end = base - 1;
+
+ hpa_range = (struct range) {
+ .start = base,
+ .end = end,
+ };
+
+ if (committed) {
+ flags |= CXL_DECODER_F_ENABLE;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
+ flags |= CXL_DECODER_F_LOCK;
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
+ target_type = CXL_DECODER_HOSTONLYMEM;
+ else
+ target_type = CXL_DECODER_DEVMEM;
+ }
+
+ rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
+ &interleave_ways);
+ if (rc)
+ return rc;
+ rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
+ &interleave_granularity);
+ if (rc)
+ return rc;
+
+ *config = (struct cxl_decoder_config) {
+ .id = id,
+ .hpa_range = hpa_range,
+ .interleave_ways = interleave_ways,
+ .interleave_granularity = interleave_granularity,
+ .target_type = target_type,
+ .flags = flags,
+ };
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 08/16] cxl: Refresh cached PCI HDM decoder settings
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (6 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 07/16] cxl: Share HDM decoder register unpacking Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 09/16] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Early PCI discovery creates the HDM cache, while later CXL enumeration and
decoder operations provide updated programming state.
Refresh the PCI snapshot when decoders are enumerated, committed, or reset
so reset recovery need not walk the CXL topology. Ignore updates when no
cache exists and reject decoder-count mismatches.
Use CXL_RESET for the PCI HDM cache. Shared HDM register helpers do not
require it.
Skip cache checks and updates for DVSEC-emulated decoders, which do not
use HDM registers. Keep cached global control when MMIO reads return all
ones.
Skip non-PCI mock ports to avoid test wrappers and a circular dependency
between cxl_core and cxl_mock.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=50a88f5194e5df38590d413f925d8d5afa8a8488
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
drivers/cxl/Kconfig | 5 +++
drivers/cxl/core/core.h | 3 ++
drivers/cxl/core/hdm.c | 55 +++++++++++++++++++++++++++++++-
drivers/cxl/core/pci.c | 70 +++++++++++++++++++++++++++++++++++++++++
include/cxl/cxl.h | 1 +
include/cxl/hdm.h | 25 +++++++++++++++
include/linux/pci.h | 6 ++++
7 files changed, 164 insertions(+), 1 deletion(-)
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 80aeb0d556bd..8b08bfdc4982 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
@@ -244,3 +245,7 @@ config CXL_ATL
depends on ACPI_PRMT && AMD_NB
endif
+
+# CXL_BUS requires the PCI HDM cache and built-in state helpers, even when modular.
+config CXL_RESET
+ bool
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index d69864b25656..77bc4f7d2e99 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -222,6 +222,9 @@ static inline void devm_cxl_dport_ras_setup(struct cxl_dport *dport) { }
int cxl_gpf_port_setup(struct cxl_dport *dport);
struct cxl_hdm;
+/* Hold cxl_rwsem.dpa for write while using the returned cache. */
+struct cxl_hdm_info *cxl_hdm_get_cached_info(struct cxl_hdm *cxlhdm, int id);
+int cxl_hdm_check_decoder_count(struct cxl_hdm *cxlhdm);
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm,
struct cxl_endpoint_dvsec_info *info);
int cxl_port_get_possible_dports(struct cxl_port *port);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 1a19078e6c6c..b57257efbf4e 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -730,6 +730,47 @@ static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
*tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
}
+static void cxl_hdm_refresh_decoder(struct cxl_hdm *cxlhdm,
+ struct cxl_decoder *cxld)
+{
+ void __iomem *hdm = cxlhdm->regs.hdm_decoder;
+ struct cxl_endpoint_decoder_settings *settings;
+ struct cxl_hdm_info *info;
+ u32 global_ctrl;
+
+ if (!hdm)
+ return;
+ if (!is_endpoint_decoder(&cxld->dev))
+ return;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ info = cxl_hdm_get_cached_info(cxlhdm, cxld->config.id);
+ if (!info)
+ return;
+
+ /* Preserve the last good value when the device does not respond. */
+ global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ if (global_ctrl != U32_MAX)
+ info->global_ctrl = global_ctrl;
+ settings = &info->settings[cxld->config.id];
+
+ /*
+ * A disabled decoder's software object may retain its old range and
+ * skip state. Leave only the decoder id in the cached settings so stale
+ * state is not restored as an enabled decode.
+ */
+ *settings = (struct cxl_endpoint_decoder_settings) {
+ .config.id = cxld->config.id,
+ };
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE) {
+ struct cxl_endpoint_decoder *cxled =
+ to_cxl_endpoint_decoder(&cxld->dev);
+
+ settings->config = cxld->config;
+ settings->skip = cxled->skip;
+ }
+}
+
static void
cxl_endpoint_decoder_snapshot(struct cxl_endpoint_decoder *cxled,
struct cxl_endpoint_decoder_settings *settings)
@@ -811,6 +852,7 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
}
port->commit_end++;
cxld->config.flags |= CXL_DECODER_F_ENABLE;
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
return 0;
}
@@ -883,6 +925,7 @@ static void cxl_decoder_reset(struct cxl_decoder *cxld)
writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
cxld->config.flags &= ~CXL_DECODER_F_ENABLE;
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
/* Userspace is now responsible for reconfiguring this decoder */
if (is_endpoint_decoder(&cxld->dev)) {
@@ -1090,9 +1133,17 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
{
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
struct cxl_port *port = cxlhdm->port;
- int i;
+ /* DVSEC-emulated decoders do not describe the cached HDM registers. */
+ bool emulated = should_emulate_decoders(info);
+ int check_rc, i;
u64 dpa_base = 0;
+ if (is_cxl_endpoint(port) && hdm && !emulated) {
+ check_rc = cxl_hdm_check_decoder_count(cxlhdm);
+ if (check_rc)
+ return check_rc;
+ }
+
cxl_settle_decoders(cxlhdm);
for (i = 0; i < cxlhdm->decoder_count; i++) {
@@ -1131,6 +1182,8 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
put_device(&cxld->dev);
return rc;
}
+ if (!emulated)
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
rc = add_hdm_decoder(port, cxld);
if (rc) {
dev_warn(&port->dev,
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 33b21c8dcb21..2acddb472103 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -6,6 +6,7 @@
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
+#include <cxl/hdm.h>
#include <cxl/pci.h>
#include <linux/aer.h>
#include <cxlpci.h>
@@ -180,6 +181,75 @@ int cxl_await_media_ready(struct cxl_dev_state *cxlds)
}
EXPORT_SYMBOL_NS_GPL(cxl_await_media_ready, "CXL");
+/**
+ * cxl_hdm_get_cached_info() - get the PCI-owned HDM cache for a decoder
+ * @cxlhdm: HDM capability whose port identifies the upstream PCI device
+ * @id: decoder ID to validate against the cached decoder count
+ *
+ * The caller must hold cxl_rwsem.dpa for write throughout this call and
+ * all access through the returned pointer. The pointer refers to the live
+ * cache; ownership remains with the PCI device.
+ *
+ * Return: the cache containing @id, or NULL if no upstream PCI device or
+ * cache exists, or @id is outside the cached decoder count.
+ */
+struct cxl_hdm_info *cxl_hdm_get_cached_info(struct cxl_hdm *cxlhdm, int id)
+{
+ struct cxl_hdm_info *info;
+
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(cxlhdm->port);
+
+ if (!pdev)
+ return NULL;
+
+ lockdep_assert_held_write(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ if (!info)
+ return NULL;
+
+ if (id < 0 || id >= info->decoder_count) {
+ pci_warn(pdev, "CXL HDM decoder %d exceeds cached count %d\n",
+ id, info->decoder_count);
+ return NULL;
+ }
+
+ return info;
+}
+EXPORT_SYMBOL_NS_GPL(cxl_hdm_get_cached_info, "CXL");
+
+/**
+ * cxl_hdm_check_decoder_count() - check enumeration against the PCI HDM cache
+ * @cxlhdm: HDM capability containing the decoder count to check
+ *
+ * Take cxl_rwsem.dpa for read while comparing the cached count. Call only
+ * for hardware HDM decoders, since DVSEC-emulated ranges do not describe
+ * the HDM registers represented by the cache.
+ *
+ * Return: 0 if the counts match or no upstream PCI device or cache exists,
+ * or -ENXIO if the cached and enumerated decoder counts differ.
+ */
+int cxl_hdm_check_decoder_count(struct cxl_hdm *cxlhdm)
+{
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(cxlhdm->port);
+
+ if (!pdev)
+ return 0;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ if (pdev->hdm &&
+ pdev->hdm->decoder_count != cxlhdm->decoder_count) {
+ pci_warn(pdev,
+ "CXL HDM cache decoder count mismatch: cached=%d hdm=%d\n",
+ pdev->hdm->decoder_count, cxlhdm->decoder_count);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cxl_hdm_check_decoder_count, "CXL");
+
static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 9af01e1f45ba..b29374b34cfa 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -26,6 +26,7 @@ enum cxl_devtype {
};
struct cxl_region;
+struct cxl_hdm_info;
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 219b9bb67440..d5e7efd2ffc0 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -16,4 +16,29 @@ struct cxl_endpoint_decoder_settings {
u64 skip;
};
+/**
+ * struct cxl_hdm_info - cached CXL HDM state for a PCI device
+ * @decoder_count: number of entries in @settings
+ * @hdm_bar: PCI BAR containing the HDM decoder capability
+ * @hdm_offset: offset of the HDM decoder capability in @hdm_bar
+ * @hdm_size: size of the HDM decoder register block
+ * @global_ctrl: HDM decoder global control register
+ * @dvsec_ctrl: CXL DVSEC control register
+ * @dvsec_ctrl_valid: whether @dvsec_ctrl contains valid state
+ * @settings: per-endpoint-decoder programming state
+ *
+ * Publication through pci_dev.hdm and all mutable fields are protected by
+ * cxl_rwsem.dpa.
+ */
+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_endpoint_decoder_settings settings[] __counted_by(decoder_count);
+};
+
#endif /* __CXL_HDM_H__ */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d31a8d107b1e..7bb37fcb556d 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 state */
+#endif
#ifdef CONFIG_PCI_NPEM
struct npem *npem; /* Native PCIe Enclosure Management */
#endif
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 09/16] PCI/CXL: Cache endpoint HDM state during PCI enumeration
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (7 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 08/16] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 10/16] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Cache HDM state in pci_dev->hdm after BAR resource setup and before driver
binding, so reset can restore it without a bound CXL driver.
Save the HDM register location, decoder settings, and Device DVSEC state.
Validate capability reads and decoder counts, and restore PCI_COMMAND
after temporary MMIO access, reporting restoration failures.
Keep PCI discovery separate from shared HDM helpers. Move cxl_rwsem into
built-in state code and protect cache publication with its DPA lock, so
the cache remains usable when cxl_core is modular.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=b12baa1105f33cc1a10f9c9c0c6376194397d9f3
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
MAINTAINERS | 1 +
drivers/cxl/core/Makefile | 3 +-
drivers/cxl/core/hdm.c | 5 -
drivers/cxl/core/hdm_regs.c | 99 ++++++++++++++++
drivers/cxl/core/hdm_state.c | 64 ++++++++++
drivers/cxl/core/pci.c | 17 ++-
drivers/cxl/core/regs.c | 9 ++
drivers/cxl/cxl.h | 3 -
drivers/pci/Makefile | 1 +
drivers/pci/bus.c | 1 +
drivers/pci/cxl.c | 223 +++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 8 ++
drivers/pci/probe.c | 1 +
include/cxl/cxl.h | 6 +
include/cxl/hdm.h | 12 ++
include/cxl/pci.h | 13 ++
tools/testing/cxl/Kbuild | 1 -
17 files changed, 454 insertions(+), 13 deletions(-)
create mode 100644 drivers/cxl/core/hdm_state.c
create mode 100644 drivers/pci/cxl.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c2414447892c..b874340b0265 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6572,6 +6572,7 @@ S: Maintained
F: Documentation/driver-api/cxl
F: Documentation/userspace-api/fwctl/fwctl-cxl.rst
F: drivers/cxl/
+F: drivers/pci/cxl.c
F: include/cxl/
F: include/uapi/linux/cxl_mem.h
F: tools/testing/cxl/
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 6703c1729396..9a77e68d44ce 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-$(subst m,y,$(CONFIG_CXL_BUS)) += hdm_regs.o
+obj-$(subst m,y,$(CONFIG_CXL_BUS)) += hdm_regs.o hdm_state.o regs.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
@@ -8,7 +8,6 @@ CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
cxl_core-y += pmem.o
-cxl_core-y += regs.o
cxl_core-y += memdev.o
cxl_core-y += mbox.o
cxl_core-y += pci.o
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index b57257efbf4e..a0d8f6a66810 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -17,11 +17,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;
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index b6ee4becde8d..f0b360867655 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -2,6 +2,7 @@
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
#include <linux/delay.h>
#include <linux/bug.h>
+#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/kernel.h>
@@ -174,3 +175,101 @@ int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
return 0;
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
+
+/**
+ * cxl_hdm_decoder_count_mapped() - read the decoder count of a mapped block
+ * @dev: device for error reporting
+ * @hdm: mapped HDM decoder capability
+ * @size: size of the mapping
+ *
+ * Return: the decoder count, or a negative errno if the count is reserved or
+ * the mapping cannot hold every decoder.
+ */
+int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
+ resource_size_t size)
+{
+ int count;
+
+ count = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (count < 0)
+ return count;
+
+ /* The last decoder's target-list high register must fit in the mapping. */
+ if (CXL_HDM_DECODER0_TL_HIGH(count - 1) + sizeof(u32) > size) {
+ dev_err(dev, "CXL HDM decoder count exceeds mapped register block\n");
+ return -ENXIO;
+ }
+
+ return count;
+}
+
+/* Read one endpoint decoder; a disabled decoder keeps only its id. */
+static int cxl_hdm_read_decoder_settings(struct device *dev,
+ struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm, int id)
+{
+ u64 base, size;
+ u32 ctrl;
+ int rc;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ base = ((u64)readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id)) << 32) |
+ readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ size = ((u64)readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id)) << 32) |
+ readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ rc = cxl_hdm_unpack_decoder(&settings->config, id, ctrl, base, size);
+ if (rc) {
+ dev_err(dev,
+ "CXL HDM decoder %d has invalid configuration: %d\n",
+ id, rc);
+ return rc;
+ }
+
+ if (!(settings->config.flags & CXL_DECODER_F_ENABLE))
+ *settings = (struct cxl_endpoint_decoder_settings) {
+ .config.id = id,
+ };
+ else
+ settings->skip =
+ ((u64)readl(hdm + CXL_HDM_DECODER0_SKIP_HIGH(id)) << 32) |
+ readl(hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
+
+ return 0;
+}
+
+/**
+ * cxl_hdm_read_state() - read endpoint HDM decoder state
+ * @dev: device for error messages
+ * @hdm: mapped HDM decoder capability
+ * @info: state to fill; @info->decoder_count must be set
+ *
+ * Return: 0 on success, or a negative errno if a decoder holds an invalid
+ * configuration or the decoder count changed while reading.
+ */
+int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
+ struct cxl_hdm_info *info)
+{
+ int count;
+ int rc;
+
+ info->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ for (int i = 0; i < info->decoder_count; i++) {
+ rc = cxl_hdm_read_decoder_settings(dev, &info->settings[i],
+ hdm, i);
+ if (rc)
+ return rc;
+ }
+
+ /*
+ * A changed decoder count means the snapshot no longer describes the
+ * complete hardware state. Reject it rather than publish a partial cache.
+ */
+ count = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (count != info->decoder_count) {
+ dev_err(dev, "CXL HDM decoder count changed from %d to %d\n",
+ info->decoder_count, count);
+ return -ENXIO;
+ }
+
+ return 0;
+}
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
new file mode 100644
index 000000000000..9a31f5996c74
--- /dev/null
+++ b/drivers/cxl/core/hdm_state.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <linux/cleanup.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+
+#include <cxl/hdm.h>
+#include "cxl.h"
+#include "core.h"
+
+/*
+ * CXL state that must stay available while no CXL driver is bound: the CXL
+ * locks and the per-device HDM cache filled during PCI enumeration.
+ */
+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");
+
+/* A published cache is usable only if its HDM register block was mapped. */
+bool cxl_hdm_cache_present(struct cxl_hdm_info *const *slot)
+{
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ return *slot && (*slot)->hdm_size;
+}
+
+/**
+ * cxl_hdm_cache_publish() - publish HDM state read during enumeration
+ * @slot: cache pointer owned by the device, for example &pci_dev->hdm
+ * @info: state to publish
+ *
+ * Ownership of @info passes to @slot, or @info is freed if another caller
+ * published first.
+ */
+void cxl_hdm_cache_publish(struct cxl_hdm_info **slot,
+ struct cxl_hdm_info *info)
+{
+ struct cxl_hdm_info *new __free(kfree) = info;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ /* Another initializer may have published while we read MMIO. */
+ if (!*slot)
+ *slot = no_free_ptr(new);
+}
+
+/**
+ * cxl_hdm_cache_release() - unpublish and free cached HDM state
+ * @slot: cache pointer owned by the device
+ *
+ * Take the DPA write lock to exclude readers while clearing @slot, then
+ * free the unpublished cache after releasing the lock.
+ */
+void cxl_hdm_cache_release(struct cxl_hdm_info **slot)
+{
+ struct cxl_hdm_info *info;
+
+ scoped_guard(rwsem_write, &cxl_rwsem.dpa) {
+ info = *slot;
+ /* Unpublish before freeing so readers cannot see stale state. */
+ *slot = NULL;
+ }
+ kfree(info);
+}
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 2acddb472103..88d25d0776b4 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -250,6 +250,16 @@ int cxl_hdm_check_decoder_count(struct cxl_hdm *cxlhdm)
}
EXPORT_SYMBOL_NS_GPL(cxl_hdm_check_decoder_count, "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);
@@ -261,8 +271,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;
@@ -270,6 +282,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;
}
@@ -360,7 +373,7 @@ int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds,
* capable, there is no point in continuing to enable CXL.mem.
*/
hdm_count = FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap);
- if (!hdm_count || hdm_count > 2)
+ if (!cxl_dvsec_hdm_count_valid(hdm_count))
return -EINVAL;
/*
diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c
index 20c2d9fbcfe7..ba1f942d20e1 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,
@@ -285,6 +286,8 @@ static bool cxl_decode_regblock(struct pci_dev *pdev, u32 reg_lo, u32 reg_hi,
}
map->reg_type = reg_type;
+ map->bar = bar;
+ map->bar_offset = offset;
map->resource = pci_resource_start(pdev, bar) + offset;
map->max_size = pci_resource_len(pdev, bar) - offset;
return true;
@@ -309,6 +312,7 @@ static int __cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_ty
*map = (struct cxl_register_map) {
.host = &pdev->dev,
.resource = CXL_RESOURCE_NONE,
+ .bar = -1,
};
regloc = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
@@ -339,6 +343,8 @@ static int __cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_ty
}
map->resource = CXL_RESOURCE_NONE;
+ map->bar = -1;
+ map->bar_offset = 0;
if (index == CXL_INSTANCES_COUNT)
return instance;
@@ -518,6 +524,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 +641,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 +650,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/cxl.h b/drivers/cxl/cxl.h
index 688350c7c5fb..ad4e80be2bd7 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -239,9 +239,6 @@ enum cxl_regloc_type;
int cxl_count_regblock(struct pci_dev *pdev, enum cxl_regloc_type type);
int cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map, unsigned int index);
-int cxl_find_regblock(struct pci_dev *pdev, enum cxl_regloc_type type,
- struct cxl_register_map *map);
-int cxl_setup_regs(struct cxl_register_map *map);
struct cxl_dport;
int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..7a5d9f7297e9 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_PCI_P2PDMA) += p2pdma.o
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_PCI_DOE) += doe.o
+obj-$(CONFIG_CXL_RESET) += cxl.o
obj-$(CONFIG_PCI_IDE) += ide.o
obj-$(CONFIG_PCI_TSM) += tsm.o
obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
index 655ed53436d3..aad567fb7c56 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -359,6 +359,7 @@ void pci_bus_add_device(struct pci_dev *dev)
/* Save config space for error recoverability */
pci_save_state(dev);
+ pci_cxl_hdm_cache_init(dev);
/*
* Enable runtime PM, which potentially allows the device to
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
new file mode 100644
index 000000000000..471b57391593
--- /dev/null
+++ b/drivers/pci/cxl.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+/*
+ * CXL reset support in the PCI core. Cache the HDM decoder state of a CXL
+ * memory device at enumeration, so it can be restored after a reset without
+ * a CXL driver. HDM register formats and programming live in drivers/cxl.
+ */
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include <cxl/hdm.h>
+#include <cxl/pci.h>
+
+#include "pci.h"
+
+/* Find the CXL Device DVSEC and read its capability register. */
+static int cxl_pci_device_dvsec(struct pci_dev *pdev, u16 *dvsec, u16 *cap)
+{
+ int rc;
+
+ *dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!*dvsec)
+ return -ENOTTY;
+
+ rc = pci_read_config_word(pdev, *dvsec + PCI_DVSEC_CXL_CAP, cap);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(*cap))
+ return -ENODEV;
+
+ return 0;
+}
+
+static bool cxl_pci_bar_assigned(struct pci_dev *pdev, int bar)
+{
+ struct resource *res = &pdev->resource[bar];
+
+ return pci_resource_len(pdev, bar) &&
+ !(res->flags & (IORESOURCE_UNSET | IORESOURCE_DISABLED)) &&
+ resource_type(res) == IORESOURCE_MEM && res->start && res->end;
+}
+
+static void __iomem *cxl_pci_hdm_map_regs(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ resource_size_t *hdm_offset,
+ int *decoder_count)
+{
+ struct cxl_reg_map *hdm_map = &map->component_map.hdm_decoder;
+ void __iomem *hdm;
+ int rc;
+
+ /* cxl_setup_regs() populates hdm_map, including its valid bit. */
+ rc = cxl_setup_regs(map);
+ if (rc)
+ return ERR_PTR(rc);
+ if (!hdm_map->valid)
+ return ERR_PTR(-ENODEV);
+ if (hdm_map->offset > map->max_size ||
+ hdm_map->size > map->max_size - hdm_map->offset)
+ return ERR_PTR(-ENXIO);
+ if (check_add_overflow(map->bar_offset,
+ (resource_size_t)hdm_map->offset, hdm_offset))
+ return ERR_PTR(-EOVERFLOW);
+
+ hdm = ioremap(map->resource + hdm_map->offset, hdm_map->size);
+ if (!hdm)
+ return ERR_PTR(-ENOMEM);
+
+ *decoder_count = cxl_hdm_decoder_count_mapped(&pdev->dev, hdm,
+ hdm_map->size);
+ if (*decoder_count < 0) {
+ iounmap(hdm);
+ return ERR_PTR(*decoder_count);
+ }
+
+ return hdm;
+}
+
+static struct cxl_hdm_info *__cxl_pci_hdm_read_info(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ int dvsec)
+{
+ struct cxl_reg_map *hdm_map = &map->component_map.hdm_decoder;
+ resource_size_t hdm_offset;
+ void __iomem *hdm;
+ int decoder_count;
+ u16 dvsec_ctrl;
+ int rc;
+
+ hdm = cxl_pci_hdm_map_regs(pdev, map, &hdm_offset, &decoder_count);
+ if (IS_ERR(hdm))
+ return ERR_CAST(hdm);
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL,
+ &dvsec_ctrl);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ goto out_unmap;
+ }
+
+ struct cxl_hdm_info *info =
+ kzalloc_flex(*info, settings, decoder_count, GFP_KERNEL);
+ if (!info) {
+ rc = -ENOMEM;
+ goto out_unmap;
+ }
+
+ info->decoder_count = decoder_count;
+ info->hdm_bar = map->bar;
+ info->hdm_offset = hdm_offset;
+ info->hdm_size = hdm_map->size;
+ info->dvsec = dvsec;
+ info->dvsec_ctrl = dvsec_ctrl;
+ info->dvsec_ctrl_valid = true;
+
+ rc = cxl_hdm_read_state(&pdev->dev, hdm, info);
+ if (rc)
+ goto out_free;
+
+ iounmap(hdm);
+ return info;
+
+out_free:
+ kfree(info);
+out_unmap:
+ iounmap(hdm);
+ return ERR_PTR(rc);
+}
+
+static struct cxl_hdm_info *cxl_pci_hdm_read_info(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ int dvsec)
+{
+ struct cxl_hdm_info *info;
+ u16 command;
+ int rc;
+
+ guard(pci_dev)(pdev);
+
+ rc = pci_read_config_word(pdev, PCI_COMMAND, &command);
+ if (rc)
+ return ERR_PTR(pcibios_err_to_errno(rc));
+
+ if (command & PCI_COMMAND_MEMORY)
+ return __cxl_pci_hdm_read_info(pdev, map, dvsec);
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND,
+ command | PCI_COMMAND_MEMORY);
+ if (rc)
+ return ERR_PTR(pcibios_err_to_errno(rc));
+
+ info = __cxl_pci_hdm_read_info(pdev, map, dvsec);
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (!rc)
+ return info;
+
+ pci_err(pdev,
+ "failed to restore PCI_COMMAND after CXL HDM cache init: %d\n",
+ pcibios_err_to_errno(rc));
+ if (IS_ERR(info))
+ return info;
+
+ kfree(info);
+ return ERR_PTR(pcibios_err_to_errno(rc));
+}
+
+static int __pci_cxl_hdm_cache_init(struct pci_dev *pdev)
+{
+ struct cxl_register_map map = { };
+ u16 dvsec, cap;
+ int rc;
+
+ /* Avoid duplicate MMIO reads when the cache is already present. */
+ if (cxl_hdm_cache_present(&pdev->hdm))
+ return 0;
+
+ rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
+ if (rc == -ENOTTY)
+ return 0;
+ if (rc)
+ return rc;
+ if (!(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
+ !cxl_dvsec_hdm_count_valid(FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap)))
+ return 0;
+
+ rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
+ if (rc)
+ return rc;
+ if (map.bar < 0 || map.bar >= PCI_STD_NUM_BARS ||
+ !cxl_pci_bar_assigned(pdev, map.bar))
+ return -ENODEV;
+
+ struct cxl_hdm_info *read_info =
+ cxl_pci_hdm_read_info(pdev, &map, dvsec);
+ if (IS_ERR(read_info))
+ return PTR_ERR(read_info);
+
+ /* Another initializer may have published while we read MMIO. */
+ cxl_hdm_cache_publish(&pdev->hdm, read_info);
+
+ return 0;
+}
+
+void pci_cxl_hdm_cache_init(struct pci_dev *pdev)
+{
+ int rc;
+
+ rc = __pci_cxl_hdm_cache_init(pdev);
+ if (rc)
+ pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
+}
+
+void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
+{
+ cxl_hdm_cache_release(&pdev->hdm);
+}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index ba3c3fddddc2..9e218f236e66 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -730,6 +730,14 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
+#ifdef CONFIG_CXL_RESET
+void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
+void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
+#else
+static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
+static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev) { }
+#endif
+
#ifdef CONFIG_PCI_NPEM
void pci_npem_create(struct pci_dev *dev);
void pci_npem_remove(struct pci_dev *dev);
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 27008e2ea5af..cb52357344ed 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2484,6 +2484,7 @@ static void pci_release_dev(struct device *dev)
struct pci_dev *pci_dev;
pci_dev = to_pci_dev(dev);
+ pci_cxl_hdm_cache_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 b29374b34cfa..3e70f74685b6 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -27,6 +27,7 @@ enum cxl_devtype {
struct cxl_region;
struct cxl_hdm_info;
+struct pci_dev;
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
@@ -149,6 +150,8 @@ struct cxl_pmu_reg_map {
* @base: virtual base of the register-block-BAR + @block_offset
* @resource: physical resource base of the register block
* @max_size: maximum mapping size to perform register search
+ * @bar: PCI BAR containing the register block
+ * @bar_offset: register block offset relative to @bar
* @reg_type: see enum cxl_regloc_type
* @component_map: cxl_reg_map for component registers
* @device_map: cxl_reg_maps for device registers
@@ -159,6 +162,8 @@ struct cxl_register_map {
void __iomem *base;
resource_size_t resource;
resource_size_t max_size;
+ int bar;
+ resource_size_t bar_offset;
u8 reg_type;
union {
struct cxl_component_reg_map component_map;
@@ -167,6 +172,7 @@ struct cxl_register_map {
};
};
+
/**
* struct cxl_dpa_perf - DPA performance property entry
* @dpa_range: range for DPA address
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index d5e7efd2ffc0..af7cef649446 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -23,6 +23,7 @@ struct cxl_endpoint_decoder_settings {
* @hdm_offset: offset of the HDM decoder capability in @hdm_bar
* @hdm_size: size of the HDM decoder register block
* @global_ctrl: HDM decoder global control register
+ * @dvsec: config space offset of the CXL Device DVSEC
* @dvsec_ctrl: CXL DVSEC control register
* @dvsec_ctrl_valid: whether @dvsec_ctrl contains valid state
* @settings: per-endpoint-decoder programming state
@@ -36,9 +37,20 @@ struct cxl_hdm_info {
resource_size_t hdm_offset;
resource_size_t hdm_size;
u32 global_ctrl;
+ u16 dvsec;
u16 dvsec_ctrl;
bool dvsec_ctrl_valid;
struct cxl_endpoint_decoder_settings settings[] __counted_by(decoder_count);
};
+struct device;
+
+int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
+ resource_size_t size);
+int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
+ struct cxl_hdm_info *info);
+bool cxl_hdm_cache_present(struct cxl_hdm_info *const *slot);
+void cxl_hdm_cache_publish(struct cxl_hdm_info **slot,
+ struct cxl_hdm_info *info);
+void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
#endif /* __CXL_HDM_H__ */
diff --git a/include/cxl/pci.h b/include/cxl/pci.h
index 3e0000015871..b15d7e415e27 100644
--- a/include/cxl/pci.h
+++ b/include/cxl/pci.h
@@ -4,6 +4,8 @@
#ifndef __CXL_CXL_PCI_H__
#define __CXL_CXL_PCI_H__
+#include <linux/types.h>
+
/* Register Block Identifier (RBI) */
enum cxl_regloc_type {
CXL_REGLOC_RBI_EMPTY = 0,
@@ -19,4 +21,15 @@ struct pci_dev;
int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map);
+int cxl_find_regblock(struct pci_dev *pdev, enum cxl_regloc_type type,
+ struct cxl_register_map *map);
+int cxl_setup_regs(struct cxl_register_map *map);
+/*
+ * CXL r4.0 sec 8.1.3.1: a CXL.mem capable device reports one or two HDM
+ * ranges. 00b is illegal when Mem_Capable is set and 11b is reserved.
+ */
+static inline bool cxl_dvsec_hdm_count_valid(unsigned int hdm_count)
+{
+ return hdm_count == 1 || hdm_count == 2;
+}
#endif
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] 17+ messages in thread
* [PATCH v15 10/16] PCI/CXL: Add CXL Device Reset sequencing
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (8 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 09/16] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 11/16] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Implement CXL Device Reset for eligible Type 2 devices: disable CXL.cache,
perform supported writeback, drain transactions, prepare the IOMMU,
initiate reset with Memory Clear enabled, and wait for completion.
Observe the 100 ms post-reset quiet period before polling Status2, and
treat an all-ones read as not ready. Treat Control2 command bits as
transient state, restore cache policy on exit, preserve the primary error,
and abort when IOMMU preparation fails.
Reuse Device DVSEC discovery from cache initialization and keep the
reset entry point declaration private to PCI core.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=04f8f84816d724f8b47f4d47a77916ac37c63a49
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/pci/cxl.c | 249 +++++++++++++++++++++++++++++++++-
drivers/pci/pci.h | 5 +
include/uapi/linux/pci_regs.h | 14 ++
3 files changed, 267 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 471b57391593..9eb13adf0daa 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -3,13 +3,17 @@
/*
* CXL reset support in the PCI core. Cache the HDM decoder state of a CXL
* memory device at enumeration, so it can be restored after a reset without
- * a CXL driver. HDM register formats and programming live in drivers/cxl.
+ * a CXL driver, and implement CXL Device Reset. HDM register formats and
+ * programming live in drivers/cxl.
*/
#include <linux/bitfield.h>
#include <linux/cleanup.h>
+#include <linux/delay.h>
#include <linux/errno.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>
@@ -221,3 +225,246 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
{
cxl_hdm_cache_release(&pdev->hdm);
}
+
+/*
+ * 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
+#define CXL_RESET_MAX_WAIT_MS 20000
+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
+
+#define CXL_RESET_CTRL2_CMD_MASK \
+ (PCI_DVSEC_CXL_INIT_CACHE_WBI | PCI_DVSEC_CXL_INIT_CXL_RST)
+
+static int cxl_reset_update_ctrl2_no_replay(struct pci_dev *pdev, int dvsec,
+ u16 set, u16 clear)
+{
+ u16 ctrl2;
+ int rc;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(ctrl2))
+ return -ENODEV;
+
+ /*
+ * INIT_CACHE_WBI and INIT_CXL_RST are commands. Do not replay a command
+ * that remains set in the value read back while updating persistent
+ * Control2 state.
+ */
+ ctrl2 &= ~CXL_RESET_CTRL2_CMD_MASK;
+ ctrl2 &= ~clear;
+ ctrl2 |= set;
+
+ 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_wait_cache_wbi(struct pci_dev *pdev, int dvsec)
+{
+ unsigned long deadline;
+ u16 status2;
+ int rc;
+
+ rc = cxl_reset_update_ctrl2_no_replay(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CACHE_WBI, 0);
+ if (rc)
+ return rc;
+
+ deadline = jiffies + usecs_to_jiffies(CXL_CACHE_WBI_TIMEOUT_US);
+ while (time_before(jiffies, deadline)) {
+ usleep_range(CXL_CACHE_WBI_POLL_US,
+ CXL_CACHE_WBI_POLL_US * 2);
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(status2))
+ return -ENODEV;
+ if (status2 & PCI_DVSEC_CXL_CACHE_INV)
+ return 0;
+ }
+
+ /* Do not miss completion concurrent with the polling deadline. */
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(status2))
+ return -ENODEV;
+ if (status2 & PCI_DVSEC_CXL_CACHE_INV)
+ return 0;
+
+ return -ETIMEDOUT;
+}
+
+static int cxl_reset_disable_cache(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc;
+
+ rc = cxl_reset_update_ctrl2_no_replay(pdev, dvsec,
+ PCI_DVSEC_CXL_DISABLE_CACHING, 0);
+ if (rc || !(cap & PCI_DVSEC_CXL_CACHE_WBI_CAPABLE))
+ return rc;
+
+ return cxl_reset_wait_cache_wbi(pdev, dvsec);
+}
+
+static int cxl_reset_restore_cache_policy(struct pci_dev *pdev, int dvsec,
+ u16 saved_ctrl2)
+{
+ int rc;
+
+ rc = cxl_reset_update_ctrl2_no_replay(pdev, dvsec,
+ saved_ctrl2 & PCI_DVSEC_CXL_DISABLE_CACHING,
+ PCI_DVSEC_CXL_DISABLE_CACHING);
+ if (rc)
+ pci_err(pdev, "failed to restore CXL cache policy: %d\n", rc);
+
+ return rc;
+}
+
+static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ unsigned long deadline;
+ u32 timeout_ms;
+ u16 status2;
+ int idx, rc;
+
+ idx = FIELD_GET(PCI_DVSEC_CXL_RST_TIMEOUT, cap);
+ if (idx >= ARRAY_SIZE(cxl_reset_timeout_ms)) {
+ pci_warn(pdev,
+ "unknown CXL reset timeout encoding %d; using %u ms\n",
+ idx, CXL_RESET_MAX_WAIT_MS);
+ idx = ARRAY_SIZE(cxl_reset_timeout_ms) - 1;
+ }
+
+ /* Allow one retry after the mandatory 100 ms quiet period. */
+ timeout_ms = clamp_t(u32, cxl_reset_timeout_ms[idx],
+ CXL_RESET_RRS_WAIT_MS + CXL_RESET_STATUS_POLL_MS,
+ CXL_RESET_MAX_WAIT_MS);
+ msleep(CXL_RESET_RRS_WAIT_MS);
+ deadline = jiffies + msecs_to_jiffies(timeout_ms -
+ CXL_RESET_RRS_WAIT_MS);
+
+ for (;;) {
+ 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))
+ return -ETIMEDOUT;
+
+ msleep(CXL_RESET_STATUS_POLL_MS);
+ }
+}
+
+static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
+{
+ if (initiate)
+ return cxl_reset_update_ctrl2_no_replay(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CXL_RST |
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN,
+ 0);
+
+ return cxl_reset_update_ctrl2_no_replay(pdev, dvsec, 0,
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN);
+}
+
+static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc, rc2;
+
+ if (!pci_wait_for_pending_transaction(pdev)) {
+ pci_err(pdev, "timed out waiting for pending transactions\n");
+ return -ETIMEDOUT;
+ }
+
+ rc = pci_dev_reset_iommu_prepare(pdev);
+ if (rc) {
+ pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
+ return rc;
+ }
+
+ rc = cxl_clear_memory(pdev, dvsec, true);
+ if (!rc)
+ rc = cxl_reset_wait_done(pdev, dvsec, cap);
+
+ /* Always clear Memory Clear; successful cleanup must not hide reset failure. */
+ rc2 = cxl_clear_memory(pdev, dvsec, false);
+ if (rc2)
+ pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n", rc2);
+ rc = rc ?: rc2;
+
+ pci_dev_reset_iommu_done(pdev);
+ return rc;
+}
+
+static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ u16 saved_ctrl2;
+ int rc, rc2;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &saved_ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(saved_ctrl2))
+ return -ENODEV;
+
+ rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ if (!rc)
+ rc = __cxl_reset_execute(pdev, dvsec, cap);
+ /* Restore cache policy after any attempt to disable caching. */
+ rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, saved_ctrl2);
+ return rc ?: rc2;
+}
+
+int cxl_reset_function(struct pci_dev *pdev, bool probe)
+{
+ u16 dvsec, cap, ctrl;
+ int rc;
+
+ rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
+ if (rc)
+ return rc;
+ if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
+ !(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 (PCI_POSSIBLE_ERROR(ctrl))
+ return -ENODEV;
+ if (!(ctrl & PCI_DVSEC_CXL_CACHE_ENABLE) ||
+ !(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
+ return -ENOTTY;
+
+ if (probe)
+ return 0;
+
+ return cxl_reset_execute(pdev, dvsec, cap);
+}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 9e218f236e66..743e65abd758 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -733,9 +733,14 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#ifdef CONFIG_CXL_RESET
void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
+int cxl_reset_function(struct pci_dev *pdev, bool probe);
#else
static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev) { }
+static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
+{
+ return -ENOTTY;
+}
#endif
#ifdef CONFIG_PCI_NPEM
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
index facaa324bd86..7224857c7b30 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] 17+ messages in thread
* [PATCH v15 11/16] cxl: Validate and synchronize HDM ranges around reset
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (9 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 10/16] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 12/16] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Refuse reset unless enabled system-physical HDM ranges can be reserved
exclusively and CPU-cache invalidation is available. Invalidate before
reset and again before ending IOMMU exclusion, holding range reservations
until the second invalidation completes. A later patch places state
restoration before the second invalidation.
Reject normalized-addressing decoders because their cached ranges are not
system physical addresses. Ignore zero-size decoders because they map no
address range. Refuse reset when the device decodes memory through its
DVSEC ranges instead of HDM decoders, since the cache holds no range to
reserve for that memory.
Provide the range handling as a region quiesce in built-in CXL code. The
quiesce lock blocks region commit and teardown for the whole reset, and
the HDM state copy taken under it supplies both the reserved ranges and,
later, the state to restore.
The range handling and region-quiesce structure follow Dave Jiang's
reference implementation on his cxl-type2-reset branch.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=5944c6e61f14c0dd28d54537be15edb2cadb0e8b
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
drivers/cxl/core/hdm_state.c | 274 ++++++++++++++++++++++++++++++++++-
drivers/pci/cxl.c | 50 ++++++-
include/cxl/hdm.h | 47 +++++-
3 files changed, 366 insertions(+), 5 deletions(-)
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index 9a31f5996c74..cef604013350 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -1,7 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/errno.h>
#include <linux/export.h>
+#include <linux/ioport.h>
+#include <linux/list.h>
+#include <linux/memregion.h>
+#include <linux/overflow.h>
+#include <linux/pci.h>
+#include <linux/rwsem.h>
#include <linux/slab.h>
#include <cxl/hdm.h>
@@ -10,7 +18,8 @@
/*
* CXL state that must stay available while no CXL driver is bound: the CXL
- * locks and the per-device HDM cache filled during PCI enumeration.
+ * locks, the per-device HDM cache filled during PCI enumeration, and the
+ * region quiesce used around a reset.
*/
struct cxl_rwsem cxl_rwsem = {
.region = __RWSEM_INITIALIZER(cxl_rwsem.region),
@@ -62,3 +71,266 @@ void cxl_hdm_cache_release(struct cxl_hdm_info **slot)
}
kfree(info);
}
+
+/**
+ * cxl_hdm_cache_dvsec_decode() - check for memory decoded by DVSEC ranges
+ * @slot: cache pointer owned by the device
+ *
+ * Return: true if the published state shows CXL.mem enabled while HDM
+ * decoders are globally disabled, so that the device decodes memory through
+ * its DVSEC ranges, which the cache does not describe.
+ */
+bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot)
+{
+ struct cxl_hdm_info *info;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = *slot;
+ return info && (info->dvsec_ctrl & PCI_DVSEC_CXL_MEM_ENABLE) &&
+ !(info->global_ctrl & CXL_HDM_DECODER_ENABLE);
+}
+
+/**
+ * cxl_hdm_cache_snapshot() - copy the published HDM state
+ * @slot: cache pointer owned by the device
+ *
+ * Return: a copy the caller frees with kfree(), ERR_PTR(-ENXIO) when nothing
+ * is published, or another ERR_PTR() on failure.
+ */
+struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot)
+{
+ struct cxl_hdm_info *info, *copy;
+ size_t size;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = *slot;
+ if (!info)
+ return ERR_PTR(-ENXIO);
+ if (info->decoder_count <= 0)
+ return ERR_PTR(-EINVAL);
+
+ size = struct_size(info, settings, info->decoder_count);
+ if (size == SIZE_MAX)
+ return ERR_PTR(-EOVERFLOW);
+
+ copy = kmemdup(info, size, GFP_KERNEL);
+ if (!copy)
+ return ERR_PTR(-ENOMEM);
+
+ return copy;
+}
+
+void cxl_region_quiesce_lock(void)
+ __acquires(cxl_region_quiesce)
+ __context_unsafe(token maps to cxl_rwsem.region)
+{
+ down_write(&cxl_rwsem.region);
+ __acquire(cxl_region_quiesce);
+}
+
+void cxl_region_quiesce_unlock(void)
+ __releases(cxl_region_quiesce)
+ __context_unsafe(token maps to cxl_rwsem.region)
+{
+ __release(cxl_region_quiesce);
+ up_write(&cxl_rwsem.region);
+}
+
+/*
+ * 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 device *dev,
+ const struct range *hpa_range)
+{
+ u64 len = range_len(hpa_range);
+
+ if (!len)
+ return -EINVAL;
+
+ if (hpa_range->end > RESOURCE_SIZE_MAX) {
+ dev_err(dev,
+ "CXL reset range [%#llx-%#llx] exceeds resource address size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (len > RESOURCE_SIZE_MAX) {
+ dev_err(dev,
+ "CXL reset range [%#llx-%#llx] exceeds resource size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (len > SIZE_MAX) {
+ dev_err(dev,
+ "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_quiesce *q, struct device *dev,
+ const struct range *hpa_range)
+{
+ struct cxl_hdm_range *range, *next, *new_range;
+ int rc;
+
+ rc = cxl_hdm_range_validate(dev, hpa_range);
+ if (rc)
+ return rc;
+
+ list_for_each_entry(range, &q->ranges, list)
+ if (range_contains(&range->hpa_range, hpa_range))
+ return 0;
+
+ new_range = kzalloc_obj(*new_range);
+ if (!new_range)
+ return -ENOMEM;
+
+ new_range->dev = dev;
+ new_range->hpa_range = *hpa_range;
+ new_range->len = range_len(hpa_range);
+
+ list_for_each_entry_safe(range, next, &q->ranges, list) {
+ if (range_contains(hpa_range, &range->hpa_range)) {
+ list_del(&range->list);
+ kfree(range);
+ }
+ }
+ list_add_tail(&new_range->list, &q->ranges);
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_collect(struct cxl_hdm_quiesce *q, struct device *dev,
+ const struct cxl_hdm_info *snapshot)
+{
+ int rc;
+
+ for (int i = 0; i < snapshot->decoder_count; i++) {
+ const struct cxl_decoder_config *config =
+ &snapshot->settings[i].config;
+
+ /* A committed zero-size decoder maps no HPA. */
+ if (!(config->flags & CXL_DECODER_F_ENABLE) ||
+ !range_len(&config->hpa_range))
+ continue;
+
+ if (config->flags & CXL_DECODER_F_NORMALIZED_ADDRESSING) {
+ dev_err(dev,
+ "CXL reset does not support normalized address decoders\n");
+ return -EOPNOTSUPP;
+ }
+
+ rc = cxl_hdm_range_add(q, dev, &config->hpa_range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_request(struct cxl_hdm_quiesce *q)
+{
+ struct cxl_hdm_range *range;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ list_for_each_entry(range, &q->ranges, list) {
+ const struct range *hpa_range = &range->hpa_range;
+
+ range->res = request_mem_region(hpa_range->start, range->len,
+ "cxl_reset");
+ if (!range->res) {
+ dev_err(range->dev,
+ "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_invalidate(struct cxl_hdm_quiesce *q)
+{
+ struct cxl_hdm_range *range;
+ int rc = 0;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ list_for_each_entry(range, &q->ranges, list) {
+ const struct range *hpa_range = &range->hpa_range;
+ int rc2;
+
+ rc2 = cpu_cache_invalidate_memregion(hpa_range->start, range->len);
+ if (rc2)
+ dev_err(range->dev,
+ "failed to invalidate CPU cache [%#llx-%#llx]: %d\n",
+ hpa_range->start, hpa_range->end, rc2);
+ rc = rc ?: rc2;
+ }
+
+ return rc;
+}
+
+/**
+ * cxl_hdm_quiesce_prepare() - take the HPA ranges of a device out of use
+ * @dev: device for error messages
+ * @q: quiesce state to initialize
+ * @snapshot: HDM state from cxl_hdm_cache_snapshot()
+ *
+ * Reserve every enabled system-physical range in @snapshot exclusively, then
+ * write back and invalidate CPU caches for them. Call
+ * cxl_hdm_quiesce_destroy() afterwards whether or not this succeeds.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int cxl_hdm_quiesce_prepare(struct device *dev, struct cxl_hdm_quiesce *q,
+ const struct cxl_hdm_info *snapshot)
+{
+ int rc;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+ INIT_LIST_HEAD(&q->ranges);
+
+ if (!cpu_cache_has_invalidate_memregion()) {
+ dev_err(dev, "CPU cache invalidation unavailable\n");
+ return -ENXIO;
+ }
+
+ rc = cxl_hdm_ranges_collect(q, dev, snapshot);
+ if (rc)
+ return rc;
+
+ rc = cxl_hdm_ranges_request(q);
+ if (rc)
+ return rc;
+
+ return cxl_hdm_ranges_invalidate(q);
+}
+
+/* Evict lines fetched during reset before DMA exclusion ends. */
+int cxl_hdm_quiesce_flush(struct cxl_hdm_quiesce *q)
+{
+ return cxl_hdm_ranges_invalidate(q);
+}
+
+void cxl_hdm_quiesce_destroy(struct cxl_hdm_quiesce *q)
+{
+ struct cxl_hdm_range *range, *next;
+
+ list_for_each_entry_safe(range, next, &q->ranges, list) {
+ list_del(&range->list);
+ if (range->res)
+ release_mem_region(range->hpa_range.start,
+ resource_size(range->res));
+ kfree(range);
+ }
+}
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 9eb13adf0daa..3da8b316857f 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -15,6 +15,7 @@
#include <linux/iommu.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
+#include <linux/memregion.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -392,7 +393,8 @@ static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
PCI_DVSEC_CXL_RST_MEM_CLR_EN);
}
-static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
+ struct cxl_hdm_quiesce *q)
{
int rc, rc2;
@@ -417,12 +419,18 @@ static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n", rc2);
rc = rc ?: rc2;
+ rc2 = cxl_hdm_quiesce_flush(q);
+ rc = rc ?: rc2;
+
pci_dev_reset_iommu_done(pdev);
return rc;
}
-static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
+ __must_hold(cxl_region_quiesce)
{
+ struct cxl_hdm_info *snapshot __free(kfree) = NULL;
+ struct cxl_hdm_quiesce q;
u16 saved_ctrl2;
int rc, rc2;
@@ -432,14 +440,39 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
if (PCI_POSSIBLE_ERROR(saved_ctrl2))
return -ENODEV;
+ snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
+ if (IS_ERR(snapshot)) {
+ pci_err(pdev, "CXL HDM decoder state unavailable: %ld\n",
+ PTR_ERR(snapshot));
+ return PTR_ERR(snapshot);
+ }
+
+ rc = cxl_hdm_quiesce_prepare(&pdev->dev, &q, snapshot);
+ if (rc) {
+ cxl_hdm_quiesce_destroy(&q);
+ return rc;
+ }
+
rc = cxl_reset_disable_cache(pdev, dvsec, cap);
if (!rc)
- rc = __cxl_reset_execute(pdev, dvsec, cap);
+ rc = __cxl_reset_execute(pdev, dvsec, cap, &q);
/* Restore cache policy after any attempt to disable caching. */
rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, saved_ctrl2);
+ cxl_hdm_quiesce_destroy(&q);
return rc ?: rc2;
}
+static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc;
+
+ cxl_region_quiesce_lock();
+ rc = cxl_reset_quiesced(pdev, dvsec, cap);
+ cxl_region_quiesce_unlock();
+
+ return rc;
+}
+
int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
u16 dvsec, cap, ctrl;
@@ -466,5 +499,16 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (probe)
return 0;
+ /* The cache is owned by @pdev and does not require a bound CXL driver. */
+ if (!cxl_hdm_cache_present(&pdev->hdm))
+ return -ENOTTY;
+
+ /* Memory decoded through DVSEC ranges has no HPA ranges to quiesce. */
+ if (cxl_hdm_cache_dvsec_decode(&pdev->hdm))
+ return -ENOTTY;
+
+ if (!cpu_cache_has_invalidate_memregion())
+ return -ENOTTY;
+
return cxl_reset_execute(pdev, dvsec, cap);
}
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index af7cef649446..f6446a2a7fcd 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -3,9 +3,14 @@
#ifndef __CXL_HDM_H__
#define __CXL_HDM_H__
+#include <linux/list.h>
#include <linux/types.h>
+
#include <cxl/cxl.h>
+struct device;
+struct resource;
+
/**
* struct cxl_endpoint_decoder_settings - endpoint HDM decoder programming
* @config: common decoder configuration
@@ -42,7 +47,6 @@ struct cxl_hdm_info {
bool dvsec_ctrl_valid;
struct cxl_endpoint_decoder_settings settings[] __counted_by(decoder_count);
};
-struct device;
int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
resource_size_t size);
@@ -52,5 +56,46 @@ bool cxl_hdm_cache_present(struct cxl_hdm_info *const *slot);
void cxl_hdm_cache_publish(struct cxl_hdm_info **slot,
struct cxl_hdm_info *info);
void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
+bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot);
+struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot);
+
+/*
+ * Region quiesce blocks CXL region commit and teardown, and with them decoder
+ * reprogramming, while reset handling captures or restores HDM decoder state.
+ */
+token_context_lock(cxl_region_quiesce);
+
+/**
+ * struct cxl_hdm_range - HPA range held exclusively across a reset
+ * @list: entry in &cxl_hdm_quiesce.ranges
+ * @dev: device for error messages
+ * @hpa_range: system physical address range decoded by the device
+ * @len: length of @hpa_range
+ * @res: reservation of @hpa_range, once requested
+ */
+struct cxl_hdm_range {
+ struct list_head list;
+ struct device *dev;
+ struct range hpa_range;
+ u64 len;
+ struct resource *res;
+};
+
+/**
+ * struct cxl_hdm_quiesce - HPA ranges held exclusively across a reset
+ * @ranges: list of &struct cxl_hdm_range, see cxl_hdm_quiesce_prepare()
+ */
+struct cxl_hdm_quiesce {
+ struct list_head ranges;
+};
+
+void cxl_region_quiesce_lock(void);
+void cxl_region_quiesce_unlock(void);
+int cxl_hdm_quiesce_prepare(struct device *dev, struct cxl_hdm_quiesce *q,
+ const struct cxl_hdm_info *snapshot)
+ __must_hold(cxl_region_quiesce);
+int cxl_hdm_quiesce_flush(struct cxl_hdm_quiesce *q)
+ __must_hold(cxl_region_quiesce);
+void cxl_hdm_quiesce_destroy(struct cxl_hdm_quiesce *q);
#endif /* __CXL_HDM_H__ */
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 12/16] PCI/CXL: Reject reset with unsafe function scope
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (10 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 11/16] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 13/16] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
The per-device PCI reset interface must not reset sibling functions.
Reject cxl_reset and cxl_bus for multifunction devices and PFs with
enabled VFs.
Suggested-by: Alex Williamson <alwilliamson@nvidia.com>
Link: https://lore.kernel.org/linux-cxl/20260626160846.426e71b6@nvidia.com/
Link: https://lore.kernel.org/linux-cxl/6a50441e2bb5e_3cabcb1003e@djbw-dev.notmuch/
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
---
drivers/pci/cxl.c | 3 +++
drivers/pci/pci.c | 3 +++
2 files changed, 6 insertions(+)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 3da8b316857f..eccdb57945fe 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -496,6 +496,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
!(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
return -ENOTTY;
+ if (pdev->multifunction || pci_num_vf(pdev))
+ return -ENOTTY;
+
if (probe)
return 0;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index b2879a6be5f8..fe611340f5ab 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5026,6 +5026,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;
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 13/16] PCI/CXL: Restore CXL state after PCI reset
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (11 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 12/16] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 14/16] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Restore the PCI configuration needed for HDM access, followed by cached
HDM decoder and CXL Device DVSEC state. Recommit a cleared locked decoder
before restoring its lock, but leave surviving locked state unchanged.
Stop at the first restoration failure and preserve a disabled PCI command
snapshot. Keep IOMMU exclusion active through restoration and post-reset
cache invalidation.
Program the decoders from built-in CXL code that takes only a mapped
register block, and keep PCI state handling in drivers/pci/cxl.c.
The restore helper structure and diagnostics follow Dave Jiang's
reference implementation on his cxl-type2-reset branch.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=81fff5893a3140361cac625b09ada798afb92e03
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
drivers/cxl/core/hdm_regs.c | 180 ++++++++++++++++++++++++++++++++++++
drivers/pci/cxl.c | 176 +++++++++++++++++++++++++++++++++--
include/cxl/hdm.h | 2 +
3 files changed, 351 insertions(+), 7 deletions(-)
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index f0b360867655..cef5e9949d2c 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -73,6 +73,26 @@ int cxld_await_commit(void __iomem *hdm, int id)
}
EXPORT_SYMBOL_FOR_MODULES(cxld_await_commit, "cxl_core");
+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 u32 cxl_hdm_write_range(void __iomem *hdm,
const struct cxl_decoder_config *config)
{
@@ -273,3 +293,163 @@ int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
return 0;
}
+
+static int cxl_hdm_decoder_uncommit(struct device *dev, void __iomem *hdm,
+ int id, bool *retained)
+{
+ u32 ctrl;
+ int rc;
+
+ *retained = false;
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (ctrl == U32_MAX)
+ return -EIO;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMIT_ERROR) {
+ dev_err(dev, "CXL HDM decoder %d has commit error set\n", id);
+ return -EIO;
+ }
+
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK) {
+ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) {
+ *retained = true;
+ return 0;
+ }
+
+ dev_err(dev, "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)
+ dev_err(dev, "CXL HDM decoder %d uncommit failed: %d\n", id, rc);
+
+ return rc;
+}
+
+static bool
+cxl_hdm_decoder_settings_match(const struct cxl_endpoint_decoder_settings *saved,
+ const struct cxl_endpoint_decoder_settings *active)
+{
+ const struct cxl_decoder_config *a = &saved->config;
+ const struct cxl_decoder_config *b = &active->config;
+
+ return a->id == b->id &&
+ a->hpa_range.start == b->hpa_range.start &&
+ a->hpa_range.end == b->hpa_range.end &&
+ a->interleave_ways == b->interleave_ways &&
+ a->interleave_granularity == b->interleave_granularity &&
+ a->target_type == b->target_type &&
+ (a->flags & CXL_DECODER_F_RESET_MASK) ==
+ (b->flags & CXL_DECODER_F_RESET_MASK) &&
+ saved->skip == active->skip;
+}
+
+static int
+cxl_hdm_restore_decoder(struct device *dev, void __iomem *hdm,
+ const struct cxl_endpoint_decoder_settings *settings)
+{
+ const struct cxl_decoder_config *config = &settings->config;
+ bool retained;
+ u32 ctrl;
+ int rc;
+
+ rc = cxl_hdm_decoder_uncommit(dev, hdm, config->id, &retained);
+ if (rc)
+ return rc;
+
+ if (retained) {
+ struct cxl_endpoint_decoder_settings active;
+
+ rc = cxl_hdm_read_decoder_settings(dev, &active, hdm, config->id);
+ if (rc) {
+ dev_err(dev,
+ "CXL HDM decoder %d has invalid configuration: %d\n",
+ config->id, rc);
+ return rc;
+ }
+ if (!cxl_hdm_decoder_settings_match(settings, &active)) {
+ dev_err(dev,
+ "CXL HDM decoder %d retained unexpected locked state\n",
+ config->id);
+ return -EIO;
+ }
+ return 0;
+ }
+
+ if (!(config->flags & CXL_DECODER_F_ENABLE))
+ return 0;
+
+ cxl_commit_start_endpoint(settings, hdm);
+ rc = cxld_await_commit(hdm, config->id);
+ if (rc) {
+ dev_err(dev, "CXL HDM decoder %d restore failed: %d\n",
+ config->id, rc);
+ return rc;
+ }
+
+ if (!(config->flags & CXL_DECODER_F_LOCK))
+ return 0;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ if (ctrl == U32_MAX)
+ return -EIO;
+ ctrl |= CXL_HDM_DECODER0_CTRL_LOCK;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ if (ctrl == U32_MAX ||
+ !(ctrl & CXL_HDM_DECODER0_CTRL_LOCK)) {
+ dev_err(dev, "CXL HDM decoder %d failed to restore lock\n",
+ config->id);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * cxl_hdm_restore_state() - reprogram endpoint HDM decoders from a snapshot
+ * @dev: device for error messages
+ * @hdm: mapped HDM decoder capability
+ * @snapshot: state from cxl_hdm_cache_snapshot()
+ *
+ * Restore global control, then recommit each enabled decoder in order. Leave
+ * a locked decoder that kept its programming across the reset unchanged,
+ * after checking that it still matches @snapshot.
+ *
+ * Return: 0 on success, or the first failure.
+ */
+int cxl_hdm_restore_state(struct device *dev, void __iomem *hdm,
+ const struct cxl_hdm_info *snapshot)
+{
+ int count;
+ int rc;
+
+ count = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (count != snapshot->decoder_count) {
+ dev_err(dev, "CXL HDM decoder count changed from %d to %d\n",
+ snapshot->decoder_count, count);
+ return -ENXIO;
+ }
+
+ /* Restore global HDM control before committing individual decoders. */
+ writel(snapshot->global_ctrl, hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+
+ for (int i = 0; i < snapshot->decoder_count; i++) {
+ rc = cxl_hdm_restore_decoder(dev, hdm, &snapshot->settings[i]);
+ if (rc)
+ return rc;
+ }
+
+ /* Flush posted HDM writes before restoring protocol enables. */
+ readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+
+ return 0;
+}
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index eccdb57945fe..4c6b712ed4fa 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
/*
- * CXL reset support in the PCI core. Cache the HDM decoder state of a CXL
- * memory device at enumeration, so it can be restored after a reset without
- * a CXL driver, and implement CXL Device Reset. HDM register formats and
+ * CXL reset support in the PCI core: cache the HDM decoder state of a CXL
+ * memory device at enumeration, implement CXL Device Reset, and restore the
+ * cached state afterwards without a CXL driver. HDM register formats and
* programming live in drivers/cxl.
*/
#include <linux/bitfield.h>
@@ -16,6 +16,7 @@
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/memregion.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -227,6 +228,150 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
cxl_hdm_cache_release(&pdev->hdm);
}
+static void __iomem *cxl_pci_hdm_ioremap_current(struct pci_dev *pdev,
+ int bar,
+ resource_size_t offset,
+ resource_size_t size)
+{
+ struct resource *res;
+ resource_size_t hdm_start;
+ void __iomem *hdm;
+
+ if (bar < 0 || bar >= PCI_STD_NUM_BARS || !size)
+ return IOMEM_ERR_PTR(-EINVAL);
+ if (!cxl_pci_bar_assigned(pdev, bar))
+ return IOMEM_ERR_PTR(-ENODEV);
+
+ res = &pdev->resource[bar];
+ if (offset > resource_size(res) || size > resource_size(res) - offset)
+ return IOMEM_ERR_PTR(-ENODEV);
+ if (check_add_overflow(res->start, offset, &hdm_start))
+ return IOMEM_ERR_PTR(-EOVERFLOW);
+
+ hdm = ioremap(hdm_start, size);
+ if (!hdm) {
+ pci_err(pdev, "failed to remap CXL HDM decoder registers\n");
+ return IOMEM_ERR_PTR(-ENOMEM);
+ }
+
+ return hdm;
+}
+
+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];
+ u32 restore_config;
+
+ device_lock_assert(&pdev->dev);
+ *command = saved_config & 0xffff;
+
+ /*
+ * Restore BARs and the related PCI state needed to reach HDM MMIO.
+ * Temporarily alter the saved command value so that
+ * pci_restore_state() enables MSE but cannot enable BME or INTx before
+ * CXL state restoration completes. Preserve the original saved value
+ * for the outer PCI reset path.
+ */
+ restore_config = saved_config | PCI_COMMAND_MEMORY |
+ PCI_COMMAND_INTX_DISABLE;
+ restore_config &= ~PCI_COMMAND_MASTER;
+
+ pdev->saved_config_space[PCI_COMMAND / 4] = restore_config;
+ pci_restore_state(pdev);
+ pdev->saved_config_space[PCI_COMMAND / 4] = saved_config;
+}
+
+static int cxl_restore_dvsec_ctrl(struct pci_dev *pdev,
+ const struct cxl_hdm_info *snapshot)
+{
+ int rc;
+
+ if (!snapshot->dvsec_ctrl_valid) {
+ pci_err(pdev, "CXL DVSEC control snapshot is invalid\n");
+ return -ENXIO;
+ }
+
+ rc = pci_write_config_word(pdev, snapshot->dvsec + PCI_DVSEC_CXL_CTRL,
+ snapshot->dvsec_ctrl);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ pci_err(pdev, "failed to restore CXL DVSEC control: %d\n", rc);
+ }
+
+ return rc;
+}
+
+static int cxl_restore_state(struct pci_dev *pdev,
+ const struct cxl_hdm_info *snapshot)
+{
+ void __iomem *hdm;
+ int rc;
+
+ hdm = cxl_pci_hdm_ioremap_current(pdev, snapshot->hdm_bar,
+ snapshot->hdm_offset,
+ snapshot->hdm_size);
+ if (IS_ERR(hdm)) {
+ pci_err(pdev, "failed to map CXL HDM state for restore: %ld\n",
+ PTR_ERR(hdm));
+ return PTR_ERR(hdm);
+ }
+
+ rc = cxl_hdm_restore_state(&pdev->dev, hdm, snapshot);
+ iounmap(hdm);
+ if (rc)
+ return rc;
+
+ return cxl_restore_dvsec_ctrl(pdev, snapshot);
+}
+
+static void cxl_reset_save_disabled_state(struct pci_dev *pdev)
+{
+ u32 *saved_command = &pdev->saved_config_space[PCI_COMMAND / 4];
+ int rc;
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
+ if (rc)
+ pci_err(pdev,
+ "failed to disable device after CXL reset failure: %d\n",
+ pcibios_err_to_errno(rc));
+
+ /* Preserve the pre-reset snapshot if PCI state has not been restored. */
+ if (!pdev->state_saved) {
+ rc = pci_save_state(pdev);
+ if (rc)
+ pci_err(pdev,
+ "failed to save disabled state after CXL reset failure: %d\n",
+ rc);
+ }
+
+ /* Ensure the outer PCI restore cannot re-enable MSE or BME. */
+ *saved_command &= GENMASK(31, 16);
+ *saved_command |= PCI_COMMAND_INTX_DISABLE;
+}
+
+static int cxl_reset_save_restored_state(struct pci_dev *pdev, u16 command)
+{
+ int rc;
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ pci_err(pdev, "failed to restore PCI command state: %d\n", rc);
+ cxl_reset_save_disabled_state(pdev);
+ return rc;
+ }
+
+ rc = pci_save_state(pdev);
+ if (rc) {
+ pci_err(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
@@ -394,7 +539,9 @@ static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
}
static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
- struct cxl_hdm_quiesce *q)
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_hdm_quiesce *q,
+ u16 *command)
{
int rc, rc2;
@@ -419,8 +566,15 @@ static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n", rc2);
rc = rc ?: rc2;
+ if (!rc) {
+ cxl_restore_pci_state_for_hdm_restore(pdev, command);
+ rc = cxl_restore_state(pdev, snapshot);
+ }
+
rc2 = cxl_hdm_quiesce_flush(q);
rc = rc ?: rc2;
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
pci_dev_reset_iommu_done(pdev);
return rc;
@@ -431,7 +585,7 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
{
struct cxl_hdm_info *snapshot __free(kfree) = NULL;
struct cxl_hdm_quiesce q;
- u16 saved_ctrl2;
+ u16 command, saved_ctrl2;
int rc, rc2;
rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &saved_ctrl2);
@@ -455,11 +609,19 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
rc = cxl_reset_disable_cache(pdev, dvsec, cap);
if (!rc)
- rc = __cxl_reset_execute(pdev, dvsec, cap, &q);
+ rc = __cxl_reset_execute(pdev, dvsec, cap, snapshot, &q,
+ &command);
/* Restore cache policy after any attempt to disable caching. */
rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, saved_ctrl2);
cxl_hdm_quiesce_destroy(&q);
- return rc ?: rc2;
+ if (rc)
+ return rc;
+ if (rc2) {
+ cxl_reset_save_disabled_state(pdev);
+ return rc2;
+ }
+
+ return cxl_reset_save_restored_state(pdev, command);
}
static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index f6446a2a7fcd..445093cf0452 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -52,6 +52,8 @@ int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
resource_size_t size);
int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
struct cxl_hdm_info *info);
+int cxl_hdm_restore_state(struct device *dev, void __iomem *hdm,
+ const struct cxl_hdm_info *snapshot);
bool cxl_hdm_cache_present(struct cxl_hdm_info *const *slot);
void cxl_hdm_cache_publish(struct cxl_hdm_info **slot,
struct cxl_hdm_info *info);
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 14/16] PCI/CXL: Expose CXL Reset as a PCI reset method
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (12 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 13/16] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 15/16] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 16/16] tools/testing/cxl: Add HDM decoder range checks Srirangan Madhavan
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Register cxl_reset with the PCI reset-method interface and place it before
FLR, which does not reset CXL.cache or CXL.mem protocol state.
Return -ENOTTY when device capabilities, function scope, cached HDM state,
or required cache synchronization do not permit a safe reset.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/pci/cxl.c | 12 +++++++++---
drivers/pci/pci.c | 1 +
include/linux/pci.h | 2 +-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 4c6b712ed4fa..7a225e215ca5 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -642,7 +642,7 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
if (rc)
- return rc;
+ return probe ? -ENOTTY : rc;
if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
!(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
!(cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
@@ -651,9 +651,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL, &ctrl);
if (rc)
- return pcibios_err_to_errno(rc);
+ return probe ? -ENOTTY : pcibios_err_to_errno(rc);
if (PCI_POSSIBLE_ERROR(ctrl))
- return -ENODEV;
+ return probe ? -ENOTTY : -ENODEV;
if (!(ctrl & PCI_DVSEC_CXL_CACHE_ENABLE) ||
!(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
return -ENOTTY;
@@ -675,5 +675,11 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (!cpu_cache_has_invalidate_memregion())
return -ENOTTY;
+ /* Raw reset callers may not have saved the PCI state needed for restore. */
+ if (!pdev->state_saved) {
+ pci_err(pdev, "CXL reset requires saved PCI state\n");
+ return -EINVAL;
+ }
+
return cxl_reset_execute(pdev, dvsec, cap);
}
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index fe611340f5ab..b58a6a613311 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5148,6 +5148,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 7bb37fcb556d..743b57ff885a 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] 17+ messages in thread
* [PATCH v15 15/16] PCI/CXL: Restore CXL state after CXL bus reset
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (13 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 14/16] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 16/16] tools/testing/cxl: Add HDM decoder range checks Srirangan Madhavan
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
control state. Restore the cached state after a successful cxl_bus reset
while IOMMU exclusion remains active.
Restore PCI configuration first so HDM MMIO is accessible, and preserve a
disabled state if restoration fails.
Copy the cached state before the reset, then refresh its decoder settings
from the cache under the CXL region quiesce once the reset completes. A
region decommit during the reset updates the cache, and restoring the
earlier decoders would leave the endpoint decoding an HPA range that the
CXL core considers free. Keep global and DVSEC control from before the
reset, since a refresh during the reset can read them as all-ones.
The prepare/restore interface and decoder-refresh structure follow Dave
Jiang's cxl-type2-reset reference branch.
Link: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=b93f0c8e67b198a63786fd3721b7177ab9df60be
Suggested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
Acked-by: Bjorn Helgaas <bhelgaas@google.com> # pci/pci.c
---
drivers/cxl/core/hdm_state.c | 31 ++++++++++++++++++
drivers/pci/cxl.c | 62 ++++++++++++++++++++++++++++++++++++
drivers/pci/pci.c | 14 ++++++++
drivers/pci/pci.h | 16 ++++++++++
include/cxl/hdm.h | 2 ++
5 files changed, 125 insertions(+)
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index cef604013350..95daeda0ef80 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -120,6 +120,37 @@ struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot)
return copy;
}
+/**
+ * cxl_hdm_cache_copy_decoders() - refresh the decoders of a snapshot
+ * @slot: cache pointer owned by the device
+ * @dst: snapshot of the same device from cxl_hdm_cache_snapshot()
+ *
+ * Copy only the per-decoder settings, which region commit and teardown keep
+ * current. Leave the rest of @dst, including global and DVSEC control, as
+ * captured: a refresh during a reset may have read those registers from a
+ * device that returned all-ones.
+ *
+ * Return: 0 on success, -ENXIO when nothing is published, or -EINVAL when
+ * @dst holds a different number of decoders.
+ */
+int cxl_hdm_cache_copy_decoders(struct cxl_hdm_info *const *slot,
+ struct cxl_hdm_info *dst)
+{
+ struct cxl_hdm_info *info;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = *slot;
+ if (!info)
+ return -ENXIO;
+ if (info->decoder_count != dst->decoder_count)
+ return -EINVAL;
+
+ memcpy(dst->settings, info->settings,
+ flex_array_size(info, settings, info->decoder_count));
+ return 0;
+}
+
void cxl_region_quiesce_lock(void)
__acquires(cxl_region_quiesce)
__context_unsafe(token maps to cxl_rwsem.region)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 7a225e215ca5..ed0b6f619c70 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -372,6 +372,68 @@ static int cxl_reset_save_restored_state(struct pci_dev *pdev, u16 command)
return rc;
}
+/**
+ * pci_cxl_reset_prepare() - allocate HDM state storage ahead of a bus reset
+ * @pdev: device about to be reset
+ *
+ * Copy the cached HDM state before the reset, so that restoring cannot fail
+ * for lack of memory. The decoder settings are refreshed when the state is
+ * restored.
+ *
+ * Return: storage for cxl_restore_state_after_pci_reset(), to be freed with
+ * kfree(); NULL when @pdev has no cached HDM state; or an ERR_PTR() on
+ * failure.
+ */
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *snapshot;
+
+ device_lock_assert(&pdev->dev);
+ snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
+ if (IS_ERR(snapshot) && PTR_ERR(snapshot) == -ENXIO)
+ return NULL;
+
+ return snapshot;
+}
+
+/**
+ * cxl_restore_state_after_pci_reset() - restore CXL state after a bus reset
+ * @pdev: device that was reset
+ * @snapshot: storage from pci_cxl_reset_prepare()
+ *
+ * Under the region quiesce, refresh the decoder settings in @snapshot from the
+ * cache and restore: first the PCI configuration needed to reach HDM MMIO,
+ * then the HDM decoder and CXL Device DVSEC state. Leave @pdev disabled on
+ * failure.
+ *
+ * Refresh the decoders now rather than rely on the copy taken before the
+ * reset: a region commit or teardown during the reset has updated the cache,
+ * and restoring older decoders would leave the device decoding ranges the
+ * CXL core considers free.
+ */
+int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot)
+{
+ u16 command;
+ int rc;
+
+ device_lock_assert(&pdev->dev);
+ cxl_region_quiesce_lock();
+
+ rc = cxl_hdm_cache_copy_decoders(&pdev->hdm, snapshot);
+ if (!rc) {
+ cxl_restore_pci_state_for_hdm_restore(pdev, &command);
+ rc = cxl_restore_state(pdev, snapshot);
+ }
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
+ else
+ rc = cxl_reset_save_restored_state(pdev, command);
+
+ cxl_region_quiesce_unlock();
+ 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
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index b58a6a613311..cecf8e681e69 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5014,6 +5014,7 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
{
+ struct cxl_hdm_info *snapshot __free(kfree) = NULL;
struct pci_dev *bridge;
u16 dvsec, reg, val;
int rc;
@@ -5036,6 +5037,16 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (rc)
return -ENOTTY;
+ snapshot = pci_cxl_reset_prepare(dev);
+ if (IS_ERR(snapshot))
+ return PTR_ERR(snapshot);
+
+ /* Raw reset callers may not have saved the PCI state needed for restore. */
+ if (snapshot && !dev->state_saved) {
+ pci_err(dev, "CXL bus reset requires saved PCI state\n");
+ return -EINVAL;
+ }
+
rc = pci_dev_reset_iommu_prepare(dev);
if (rc) {
pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
@@ -5056,6 +5067,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 && snapshot)
+ rc = cxl_restore_state_after_pci_reset(dev, snapshot);
+
pci_dev_reset_iommu_done(dev);
return rc;
}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 743e65abd758..476e8eefafb2 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -730,10 +730,14 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
+struct cxl_hdm_info;
#ifdef CONFIG_CXL_RESET
void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
int cxl_reset_function(struct pci_dev *pdev, bool probe);
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev);
+int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot);
#else
static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev) { }
@@ -741,6 +745,18 @@ static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
return -ENOTTY;
}
+
+static inline struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
+{
+ return NULL;
+}
+
+static inline int
+cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot)
+{
+ return 0;
+}
#endif
#ifdef CONFIG_PCI_NPEM
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 445093cf0452..02cad2d74329 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -60,6 +60,8 @@ void cxl_hdm_cache_publish(struct cxl_hdm_info **slot,
void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot);
struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot);
+int cxl_hdm_cache_copy_decoders(struct cxl_hdm_info *const *slot,
+ struct cxl_hdm_info *dst);
/*
* Region quiesce blocks CXL region commit and teardown, and with them decoder
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v15 16/16] tools/testing/cxl: Add HDM decoder range checks
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (14 preceding siblings ...)
2026-10-11 2:14 ` [PATCH v15 15/16] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
@ 2026-10-11 2:14 ` Srirangan Madhavan
15 siblings, 0 replies; 17+ messages in thread
From: Srirangan Madhavan @ 2026-10-11 2:14 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
Test cxl_hdm_unpack_decoder() when cxl_test loads, failing module loading
if a check fails. Cover range boundaries and overflow, an all-ones base,
zero-size and uncommitted decoders, and a locked host-only decoder with
12-way interleave. Verify the decoded fields for valid inputs.
Run the checks separately from the watermark hooks, using a helper
exported only by mock cxl_core. No PCI device or MMIO is needed.
Cache refresh, retained-state comparison, and reset restoration need
follow-up tests with additional mock support. These checks do not cover
reset sequencing or hardware cache invalidation.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
tools/testing/cxl/cxl_core_test.c | 78 +++++++++++++++++++++++++++++++
tools/testing/cxl/cxl_core_test.h | 8 ++++
tools/testing/cxl/test/cxl.c | 5 ++
3 files changed, 91 insertions(+)
create mode 100644 tools/testing/cxl/cxl_core_test.h
diff --git a/tools/testing/cxl/cxl_core_test.c b/tools/testing/cxl/cxl_core_test.c
index 464a9255e4d6..8072146643bd 100644
--- a/tools/testing/cxl/cxl_core_test.c
+++ b/tools/testing/cxl/cxl_core_test.c
@@ -1,6 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
+#include <linux/bitfield.h>
+#include <linux/sizes.h>
+#include "cxl.h"
+#include "core.h"
+#include "cxl_core_test.h"
#include "watermark.h"
cxl_test_watermark(cxl_core);
+
+/* Exercise the shared unpacker without HDM MMIO or a PCI device. */
+int cxl_test_hdm_ranges(void)
+{
+ static const struct {
+ const char *name;
+ u32 ctrl;
+ u64 base;
+ u64 size;
+ int rc;
+ unsigned long flags;
+ int ways;
+ int granularity;
+ enum cxl_decoder_type target_type;
+ } cases[] = {
+ { "valid", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ SZ_1G, SZ_256M, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "overflow", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX - 127, 256, -ENXIO },
+ { "all-ones base", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX, SZ_256M, -ENXIO },
+ { "last address", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX - 255, 256, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "zero size", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ SZ_1G, 0, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "uncommitted stale size", 0,
+ SZ_1G, SZ_256M, 0, 0, 1, 256, 0 },
+ { "locked host-only interleave",
+ CXL_HDM_DECODER0_CTRL_COMMITTED | CXL_HDM_DECODER0_CTRL_LOCK |
+ CXL_HDM_DECODER0_CTRL_HOSTONLY |
+ FIELD_PREP_CONST(CXL_HDM_DECODER0_CTRL_IW_MASK, 10) |
+ FIELD_PREP_CONST(CXL_HDM_DECODER0_CTRL_IG_MASK, 2),
+ SZ_1G, SZ_256M, 0, CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK,
+ 12, SZ_1K, CXL_DECODER_HOSTONLYMEM },
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(cases); i++) {
+ struct cxl_decoder_config config;
+ u64 size;
+ int rc;
+
+ rc = cxl_hdm_unpack_decoder(&config, 3, cases[i].ctrl,
+ cases[i].base, cases[i].size);
+ if (rc != cases[i].rc) {
+ pr_err("cxl_test: HDM %s returned %d, expected %d\n",
+ cases[i].name, rc, cases[i].rc);
+ return -EINVAL;
+ }
+ if (rc)
+ continue;
+ size = cases[i].size;
+ if (!(cases[i].flags & CXL_DECODER_F_ENABLE))
+ size = 0;
+ if (config.id != 3 || config.hpa_range.start != cases[i].base ||
+ range_len(&config.hpa_range) != size ||
+ config.flags != cases[i].flags ||
+ config.interleave_ways != cases[i].ways ||
+ config.interleave_granularity != cases[i].granularity ||
+ config.target_type != cases[i].target_type) {
+ pr_err("cxl_test: HDM %s decoded unexpected state\n",
+ cases[i].name);
+ return -EINVAL;
+ }
+ }
+
+ pr_info("cxl_test: HDM decoder range tests passed\n");
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(cxl_test_hdm_ranges);
diff --git a/tools/testing/cxl/cxl_core_test.h b/tools/testing/cxl/cxl_core_test.h
new file mode 100644
index 000000000000..48392759cd22
--- /dev/null
+++ b/tools/testing/cxl/cxl_core_test.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#ifndef _CXL_CORE_TEST_H_
+#define _CXL_CORE_TEST_H_
+
+int cxl_test_hdm_ranges(void);
+
+#endif /* _CXL_CORE_TEST_H_ */
diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index 034d3a923b2c..5e6e9a8a21d9 100644
--- a/tools/testing/cxl/test/cxl.c
+++ b/tools/testing/cxl/test/cxl.c
@@ -12,6 +12,7 @@
#include <cxlmem.h>
#include "../watermark.h"
+#include "../cxl_core_test.h"
#include "mock.h"
static int interleave_arithmetic;
@@ -2453,6 +2454,10 @@ static __init int cxl_test_init(void)
cxl_pmem_test();
cxl_port_test();
+ rc = cxl_test_hdm_ranges();
+ if (rc)
+ return rc;
+
register_cxl_mock_ops(&cxl_mock_ops);
cxl_mock_pool = gen_pool_create(ilog2(SZ_2M), NUMA_NO_NODE);
--
2.43.0
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-10-11 2:15 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-11 2:14 [PATCH v15 00/16] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 01/16] cxl: Drop stale decoder interleave limit comment Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 02/16] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 03/16] cxl: Move decoder declarations to shared header Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 04/16] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 05/16] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 06/16] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 07/16] cxl: Share HDM decoder register unpacking Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 08/16] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 09/16] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 10/16] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 11/16] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 12/16] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 13/16] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 14/16] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 15/16] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
2026-10-11 2:14 ` [PATCH v15 16/16] tools/testing/cxl: Add HDM decoder range checks 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®