* [PATCH v16 01/18] cxl: Clarify decoder interleave limit
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:00 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 02/18] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
` (16 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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 related [flat|nested] 37+ messages in thread* [PATCH v16 02/18] cxl: Share CXL port upstream PCI device lookup
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-10-29 14:56 ` [PATCH v16 01/18] cxl: Clarify decoder interleave limit Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:07 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 03/18] cxl: Move decoder declarations to shared header Srirangan Madhavan
` (15 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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>
---
drivers/cxl/core/core.h | 1 +
drivers/cxl/core/pci.c | 16 ++--------------
drivers/cxl/core/port.c | 26 ++++++++++++++++++++++++++
3 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 35eaf636adc9..983d7690c3a5 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -157,6 +157,7 @@ long cxl_pci_get_latency(struct pci_dev *pdev);
int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c);
int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
struct access_coordinate *c);
+struct pci_dev *cxl_port_get_uport_pci_dev(struct cxl_port *port);
static inline struct device *port_to_host(struct cxl_port *port)
{
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 9d807c1a002c..c3c41efae5fd 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -564,26 +564,14 @@ static unsigned char cdat_checksum(void *buf, size_t size)
*/
void read_cdat_data(struct cxl_port *port)
{
- struct device *uport = port->uport_dev;
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(port);
struct device *dev = &port->dev;
struct pci_doe_mb *doe_mb;
- struct pci_dev *pdev = NULL;
- struct cxl_memdev *cxlmd;
struct cdat_doe_rsp *buf;
size_t table_length, length;
int rc;
- if (is_cxl_memdev(uport)) {
- struct device *host;
-
- cxlmd = to_cxl_memdev(uport);
- host = cxlmd->dev.parent;
- if (dev_is_pci(host))
- pdev = to_pci_dev(host);
- } else if (dev_is_pci(uport)) {
- pdev = to_pci_dev(uport);
- }
-
if (!pdev)
return;
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 625e4aa427db..5dc2815d82d8 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -34,6 +34,32 @@
static DEFINE_IDA(cxl_port_ida);
static DEFINE_XARRAY(cxl_root_buses);
+/**
+ * cxl_port_get_uport_pci_dev - get the PCI device for a port's upstream device
+ * @port: CXL port to map to a PCI device
+ *
+ * Return: A referenced PCI device, or NULL if the upstream device is not PCI.
+ * The caller must release the returned reference with pci_dev_put().
+ */
+struct pci_dev *cxl_port_get_uport_pci_dev(struct cxl_port *port)
+{
+ struct device *uport = port->uport_dev;
+ struct device *host;
+
+ if (is_cxl_memdev(uport)) {
+ struct cxl_memdev *cxlmd = to_cxl_memdev(uport);
+
+ host = cxlmd->dev.parent;
+ } else {
+ host = uport;
+ }
+
+ if (!host || !dev_is_pci(host))
+ return NULL;
+
+ return pci_dev_get(to_pci_dev(host));
+}
+
/*
* The terminal device in PCI is NULL and @platform_bus
* for platform devices (for cxl_test)
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH v16 02/18] cxl: Share CXL port upstream PCI device lookup
2026-10-29 14:56 ` [PATCH v16 02/18] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
@ 2026-10-29 15:07 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-10-29 15:07 UTC (permalink / raw)
To: Srirangan Madhavan, linux-cxl, linux-pci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] cxl: mix of __free() and goto cleanup in read_cdat_data()
--
commit e44dda37d2191c0dc43154c400e85f444104b3ba
Author: Srirangan Madhavan <smadhavan@nvidia.com>
cxl: Share CXL port upstream PCI device lookup
This commit factors the lookup of a CXL port's upstream PCI device out of
read_cdat_data() into cxl_port_get_uport_pci_dev(), returning a referenced
pci_dev. It also updates read_cdat_data() to release the PCI device reference
using __free(pci_dev_put).
> diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
> index 9d807c1a002ca..c3c41efae5fde 100644
> --- a/drivers/cxl/core/pci.c
> +++ b/drivers/cxl/core/pci.c
> @@ -564,26 +564,14 @@ static unsigned char cdat_checksum(void *buf, size_t size)
> */
> void read_cdat_data(struct cxl_port *port)
> {
> - struct device *uport = port->uport_dev;
> + struct pci_dev *pdev __free(pci_dev_put) =
> + cxl_port_get_uport_pci_dev(port);
[Severity: Low]
In drivers/cxl/core/pci.c, read_cdat_data() now uses __free(pci_dev_put) for
pdev, while the remainder of the function still uses goto err on failure of
devm_kzalloc(), cxl_cdat_read_table(), or cdat_checksum() to free buf:
buf = devm_kzalloc(dev, sizeof(*buf) + length, GFP_KERNEL);
if (!buf)
goto err;
table_length = length;
rc = cxl_cdat_read_table(dev, doe_mb, buf, &length);
if (rc)
goto err;
...
if (cdat_checksum(buf->data, length))
goto err;
...
err:
/* Don't leave table data allocated on error */
devm_kfree(dev, buf);
dev_err(dev, "Failed to read/validate CDAT.\n");
Per include/linux/cleanup.h, goto-based cleanup and __free() scope-based
cleanup helpers should not be mixed within the same function:
* Lastly, given that the benefit of cleanup helpers is removal of
* "goto", and that the "goto" statement can jump between scopes, the
* expectation is that usage of "goto" and cleanup helpers is never
* mixed in the same function. I.e. for a given routine, convert all
* resources that need a "goto" cleanup to scope-based cleanup, or
* convert none of them.
Should read_cdat_data() either convert buf cleanup to avoid the err goto
label as well, or avoid mixing __free() with goto-based error handling here?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=2
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v16 03/18] cxl: Move decoder declarations to shared header
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-10-29 14:56 ` [PATCH v16 01/18] cxl: Clarify decoder interleave limit Srirangan Madhavan
2026-10-29 14:56 ` [PATCH v16 02/18] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:03 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 04/18] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
` (14 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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 related [flat|nested] 37+ messages in thread* [PATCH v16 04/18] cxl: Embed decoder configuration in a standalone structure
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (2 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 03/18] cxl: Move decoder declarations to shared header Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:03 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 05/18] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
` (13 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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 c3c41efae5fd..33b21c8dcb21 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -231,10 +231,10 @@ static int dvsec_range_allowed(struct device *dev, const void *arg)
cxld = to_cxl_decoder(dev);
- if (!(cxld->flags & CXL_DECODER_F_RAM))
+ if (!(cxld->config.flags & CXL_DECODER_F_RAM))
return 0;
- return range_contains(&cxld->hpa_range, dev_range);
+ return range_contains(&cxld->config.hpa_range, dev_range);
}
static void disable_hdm(void *_cxlhdm)
@@ -668,12 +668,12 @@ static int __cxl_endpoint_decoder_reset_detected(struct device *dev, void *data)
return 0;
cxld = to_cxl_decoder(dev);
- if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)
return 0;
cxlhdm = dev_get_drvdata(&port->dev);
hdm = cxlhdm->regs.hdm_decoder;
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->config.id));
return !FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
}
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 5dc2815d82d8..84374873f3d0 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -129,7 +129,7 @@ static ssize_t start_show(struct device *dev, struct device_attribute *attr,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%#llx\n", cxld->hpa_range.start);
+ return sysfs_emit(buf, "%#llx\n", cxld->config.hpa_range.start);
}
static DEVICE_ATTR_ADMIN_RO(start);
@@ -138,7 +138,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%#llx\n", range_len(&cxld->hpa_range));
+ return sysfs_emit(buf, "%#llx\n", range_len(&cxld->config.hpa_range));
}
static DEVICE_ATTR_RO(size);
@@ -149,7 +149,7 @@ static ssize_t name##_show(struct device *dev, \
struct cxl_decoder *cxld = to_cxl_decoder(dev); \
\
return sysfs_emit(buf, "%s\n", \
- (cxld->flags & (flag)) ? "1" : "0"); \
+ (cxld->config.flags & (flag)) ? "1" : "0"); \
} \
static DEVICE_ATTR_RO(name)
@@ -164,7 +164,7 @@ static ssize_t target_type_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- switch (cxld->target_type) {
+ switch (cxld->config.target_type) {
case CXL_DECODER_DEVMEM:
return sysfs_emit(buf, "accelerator\n");
case CXL_DECODER_HOSTONLYMEM:
@@ -180,14 +180,14 @@ static ssize_t emit_target_list(struct cxl_switch_decoder *cxlsd, char *buf)
ssize_t offset = 0;
int i, rc = 0;
- for (i = 0; i < cxld->interleave_ways; i++) {
+ for (i = 0; i < cxld->config.interleave_ways; i++) {
struct cxl_dport *dport = cxlsd->target[i];
struct cxl_dport *next = NULL;
if (!dport)
break;
- if (i + 1 < cxld->interleave_ways)
+ if (i + 1 < cxld->config.interleave_ways)
next = cxlsd->target[i + 1];
rc = sysfs_emit_at(buf, offset, "%d%s", dport->port_id,
next ? "," : "");
@@ -314,7 +314,7 @@ static ssize_t interleave_granularity_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%d\n", cxld->interleave_granularity);
+ return sysfs_emit(buf, "%d\n", cxld->config.interleave_granularity);
}
static DEVICE_ATTR_RO(interleave_granularity);
@@ -324,7 +324,7 @@ static ssize_t interleave_ways_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%d\n", cxld->interleave_ways);
+ return sysfs_emit(buf, "%d\n", cxld->config.interleave_ways);
}
static DEVICE_ATTR_RO(interleave_ways);
@@ -368,14 +368,14 @@ static bool can_create_pmem(struct cxl_root_decoder *cxlrd)
{
unsigned long flags = CXL_DECODER_F_TYPE3 | CXL_DECODER_F_PMEM;
- return (cxlrd->cxlsd.cxld.flags & flags) == flags;
+ return (cxlrd->cxlsd.cxld.config.flags & flags) == flags;
}
static bool can_create_ram(struct cxl_root_decoder *cxlrd)
{
unsigned long flags = CXL_DECODER_F_TYPE3 | CXL_DECODER_F_RAM;
- return (cxlrd->cxlsd.cxld.flags & flags) == flags;
+ return (cxlrd->cxlsd.cxld.config.flags & flags) == flags;
}
static umode_t cxl_root_decoder_visible(struct kobject *kobj, struct attribute *a, int n)
@@ -450,7 +450,7 @@ static void __cxl_decoder_release(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- ida_free(&port->decoder_ida, cxld->id);
+ ida_free(&port->decoder_ida, cxld->config.id);
put_device(&port->dev);
}
@@ -1646,7 +1646,7 @@ static int update_decoder_targets(struct device *dev, void *data)
cxld = &cxlsd->cxld;
guard(rwsem_write)(&cxl_rwsem.region);
- for (i = 0; i < cxld->interleave_ways; i++) {
+ for (i = 0; i < cxld->config.interleave_ways; i++) {
if (cxld->target_map[i] == dport->port_id) {
cxlsd->target[i] = dport;
dev_dbg(dev, "dport%d found in target list, index %d\n",
@@ -1952,7 +1952,7 @@ static int decoder_populate_targets(struct cxl_switch_decoder *cxlsd,
return 0;
guard(rwsem_write)(&cxl_rwsem.region);
- for (i = 0; i < cxlsd->cxld.interleave_ways; i++) {
+ for (i = 0; i < cxlsd->cxld.config.interleave_ways; i++) {
struct cxl_dport *dport = find_dport(port, cxld->target_map[i]);
if (!dport) {
@@ -1988,7 +1988,7 @@ static int cxl_decoder_init(struct cxl_port *port, struct cxl_decoder *cxld)
/* need parent to stick around to release the id */
get_device(&port->dev);
- cxld->id = rc;
+ cxld->config.id = rc;
dev = &cxld->dev;
device_initialize(dev);
@@ -1998,10 +1998,10 @@ static int cxl_decoder_init(struct cxl_port *port, struct cxl_decoder *cxld)
dev->bus = &cxl_bus_type;
/* Pre initialize an "empty" decoder */
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = PAGE_SIZE;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->hpa_range = (struct range) {
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = PAGE_SIZE;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -2172,7 +2172,7 @@ int cxl_decoder_add_locked(struct cxl_decoder *cxld)
if (WARN_ON_ONCE(IS_ERR(cxld)))
return PTR_ERR(cxld);
- if (cxld->interleave_ways < 1)
+ if (cxld->config.interleave_ways < 1)
return -EINVAL;
dev = &cxld->dev;
@@ -2182,14 +2182,14 @@ int cxl_decoder_add_locked(struct cxl_decoder *cxld)
struct cxl_switch_decoder *cxlsd = to_cxl_switch_decoder(dev);
rc = decoder_populate_targets(cxlsd, port);
- if (rc && (cxld->flags & CXL_DECODER_F_ENABLE)) {
+ if (rc && (cxld->config.flags & CXL_DECODER_F_ENABLE)) {
dev_err(&port->dev,
"Failed to populate active decoder targets\n");
return rc;
}
}
- rc = dev_set_name(dev, "decoder%d.%d", port->id, cxld->id);
+ rc = dev_set_name(dev, "decoder%d.%d", port->id, cxld->config.id);
if (rc)
return rc;
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index bb51e67f25f5..8cfc06df3134 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -502,8 +502,8 @@ static int set_interleave_ways(struct cxl_region *cxlr, int val)
* Even for x3, x6, and x12 interleaves the region interleave must be a
* power of 2 multiple of the host bridge interleave.
*/
- if (!is_power_of_2(val / cxld->interleave_ways) ||
- (val % cxld->interleave_ways)) {
+ if (!is_power_of_2(val / cxld->config.interleave_ways) ||
+ (val % cxld->config.interleave_ways)) {
dev_dbg(&cxlr->dev, "invalid interleave: %d\n", val);
return -EINVAL;
}
@@ -580,7 +580,7 @@ static int set_interleave_granularity(struct cxl_region *cxlr, int val)
* with a granularity greater than the root interleave result in invalid
* DPA translations (invalid to support).
*/
- if (cxld->interleave_ways > 1 && val != cxld->interleave_granularity)
+ if (cxld->config.interleave_ways > 1 && val != cxld->config.interleave_granularity)
return -EINVAL;
lockdep_assert_held_write(&cxl_rwsem.region);
@@ -880,7 +880,7 @@ static int check_commit_order(struct device *dev, void *data)
* order shutdown has occurred, block further allocations until
* that is resolved
*/
- if (((cxld->flags & CXL_DECODER_F_ENABLE) == 0))
+ if (((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0))
return -EBUSY;
return 0;
}
@@ -896,7 +896,7 @@ static int match_free_decoder(struct device *dev, const void *data)
cxld = to_cxl_decoder(dev);
- if (cxld->id != port->commit_end + 1)
+ if (cxld->config.id != port->commit_end + 1)
return 0;
if (cxld->region) {
@@ -942,7 +942,7 @@ static int match_auto_decoder(struct device *dev, const void *data)
return 0;
cxld = to_cxl_decoder(dev);
- r = &cxld->hpa_range;
+ r = &cxld->config.hpa_range;
if (spa_maps_hpa(p, r))
return 1;
@@ -1003,10 +1003,10 @@ static bool auto_order_ok(struct cxl_port *port, struct cxl_region *cxlr_iter,
* starting address has the lesser id.
*/
dev_dbg(&cxld->dev, "check for HPA violation %s:%d < %s:%d\n",
- dev_name(&cxld->dev), cxld->id,
- dev_name(&cxld_iter->dev), cxld_iter->id);
+ dev_name(&cxld->dev), cxld->config.id,
+ dev_name(&cxld_iter->dev), cxld_iter->config.id);
- if (cxld_iter->id > cxld->id)
+ if (cxld_iter->config.id > cxld->config.id)
return true;
return false;
@@ -1137,11 +1137,11 @@ static int cxl_rr_assign_decoder(struct cxl_port *port, struct cxl_region *cxlr,
*/
dev_WARN_ONCE(&cxlr->dev,
port == cxled_to_port(cxled) &&
- cxld->target_type != cxlr->type,
+ cxld->config.target_type != cxlr->type,
"%s:%s mismatch decoder type %d -> %d\n",
dev_name(&cxled_to_memdev(cxled)->dev),
- dev_name(&cxld->dev), cxld->target_type, cxlr->type);
- cxld->target_type = cxlr->type;
+ dev_name(&cxld->dev), cxld->config.target_type, cxlr->type);
+ cxld->config.target_type = cxlr->type;
cxl_rr->decoder = cxld;
return 0;
}
@@ -1162,12 +1162,12 @@ static void cxl_region_setup_flags(struct cxl_region *cxlr,
set_bit(CXL_REGION_F_LOCK, &cxlr->flags);
}
- if (cxld->flags & CXL_DECODER_F_LOCK) {
+ if (cxld->config.flags & CXL_DECODER_F_LOCK) {
set_bit(CXL_REGION_F_LOCK, &cxlr->flags);
clear_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
}
- if (cxld->flags & CXL_DECODER_F_NORMALIZED_ADDRESSING)
+ if (cxld->config.flags & CXL_DECODER_F_NORMALIZED_ADDRESSING)
set_bit(CXL_REGION_F_NORMALIZED_ADDRESSING, &cxlr->flags);
}
@@ -1483,7 +1483,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
distance *= cxl_rr_iter->nr_targets;
iter = to_cxl_port(iter->dev.parent);
} while (!is_cxl_root(iter));
- distance *= cxlrd->cxlsd.cxld.interleave_ways;
+ distance *= cxlrd->cxlsd.cxld.config.interleave_ways;
for (i = 0; i < cxl_rr->nr_targets_set; i++)
if (ep->dport == cxlsd->target[i]) {
@@ -1505,7 +1505,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
* root IG != region IG.
*/
parent_ig = p->interleave_granularity;
- parent_iw = cxlrd->cxlsd.cxld.interleave_ways;
+ parent_iw = cxlrd->cxlsd.cxld.config.interleave_ways;
/*
* For purposes of address bit routing, use power-of-2 math for
* switch ports.
@@ -1518,8 +1518,8 @@ static int cxl_port_setup_targets(struct cxl_port *port,
parent_rr = cxl_rr_load(parent_port, cxlr);
parent_cxld = parent_rr->decoder;
- parent_ig = parent_cxld->interleave_granularity;
- parent_iw = parent_cxld->interleave_ways;
+ parent_ig = parent_cxld->config.interleave_granularity;
+ parent_iw = parent_cxld->config.interleave_ways;
}
rc = granularity_to_eig(parent_ig, &peig);
@@ -1575,10 +1575,10 @@ static int cxl_port_setup_targets(struct cxl_port *port,
}
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
- if (cxld->interleave_ways != iw ||
- (iw > 1 && cxld->interleave_granularity != ig) ||
- !spa_maps_hpa(p, &cxld->hpa_range) ||
- ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
+ if (cxld->config.interleave_ways != iw ||
+ (iw > 1 && cxld->config.interleave_granularity != ig) ||
+ !spa_maps_hpa(p, &cxld->config.hpa_range) ||
+ ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)) {
dev_err(&cxlr->dev,
"%s:%s %s expected iw: %d ig: %d %pr\n",
dev_name(port->uport_dev), dev_name(&port->dev),
@@ -1586,10 +1586,10 @@ static int cxl_port_setup_targets(struct cxl_port *port,
dev_err(&cxlr->dev,
"%s:%s %s got iw: %d ig: %d state: %s %#llx:%#llx\n",
dev_name(port->uport_dev), dev_name(&port->dev),
- __func__, cxld->interleave_ways,
- cxld->interleave_granularity,
- str_enabled_disabled(cxld->flags & CXL_DECODER_F_ENABLE),
- cxld->hpa_range.start, cxld->hpa_range.end);
+ __func__, cxld->config.interleave_ways,
+ cxld->config.interleave_granularity,
+ str_enabled_disabled(cxld->config.flags & CXL_DECODER_F_ENABLE),
+ cxld->config.hpa_range.start, cxld->config.hpa_range.end);
return -ENXIO;
}
} else {
@@ -1602,9 +1602,9 @@ static int cxl_port_setup_targets(struct cxl_port *port,
return rc;
}
- cxld->interleave_ways = iw;
- cxld->interleave_granularity = ig;
- cxld->hpa_range = (struct range) {
+ cxld->config.interleave_ways = iw;
+ cxld->config.interleave_granularity = ig;
+ cxld->config.hpa_range = (struct range) {
.start = p->res->start,
.end = p->res->end,
};
@@ -1657,7 +1657,7 @@ static void cxl_port_reset_targets(struct cxl_port *port,
cxl_rr->nr_targets_set = 0;
cxld = cxl_rr->decoder;
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -1804,7 +1804,7 @@ static int cxl_region_attach_position(struct cxl_region *cxlr,
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_switch_decoder *cxlsd = &cxlrd->cxlsd;
struct cxl_decoder *cxld = &cxlsd->cxld;
- int iw = cxld->interleave_ways;
+ int iw = cxld->config.interleave_ways;
struct cxl_port *iter;
int rc;
@@ -1902,7 +1902,7 @@ static int match_switch_decoder_by_range(struct device *dev,
return 0;
cxlsd = to_cxl_switch_decoder(dev);
- r1 = &cxlsd->cxld.hpa_range;
+ r1 = &cxlsd->cxld.config.hpa_range;
if (is_root_decoder(dev))
return range_contains(r1, r2);
@@ -1929,7 +1929,7 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
return rc;
}
cxlsd = to_cxl_switch_decoder(dev);
- *ways = cxlsd->cxld.interleave_ways;
+ *ways = cxlsd->cxld.config.interleave_ways;
for (int i = 0; i < *ways; i++) {
if (cxlsd->target[i] == port->parent_dport) {
@@ -2105,10 +2105,10 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
- if (cxled->cxld.target_type != cxlr->type) {
+ if (cxled->cxld.config.target_type != cxlr->type) {
dev_dbg(&cxlr->dev, "%s:%s type mismatch: %d vs %d\n",
dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
- cxled->cxld.target_type, cxlr->type);
+ cxled->cxld.config.target_type, cxlr->type);
return -ENXIO;
}
@@ -2194,9 +2194,9 @@ static int cxl_region_attach(struct cxl_region *cxlr,
cxl_region_shared_upstream_bandwidth_update(cxlr);
}
- cxled->cxld.interleave_ways = p->interleave_ways;
- cxled->cxld.interleave_granularity = p->interleave_granularity;
- cxled->cxld.hpa_range = (struct range) {
+ cxled->cxld.config.interleave_ways = p->interleave_ways;
+ cxled->cxld.config.interleave_granularity = p->interleave_granularity;
+ cxled->cxld.config.hpa_range = (struct range) {
.start = p->res->start,
.end = p->res->end,
};
@@ -2320,7 +2320,7 @@ __cxl_decoder_detach(struct cxl_region *cxlr,
}
p->targets[cxled->pos] = NULL;
p->nr_targets--;
- cxled->cxld.hpa_range = (struct range) {
+ cxled->cxld.config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -2992,7 +2992,7 @@ static int poison_by_decoder(struct device *dev, void *arg)
}
/* Iterate until commit_end is reached */
- if (cxled->cxld.id == ctx->port->commit_end) {
+ if (cxled->cxld.config.id == ctx->port->commit_end) {
ctx->offset = cxled->dpa_res->end + 1;
ctx->part = cxled->part;
return 1;
@@ -3315,10 +3315,10 @@ static u64 restore_parent(u64 addr, u64 pos, u64 gran, u64 ways)
static u64 unaligned_dpa_to_hpa(struct cxl_decoder *cxld,
struct cxl_region_params *p, int pos, u64 dpa)
{
- int ways_port = p->interleave_ways / cxld->interleave_ways;
+ int ways_port = p->interleave_ways / cxld->config.interleave_ways;
int gran_port = p->interleave_granularity;
- int gran_hb = cxld->interleave_granularity;
- int ways_hb = cxld->interleave_ways;
+ int gran_hb = cxld->config.interleave_granularity;
+ int ways_hb = cxld->config.interleave_ways;
int pos_port, pos_hb, gran_shift;
u64 hpa_port = 0;
@@ -3365,7 +3365,7 @@ static bool region_is_unaligned_mod3(struct cxl_region *cxlr)
struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
- int hbiw = cxld->interleave_ways;
+ int hbiw = cxld->config.interleave_ways;
u64 rem;
if (is_power_of_2(hbiw))
@@ -3480,8 +3480,8 @@ static int unaligned_region_offset_to_dpa_result(struct cxl_region *cxlr,
* a dpa_offset independent of the target device and then enumerate
* and test that dpa_offset against each candidate endpoint decoder.
*/
- gran = cxld->interleave_granularity;
- interleave_width = gran * cxld->interleave_ways;
+ gran = cxld->config.interleave_granularity;
+ interleave_width = gran * cxld->config.interleave_ways;
interleave_index = div64_u64(offset, interleave_width);
gran_offset = do_div(tmp, gran);
@@ -3581,7 +3581,7 @@ static int match_root_decoder(struct device *dev, const void *data)
return 0;
cxlrd = to_cxl_root_decoder(dev);
- r1 = &cxlrd->cxlsd.cxld.hpa_range;
+ r1 = &cxlrd->cxlsd.cxld.config.hpa_range;
return range_contains(r1, r2);
}
@@ -3786,7 +3786,7 @@ static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd,
do {
cxlr = __create_region(cxlrd, cxlds->part[part].mode,
atomic_read(&cxlrd->region_id),
- cxled->cxld.target_type);
+ cxled->cxld.config.target_type);
} while (IS_ERR(cxlr) && PTR_ERR(cxlr) == -EBUSY);
if (IS_ERR(cxlr)) {
@@ -3829,9 +3829,9 @@ int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
ctx = (struct cxl_region_context) {
.cxled = cxled,
- .hpa_range = cxled->cxld.hpa_range,
- .interleave_ways = cxled->cxld.interleave_ways,
- .interleave_granularity = cxled->cxld.interleave_granularity,
+ .hpa_range = cxled->cxld.config.hpa_range,
+ .interleave_ways = cxled->cxld.config.interleave_ways,
+ .interleave_granularity = cxled->cxld.config.interleave_granularity,
};
struct cxl_root_decoder *cxlrd __free(put_cxl_root_decoder) =
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 44ff3334648c..688350c7c5fb 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -281,7 +281,7 @@ enum cxl_decoder_state {
* struct cxl_endpoint_decoder - Endpoint / SPA to DPA decoder
* @cxld: base cxl_decoder_object
* @dpa_res: actively claimed DPA span of this decoder
- * @skip: offset into @dpa_res where @cxld.hpa_range maps
+ * @skip: offset into @dpa_res where @cxld.config.hpa_range maps
* @state: autodiscovery state
* @part: partition index this decoder maps
* @pos: interleave position in @cxld.region
diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c
index c7c91e8dc51d..9a0cbcd230b2 100644
--- a/drivers/cxl/pci.c
+++ b/drivers/cxl/pci.c
@@ -966,7 +966,7 @@ static int cxl_endpoint_decoder_clear_reset_flags(struct device *dev, void *data
return 0;
cxled = to_cxl_endpoint_decoder(dev);
- cxled->cxld.flags &= ~CXL_DECODER_F_RESET_MASK;
+ cxled->cxld.config.flags &= ~CXL_DECODER_F_RESET_MASK;
return 0;
}
diff --git a/drivers/cxl/port.c b/drivers/cxl/port.c
index c12fd0b89883..b369d3e3876d 100644
--- a/drivers/cxl/port.c
+++ b/drivers/cxl/port.c
@@ -40,7 +40,7 @@ static int discover_region(struct device *dev, void *unused)
return 0;
cxled = to_cxl_endpoint_decoder(dev);
- if ((cxled->cxld.flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxled->cxld.config.flags & CXL_DECODER_F_ENABLE) == 0)
return 0;
if (cxled->state != CXL_DECODER_STATE_AUTO)
@@ -56,7 +56,7 @@ static int discover_region(struct device *dev, void *unused)
rc = cxl_add_to_region(cxled);
if (rc)
dev_dbg(dev, "failed to add to region: %#llx-%#llx\n",
- cxled->cxld.hpa_range.start, cxled->cxld.hpa_range.end);
+ cxled->cxld.config.hpa_range.start, cxled->cxld.config.hpa_range.end);
return 0;
}
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 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 related [flat|nested] 37+ messages in thread* [PATCH v16 05/18] cxl: Introduce endpoint HDM decoder settings
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (3 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 04/18] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:03 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 06/18] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
` (12 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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 related [flat|nested] 37+ messages in thread* [PATCH v16 06/18] cxl: Move HDM decoder helpers to built-in code
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (4 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 05/18] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:03 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 07/18] cxl: Share HDM decoder register unpacking Srirangan Madhavan
` (11 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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>
---
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 related [flat|nested] 37+ messages in thread* [PATCH v16 07/18] cxl: Share HDM decoder register unpacking
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (5 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 06/18] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:09 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 08/18] cxl: Move cxl_rwsem into built-in code Srirangan Madhavan
` (10 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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
---
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..684a17e9a601 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;
+ if (size && check_add_overflow(base, size - 1, &end))
+ return -ENXIO;
+ if (!size)
+ end = base - 1;
+
+ hpa_range = (struct range) {
+ .start = base,
+ .end = end,
+ };
+
+ if (committed) {
+ flags |= CXL_DECODER_F_ENABLE;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
+ flags |= CXL_DECODER_F_LOCK;
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
+ target_type = CXL_DECODER_HOSTONLYMEM;
+ else
+ target_type = CXL_DECODER_DEVMEM;
+ }
+
+ rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
+ &interleave_ways);
+ if (rc)
+ return rc;
+ rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
+ &interleave_granularity);
+ if (rc)
+ return rc;
+
+ *config = (struct cxl_decoder_config) {
+ .id = id,
+ .hpa_range = hpa_range,
+ .interleave_ways = interleave_ways,
+ .interleave_granularity = interleave_granularity,
+ .target_type = target_type,
+ .flags = flags,
+ };
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
+
+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 related [flat|nested] 37+ messages in thread* [PATCH v16 08/18] cxl: Move cxl_rwsem into built-in code
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (6 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 07/18] cxl: Share HDM decoder register unpacking Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:04 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 09/18] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
` (9 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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>
---
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 related [flat|nested] 37+ messages in thread* [PATCH v16 09/18] cxl: Refresh cached PCI HDM decoder settings
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (7 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 08/18] cxl: Move cxl_rwsem into built-in code Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:08 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 10/18] cxl: Build register block helpers into the kernel Srirangan Madhavan
` (8 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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
---
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 33b21c8dcb21..e2034a2179b4 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 related [flat|nested] 37+ messages in thread* Re: [PATCH v16 09/18] cxl: Refresh cached PCI HDM decoder settings
2026-10-29 14:56 ` [PATCH v16 09/18] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
@ 2026-10-29 15:08 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-10-29 15:08 UTC (permalink / raw)
To: Srirangan Madhavan, linux-pci
> 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>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=9
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v16 10/18] cxl: Build register block helpers into the kernel
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (8 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 09/18] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:07 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 11/18] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
` (7 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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>
---
drivers/cxl/core/Makefile | 3 +--
drivers/cxl/core/regs.c | 9 +++++++++
drivers/cxl/cxl.h | 3 ---
include/cxl/cxl.h | 6 ++++++
include/cxl/pci.h | 13 +++++++++++++
tools/testing/cxl/Kbuild | 1 -
6 files changed, 29 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..206acf5845e1 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;
@@ -168,6 +173,7 @@ struct cxl_register_map {
};
};
+
/**
* struct cxl_dpa_perf - DPA performance property entry
* @dpa_range: range for DPA address
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 related [flat|nested] 37+ messages in thread* [PATCH v16 11/18] PCI/CXL: Cache endpoint HDM state during PCI enumeration
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (9 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 10/18] cxl: Build register block helpers into the kernel Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:29 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 12/18] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
` (6 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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. Protect cache
publication and updates with the DPA lock.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
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 | 64 +++++++++-
drivers/cxl/core/pci.c | 22 ++--
drivers/pci/Makefile | 1 +
drivers/pci/bus.c | 1 +
drivers/pci/cxl.c | 221 ++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 8 ++
drivers/pci/probe.c | 1 +
include/cxl/hdm.h | 12 ++
include/uapi/linux/pci_regs.h | 2 +
11 files changed, 418 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 684a17e9a601..9409beb26478 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..1140006de50d 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -1,21 +1,45 @@
// 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");
+/* A published cache is usable only if its HDM register block was mapped. */
+bool cxl_hdm_cache_present(struct cxl_hdm_info *const *slot)
+{
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+
+ return *slot && (*slot)->hdm_size;
+}
+
+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 +94,41 @@ 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_publish() - publish HDM state read during enumeration
+ * @slot: cache pointer owned by the device, for example &pci_dev->hdm
+ * @info: state to publish
+ *
+ * Ownership of @info passes to @slot, or @info is freed if another caller
+ * published first.
+ */
+void cxl_hdm_cache_publish(struct cxl_hdm_info **slot,
+ struct cxl_hdm_info *info)
+{
+ struct cxl_hdm_info *new __free(kfree) = info;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ /* Another initializer may have published while we read MMIO. */
+ if (!*slot)
+ *slot = no_free_ptr(new);
+}
+
+/**
+ * cxl_hdm_cache_release() - unpublish and free cached HDM state
+ * @slot: cache pointer owned by the device
+ *
+ * Take the DPA write lock to exclude readers while clearing @slot, then
+ * free the unpublished cache after releasing the lock.
+ */
+void cxl_hdm_cache_release(struct cxl_hdm_info **slot)
+{
+ struct cxl_hdm_info *info;
+
+ scoped_guard(rwsem_write, &cxl_rwsem.dpa) {
+ info = *slot;
+ /* Unpublish before freeing so readers cannot see stale state. */
+ *slot = NULL;
+ }
+ kfree(info);
+}
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index e2034a2179b4..44fa9d5d1a05 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -209,22 +209,24 @@ 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);
- 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;
+ cxl_hdm_cache_set_dvsec_ctrl(pdev, ctrl);
+ return ret;
}
static void clear_mem_enable(void *cxlds)
@@ -314,7 +316,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..b992f71d964a
--- /dev/null
+++ b/drivers/pci/cxl.c
@@ -0,0 +1,221 @@
+// 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 (cxl_hdm_cache_present(&pdev->hdm))
+ return 0;
+
+ rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
+ if (rc == -ENOTTY)
+ return 0;
+ if (rc)
+ return rc;
+ if (!(cap & PCI_DVSEC_CXL_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);
+
+ /* Another initializer may have published while we read MMIO. */
+ cxl_hdm_cache_publish(&pdev->hdm, read_info);
+
+ return 0;
+}
+
+void pci_cxl_hdm_cache_init(struct pci_dev *pdev)
+{
+ int rc;
+
+ rc = __pci_cxl_hdm_cache_init(pdev);
+ if (rc)
+ pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
+}
+
+void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
+{
+ cxl_hdm_cache_release(&pdev->hdm);
+}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index ba3c3fddddc2..9e218f236e66 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -730,6 +730,14 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
+#ifdef CONFIG_CXL_RESET
+void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
+void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
+#else
+static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
+static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev) { }
+#endif
+
#ifdef CONFIG_PCI_NPEM
void pci_npem_create(struct pci_dev *dev);
void pci_npem_remove(struct pci_dev *dev);
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 27008e2ea5af..cb52357344ed 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2484,6 +2484,7 @@ static void pci_release_dev(struct device *dev)
struct pci_dev *pci_dev;
pci_dev = to_pci_dev(dev);
+ pci_cxl_hdm_cache_release(pci_dev);
pci_release_capabilities(pci_dev);
pci_release_of_node(pci_dev);
pcibios_release_device(pci_dev);
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 06e9aa4479c2..cfb55b10f8ac 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,24 @@ struct cxl_hdm_info {
resource_size_t hdm_offset;
resource_size_t hdm_size;
u32 global_ctrl;
+ u16 dvsec;
u16 dvsec_ctrl;
bool dvsec_ctrl_valid;
struct cxl_endpoint_decoder_settings settings[] __counted_by(decoder_count);
};
+struct device;
+int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
+ resource_size_t size);
+int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
+ struct cxl_hdm_info *info);
+bool cxl_hdm_cache_present(struct cxl_hdm_info *const *slot);
+void cxl_hdm_cache_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_publish(struct cxl_hdm_info **slot,
+ struct cxl_hdm_info *info);
+void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
#endif /* __CXL_HDM_H__ */
diff --git a/include/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 related [flat|nested] 37+ messages in thread* Re: [PATCH v16 11/18] PCI/CXL: Cache endpoint HDM state during PCI enumeration
2026-10-29 14:56 ` [PATCH v16 11/18] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
@ 2026-10-29 15:29 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-10-29 15:29 UTC (permalink / raw)
To: Srirangan Madhavan, linux-cxl, linux-pci
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] PCI/CXL: out-of-bounds MMIO read in cxl_pci_hdm_map_regs() before bounds check
- [High] PCI/CXL: pci_cxl_hdm_cache_init() runs before BAR assignment and bridge enable
- [Medium] cxl/pci: unchecked PCI_POSSIBLE_ERROR() in cxl_set_mem_enable() corrupts cached dvsec_ctrl
- [Medium] cxl/pci: disable_hdm() leaves stale CXL_HDM_DECODER_ENABLE in cached global_ctrl
--
commit a7ebc7643586c3c5e8b6adb597a5c7e1384802d3
Author: Srirangan Madhavan <smadhavan@nvidia.com>
PCI/CXL: Cache endpoint HDM state during PCI enumeration
This commit caches endpoint HDM decoder and Device DVSEC state in
pci_dev->hdm during PCI enumeration so that CXL reset paths can restore
hardware state without a bound CXL driver. It also hooks DVSEC control
updates in the CXL core to keep the cached DVSEC control value synchronized.
> diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
> index e2034a2179b48..44fa9d5d1a052 100644
> --- a/drivers/cxl/core/pci.c
> +++ b/drivers/cxl/core/pci.c
> @@ -209,22 +209,24 @@ 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);
>
> - 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;
> + cxl_hdm_cache_set_dvsec_ctrl(pdev, ctrl);
[Severity: Medium]
What happens in cxl_set_mem_enable() if the device is undergoing a reset or
the link is down when pci_read_config_word() reads PCI_DVSEC_CXL_CTRL?
On root complexes where config reads to an unresponsive device return
PCIBIOS_SUCCESSFUL with ctrl == 0xffff, cxl_set_mem_enable() does not check
PCI_POSSIBLE_ERROR(ctrl). When called from clear_mem_enable() during endpoint
port teardown (which is not serialized by pci_dev_lock(pdev)) or from
devm_cxl_enable_mem(), ctrl (0xffff) is masked to 0xfffb (or kept as 0xffff),
written back to PCI_DVSEC_CXL_CTRL (including RW1C Viral_Status,
Viral_Enable, Cache_Enable, and reserved bits), and then stored into
pdev->hdm->dvsec_ctrl via cxl_hdm_cache_set_dvsec_ctrl().
Unlike cxl_hdm_cache_refresh_decoder(), which checks global_ctrl != U32_MAX
to preserve the last good value when the device does not respond,
cxl_hdm_cache_set_dvsec_ctrl() unconditionally overwrites
pdev->hdm->dvsec_ctrl, permanently clobbering the cached DVSEC control state
for subsequent resets. Should cxl_set_mem_enable() check
PCI_POSSIBLE_ERROR(ctrl) before modifying and caching ctrl?
[Severity: Medium]
Also, while cxl_set_mem_enable() and clear_mem_enable() keep
pdev->hdm->dvsec_ctrl updated on both enable and teardown, does disable_hdm()
leave a stale CXL_HDM_DECODER_ENABLE bit in pdev->hdm->global_ctrl?
When the CXL driver binds and sets up endpoint decoders:
devm_cxl_endpoint_decoders_setup()
cxl_hdm_decode_init()
devm_cxl_enable_hdm()
devm_cxl_enable_mem()
cxl_set_mem_enable()
cxl_hdm_cache_set_dvsec_ctrl()
devm_cxl_enumerate_decoders()
cxl_hdm_refresh_decoder()
cxl_hdm_cache_refresh_decoder()
info->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
cxl_hdm_cache_refresh_decoder() latches CXL_HDM_DECODER_ENABLE into
pdev->hdm->global_ctrl. When the endpoint port driver later unbinds (or fails
probe after refreshing a decoder), devres runs clear_mem_enable(), which
clears PCI_DVSEC_CXL_MEM_ENABLE in pdev->hdm->dvsec_ctrl, and then runs
disable_hdm():
drivers/cxl/core/pci.c:disable_hdm() {
global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
writel(global_ctrl & ~CXL_HDM_DECODER_ENABLE,
hdm + CXL_HDM_DECODER_CTRL_OFFSET);
}
Because disable_hdm() does not update pdev->hdm->global_ctrl, a subsequent
reset while unbound will restore CXL_HDM_DECODER_ENABLE in hardware while
PCI_DVSEC_CXL_MEM_ENABLE is disabled, and a later CXL driver re-bind in
cxl_hdm_decode_init() will see CXL_HDM_DECODER_ENABLE already set and skip
devm_cxl_enable_hdm(). Should disable_hdm() (and devm_cxl_enable_hdm()) also
update pdev->hdm->global_ctrl?
[ ... ]
> diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
> index 655ed53436d3e..aad567fb7c560 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);
[Severity: High]
Can pci_cxl_hdm_cache_init() run here before BAR resources are validated and
assigned, or while upstream bridges still have memory decoding disabled?
On x86 ACPI boot, acpi_init() (subsys_initcall) enumerates the root bus and
calls pci_bus_add_devices() -> pci_bus_add_device() before pci_subsys_init()
runs pcibios_resource_survey() and before pcibios_assign_resources()
(fs_initcall) assigns unassigned resources:
acpi_init()
acpi_pci_root_add()
pci_bus_add_devices()
pci_bus_add_device()
pci_cxl_hdm_cache_init()
In drivers/pci/cxl.c, cxl_pci_bar_assigned() checks res->start and res->end
without checking res->parent:
drivers/pci/cxl.c:cxl_pci_bar_assigned() {
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;
}
And cxl_pci_hdm_read_info() temporarily sets PCI_COMMAND_MEMORY directly on
pdev without enabling upstream bridges:
drivers/pci/cxl.c:cxl_pci_hdm_read_info() {
if (!(command & PCI_COMMAND_MEMORY)) {
rc = pci_write_config_word(pdev, PCI_COMMAND,
command | PCI_COMMAND_MEMORY);
...
}
}
This appears to have two consequences:
1. If a disabled device has an unclaimed BIOS BAR address that conflicts with
another resource, enabling PCI_COMMAND_MEMORY before
pcibios_resource_survey() claims and validates resources can activate the
conflicting BAR decoding.
2. If the endpoint BAR or an upstream bridge window is assigned later during
pcibios_assign_resources(), or if an upstream PCI-to-PCI bridge does not
have PCI_COMMAND_MEMORY enabled yet, pci_cxl_hdm_cache_init() fails and
leaves pdev->hdm permanently NULL.
> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> new file mode 100644
> index 0000000000000..b992f71d964a8
> --- /dev/null
> +++ b/drivers/pci/cxl.c
[ ... ]
> +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);
[Severity: High]
Does cxl_setup_regs(map) perform MMIO reads past map->max_size before the
bounds check in cxl_pci_hdm_map_regs() is reached?
In __pci_cxl_hdm_cache_init(), cxl_find_regblock() populates map via
cxl_decode_regblock(), which only checks offset > pci_resource_len(pdev, bar)
and sets map->max_size = pci_resource_len(pdev, bar) - offset.
When cxl_pci_hdm_map_regs() calls cxl_setup_regs(map):
cxl_pci_hdm_map_regs()
cxl_setup_regs()
cxl_map_regblock()
map->base = ioremap(map->resource, map->max_size);
cxl_probe_regs()
cxl_probe_component_regs()
cxl_probe_component_regs() unconditionally adds CXL_CM_OFFSET (0x1000) to
map->base, reads CXL_CM_CAP_HDR_OFFSET, and then reads each capability header
at base + offset (which is map->base + hdm_map->offset) without checking
map->max_size.
If a device's remaining BAR length (map->max_size) is smaller than
CXL_CM_OFFSET + sizeof(u32) or smaller than hdm_map->offset + sizeof(u32),
won't cxl_probe_component_regs() fault on the unmapped ioremap guard page
before cxl_pci_hdm_map_regs() checks hdm_map->offset against map->max_size?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=11
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v16 12/18] PCI/CXL: Add CXL Device Reset sequencing
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (10 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 11/18] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:05 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 13/18] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
` (5 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Implement CXL Device Reset for eligible Type 2 devices: disable CXL.cache,
perform supported writeback, drain transactions, prepare the IOMMU,
initiate reset with Memory Clear enabled, and wait for completion.
Observe the 100 ms post-reset quiet period before polling Status2, and
treat an all-ones read as not ready. Treat Control2 command bits as
transient state, restore cache policy on exit, preserve the primary error,
and abort when IOMMU preparation fails.
Reuse Device DVSEC discovery from cache initialization and keep the
reset entry point declaration private to PCI core.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=04f8f84816d724f8b47f4d47a77916ac37c63a49
---
drivers/pci/cxl.c | 245 +++++++++++++++++++++++++++++++++-
drivers/pci/pci.h | 5 +
include/uapi/linux/pci_regs.h | 12 ++
3 files changed, 261 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index b992f71d964a..bfc31f821d01 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>
@@ -219,3 +223,242 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
{
cxl_hdm_cache_release(&pdev->hdm);
}
+
+/*
+ * CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
+ * Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
+ * initiating CXL Reset, then limits software to CXL Status2 access until
+ * reset completion, timeout, or error.
+ */
+#define CXL_RESET_RRS_WAIT_MS 100
+#define CXL_RESET_STATUS_POLL_MS 20
+#define CXL_RESET_MAX_WAIT_MS 20000
+static const u32 cxl_reset_timeout_ms[] = {
+ 10, 100, 1000, 10000, 100000,
+};
+
+/*
+ * 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)) {
+ pci_warn(pdev,
+ "unknown CXL reset timeout encoding %d; using %u ms\n",
+ idx, CXL_RESET_MAX_WAIT_MS);
+ idx = ARRAY_SIZE(cxl_reset_timeout_ms) - 1;
+ }
+
+ /* Allow one retry after the mandatory 100 ms quiet period. */
+ timeout_ms = clamp_t(u32, cxl_reset_timeout_ms[idx],
+ CXL_RESET_RRS_WAIT_MS + CXL_RESET_STATUS_POLL_MS,
+ CXL_RESET_MAX_WAIT_MS);
+ 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 related [flat|nested] 37+ messages in thread* Re: [PATCH v16 12/18] PCI/CXL: Add CXL Device Reset sequencing
2026-10-29 14:56 ` [PATCH v16 12/18] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
@ 2026-10-29 15:05 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-10-29 15:05 UTC (permalink / raw)
To: Srirangan Madhavan, linux-pci
> Implement CXL Device Reset for eligible Type 2 devices: disable CXL.cache,
> perform supported writeback, drain transactions, prepare the IOMMU,
> initiate reset with Memory Clear enabled, and wait for completion.
>
> Observe the 100 ms post-reset quiet period before polling Status2, and
> treat an all-ones read as not ready. Treat Control2 command bits as
> transient state, restore cache policy on exit, preserve the primary error,
> and abort when IOMMU preparation fails.
>
> Reuse Device DVSEC discovery from cache initialization and keep the
> reset entry point declaration private to PCI core.
>
> Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=12
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v16 13/18] cxl: Validate and synchronize HDM ranges around reset
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (11 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 12/18] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:25 ` sashiko-bot
2026-10-29 14:56 ` [PATCH v16 14/18] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
` (4 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Refuse reset unless enabled system-physical HDM ranges can be reserved
exclusively and CPU-cache invalidation is available. Invalidate before
reset and again before ending IOMMU exclusion, holding range reservations
until the second invalidation completes. A later patch places state
restoration before the second invalidation.
Reject normalized-addressing decoders because their cached ranges are not
system physical addresses. Ignore zero-size decoders because they map no
address range. Refuse reset when the device decodes memory through its
DVSEC ranges instead of HDM decoders, since the cache holds no range to
reserve for that memory.
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 is
capped at 20 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
---
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 | 245 ++++++++++++++++++++++++++++++++++-
drivers/pci/cxl.c | 86 ++++++++++--
include/cxl/hdm.h | 44 ++++++-
4 files changed, 367 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 1140006de50d..cb7ec0f86ab1 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),
@@ -132,3 +139,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/pci/cxl.c b/drivers/pci/cxl.c
index bfc31f821d01..4a8969145f8b 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)
{
@@ -331,6 +339,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;
@@ -386,7 +411,8 @@ static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
PCI_DVSEC_CXL_RST_MEM_CLR_EN);
}
-static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
+ struct cxl_hdm_quiesce *q)
{
int rc, rc2;
@@ -411,29 +437,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;
@@ -460,5 +518,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 (!cxl_hdm_cache_present(&pdev->hdm))
+ return -ENOTTY;
+
+ /* Memory decoded through DVSEC ranges has no HPA ranges to quiesce. */
+ if (cxl_hdm_cache_dvsec_decode(&pdev->hdm))
+ return -ENOTTY;
+
+ if (!cpu_cache_has_invalidate_memregion() &&
+ !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 cfb55b10f8ac..7919d872a34d 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);
@@ -59,5 +62,44 @@ int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count);
void cxl_hdm_cache_publish(struct cxl_hdm_info **slot,
struct cxl_hdm_info *info);
void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
+bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot);
+struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot);
+
+/*
+ * Region quiesce blocks CXL region commit and teardown, and with them decoder
+ * reprogramming, while reset handling captures or restores HDM decoder state.
+ */
+token_context_lock(cxl_region_quiesce);
+
+/**
+ * struct cxl_hdm_range - HPA range held exclusively across a reset
+ * @list: entry in &cxl_hdm_quiesce.ranges
+ * @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);
+void cxl_region_quiesce_unlock(void);
+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 related [flat|nested] 37+ messages in thread* Re: [PATCH v16 13/18] cxl: Validate and synchronize HDM ranges around reset
2026-10-29 14:56 ` [PATCH v16 13/18] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
@ 2026-10-29 15:25 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-10-29 15:25 UTC (permalink / raw)
To: Srirangan Madhavan, linux-cxl, linux-pci
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] cxl: unpropagated normalized addressing flag in cxl_hdm_ranges_collect()
- [High] cxl: stale cache state and unlocked pre-checks in cxl_reset_function()
- [Low] cxl: missing context lock annotations on cxl_region_quiesce_lock()
--
commit 2ab2ba1a8c9580e8171daa685fd9f57270c18ea2
Author: Srirangan Madhavan <smadhavan@nvidia.com>
cxl: Validate and synchronize HDM ranges around reset
This commit validates and reserves enabled system-physical HDM ranges and
invalidates CPU caches before and after a CXL reset. It also holds
cxl_rwsem.region across snapshot, reset, and restoration while rejecting
resets on devices using normalized addressing or DVSEC range decoding.
> diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
> index 1140006de50d9..cb7ec0f86ab15 100644
> --- a/drivers/cxl/core/hdm_state.c
> +++ b/drivers/cxl/core/hdm_state.c
[ ... ]
> @@ -132,3 +139,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);
> +}
[Severity: High]
Can info->dvsec_ctrl and info->global_ctrl be stale relative to the hardware
state when cxl_reset_function() calls cxl_hdm_cache_dvsec_decode()?
First, cxl_reset_function() reads the live PCI_DVSEC_CXL_CTRL register into
ctrl and confirms PCI_DVSEC_CXL_MEM_ENABLE is set, but
cxl_hdm_cache_dvsec_decode() checks the cached info->dvsec_ctrl instead.
info->dvsec_ctrl is only populated during PCI enumeration in
pci_cxl_hdm_cache_init() or updated via cxl_set_mem_enable(). If
PCI_DVSEC_CXL_MEM_ENABLE was clear at enumeration and later enabled without
cxl_set_mem_enable(), or if cxl_hdm_decode_init() takes the active DVSEC
range return path without calling devm_cxl_enable_mem() (where
devm_cxl_enumerate_decoders() also skips cxl_hdm_refresh_decoder() because
emulated is true):
drivers/cxl/core/pci.c:cxl_hdm_decode_init() {
...
if (allowed && info->mem_enabled)
return 0;
...
}
then info->dvsec_ctrl remains 0 while the device actively decodes memory
through DVSEC ranges, causing cxl_hdm_cache_dvsec_decode() to return false
and allowing the reset to proceed without quiescing those ranges.
Second, neither devm_cxl_enable_hdm() nor its devres teardown callback
disable_hdm() in drivers/cxl/core/pci.c updates pdev->hdm->global_ctrl:
drivers/cxl/core/pci.c:disable_hdm() {
...
global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
writel(global_ctrl & ~CXL_HDM_DECODER_ENABLE,
hdm + CXL_HDM_DECODER_CTRL_OFFSET);
}
When cxl_port unbinds, disable_hdm() clears CXL_HDM_DECODER_ENABLE in
hardware while info->global_ctrl keeps CXL_HDM_DECODER_ENABLE set in the
cache. Conversely, during cxl_endpoint_port_probe(), devm_cxl_enable_mem()
sets PCI_DVSEC_CXL_MEM_ENABLE in info->dvsec_ctrl before
devm_cxl_enumerate_decoders() refreshes info->global_ctrl.
Finally, cxl_reset_function() calls cxl_hdm_cache_present() and
cxl_hdm_cache_dvsec_decode() in separate cxl_rwsem.dpa read-lock sections
before cxl_reset_execute() acquires cxl_region_quiesce_lock(), and neither
cxl_hdm_cache_snapshot() nor cxl_hdm_quiesce_prepare() re-checks the
captured snapshot for DVSEC decode mode while cxl_endpoint_port_probe() and
port unbind run under device_lock(&port->dev) without holding
cxl_rwsem.region.
[ ... ]
> +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;
[Severity: High]
Can CXL_DECODER_F_NORMALIZED_ADDRESSING ever be set in
snapshot->settings[i].config.flags here?
CXL_DECODER_F_NORMALIZED_ADDRESSING is a software flag rather than a
hardware HDM register bit, so pci_cxl_hdm_cache_init() ->
cxl_hdm_read_state() -> cxl_hdm_unpack_decoder() never sets it during PCI
enumeration.
When the endpoint port driver probes in cxl_endpoint_port_probe(), it first
runs devm_cxl_endpoint_decoders_setup() -> devm_cxl_enumerate_decoders(),
which refreshes pdev->hdm before region discovery runs:
drivers/cxl/port.c:cxl_endpoint_port_probe() {
...
rc = devm_cxl_endpoint_decoders_setup(port);
if (rc)
return rc;
...
device_for_each_child(&port->dev, NULL, discover_region);
...
}
CXL_DECODER_F_NORMALIZED_ADDRESSING is only set later during
discover_region() -> cxl_add_to_region() -> get_cxl_root_decoder() ->
cxl_root_setup_translation() -> cxl_prm_setup_root():
drivers/cxl/core/atl.c:cxl_prm_setup_root() {
...
cxld->config.flags |= CXL_DECODER_F_LOCK;
cxld->config.flags |= CXL_DECODER_F_NORMALIZED_ADDRESSING;
...
}
Because nothing calls cxl_hdm_refresh_decoder() after cxl_prm_setup_root()
sets that flag (and the auto-discovered decoder is already enabled and
locked, so cxl_decoder_commit() and cxl_decoder_reset() will not run on it),
pdev->hdm->settings[i].config.flags never receives
CXL_DECODER_F_NORMALIZED_ADDRESSING. Would a reset on a
normalized-addressing endpoint bypass this -EOPNOTSUPP check and instead
reserve and invalidate the untranslated normalized address range?
[ ... ]
> diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
> index cfb55b10f8acf..7919d872a34de 100644
> --- a/include/cxl/hdm.h
> +++ b/include/cxl/hdm.h
[ ... ]
> +void cxl_region_quiesce_lock(void);
> +void cxl_region_quiesce_unlock(void);
> +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);
[Severity: Low]
Should the declarations of cxl_region_quiesce_lock() and
cxl_region_quiesce_unlock() in include/cxl/hdm.h carry the
__acquires(cxl_region_quiesce) and __releases(cxl_region_quiesce)
annotations that are present on their definitions in
drivers/cxl/core/hdm_state.c, and should __cxl_reset_execute() in
drivers/pci/cxl.c also be annotated with __must_hold(cxl_region_quiesce)?
Without those annotations visible in drivers/pci/cxl.c, context analysis
(-Wthread-safety) cannot see that cxl_reset_execute() acquires
cxl_region_quiesce before calling cxl_reset_quiesced(), or that
__cxl_reset_execute() holds cxl_region_quiesce when calling
cxl_hdm_quiesce_flush().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=13
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v16 14/18] PCI/CXL: Reject reset with unsafe function scope
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (12 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 13/18] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
@ 2026-10-29 14:56 ` Srirangan Madhavan
2026-10-29 15:10 ` sashiko-bot
2026-10-29 14:57 ` [PATCH v16 15/18] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
` (3 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:56 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, 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>
---
drivers/pci/cxl.c | 3 +++
drivers/pci/pci.c | 3 +++
2 files changed, 6 insertions(+)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 4a8969145f8b..9a298e90586d 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -515,6 +515,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
!(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
return -ENOTTY;
+ if (pdev->is_virtfn || pdev->multifunction || pci_num_vf(pdev))
+ return -ENOTTY;
+
if (probe)
return 0;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index b2879a6be5f8..9031060423cc 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5026,6 +5026,9 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (!dvsec)
return -ENOTTY;
+ if (dev->is_virtfn || dev->multifunction || pci_num_vf(dev))
+ return -ENOTTY;
+
if (probe)
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* [PATCH v16 15/18] PCI/CXL: Restore CXL state after PCI reset
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (13 preceding siblings ...)
2026-10-29 14:56 ` [PATCH v16 14/18] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
@ 2026-10-29 14:57 ` Srirangan Madhavan
2026-10-29 15:29 ` sashiko-bot
2026-10-29 14:57 ` [PATCH v16 16/18] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
` (2 subsequent siblings)
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:57 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, 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
---
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 | 153 ++++++++++++++++++++++++++---
drivers/pci/cxl.c | 185 ++++++++++++++++++++++++++++++++++--
include/cxl/hdm.h | 2 +
5 files changed, 325 insertions(+), 19 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 9409beb26478..fe5e65a9ddb1 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);
}
@@ -220,8 +220,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 +229,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 +262,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 +284,136 @@ 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;
+
+ if (config->flags & CXL_DECODER_F_LOCK) {
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ if (ctrl == U32_MAX)
+ return -EIO;
+ ctrl |= CXL_HDM_DECODER0_CTRL_LOCK;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ }
+
+ cxl_commit_start_endpoint(settings, hdm);
+ 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 9a298e90586d..58bbd5b47539 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>
@@ -232,6 +233,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
@@ -412,7 +569,9 @@ static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
}
static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
- struct cxl_hdm_quiesce *q)
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_hdm_quiesce *q,
+ u16 *command)
{
int rc, rc2;
@@ -437,10 +596,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;
@@ -451,6 +615,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,
@@ -474,11 +639,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 7919d872a34d..80d6ebb9f4aa 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);
bool cxl_hdm_cache_present(struct cxl_hdm_info *const *slot);
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,
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH v16 15/18] PCI/CXL: Restore CXL state after PCI reset
2026-10-29 14:57 ` [PATCH v16 15/18] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
@ 2026-10-29 15:29 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-10-29 15:29 UTC (permalink / raw)
To: Srirangan Madhavan, linux-cxl, linux-pci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] PCI/CXL: missing __must_hold(cxl_region_quiesce) on __cxl_reset_execute()
--
commit da830bbfe7ea5fbc66b29d8fb48ea9c9acd0a112
Author: Srirangan Madhavan <smadhavan@nvidia.com>
PCI/CXL: Restore CXL state after PCI reset
This commit restores the PCI configuration needed for HDM access, cached HDM
decoder settings, and CXL Device DVSEC state after a CXL device reset. It also
handles restoration failures by disabling the device in the saved PCI command
state.
> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> index 9a298e90586da..58bbd5b47539f 100644
> --- a/drivers/pci/cxl.c
> +++ b/drivers/pci/cxl.c
[ ... ]
> @@ -232,6 +233,162 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
[ ... ]
> +/* 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
> @@ -412,7 +569,9 @@ static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
> }
>
> static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
> - struct cxl_hdm_quiesce *q)
> + const struct cxl_hdm_info *snapshot,
> + struct cxl_hdm_quiesce *q,
> + u16 *command)
> {
> int rc, rc2;
>
> @@ -437,10 +596,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);
[Severity: Low]
Does __cxl_reset_execute() also need the __must_hold(cxl_region_quiesce)
annotation to avoid compiler context-analysis (-Wthread-safety) warnings when
context analysis is enabled?
Both its caller cxl_reset_quiesced() and its callees pci_cxl_restore_state()
and cxl_hdm_quiesce_flush() are annotated with
__must_hold(cxl_region_quiesce), while __cxl_reset_execute() itself is not:
cxl_reset_execute()
cxl_region_quiesce_lock()
cxl_reset_quiesced() [__must_hold(cxl_region_quiesce)]
__cxl_reset_execute() [unannotated]
pci_cxl_restore_state() [__must_hold(cxl_region_quiesce)]
cxl_hdm_quiesce_flush() [__must_hold(cxl_region_quiesce)]
cxl_region_quiesce_unlock()
Relatedly, in include/cxl/hdm.h:
void cxl_region_quiesce_lock(void);
void cxl_region_quiesce_unlock(void);
the prototypes omit the __acquires(cxl_region_quiesce) and
__releases(cxl_region_quiesce) annotations that are present on the definitions
in drivers/cxl/core/hdm_state.c, which also prevents context analysis from
seeing the lock acquisition in cxl_reset_execute().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=15
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v16 16/18] PCI/CXL: Expose CXL Reset as a PCI reset method
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (14 preceding siblings ...)
2026-10-29 14:57 ` [PATCH v16 15/18] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
@ 2026-10-29 14:57 ` Srirangan Madhavan
2026-10-29 15:12 ` sashiko-bot
2026-10-29 14:57 ` [PATCH v16 17/18] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
2026-10-29 14:57 ` [PATCH v16 18/18] cxl: Add KUnit checks for HDM decoder unpacking Srirangan Madhavan
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:57 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, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Register cxl_reset with the PCI reset-method interface and place it before
FLR, which does not reset CXL.cache or CXL.mem protocol state.
Return -ENOTTY when device capabilities, function scope, cached HDM state,
or required cache synchronization do not permit a safe reset.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/pci/cxl.c | 12 +++++++++---
drivers/pci/pci.c | 1 +
include/linux/pci.h | 2 +-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 58bbd5b47539..0c4ac0832b34 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -672,7 +672,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) ||
@@ -681,9 +681,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;
@@ -706,5 +706,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 9031060423cc..f5990d6cc51b 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5148,6 +5148,7 @@ const struct pci_reset_fn_method pci_reset_fn_methods[] = {
{ },
{ pci_dev_specific_reset, .name = "device_specific" },
{ pci_dev_acpi_reset, .name = "acpi" },
+ { cxl_reset_function, .name = "cxl_reset" },
{ pcie_reset_flr, .name = "flr" },
{ pci_af_flr, .name = "af_flr" },
{ pci_pm_reset, .name = "pm" },
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 7bb37fcb556d..743b57ff885a 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -52,7 +52,7 @@
PCI_STATUS_PARITY)
/* Number of reset methods used in pci_reset_fn_methods array in pci.c */
-#define PCI_NUM_RESET_METHODS 8
+#define PCI_NUM_RESET_METHODS 9
#define PCI_RESET_PROBE true
#define PCI_RESET_DO_RESET false
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* [PATCH v16 17/18] PCI/CXL: Restore CXL state after CXL bus reset
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (15 preceding siblings ...)
2026-10-29 14:57 ` [PATCH v16 16/18] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
@ 2026-10-29 14:57 ` Srirangan Madhavan
2026-10-29 15:22 ` sashiko-bot
2026-10-29 14:57 ` [PATCH v16 18/18] cxl: Add KUnit checks for HDM decoder unpacking Srirangan Madhavan
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:57 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, 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
---
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 | 68 ++++++++++++++++++++++++++++++++++++
drivers/pci/pci.c | 8 +++++
drivers/pci/pci.h | 16 +++++++++
include/cxl/hdm.h | 2 ++
5 files changed, 125 insertions(+)
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index cb7ec0f86ab1..6b91721dbcd8 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -188,6 +188,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 0c4ac0832b34..5aa97705a669 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -389,6 +389,74 @@ 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()
+ *
+ * Under the region quiesce, refresh the decoder settings in @snapshot from the
+ * cache and restore: first the PCI configuration needed to reach HDM MMIO,
+ * then the HDM decoder and CXL Device DVSEC state. Leave @pdev disabled on
+ * failure.
+ *
+ * Refresh the decoders now rather than rely on the copy taken before the
+ * reset: a region commit or teardown during the reset has updated the cache,
+ * and restoring older decoders would leave the device decoding ranges the
+ * CXL core considers free.
+ */
+int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot)
+{
+ u16 command;
+ int rc;
+
+ device_lock_assert(&pdev->dev);
+ cxl_region_quiesce_lock();
+
+ rc = cxl_hdm_cache_copy_decoders(&pdev->hdm, snapshot);
+ if (!rc) {
+ rc = pci_cxl_restore_state(pdev, snapshot, &command);
+ }
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
+ else
+ rc = cxl_reset_save_restored_state(pdev, command);
+
+ cxl_region_quiesce_unlock();
+ return rc;
+}
+
/*
* CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
* Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index f5990d6cc51b..22f740417673 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5014,6 +5014,7 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
{
+ struct cxl_hdm_info *snapshot __free(kfree) = NULL;
struct pci_dev *bridge;
u16 dvsec, reg, val;
int rc;
@@ -5036,6 +5037,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);
@@ -5056,6 +5061,9 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
reg);
+ if (!rc && snapshot)
+ rc = cxl_restore_state_after_pci_reset(dev, snapshot);
+
pci_dev_reset_iommu_done(dev);
return rc;
}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 743e65abd758..476e8eefafb2 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -730,10 +730,14 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
+struct cxl_hdm_info;
#ifdef CONFIG_CXL_RESET
void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
int cxl_reset_function(struct pci_dev *pdev, bool probe);
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev);
+int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot);
#else
static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev) { }
@@ -741,6 +745,18 @@ static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
return -ENOTTY;
}
+
+static inline struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
+{
+ return NULL;
+}
+
+static inline int
+cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot)
+{
+ return 0;
+}
#endif
#ifdef CONFIG_PCI_NPEM
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 80d6ebb9f4aa..090eea2c0b46 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -66,6 +66,8 @@ void cxl_hdm_cache_publish(struct cxl_hdm_info **slot,
void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot);
struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot);
+int cxl_hdm_cache_copy_decoders(struct cxl_hdm_info *const *slot,
+ struct cxl_hdm_info *dst);
/*
* Region quiesce blocks CXL region commit and teardown, and with them decoder
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH v16 17/18] PCI/CXL: Restore CXL state after CXL bus reset
2026-10-29 14:57 ` [PATCH v16 17/18] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
@ 2026-10-29 15:22 ` sashiko-bot
0 siblings, 0 replies; 37+ messages in thread
From: sashiko-bot @ 2026-10-29 15:22 UTC (permalink / raw)
To: Srirangan Madhavan, linux-pci
> 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>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=17
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v16 18/18] cxl: Add KUnit checks for HDM decoder unpacking
2026-10-29 14:56 [PATCH v16 00/18] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (16 preceding siblings ...)
2026-10-29 14:57 ` [PATCH v16 17/18] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
@ 2026-10-29 14:57 ` Srirangan Madhavan
2026-10-29 15:09 ` sashiko-bot
17 siblings, 1 reply; 37+ messages in thread
From: Srirangan Madhavan @ 2026-10-29 14:57 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, 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 | 90 ++++++++++++++++++++++++++++
6 files changed, 111 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 fe5e65a9ddb1..ef1b0e07c352 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -174,7 +174,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..6e5b59ef6309
--- /dev/null
+++ b/drivers/cxl/tests/hdm_unpack_kunit.c
@@ -0,0 +1,90 @@
+// 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 related [flat|nested] 37+ messages in thread