* [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices
@ 2026-11-05 18:58 Srirangan Madhavan
2026-11-05 18:58 ` [PATCH v17 01/19] cxl: Clarify decoder interleave limit Srirangan Madhavan
` (19 more replies)
0 siblings, 20 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:58 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: Li Ming, Joshua Machauer, 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. CXL bus reset restores
the same cached state and flushes the host and device caches around the
reset.
The reset sequence follows the CXL 4.0 specification [4], 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: userspace is expected to offline CXL.mem users before
reset. The reservation enforces this for claimed ranges by failing the
reset while a conflicting resource is claimed [9].
The global region lock is held from snapshot through restoration. Memory
Clear completion is awaited for the timeout the device advertises, up to
100 seconds, so the lock can be held for that long plus cache flushes.
Changes from v16 [1]:
- Publish the PCI HDM cache under the PCI device lock instead of
cxl_rwsem.dpa, and drop the cxl_hdm_cache_present() and
cxl_hdm_cache_publish() helpers (Li Ming) [6][7]
- Wait for the reset timeout the device advertises, up to 100 seconds,
instead of clamping it to 20 seconds, so Control2 is not accessed while
the device may still be resetting (Richard) [8]
- State that userspace is expected to offline CXL.mem users before reset,
and what the HPA reservation does and does not cover (Richard) [9]
- Enable bus mastering after the IOMMU is restored on CXL bus reset
(Richard) [10]
- Explain why cxl_set_mem_enable() refreshes the cached DVSEC Control when
MEM_ENABLE already matches (Richard) [11]
- Initialize the zero-size HDM decoder end to base - 1 and document the
zero-size case (Richard) [12]
- Set Lock On Commit after programming the decoder registers and before
Commit when recommitting a locked decoder (Alison) [13]
- Record Normalized Addressing in the cached decoder when region discovery
sets it, and note the short window before that update (Sashiko) [15]
- Reject an all-ones DVSEC Control read in cxl_set_mem_enable() (Sashiko)
[15]
- Add the missing context-analysis annotations on the region quiesce
lock and unlock and on __cxl_reset_execute() (Sashiko) [15]
- Use an explicit pci_dev_put() in read_cdat_data() instead of mixing
__free() with goto cleanup (Sashiko) [15]
- Check for VFs and multifunction devices before the CXL DVSEC lookups in
the two reset methods, to avoid repeated capability walks while probing
VFs (Joshua) [18]
- Fold the CXL bus reset cache flush patch into this series (Dave); the
series is now 19 patches
- Added Richard's Reviewed-by tags to patches 6-10, 14 and 16
- Specified the reviewer behind each change in the changelogs, per Alison [14]
Earlier revision history is in the v16 [1] and v15 [2] cover letters.
Attribution for Dave Jiang's reference branch is still open.
Since I am unsure about [16] I have not yet added the right tags.
The patches link to his branch below the separator for now.
The v15 discussion on the scope check is still pending [17].
HDM caching requires CXL.cache, CXL.mem, CXL Reset and Reset Memory
Clear capabilities. CXL bus reset state restoration uses that cache.
CXL Reset requires a registered CPU cache clean/invalidate provider
and matching firmware, such as the separate SMCCC provider series [3].
Without that provider, cxl_reset can be advertised while execution returns
-ENOTTY, unless the test-only CXL_REGION_INVALIDATION_TEST bypass is
enabled.
The KUnit suite checks HDM range boundaries, zero-size and uncommitted
state, lock and target-type decoding, and 12-way interleave.
Testing: v17 was tested on an ARM64 target with v5 of the SMCCC
cache provider series [3] applied. Reset loops passed for both cxl_reset and
cxl_bus after running bidirectional host-device traffic. The
cxl_hdm_unpack_kunit suite passes.
[1] https://lore.kernel.org/linux-cxl/20261029145703.2185231-1-smadhavan@nvidia.com/
[2] https://lore.kernel.org/linux-cxl/20261011021422.3428136-1-smadhavan@nvidia.com/
[3] https://lore.kernel.org/linux-arm-kernel/20261021224557.1827361-1-smadhavan@nvidia.com/
[4] https://computeexpresslink.org/wp-content/uploads/2026/02/CXL-Specification_rev4p0_ver1p0_2026February26_clean_evalcopy_v2.pdf
[5] https://lore.kernel.org/all/cover.1791949910.git.alison.schofield@intel.com/
[6] https://lore.kernel.org/linux-cxl/d581f2a0-dd9e-4d0a-bd19-639c1bd9b832@zohomail.com/
[7] https://lore.kernel.org/linux-cxl/20261011021422.3428136-1-smadhavan@nvidia.com/T/#m7f8d0e51d4cfe09fefcebd93d49dcb916be9198a
[8] https://lore.kernel.org/linux-cxl/auQC2UECQ8vaelPz@MWDK4CY14F/
[9] https://lore.kernel.org/linux-cxl/auQRSOGWEDzB_qjF@MWDK4CY14F/
[10] https://lore.kernel.org/linux-cxl/auRhJa-nzR0CnKf3@MWDK4CY14F/
[11] https://lore.kernel.org/linux-cxl/auRQN6a3QVtqjd5a@MWDK4CY14F/
[12] https://lore.kernel.org/linux-cxl/auQ27s1rSCcbdRJ3@MWDK4CY14F/
[13] https://lore.kernel.org/linux-cxl/auAg6MFDGyVQvQxM@aschofie-mobl2.lan/
[14] https://lore.kernel.org/linux-cxl/auOXW3BTb8CYIh4m@aschofie-mobl2.lan/
[15] https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com
[16] https://lore.kernel.org/linux-cxl/20261011021422.3428136-1-smadhavan@nvidia.com/T/#m2f61467417f422316cb15b976773944dcdcb48c7
[17] https://lore.kernel.org/linux-cxl/20261011021422.3428136-1-smadhavan@nvidia.com/T/#m4f62ed0f038f2d136158c74c1c6c338129266131
[18] https://lore.kernel.org/linux-cxl/20261105143611.31563-1-machaujo@amazon.de/
Srirangan Madhavan (19):
cxl: Clarify decoder interleave limit
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: Move cxl_rwsem into built-in code
cxl: Refresh cached PCI HDM decoder settings
cxl: Build register block helpers into the kernel
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
PCI/CXL: Flush host and device caches for CXL bus reset
cxl: Add KUnit checks for HDM decoder unpacking
MAINTAINERS | 1 +
drivers/cxl/.kunitconfig | 4 +
drivers/cxl/Kconfig | 18 +-
drivers/cxl/Makefile | 1 +
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 | 273 +++-----
drivers/cxl/core/hdm_regs.c | 437 +++++++++++++
drivers/cxl/core/hdm_state.c | 402 ++++++++++++
drivers/cxl/core/pci.c | 85 ++-
drivers/cxl/core/port.c | 68 +-
drivers/cxl/core/region.c | 116 ++--
drivers/cxl/core/regs.c | 9 +
drivers/cxl/cxl.h | 46 +-
drivers/cxl/pci.c | 2 +-
drivers/cxl/port.c | 4 +-
drivers/cxl/tests/Makefile | 5 +
drivers/cxl/tests/hdm_unpack_kunit.c | 91 +++
drivers/pci/Makefile | 1 +
drivers/pci/bus.c | 1 +
drivers/pci/cxl.c | 907 +++++++++++++++++++++++++++
drivers/pci/pci.c | 27 +-
drivers/pci/pci.h | 44 ++
drivers/pci/probe.c | 1 +
include/cxl/cxl.h | 59 +-
include/cxl/hdm.h | 109 ++++
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/test/cxl.c | 158 +++--
33 files changed, 2526 insertions(+), 426 deletions(-)
create mode 100644 drivers/cxl/.kunitconfig
create mode 100644 drivers/cxl/core/hdm_regs.c
create mode 100644 drivers/cxl/core/hdm_state.c
create mode 100644 drivers/cxl/tests/Makefile
create mode 100644 drivers/cxl/tests/hdm_unpack_kunit.c
create mode 100644 drivers/pci/cxl.c
create mode 100644 include/cxl/hdm.h
base-commit: fef22d37d47043b63e4007f2f5fe762fa21fae0c
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v17 01/19] cxl: Clarify decoder interleave limit
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
@ 2026-11-05 18:58 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
` (18 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:58 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: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan,
Jonathan Cameron
CXL 2.0 added 16-way memory interleaving, making the comment that the
specification supports only 8-way interleaving stale. Explain that 16 is
the largest interleave a decoder can express, without changing the limit.
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 | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 3ef0810ab86b..50af716f69df 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -269,10 +269,7 @@ 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
- */
+/* 16 is the largest interleave a decoder can express (CXL 4.0 8.2.4.20.7). */
#define CXL_DECODER_MAX_INTERLEAVE 16
#define CXL_QOS_CLASS_INVALID -1
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-11-05 18:58 ` [PATCH v17 01/19] cxl: Clarify decoder interleave limit Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 03/19] cxl: Move decoder declarations to shared header Srirangan Madhavan
` (17 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan,
Jonathan Cameron
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>
---
Changes in v17:
- Use an explicit pci_dev_put() on a single exit path in
read_cdat_data() instead of mixing __free() with goto cleanup
(Sashiko)
https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=2
---
drivers/cxl/core/core.h | 1 +
drivers/cxl/core/pci.c | 24 ++++++------------------
drivers/cxl/core/port.c | 26 ++++++++++++++++++++++++++
3 files changed, 33 insertions(+), 18 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..905892cfedef 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -564,26 +564,13 @@ 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 = 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;
@@ -591,14 +578,14 @@ void read_cdat_data(struct cxl_port *port)
CXL_DOE_PROTOCOL_TABLE_ACCESS);
if (!doe_mb) {
dev_dbg(dev, "No CDAT mailbox\n");
- return;
+ goto out;
}
port->cdat_available = true;
if (cxl_cdat_get_length(dev, doe_mb, &length)) {
dev_dbg(dev, "No CDAT length\n");
- return;
+ goto out;
}
/*
@@ -624,12 +611,13 @@ void read_cdat_data(struct cxl_port *port)
port->cdat.table = buf->data;
port->cdat.length = length;
-
- return;
+ goto out;
err:
/* Don't leave table data allocated on error */
devm_kfree(dev, buf);
dev_err(dev, "Failed to read/validate CDAT.\n");
+out:
+ pci_dev_put(pdev);
}
EXPORT_SYMBOL_NS_GPL(read_cdat_data, "CXL");
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] 21+ messages in thread
* [PATCH v17 03/19] cxl: Move decoder declarations to shared header
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-11-05 18:58 ` [PATCH v17 01/19] cxl: Clarify decoder interleave limit Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 04/19] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
` (16 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan,
Jonathan Cameron
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 | 38 --------------------------------------
include/cxl/cxl.h | 42 +++++++++++++++++++++++++++++++++++++++++-
2 files changed, 41 insertions(+), 39 deletions(-)
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 50af716f69df..44ff3334648c 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -264,46 +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,
-};
-
-/* 16 is the largest interleave a decoder can express (CXL 4.0 8.2.4.20.7). */
-#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..613dab8150c7 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,45 @@ enum cxl_devtype {
CXL_DEVTYPE_CLASSMEM,
};
-struct device;
+struct cxl_region;
+
+enum cxl_decoder_type {
+ CXL_DECODER_DEVMEM = 2,
+ CXL_DECODER_HOSTONLYMEM = 3,
+};
+
+/* 16 is the largest interleave a decoder can express (CXL 4.0 8.2.4.20.7). */
+#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] 21+ messages in thread
* [PATCH v17 04/19] cxl: Embed decoder configuration in a standalone structure
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (2 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 03/19] cxl: Move decoder declarations to shared header Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 05/19] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
` (15 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
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().
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.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 905892cfedef..f72cd8f50e77 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 613dab8150c7..6d6e564be27a 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -36,15 +36,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.
@@ -53,13 +69,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] 21+ messages in thread
* [PATCH v17 05/19] cxl: Introduce endpoint HDM decoder settings
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (3 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 04/19] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 06/19] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
` (14 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
Assisted-by: LLM
---
drivers/cxl/core/core.h | 8 ++++
drivers/cxl/core/hdm.c | 95 ++++++++++++++++++++++++++++-------------
include/cxl/hdm.h | 19 +++++++++
3 files changed, 92 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..af445f1b9798 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,79 @@ 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);
+ cxl_commit_start_switch(&cxld->config, targets, hdm);
} 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_endpoint_decoder_snapshot(to_cxl_endpoint_decoder(&cxld->dev),
+ &settings);
+ cxl_commit_start_endpoint(&settings, hdm);
}
-
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
}
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] 21+ messages in thread
* [PATCH v17 06/19] cxl: Move HDM decoder helpers to built-in code
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (4 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 05/19] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 07/19] cxl: Share HDM decoder register unpacking Srirangan Madhavan
` (13 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=e0b476ea99567fb5aa416eac0cf61bca20724880
---
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 af445f1b9798..2c373dfbc070 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] 21+ messages in thread
* [PATCH v17 07/19] cxl: Share HDM decoder register unpacking
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (5 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 06/19] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 08/19] cxl: Move cxl_rwsem into built-in code Srirangan Madhavan
` (12 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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 decoder base and size registers low then high through a shared
helper used by enumeration and the reset snapshot.
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.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
Changes in v17:
- Initialize the zero-size end to base - 1 and document the zero-size
case, removing a branch (Richard)
https://lore.kernel.org/linux-cxl/auQ27s1rSCcbdRJ3@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=7d6ed7faf7d8e14abce8ae23902d5323738868d2
---
drivers/cxl/core/core.h | 5 +++
drivers/cxl/core/hdm.c | 65 ++++++++------------------------
drivers/cxl/core/hdm_regs.c | 74 +++++++++++++++++++++++++++++++++++++
3 files changed, 95 insertions(+), 49 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index fb14b222f3ca..ea539f00e55d 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -147,6 +147,11 @@ 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_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
+ int id, u32 ctrl);
+
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 2c373dfbc070..5b5f5f22ea32 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -938,11 +938,11 @@ 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;
- u64 size, base, skip, dpa_size, lo, hi;
- bool committed;
+ u64 size, skip, dpa_size, lo, hi;
+ struct cxl_decoder_config config;
u32 remainder;
- int i, rc;
u32 ctrl;
+ int i, rc;
union {
u64 value;
unsigned char target_id[8];
@@ -953,41 +953,25 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
which, info);
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
- lo = readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(which));
- hi = readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(which));
- base = (hi << 32) + lo;
- 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_read_decoder(&config, hdm, which, ctrl);
+ 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;
+ }
+
+ config.id = cxld->config.id;
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,
@@ -1021,23 +1005,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);
@@ -1052,7 +1019,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..cc5d77c4abf5 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -116,3 +116,77 @@ 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;
+ unsigned long flags = 0;
+ struct range hpa_range;
+ int interleave_ways;
+ u64 end;
+ int rc;
+
+ if (!committed)
+ size = 0;
+ if (base == U64_MAX || size == U64_MAX)
+ return -ENXIO;
+ /* A zero-size decoder maps no HPA; its range is empty at @base. */
+ end = base - 1;
+ if (size && check_add_overflow(base, size - 1, &end))
+ return -ENXIO;
+
+ 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");
+
+int cxl_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
+ int id, u32 ctrl)
+{
+ u64 lo, hi, base, size;
+
+ lo = readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ hi = readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
+ base = (hi << 32) | lo;
+ lo = readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ hi = readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
+ size = (hi << 32) | lo;
+
+ return cxl_hdm_unpack_decoder(config, id, ctrl, base, size);
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_read_decoder, "cxl_core");
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v17 08/19] cxl: Move cxl_rwsem into built-in code
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (6 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 07/19] cxl: Share HDM decoder register unpacking Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
` (11 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Keep the region and DPA locks available to built-in CXL state management
when cxl_core is modular.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
drivers/cxl/core/Makefile | 2 +-
drivers/cxl/core/hdm.c | 5 -----
drivers/cxl/core/hdm_state.c | 15 +++++++++++++++
3 files changed, 16 insertions(+), 6 deletions(-)
create mode 100644 drivers/cxl/core/hdm_state.c
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 6703c1729396..9971efb237fb 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
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 5b5f5f22ea32..85f9abf58a42 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_state.c b/drivers/cxl/core/hdm_state.c
new file mode 100644
index 000000000000..c9cc019902ef
--- /dev/null
+++ b/drivers/cxl/core/hdm_state.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <linux/export.h>
+#include <linux/rwsem.h>
+
+#include <cxl/hdm.h>
+
+#include "cxl.h"
+#include "core.h"
+
+struct cxl_rwsem cxl_rwsem = {
+ .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
+ .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
+};
+EXPORT_SYMBOL_FOR_MODULES(cxl_rwsem, "cxl_core");
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (7 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 08/19] cxl: Move cxl_rwsem into built-in code Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 10/19] cxl: Build register block helpers into the kernel Srirangan Madhavan
` (10 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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. Keep cache access and
locking in the built-in HDM state helpers. 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.
Non-PCI mock ports have no upstream PCI device, so the cache helpers
ignore them. This avoids test wrappers and a circular dependency between
cxl_core and cxl_mock.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=50a88f5194e5df38590d413f925d8d5afa8a8488
---
drivers/cxl/Kconfig | 5 ++++
drivers/cxl/core/core.h | 1 +
drivers/cxl/core/hdm.c | 34 +++++++++++++++++++--
drivers/cxl/core/hdm_state.c | 57 ++++++++++++++++++++++++++++++++++++
drivers/cxl/core/pci.c | 24 +++++++++++++++
include/cxl/cxl.h | 1 +
include/cxl/hdm.h | 32 ++++++++++++++++++++
include/linux/pci.h | 6 ++++
8 files changed, 158 insertions(+), 2 deletions(-)
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 ea539f00e55d..e34d574f6594 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -224,6 +224,7 @@ static inline void devm_cxl_dport_ras_setup(struct cxl_dport *dport) { }
int cxl_gpf_port_setup(struct cxl_dport *dport);
struct cxl_hdm;
+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 85f9abf58a42..ec387070d7ad 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -725,6 +725,24 @@ 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 pci_dev *pdev __free(pci_dev_put) = NULL;
+
+ if (!hdm)
+ return;
+ if (!is_endpoint_decoder(&cxld->dev))
+ return;
+
+ pdev = cxl_port_get_uport_pci_dev(cxlhdm->port);
+ if (!pdev)
+ return;
+ cxl_hdm_cache_refresh_decoder(pdev, hdm,
+ to_cxl_endpoint_decoder(&cxld->dev));
+}
+
static void
cxl_endpoint_decoder_snapshot(struct cxl_endpoint_decoder *cxled,
struct cxl_endpoint_decoder_settings *settings)
@@ -805,6 +823,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;
}
@@ -877,6 +896,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)) {
@@ -1079,13 +1099,21 @@ 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);
u64 dpa_base = 0;
+ int i, rc;
+
+ if (is_cxl_endpoint(port) && hdm && !emulated) {
+ rc = cxl_hdm_check_decoder_count(cxlhdm);
+ if (rc)
+ return rc;
+ }
cxl_settle_decoders(cxlhdm);
for (i = 0; i < cxlhdm->decoder_count; i++) {
- int rc, target_count = cxlhdm->target_count;
+ int target_count = cxlhdm->target_count;
struct cxl_decoder *cxld;
if (is_cxl_endpoint(port)) {
@@ -1120,6 +1148,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/hdm_state.c b/drivers/cxl/core/hdm_state.c
index c9cc019902ef..d4c2be1ef833 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/pci.h>
#include <linux/rwsem.h>
#include <cxl/hdm.h>
@@ -13,3 +15,58 @@ struct cxl_rwsem cxl_rwsem = {
.dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
};
EXPORT_SYMBOL_FOR_MODULES(cxl_rwsem, "cxl_core");
+
+void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
+ struct cxl_endpoint_decoder *cxled)
+{
+ struct cxl_endpoint_decoder_settings *settings;
+ struct cxl_decoder *cxld = &cxled->cxld;
+ struct cxl_hdm_info *info;
+ u32 global_ctrl;
+ int id;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ id = cxld->config.id;
+ info = pdev->hdm;
+ if (!info)
+ return;
+ if (id < 0 || id >= info->decoder_count) {
+ pci_warn(pdev, "CXL HDM decoder %d exceeds cached count %d\n",
+ id, info->decoder_count);
+ 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[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 = id,
+ };
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE) {
+ settings->config = cxld->config;
+ settings->skip = cxled->skip;
+ }
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_refresh_decoder, "cxl_core");
+
+int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count)
+{
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ if (pdev->hdm && pdev->hdm->decoder_count != count) {
+ pci_warn(pdev,
+ "CXL HDM cache decoder count mismatch: cached=%d hdm=%d\n",
+ pdev->hdm->decoder_count, count);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_check_decoder_count, "cxl_core");
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index f72cd8f50e77..5272013548c7 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,29 @@ int cxl_await_media_ready(struct cxl_dev_state *cxlds)
}
EXPORT_SYMBOL_NS_GPL(cxl_await_media_ready, "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;
+
+ return cxl_hdm_cache_check_decoder_count(pdev, cxlhdm->decoder_count);
+}
+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 6d6e564be27a..341a7c52894a 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..06e9aa4479c2 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -6,6 +6,9 @@
#include <linux/types.h>
#include <cxl/cxl.h>
+struct pci_dev;
+struct cxl_endpoint_decoder;
+
/**
* struct cxl_endpoint_decoder_settings - endpoint HDM decoder programming
* @config: common decoder configuration
@@ -16,4 +19,33 @@ 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);
+};
+
+void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
+ struct cxl_endpoint_decoder *cxled);
+int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int 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] 21+ messages in thread
* [PATCH v17 10/19] cxl: Build register block helpers into the kernel
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (8 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
` (9 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Build register discovery with the built-in HDM helpers so PCI enumeration
can use it even when cxl_core is modular. Export the interfaces used by
cxl_core and preserve the BAR and offset in the register map.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
drivers/cxl/core/Makefile | 3 +--
drivers/cxl/core/regs.c | 9 +++++++++
drivers/cxl/cxl.h | 3 ---
include/cxl/cxl.h | 5 +++++
include/cxl/pci.h | 13 +++++++++++++
tools/testing/cxl/Kbuild | 1 -
6 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 9971efb237fb..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 hdm_state.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/regs.c b/drivers/cxl/core/regs.c
index 20c2d9fbcfe7..def3c20d1a7a 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_FOR_MODULES(devm_cxl_iomap_block, "cxl_core");
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/include/cxl/cxl.h b/include/cxl/cxl.h
index 341a7c52894a..ea23bc15d00e 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,
@@ -150,6 +151,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
@@ -160,6 +163,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;
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] 21+ messages in thread
* [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (9 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 10/19] cxl: Build register block helpers into the kernel Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
` (8 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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.
Limit caching to devices that advertise CXL.cache, CXL.mem, CXL Reset,
and Reset Memory Clear. Save their 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. Publish the
cache under the PCI device lock and protect later updates with the DPA
lock.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
Changes in v17:
- Publish the cache under the PCI device lock without taking
cxl_rwsem.dpa, and drop the cxl_hdm_cache_present() and
cxl_hdm_cache_publish() helpers (Li Ming)
https://lore.kernel.org/linux-cxl/d581f2a0-dd9e-4d0a-bd19-639c1bd9b832@zohomail.com/
- Reject an all-ones DVSEC Control read in cxl_set_mem_enable() before
it is written back or cached (Sashiko)
https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=11
- Explain why cxl_set_mem_enable() refreshes the cached DVSEC Control
when MEM_ENABLE already matches (Richard)
https://lore.kernel.org/linux-cxl/auRQN6a3QVtqjd5a@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=b12baa1105f33cc1a10f9c9c0c6376194397d9f3
---
MAINTAINERS | 1 +
drivers/cxl/core/hdm_regs.c | 96 +++++++++++++++
drivers/cxl/core/hdm_state.c | 37 +++++-
drivers/cxl/core/pci.c | 29 +++--
drivers/pci/Makefile | 1 +
drivers/pci/bus.c | 1 +
drivers/pci/cxl.c | 225 ++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 8 ++
drivers/pci/probe.c | 1 +
include/cxl/hdm.h | 9 ++
include/uapi/linux/pci_regs.h | 2 +
11 files changed, 399 insertions(+), 11 deletions(-)
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/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index cc5d77c4abf5..c92dbb017162 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>
@@ -190,3 +191,98 @@ int cxl_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
return cxl_hdm_unpack_decoder(config, id, ctrl, base, size);
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_read_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 lo, hi;
+ u32 ctrl;
+ int rc;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ rc = cxl_hdm_read_decoder(&settings->config, hdm, id, ctrl);
+ 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 {
+ lo = readl(hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
+ hi = readl(hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
+ settings->skip = (hi << 32) | lo;
+ }
+
+ 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
index d4c2be1ef833..9def4a7439c9 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -1,21 +1,37 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <linux/cleanup.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/rwsem.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");
+void cxl_hdm_cache_set_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;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_set_dvsec_ctrl, "cxl_core");
+
void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
struct cxl_endpoint_decoder *cxled)
{
@@ -70,3 +86,22 @@ int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count)
return 0;
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_check_decoder_count, "cxl_core");
+
+/**
+ * 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 5272013548c7..ea5327cd627f 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -209,22 +209,31 @@ static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
int d = cxlds->cxl_dvsec;
u16 ctrl;
- int rc;
+ int rc, ret = 1;
rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, &ctrl);
if (rc)
return pcibios_err_to_errno(rc);
+ /* An all-ones read must not be written back or cached. */
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return -ENODEV;
- if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) == val)
- return 1;
- ctrl &= ~PCI_DVSEC_CXL_MEM_ENABLE;
- ctrl |= val;
+ if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) != val) {
+ ctrl &= ~PCI_DVSEC_CXL_MEM_ENABLE;
+ ctrl |= val;
- rc = pci_write_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, ctrl);
- if (rc)
- return pcibios_err_to_errno(rc);
+ rc = pci_write_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, ctrl);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ ret = 0;
+ }
- return 0;
+ /*
+ * Refresh the PCI-core cache from the live register even when
+ * MEM_ENABLE already matched: a reset restores the cached value.
+ */
+ cxl_hdm_cache_set_dvsec_ctrl(pdev, ctrl);
+ return ret;
}
static void clear_mem_enable(void *cxlds)
@@ -314,7 +323,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/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..a0d20f4210a5
--- /dev/null
+++ b/drivers/pci/cxl.c
@@ -0,0 +1,225 @@
+// 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;
+ struct cxl_hdm_info *info;
+ 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;
+ }
+
+ 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;
+
+ rc = pci_read_config_word(pdev, PCI_COMMAND, &command);
+ if (rc)
+ return ERR_PTR(pcibios_err_to_errno(rc));
+
+ if (!(command & PCI_COMMAND_MEMORY)) {
+ 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);
+ if (!(command & PCI_COMMAND_MEMORY)) {
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc)
+ pci_err(pdev,
+ "failed to restore PCI_COMMAND after CXL HDM cache init: %d\n",
+ pcibios_err_to_errno(rc));
+ }
+ return info;
+}
+
+static int __pci_cxl_hdm_cache_init(struct pci_dev *pdev)
+{
+ struct cxl_register_map map = { };
+ struct cxl_hdm_info *read_info;
+ u16 dvsec, cap;
+ int rc;
+
+ guard(pci_dev)(pdev);
+
+ /* Avoid duplicate MMIO reads when the cache is already present. */
+ if (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_CACHE_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_RST_MEM_CLR_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;
+
+ read_info = cxl_pci_hdm_read_info(pdev, &map, dvsec);
+ if (IS_ERR(read_info))
+ return PTR_ERR(read_info);
+
+ /*
+ * The PCI device lock held since the cache check excludes other
+ * initializers. CXL drivers bind only after enumeration, so no cache
+ * mutator can run yet and cxl_rwsem.dpa is not needed to 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/hdm.h b/include/cxl/hdm.h
index 06e9aa4479c2..0604dcc439c9 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -26,6 +26,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
@@ -39,13 +40,21 @@ 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);
+void cxl_hdm_cache_set_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl);
void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
struct cxl_endpoint_decoder *cxled);
int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count);
+void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
#endif /* __CXL_HDM_H__ */
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
index facaa324bd86..ca3e77b72d87 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1352,6 +1352,8 @@
#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_RST_CAPABLE _BITUL(7)
+#define PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE _BITUL(11)
#define PCI_DVSEC_CXL_CTRL 0xC
#define PCI_DVSEC_CXL_MEM_ENABLE _BITUL(2)
#define PCI_DVSEC_CXL_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10))
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (10 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
` (7 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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. Wait for the timeout the device
advertises, up to 100 seconds, and access only Status2 until reset
completion, error or expiry, as CXL 4.0 section 9.7.3 requires. Control2
cleanup runs only afterwards. 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.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
Changes in v17:
- Wait for the reset timeout the device advertises, up to 100 seconds,
instead of clamping it to 20 seconds, so that Control2 is not accessed
while the device may still be resetting. The warning for an unknown
timeout encoding now reports the timeout used (Richard)
https://lore.kernel.org/linux-cxl/auQC2UECQ8vaelPz@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=04f8f84816d724f8b47f4d47a77916ac37c63a49
---
drivers/pci/cxl.c | 244 +++++++++++++++++++++++++++++++++-
drivers/pci/pci.h | 5 +
include/uapi/linux/pci_regs.h | 12 ++
3 files changed, 260 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index a0d20f4210a5..9ac5948f595b 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>
@@ -223,3 +227,241 @@ 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. Wait for the full advertised timeout
+ * so that Control2 is not accessed while the device may still be resetting.
+ */
+#define CXL_RESET_RRS_WAIT_MS 100
+#define CXL_RESET_STATUS_POLL_MS 20
+static const u32 cxl_reset_timeout_ms[] = {
+ 10, 100, 1000, 10000, 100000,
+};
+
+/*
+ * CXL 4.0 section 9.6 permits using the device cache size to determine
+ * a timeout. Use a conservative fixed estimate of 100 ms.
+ */
+#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(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(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;
+ }
+
+ pci_warn(pdev, "timed out waiting for CXL cache writeback and invalidate\n");
+ return -ETIMEDOUT;
+}
+
+static int cxl_reset_disable_cache(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc;
+
+ rc = cxl_reset_update_ctrl2(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(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)) {
+ idx = ARRAY_SIZE(cxl_reset_timeout_ms) - 1;
+ pci_warn(pdev,
+ "unknown CXL reset timeout encoding; using %u ms\n",
+ cxl_reset_timeout_ms[idx]);
+ }
+
+ /* Allow one retry after the mandatory 100 ms quiet period. */
+ timeout_ms = max_t(u32, cxl_reset_timeout_ms[idx],
+ CXL_RESET_RRS_WAIT_MS + CXL_RESET_STATUS_POLL_MS);
+ pci_info(pdev, "CXL reset timeout: %u ms\n", timeout_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(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CXL_RST |
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN,
+ 0);
+
+ return cxl_reset_update_ctrl2(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 ca3e77b72d87..7224857c7b30 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1352,10 +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] 21+ messages in thread
* [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (11 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
` (6 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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.
Userspace is expected to offline CXL.mem users before reset. The
reservation enforces this for claimed ranges: the reset fails while a
conflicting resource is claimed, and no one can claim the range during
the reset. It cannot see accesses by code that uses the range without
claiming it, which the offline expectation also covers.
Reject normalized-addressing decoders because their cached ranges are not
system physical addresses. Record Normalized Addressing in the cache when
region discovery sets it, since the cache is filled earlier. A reset that
takes its snapshot between endpoint probe and that update can still
miss it.
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.
Hold the global cxl_rwsem.region write lock across snapshot, reset and
restoration to keep the decoder state and reserved ranges stable. This also
blocks region and decoder sysfs, region probe, poison operations, sanitize
and region-aware event tracing across CXL devices. The Memory Clear wait
lasts up to the timeout the device advertises, at most 100 seconds; cache
flushes and other reset work extend the hold.
Allow the existing CXL_REGION_INVALIDATION_TEST option to bypass missing
CPU cache invalidation for test environments without a provider.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
Changes in v17:
- State that userspace is expected to offline CXL.mem users before
reset, and what the HPA reservation does and does not cover (Richard)
https://lore.kernel.org/linux-cxl/auQRSOGWEDzB_qjF@MWDK4CY14F/
- Record Normalized Addressing in the cached decoder when region
discovery sets it, and note the short window before that update
(Sashiko)
https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=13
- Annotate cxl_region_quiesce_lock()/unlock() and __cxl_reset_execute()
for context analysis (Sashiko)
https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=15
- Describe the region lock hold as bounded by the advertised reset
timeout, at most 100 seconds, plus cache flushes (Richard)
https://lore.kernel.org/linux-cxl/auQC2UECQ8vaelPz@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=5944c6e61f14c0dd28d54537be15edb2cadb0e8b
---
drivers/cxl/Kconfig | 3 +-
drivers/cxl/core/hdm_state.c | 266 ++++++++++++++++++++++++++++++++++-
drivers/cxl/core/region.c | 14 ++
drivers/pci/cxl.c | 87 ++++++++++--
include/cxl/hdm.h | 47 ++++++-
5 files changed, 406 insertions(+), 11 deletions(-)
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 8b08bfdc4982..e927e69a3f7b 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -226,7 +226,8 @@ config CXL_REGION_INVALIDATION_TEST
invalidation mechanism. For example usage of wbinvd is restricted to
bare metal x86. However, for testing purposes toggling this option
can disable that data integrity safety and proceed with enabling
- regions when there might be conflicting contents in the CPU cache.
+ regions or performing CXL reset when there might be conflicting
+ contents in the CPU cache.
If unsure, or if this kernel is meant for production environments,
say N.
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index 9def4a7439c9..51df56377f7a 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -1,8 +1,14 @@
// 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/io.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>
@@ -13,7 +19,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),
@@ -87,6 +94,27 @@ int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count)
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_check_decoder_count, "cxl_core");
+/**
+ * cxl_hdm_cache_set_normalized() - record Normalized Addressing in the cache
+ * @pdev: endpoint that owns the cache
+ * @id: endpoint decoder id
+ *
+ * Normalized Addressing is determined during region discovery, after the
+ * cache was filled, so the cached decoder lacks it. Record only that flag: the
+ * decoder's software lock must not become a hardware Lock On Commit when the
+ * cache is restored.
+ */
+void cxl_hdm_cache_set_normalized(struct pci_dev *pdev, int id)
+{
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ if (!pdev->hdm || id < 0 || id >= pdev->hdm->decoder_count)
+ return;
+
+ pdev->hdm->settings[id].config.flags |=
+ CXL_DECODER_F_NORMALIZED_ADDRESSING;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_set_normalized, "cxl_core");
+
/**
* cxl_hdm_cache_release() - unpublish and free cached HDM state
* @slot: cache pointer owned by the device
@@ -105,3 +133,239 @@ 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(const struct range *hpa_range)
+{
+ u64 len = range_len(hpa_range);
+
+ if (!len)
+ return -EINVAL;
+ if (hpa_range->end > RESOURCE_SIZE_MAX || len > RESOURCE_SIZE_MAX ||
+ len > SIZE_MAX)
+ return -EOVERFLOW;
+
+ return 0;
+}
+
+static int cxl_hdm_range_add(struct cxl_hdm_quiesce *q,
+ const struct range *hpa_range)
+{
+ struct cxl_hdm_range *range, *next, *new_range;
+ int rc;
+
+ rc = cxl_hdm_range_validate(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->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,
+ 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)
+ return -EOPNOTSUPP;
+
+ rc = cxl_hdm_range_add(q, &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)
+ 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);
+
+ if (!cpu_cache_has_invalidate_memregion() &&
+ IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST)) {
+ pr_info_once("CXL reset: bypassing CPU cache invalidation for testing\n");
+ return 0;
+ }
+
+ 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);
+ rc = rc ?: rc2;
+ }
+
+ return rc;
+}
+
+/**
+ * cxl_hdm_quiesce_prepare() - take the HPA ranges of a device out of use
+ * @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 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() &&
+ !IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST))
+ return -ENXIO;
+
+ rc = cxl_hdm_ranges_collect(q, 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/cxl/core/region.c b/drivers/cxl/core/region.c
index 8cfc06df3134..b06dd9b901f0 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -12,6 +12,7 @@
#include <linux/idr.h>
#include <linux/memory-tiers.h>
#include <linux/string_choices.h>
+#include <cxl/hdm.h>
#include <cxlmem.h>
#include <cxl.h>
#include "core.h"
@@ -3840,6 +3841,19 @@ int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
if (IS_ERR(cxlrd))
return PTR_ERR(cxlrd);
+ /*
+ * Translation setup may have marked the decoder as Normalized
+ * Addressing, after the PCI cache was filled. Record it for reset.
+ */
+ if (cxled->cxld.config.flags & CXL_DECODER_F_NORMALIZED_ADDRESSING) {
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(cxled_to_port(cxled));
+
+ if (pdev)
+ cxl_hdm_cache_set_normalized(pdev,
+ cxled->cxld.config.id);
+ }
+
/*
* Ensure that, if multiple threads race to construct_region()
* for the HPA range, one does the construction and the others
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 9ac5948f595b..af8ec8db20f1 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>
@@ -23,6 +24,13 @@
#include "pci.h"
+struct cxl_pci_cache {
+ struct cxl_hdm_quiesce quiesce;
+ u16 saved_ctrl2;
+ /* A cache-policy write was attempted and may need undoing. */
+ bool cache_disabled;
+};
+
/* Find the CXL Device DVSEC and read its capability register. */
static int cxl_pci_device_dvsec(struct pci_dev *pdev, u16 *dvsec, u16 *cap)
{
@@ -335,6 +343,23 @@ static int cxl_reset_restore_cache_policy(struct pci_dev *pdev, int dvsec,
return rc;
}
+/* Shared range reservation, host invalidation, and device cache writeback. */
+static int cxl_reset_cache_prepare(struct pci_dev *pdev, int dvsec, u16 cap,
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache)
+ __must_hold(cxl_region_quiesce)
+{
+ int rc;
+
+ rc = cxl_hdm_quiesce_prepare(&cache->quiesce, snapshot);
+ if (rc)
+ return rc;
+
+ /* Track an attempted change so partial failures still restore policy. */
+ cache->cache_disabled = true;
+ return cxl_reset_disable_cache(pdev, dvsec, cap);
+}
+
static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
{
unsigned long deadline;
@@ -389,7 +414,9 @@ 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)
+ __must_hold(cxl_region_quiesce)
{
int rc, rc2;
@@ -414,29 +441,61 @@ 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);
+ if (rc2)
+ pci_err(pdev, "failed to invalidate CPU cache after CXL reset: %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)
+static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
+ __must_hold(cxl_region_quiesce)
{
- u16 saved_ctrl2;
+ struct cxl_hdm_info *snapshot __free(kfree) = NULL;
+ struct cxl_pci_cache cache = {};
int rc, rc2;
- rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &saved_ctrl2);
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2,
+ &cache.saved_ctrl2);
if (rc)
return pcibios_err_to_errno(rc);
- if (PCI_POSSIBLE_ERROR(saved_ctrl2))
+ if (PCI_POSSIBLE_ERROR(cache.saved_ctrl2))
return -ENODEV;
- rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
+ if (IS_ERR(snapshot)) {
+ pci_err(pdev, "CXL HDM decoder state unavailable: %ld\n",
+ PTR_ERR(snapshot));
+ return PTR_ERR(snapshot);
+ }
+
+ rc = cxl_reset_cache_prepare(pdev, dvsec, cap, snapshot, &cache);
+ if (rc && !cache.cache_disabled) {
+ pci_err(pdev, "failed to prepare CXL reset ranges: %d\n", rc);
+ cxl_hdm_quiesce_destroy(&cache.quiesce);
+ return rc;
+ }
if (!rc)
- rc = __cxl_reset_execute(pdev, dvsec, cap);
+ rc = __cxl_reset_execute(pdev, dvsec, cap, &cache.quiesce);
/* Restore cache policy after any attempt to disable caching. */
- rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, saved_ctrl2);
+ rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
+ cxl_hdm_quiesce_destroy(&cache.quiesce);
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;
@@ -463,5 +522,17 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (probe)
return 0;
+ /* The cache is owned by @pdev and does not require a bound CXL driver. */
+ if (!pdev->hdm)
+ return -ENOTTY;
+
+ /* Memory decoded through DVSEC ranges has no HPA ranges to quiesce. */
+ if (cxl_hdm_cache_dvsec_decode(&pdev->hdm))
+ return -ENOTTY;
+
+ if (!cpu_cache_has_invalidate_memregion() &&
+ !IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST))
+ return -ENOTTY;
+
return cxl_reset_execute(pdev, dvsec, cap);
}
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 0604dcc439c9..49b36a45c682 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -3,10 +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 pci_dev;
+struct resource;
struct cxl_endpoint_decoder;
/**
@@ -45,7 +49,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);
@@ -55,6 +58,48 @@ void cxl_hdm_cache_set_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl);
void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
struct cxl_endpoint_decoder *cxled);
int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count);
+void cxl_hdm_cache_set_normalized(struct pci_dev *pdev, int id);
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
+ * @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 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)
+ __acquires(cxl_region_quiesce);
+void cxl_region_quiesce_unlock(void)
+ __releases(cxl_region_quiesce);
+int cxl_hdm_quiesce_prepare(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] 21+ messages in thread
* [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (12 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
` (5 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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 VFs, 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>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
Changes in v17:
- Move the function-scope checks ahead of the CXL DVSEC lookups, so a VF
returns -ENOTTY without config reads during reset-method probing
(Joshua)
https://lore.kernel.org/linux-cxl/20261105143611.31563-1-machaujo@amazon.de/
---
drivers/pci/cxl.c | 3 +++
drivers/pci/pci.c | 7 +++++++
2 files changed, 10 insertions(+)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index af8ec8db20f1..c07266ab0133 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -501,6 +501,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
u16 dvsec, cap, ctrl;
int rc;
+ if (pdev->is_virtfn || pdev->multifunction || pci_num_vf(pdev))
+ return -ENOTTY;
+
rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
if (rc)
return rc;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index b2879a6be5f8..aef976d0c850 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5018,6 +5018,13 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
u16 dvsec, reg, val;
int rc;
+ /*
+ * Reject VFs and multifunction devices before walking the bridge's
+ * capabilities: every VF behind the same bridge would repeat the walk.
+ */
+ if (dev->is_virtfn || dev->multifunction || pci_num_vf(dev))
+ return -ENOTTY;
+
bridge = pci_upstream_bridge(dev);
if (!bridge)
return -ENOTTY;
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (13 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 16/19] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
` (4 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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. Set Lock On Commit before
recommitting a cleared locked decoder; 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.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
Changes in v17:
- Set Lock On Commit after programming the decoder registers and before
Commit when recommitting a locked decoder, so the lock takes effect at
commit (Alison)
https://lore.kernel.org/linux-cxl/auAg6MFDGyVQvQxM@aschofie-mobl2.lan/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=81fff5893a3140361cac625b09ada798afb92e03
---
drivers/cxl/core/core.h | 2 +-
drivers/cxl/core/hdm.c | 2 +-
drivers/cxl/core/hdm_regs.c | 177 +++++++++++++++++++++++++++++++---
drivers/pci/cxl.c | 185 ++++++++++++++++++++++++++++++++++--
include/cxl/hdm.h | 2 +
5 files changed, 346 insertions(+), 22 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index e34d574f6594..9fc9fd6c771e 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -145,7 +145,7 @@ void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *setti
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 cxld_await_commit(void __iomem *hdm, int id, bool committed);
int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
u32 ctrl, u64 base, u64 size);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index ec387070d7ad..ad8cb1e76afb 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -815,7 +815,7 @@ 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->config.id);
+ rc = cxld_await_commit(hdm, cxld->config.id, true);
if (rc) {
dev_dbg(&port->dev, "%s: error %d committing decoder\n",
dev_name(&cxld->dev), rc);
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index c92dbb017162..4f5e843cd141 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -52,7 +52,7 @@ static void cxld_set_type(const struct cxl_decoder_config *config, u32 *ctrl)
* clock skew and other marginal behavior
*/
#define COMMIT_TIMEOUT_MS 20
-int cxld_await_commit(void __iomem *hdm, int id)
+int cxld_await_commit(void __iomem *hdm, int id, bool committed)
{
u32 ctrl;
int i;
@@ -64,7 +64,7 @@ int cxld_await_commit(void __iomem *hdm, int id)
writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
return -EIO;
}
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
+ if (!!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) == committed)
return 0;
fsleep(1000);
}
@@ -92,8 +92,12 @@ static u32 cxl_hdm_write_range(void __iomem *hdm,
return ctrl;
}
-void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
- void __iomem *hdm)
+/*
+ * Program every endpoint decoder register except Commit and Lock On Commit.
+ * Return the control value to write; Commit is not set in it.
+ */
+static u32 cxl_hdm_program_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm)
{
int id = settings->config.id;
u32 ctrl;
@@ -101,7 +105,17 @@ void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *setti
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));
+
+ return ctrl & ~CXL_HDM_DECODER0_CTRL_COMMIT;
+}
+
+void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ u32 ctrl = cxl_hdm_program_endpoint(settings, hdm);
+
+ writel(ctrl | CXL_HDM_DECODER0_CTRL_COMMIT,
+ hdm + CXL_HDM_DECODER0_CTRL_OFFSET(settings->config.id));
}
EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start_endpoint, "cxl_core");
@@ -220,8 +234,7 @@ int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
}
/* 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,
+static int cxl_hdm_read_decoder_settings(struct cxl_endpoint_decoder_settings *settings,
void __iomem *hdm, int id)
{
u64 lo, hi;
@@ -230,12 +243,8 @@ static int cxl_hdm_read_decoder_settings(struct device *dev,
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
rc = cxl_hdm_read_decoder(&settings->config, hdm, id, ctrl);
- if (rc) {
- dev_err(dev,
- "CXL HDM decoder %d has invalid configuration: %d\n",
- id, rc);
+ if (rc)
return rc;
- }
if (!(settings->config.flags & CXL_DECODER_F_ENABLE)) {
*settings = (struct cxl_endpoint_decoder_settings) {
@@ -267,10 +276,13 @@ int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
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],
+ rc = cxl_hdm_read_decoder_settings(&info->settings[i],
hdm, i);
- if (rc)
+ if (rc) {
+ dev_err(dev, "CXL HDM decoder %d has invalid configuration: %d\n",
+ i, rc);
return rc;
+ }
}
/*
@@ -286,3 +298,140 @@ int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
return 0;
}
+
+static int cxl_hdm_decoder_uncommit(void __iomem *hdm, int id, bool *retained)
+{
+ u32 ctrl;
+
+ *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)
+ return -EIO;
+
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK) {
+ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) {
+ *retained = true;
+ return 0;
+ }
+
+ 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));
+
+ return cxld_await_commit(hdm, id, false);
+}
+
+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(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(hdm, config->id, &retained);
+ if (rc)
+ return rc;
+
+ if (retained) {
+ struct cxl_endpoint_decoder_settings active;
+
+ rc = cxl_hdm_read_decoder_settings(&active, hdm, config->id);
+ if (rc)
+ return rc;
+ if (!cxl_hdm_decoder_settings_match(settings, &active))
+ return -EIO;
+ return 0;
+ }
+
+ if (!(config->flags & CXL_DECODER_F_ENABLE))
+ return 0;
+
+ /*
+ * Configure every decoder register, then set Lock On Commit, then set
+ * Commit, as CXL describes. Setting Lock On Commit before the base and
+ * size are written can make a device drop those writes.
+ */
+ ctrl = cxl_hdm_program_endpoint(settings, hdm);
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ if (config->flags & CXL_DECODER_F_LOCK) {
+ ctrl |= CXL_HDM_DECODER0_CTRL_LOCK;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ }
+ writel(ctrl | CXL_HDM_DECODER0_CTRL_COMMIT,
+ hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ rc = cxld_await_commit(hdm, config->id, true);
+ if (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 || !(ctrl & CXL_HDM_DECODER0_CTRL_LOCK))
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * cxl_hdm_restore_state() - reprogram endpoint HDM decoders from a snapshot
+ * @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(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)
+ 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(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 c07266ab0133..77d0e30841d9 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>
@@ -236,6 +237,162 @@ 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(hdm, snapshot);
+ iounmap(hdm);
+ if (rc) {
+ pci_err(pdev, "failed to restore CXL HDM state: %d\n", 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;
+}
+
+/* Caller holds the region lock and keeps device traffic isolated. */
+static int pci_cxl_restore_state(struct pci_dev *pdev,
+ const struct cxl_hdm_info *snapshot, u16 *command)
+ __must_hold(cxl_region_quiesce)
+{
+ device_lock_assert(&pdev->dev);
+ cxl_restore_pci_state_for_hdm_restore(pdev, command);
+ return cxl_restore_state(pdev, snapshot);
+}
+
/*
* 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
@@ -415,7 +572,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)
__must_hold(cxl_region_quiesce)
{
int rc, rc2;
@@ -441,10 +600,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)
+ rc = pci_cxl_restore_state(pdev, snapshot, command);
+
rc2 = cxl_hdm_quiesce_flush(q);
if (rc2)
pci_err(pdev, "failed to invalidate CPU cache after CXL reset: %d\n", rc2);
rc = rc ?: rc2;
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
pci_dev_reset_iommu_done(pdev);
return rc;
@@ -455,6 +619,7 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
{
struct cxl_hdm_info *snapshot __free(kfree) = NULL;
struct cxl_pci_cache cache = {};
+ u16 command;
int rc, rc2;
rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2,
@@ -478,11 +643,19 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
return rc;
}
if (!rc)
- rc = __cxl_reset_execute(pdev, dvsec, cap, &cache.quiesce);
+ rc = __cxl_reset_execute(pdev, dvsec, cap, snapshot, &cache.quiesce,
+ &command);
/* Restore cache policy after any attempt to disable caching. */
rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
cxl_hdm_quiesce_destroy(&cache.quiesce);
- 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 49b36a45c682..94a8f8a1a65a 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -54,6 +54,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(void __iomem *hdm,
+ const struct cxl_hdm_info *snapshot);
void cxl_hdm_cache_set_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl);
void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
struct cxl_endpoint_decoder *cxled);
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v17 16/19] PCI/CXL: Expose CXL Reset as a PCI reset method
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (14 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
` (3 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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>
Reviewed-by: Richard Cheng <icheng@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 77d0e30841d9..c68b08f7f95b 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -679,7 +679,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) ||
@@ -688,9 +688,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;
@@ -710,5 +710,11 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
!IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST))
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 aef976d0c850..426b5ee38d04 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5152,6 +5152,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] 21+ messages in thread
* [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (15 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 16/19] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 18/19] PCI/CXL: Flush host and device caches for " Srirangan Madhavan
` (2 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Acked-by: Bjorn Helgaas <bhelgaas@google.com> # pci/pci.c
Assisted-by: LLM
---
Changes in v17:
- Enable bus mastering only after the IOMMU is restored, via a separate
cxl_restore_command_after_pci_reset() step (Richard)
https://lore.kernel.org/linux-cxl/auRhJa-nzR0CnKf3@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=b93f0c8e67b198a63786fd3721b7177ab9df60be
---
drivers/cxl/core/hdm_state.c | 31 +++++++++++++
drivers/pci/cxl.c | 85 ++++++++++++++++++++++++++++++++++++
drivers/pci/pci.c | 14 ++++++
drivers/pci/pci.h | 24 ++++++++++
include/cxl/hdm.h | 2 +
5 files changed, 156 insertions(+)
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index 51df56377f7a..7e0903ce0b78 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -182,6 +182,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 c68b08f7f95b..a9aa2e1c631d 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -393,6 +393,91 @@ static int pci_cxl_restore_state(struct pci_dev *pdev,
return cxl_restore_state(pdev, snapshot);
}
+/**
+ * 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))
+ return PTR_ERR(snapshot) == -ENXIO ? NULL : snapshot;
+
+ /* Raw reset callers may not have saved the PCI state needed for restore. */
+ if (!pdev->state_saved) {
+ pci_err(pdev, "CXL bus reset requires saved PCI state\n");
+ kfree(snapshot);
+ return ERR_PTR(-EINVAL);
+ }
+
+ 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()
+ * @command: returns the saved PCI_COMMAND value to apply once the IOMMU is
+ * restored
+ *
+ * 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.
+ *
+ * Bus mastering stays off, because the caller has not yet restored IOMMU
+ * translation and a DMA request now would hit the blocking domain. The caller
+ * calls cxl_restore_command_after_pci_reset() once the IOMMU is restored.
+ *
+ * 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)
+ rc = pci_cxl_restore_state(pdev, snapshot, command);
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
+
+ cxl_region_quiesce_unlock();
+ return rc;
+}
+
+/**
+ * cxl_restore_command_after_pci_reset() - re-enable the device after a bus reset
+ * @pdev: device that was reset
+ * @command: PCI_COMMAND value from cxl_restore_state_after_pci_reset()
+ *
+ * Call after the IOMMU is restored: this can enable bus mastering. Save the
+ * restored PCI state, and leave @pdev disabled on failure.
+ */
+int cxl_restore_command_after_pci_reset(struct pci_dev *pdev, u16 command)
+{
+ device_lock_assert(&pdev->dev);
+ return cxl_reset_save_restored_state(pdev, command);
+}
+
/*
* 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 426b5ee38d04..1d4ba90a6976 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5014,8 +5014,10 @@ 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;
+ u16 command;
int rc;
/*
@@ -5040,6 +5042,10 @@ 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);
+
rc = pci_dev_reset_iommu_prepare(dev);
if (rc) {
pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
@@ -5060,7 +5066,15 @@ 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, &command);
+
pci_dev_reset_iommu_done(dev);
+
+ /* Enable bus mastering only once the IOMMU translates again. */
+ if (!rc && snapshot)
+ rc = cxl_restore_command_after_pci_reset(dev, command);
+
return rc;
}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 743e65abd758..2f819e1fbead 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -730,10 +730,16 @@ 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,
+ u16 *command);
+int cxl_restore_command_after_pci_reset(struct pci_dev *pdev, u16 command);
#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 +747,24 @@ 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, u16 *command)
+{
+ return 0;
+}
+
+static inline int cxl_restore_command_after_pci_reset(struct pci_dev *pdev,
+ u16 command)
+{
+ return 0;
+}
#endif
#ifdef CONFIG_PCI_NPEM
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 94a8f8a1a65a..81767f21052a 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -64,6 +64,8 @@ void cxl_hdm_cache_set_normalized(struct pci_dev *pdev, int id);
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] 21+ messages in thread
* [PATCH v17 18/19] PCI/CXL: Flush host and device caches for CXL bus reset
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (16 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 19/19] cxl: Add KUnit checks for HDM decoder unpacking Srirangan Madhavan
2026-11-05 19:31 ` [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Alison Schofield
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
CXL bus reset can reset a Type 2 device while its host and device caches
still contain data for the device's HDM ranges. Flush the host caches,
disable device caching, and trigger device cache writeback and
invalidation when supported before reset. Invalidate the host caches
again before ending IOMMU exclusion.
Hold the CXL region quiesce from snapshot through restoration so region
commit and teardown cannot change the decoder state while the reset is
in progress. Reserve the affected HPA ranges and preserve the device's
cache policy across the operation.
If reset was not attempted, undo preparation without changing the saved
PCI command unless cleanup fails. After an attempted reset, keep the
device disabled on recovery failure and do not re-enable caching against
unrestored HDM state. Enable bus mastering only after the IOMMU is
restored, so that no DMA request reaches the blocking domain.
If no cached HDM state exists, retain the existing CXL bus reset path.
For a device with cached state, reject the reset if its cache-maintenance
state cannot be safely prepared or restored. An invalidation provider is
required unless the existing test-only CXL_REGION_INVALIDATION_TEST bypass
is enabled.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
Changes in v17:
- New in this series: folded into the CXL reset series instead of being
posted separately (Dave)
---
drivers/pci/cxl.c | 166 +++++++++++++++++++++++++++++++++++++---------
drivers/pci/pci.c | 11 +--
drivers/pci/pci.h | 19 ++++--
3 files changed, 154 insertions(+), 42 deletions(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index a9aa2e1c631d..c5cdad4b0590 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -393,74 +393,174 @@ static int pci_cxl_restore_state(struct pci_dev *pdev,
return cxl_restore_state(pdev, snapshot);
}
+static int cxl_reset_restore_cache_policy(struct pci_dev *pdev, int dvsec,
+ u16 saved_ctrl2);
+static int cxl_reset_cache_prepare(struct pci_dev *pdev, int dvsec, u16 cap,
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache);
+
/**
- * pci_cxl_reset_prepare() - allocate HDM state storage ahead of a bus reset
+ * pci_cxl_reset_prepare() - prepare HDM state and caches before a bus reset
* @pdev: device about to be reset
+ * @cachep: receives cache state owning the region quiesce on success
*
- * 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.
+ * Capture HDM state under the region quiesce, reserve its ranges, and complete
+ * host and device cache maintenance. Hold the quiesce through reset and
+ * cxl_restore_state_after_pci_reset(), which consumes *@cachep.
*
- * 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.
+ * Return: snapshot to be freed with kfree(), NULL when no cached HDM state is
+ * present, or an ERR_PTR() on failure. A cached device without a valid DVSEC
+ * snapshot is rejected because its cache-maintenance scope is unknown.
+ * Only a successful non-NULL snapshot returns with the region quiesce held
+ * and *@cachep set. The caller must pass both to
+ * cxl_restore_state_after_pci_reset() even if reset is never attempted.
+ * The snapshot remains caller-owned. All other returns leave the quiesce
+ * unheld and *@cachep NULL.
*/
-struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev,
+ struct cxl_pci_cache **cachep)
{
struct cxl_hdm_info *snapshot;
+ struct cxl_pci_cache *cache;
+ u16 dvsec, cap, command;
+ int rc;
device_lock_assert(&pdev->dev);
- snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
- if (IS_ERR(snapshot))
- return PTR_ERR(snapshot) == -ENXIO ? NULL : snapshot;
+ *cachep = NULL;
+ if (!pdev->hdm)
+ return NULL;
+ rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
+ if (rc == -ENOTTY)
+ return ERR_PTR(-ENODEV);
+ if (rc)
+ return ERR_PTR(rc);
/* Raw reset callers may not have saved the PCI state needed for restore. */
if (!pdev->state_saved) {
pci_err(pdev, "CXL bus reset requires saved PCI state\n");
- kfree(snapshot);
return ERR_PTR(-EINVAL);
}
+ cache = kzalloc_obj(*cache);
+ if (!cache)
+ return ERR_PTR(-ENOMEM);
+ cxl_region_quiesce_lock();
+ snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
+ if (IS_ERR(snapshot)) {
+ rc = PTR_ERR(snapshot);
+ goto unlock;
+ }
+ if (!snapshot->dvsec_ctrl_valid) {
+ rc = -ENXIO;
+ goto free_snapshot;
+ }
+ if (cxl_hdm_cache_dvsec_decode(&pdev->hdm) ||
+ (snapshot->dvsec_ctrl & PCI_DVSEC_CXL_CACHE_ENABLE &&
+ !(cap & PCI_DVSEC_CXL_CACHE_WBI_CAPABLE))) {
+ rc = -EOPNOTSUPP;
+ goto free_snapshot;
+ }
+
+ if (cap & PCI_DVSEC_CXL_CACHE_CAPABLE) {
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2,
+ &cache->saved_ctrl2);
+ if (rc || PCI_POSSIBLE_ERROR(cache->saved_ctrl2)) {
+ rc = rc ? pcibios_err_to_errno(rc) : -ENODEV;
+ goto free_snapshot;
+ }
+ }
+
+ rc = cxl_reset_cache_prepare(pdev, dvsec, cap, snapshot, cache);
+ if (rc) {
+ if (cache->cache_disabled)
+ goto finish;
+ pci_err(pdev, "failed to prepare CXL bus reset ranges: %d\n", rc);
+ cxl_hdm_quiesce_destroy(&cache->quiesce);
+ goto free_snapshot;
+ }
+ if (!pci_wait_for_pending_transaction(pdev)) {
+ rc = -ETIMEDOUT;
+ goto finish;
+ }
+ *cachep = cache;
return snapshot;
+
+finish:
+ rc = cxl_restore_state_after_pci_reset(pdev, snapshot, cache, false, rc,
+ &command);
+ kfree(snapshot);
+ return ERR_PTR(rc);
+free_snapshot:
+ kfree(snapshot);
+unlock:
+ cxl_region_quiesce_unlock();
+ kfree(cache);
+ return ERR_PTR(rc);
}
/**
- * cxl_restore_state_after_pci_reset() - restore CXL state after a bus reset
+ * cxl_restore_state_after_pci_reset() - finish a prepared reset and release its quiesce
* @pdev: device that was reset
* @snapshot: storage from pci_cxl_reset_prepare()
+ * @cache: quiesce state from pci_cxl_reset_prepare()
+ * @reset_attempted: reset helper was invoked and may have affected hardware
+ * @rc: preparation or PCI reset result
* @command: returns the saved PCI_COMMAND value to apply once the IOMMU is
* restored
*
- * 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.
+ * Restore state through pci_cxl_restore_state(), complete host invalidation,
+ * and restore the cache policy changed by planned-reset preparation. When
+ * @reset_attempted is false, undo preparation without changing the saved PCI
+ * command, unless cleanup itself fails. After an attempted reset, keep caching
+ * and the device disabled if state restoration or host invalidation fails.
+ * A reset-helper error does not prove that hardware was untouched.
+ * Unplanned recovery uses pci_cxl_restore_state() directly and owns its own
+ * quiesce and cache-maintenance sequencing.
*
* Bus mastering stays off, because the caller has not yet restored IOMMU
* translation and a DMA request now would hit the blocking domain. The caller
* calls cxl_restore_command_after_pci_reset() once the IOMMU is restored.
*
- * 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.
+ * The quiesce blocks region commit and teardown throughout reset. Complete
+ * host cache invalidation before PCI operation resumes, then release the
+ * quiesce and free @cache on every exit. @snapshot remains caller-owned.
*/
int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
- struct cxl_hdm_info *snapshot,
- u16 *command)
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache,
+ bool reset_attempted, int rc, u16 *command)
{
- int rc;
+ int rc2, policy_rc = 0;
+ *command = 0;
+ if (!cache)
+ return rc;
device_lock_assert(&pdev->dev);
- cxl_region_quiesce_lock();
-
- rc = cxl_hdm_cache_copy_decoders(&pdev->hdm, snapshot);
- if (!rc)
+ if (reset_attempted && !rc)
rc = pci_cxl_restore_state(pdev, snapshot, command);
- if (rc)
- cxl_reset_save_disabled_state(pdev);
+ /* Complete host invalidation before ending IOMMU exclusion. */
+ rc2 = cxl_hdm_quiesce_flush(&cache->quiesce);
+ if (rc2)
+ pci_err(pdev, "failed to invalidate CPU cache after CXL bus reset: %d\n", rc2);
+ /* Undo preparation on abort; enable caching after reset only if safe. */
+ if (cache->cache_disabled && (!reset_attempted || (!rc && !rc2)))
+ policy_rc = cxl_reset_restore_cache_policy(pdev, snapshot->dvsec,
+ cache->saved_ctrl2);
+ if (reset_attempted) {
+ rc = rc ?: rc2 ?: policy_rc;
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
+ } else {
+ /* An aborted reset alone must not poison the saved PCI command. */
+ if (rc2 || policy_rc)
+ cxl_reset_save_disabled_state(pdev);
+ rc = rc ?: rc2 ?: policy_rc;
+ }
+
+ cxl_hdm_quiesce_destroy(&cache->quiesce);
cxl_region_quiesce_unlock();
+ kfree(cache);
return rc;
}
@@ -594,7 +694,7 @@ static int cxl_reset_cache_prepare(struct pci_dev *pdev, int dvsec, u16 cap,
int rc;
rc = cxl_hdm_quiesce_prepare(&cache->quiesce, snapshot);
- if (rc)
+ if (rc || !(cap & PCI_DVSEC_CXL_CACHE_CAPABLE))
return rc;
/* Track an attempted change so partial failures still restore policy. */
@@ -731,7 +831,9 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
rc = __cxl_reset_execute(pdev, dvsec, cap, snapshot, &cache.quiesce,
&command);
/* Restore cache policy after any attempt to disable caching. */
- rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
+ rc2 = 0;
+ if (cache.cache_disabled)
+ rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
cxl_hdm_quiesce_destroy(&cache.quiesce);
if (rc)
return rc;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 1d4ba90a6976..b29a6a43249d 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5015,6 +5015,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 cxl_pci_cache *cache;
struct pci_dev *bridge;
u16 dvsec, reg, val;
u16 command;
@@ -5042,14 +5043,15 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (rc)
return -ENOTTY;
- snapshot = pci_cxl_reset_prepare(dev);
+ snapshot = pci_cxl_reset_prepare(dev, &cache);
if (IS_ERR(snapshot))
return PTR_ERR(snapshot);
rc = pci_dev_reset_iommu_prepare(dev);
if (rc) {
pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
- return rc;
+ return cxl_restore_state_after_pci_reset(dev, snapshot, cache,
+ false, rc, &command);
}
if (reg & PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR) {
@@ -5066,8 +5068,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, &command);
+ /* Reset-helper failure does not prove that no hardware reset occurred. */
+ rc = cxl_restore_state_after_pci_reset(dev, snapshot, cache, true, rc,
+ &command);
pci_dev_reset_iommu_done(dev);
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 2f819e1fbead..7f6b81cf734b 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -731,14 +731,17 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
struct cxl_hdm_info;
+struct cxl_pci_cache;
#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);
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev,
+ struct cxl_pci_cache **cachep);
int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
- struct cxl_hdm_info *snapshot,
- u16 *command);
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache,
+ bool reset_attempted, int rc, u16 *command);
int cxl_restore_command_after_pci_reset(struct pci_dev *pdev, u16 command);
#else
static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
@@ -748,16 +751,20 @@ 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)
+static inline struct cxl_hdm_info *
+pci_cxl_reset_prepare(struct pci_dev *pdev, struct cxl_pci_cache **cachep)
{
+ *cachep = NULL;
return NULL;
}
static inline int
cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
- struct cxl_hdm_info *snapshot, u16 *command)
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache,
+ bool reset_attempted, int rc, u16 *command)
{
- return 0;
+ return rc;
}
static inline int cxl_restore_command_after_pci_reset(struct pci_dev *pdev,
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v17 19/19] cxl: Add KUnit checks for HDM decoder unpacking
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (17 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 18/19] PCI/CXL: Flush host and device caches for " Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:31 ` [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Alison Schofield
19 siblings, 0 replies; 21+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 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: Li Ming, Joshua Machauer, 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() without a PCI device or HDM MMIO. Cover
range boundaries and overflow, an all-ones base, zero-size and uncommitted
decoders, and a locked host-only decoder with 12-way interleave.
Run the checks as a KUnit suite rather than during cxl_test module loading,
so a failure does not prevent unrelated CXL mock tests from running. An
uncommitted decoder has no meaningful target type, so do not check it.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/.kunitconfig | 4 ++
drivers/cxl/Kconfig | 10 +++
drivers/cxl/Makefile | 1 +
drivers/cxl/core/hdm_regs.c | 2 +-
drivers/cxl/tests/Makefile | 5 ++
drivers/cxl/tests/hdm_unpack_kunit.c | 91 ++++++++++++++++++++++++++++
6 files changed, 112 insertions(+), 1 deletion(-)
create mode 100644 drivers/cxl/.kunitconfig
create mode 100644 drivers/cxl/tests/Makefile
create mode 100644 drivers/cxl/tests/hdm_unpack_kunit.c
diff --git a/drivers/cxl/.kunitconfig b/drivers/cxl/.kunitconfig
new file mode 100644
index 000000000000..4fee684f5ff6
--- /dev/null
+++ b/drivers/cxl/.kunitconfig
@@ -0,0 +1,4 @@
+CONFIG_KUNIT=y
+CONFIG_PCI=y
+CONFIG_CXL_BUS=y
+CONFIG_CXL_KUNIT_TEST=y
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index e927e69a3f7b..7f70f6babd74 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -213,6 +213,16 @@ config CXL_REGION
If unsure say 'y'
+config CXL_KUNIT_TEST
+ tristate "KUnit tests for CXL" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Build the KUnit tests for the CXL subsystem. When built as modules,
+ each test suite is its own module.
+
+ If unsure, say N.
+
config CXL_REGION_INVALIDATION_TEST
bool "CXL: Region Cache Management Bypass (TEST)"
depends on CXL_REGION
diff --git a/drivers/cxl/Makefile b/drivers/cxl/Makefile
index 2caa90fa4bf2..d57fb0e9e7da 100644
--- a/drivers/cxl/Makefile
+++ b/drivers/cxl/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_CXL_ACPI) += cxl_acpi.o
obj-$(CONFIG_CXL_PMEM) += cxl_pmem.o
obj-$(CONFIG_CXL_MEM) += cxl_mem.o
obj-$(CONFIG_CXL_PCI) += cxl_pci.o
+obj-$(CONFIG_CXL_KUNIT_TEST) += tests/
cxl_port-y := port.o
cxl_acpi-y := acpi.o
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index 4f5e843cd141..98b37edf5590 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -188,7 +188,7 @@ int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
return 0;
}
-EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core,cxl_hdm_unpack_kunit");
int cxl_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
int id, u32 ctrl)
diff --git a/drivers/cxl/tests/Makefile b/drivers/cxl/tests/Makefile
new file mode 100644
index 000000000000..c29dccfa9b24
--- /dev/null
+++ b/drivers/cxl/tests/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+ccflags-y += -I$(srctree)/drivers/cxl
+
+obj-$(CONFIG_CXL_KUNIT_TEST) += cxl_hdm_unpack_kunit.o
+cxl_hdm_unpack_kunit-y := hdm_unpack_kunit.o
diff --git a/drivers/cxl/tests/hdm_unpack_kunit.c b/drivers/cxl/tests/hdm_unpack_kunit.c
new file mode 100644
index 000000000000..c1131e145757
--- /dev/null
+++ b/drivers/cxl/tests/hdm_unpack_kunit.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <kunit/test.h>
+#include <linux/bitfield.h>
+#include <linux/sizes.h>
+
+#include "cxl.h"
+#include "../core/core.h"
+
+static void cxl_hdm_unpack_test(struct kunit *test)
+{
+ 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 },
+ { "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);
+ KUNIT_EXPECT_EQ_MSG(test, rc, cases[i].rc, "%s", cases[i].name);
+ if (rc || rc != cases[i].rc)
+ continue;
+ size = cases[i].size;
+ if (!(cases[i].flags & CXL_DECODER_F_ENABLE))
+ size = 0;
+ KUNIT_EXPECT_EQ_MSG(test, config.id, 3, "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.hpa_range.start, cases[i].base,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, range_len(&config.hpa_range), size,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.flags, cases[i].flags,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.interleave_ways, cases[i].ways,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.interleave_granularity,
+ cases[i].granularity, "%s", cases[i].name);
+ /* An uncommitted decoder has no meaningful target type. */
+ if (cases[i].flags & CXL_DECODER_F_ENABLE)
+ KUNIT_EXPECT_EQ_MSG(test, config.target_type,
+ cases[i].target_type, "%s", cases[i].name);
+ }
+}
+
+static struct kunit_case cxl_hdm_test_cases[] = {
+ KUNIT_CASE(cxl_hdm_unpack_test),
+ {}
+};
+
+static struct kunit_suite cxl_hdm_test_suite = {
+ .name = "cxl-hdm-unpack",
+ .test_cases = cxl_hdm_test_cases,
+};
+
+kunit_test_suite(cxl_hdm_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for CXL HDM decoder unpacking");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (18 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 19/19] cxl: Add KUnit checks for HDM decoder unpacking Srirangan Madhavan
@ 2026-11-05 19:31 ` Alison Schofield
19 siblings, 0 replies; 21+ messages in thread
From: Alison Schofield @ 2026-11-05 19:31 UTC (permalink / raw)
To: Srirangan Madhavan
Cc: Bjorn Helgaas, Dave Jiang, Davidlohr Bueso, Ira Weiny,
Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel, Li Ming, Joshua Machauer, Alex Williamson, vsethi,
alwilliamson, Sai Yashwanth Reddy Kancherla, Vishal Aslot,
Manish Honap, Jiandi An, Richard Cheng, linux-tegra
On Thu, Nov 05, 2026 at 06:58:58PM +0000, Srirangan Madhavan wrote:
> Hi folks,
snip
>
> The KUnit suite checks HDM range boundaries, zero-size and uncommitted
> state, lock and target-type decoding, and 12-way interleave.
There is a KUnit branch available now to base on: for-7.5/cxl-kunit
https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git/
-- Alison
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-11-05 19:31 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-11-05 18:58 ` [PATCH v17 01/19] cxl: Clarify decoder interleave limit Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 03/19] cxl: Move decoder declarations to shared header Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 04/19] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 05/19] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 06/19] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 07/19] cxl: Share HDM decoder register unpacking Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 08/19] cxl: Move cxl_rwsem into built-in code Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 10/19] cxl: Build register block helpers into the kernel Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 16/19] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 18/19] PCI/CXL: Flush host and device caches for " Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 19/19] cxl: Add KUnit checks for HDM decoder unpacking Srirangan Madhavan
2026-11-05 19:31 ` [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Alison Schofield
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®