* [PATCH v17 01/19] cxl: Clarify decoder interleave limit
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
@ 2026-11-05 18:58 ` Srirangan Madhavan
2026-11-05 19:03 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
` (18 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:58 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan,
Jonathan Cameron
CXL 2.0 added 16-way memory interleaving, making the comment that the
specification supports only 8-way interleaving stale. Explain that 16 is
the largest interleave a decoder can express, without changing the limit.
Suggested-by: Jonathan Cameron <jic23@kernel.org>
Link: https://lore.kernel.org/linux-cxl/20260912003057.3db9335c@jic23-hlaptop/
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
---
drivers/cxl/cxl.h | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 3ef0810ab86b..50af716f69df 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -269,10 +269,7 @@ enum cxl_decoder_type {
CXL_DECODER_HOSTONLYMEM = 3,
};
-/*
- * Current specification goes up to 8, double that seems a reasonable
- * software max for the foreseeable future
- */
+/* 16 is the largest interleave a decoder can express (CXL 4.0 8.2.4.20.7). */
#define CXL_DECODER_MAX_INTERLEAVE 16
#define CXL_QOS_CLASS_INVALID -1
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-11-05 18:58 ` [PATCH v17 01/19] cxl: Clarify decoder interleave limit Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:12 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 03/19] cxl: Move decoder declarations to shared header Srirangan Madhavan
` (17 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan,
Jonathan Cameron
read_cdat_data() resolves a CXL port's upstream device to its backing PCI
device. HDM cache updates need the same mapping.
Factor the lookup into cxl_port_get_uport_pci_dev() and return a referenced
PCI device to make caller ownership explicit.
Use automatic reference cleanup in read_cdat_data(), as suggested by
Li Ming, so early returns release the PCI device reference.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
---
Changes in v17:
- Use an explicit pci_dev_put() on a single exit path in
read_cdat_data() instead of mixing __free() with goto cleanup
(Sashiko)
https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=2
---
drivers/cxl/core/core.h | 1 +
drivers/cxl/core/pci.c | 24 ++++++------------------
drivers/cxl/core/port.c | 26 ++++++++++++++++++++++++++
3 files changed, 33 insertions(+), 18 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 35eaf636adc9..983d7690c3a5 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -157,6 +157,7 @@ long cxl_pci_get_latency(struct pci_dev *pdev);
int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c);
int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
struct access_coordinate *c);
+struct pci_dev *cxl_port_get_uport_pci_dev(struct cxl_port *port);
static inline struct device *port_to_host(struct cxl_port *port)
{
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 9d807c1a002c..905892cfedef 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -564,26 +564,13 @@ static unsigned char cdat_checksum(void *buf, size_t size)
*/
void read_cdat_data(struct cxl_port *port)
{
- struct device *uport = port->uport_dev;
+ struct pci_dev *pdev = cxl_port_get_uport_pci_dev(port);
struct device *dev = &port->dev;
struct pci_doe_mb *doe_mb;
- struct pci_dev *pdev = NULL;
- struct cxl_memdev *cxlmd;
struct cdat_doe_rsp *buf;
size_t table_length, length;
int rc;
- if (is_cxl_memdev(uport)) {
- struct device *host;
-
- cxlmd = to_cxl_memdev(uport);
- host = cxlmd->dev.parent;
- if (dev_is_pci(host))
- pdev = to_pci_dev(host);
- } else if (dev_is_pci(uport)) {
- pdev = to_pci_dev(uport);
- }
-
if (!pdev)
return;
@@ -591,14 +578,14 @@ void read_cdat_data(struct cxl_port *port)
CXL_DOE_PROTOCOL_TABLE_ACCESS);
if (!doe_mb) {
dev_dbg(dev, "No CDAT mailbox\n");
- return;
+ goto out;
}
port->cdat_available = true;
if (cxl_cdat_get_length(dev, doe_mb, &length)) {
dev_dbg(dev, "No CDAT length\n");
- return;
+ goto out;
}
/*
@@ -624,12 +611,13 @@ void read_cdat_data(struct cxl_port *port)
port->cdat.table = buf->data;
port->cdat.length = length;
-
- return;
+ goto out;
err:
/* Don't leave table data allocated on error */
devm_kfree(dev, buf);
dev_err(dev, "Failed to read/validate CDAT.\n");
+out:
+ pci_dev_put(pdev);
}
EXPORT_SYMBOL_NS_GPL(read_cdat_data, "CXL");
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 625e4aa427db..5dc2815d82d8 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -34,6 +34,32 @@
static DEFINE_IDA(cxl_port_ida);
static DEFINE_XARRAY(cxl_root_buses);
+/**
+ * cxl_port_get_uport_pci_dev - get the PCI device for a port's upstream device
+ * @port: CXL port to map to a PCI device
+ *
+ * Return: A referenced PCI device, or NULL if the upstream device is not PCI.
+ * The caller must release the returned reference with pci_dev_put().
+ */
+struct pci_dev *cxl_port_get_uport_pci_dev(struct cxl_port *port)
+{
+ struct device *uport = port->uport_dev;
+ struct device *host;
+
+ if (is_cxl_memdev(uport)) {
+ struct cxl_memdev *cxlmd = to_cxl_memdev(uport);
+
+ host = cxlmd->dev.parent;
+ } else {
+ host = uport;
+ }
+
+ if (!host || !dev_is_pci(host))
+ return NULL;
+
+ return pci_dev_get(to_pci_dev(host));
+}
+
/*
* The terminal device in PCI is NULL and @platform_bus
* for platform devices (for cxl_test)
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup
2026-11-05 18:59 ` [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
@ 2026-11-05 19:12 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:12 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:
- [Medium] cxl: stale commit message and goto out on success path in read_cdat_data()
--
commit 731df7235bfc37ad38e18947501f81078ce6230b
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 a new cxl_port_get_uport_pci_dev() helper that returns
a referenced struct pci_dev, and updates read_cdat_data() to release that
reference via pci_dev_put().
> cxl: Share CXL port upstream PCI device lookup
>
> 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>
[ ... ]
> diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
> index 9d807c1a002ca..905892cfedef6 100644
> --- a/drivers/cxl/core/pci.c
> +++ b/drivers/cxl/core/pci.c
> @@ -564,26 +564,13 @@ static unsigned char cdat_checksum(void *buf, size_t size)
> */
> void read_cdat_data(struct cxl_port *port)
> {
> - struct device *uport = port->uport_dev;
> + struct pci_dev *pdev = cxl_port_get_uport_pci_dev(port);
> struct device *dev = &port->dev;
> struct pci_doe_mb *doe_mb;
> - struct pci_dev *pdev = NULL;
> - struct cxl_memdev *cxlmd;
> struct cdat_doe_rsp *buf;
> size_t table_length, length;
> int rc;
>
> - if (is_cxl_memdev(uport)) {
> - struct device *host;
> -
> - cxlmd = to_cxl_memdev(uport);
> - host = cxlmd->dev.parent;
> - if (dev_is_pci(host))
> - pdev = to_pci_dev(host);
> - } else if (dev_is_pci(uport)) {
> - pdev = to_pci_dev(uport);
> - }
> -
> if (!pdev)
> return;
>
> @@ -591,14 +578,14 @@ void read_cdat_data(struct cxl_port *port)
> CXL_DOE_PROTOCOL_TABLE_ACCESS);
> if (!doe_mb) {
> dev_dbg(dev, "No CDAT mailbox\n");
> - return;
> + goto out;
> }
>
> port->cdat_available = true;
>
> if (cxl_cdat_get_length(dev, doe_mb, &length)) {
> dev_dbg(dev, "No CDAT length\n");
> - return;
> + goto out;
> }
>
> /*
> @@ -624,12 +611,13 @@ void read_cdat_data(struct cxl_port *port)
>
> port->cdat.table = buf->data;
> port->cdat.length = length;
> -
> - return;
> + goto out;
> err:
> /* Don't leave table data allocated on error */
> devm_kfree(dev, buf);
> dev_err(dev, "Failed to read/validate CDAT.\n");
> +out:
> + pci_dev_put(pdev);
> }
[Severity: Medium]
As reported by Jonathan Cameron on v13 [1], having the normal success path
in read_cdat_data() jump over the err: label via goto out makes the control
flow harder to follow, and Jonathan conditioned his Reviewed-by tag on not
having the good path follow a goto.
In addition, when read_cdat_data() switched from __free(pci_dev_put) to
explicit pci_dev_put(pdev) calls, the third paragraph of the commit message
still states:
Use automatic reference cleanup in read_cdat_data(), as suggested by
Li Ming, so early returns release the PCI device reference.
Should read_cdat_data() call pci_dev_put(pdev) directly before each return
(and in the err: block) instead of jumping over err: with goto out on the
normal path, and should the stale paragraph in the commit message be dropped?
[1] https://lore.kernel.org/r/20260924022156.5d376e91@jic23-hlaptop
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=2
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 03/19] cxl: Move decoder declarations to shared header
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-11-05 18:58 ` [PATCH v17 01/19] cxl: Clarify decoder interleave limit Srirangan Madhavan
2026-11-05 18:59 ` [PATCH v17 02/19] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:06 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 04/19] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
` (16 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan,
Jonathan Cameron
Move the common decoder declarations to include/cxl/cxl.h for use by
shared decoder settings. Preserve their layout and documentation.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
---
drivers/cxl/cxl.h | 38 --------------------------------------
include/cxl/cxl.h | 42 +++++++++++++++++++++++++++++++++++++++++-
2 files changed, 41 insertions(+), 39 deletions(-)
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 50af716f69df..44ff3334648c 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -264,46 +264,8 @@ int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
#define CXL_DECODER_F_NORMALIZED_ADDRESSING BIT(6)
#define CXL_DECODER_F_RESET_MASK (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK)
-enum cxl_decoder_type {
- CXL_DECODER_DEVMEM = 2,
- CXL_DECODER_HOSTONLYMEM = 3,
-};
-
-/* 16 is the largest interleave a decoder can express (CXL 4.0 8.2.4.20.7). */
-#define CXL_DECODER_MAX_INTERLEAVE 16
-
#define CXL_QOS_CLASS_INVALID -1
-/**
- * struct cxl_decoder - Common CXL HDM Decoder Attributes
- * @dev: this decoder's device
- * @id: kernel device name id
- * @hpa_range: Host physical address range mapped by this decoder
- * @interleave_ways: number of cxl_dports in this decode
- * @interleave_granularity: data stride per dport
- * @target_type: accelerator vs expander (type2 vs type3) selector
- * @region: currently assigned region for this decoder
- * @flags: memory type capabilities and locking
- * @target_map: cached copy of hardware port-id list, available at init
- * before all @dport objects have been instantiated. While
- * dport id is 8bit, CFMWS interleave targets are 32bits.
- * @commit: device/decoder-type specific callback to commit settings to hw
- * @reset: device/decoder-type specific callback to reset hw settings
-*/
-struct cxl_decoder {
- struct device dev;
- int id;
- struct range hpa_range;
- int interleave_ways;
- int interleave_granularity;
- enum cxl_decoder_type target_type;
- struct cxl_region *region;
- unsigned long flags;
- u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
- int (*commit)(struct cxl_decoder *cxld);
- void (*reset)(struct cxl_decoder *cxld);
-};
-
/*
* Track whether this decoder is free for userspace provisioning, reserved for
* region autodiscovery, whether it is started connecting (awaiting other
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 802b143de83d..613dab8150c7 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -5,8 +5,10 @@
#ifndef __CXL_CXL_H__
#define __CXL_CXL_H__
+#include <linux/device.h>
#include <linux/node.h>
#include <linux/ioport.h>
+#include <linux/range.h>
#include <cxl/mailbox.h>
/**
@@ -23,7 +25,45 @@ enum cxl_devtype {
CXL_DEVTYPE_CLASSMEM,
};
-struct device;
+struct cxl_region;
+
+enum cxl_decoder_type {
+ CXL_DECODER_DEVMEM = 2,
+ CXL_DECODER_HOSTONLYMEM = 3,
+};
+
+/* 16 is the largest interleave a decoder can express (CXL 4.0 8.2.4.20.7). */
+#define CXL_DECODER_MAX_INTERLEAVE 16
+
+/**
+ * struct cxl_decoder - Common CXL HDM Decoder Attributes
+ * @dev: this decoder's device
+ * @id: kernel device name id
+ * @hpa_range: Host physical address range mapped by this decoder
+ * @interleave_ways: number of cxl_dports in this decode
+ * @interleave_granularity: data stride per dport
+ * @target_type: accelerator vs expander (type2 vs type3) selector
+ * @region: currently assigned region for this decoder
+ * @flags: memory type capabilities and locking
+ * @target_map: cached copy of hardware port-id list, available at init
+ * before all @dport objects have been instantiated. While
+ * dport id is 8bit, CFMWS interleave targets are 32bits.
+ * @commit: device/decoder-type specific callback to commit settings to hw
+ * @reset: device/decoder-type specific callback to reset hw settings
+*/
+struct cxl_decoder {
+ struct device dev;
+ int id;
+ struct range hpa_range;
+ int interleave_ways;
+ int interleave_granularity;
+ enum cxl_decoder_type target_type;
+ struct cxl_region *region;
+ unsigned long flags;
+ u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
+ int (*commit)(struct cxl_decoder *cxld);
+ void (*reset)(struct cxl_decoder *cxld);
+};
/*
* Using struct_group() allows for per register-block-type helper routines,
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 04/19] cxl: Embed decoder configuration in a standalone structure
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (2 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 03/19] cxl: Move decoder declarations to shared header Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:07 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 05/19] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
` (15 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Define cxl_decoder_config independently and embed it in cxl_decoder. Update
decoder field accesses without changing behavior, so reset snapshots can
reuse the configuration type without struct_group_tagged().
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
---
drivers/cxl/acpi.c | 26 +++---
drivers/cxl/core/atl.c | 4 +-
drivers/cxl/core/hdm.c | 114 ++++++++++++-------------
drivers/cxl/core/pci.c | 8 +-
drivers/cxl/core/port.c | 42 +++++-----
drivers/cxl/core/region.c | 102 +++++++++++-----------
drivers/cxl/cxl.h | 2 +-
drivers/cxl/pci.c | 2 +-
drivers/cxl/port.c | 4 +-
include/cxl/cxl.h | 29 +++++--
tools/testing/cxl/test/cxl.c | 158 +++++++++++++++++------------------
11 files changed, 249 insertions(+), 242 deletions(-)
diff --git a/drivers/cxl/acpi.c b/drivers/cxl/acpi.c
index 3b818adbd38b..692a4e067678 100644
--- a/drivers/cxl/acpi.c
+++ b/drivers/cxl/acpi.c
@@ -109,16 +109,16 @@ static int cxl_parse_cxims(union acpi_subtable_headers *header, void *arg,
return rc;
/* Does this CXIMS entry apply to the given CXL Window? */
- if (hbig != cxld->interleave_granularity)
+ if (hbig != cxld->config.interleave_granularity)
return 0;
/* IW 1,3 do not use xormaps and skip this parsing entirely */
- if (is_power_of_2(cxld->interleave_ways))
+ if (is_power_of_2(cxld->config.interleave_ways))
/* 2, 4, 8, 16 way */
- nr_maps = ilog2(cxld->interleave_ways);
+ nr_maps = ilog2(cxld->config.interleave_ways);
else
/* 6, 12 way */
- nr_maps = ilog2(cxld->interleave_ways / 3);
+ nr_maps = ilog2(cxld->config.interleave_ways / 3);
if (cxims->nr_xormaps < nr_maps) {
dev_dbg(dev, "CXIMS nr_xormaps[%d] expected[%d]\n",
@@ -363,7 +363,7 @@ static int add_or_reset_cxl_resource(struct resource *parent, struct resource *r
static void cxl_setup_extended_linear_cache(struct cxl_root_decoder *cxlrd)
{
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
- struct range *hpa = &cxld->hpa_range;
+ struct range *hpa = &cxld->config.hpa_range;
resource_size_t size = range_len(hpa);
resource_size_t start = hpa->start;
resource_size_t cache_size;
@@ -436,13 +436,13 @@ static int __cxl_parse_cfmws(struct acpi_cedt_cfmws *cfmws,
return PTR_ERR(cxlrd);
cxld = &cxlrd->cxlsd.cxld;
- cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions);
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->hpa_range = (struct range) {
+ cxld->config.flags = cfmws_to_decoder_flags(cfmws->restrictions);
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.hpa_range = (struct range) {
.start = cfmws->base_hpa,
.end = cfmws->base_hpa + cfmws->window_size - 1,
};
- cxld->interleave_ways = ways;
+ cxld->config.interleave_ways = ways;
for (i = 0; i < ways; i++)
cxld->target_map[i] = cfmws->interleave_targets[i];
/*
@@ -451,7 +451,7 @@ static int __cxl_parse_cfmws(struct acpi_cedt_cfmws *cfmws,
*/
if (ways == 1)
ig = CXL_DECODER_MIN_GRANULARITY;
- cxld->interleave_granularity = ig;
+ cxld->config.interleave_granularity = ig;
if (cfmws->interleave_arithmetic == ACPI_CEDT_CFMWS_ARITHMETIC_XOR) {
if (ways != 1 && ways != 3) {
@@ -752,7 +752,7 @@ static int add_root_nvdimm_bridge(struct device *match, void *data)
return 0;
cxld = to_cxl_decoder(match);
- if (!(cxld->flags & CXL_DECODER_F_PMEM))
+ if (!(cxld->config.flags & CXL_DECODER_F_PMEM))
return 0;
cxl_nvb = devm_cxl_add_nvdimm_bridge(host, root_port);
@@ -869,8 +869,8 @@ static int pair_cxl_resource(struct device *dev, void *data)
struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev);
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct resource res = {
- .start = cxld->hpa_range.start,
- .end = cxld->hpa_range.end,
+ .start = cxld->config.hpa_range.start,
+ .end = cxld->config.hpa_range.end,
.flags = IORESOURCE_MEM,
};
diff --git a/drivers/cxl/core/atl.c b/drivers/cxl/core/atl.c
index 310668786189..07ce7c50c47a 100644
--- a/drivers/cxl/core/atl.c
+++ b/drivers/cxl/core/atl.c
@@ -172,8 +172,8 @@ static int cxl_prm_setup_root(struct cxl_root *cxl_root, void *data)
*
* Indicate that Normalized Addressing is enabled.
*/
- cxld->flags |= CXL_DECODER_F_LOCK;
- cxld->flags |= CXL_DECODER_F_NORMALIZED_ADDRESSING;
+ cxld->config.flags |= CXL_DECODER_F_LOCK;
+ cxld->config.flags |= CXL_DECODER_F_NORMALIZED_ADDRESSING;
ctx->hpa_range = hpa_range;
ctx->interleave_ways = ways;
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 39fe283cbc74..9e05032a5426 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -384,11 +384,11 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
if (cxled->dpa_res) {
dev_dbg(dev, "decoder%d.%d: existing allocation %pr assigned\n",
- port->id, cxled->cxld.id, cxled->dpa_res);
+ port->id, cxled->cxld.config.id, cxled->dpa_res);
return -EBUSY;
}
- if (port->hdm_end + 1 != cxled->cxld.id) {
+ if (port->hdm_end + 1 != cxled->cxld.config.id) {
/*
* Assumes alloc and commit order is always in hardware instance
* order per expectations from 8.2.5.12.20 Committing Decoder
@@ -396,7 +396,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
* decoder[m+1] commit start.
*/
dev_dbg(dev, "decoder%d.%d: expected decoder%d.%d\n", port->id,
- cxled->cxld.id, port->id, port->hdm_end + 1);
+ cxled->cxld.config.id, port->id, port->hdm_end + 1);
return -EBUSY;
}
@@ -409,7 +409,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
dev_name(&cxled->cxld.dev));
if (IS_ERR(res)) {
dev_dbg(dev, "decoder%d.%d: failed to reserve allocation\n",
- port->id, cxled->cxld.id);
+ port->id, cxled->cxld.config.id);
if (skipped)
release_skip(cxlds, base - skipped, skipped);
return PTR_ERR(res);
@@ -432,7 +432,7 @@ static int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
/* Empty decoders may not be contained by a partition boundary */
if (cxled->part < 0 && resource_size(res))
dev_warn(dev, "decoder%d.%d: %pr does not map any partition\n",
- port->id, cxled->cxld.id, res);
+ port->id, cxled->cxld.config.id, res);
port->hdm_end++;
get_device(&cxled->cxld.dev);
@@ -580,11 +580,11 @@ int cxl_dpa_free(struct cxl_endpoint_decoder *cxled)
dev_name(&cxled->cxld.region->dev));
return -EBUSY;
}
- if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) {
+ if (cxled->cxld.config.flags & CXL_DECODER_F_ENABLE) {
dev_dbg(dev, "decoder enabled\n");
return -EBUSY;
}
- if (cxled->cxld.id != port->hdm_end) {
+ if (cxled->cxld.config.id != port->hdm_end) {
dev_dbg(dev, "expected decoder%d.%d\n", port->id,
port->hdm_end);
return -EBUSY;
@@ -603,7 +603,7 @@ int cxl_dpa_set_part(struct cxl_endpoint_decoder *cxled,
int part;
guard(rwsem_write)(&cxl_rwsem.dpa);
- if (cxled->cxld.flags & CXL_DECODER_F_ENABLE)
+ if (cxled->cxld.config.flags & CXL_DECODER_F_ENABLE)
return -EBUSY;
for (part = 0; part < cxlds->nr_partitions; part++)
@@ -641,7 +641,7 @@ static int __cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
return -EBUSY;
}
- if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) {
+ if (cxled->cxld.config.flags & CXL_DECODER_F_ENABLE) {
dev_dbg(dev, "decoder enabled\n");
return -EBUSY;
}
@@ -716,12 +716,12 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
* Input validation ensures these warns never fire, but otherwise
* suppress unititalized variable usage warnings.
*/
- if (WARN_ONCE(ways_to_eiw(cxld->interleave_ways, &eiw),
- "invalid interleave_ways: %d\n", cxld->interleave_ways))
+ if (WARN_ONCE(ways_to_eiw(cxld->config.interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n", cxld->config.interleave_ways))
return;
- if (WARN_ONCE(granularity_to_eig(cxld->interleave_granularity, &eig),
+ if (WARN_ONCE(granularity_to_eig(cxld->config.interleave_granularity, &eig),
"invalid interleave_granularity: %d\n",
- cxld->interleave_granularity))
+ cxld->config.interleave_granularity))
return;
u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
@@ -732,14 +732,14 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
{
u32p_replace_bits(ctrl,
- !!(cxld->target_type == CXL_DECODER_HOSTONLYMEM),
+ !!(cxld->config.target_type == CXL_DECODER_HOSTONLYMEM),
CXL_HDM_DECODER0_CTRL_HOSTONLY);
}
static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
{
struct cxl_dport **t = &cxlsd->target[0];
- int ways = cxlsd->cxld.interleave_ways;
+ int ways = cxlsd->cxld.config.interleave_ways;
*tgt = FIELD_PREP(GENMASK(7, 0), t[0]->port_id);
if (ways > 1)
@@ -786,16 +786,16 @@ static int cxld_await_commit(void __iomem *hdm, int id)
static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
{
- int id = cxld->id;
+ int id = cxld->config.id;
u64 base, size;
u32 ctrl;
/* common decoder settings */
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->config.id));
cxld_set_interleave(cxld, &ctrl);
cxld_set_type(cxld, &ctrl);
- base = cxld->hpa_range.start;
- size = range_len(&cxld->hpa_range);
+ base = cxld->config.hpa_range.start;
+ size = range_len(&cxld->config.hpa_range);
writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
@@ -830,9 +830,9 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
- int id = cxld->id, rc;
+ int id = cxld->config.id, rc;
- if (cxld->flags & CXL_DECODER_F_ENABLE)
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE)
return 0;
if (cxl_num_decoders_committed(port) != id) {
@@ -865,14 +865,14 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
scoped_guard(rwsem_read, &cxl_rwsem.dpa)
setup_hw_decoder(cxld, hdm);
- rc = cxld_await_commit(hdm, cxld->id);
+ rc = cxld_await_commit(hdm, cxld->config.id);
if (rc) {
dev_dbg(&port->dev, "%s: error %d committing decoder\n",
dev_name(&cxld->dev), rc);
return rc;
}
port->commit_end++;
- cxld->flags |= CXL_DECODER_F_ENABLE;
+ cxld->config.flags |= CXL_DECODER_F_ENABLE;
return 0;
}
@@ -886,8 +886,8 @@ static int commit_reap(struct device *dev, void *data)
return 0;
cxld = to_cxl_decoder(dev);
- if (port->commit_end == cxld->id &&
- ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
+ if (port->commit_end == cxld->config.id &&
+ ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)) {
port->commit_end--;
dev_dbg(&port->dev, "reap: %s commit_end: %d\n",
dev_name(&cxld->dev), port->commit_end);
@@ -919,13 +919,13 @@ static void cxl_decoder_reset(struct cxl_decoder *cxld)
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
- int id = cxld->id;
+ int id = cxld->config.id;
u32 ctrl;
- if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)
return;
- if (cxld->flags & CXL_DECODER_F_LOCK)
+ if (cxld->config.flags & CXL_DECODER_F_LOCK)
return;
if (port->commit_end == id)
@@ -944,7 +944,7 @@ static void cxl_decoder_reset(struct cxl_decoder *cxld)
writel(0, hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
- cxld->flags &= ~CXL_DECODER_F_ENABLE;
+ cxld->config.flags &= ~CXL_DECODER_F_ENABLE;
/* Userspace is now responsible for reconfiguring this decoder */
if (is_endpoint_decoder(&cxld->dev)) {
@@ -971,23 +971,23 @@ static int cxl_setup_hdm_decoder_from_dvsec(
if (!len)
return -ENOENT;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
cxld->commit = NULL;
cxld->reset = NULL;
- cxld->hpa_range = info->dvsec_range[which];
+ cxld->config.hpa_range = info->dvsec_range[which];
/*
* Set the emulated decoder as locked pending additional support to
* change the range registers at run time.
*/
- cxld->flags |= CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
- port->commit_end = cxld->id;
+ cxld->config.flags |= CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
+ port->commit_end = cxld->config.id;
rc = devm_cxl_dpa_reserve(cxled, *dpa_base, len, 0);
if (rc) {
dev_err(&port->dev,
"decoder%d.%d: Failed to reserve DPA range %#llx - %#llx: %d\n",
- port->id, cxld->id, *dpa_base, *dpa_base + len - 1, rc);
+ port->id, cxld->config.id, *dpa_base, *dpa_base + len - 1, rc);
return rc;
}
*dpa_base += len;
@@ -1030,36 +1030,36 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
size = 0;
if (base == U64_MAX || size == U64_MAX) {
dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n",
- port->id, cxld->id);
+ port->id, cxld->config.id);
return -ENXIO;
}
if (info)
cxled = to_cxl_endpoint_decoder(&cxld->dev);
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + size - 1,
};
/* decoders are enabled if committed */
if (committed) {
- cxld->flags |= CXL_DECODER_F_ENABLE;
+ cxld->config.flags |= CXL_DECODER_F_ENABLE;
if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
- cxld->flags |= CXL_DECODER_F_LOCK;
+ cxld->config.flags |= CXL_DECODER_F_LOCK;
if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
else
- cxld->target_type = CXL_DECODER_DEVMEM;
+ cxld->config.target_type = CXL_DECODER_DEVMEM;
guard(rwsem_write)(&cxl_rwsem.region);
- if (cxld->id != cxl_num_decoders_committed(port)) {
+ if (cxld->config.id != cxl_num_decoders_committed(port)) {
dev_warn(&port->dev,
"decoder%d.%d: Committed out of order\n",
- port->id, cxld->id);
+ port->id, cxld->config.id);
return -ENXIO;
}
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
} else {
if (cxled) {
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
@@ -1070,46 +1070,46 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
* more precision.
*/
if (cxlds->type == CXL_DEVTYPE_CLASSMEM)
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
else
- cxld->target_type = CXL_DECODER_DEVMEM;
+ cxld->config.target_type = CXL_DECODER_DEVMEM;
} else {
/* To be overridden by region type at commit time */
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
}
if (!FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl) &&
- cxld->target_type == CXL_DECODER_HOSTONLYMEM) {
+ cxld->config.target_type == CXL_DECODER_HOSTONLYMEM) {
ctrl |= CXL_HDM_DECODER0_CTRL_HOSTONLY;
writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
}
}
rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
- &cxld->interleave_ways);
+ &cxld->config.interleave_ways);
if (rc) {
dev_warn(&port->dev,
"decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
- port->id, cxld->id, ctrl);
+ port->id, cxld->config.id, ctrl);
return rc;
}
rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
- &cxld->interleave_granularity);
+ &cxld->config.interleave_granularity);
if (rc) {
dev_warn(&port->dev,
"decoder%d.%d: Invalid interleave granularity (ctrl: %#x)\n",
- port->id, cxld->id, ctrl);
+ port->id, cxld->config.id, ctrl);
return rc;
}
dev_dbg(&port->dev, "decoder%d.%d: range: %#llx-%#llx iw: %d ig: %d\n",
- port->id, cxld->id, cxld->hpa_range.start, cxld->hpa_range.end,
- cxld->interleave_ways, cxld->interleave_granularity);
+ port->id, cxld->config.id, cxld->config.hpa_range.start, cxld->config.hpa_range.end,
+ cxld->config.interleave_ways, cxld->config.interleave_granularity);
if (!cxled) {
lo = readl(hdm + CXL_HDM_DECODER0_TL_LOW(which));
hi = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(which));
target_list.value = (hi << 32) + lo;
- for (i = 0; i < cxld->interleave_ways; i++)
+ for (i = 0; i < cxld->config.interleave_ways; i++)
cxld->target_map[i] = target_list.target_id[i];
return 0;
@@ -1118,11 +1118,11 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
if (!committed)
return 0;
- dpa_size = div_u64_rem(size, cxld->interleave_ways, &remainder);
+ dpa_size = div_u64_rem(size, cxld->config.interleave_ways, &remainder);
if (remainder) {
dev_err(&port->dev,
"decoder%d.%d: invalid committed configuration size: %#llx ways: %d\n",
- port->id, cxld->id, size, cxld->interleave_ways);
+ port->id, cxld->config.id, size, cxld->config.interleave_ways);
return -ENXIO;
}
lo = readl(hdm + CXL_HDM_DECODER0_SKIP_LOW(which));
@@ -1132,7 +1132,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
if (rc) {
dev_err(&port->dev,
"decoder%d.%d: Failed to reserve DPA range %#llx - %#llx: %d\n",
- port->id, cxld->id, *dpa_base,
+ port->id, cxld->config.id, *dpa_base,
*dpa_base + dpa_size + skip - 1, rc);
return rc;
}
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 905892cfedef..f72cd8f50e77 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -231,10 +231,10 @@ static int dvsec_range_allowed(struct device *dev, const void *arg)
cxld = to_cxl_decoder(dev);
- if (!(cxld->flags & CXL_DECODER_F_RAM))
+ if (!(cxld->config.flags & CXL_DECODER_F_RAM))
return 0;
- return range_contains(&cxld->hpa_range, dev_range);
+ return range_contains(&cxld->config.hpa_range, dev_range);
}
static void disable_hdm(void *_cxlhdm)
@@ -668,12 +668,12 @@ static int __cxl_endpoint_decoder_reset_detected(struct device *dev, void *data)
return 0;
cxld = to_cxl_decoder(dev);
- if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)
return 0;
cxlhdm = dev_get_drvdata(&port->dev);
hdm = cxlhdm->regs.hdm_decoder;
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->config.id));
return !FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
}
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 5dc2815d82d8..84374873f3d0 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -129,7 +129,7 @@ static ssize_t start_show(struct device *dev, struct device_attribute *attr,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%#llx\n", cxld->hpa_range.start);
+ return sysfs_emit(buf, "%#llx\n", cxld->config.hpa_range.start);
}
static DEVICE_ATTR_ADMIN_RO(start);
@@ -138,7 +138,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%#llx\n", range_len(&cxld->hpa_range));
+ return sysfs_emit(buf, "%#llx\n", range_len(&cxld->config.hpa_range));
}
static DEVICE_ATTR_RO(size);
@@ -149,7 +149,7 @@ static ssize_t name##_show(struct device *dev, \
struct cxl_decoder *cxld = to_cxl_decoder(dev); \
\
return sysfs_emit(buf, "%s\n", \
- (cxld->flags & (flag)) ? "1" : "0"); \
+ (cxld->config.flags & (flag)) ? "1" : "0"); \
} \
static DEVICE_ATTR_RO(name)
@@ -164,7 +164,7 @@ static ssize_t target_type_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- switch (cxld->target_type) {
+ switch (cxld->config.target_type) {
case CXL_DECODER_DEVMEM:
return sysfs_emit(buf, "accelerator\n");
case CXL_DECODER_HOSTONLYMEM:
@@ -180,14 +180,14 @@ static ssize_t emit_target_list(struct cxl_switch_decoder *cxlsd, char *buf)
ssize_t offset = 0;
int i, rc = 0;
- for (i = 0; i < cxld->interleave_ways; i++) {
+ for (i = 0; i < cxld->config.interleave_ways; i++) {
struct cxl_dport *dport = cxlsd->target[i];
struct cxl_dport *next = NULL;
if (!dport)
break;
- if (i + 1 < cxld->interleave_ways)
+ if (i + 1 < cxld->config.interleave_ways)
next = cxlsd->target[i + 1];
rc = sysfs_emit_at(buf, offset, "%d%s", dport->port_id,
next ? "," : "");
@@ -314,7 +314,7 @@ static ssize_t interleave_granularity_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%d\n", cxld->interleave_granularity);
+ return sysfs_emit(buf, "%d\n", cxld->config.interleave_granularity);
}
static DEVICE_ATTR_RO(interleave_granularity);
@@ -324,7 +324,7 @@ static ssize_t interleave_ways_show(struct device *dev,
{
struct cxl_decoder *cxld = to_cxl_decoder(dev);
- return sysfs_emit(buf, "%d\n", cxld->interleave_ways);
+ return sysfs_emit(buf, "%d\n", cxld->config.interleave_ways);
}
static DEVICE_ATTR_RO(interleave_ways);
@@ -368,14 +368,14 @@ static bool can_create_pmem(struct cxl_root_decoder *cxlrd)
{
unsigned long flags = CXL_DECODER_F_TYPE3 | CXL_DECODER_F_PMEM;
- return (cxlrd->cxlsd.cxld.flags & flags) == flags;
+ return (cxlrd->cxlsd.cxld.config.flags & flags) == flags;
}
static bool can_create_ram(struct cxl_root_decoder *cxlrd)
{
unsigned long flags = CXL_DECODER_F_TYPE3 | CXL_DECODER_F_RAM;
- return (cxlrd->cxlsd.cxld.flags & flags) == flags;
+ return (cxlrd->cxlsd.cxld.config.flags & flags) == flags;
}
static umode_t cxl_root_decoder_visible(struct kobject *kobj, struct attribute *a, int n)
@@ -450,7 +450,7 @@ static void __cxl_decoder_release(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- ida_free(&port->decoder_ida, cxld->id);
+ ida_free(&port->decoder_ida, cxld->config.id);
put_device(&port->dev);
}
@@ -1646,7 +1646,7 @@ static int update_decoder_targets(struct device *dev, void *data)
cxld = &cxlsd->cxld;
guard(rwsem_write)(&cxl_rwsem.region);
- for (i = 0; i < cxld->interleave_ways; i++) {
+ for (i = 0; i < cxld->config.interleave_ways; i++) {
if (cxld->target_map[i] == dport->port_id) {
cxlsd->target[i] = dport;
dev_dbg(dev, "dport%d found in target list, index %d\n",
@@ -1952,7 +1952,7 @@ static int decoder_populate_targets(struct cxl_switch_decoder *cxlsd,
return 0;
guard(rwsem_write)(&cxl_rwsem.region);
- for (i = 0; i < cxlsd->cxld.interleave_ways; i++) {
+ for (i = 0; i < cxlsd->cxld.config.interleave_ways; i++) {
struct cxl_dport *dport = find_dport(port, cxld->target_map[i]);
if (!dport) {
@@ -1988,7 +1988,7 @@ static int cxl_decoder_init(struct cxl_port *port, struct cxl_decoder *cxld)
/* need parent to stick around to release the id */
get_device(&port->dev);
- cxld->id = rc;
+ cxld->config.id = rc;
dev = &cxld->dev;
device_initialize(dev);
@@ -1998,10 +1998,10 @@ static int cxl_decoder_init(struct cxl_port *port, struct cxl_decoder *cxld)
dev->bus = &cxl_bus_type;
/* Pre initialize an "empty" decoder */
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = PAGE_SIZE;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->hpa_range = (struct range) {
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = PAGE_SIZE;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -2172,7 +2172,7 @@ int cxl_decoder_add_locked(struct cxl_decoder *cxld)
if (WARN_ON_ONCE(IS_ERR(cxld)))
return PTR_ERR(cxld);
- if (cxld->interleave_ways < 1)
+ if (cxld->config.interleave_ways < 1)
return -EINVAL;
dev = &cxld->dev;
@@ -2182,14 +2182,14 @@ int cxl_decoder_add_locked(struct cxl_decoder *cxld)
struct cxl_switch_decoder *cxlsd = to_cxl_switch_decoder(dev);
rc = decoder_populate_targets(cxlsd, port);
- if (rc && (cxld->flags & CXL_DECODER_F_ENABLE)) {
+ if (rc && (cxld->config.flags & CXL_DECODER_F_ENABLE)) {
dev_err(&port->dev,
"Failed to populate active decoder targets\n");
return rc;
}
}
- rc = dev_set_name(dev, "decoder%d.%d", port->id, cxld->id);
+ rc = dev_set_name(dev, "decoder%d.%d", port->id, cxld->config.id);
if (rc)
return rc;
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index bb51e67f25f5..8cfc06df3134 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -502,8 +502,8 @@ static int set_interleave_ways(struct cxl_region *cxlr, int val)
* Even for x3, x6, and x12 interleaves the region interleave must be a
* power of 2 multiple of the host bridge interleave.
*/
- if (!is_power_of_2(val / cxld->interleave_ways) ||
- (val % cxld->interleave_ways)) {
+ if (!is_power_of_2(val / cxld->config.interleave_ways) ||
+ (val % cxld->config.interleave_ways)) {
dev_dbg(&cxlr->dev, "invalid interleave: %d\n", val);
return -EINVAL;
}
@@ -580,7 +580,7 @@ static int set_interleave_granularity(struct cxl_region *cxlr, int val)
* with a granularity greater than the root interleave result in invalid
* DPA translations (invalid to support).
*/
- if (cxld->interleave_ways > 1 && val != cxld->interleave_granularity)
+ if (cxld->config.interleave_ways > 1 && val != cxld->config.interleave_granularity)
return -EINVAL;
lockdep_assert_held_write(&cxl_rwsem.region);
@@ -880,7 +880,7 @@ static int check_commit_order(struct device *dev, void *data)
* order shutdown has occurred, block further allocations until
* that is resolved
*/
- if (((cxld->flags & CXL_DECODER_F_ENABLE) == 0))
+ if (((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0))
return -EBUSY;
return 0;
}
@@ -896,7 +896,7 @@ static int match_free_decoder(struct device *dev, const void *data)
cxld = to_cxl_decoder(dev);
- if (cxld->id != port->commit_end + 1)
+ if (cxld->config.id != port->commit_end + 1)
return 0;
if (cxld->region) {
@@ -942,7 +942,7 @@ static int match_auto_decoder(struct device *dev, const void *data)
return 0;
cxld = to_cxl_decoder(dev);
- r = &cxld->hpa_range;
+ r = &cxld->config.hpa_range;
if (spa_maps_hpa(p, r))
return 1;
@@ -1003,10 +1003,10 @@ static bool auto_order_ok(struct cxl_port *port, struct cxl_region *cxlr_iter,
* starting address has the lesser id.
*/
dev_dbg(&cxld->dev, "check for HPA violation %s:%d < %s:%d\n",
- dev_name(&cxld->dev), cxld->id,
- dev_name(&cxld_iter->dev), cxld_iter->id);
+ dev_name(&cxld->dev), cxld->config.id,
+ dev_name(&cxld_iter->dev), cxld_iter->config.id);
- if (cxld_iter->id > cxld->id)
+ if (cxld_iter->config.id > cxld->config.id)
return true;
return false;
@@ -1137,11 +1137,11 @@ static int cxl_rr_assign_decoder(struct cxl_port *port, struct cxl_region *cxlr,
*/
dev_WARN_ONCE(&cxlr->dev,
port == cxled_to_port(cxled) &&
- cxld->target_type != cxlr->type,
+ cxld->config.target_type != cxlr->type,
"%s:%s mismatch decoder type %d -> %d\n",
dev_name(&cxled_to_memdev(cxled)->dev),
- dev_name(&cxld->dev), cxld->target_type, cxlr->type);
- cxld->target_type = cxlr->type;
+ dev_name(&cxld->dev), cxld->config.target_type, cxlr->type);
+ cxld->config.target_type = cxlr->type;
cxl_rr->decoder = cxld;
return 0;
}
@@ -1162,12 +1162,12 @@ static void cxl_region_setup_flags(struct cxl_region *cxlr,
set_bit(CXL_REGION_F_LOCK, &cxlr->flags);
}
- if (cxld->flags & CXL_DECODER_F_LOCK) {
+ if (cxld->config.flags & CXL_DECODER_F_LOCK) {
set_bit(CXL_REGION_F_LOCK, &cxlr->flags);
clear_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
}
- if (cxld->flags & CXL_DECODER_F_NORMALIZED_ADDRESSING)
+ if (cxld->config.flags & CXL_DECODER_F_NORMALIZED_ADDRESSING)
set_bit(CXL_REGION_F_NORMALIZED_ADDRESSING, &cxlr->flags);
}
@@ -1483,7 +1483,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
distance *= cxl_rr_iter->nr_targets;
iter = to_cxl_port(iter->dev.parent);
} while (!is_cxl_root(iter));
- distance *= cxlrd->cxlsd.cxld.interleave_ways;
+ distance *= cxlrd->cxlsd.cxld.config.interleave_ways;
for (i = 0; i < cxl_rr->nr_targets_set; i++)
if (ep->dport == cxlsd->target[i]) {
@@ -1505,7 +1505,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
* root IG != region IG.
*/
parent_ig = p->interleave_granularity;
- parent_iw = cxlrd->cxlsd.cxld.interleave_ways;
+ parent_iw = cxlrd->cxlsd.cxld.config.interleave_ways;
/*
* For purposes of address bit routing, use power-of-2 math for
* switch ports.
@@ -1518,8 +1518,8 @@ static int cxl_port_setup_targets(struct cxl_port *port,
parent_rr = cxl_rr_load(parent_port, cxlr);
parent_cxld = parent_rr->decoder;
- parent_ig = parent_cxld->interleave_granularity;
- parent_iw = parent_cxld->interleave_ways;
+ parent_ig = parent_cxld->config.interleave_granularity;
+ parent_iw = parent_cxld->config.interleave_ways;
}
rc = granularity_to_eig(parent_ig, &peig);
@@ -1575,10 +1575,10 @@ static int cxl_port_setup_targets(struct cxl_port *port,
}
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
- if (cxld->interleave_ways != iw ||
- (iw > 1 && cxld->interleave_granularity != ig) ||
- !spa_maps_hpa(p, &cxld->hpa_range) ||
- ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
+ if (cxld->config.interleave_ways != iw ||
+ (iw > 1 && cxld->config.interleave_granularity != ig) ||
+ !spa_maps_hpa(p, &cxld->config.hpa_range) ||
+ ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)) {
dev_err(&cxlr->dev,
"%s:%s %s expected iw: %d ig: %d %pr\n",
dev_name(port->uport_dev), dev_name(&port->dev),
@@ -1586,10 +1586,10 @@ static int cxl_port_setup_targets(struct cxl_port *port,
dev_err(&cxlr->dev,
"%s:%s %s got iw: %d ig: %d state: %s %#llx:%#llx\n",
dev_name(port->uport_dev), dev_name(&port->dev),
- __func__, cxld->interleave_ways,
- cxld->interleave_granularity,
- str_enabled_disabled(cxld->flags & CXL_DECODER_F_ENABLE),
- cxld->hpa_range.start, cxld->hpa_range.end);
+ __func__, cxld->config.interleave_ways,
+ cxld->config.interleave_granularity,
+ str_enabled_disabled(cxld->config.flags & CXL_DECODER_F_ENABLE),
+ cxld->config.hpa_range.start, cxld->config.hpa_range.end);
return -ENXIO;
}
} else {
@@ -1602,9 +1602,9 @@ static int cxl_port_setup_targets(struct cxl_port *port,
return rc;
}
- cxld->interleave_ways = iw;
- cxld->interleave_granularity = ig;
- cxld->hpa_range = (struct range) {
+ cxld->config.interleave_ways = iw;
+ cxld->config.interleave_granularity = ig;
+ cxld->config.hpa_range = (struct range) {
.start = p->res->start,
.end = p->res->end,
};
@@ -1657,7 +1657,7 @@ static void cxl_port_reset_targets(struct cxl_port *port,
cxl_rr->nr_targets_set = 0;
cxld = cxl_rr->decoder;
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -1804,7 +1804,7 @@ static int cxl_region_attach_position(struct cxl_region *cxlr,
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_switch_decoder *cxlsd = &cxlrd->cxlsd;
struct cxl_decoder *cxld = &cxlsd->cxld;
- int iw = cxld->interleave_ways;
+ int iw = cxld->config.interleave_ways;
struct cxl_port *iter;
int rc;
@@ -1902,7 +1902,7 @@ static int match_switch_decoder_by_range(struct device *dev,
return 0;
cxlsd = to_cxl_switch_decoder(dev);
- r1 = &cxlsd->cxld.hpa_range;
+ r1 = &cxlsd->cxld.config.hpa_range;
if (is_root_decoder(dev))
return range_contains(r1, r2);
@@ -1929,7 +1929,7 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
return rc;
}
cxlsd = to_cxl_switch_decoder(dev);
- *ways = cxlsd->cxld.interleave_ways;
+ *ways = cxlsd->cxld.config.interleave_ways;
for (int i = 0; i < *ways; i++) {
if (cxlsd->target[i] == port->parent_dport) {
@@ -2105,10 +2105,10 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
- if (cxled->cxld.target_type != cxlr->type) {
+ if (cxled->cxld.config.target_type != cxlr->type) {
dev_dbg(&cxlr->dev, "%s:%s type mismatch: %d vs %d\n",
dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
- cxled->cxld.target_type, cxlr->type);
+ cxled->cxld.config.target_type, cxlr->type);
return -ENXIO;
}
@@ -2194,9 +2194,9 @@ static int cxl_region_attach(struct cxl_region *cxlr,
cxl_region_shared_upstream_bandwidth_update(cxlr);
}
- cxled->cxld.interleave_ways = p->interleave_ways;
- cxled->cxld.interleave_granularity = p->interleave_granularity;
- cxled->cxld.hpa_range = (struct range) {
+ cxled->cxld.config.interleave_ways = p->interleave_ways;
+ cxled->cxld.config.interleave_granularity = p->interleave_granularity;
+ cxled->cxld.config.hpa_range = (struct range) {
.start = p->res->start,
.end = p->res->end,
};
@@ -2320,7 +2320,7 @@ __cxl_decoder_detach(struct cxl_region *cxlr,
}
p->targets[cxled->pos] = NULL;
p->nr_targets--;
- cxled->cxld.hpa_range = (struct range) {
+ cxled->cxld.config.hpa_range = (struct range) {
.start = 0,
.end = -1,
};
@@ -2992,7 +2992,7 @@ static int poison_by_decoder(struct device *dev, void *arg)
}
/* Iterate until commit_end is reached */
- if (cxled->cxld.id == ctx->port->commit_end) {
+ if (cxled->cxld.config.id == ctx->port->commit_end) {
ctx->offset = cxled->dpa_res->end + 1;
ctx->part = cxled->part;
return 1;
@@ -3315,10 +3315,10 @@ static u64 restore_parent(u64 addr, u64 pos, u64 gran, u64 ways)
static u64 unaligned_dpa_to_hpa(struct cxl_decoder *cxld,
struct cxl_region_params *p, int pos, u64 dpa)
{
- int ways_port = p->interleave_ways / cxld->interleave_ways;
+ int ways_port = p->interleave_ways / cxld->config.interleave_ways;
int gran_port = p->interleave_granularity;
- int gran_hb = cxld->interleave_granularity;
- int ways_hb = cxld->interleave_ways;
+ int gran_hb = cxld->config.interleave_granularity;
+ int ways_hb = cxld->config.interleave_ways;
int pos_port, pos_hb, gran_shift;
u64 hpa_port = 0;
@@ -3365,7 +3365,7 @@ static bool region_is_unaligned_mod3(struct cxl_region *cxlr)
struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
- int hbiw = cxld->interleave_ways;
+ int hbiw = cxld->config.interleave_ways;
u64 rem;
if (is_power_of_2(hbiw))
@@ -3480,8 +3480,8 @@ static int unaligned_region_offset_to_dpa_result(struct cxl_region *cxlr,
* a dpa_offset independent of the target device and then enumerate
* and test that dpa_offset against each candidate endpoint decoder.
*/
- gran = cxld->interleave_granularity;
- interleave_width = gran * cxld->interleave_ways;
+ gran = cxld->config.interleave_granularity;
+ interleave_width = gran * cxld->config.interleave_ways;
interleave_index = div64_u64(offset, interleave_width);
gran_offset = do_div(tmp, gran);
@@ -3581,7 +3581,7 @@ static int match_root_decoder(struct device *dev, const void *data)
return 0;
cxlrd = to_cxl_root_decoder(dev);
- r1 = &cxlrd->cxlsd.cxld.hpa_range;
+ r1 = &cxlrd->cxlsd.cxld.config.hpa_range;
return range_contains(r1, r2);
}
@@ -3786,7 +3786,7 @@ static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd,
do {
cxlr = __create_region(cxlrd, cxlds->part[part].mode,
atomic_read(&cxlrd->region_id),
- cxled->cxld.target_type);
+ cxled->cxld.config.target_type);
} while (IS_ERR(cxlr) && PTR_ERR(cxlr) == -EBUSY);
if (IS_ERR(cxlr)) {
@@ -3829,9 +3829,9 @@ int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
ctx = (struct cxl_region_context) {
.cxled = cxled,
- .hpa_range = cxled->cxld.hpa_range,
- .interleave_ways = cxled->cxld.interleave_ways,
- .interleave_granularity = cxled->cxld.interleave_granularity,
+ .hpa_range = cxled->cxld.config.hpa_range,
+ .interleave_ways = cxled->cxld.config.interleave_ways,
+ .interleave_granularity = cxled->cxld.config.interleave_granularity,
};
struct cxl_root_decoder *cxlrd __free(put_cxl_root_decoder) =
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 44ff3334648c..688350c7c5fb 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -281,7 +281,7 @@ enum cxl_decoder_state {
* struct cxl_endpoint_decoder - Endpoint / SPA to DPA decoder
* @cxld: base cxl_decoder_object
* @dpa_res: actively claimed DPA span of this decoder
- * @skip: offset into @dpa_res where @cxld.hpa_range maps
+ * @skip: offset into @dpa_res where @cxld.config.hpa_range maps
* @state: autodiscovery state
* @part: partition index this decoder maps
* @pos: interleave position in @cxld.region
diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c
index c7c91e8dc51d..9a0cbcd230b2 100644
--- a/drivers/cxl/pci.c
+++ b/drivers/cxl/pci.c
@@ -966,7 +966,7 @@ static int cxl_endpoint_decoder_clear_reset_flags(struct device *dev, void *data
return 0;
cxled = to_cxl_endpoint_decoder(dev);
- cxled->cxld.flags &= ~CXL_DECODER_F_RESET_MASK;
+ cxled->cxld.config.flags &= ~CXL_DECODER_F_RESET_MASK;
return 0;
}
diff --git a/drivers/cxl/port.c b/drivers/cxl/port.c
index c12fd0b89883..b369d3e3876d 100644
--- a/drivers/cxl/port.c
+++ b/drivers/cxl/port.c
@@ -40,7 +40,7 @@ static int discover_region(struct device *dev, void *unused)
return 0;
cxled = to_cxl_endpoint_decoder(dev);
- if ((cxled->cxld.flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxled->cxld.config.flags & CXL_DECODER_F_ENABLE) == 0)
return 0;
if (cxled->state != CXL_DECODER_STATE_AUTO)
@@ -56,7 +56,7 @@ static int discover_region(struct device *dev, void *unused)
rc = cxl_add_to_region(cxled);
if (rc)
dev_dbg(dev, "failed to add to region: %#llx-%#llx\n",
- cxled->cxld.hpa_range.start, cxled->cxld.hpa_range.end);
+ cxled->cxld.config.hpa_range.start, cxled->cxld.config.hpa_range.end);
return 0;
}
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 613dab8150c7..6d6e564be27a 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -36,15 +36,31 @@ enum cxl_decoder_type {
#define CXL_DECODER_MAX_INTERLEAVE 16
/**
- * struct cxl_decoder - Common CXL HDM Decoder Attributes
- * @dev: this decoder's device
+ * struct cxl_decoder_config - common CXL decoder configuration and state
* @id: kernel device name id
* @hpa_range: Host physical address range mapped by this decoder
* @interleave_ways: number of cxl_dports in this decode
* @interleave_granularity: data stride per dport
* @target_type: accelerator vs expander (type2 vs type3) selector
- * @region: currently assigned region for this decoder
* @flags: memory type capabilities and locking
+ *
+ * This is not a complete snapshot of the hardware registers. Switch target
+ * lists and endpoint DPA skip values are not tracked in this common structure.
+ */
+struct cxl_decoder_config {
+ int id;
+ struct range hpa_range;
+ int interleave_ways;
+ int interleave_granularity;
+ enum cxl_decoder_type target_type;
+ unsigned long flags;
+};
+
+/**
+ * struct cxl_decoder - Common CXL HDM Decoder Attributes
+ * @dev: this decoder's device
+ * @config: common decoder configuration and state
+ * @region: currently assigned region for this decoder
* @target_map: cached copy of hardware port-id list, available at init
* before all @dport objects have been instantiated. While
* dport id is 8bit, CFMWS interleave targets are 32bits.
@@ -53,13 +69,8 @@ enum cxl_decoder_type {
*/
struct cxl_decoder {
struct device dev;
- int id;
- struct range hpa_range;
- int interleave_ways;
- int interleave_granularity;
- enum cxl_decoder_type target_type;
+ struct cxl_decoder_config config;
struct cxl_region *region;
- unsigned long flags;
u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
int (*commit)(struct cxl_decoder *cxld);
void (*reset)(struct cxl_decoder *cxld);
diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index 2921f1111870..034d3a923b2c 100644
--- a/tools/testing/cxl/test/cxl.c
+++ b/tools/testing/cxl/test/cxl.c
@@ -773,7 +773,7 @@ static int map_targets(struct device *dev, void *data)
* id into the low bits.
*
* The key is formed as:
- * ((unsigned long)port->uport_dev << 4) | cxld->id
+ * ((unsigned long)port->uport_dev << 4) | cxld->config.id
*
* The low bits hold the decoder id (which must fit in 4 bits) while
* the remaining bits identify the upstream port. This key is only used
@@ -783,9 +783,9 @@ static unsigned long cxld_registry_index(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- dev_WARN_ONCE(&port->dev, cxld->id >= 16,
- "decoder id:%d out of range\n", cxld->id);
- return (((unsigned long)port->uport_dev) << 4) | cxld->id;
+ dev_WARN_ONCE(&port->dev, cxld->config.id >= 16,
+ "decoder id:%d out of range\n", cxld->config.id);
+ return (((unsigned long)port->uport_dev) << 4) | cxld->config.id;
}
struct cxl_test_decoder {
@@ -801,14 +801,15 @@ static struct cxl_test_decoder *cxld_registry_find(struct cxl_decoder *cxld)
return xa_load(&decoder_registry, cxld_registry_index(cxld));
}
-#define dbg_cxld(port, msg, cxld) \
- do { \
- struct cxl_decoder *___d = (cxld); \
- dev_dbg((port)->uport_dev, \
+#define dbg_cxld(port, msg, cxld) \
+ do { \
+ struct cxl_decoder *___d = (cxld); \
+ const struct cxl_decoder_config *___c = &___d->config; \
+ dev_dbg((port)->uport_dev, \
"decoder%d: %s range: %#llx-%#llx iw: %d ig: %d flags: %#lx\n", \
- ___d->id, msg, ___d->hpa_range.start, \
- ___d->hpa_range.end + 1, ___d->interleave_ways, \
- ___d->interleave_granularity, ___d->flags); \
+ ___c->id, msg, ___c->hpa_range.start, \
+ ___c->hpa_range.end + 1, ___c->interleave_ways, \
+ ___c->interleave_granularity, ___c->flags); \
} while (0)
static int mock_decoder_commit(struct cxl_decoder *cxld);
@@ -817,12 +818,7 @@ static void init_disabled_mock_decoder(struct cxl_decoder *cxld);
static void cxld_copy(struct cxl_decoder *a, struct cxl_decoder *b)
{
- a->id = b->id;
- a->hpa_range = b->hpa_range;
- a->interleave_ways = b->interleave_ways;
- a->interleave_granularity = b->interleave_granularity;
- a->target_type = b->target_type;
- a->flags = b->flags;
+ a->config = b->config;
a->commit = mock_decoder_commit;
a->reset = mock_decoder_reset;
}
@@ -847,7 +843,7 @@ static int cxld_registry_restore(struct cxl_decoder *cxld,
struct cxl_switch_decoder *cxlsd =
to_cxl_switch_decoder(&cxld->dev);
- if (!(td->cxlsd.cxld.flags & CXL_DECODER_F_ENABLE))
+ if (!(td->cxlsd.cxld.config.flags & CXL_DECODER_F_ENABLE))
return 0;
dbg_cxld(port, "restore", &td->cxlsd.cxld);
@@ -860,13 +856,13 @@ static int cxld_registry_restore(struct cxl_decoder *cxld,
cxld->target_map[i] = td->cxlsd.cxld.target_map[i];
}
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
} else {
struct cxl_endpoint_decoder *cxled =
to_cxl_endpoint_decoder(&cxld->dev);
- if (!(td->cxled.cxld.flags & CXL_DECODER_F_ENABLE))
+ if (!(td->cxled.cxld.config.flags & CXL_DECODER_F_ENABLE))
return 0;
dbg_cxld(port, "restore", &td->cxled.cxld);
@@ -881,7 +877,7 @@ static int cxld_registry_restore(struct cxl_decoder *cxld,
init_disabled_mock_decoder(cxld);
return rc;
}
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
}
return 0;
@@ -915,7 +911,7 @@ static void __cxld_registry_save(struct cxl_test_decoder *td,
td->cxled.state = cxled->state;
td->cxled.skip = cxled->skip;
- if (!(cxld->flags & CXL_DECODER_F_ENABLE)) {
+ if (!(cxld->config.flags & CXL_DECODER_F_ENABLE)) {
td->dpa_range.start = 0;
td->dpa_range.end = -1;
} else if (cxled->dpa_res) {
@@ -952,9 +948,9 @@ static void cxld_registry_update(struct cxl_decoder *cxld)
static int mock_decoder_commit(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- int id = cxld->id;
+ int id = cxld->config.id;
- if (cxld->flags & CXL_DECODER_F_ENABLE)
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE)
return 0;
dev_dbg(&port->dev, "%s commit\n", dev_name(&cxld->dev));
@@ -967,7 +963,7 @@ static int mock_decoder_commit(struct cxl_decoder *cxld)
}
port->commit_end++;
- cxld->flags |= CXL_DECODER_F_ENABLE;
+ cxld->config.flags |= CXL_DECODER_F_ENABLE;
if (is_endpoint_decoder(&cxld->dev)) {
struct cxl_endpoint_decoder *cxled =
to_cxl_endpoint_decoder(&cxld->dev);
@@ -982,9 +978,9 @@ static int mock_decoder_commit(struct cxl_decoder *cxld)
static void mock_decoder_reset(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
- int id = cxld->id;
+ int id = cxld->config.id;
- if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
+ if ((cxld->config.flags & CXL_DECODER_F_ENABLE) == 0)
return;
dev_dbg(&port->dev, "%s reset\n", dev_name(&cxld->dev));
@@ -994,7 +990,7 @@ static void mock_decoder_reset(struct cxl_decoder *cxld)
dev_dbg(&port->dev,
"%s: out of order reset, expected decoder%d.%d\n",
dev_name(&cxld->dev), port->id, port->commit_end);
- cxld->flags &= ~CXL_DECODER_F_ENABLE;
+ cxld->config.flags &= ~CXL_DECODER_F_ENABLE;
if (is_endpoint_decoder(&cxld->dev)) {
struct cxl_endpoint_decoder *cxled =
@@ -1005,7 +1001,7 @@ static void mock_decoder_reset(struct cxl_decoder *cxld)
}
if (decoder_reset_preserve_registry)
dev_dbg(port->uport_dev, "decoder%d: skip registry update\n",
- cxld->id);
+ cxld->config.id);
else
cxld_registry_update(cxld);
}
@@ -1030,12 +1026,12 @@ static struct cxl_test_decoder *cxld_registry_new(struct cxl_decoder *cxld)
static void init_disabled_mock_decoder(struct cxl_decoder *cxld)
{
- cxld->hpa_range.start = 0;
- cxld->hpa_range.end = -1;
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = 0;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->flags = 0;
+ cxld->config.hpa_range.start = 0;
+ cxld->config.hpa_range.end = -1;
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = 0;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.flags = 0;
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
@@ -1058,14 +1054,14 @@ static void init_disabled_mock_decoder(struct cxl_decoder *cxld)
static void default_mock_decoder(struct cxl_decoder *cxld)
{
- cxld->hpa_range = (struct range){
+ cxld->config.hpa_range = (struct range){
.start = 0,
.end = -1,
};
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = 256;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = 256;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
@@ -1080,7 +1076,7 @@ static int match_decoder_by_index(struct device *dev, const void *data)
if (!is_switch_decoder(dev))
return 0;
cxld = to_cxl_decoder(dev);
- return cxld->id == target_id;
+ return cxld->config.id == target_id;
}
/*
@@ -1093,11 +1089,11 @@ static void size_zero_mock_decoder_ep(struct cxl_decoder *cxld, u64 base)
{
struct cxl_endpoint_decoder *cxled = to_cxl_endpoint_decoder(&cxld->dev);
- cxld->hpa_range = DEFINE_RANGE(base, base - 1);
- cxld->interleave_ways = 2;
- cxld->interleave_granularity = 4096;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->flags = CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
+ cxld->config.hpa_range = DEFINE_RANGE(base, base - 1);
+ cxld->config.interleave_ways = 2;
+ cxld->config.interleave_granularity = 4096;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
cxled->state = CXL_DECODER_STATE_AUTO;
/* decoder[0] reserved [0, size/2), empty decoders sit at that watermark */
devm_cxl_dpa_reserve(cxled, mock_auto_region_size / 2, 0, 0);
@@ -1108,11 +1104,11 @@ static void size_zero_mock_decoder_ep(struct cxl_decoder *cxld, u64 base)
static void size_zero_mock_decoder_sw(struct cxl_decoder *cxld, u64 base,
int level)
{
- cxld->flags = CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->interleave_ways = level == 0 ? 2 : 1;
- cxld->interleave_granularity = 4096;
- cxld->hpa_range = DEFINE_RANGE(base, base - 1);
+ cxld->config.flags = CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.interleave_ways = level == 0 ? 2 : 1;
+ cxld->config.interleave_granularity = 4096;
+ cxld->config.hpa_range = DEFINE_RANGE(base, base - 1);
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
}
@@ -1147,7 +1143,7 @@ static enum cxld_init_type get_decoder_init_type(struct cxl_decoder *cxld,
* See 'cxl list -BMPu -m cxl_mem.0,cxl_mem.4'
*/
if (!is_endpoint_decoder(&cxld->dev) || !hb0 || pdev->id % 4 ||
- pdev->id > 4 || cxld->id > (mock_zero_size_decoders ? 2 : 0))
+ pdev->id > 4 || cxld->config.id > (mock_zero_size_decoders ? 2 : 0))
return MOCK_DECODER_INIT_DEFAULT;
return type2_test ? MOCK_DECODER_INIT_TYPE2_AUTO :
@@ -1159,9 +1155,9 @@ static bool mock_decoder_handle_saved(struct cxl_decoder *cxld, struct cxl_test_
bool enabled;
if (is_switch_decoder(&cxld->dev))
- enabled = td->cxlsd.cxld.flags & CXL_DECODER_F_ENABLE;
+ enabled = td->cxlsd.cxld.config.flags & CXL_DECODER_F_ENABLE;
else
- enabled = td->cxled.cxld.flags & CXL_DECODER_F_ENABLE;
+ enabled = td->cxled.cxld.config.flags & CXL_DECODER_F_ENABLE;
if (enabled)
return !cxld_registry_restore(cxld, td);
@@ -1183,19 +1179,19 @@ static void mock_init_hdm_type2_cxled(struct cxl_endpoint_decoder *cxled,
int id = 0;
base = window->base_hpa;
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + mock_auto_region_size - 1,
};
- cxld->interleave_ways = 1;
- eig_to_granularity(window->granularity, &cxld->interleave_granularity);
- cxld->target_type = CXL_DECODER_DEVMEM;
- cxld->flags = CXL_DECODER_F_ENABLE;
+ cxld->config.interleave_ways = 1;
+ eig_to_granularity(window->granularity, &cxld->config.interleave_granularity);
+ cxld->config.target_type = CXL_DECODER_DEVMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE;
cxled->state = CXL_DECODER_STATE_AUTO;
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
devm_cxl_dpa_reserve(cxled, 0,
- mock_auto_region_size / cxld->interleave_ways, 0);
+ mock_auto_region_size / cxld->config.interleave_ways, 0);
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
@@ -1216,13 +1212,13 @@ static void mock_init_hdm_type2_cxled(struct cxl_endpoint_decoder *cxled,
cxlsd = to_cxl_switch_decoder(dev);
cxld = &cxlsd->cxld;
- cxld->target_type = CXL_DECODER_DEVMEM;
- cxld->flags = CXL_DECODER_F_ENABLE;
+ cxld->config.target_type = CXL_DECODER_DEVMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE;
root_port->commit_end = 0;
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = 4096;
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = 4096;
cxld->target_map[0] = dport->port_id;
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + mock_auto_region_size - 1,
};
@@ -1258,7 +1254,7 @@ static void mock_init_hdm_type3_cxled(struct cxl_endpoint_decoder *cxled,
int i;
/* Simulate missing cxl_mem.4 configuration */
- if (hb0 && pdev->id == 4 && cxld->id == 0 && fail_autoassemble) {
+ if (hb0 && pdev->id == 4 && cxld->config.id == 0 && fail_autoassemble) {
default_mock_decoder(cxld);
return;
}
@@ -1274,25 +1270,25 @@ static void mock_init_hdm_type3_cxled(struct cxl_endpoint_decoder *cxled,
* points at a zero-size decoder, exercising the zero-size
* reservation and poison-by-endpoint code paths.
*/
- if (cxld->id == 1 || cxld->id == 2) {
+ if (cxld->config.id == 1 || cxld->config.id == 2) {
size_zero_mock_decoder_ep(cxld, base);
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
WARN_ON_ONCE(!cxld_registry_new(cxld));
return;
}
- cxld->hpa_range = (struct range) {
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + mock_auto_region_size - 1,
};
- cxld->interleave_ways = 2;
- eig_to_granularity(window->granularity, &cxld->interleave_granularity);
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->flags = CXL_DECODER_F_ENABLE;
+ cxld->config.interleave_ways = 2;
+ eig_to_granularity(window->granularity, &cxld->config.interleave_granularity);
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE;
cxled->state = CXL_DECODER_STATE_AUTO;
- port->commit_end = cxld->id;
+ port->commit_end = cxld->config.id;
devm_cxl_dpa_reserve(cxled, 0,
- mock_auto_region_size / cxld->interleave_ways, 0);
+ mock_auto_region_size / cxld->config.interleave_ways, 0);
cxld->commit = mock_decoder_commit;
cxld->reset = mock_decoder_reset;
@@ -1326,19 +1322,19 @@ static void mock_init_hdm_type3_cxled(struct cxl_endpoint_decoder *cxled,
cxlsd->cxld.target_map[0] = dport->port_id;
}
cxld = &cxlsd->cxld;
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- cxld->flags = CXL_DECODER_F_ENABLE;
+ cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
+ cxld->config.flags = CXL_DECODER_F_ENABLE;
iter->commit_end = 0;
/*
* Switch targets 2 endpoints, while host bridge targets
* one root port
*/
if (i == 0)
- cxld->interleave_ways = 2;
+ cxld->config.interleave_ways = 2;
else
- cxld->interleave_ways = 1;
- cxld->interleave_granularity = 4096;
- cxld->hpa_range = (struct range) {
+ cxld->config.interleave_ways = 1;
+ cxld->config.interleave_granularity = 4096;
+ cxld->config.hpa_range = (struct range) {
.start = base,
.end = base + mock_auto_region_size - 1,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 05/19] cxl: Introduce endpoint HDM decoder settings
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (3 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 04/19] cxl: Embed decoder configuration in a standalone structure Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:09 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 06/19] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
` (14 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Keep common decoder configuration in cxl_decoder_config and represent
endpoint DPA skip in cxl_endpoint_decoder_settings. Share range
programming, but keep endpoint skip and switch target-list writes in
their respective commit paths.
Separate commit initiation from completion waiting so reset restoration
can reuse register programming without changing normal DPA-lock policy.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
Assisted-by: LLM
---
drivers/cxl/core/core.h | 8 ++++
drivers/cxl/core/hdm.c | 95 ++++++++++++++++++++++++++++-------------
include/cxl/hdm.h | 19 +++++++++
3 files changed, 92 insertions(+), 30 deletions(-)
create mode 100644 include/cxl/hdm.h
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 983d7690c3a5..fb14b222f3ca 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -7,6 +7,8 @@
#include <cxl/mailbox.h>
#include <linux/rwsem.h>
+struct cxl_endpoint_decoder_settings;
+
extern const struct device_type cxl_nvdimm_bridge_type;
extern const struct device_type cxl_nvdimm_type;
extern const struct device_type cxl_pmu_type;
@@ -139,6 +141,12 @@ struct cxl_rwsem {
extern struct cxl_rwsem cxl_rwsem;
+void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm);
+void cxl_commit_start_switch(const struct cxl_decoder_config *config,
+ u64 targets, void __iomem *hdm);
+int cxld_await_commit(void __iomem *hdm, int id);
+
int cxl_memdev_init(void);
void cxl_memdev_exit(void);
void cxl_mbox_init(void);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 9e05032a5426..af445f1b9798 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -3,6 +3,7 @@
#include <linux/seq_file.h>
#include <linux/device.h>
#include <linux/delay.h>
+#include <cxl/hdm.h>
#include "cxlmem.h"
#include "core.h"
@@ -707,7 +708,7 @@ int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
}
-static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
+static void cxld_set_interleave(const struct cxl_decoder_config *config, u32 *ctrl)
{
u16 eig;
u8 eiw;
@@ -716,12 +717,12 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
* Input validation ensures these warns never fire, but otherwise
* suppress unititalized variable usage warnings.
*/
- if (WARN_ONCE(ways_to_eiw(cxld->config.interleave_ways, &eiw),
- "invalid interleave_ways: %d\n", cxld->config.interleave_ways))
+ if (WARN_ONCE(ways_to_eiw(config->interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n", config->interleave_ways))
return;
- if (WARN_ONCE(granularity_to_eig(cxld->config.interleave_granularity, &eig),
+ if (WARN_ONCE(granularity_to_eig(config->interleave_granularity, &eig),
"invalid interleave_granularity: %d\n",
- cxld->config.interleave_granularity))
+ config->interleave_granularity))
return;
u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
@@ -729,10 +730,10 @@ static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
*ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
}
-static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
+static void cxld_set_type(const struct cxl_decoder_config *config, u32 *ctrl)
{
u32p_replace_bits(ctrl,
- !!(cxld->config.target_type == CXL_DECODER_HOSTONLYMEM),
+ !!(config->target_type == CXL_DECODER_HOSTONLYMEM),
CXL_HDM_DECODER0_CTRL_HOSTONLY);
}
@@ -764,7 +765,7 @@ static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
* clock skew and other marginal behavior
*/
#define COMMIT_TIMEOUT_MS 20
-static int cxld_await_commit(void __iomem *hdm, int id)
+int cxld_await_commit(void __iomem *hdm, int id)
{
u32 ctrl;
int i;
@@ -784,45 +785,79 @@ static int cxld_await_commit(void __iomem *hdm, int id)
return -ETIMEDOUT;
}
-static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
+static u32 cxl_hdm_write_range(void __iomem *hdm,
+ const struct cxl_decoder_config *config)
{
- int id = cxld->config.id;
- u64 base, size;
+ u64 size = range_len(&config->hpa_range);
+ u64 base = config->hpa_range.start;
+ int id = config->id;
u32 ctrl;
- /* common decoder settings */
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->config.id));
- cxld_set_interleave(cxld, &ctrl);
- cxld_set_type(cxld, &ctrl);
- base = cxld->config.hpa_range.start;
- size = range_len(&cxld->config.hpa_range);
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ cxld_set_interleave(config, &ctrl);
+ cxld_set_type(config, &ctrl);
writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ return ctrl;
+}
+
+void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ int id = settings->config.id;
+ u32 ctrl;
+
+ lockdep_assert_held(&cxl_rwsem.dpa);
+ ctrl = cxl_hdm_write_range(hdm, &settings->config);
+ writel(upper_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
+ writel(lower_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+
+void cxl_commit_start_switch(const struct cxl_decoder_config *config,
+ u64 targets, void __iomem *hdm)
+{
+ int id = config->id;
+ u32 ctrl;
+
+ lockdep_assert_held(&cxl_rwsem.dpa);
+ ctrl = cxl_hdm_write_range(hdm, config);
+ writel(upper_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_HIGH(id));
+ writel(lower_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_LOW(id));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+
+static void
+cxl_endpoint_decoder_snapshot(struct cxl_endpoint_decoder *cxled,
+ struct cxl_endpoint_decoder_settings *settings)
+{
+ lockdep_assert_held(&cxl_rwsem.dpa);
+
+ *settings = (struct cxl_endpoint_decoder_settings) {
+ .config = cxled->cxld.config,
+ .skip = cxled->skip,
+ };
+}
+
+static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
+{
+ struct cxl_endpoint_decoder_settings settings;
if (is_switch_decoder(&cxld->dev)) {
struct cxl_switch_decoder *cxlsd =
to_cxl_switch_decoder(&cxld->dev);
- void __iomem *tl_hi = hdm + CXL_HDM_DECODER0_TL_HIGH(id);
- void __iomem *tl_lo = hdm + CXL_HDM_DECODER0_TL_LOW(id);
u64 targets;
cxlsd_set_targets(cxlsd, &targets);
- writel(upper_32_bits(targets), tl_hi);
- writel(lower_32_bits(targets), tl_lo);
+ cxl_commit_start_switch(&cxld->config, targets, hdm);
} else {
- struct cxl_endpoint_decoder *cxled =
- to_cxl_endpoint_decoder(&cxld->dev);
- void __iomem *sk_hi = hdm + CXL_HDM_DECODER0_SKIP_HIGH(id);
- void __iomem *sk_lo = hdm + CXL_HDM_DECODER0_SKIP_LOW(id);
-
- writel(upper_32_bits(cxled->skip), sk_hi);
- writel(lower_32_bits(cxled->skip), sk_lo);
+ cxl_endpoint_decoder_snapshot(to_cxl_endpoint_decoder(&cxld->dev),
+ &settings);
+ cxl_commit_start_endpoint(&settings, hdm);
}
-
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
}
static int cxl_decoder_commit(struct cxl_decoder *cxld)
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
new file mode 100644
index 000000000000..219b9bb67440
--- /dev/null
+++ b/include/cxl/hdm.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#ifndef __CXL_HDM_H__
+#define __CXL_HDM_H__
+
+#include <linux/types.h>
+#include <cxl/cxl.h>
+
+/**
+ * struct cxl_endpoint_decoder_settings - endpoint HDM decoder programming
+ * @config: common decoder configuration
+ * @skip: length of DPA space skipped before this decoder's DPA mapping
+ */
+struct cxl_endpoint_decoder_settings {
+ struct cxl_decoder_config config;
+ u64 skip;
+};
+
+#endif /* __CXL_HDM_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 06/19] cxl: Move HDM decoder helpers to built-in code
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (4 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 05/19] cxl: Introduce endpoint HDM decoder settings Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:07 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 07/19] cxl: Share HDM decoder register unpacking Srirangan Madhavan
` (13 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
The PCI core will restore HDM decoders after a reset, possibly while no
CXL driver is loaded, so it cannot call into cxl_core.
Move the HDM register programming helpers from hdm.c to hdm_regs.c and
build that file into the kernel whenever CXL_BUS is enabled, including
when CXL_BUS is modular. Export only the symbols that cxl_core uses.
Keep the DPA lock assertion in the normal decoder programming caller so
the register helpers do not depend on cxl_rwsem. Leave cxl_rwsem in
cxl_core until built-in cache management needs it.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Li Ming <ming.li@zohomail.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=e0b476ea99567fb5aa416eac0cf61bca20724880
---
drivers/cxl/core/Makefile | 1 +
drivers/cxl/core/hdm.c | 102 +------------------------------
drivers/cxl/core/hdm_regs.c | 118 ++++++++++++++++++++++++++++++++++++
3 files changed, 121 insertions(+), 100 deletions(-)
create mode 100644 drivers/cxl/core/hdm_regs.c
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index ce7213818d3c..6703c1729396 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
+obj-$(subst m,y,$(CONFIG_CXL_BUS)) += hdm_regs.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index af445f1b9798..2c373dfbc070 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -708,35 +708,6 @@ int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
}
-static void cxld_set_interleave(const struct cxl_decoder_config *config, u32 *ctrl)
-{
- u16 eig;
- u8 eiw;
-
- /*
- * Input validation ensures these warns never fire, but otherwise
- * suppress unititalized variable usage warnings.
- */
- if (WARN_ONCE(ways_to_eiw(config->interleave_ways, &eiw),
- "invalid interleave_ways: %d\n", config->interleave_ways))
- return;
- if (WARN_ONCE(granularity_to_eig(config->interleave_granularity, &eig),
- "invalid interleave_granularity: %d\n",
- config->interleave_granularity))
- return;
-
- u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
- u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
- *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
-}
-
-static void cxld_set_type(const struct cxl_decoder_config *config, u32 *ctrl)
-{
- u32p_replace_bits(ctrl,
- !!(config->target_type == CXL_DECODER_HOSTONLYMEM),
- CXL_HDM_DECODER0_CTRL_HOSTONLY);
-}
-
static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
{
struct cxl_dport **t = &cxlsd->target[0];
@@ -759,77 +730,6 @@ static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
*tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
}
-/*
- * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set
- * committed or error within 10ms, but just be generous with 20ms to account for
- * clock skew and other marginal behavior
- */
-#define COMMIT_TIMEOUT_MS 20
-int cxld_await_commit(void __iomem *hdm, int id)
-{
- u32 ctrl;
- int i;
-
- for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
- ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
- return -EIO;
- }
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
- return 0;
- fsleep(1000);
- }
-
- return -ETIMEDOUT;
-}
-
-static u32 cxl_hdm_write_range(void __iomem *hdm,
- const struct cxl_decoder_config *config)
-{
- u64 size = range_len(&config->hpa_range);
- u64 base = config->hpa_range.start;
- int id = config->id;
- u32 ctrl;
-
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
- cxld_set_interleave(config, &ctrl);
- cxld_set_type(config, &ctrl);
-
- writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
- writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
- writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
- writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
- return ctrl;
-}
-
-void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
- void __iomem *hdm)
-{
- int id = settings->config.id;
- u32 ctrl;
-
- lockdep_assert_held(&cxl_rwsem.dpa);
- ctrl = cxl_hdm_write_range(hdm, &settings->config);
- writel(upper_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
- writel(lower_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
-}
-
-void cxl_commit_start_switch(const struct cxl_decoder_config *config,
- u64 targets, void __iomem *hdm)
-{
- int id = config->id;
- u32 ctrl;
-
- lockdep_assert_held(&cxl_rwsem.dpa);
- ctrl = cxl_hdm_write_range(hdm, config);
- writel(upper_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_HIGH(id));
- writel(lower_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_LOW(id));
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
-}
-
static void
cxl_endpoint_decoder_snapshot(struct cxl_endpoint_decoder *cxled,
struct cxl_endpoint_decoder_settings *settings)
@@ -846,6 +746,8 @@ static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
{
struct cxl_endpoint_decoder_settings settings;
+ lockdep_assert_held(&cxl_rwsem.dpa);
+
if (is_switch_decoder(&cxld->dev)) {
struct cxl_switch_decoder *cxlsd =
to_cxl_switch_decoder(&cxld->dev);
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
new file mode 100644
index 000000000000..53d2f236e49c
--- /dev/null
+++ b/drivers/cxl/core/hdm_regs.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <linux/delay.h>
+#include <linux/bug.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+
+#include <cxl/hdm.h>
+
+#include "cxl.h"
+#include "core.h"
+
+/*
+ * HDM decoder register helpers are built in whenever CXL_BUS is enabled,
+ * including when cxl_core is modular. Callers provide any required locking.
+ */
+
+static void cxld_set_interleave(const struct cxl_decoder_config *config, u32 *ctrl)
+{
+ u16 eig;
+ u8 eiw;
+
+ /*
+ * Input validation ensures these warns never fire, but otherwise
+ * suppress unititalized variable usage warnings.
+ */
+ if (WARN_ONCE(ways_to_eiw(config->interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n", config->interleave_ways))
+ return;
+ if (WARN_ONCE(granularity_to_eig(config->interleave_granularity, &eig),
+ "invalid interleave_granularity: %d\n",
+ config->interleave_granularity))
+ return;
+
+ u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
+ u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
+ *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
+}
+
+static void cxld_set_type(const struct cxl_decoder_config *config, u32 *ctrl)
+{
+ u32p_replace_bits(ctrl,
+ !!(config->target_type == CXL_DECODER_HOSTONLYMEM),
+ CXL_HDM_DECODER0_CTRL_HOSTONLY);
+}
+
+/*
+ * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set
+ * committed or error within 10ms, but just be generous with 20ms to account for
+ * clock skew and other marginal behavior
+ */
+#define COMMIT_TIMEOUT_MS 20
+int cxld_await_commit(void __iomem *hdm, int id)
+{
+ u32 ctrl;
+ int i;
+
+ for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
+ ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ return -EIO;
+ }
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
+ return 0;
+ fsleep(1000);
+ }
+
+ return -ETIMEDOUT;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxld_await_commit, "cxl_core");
+
+static u32 cxl_hdm_write_range(void __iomem *hdm,
+ const struct cxl_decoder_config *config)
+{
+ u64 size = range_len(&config->hpa_range);
+ u64 base = config->hpa_range.start;
+ int id = config->id;
+ u32 ctrl;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ cxld_set_interleave(config, &ctrl);
+ cxld_set_type(config, &ctrl);
+
+ writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
+ writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
+ writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ return ctrl;
+}
+
+void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ int id = settings->config.id;
+ u32 ctrl;
+
+ ctrl = cxl_hdm_write_range(hdm, &settings->config);
+ writel(upper_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
+ writel(lower_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start_endpoint, "cxl_core");
+
+void cxl_commit_start_switch(const struct cxl_decoder_config *config,
+ u64 targets, void __iomem *hdm)
+{
+ int id = config->id;
+ u32 ctrl;
+
+ ctrl = cxl_hdm_write_range(hdm, config);
+ writel(upper_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_HIGH(id));
+ writel(lower_32_bits(targets), hdm + CXL_HDM_DECODER0_TL_LOW(id));
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start_switch, "cxl_core");
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 07/19] cxl: Share HDM decoder register unpacking
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (5 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 06/19] cxl: Move HDM decoder helpers to built-in code Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:16 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 08/19] cxl: Move cxl_rwsem into built-in code Srirangan Madhavan
` (12 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Factor common HDM register parsing into cxl_hdm_unpack_decoder().
Validate range and interleave parameters in locals before publishing the
decoder configuration, including enable and lock flags. Reject nonzero
ranges whose end overflows the address space while preserving the
zero-size range representation.
Read decoder base and size registers low then high through a shared
helper used by enumeration and the reset snapshot.
Read switch target lists and endpoint DPA skip in their respective
branches of init_hdm_decoder().
Preserve the decoder ID allocated by cxl_decoder_init() when publishing
the parsed configuration.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
Changes in v17:
- Initialize the zero-size end to base - 1 and document the zero-size
case, removing a branch (Richard)
https://lore.kernel.org/linux-cxl/auQ27s1rSCcbdRJ3@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=7d6ed7faf7d8e14abce8ae23902d5323738868d2
---
drivers/cxl/core/core.h | 5 +++
drivers/cxl/core/hdm.c | 65 ++++++++------------------------
drivers/cxl/core/hdm_regs.c | 74 +++++++++++++++++++++++++++++++++++++
3 files changed, 95 insertions(+), 49 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index fb14b222f3ca..ea539f00e55d 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -147,6 +147,11 @@ void cxl_commit_start_switch(const struct cxl_decoder_config *config,
u64 targets, void __iomem *hdm);
int cxld_await_commit(void __iomem *hdm, int id);
+int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
+ u32 ctrl, u64 base, u64 size);
+int cxl_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
+ int id, u32 ctrl);
+
int cxl_memdev_init(void);
void cxl_memdev_exit(void);
void cxl_mbox_init(void);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 2c373dfbc070..5b5f5f22ea32 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -938,11 +938,11 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
u64 *dpa_base, struct cxl_endpoint_dvsec_info *info)
{
struct cxl_endpoint_decoder *cxled = NULL;
- u64 size, base, skip, dpa_size, lo, hi;
- bool committed;
+ u64 size, skip, dpa_size, lo, hi;
+ struct cxl_decoder_config config;
u32 remainder;
- int i, rc;
u32 ctrl;
+ int i, rc;
union {
u64 value;
unsigned char target_id[8];
@@ -953,41 +953,25 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
which, info);
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
- lo = readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(which));
- hi = readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(which));
- base = (hi << 32) + lo;
- lo = readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(which));
- hi = readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(which));
- size = (hi << 32) + lo;
- committed = !!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED);
+ rc = cxl_hdm_read_decoder(&config, hdm, which, ctrl);
+ if (rc) {
+ dev_warn(&port->dev,
+ "decoder%d.%d: Invalid decoder configuration (ctrl: %#x): %d\n",
+ port->id, cxld->config.id, ctrl, rc);
+ return rc;
+ }
+
+ config.id = cxld->config.id;
cxld->commit = cxl_decoder_commit;
cxld->reset = cxl_decoder_reset;
-
- if (!committed)
- size = 0;
- if (base == U64_MAX || size == U64_MAX) {
- dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n",
- port->id, cxld->config.id);
- return -ENXIO;
- }
+ cxld->config = config;
+ size = range_len(&cxld->config.hpa_range);
if (info)
cxled = to_cxl_endpoint_decoder(&cxld->dev);
- cxld->config.hpa_range = (struct range) {
- .start = base,
- .end = base + size - 1,
- };
/* decoders are enabled if committed */
- if (committed) {
- cxld->config.flags |= CXL_DECODER_F_ENABLE;
- if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
- cxld->config.flags |= CXL_DECODER_F_LOCK;
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
- cxld->config.target_type = CXL_DECODER_HOSTONLYMEM;
- else
- cxld->config.target_type = CXL_DECODER_DEVMEM;
-
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE) {
guard(rwsem_write)(&cxl_rwsem.region);
if (cxld->config.id != cxl_num_decoders_committed(port)) {
dev_warn(&port->dev,
@@ -1021,23 +1005,6 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
}
}
- rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
- &cxld->config.interleave_ways);
- if (rc) {
- dev_warn(&port->dev,
- "decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
- port->id, cxld->config.id, ctrl);
- return rc;
- }
- rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
- &cxld->config.interleave_granularity);
- if (rc) {
- dev_warn(&port->dev,
- "decoder%d.%d: Invalid interleave granularity (ctrl: %#x)\n",
- port->id, cxld->config.id, ctrl);
- return rc;
- }
-
dev_dbg(&port->dev, "decoder%d.%d: range: %#llx-%#llx iw: %d ig: %d\n",
port->id, cxld->config.id, cxld->config.hpa_range.start, cxld->config.hpa_range.end,
cxld->config.interleave_ways, cxld->config.interleave_granularity);
@@ -1052,7 +1019,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
return 0;
}
- if (!committed)
+ if (!(cxld->config.flags & CXL_DECODER_F_ENABLE))
return 0;
dpa_size = div_u64_rem(size, cxld->config.interleave_ways, &remainder);
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index 53d2f236e49c..cc5d77c4abf5 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -116,3 +116,77 @@ void cxl_commit_start_switch(const struct cxl_decoder_config *config,
writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
}
EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start_switch, "cxl_core");
+
+int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
+ u32 ctrl, u64 base, u64 size)
+{
+ bool committed = FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
+ enum cxl_decoder_type target_type = 0;
+ int interleave_granularity;
+ unsigned long flags = 0;
+ struct range hpa_range;
+ int interleave_ways;
+ u64 end;
+ int rc;
+
+ if (!committed)
+ size = 0;
+ if (base == U64_MAX || size == U64_MAX)
+ return -ENXIO;
+ /* A zero-size decoder maps no HPA; its range is empty at @base. */
+ end = base - 1;
+ if (size && check_add_overflow(base, size - 1, &end))
+ return -ENXIO;
+
+ hpa_range = (struct range) {
+ .start = base,
+ .end = end,
+ };
+
+ if (committed) {
+ flags |= CXL_DECODER_F_ENABLE;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
+ flags |= CXL_DECODER_F_LOCK;
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
+ target_type = CXL_DECODER_HOSTONLYMEM;
+ else
+ target_type = CXL_DECODER_DEVMEM;
+ }
+
+ rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
+ &interleave_ways);
+ if (rc)
+ return rc;
+ rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
+ &interleave_granularity);
+ if (rc)
+ return rc;
+
+ *config = (struct cxl_decoder_config) {
+ .id = id,
+ .hpa_range = hpa_range,
+ .interleave_ways = interleave_ways,
+ .interleave_granularity = interleave_granularity,
+ .target_type = target_type,
+ .flags = flags,
+ };
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
+
+int cxl_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
+ int id, u32 ctrl)
+{
+ u64 lo, hi, base, size;
+
+ lo = readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ hi = readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
+ base = (hi << 32) | lo;
+ lo = readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ hi = readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
+ size = (hi << 32) | lo;
+
+ return cxl_hdm_unpack_decoder(config, id, ctrl, base, size);
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_read_decoder, "cxl_core");
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 08/19] cxl: Move cxl_rwsem into built-in code
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (6 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 07/19] cxl: Share HDM decoder register unpacking Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:09 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
` (11 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Keep the region and DPA locks available to built-in CXL state management
when cxl_core is modular.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
drivers/cxl/core/Makefile | 2 +-
drivers/cxl/core/hdm.c | 5 -----
drivers/cxl/core/hdm_state.c | 15 +++++++++++++++
3 files changed, 16 insertions(+), 6 deletions(-)
create mode 100644 drivers/cxl/core/hdm_state.c
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 6703c1729396..9971efb237fb 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
-obj-$(subst m,y,$(CONFIG_CXL_BUS)) += hdm_regs.o
+obj-$(subst m,y,$(CONFIG_CXL_BUS)) += hdm_regs.o hdm_state.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 5b5f5f22ea32..85f9abf58a42 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -17,11 +17,6 @@
* for enumerating these registers and capabilities.
*/
-struct cxl_rwsem cxl_rwsem = {
- .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
- .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
-};
-
static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld)
{
int rc;
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
new file mode 100644
index 000000000000..c9cc019902ef
--- /dev/null
+++ b/drivers/cxl/core/hdm_state.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <linux/export.h>
+#include <linux/rwsem.h>
+
+#include <cxl/hdm.h>
+
+#include "cxl.h"
+#include "core.h"
+
+struct cxl_rwsem cxl_rwsem = {
+ .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
+ .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
+};
+EXPORT_SYMBOL_FOR_MODULES(cxl_rwsem, "cxl_core");
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (7 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 08/19] cxl: Move cxl_rwsem into built-in code Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:17 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 10/19] cxl: Build register block helpers into the kernel Srirangan Madhavan
` (10 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Early PCI discovery creates the HDM cache, while later CXL enumeration and
decoder operations provide updated programming state.
Refresh the PCI snapshot when decoders are enumerated, committed, or reset
so reset recovery need not walk the CXL topology. Keep cache access and
locking in the built-in HDM state helpers. Ignore updates when no cache
exists and reject decoder-count mismatches.
Use CXL_RESET for the PCI HDM cache. Shared HDM register helpers do not
require it.
Skip cache checks and updates for DVSEC-emulated decoders, which do not
use HDM registers. Keep cached global control when MMIO reads return all
ones.
Non-PCI mock ports have no upstream PCI device, so the cache helpers
ignore them. This avoids test wrappers and a circular dependency between
cxl_core and cxl_mock.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=50a88f5194e5df38590d413f925d8d5afa8a8488
---
drivers/cxl/Kconfig | 5 ++++
drivers/cxl/core/core.h | 1 +
drivers/cxl/core/hdm.c | 34 +++++++++++++++++++--
drivers/cxl/core/hdm_state.c | 57 ++++++++++++++++++++++++++++++++++++
drivers/cxl/core/pci.c | 24 +++++++++++++++
include/cxl/cxl.h | 1 +
include/cxl/hdm.h | 32 ++++++++++++++++++++
include/linux/pci.h | 6 ++++
8 files changed, 158 insertions(+), 2 deletions(-)
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 80aeb0d556bd..8b08bfdc4982 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -6,6 +6,7 @@ menuconfig CXL_BUS
select FW_UPLOAD
select PCI_DOE
select FIRMWARE_TABLE
+ select CXL_RESET
select NUMA_KEEP_MEMINFO if NUMA_MEMBLKS
select FWCTL if CXL_FEATURES
help
@@ -244,3 +245,7 @@ config CXL_ATL
depends on ACPI_PRMT && AMD_NB
endif
+
+# CXL_BUS requires the PCI HDM cache and built-in state helpers, even when modular.
+config CXL_RESET
+ bool
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index ea539f00e55d..e34d574f6594 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -224,6 +224,7 @@ static inline void devm_cxl_dport_ras_setup(struct cxl_dport *dport) { }
int cxl_gpf_port_setup(struct cxl_dport *dport);
struct cxl_hdm;
+int cxl_hdm_check_decoder_count(struct cxl_hdm *cxlhdm);
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm,
struct cxl_endpoint_dvsec_info *info);
int cxl_port_get_possible_dports(struct cxl_port *port);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 85f9abf58a42..ec387070d7ad 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -725,6 +725,24 @@ static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
*tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
}
+static void cxl_hdm_refresh_decoder(struct cxl_hdm *cxlhdm,
+ struct cxl_decoder *cxld)
+{
+ void __iomem *hdm = cxlhdm->regs.hdm_decoder;
+ struct pci_dev *pdev __free(pci_dev_put) = NULL;
+
+ if (!hdm)
+ return;
+ if (!is_endpoint_decoder(&cxld->dev))
+ return;
+
+ pdev = cxl_port_get_uport_pci_dev(cxlhdm->port);
+ if (!pdev)
+ return;
+ cxl_hdm_cache_refresh_decoder(pdev, hdm,
+ to_cxl_endpoint_decoder(&cxld->dev));
+}
+
static void
cxl_endpoint_decoder_snapshot(struct cxl_endpoint_decoder *cxled,
struct cxl_endpoint_decoder_settings *settings)
@@ -805,6 +823,7 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
}
port->commit_end++;
cxld->config.flags |= CXL_DECODER_F_ENABLE;
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
return 0;
}
@@ -877,6 +896,7 @@ static void cxl_decoder_reset(struct cxl_decoder *cxld)
writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
cxld->config.flags &= ~CXL_DECODER_F_ENABLE;
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
/* Userspace is now responsible for reconfiguring this decoder */
if (is_endpoint_decoder(&cxld->dev)) {
@@ -1079,13 +1099,21 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
{
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
struct cxl_port *port = cxlhdm->port;
- int i;
+ /* DVSEC-emulated decoders do not describe the cached HDM registers. */
+ bool emulated = should_emulate_decoders(info);
u64 dpa_base = 0;
+ int i, rc;
+
+ if (is_cxl_endpoint(port) && hdm && !emulated) {
+ rc = cxl_hdm_check_decoder_count(cxlhdm);
+ if (rc)
+ return rc;
+ }
cxl_settle_decoders(cxlhdm);
for (i = 0; i < cxlhdm->decoder_count; i++) {
- int rc, target_count = cxlhdm->target_count;
+ int target_count = cxlhdm->target_count;
struct cxl_decoder *cxld;
if (is_cxl_endpoint(port)) {
@@ -1120,6 +1148,8 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
put_device(&cxld->dev);
return rc;
}
+ if (!emulated)
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
rc = add_hdm_decoder(port, cxld);
if (rc) {
dev_warn(&port->dev,
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index c9cc019902ef..d4c2be1ef833 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/pci.h>
#include <linux/rwsem.h>
#include <cxl/hdm.h>
@@ -13,3 +15,58 @@ struct cxl_rwsem cxl_rwsem = {
.dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
};
EXPORT_SYMBOL_FOR_MODULES(cxl_rwsem, "cxl_core");
+
+void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
+ struct cxl_endpoint_decoder *cxled)
+{
+ struct cxl_endpoint_decoder_settings *settings;
+ struct cxl_decoder *cxld = &cxled->cxld;
+ struct cxl_hdm_info *info;
+ u32 global_ctrl;
+ int id;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ id = cxld->config.id;
+ info = pdev->hdm;
+ if (!info)
+ return;
+ if (id < 0 || id >= info->decoder_count) {
+ pci_warn(pdev, "CXL HDM decoder %d exceeds cached count %d\n",
+ id, info->decoder_count);
+ return;
+ }
+
+ /* Preserve the last good value when the device does not respond. */
+ global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ if (global_ctrl != U32_MAX)
+ info->global_ctrl = global_ctrl;
+ settings = &info->settings[id];
+
+ /*
+ * A disabled decoder's software object may retain its old range and
+ * skip state. Leave only the decoder id in the cached settings so stale
+ * state is not restored as an enabled decode.
+ */
+ *settings = (struct cxl_endpoint_decoder_settings) {
+ .config.id = id,
+ };
+ if (cxld->config.flags & CXL_DECODER_F_ENABLE) {
+ settings->config = cxld->config;
+ settings->skip = cxled->skip;
+ }
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_refresh_decoder, "cxl_core");
+
+int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count)
+{
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ if (pdev->hdm && pdev->hdm->decoder_count != count) {
+ pci_warn(pdev,
+ "CXL HDM cache decoder count mismatch: cached=%d hdm=%d\n",
+ pdev->hdm->decoder_count, count);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_check_decoder_count, "cxl_core");
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index f72cd8f50e77..5272013548c7 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -6,6 +6,7 @@
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
+#include <cxl/hdm.h>
#include <cxl/pci.h>
#include <linux/aer.h>
#include <cxlpci.h>
@@ -180,6 +181,29 @@ int cxl_await_media_ready(struct cxl_dev_state *cxlds)
}
EXPORT_SYMBOL_NS_GPL(cxl_await_media_ready, "CXL");
+/**
+ * cxl_hdm_check_decoder_count() - check enumeration against the PCI HDM cache
+ * @cxlhdm: HDM capability containing the decoder count to check
+ *
+ * Take cxl_rwsem.dpa for read while comparing the cached count. Call only
+ * for hardware HDM decoders, since DVSEC-emulated ranges do not describe
+ * the HDM registers represented by the cache.
+ *
+ * Return: 0 if the counts match or no upstream PCI device or cache exists,
+ * or -ENXIO if the cached and enumerated decoder counts differ.
+ */
+int cxl_hdm_check_decoder_count(struct cxl_hdm *cxlhdm)
+{
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(cxlhdm->port);
+
+ if (!pdev)
+ return 0;
+
+ return cxl_hdm_cache_check_decoder_count(pdev, cxlhdm->decoder_count);
+}
+EXPORT_SYMBOL_NS_GPL(cxl_hdm_check_decoder_count, "CXL");
+
static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 6d6e564be27a..341a7c52894a 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -26,6 +26,7 @@ enum cxl_devtype {
};
struct cxl_region;
+struct cxl_hdm_info;
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 219b9bb67440..06e9aa4479c2 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -6,6 +6,9 @@
#include <linux/types.h>
#include <cxl/cxl.h>
+struct pci_dev;
+struct cxl_endpoint_decoder;
+
/**
* struct cxl_endpoint_decoder_settings - endpoint HDM decoder programming
* @config: common decoder configuration
@@ -16,4 +19,33 @@ struct cxl_endpoint_decoder_settings {
u64 skip;
};
+/**
+ * struct cxl_hdm_info - cached CXL HDM state for a PCI device
+ * @decoder_count: number of entries in @settings
+ * @hdm_bar: PCI BAR containing the HDM decoder capability
+ * @hdm_offset: offset of the HDM decoder capability in @hdm_bar
+ * @hdm_size: size of the HDM decoder register block
+ * @global_ctrl: HDM decoder global control register
+ * @dvsec_ctrl: CXL DVSEC control register
+ * @dvsec_ctrl_valid: whether @dvsec_ctrl contains valid state
+ * @settings: per-endpoint-decoder programming state
+ *
+ * Publication through pci_dev.hdm and all mutable fields are protected by
+ * cxl_rwsem.dpa.
+ */
+struct cxl_hdm_info {
+ int decoder_count;
+ int hdm_bar;
+ resource_size_t hdm_offset;
+ resource_size_t hdm_size;
+ u32 global_ctrl;
+ u16 dvsec_ctrl;
+ bool dvsec_ctrl_valid;
+ struct cxl_endpoint_decoder_settings settings[] __counted_by(decoder_count);
+};
+
+void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
+ struct cxl_endpoint_decoder *cxled);
+int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count);
+
#endif /* __CXL_HDM_H__ */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d31a8d107b1e..7bb37fcb556d 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -339,6 +339,9 @@ struct pcie_link_state;
struct pci_sriov;
struct pci_p2pdma;
struct rcec_ea;
+#ifdef CONFIG_CXL_RESET
+struct cxl_hdm_info;
+#endif
/* struct pci_dev - describes a PCI device
*
@@ -566,6 +569,9 @@ struct pci_dev {
#ifdef CONFIG_PCI_DOE
struct xarray doe_mbs; /* Data Object Exchange mailboxes */
#endif
+#ifdef CONFIG_CXL_RESET
+ struct cxl_hdm_info *hdm; /* CXL HDM decoder state */
+#endif
#ifdef CONFIG_PCI_NPEM
struct npem *npem; /* Native PCIe Enclosure Management */
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings
2026-11-05 18:59 ` [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
@ 2026-11-05 19:17 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:17 UTC (permalink / raw)
To: Srirangan Madhavan, linux-cxl, linux-pci
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] cxl: split declaration and init of __free() variable in cxl_hdm_refresh_decoder()
- [Low] cxl: unused struct cxl_hdm_info forward declaration in include/cxl/cxl.h
--
commit 0273d4d08ae78949f901788b93c7db64fb203e17
Author: Srirangan Madhavan <smadhavan@nvidia.com>
cxl: Refresh cached PCI HDM decoder settings
This commit refreshes the cached PCI HDM decoder state when hardware HDM
decoders are enumerated, committed, or reset, and validates the enumerated
decoder count against the cached count.
> diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
> index 85f9abf58a42b..ec387070d7adb 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);
[Severity: Low]
As reported by Cheatham, Benjamin on v13 [1], this isn't a bug, but in
cxl_hdm_refresh_decoder() in drivers/cxl/core/hdm.c, pdev is declared with
__free(pci_dev_put) = NULL at the top of the function and assigned later after
the !hdm and !is_endpoint_decoder(&cxld->dev) early returns.
Should pdev be defined and initialized in a single statement where
cxl_port_get_uport_pci_dev(cxlhdm->port) is called after the early returns,
matching cxl_hdm_check_decoder_count() in drivers/cxl/core/pci.c and the
kernel Cleanup API conventions?
[ ... ]
> diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
> index 6d6e564be27a9..341a7c52894ab 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;
[Severity: Low]
This isn't a bug, but struct cxl_hdm_info is not referenced anywhere in
include/cxl/cxl.h (it is defined in include/cxl/hdm.h and forward-declared in
include/linux/pci.h). Can this forward declaration be dropped from
include/cxl/cxl.h?
[1] https://lore.kernel.org/r/2c37c82b-8db6-44d1-868b-0b98bae57459@amd.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=9
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 10/19] cxl: Build register block helpers into the kernel
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (8 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 09/19] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:23 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
` (9 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Build register discovery with the built-in HDM helpers so PCI enumeration
can use it even when cxl_core is modular. Export the interfaces used by
cxl_core and preserve the BAR and offset in the register map.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
drivers/cxl/core/Makefile | 3 +--
drivers/cxl/core/regs.c | 9 +++++++++
drivers/cxl/cxl.h | 3 ---
include/cxl/cxl.h | 5 +++++
include/cxl/pci.h | 13 +++++++++++++
tools/testing/cxl/Kbuild | 1 -
6 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 9971efb237fb..9a77e68d44ce 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
-obj-$(subst m,y,$(CONFIG_CXL_BUS)) += hdm_regs.o hdm_state.o
+obj-$(subst m,y,$(CONFIG_CXL_BUS)) += hdm_regs.o hdm_state.o regs.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
@@ -8,7 +8,6 @@ CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
cxl_core-y += pmem.o
-cxl_core-y += regs.o
cxl_core-y += memdev.o
cxl_core-y += mbox.o
cxl_core-y += pci.o
diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c
index 20c2d9fbcfe7..def3c20d1a7a 100644
--- a/drivers/cxl/core/regs.c
+++ b/drivers/cxl/core/regs.c
@@ -200,6 +200,7 @@ void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr,
return ret_val;
}
+EXPORT_SYMBOL_FOR_MODULES(devm_cxl_iomap_block, "cxl_core");
int cxl_map_component_regs(const struct cxl_register_map *map,
struct cxl_component_regs *regs,
@@ -285,6 +286,8 @@ static bool cxl_decode_regblock(struct pci_dev *pdev, u32 reg_lo, u32 reg_hi,
}
map->reg_type = reg_type;
+ map->bar = bar;
+ map->bar_offset = offset;
map->resource = pci_resource_start(pdev, bar) + offset;
map->max_size = pci_resource_len(pdev, bar) - offset;
return true;
@@ -309,6 +312,7 @@ static int __cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_ty
*map = (struct cxl_register_map) {
.host = &pdev->dev,
.resource = CXL_RESOURCE_NONE,
+ .bar = -1,
};
regloc = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
@@ -339,6 +343,8 @@ static int __cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_ty
}
map->resource = CXL_RESOURCE_NONE;
+ map->bar = -1;
+ map->bar_offset = 0;
if (index == CXL_INSTANCES_COUNT)
return instance;
@@ -518,6 +524,7 @@ u16 cxl_rcrb_to_aer(struct device *dev, resource_size_t rcrb)
return offset;
}
+EXPORT_SYMBOL_NS_GPL(cxl_rcrb_to_aer, "CXL");
static resource_size_t cxl_rcrb_to_linkcap(struct device *dev, struct cxl_dport *dport)
{
@@ -634,6 +641,7 @@ resource_size_t __rcrb_to_component(struct device *dev, struct cxl_rcrb_info *ri
return component_reg_phys;
}
+EXPORT_SYMBOL_NS_GPL(__rcrb_to_component, "CXL");
resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
struct cxl_dport *dport)
@@ -642,3 +650,4 @@ resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
return CXL_RESOURCE_NONE;
return __rcrb_to_component(dev, &dport->rcrb, CXL_RCRB_UPSTREAM);
}
+EXPORT_SYMBOL_NS_GPL(cxl_rcd_component_reg_phys, "CXL");
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 688350c7c5fb..ad4e80be2bd7 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -239,9 +239,6 @@ enum cxl_regloc_type;
int cxl_count_regblock(struct pci_dev *pdev, enum cxl_regloc_type type);
int cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map, unsigned int index);
-int cxl_find_regblock(struct pci_dev *pdev, enum cxl_regloc_type type,
- struct cxl_register_map *map);
-int cxl_setup_regs(struct cxl_register_map *map);
struct cxl_dport;
int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 341a7c52894a..ea23bc15d00e 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -27,6 +27,7 @@ enum cxl_devtype {
struct cxl_region;
struct cxl_hdm_info;
+struct pci_dev;
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
@@ -150,6 +151,8 @@ struct cxl_pmu_reg_map {
* @base: virtual base of the register-block-BAR + @block_offset
* @resource: physical resource base of the register block
* @max_size: maximum mapping size to perform register search
+ * @bar: PCI BAR containing the register block
+ * @bar_offset: register block offset relative to @bar
* @reg_type: see enum cxl_regloc_type
* @component_map: cxl_reg_map for component registers
* @device_map: cxl_reg_maps for device registers
@@ -160,6 +163,8 @@ struct cxl_register_map {
void __iomem *base;
resource_size_t resource;
resource_size_t max_size;
+ int bar;
+ resource_size_t bar_offset;
u8 reg_type;
union {
struct cxl_component_reg_map component_map;
diff --git a/include/cxl/pci.h b/include/cxl/pci.h
index 3e0000015871..b15d7e415e27 100644
--- a/include/cxl/pci.h
+++ b/include/cxl/pci.h
@@ -4,6 +4,8 @@
#ifndef __CXL_CXL_PCI_H__
#define __CXL_CXL_PCI_H__
+#include <linux/types.h>
+
/* Register Block Identifier (RBI) */
enum cxl_regloc_type {
CXL_REGLOC_RBI_EMPTY = 0,
@@ -19,4 +21,15 @@ struct pci_dev;
int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map);
+int cxl_find_regblock(struct pci_dev *pdev, enum cxl_regloc_type type,
+ struct cxl_register_map *map);
+int cxl_setup_regs(struct cxl_register_map *map);
+/*
+ * CXL r4.0 sec 8.1.3.1: a CXL.mem capable device reports one or two HDM
+ * ranges. 00b is illegal when Mem_Capable is set and 11b is reserved.
+ */
+static inline bool cxl_dvsec_hdm_count_valid(unsigned int hdm_count)
+{
+ return hdm_count == 1 || hdm_count == 2;
+}
#endif
diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild
index 2be1df80fcc9..e80500f457a9 100644
--- a/tools/testing/cxl/Kbuild
+++ b/tools/testing/cxl/Kbuild
@@ -55,7 +55,6 @@ obj-m += cxl_core.o
cxl_core-y := $(CXL_CORE_SRC)/port.o
cxl_core-y += $(CXL_CORE_SRC)/pmem.o
-cxl_core-y += $(CXL_CORE_SRC)/regs.o
cxl_core-y += $(CXL_CORE_SRC)/memdev.o
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
cxl_core-y += $(CXL_CORE_SRC)/pci.o
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (9 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 10/19] cxl: Build register block helpers into the kernel Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:30 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
` (8 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Cache HDM state in pci_dev->hdm after BAR resource setup and before driver
binding, so reset can restore it without a bound CXL driver.
Limit caching to devices that advertise CXL.cache, CXL.mem, CXL Reset,
and Reset Memory Clear. Save their HDM register location, decoder
settings, and Device DVSEC state.
Validate capability reads and decoder counts, and restore PCI_COMMAND
after temporary MMIO access, reporting restoration failures. Publish the
cache under the PCI device lock and protect later updates with the DPA
lock.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
Changes in v17:
- Publish the cache under the PCI device lock without taking
cxl_rwsem.dpa, and drop the cxl_hdm_cache_present() and
cxl_hdm_cache_publish() helpers (Li Ming)
https://lore.kernel.org/linux-cxl/d581f2a0-dd9e-4d0a-bd19-639c1bd9b832@zohomail.com/
- Reject an all-ones DVSEC Control read in cxl_set_mem_enable() before
it is written back or cached (Sashiko)
https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=11
- Explain why cxl_set_mem_enable() refreshes the cached DVSEC Control
when MEM_ENABLE already matches (Richard)
https://lore.kernel.org/linux-cxl/auRQN6a3QVtqjd5a@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=b12baa1105f33cc1a10f9c9c0c6376194397d9f3
---
MAINTAINERS | 1 +
drivers/cxl/core/hdm_regs.c | 96 +++++++++++++++
drivers/cxl/core/hdm_state.c | 37 +++++-
drivers/cxl/core/pci.c | 29 +++--
drivers/pci/Makefile | 1 +
drivers/pci/bus.c | 1 +
drivers/pci/cxl.c | 225 ++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 8 ++
drivers/pci/probe.c | 1 +
include/cxl/hdm.h | 9 ++
include/uapi/linux/pci_regs.h | 2 +
11 files changed, 399 insertions(+), 11 deletions(-)
create mode 100644 drivers/pci/cxl.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c2414447892c..b874340b0265 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6572,6 +6572,7 @@ S: Maintained
F: Documentation/driver-api/cxl
F: Documentation/userspace-api/fwctl/fwctl-cxl.rst
F: drivers/cxl/
+F: drivers/pci/cxl.c
F: include/cxl/
F: include/uapi/linux/cxl_mem.h
F: tools/testing/cxl/
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index cc5d77c4abf5..c92dbb017162 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -2,6 +2,7 @@
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
#include <linux/delay.h>
#include <linux/bug.h>
+#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/kernel.h>
@@ -190,3 +191,98 @@ int cxl_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
return cxl_hdm_unpack_decoder(config, id, ctrl, base, size);
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_read_decoder, "cxl_core");
+
+/**
+ * cxl_hdm_decoder_count_mapped() - read the decoder count of a mapped block
+ * @dev: device for error reporting
+ * @hdm: mapped HDM decoder capability
+ * @size: size of the mapping
+ *
+ * Return: the decoder count, or a negative errno if the count is reserved or
+ * the mapping cannot hold every decoder.
+ */
+int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
+ resource_size_t size)
+{
+ int count;
+
+ count = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (count < 0)
+ return count;
+
+ /* The last decoder's target-list high register must fit in the mapping. */
+ if (CXL_HDM_DECODER0_TL_HIGH(count - 1) + sizeof(u32) > size) {
+ dev_err(dev, "CXL HDM decoder count exceeds mapped register block\n");
+ return -ENXIO;
+ }
+
+ return count;
+}
+
+/* Read one endpoint decoder; a disabled decoder keeps only its id. */
+static int cxl_hdm_read_decoder_settings(struct device *dev,
+ struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm, int id)
+{
+ u64 lo, hi;
+ u32 ctrl;
+ int rc;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ rc = cxl_hdm_read_decoder(&settings->config, hdm, id, ctrl);
+ if (rc) {
+ dev_err(dev,
+ "CXL HDM decoder %d has invalid configuration: %d\n",
+ id, rc);
+ return rc;
+ }
+
+ if (!(settings->config.flags & CXL_DECODER_F_ENABLE)) {
+ *settings = (struct cxl_endpoint_decoder_settings) {
+ .config.id = id,
+ };
+ } else {
+ lo = readl(hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
+ hi = readl(hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
+ settings->skip = (hi << 32) | lo;
+ }
+
+ return 0;
+}
+
+/**
+ * cxl_hdm_read_state() - read endpoint HDM decoder state
+ * @dev: device for error messages
+ * @hdm: mapped HDM decoder capability
+ * @info: state to fill; @info->decoder_count must be set
+ *
+ * Return: 0 on success, or a negative errno if a decoder holds an invalid
+ * configuration or the decoder count changed while reading.
+ */
+int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
+ struct cxl_hdm_info *info)
+{
+ int count;
+ int rc;
+
+ info->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ for (int i = 0; i < info->decoder_count; i++) {
+ rc = cxl_hdm_read_decoder_settings(dev, &info->settings[i],
+ hdm, i);
+ if (rc)
+ return rc;
+ }
+
+ /*
+ * A changed decoder count means the snapshot no longer describes the
+ * complete hardware state. Reject it rather than publish a partial cache.
+ */
+ count = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (count != info->decoder_count) {
+ dev_err(dev, "CXL HDM decoder count changed from %d to %d\n",
+ info->decoder_count, count);
+ return -ENXIO;
+ }
+
+ return 0;
+}
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index d4c2be1ef833..9def4a7439c9 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -1,21 +1,37 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <linux/cleanup.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/rwsem.h>
+#include <linux/slab.h>
#include <cxl/hdm.h>
-
#include "cxl.h"
#include "core.h"
+/*
+ * CXL state that must stay available while no CXL driver is bound: the CXL
+ * locks and the per-device HDM cache filled during PCI enumeration.
+ */
struct cxl_rwsem cxl_rwsem = {
.region = __RWSEM_INITIALIZER(cxl_rwsem.region),
.dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
};
EXPORT_SYMBOL_FOR_MODULES(cxl_rwsem, "cxl_core");
+void cxl_hdm_cache_set_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl)
+{
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ if (!pdev->hdm)
+ return;
+
+ pdev->hdm->dvsec_ctrl = ctrl;
+ pdev->hdm->dvsec_ctrl_valid = true;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_set_dvsec_ctrl, "cxl_core");
+
void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
struct cxl_endpoint_decoder *cxled)
{
@@ -70,3 +86,22 @@ int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count)
return 0;
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_check_decoder_count, "cxl_core");
+
+/**
+ * cxl_hdm_cache_release() - unpublish and free cached HDM state
+ * @slot: cache pointer owned by the device
+ *
+ * Take the DPA write lock to exclude readers while clearing @slot, then
+ * free the unpublished cache after releasing the lock.
+ */
+void cxl_hdm_cache_release(struct cxl_hdm_info **slot)
+{
+ struct cxl_hdm_info *info;
+
+ scoped_guard(rwsem_write, &cxl_rwsem.dpa) {
+ info = *slot;
+ /* Unpublish before freeing so readers cannot see stale state. */
+ *slot = NULL;
+ }
+ kfree(info);
+}
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index 5272013548c7..ea5327cd627f 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -209,22 +209,31 @@ static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
int d = cxlds->cxl_dvsec;
u16 ctrl;
- int rc;
+ int rc, ret = 1;
rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, &ctrl);
if (rc)
return pcibios_err_to_errno(rc);
+ /* An all-ones read must not be written back or cached. */
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return -ENODEV;
- if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) == val)
- return 1;
- ctrl &= ~PCI_DVSEC_CXL_MEM_ENABLE;
- ctrl |= val;
+ if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) != val) {
+ ctrl &= ~PCI_DVSEC_CXL_MEM_ENABLE;
+ ctrl |= val;
- rc = pci_write_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, ctrl);
- if (rc)
- return pcibios_err_to_errno(rc);
+ rc = pci_write_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, ctrl);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ ret = 0;
+ }
- return 0;
+ /*
+ * Refresh the PCI-core cache from the live register even when
+ * MEM_ENABLE already matched: a reset restores the cached value.
+ */
+ cxl_hdm_cache_set_dvsec_ctrl(pdev, ctrl);
+ return ret;
}
static void clear_mem_enable(void *cxlds)
@@ -314,7 +323,7 @@ int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds,
* capable, there is no point in continuing to enable CXL.mem.
*/
hdm_count = FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap);
- if (!hdm_count || hdm_count > 2)
+ if (!cxl_dvsec_hdm_count_valid(hdm_count))
return -EINVAL;
/*
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..7a5d9f7297e9 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_PCI_P2PDMA) += p2pdma.o
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_PCI_DOE) += doe.o
+obj-$(CONFIG_CXL_RESET) += cxl.o
obj-$(CONFIG_PCI_IDE) += ide.o
obj-$(CONFIG_PCI_TSM) += tsm.o
obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
index 655ed53436d3..aad567fb7c56 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -359,6 +359,7 @@ void pci_bus_add_device(struct pci_dev *dev)
/* Save config space for error recoverability */
pci_save_state(dev);
+ pci_cxl_hdm_cache_init(dev);
/*
* Enable runtime PM, which potentially allows the device to
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
new file mode 100644
index 000000000000..a0d20f4210a5
--- /dev/null
+++ b/drivers/pci/cxl.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+/*
+ * CXL reset support in the PCI core. Cache the HDM decoder state of a CXL
+ * memory device at enumeration, so it can be restored after a reset without
+ * a CXL driver. HDM register formats and programming live in drivers/cxl.
+ */
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include <cxl/hdm.h>
+#include <cxl/pci.h>
+
+#include "pci.h"
+
+/* Find the CXL Device DVSEC and read its capability register. */
+static int cxl_pci_device_dvsec(struct pci_dev *pdev, u16 *dvsec, u16 *cap)
+{
+ int rc;
+
+ *dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!*dvsec)
+ return -ENOTTY;
+
+ rc = pci_read_config_word(pdev, *dvsec + PCI_DVSEC_CXL_CAP, cap);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(*cap))
+ return -ENODEV;
+
+ return 0;
+}
+
+static bool cxl_pci_bar_assigned(struct pci_dev *pdev, int bar)
+{
+ struct resource *res = &pdev->resource[bar];
+
+ return pci_resource_len(pdev, bar) &&
+ !(res->flags & (IORESOURCE_UNSET | IORESOURCE_DISABLED)) &&
+ resource_type(res) == IORESOURCE_MEM && res->start && res->end;
+}
+
+static void __iomem *cxl_pci_hdm_map_regs(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ resource_size_t *hdm_offset,
+ int *decoder_count)
+{
+ struct cxl_reg_map *hdm_map = &map->component_map.hdm_decoder;
+ void __iomem *hdm;
+ int rc;
+
+ /* cxl_setup_regs() populates hdm_map, including its valid bit. */
+ rc = cxl_setup_regs(map);
+ if (rc)
+ return ERR_PTR(rc);
+ if (!hdm_map->valid)
+ return ERR_PTR(-ENODEV);
+ if (hdm_map->offset > map->max_size ||
+ hdm_map->size > map->max_size - hdm_map->offset)
+ return ERR_PTR(-ENXIO);
+ if (check_add_overflow(map->bar_offset,
+ (resource_size_t)hdm_map->offset, hdm_offset))
+ return ERR_PTR(-EOVERFLOW);
+
+ hdm = ioremap(map->resource + hdm_map->offset, hdm_map->size);
+ if (!hdm)
+ return ERR_PTR(-ENOMEM);
+
+ *decoder_count = cxl_hdm_decoder_count_mapped(&pdev->dev, hdm,
+ hdm_map->size);
+ if (*decoder_count < 0) {
+ iounmap(hdm);
+ return ERR_PTR(*decoder_count);
+ }
+
+ return hdm;
+}
+
+static struct cxl_hdm_info *__cxl_pci_hdm_read_info(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ int dvsec)
+{
+ struct cxl_reg_map *hdm_map = &map->component_map.hdm_decoder;
+ struct cxl_hdm_info *info;
+ resource_size_t hdm_offset;
+ void __iomem *hdm;
+ int decoder_count;
+ u16 dvsec_ctrl;
+ int rc;
+
+ hdm = cxl_pci_hdm_map_regs(pdev, map, &hdm_offset, &decoder_count);
+ if (IS_ERR(hdm))
+ return ERR_CAST(hdm);
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL,
+ &dvsec_ctrl);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ goto out_unmap;
+ }
+
+ info = kzalloc_flex(*info, settings, decoder_count, GFP_KERNEL);
+ if (!info) {
+ rc = -ENOMEM;
+ goto out_unmap;
+ }
+
+ info->decoder_count = decoder_count;
+ info->hdm_bar = map->bar;
+ info->hdm_offset = hdm_offset;
+ info->hdm_size = hdm_map->size;
+ info->dvsec = dvsec;
+ info->dvsec_ctrl = dvsec_ctrl;
+ info->dvsec_ctrl_valid = true;
+
+ rc = cxl_hdm_read_state(&pdev->dev, hdm, info);
+ if (rc)
+ goto out_free;
+
+ iounmap(hdm);
+ return info;
+
+out_free:
+ kfree(info);
+out_unmap:
+ iounmap(hdm);
+ return ERR_PTR(rc);
+}
+
+static struct cxl_hdm_info *cxl_pci_hdm_read_info(struct pci_dev *pdev,
+ struct cxl_register_map *map,
+ int dvsec)
+{
+ struct cxl_hdm_info *info;
+ u16 command;
+ int rc;
+
+ rc = pci_read_config_word(pdev, PCI_COMMAND, &command);
+ if (rc)
+ return ERR_PTR(pcibios_err_to_errno(rc));
+
+ if (!(command & PCI_COMMAND_MEMORY)) {
+ rc = pci_write_config_word(pdev, PCI_COMMAND,
+ command | PCI_COMMAND_MEMORY);
+ if (rc)
+ return ERR_PTR(pcibios_err_to_errno(rc));
+ }
+
+ info = __cxl_pci_hdm_read_info(pdev, map, dvsec);
+ if (!(command & PCI_COMMAND_MEMORY)) {
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc)
+ pci_err(pdev,
+ "failed to restore PCI_COMMAND after CXL HDM cache init: %d\n",
+ pcibios_err_to_errno(rc));
+ }
+ return info;
+}
+
+static int __pci_cxl_hdm_cache_init(struct pci_dev *pdev)
+{
+ struct cxl_register_map map = { };
+ struct cxl_hdm_info *read_info;
+ u16 dvsec, cap;
+ int rc;
+
+ guard(pci_dev)(pdev);
+
+ /* Avoid duplicate MMIO reads when the cache is already present. */
+ if (pdev->hdm)
+ return 0;
+
+ rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
+ if (rc == -ENOTTY)
+ return 0;
+ if (rc)
+ return rc;
+ if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE) ||
+ !cxl_dvsec_hdm_count_valid(FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap)))
+ return 0;
+
+ rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
+ if (rc)
+ return rc;
+ if (map.bar < 0 || map.bar >= PCI_STD_NUM_BARS ||
+ !cxl_pci_bar_assigned(pdev, map.bar))
+ return -ENODEV;
+
+ read_info = cxl_pci_hdm_read_info(pdev, &map, dvsec);
+ if (IS_ERR(read_info))
+ return PTR_ERR(read_info);
+
+ /*
+ * The PCI device lock held since the cache check excludes other
+ * initializers. CXL drivers bind only after enumeration, so no cache
+ * mutator can run yet and cxl_rwsem.dpa is not needed to publish.
+ */
+ pdev->hdm = read_info;
+
+ return 0;
+}
+
+void pci_cxl_hdm_cache_init(struct pci_dev *pdev)
+{
+ int rc;
+
+ rc = __pci_cxl_hdm_cache_init(pdev);
+ if (rc)
+ pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
+}
+
+void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
+{
+ cxl_hdm_cache_release(&pdev->hdm);
+}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index ba3c3fddddc2..9e218f236e66 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -730,6 +730,14 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
+#ifdef CONFIG_CXL_RESET
+void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
+void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
+#else
+static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
+static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev) { }
+#endif
+
#ifdef CONFIG_PCI_NPEM
void pci_npem_create(struct pci_dev *dev);
void pci_npem_remove(struct pci_dev *dev);
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 27008e2ea5af..cb52357344ed 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2484,6 +2484,7 @@ static void pci_release_dev(struct device *dev)
struct pci_dev *pci_dev;
pci_dev = to_pci_dev(dev);
+ pci_cxl_hdm_cache_release(pci_dev);
pci_release_capabilities(pci_dev);
pci_release_of_node(pci_dev);
pcibios_release_device(pci_dev);
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 06e9aa4479c2..0604dcc439c9 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -26,6 +26,7 @@ struct cxl_endpoint_decoder_settings {
* @hdm_offset: offset of the HDM decoder capability in @hdm_bar
* @hdm_size: size of the HDM decoder register block
* @global_ctrl: HDM decoder global control register
+ * @dvsec: config space offset of the CXL Device DVSEC
* @dvsec_ctrl: CXL DVSEC control register
* @dvsec_ctrl_valid: whether @dvsec_ctrl contains valid state
* @settings: per-endpoint-decoder programming state
@@ -39,13 +40,21 @@ struct cxl_hdm_info {
resource_size_t hdm_offset;
resource_size_t hdm_size;
u32 global_ctrl;
+ u16 dvsec;
u16 dvsec_ctrl;
bool dvsec_ctrl_valid;
struct cxl_endpoint_decoder_settings settings[] __counted_by(decoder_count);
};
+struct device;
+int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
+ resource_size_t size);
+int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
+ struct cxl_hdm_info *info);
+void cxl_hdm_cache_set_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl);
void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
struct cxl_endpoint_decoder *cxled);
int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count);
+void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
#endif /* __CXL_HDM_H__ */
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
index facaa324bd86..ca3e77b72d87 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1352,6 +1352,8 @@
#define PCI_DVSEC_CXL_CACHE_CAPABLE _BITUL(0)
#define PCI_DVSEC_CXL_MEM_CAPABLE _BITUL(2)
#define PCI_DVSEC_CXL_HDM_COUNT __GENMASK(5, 4)
+#define PCI_DVSEC_CXL_RST_CAPABLE _BITUL(7)
+#define PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE _BITUL(11)
#define PCI_DVSEC_CXL_CTRL 0xC
#define PCI_DVSEC_CXL_MEM_ENABLE _BITUL(2)
#define PCI_DVSEC_CXL_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10))
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration
2026-11-05 18:59 ` [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
@ 2026-11-05 19:30 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:30 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: missing all-ones checks when caching HDM and DVSEC control state
- [High] PCI/CXL: out-of-bounds MMIO read and size underflow in cxl_pci_hdm_map_regs()
- [High] PCI/CXL: pci_cxl_hdm_cache_init() runs before BAR survey and bridge enable
- [Medium] cxl/pci: disable_hdm() leaves stale CXL_HDM_DECODER_ENABLE in cached state
--
commit e053ed26c570120750868c635b177e4dbb11e00d
Author: Srirangan Madhavan <smadhavan@nvidia.com>
PCI/CXL: Cache endpoint HDM state during PCI enumeration
This commit caches CXL endpoint HDM register locations, decoder settings,
and Device DVSEC state in pci_dev->hdm during PCI enumeration so that reset
paths can restore HDM state without a bound CXL driver.
> diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
> index 5272013548c73..ea5327cd627fc 100644
> --- a/drivers/cxl/core/pci.c
> +++ b/drivers/cxl/core/pci.c
> @@ -209,22 +209,31 @@ static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
[ ... ]
> + /*
> + * Refresh the PCI-core cache from the live register even when
> + * MEM_ENABLE already matched: a reset restores the cached value.
> + */
> + cxl_hdm_cache_set_dvsec_ctrl(pdev, ctrl);
> + return ret;
> }
>
> static void clear_mem_enable(void *cxlds)
[Severity: Medium]
As reported by Sashiko on v16 [1], when the CXL driver unbinds or unwinds
probe, can disable_hdm() leave a stale CXL_HDM_DECODER_ENABLE bit set in
pdev->hdm->global_ctrl?
When the CXL driver binds and platform firmware did not pre-enable HDM,
devm_cxl_endpoint_decoders_setup() runs:
cxl_hdm_decode_init()
devm_cxl_enable_hdm() /* sets CXL_HDM_DECODER_ENABLE in MMIO */
devm_cxl_enable_mem()
cxl_set_mem_enable() /* updates pdev->hdm->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);
which latches CXL_HDM_DECODER_ENABLE into pdev->hdm->global_ctrl.
On driver unbind or probe failure, devres runs clear_mem_enable(), which
clears PCI_DVSEC_CXL_MEM_ENABLE in both hardware and pdev->hdm->dvsec_ctrl,
and then runs disable_hdm():
static void disable_hdm(void *_cxlhdm)
{
u32 global_ctrl;
struct cxl_hdm *cxlhdm = _cxlhdm;
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
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() clears CXL_HDM_DECODER_ENABLE in hardware without
updating pdev->hdm->global_ctrl, a subsequent reset while unbound will
restore CXL_HDM_DECODER_ENABLE in hardware while PCI_DVSEC_CXL_MEM_ENABLE
remains disabled, and a later driver re-bind in cxl_hdm_decode_init() will
see global_ctrl & CXL_HDM_DECODER_ENABLE set and assume firmware enabled it.
Should disable_hdm() (or devm_cxl_enable_hdm()) also update the cached
global_ctrl in pdev->hdm?
> 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]
As reported by Sashiko on v16 [2], does calling pci_cxl_hdm_cache_init() in
pci_bus_add_device() run too early during boot and miss upstream bridge
enablement?
On x86 ACPI boot, subsys_initcall(acpi_init) calls:
acpi_pci_root_add()
pci_bus_add_devices()
pci_bus_add_device()
pci_cxl_hdm_cache_init()
before subsys_initcall(pci_subsys_init) runs pcibios_resource_survey() and
before fs_initcall(pcibios_assign_resources) runs
pci_assign_unassigned_resources().
In addition, cxl_pci_bar_assigned() in drivers/pci/cxl.c checks only
res->start and res->end without verifying res->parent, and
cxl_pci_hdm_read_info() sets PCI_COMMAND_MEMORY only on the endpoint pdev
without ensuring upstream PCI-to-PCI bridges have memory decoding enabled.
Can this cause cxl_pci_hdm_read_info() to enable PCI_COMMAND_MEMORY on an
unclaimed or conflicting BIOS BAR address before pcibios_resource_survey()
validates it, or fail to populate pdev->hdm permanently when BARs or bridge
windows are assigned later or upstream bridges do not yet have
PCI_COMMAND_MEMORY enabled?
> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> new file mode 100644
> index 0000000000000..a0d20f4210a52
> --- /dev/null
> +++ b/drivers/pci/cxl.c
> @@ -0,0 +1,225 @@
[ ... ]
> +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);
[Severity: High]
As reported by Sashiko on v16 [3], can cxl_pci_hdm_map_regs() perform
out-of-bounds MMIO reads inside cxl_setup_regs() before the map->max_size
bounds check runs, and underflow hdm_map->size when the HDM capability
header has an invalid decoder count?
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_setup_regs()
cxl_map_regblock() /* ioremap(map->resource, map->max_size) */
cxl_probe_regs()
cxl_probe_component_regs()
cxl_probe_component_regs() immediately reads from map->base + CXL_CM_OFFSET
and map->base + CXL_CM_OFFSET + offset before cxl_pci_hdm_map_regs() checks
hdm_map->offset > map->max_size. If map->max_size is smaller than those
offsets, the read can fault past the ioremap() window.
Furthermore, in cxl_probe_component_regs():
case CXL_CM_CAP_CAP_ID_HDM: {
int decoder_cnt;
decoder_cnt = cxl_hdm_decoder_count(hdr);
length = 0x20 * decoder_cnt + 0x10;
rmap = &map->hdm_decoder;
break;
}
If hdr contains a reserved decoder count encoding (such as 0xffffffff from a
failed MMIO read), cxl_hdm_decoder_count() returns -ENXIO (-6), underflowing
u32 length to 0xffffff50 (~4 GiB) while still marking hdm_map->valid = true.
If the BAR has at least 4 GiB remaining, cxl_pci_hdm_map_regs() passes
hdm_map->size (0xffffff50) to ioremap() before cxl_hdm_decoder_count_mapped()
checks the decoder count.
> + 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;
> + }
[ ... ]
> +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));
> + }
[Severity: High]
Can all-ones config and MMIO error responses (0xffff / 0xffffffff) corrupt
the cached state or bypass enabling PCI_COMMAND_MEMORY here?
In cxl_set_mem_enable() and cxl_pci_device_dvsec(), pci_read_config_word()
is followed by a PCI_POSSIBLE_ERROR() check because PCI config reads to a
non-responding device typically return PCIBIOS_SUCCESSFUL with an all-ones
value.
However, in several paths here, all-ones reads are not checked:
1. In __cxl_pci_hdm_read_info(), pci_read_config_word(pdev,
dvsec + PCI_DVSEC_CXL_CTRL, &dvsec_ctrl) does not check
PCI_POSSIBLE_ERROR(dvsec_ctrl) before storing info->dvsec_ctrl = dvsec_ctrl
and info->dvsec_ctrl_valid = true. Per CXL r4.0 sec 8.1.3.2, bits 15, 13:12,
and 6:3 of DVSEC CXL Control are RsvdP, so 0xffff is never valid, and caching
it would cause reset recovery to write 0xffff back to PCI_DVSEC_CXL_CTRL.
2. In cxl_pci_hdm_read_info(), pci_read_config_word(pdev, PCI_COMMAND,
&command) does not check PCI_POSSIBLE_ERROR(command). Because 0xffff has
PCI_COMMAND_MEMORY set, an all-ones read is treated as memory space already
being enabled and proceeds directly to MMIO reads.
3. In cxl_hdm_read_state() in drivers/cxl/core/hdm_regs.c:
info->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
global_ctrl is stored without checking for U32_MAX, unlike
cxl_hdm_cache_refresh_decoder() in drivers/cxl/core/hdm_state.c which checks
global_ctrl != U32_MAX, and settings->skip in cxl_hdm_read_decoder_settings()
is not checked for U64_MAX (unlike base and size in
cxl_hdm_unpack_decoder()).
Should these reads also reject all-ones values before caching them or using
command to gate PCI_COMMAND_MEMORY?
[1] https://lore.kernel.org/r/20261029145703.2185231-12-smadhavan@nvidia.com
[2] https://lore.kernel.org/r/20261029145703.2185231-12-smadhavan@nvidia.com
[3] https://lore.kernel.org/r/20261029145703.2185231-12-smadhavan@nvidia.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=11
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (10 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 11/19] PCI/CXL: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:17 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
` (7 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Implement CXL Device Reset for eligible Type 2 devices: disable CXL.cache,
perform supported writeback, drain transactions, prepare the IOMMU,
initiate reset with Memory Clear enabled, and wait for completion.
Observe the 100 ms post-reset quiet period before polling Status2, and
treat an all-ones read as not ready. Wait for the timeout the device
advertises, up to 100 seconds, and access only Status2 until reset
completion, error or expiry, as CXL 4.0 section 9.7.3 requires. Control2
cleanup runs only afterwards. Treat Control2 command bits as
transient state, restore cache policy on exit, preserve the primary error,
and abort when IOMMU preparation fails.
Reuse Device DVSEC discovery from cache initialization and keep the
reset entry point declaration private to PCI core.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
Changes in v17:
- Wait for the reset timeout the device advertises, up to 100 seconds,
instead of clamping it to 20 seconds, so that Control2 is not accessed
while the device may still be resetting. The warning for an unknown
timeout encoding now reports the timeout used (Richard)
https://lore.kernel.org/linux-cxl/auQC2UECQ8vaelPz@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=04f8f84816d724f8b47f4d47a77916ac37c63a49
---
drivers/pci/cxl.c | 244 +++++++++++++++++++++++++++++++++-
drivers/pci/pci.h | 5 +
include/uapi/linux/pci_regs.h | 12 ++
3 files changed, 260 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index a0d20f4210a5..9ac5948f595b 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -3,13 +3,17 @@
/*
* CXL reset support in the PCI core. Cache the HDM decoder state of a CXL
* memory device at enumeration, so it can be restored after a reset without
- * a CXL driver. HDM register formats and programming live in drivers/cxl.
+ * a CXL driver, and implement CXL Device Reset. HDM register formats and
+ * programming live in drivers/cxl.
*/
#include <linux/bitfield.h>
#include <linux/cleanup.h>
+#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/ioport.h>
+#include <linux/iommu.h>
+#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -223,3 +227,241 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
{
cxl_hdm_cache_release(&pdev->hdm);
}
+
+/*
+ * CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
+ * Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
+ * initiating CXL Reset, then limits software to CXL Status2 access until
+ * reset completion, timeout, or error. Wait for the full advertised timeout
+ * so that Control2 is not accessed while the device may still be resetting.
+ */
+#define CXL_RESET_RRS_WAIT_MS 100
+#define CXL_RESET_STATUS_POLL_MS 20
+static const u32 cxl_reset_timeout_ms[] = {
+ 10, 100, 1000, 10000, 100000,
+};
+
+/*
+ * CXL 4.0 section 9.6 permits using the device cache size to determine
+ * a timeout. Use a conservative fixed estimate of 100 ms.
+ */
+#define CXL_CACHE_WBI_TIMEOUT_US 100000
+#define CXL_CACHE_WBI_POLL_US 100
+
+#define CXL_RESET_CTRL2_CMD_MASK \
+ (PCI_DVSEC_CXL_INIT_CACHE_WBI | PCI_DVSEC_CXL_INIT_CXL_RST)
+
+static int cxl_reset_update_ctrl2(struct pci_dev *pdev, int dvsec,
+ u16 set, u16 clear)
+{
+ u16 ctrl2;
+ int rc;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(ctrl2))
+ return -ENODEV;
+
+ /*
+ * INIT_CACHE_WBI and INIT_CXL_RST are commands. Do not replay a command
+ * that remains set in the value read back while updating persistent
+ * Control2 state.
+ */
+ ctrl2 &= ~CXL_RESET_CTRL2_CMD_MASK;
+ ctrl2 &= ~clear;
+ ctrl2 |= set;
+
+ rc = pci_write_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ return 0;
+}
+
+static int cxl_reset_wait_cache_wbi(struct pci_dev *pdev, int dvsec)
+{
+ unsigned long deadline;
+ u16 status2;
+ int rc;
+
+ rc = cxl_reset_update_ctrl2(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CACHE_WBI, 0);
+ if (rc)
+ return rc;
+
+ deadline = jiffies + usecs_to_jiffies(CXL_CACHE_WBI_TIMEOUT_US);
+ while (time_before(jiffies, deadline)) {
+ usleep_range(CXL_CACHE_WBI_POLL_US,
+ CXL_CACHE_WBI_POLL_US * 2);
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(status2))
+ return -ENODEV;
+ if (status2 & PCI_DVSEC_CXL_CACHE_INV)
+ return 0;
+ }
+
+ pci_warn(pdev, "timed out waiting for CXL cache writeback and invalidate\n");
+ return -ETIMEDOUT;
+}
+
+static int cxl_reset_disable_cache(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc;
+
+ rc = cxl_reset_update_ctrl2(pdev, dvsec,
+ PCI_DVSEC_CXL_DISABLE_CACHING, 0);
+ if (rc || !(cap & PCI_DVSEC_CXL_CACHE_WBI_CAPABLE))
+ return rc;
+
+ return cxl_reset_wait_cache_wbi(pdev, dvsec);
+}
+
+static int cxl_reset_restore_cache_policy(struct pci_dev *pdev, int dvsec,
+ u16 saved_ctrl2)
+{
+ int rc;
+
+ rc = cxl_reset_update_ctrl2(pdev, dvsec,
+ saved_ctrl2 & PCI_DVSEC_CXL_DISABLE_CACHING,
+ PCI_DVSEC_CXL_DISABLE_CACHING);
+ if (rc)
+ pci_err(pdev, "failed to restore CXL cache policy: %d\n", rc);
+
+ return rc;
+}
+
+static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ unsigned long deadline;
+ u32 timeout_ms;
+ u16 status2;
+ int idx, rc;
+
+ idx = FIELD_GET(PCI_DVSEC_CXL_RST_TIMEOUT, cap);
+ if (idx >= ARRAY_SIZE(cxl_reset_timeout_ms)) {
+ idx = ARRAY_SIZE(cxl_reset_timeout_ms) - 1;
+ pci_warn(pdev,
+ "unknown CXL reset timeout encoding; using %u ms\n",
+ cxl_reset_timeout_ms[idx]);
+ }
+
+ /* Allow one retry after the mandatory 100 ms quiet period. */
+ timeout_ms = max_t(u32, cxl_reset_timeout_ms[idx],
+ CXL_RESET_RRS_WAIT_MS + CXL_RESET_STATUS_POLL_MS);
+ pci_info(pdev, "CXL reset timeout: %u ms\n", timeout_ms);
+ msleep(CXL_RESET_RRS_WAIT_MS);
+ deadline = jiffies + msecs_to_jiffies(timeout_ms -
+ CXL_RESET_RRS_WAIT_MS);
+
+ for (;;) {
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (status2 != U16_MAX) {
+ if (status2 & PCI_DVSEC_CXL_RST_ERR)
+ return -EIO;
+ if (status2 & PCI_DVSEC_CXL_RST_DONE)
+ return 0;
+ }
+
+ if (time_after_eq(jiffies, deadline))
+ return -ETIMEDOUT;
+
+ msleep(CXL_RESET_STATUS_POLL_MS);
+ }
+}
+
+static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
+{
+ if (initiate)
+ return cxl_reset_update_ctrl2(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CXL_RST |
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN,
+ 0);
+
+ return cxl_reset_update_ctrl2(pdev, dvsec, 0,
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN);
+}
+
+static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc, rc2;
+
+ if (!pci_wait_for_pending_transaction(pdev)) {
+ pci_err(pdev, "timed out waiting for pending transactions\n");
+ return -ETIMEDOUT;
+ }
+
+ rc = pci_dev_reset_iommu_prepare(pdev);
+ if (rc) {
+ pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
+ return rc;
+ }
+
+ rc = cxl_clear_memory(pdev, dvsec, true);
+ if (!rc)
+ rc = cxl_reset_wait_done(pdev, dvsec, cap);
+
+ /* Always clear Memory Clear; successful cleanup must not hide reset failure. */
+ rc2 = cxl_clear_memory(pdev, dvsec, false);
+ if (rc2)
+ pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n", rc2);
+ rc = rc ?: rc2;
+
+ pci_dev_reset_iommu_done(pdev);
+ return rc;
+}
+
+static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ u16 saved_ctrl2;
+ int rc, rc2;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &saved_ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(saved_ctrl2))
+ return -ENODEV;
+
+ rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ if (!rc)
+ rc = __cxl_reset_execute(pdev, dvsec, cap);
+ /* Restore cache policy after any attempt to disable caching. */
+ rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, saved_ctrl2);
+ return rc ?: rc2;
+}
+
+int cxl_reset_function(struct pci_dev *pdev, bool probe)
+{
+ u16 dvsec, cap, ctrl;
+ int rc;
+
+ rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
+ if (rc)
+ return rc;
+ if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE))
+ return -ENOTTY;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL, &ctrl);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return -ENODEV;
+ if (!(ctrl & PCI_DVSEC_CXL_CACHE_ENABLE) ||
+ !(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
+ return -ENOTTY;
+
+ if (probe)
+ return 0;
+
+ return cxl_reset_execute(pdev, dvsec, cap);
+}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 9e218f236e66..743e65abd758 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -733,9 +733,14 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#ifdef CONFIG_CXL_RESET
void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
+int cxl_reset_function(struct pci_dev *pdev, bool probe);
#else
static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev) { }
+static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
+{
+ return -ENOTTY;
+}
#endif
#ifdef CONFIG_PCI_NPEM
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
index ca3e77b72d87..7224857c7b30 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1352,10 +1352,22 @@
#define PCI_DVSEC_CXL_CACHE_CAPABLE _BITUL(0)
#define PCI_DVSEC_CXL_MEM_CAPABLE _BITUL(2)
#define PCI_DVSEC_CXL_HDM_COUNT __GENMASK(5, 4)
+#define PCI_DVSEC_CXL_CACHE_WBI_CAPABLE _BITUL(6)
#define PCI_DVSEC_CXL_RST_CAPABLE _BITUL(7)
+#define PCI_DVSEC_CXL_RST_TIMEOUT __GENMASK(10, 8)
#define PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE _BITUL(11)
#define PCI_DVSEC_CXL_CTRL 0xC
+#define PCI_DVSEC_CXL_CACHE_ENABLE _BITUL(0)
#define PCI_DVSEC_CXL_MEM_ENABLE _BITUL(2)
+#define PCI_DVSEC_CXL_CTRL2 0x10
+#define PCI_DVSEC_CXL_DISABLE_CACHING _BITUL(0)
+#define PCI_DVSEC_CXL_INIT_CACHE_WBI _BITUL(1)
+#define PCI_DVSEC_CXL_INIT_CXL_RST _BITUL(2)
+#define PCI_DVSEC_CXL_RST_MEM_CLR_EN _BITUL(3)
+#define PCI_DVSEC_CXL_STATUS2 0x12
+#define PCI_DVSEC_CXL_CACHE_INV _BITUL(0)
+#define PCI_DVSEC_CXL_RST_DONE _BITUL(1)
+#define PCI_DVSEC_CXL_RST_ERR _BITUL(2)
#define PCI_DVSEC_CXL_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10))
#define PCI_DVSEC_CXL_RANGE_SIZE_LOW(i) (0x1C + (i * 0x10))
#define PCI_DVSEC_CXL_MEM_INFO_VALID _BITUL(0)
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing
2026-11-05 18:59 ` [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
@ 2026-11-05 19:17 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:17 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. Wait for the timeout the device
> advertises, up to 100 seconds, and access only Status2 until reset
> completion, error or expiry, as CXL 4.0 section 9.7.3 requires. Control2
> cleanup runs only afterwards. Treat Control2 command bits as
> transient state, restore cache policy on exit, preserve the primary error,
> and abort when IOMMU preparation fails.
>
> Reuse Device DVSEC discovery from cache initialization and keep the
> reset entry point declaration private to PCI core.
>
> Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=12
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (11 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 12/19] PCI/CXL: Add CXL Device Reset sequencing Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:44 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
` (6 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Refuse reset unless enabled system-physical HDM ranges can be reserved
exclusively and CPU-cache invalidation is available. Invalidate before
reset and again before ending IOMMU exclusion, holding range reservations
until the second invalidation completes. A later patch places state
restoration before the second invalidation.
Userspace is expected to offline CXL.mem users before reset. The
reservation enforces this for claimed ranges: the reset fails while a
conflicting resource is claimed, and no one can claim the range during
the reset. It cannot see accesses by code that uses the range without
claiming it, which the offline expectation also covers.
Reject normalized-addressing decoders because their cached ranges are not
system physical addresses. Record Normalized Addressing in the cache when
region discovery sets it, since the cache is filled earlier. A reset that
takes its snapshot between endpoint probe and that update can still
miss it.
Ignore zero-size decoders because they map no address range. Refuse reset
when the device decodes memory through its DVSEC ranges instead of HDM
decoders, since the cache holds no range to reserve for that memory.
Hold the global cxl_rwsem.region write lock across snapshot, reset and
restoration to keep the decoder state and reserved ranges stable. This also
blocks region and decoder sysfs, region probe, poison operations, sanitize
and region-aware event tracing across CXL devices. The Memory Clear wait
lasts up to the timeout the device advertises, at most 100 seconds; cache
flushes and other reset work extend the hold.
Allow the existing CXL_REGION_INVALIDATION_TEST option to bypass missing
CPU cache invalidation for test environments without a provider.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
Changes in v17:
- State that userspace is expected to offline CXL.mem users before
reset, and what the HPA reservation does and does not cover (Richard)
https://lore.kernel.org/linux-cxl/auQRSOGWEDzB_qjF@MWDK4CY14F/
- Record Normalized Addressing in the cached decoder when region
discovery sets it, and note the short window before that update
(Sashiko)
https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=13
- Annotate cxl_region_quiesce_lock()/unlock() and __cxl_reset_execute()
for context analysis (Sashiko)
https://sashiko.dev/#/patchset/20261029145703.2185231-1-smadhavan@nvidia.com?part=15
- Describe the region lock hold as bounded by the advertised reset
timeout, at most 100 seconds, plus cache flushes (Richard)
https://lore.kernel.org/linux-cxl/auQC2UECQ8vaelPz@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=5944c6e61f14c0dd28d54537be15edb2cadb0e8b
---
drivers/cxl/Kconfig | 3 +-
drivers/cxl/core/hdm_state.c | 266 ++++++++++++++++++++++++++++++++++-
drivers/cxl/core/region.c | 14 ++
drivers/pci/cxl.c | 87 ++++++++++--
include/cxl/hdm.h | 47 ++++++-
5 files changed, 406 insertions(+), 11 deletions(-)
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 8b08bfdc4982..e927e69a3f7b 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -226,7 +226,8 @@ config CXL_REGION_INVALIDATION_TEST
invalidation mechanism. For example usage of wbinvd is restricted to
bare metal x86. However, for testing purposes toggling this option
can disable that data integrity safety and proceed with enabling
- regions when there might be conflicting contents in the CPU cache.
+ regions or performing CXL reset when there might be conflicting
+ contents in the CPU cache.
If unsure, or if this kernel is meant for production environments,
say N.
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index 9def4a7439c9..51df56377f7a 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -1,8 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/errno.h>
#include <linux/export.h>
#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/list.h>
+#include <linux/memregion.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/rwsem.h>
#include <linux/slab.h>
@@ -13,7 +19,8 @@
/*
* CXL state that must stay available while no CXL driver is bound: the CXL
- * locks and the per-device HDM cache filled during PCI enumeration.
+ * locks, the per-device HDM cache filled during PCI enumeration, and the
+ * region quiesce used around a reset.
*/
struct cxl_rwsem cxl_rwsem = {
.region = __RWSEM_INITIALIZER(cxl_rwsem.region),
@@ -87,6 +94,27 @@ int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count)
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_check_decoder_count, "cxl_core");
+/**
+ * cxl_hdm_cache_set_normalized() - record Normalized Addressing in the cache
+ * @pdev: endpoint that owns the cache
+ * @id: endpoint decoder id
+ *
+ * Normalized Addressing is determined during region discovery, after the
+ * cache was filled, so the cached decoder lacks it. Record only that flag: the
+ * decoder's software lock must not become a hardware Lock On Commit when the
+ * cache is restored.
+ */
+void cxl_hdm_cache_set_normalized(struct pci_dev *pdev, int id)
+{
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ if (!pdev->hdm || id < 0 || id >= pdev->hdm->decoder_count)
+ return;
+
+ pdev->hdm->settings[id].config.flags |=
+ CXL_DECODER_F_NORMALIZED_ADDRESSING;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_cache_set_normalized, "cxl_core");
+
/**
* cxl_hdm_cache_release() - unpublish and free cached HDM state
* @slot: cache pointer owned by the device
@@ -105,3 +133,239 @@ void cxl_hdm_cache_release(struct cxl_hdm_info **slot)
}
kfree(info);
}
+
+/**
+ * cxl_hdm_cache_dvsec_decode() - check for memory decoded by DVSEC ranges
+ * @slot: cache pointer owned by the device
+ *
+ * Return: true if the published state shows CXL.mem enabled while HDM
+ * decoders are globally disabled, so that the device decodes memory through
+ * its DVSEC ranges, which the cache does not describe.
+ */
+bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot)
+{
+ struct cxl_hdm_info *info;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = *slot;
+ return info && (info->dvsec_ctrl & PCI_DVSEC_CXL_MEM_ENABLE) &&
+ !(info->global_ctrl & CXL_HDM_DECODER_ENABLE);
+}
+
+/**
+ * cxl_hdm_cache_snapshot() - copy the published HDM state
+ * @slot: cache pointer owned by the device
+ *
+ * Return: a copy the caller frees with kfree(), ERR_PTR(-ENXIO) when nothing
+ * is published, or another ERR_PTR() on failure.
+ */
+struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot)
+{
+ struct cxl_hdm_info *info, *copy;
+ size_t size;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = *slot;
+ if (!info)
+ return ERR_PTR(-ENXIO);
+ if (info->decoder_count <= 0)
+ return ERR_PTR(-EINVAL);
+
+ size = struct_size(info, settings, info->decoder_count);
+ if (size == SIZE_MAX)
+ return ERR_PTR(-EOVERFLOW);
+
+ copy = kmemdup(info, size, GFP_KERNEL);
+ if (!copy)
+ return ERR_PTR(-ENOMEM);
+
+ return copy;
+}
+
+void cxl_region_quiesce_lock(void)
+ __acquires(cxl_region_quiesce)
+ __context_unsafe(token maps to cxl_rwsem.region)
+{
+ down_write(&cxl_rwsem.region);
+ __acquire(cxl_region_quiesce);
+}
+
+void cxl_region_quiesce_unlock(void)
+ __releases(cxl_region_quiesce)
+ __context_unsafe(token maps to cxl_rwsem.region)
+{
+ __release(cxl_region_quiesce);
+ up_write(&cxl_rwsem.region);
+}
+
+/*
+ * Bound the range twice: request_mem_region() takes resource_size_t while
+ * cpu_cache_invalidate_memregion() takes size_t, and the two differ on
+ * 32-bit builds with CONFIG_PHYS_ADDR_T_64BIT. range_len() can also reach
+ * RESOURCE_SIZE_MAX + 1 for a full-width range, and wraps to zero when
+ * resource_size_t is 64-bit, which the !len test catches.
+ */
+static int cxl_hdm_range_validate(const struct range *hpa_range)
+{
+ u64 len = range_len(hpa_range);
+
+ if (!len)
+ return -EINVAL;
+ if (hpa_range->end > RESOURCE_SIZE_MAX || len > RESOURCE_SIZE_MAX ||
+ len > SIZE_MAX)
+ return -EOVERFLOW;
+
+ return 0;
+}
+
+static int cxl_hdm_range_add(struct cxl_hdm_quiesce *q,
+ const struct range *hpa_range)
+{
+ struct cxl_hdm_range *range, *next, *new_range;
+ int rc;
+
+ rc = cxl_hdm_range_validate(hpa_range);
+ if (rc)
+ return rc;
+
+ list_for_each_entry(range, &q->ranges, list)
+ if (range_contains(&range->hpa_range, hpa_range))
+ return 0;
+
+ new_range = kzalloc_obj(*new_range);
+ if (!new_range)
+ return -ENOMEM;
+
+ new_range->hpa_range = *hpa_range;
+ new_range->len = range_len(hpa_range);
+
+ list_for_each_entry_safe(range, next, &q->ranges, list) {
+ if (range_contains(hpa_range, &range->hpa_range)) {
+ list_del(&range->list);
+ kfree(range);
+ }
+ }
+ list_add_tail(&new_range->list, &q->ranges);
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_collect(struct cxl_hdm_quiesce *q,
+ const struct cxl_hdm_info *snapshot)
+{
+ int rc;
+
+ for (int i = 0; i < snapshot->decoder_count; i++) {
+ const struct cxl_decoder_config *config =
+ &snapshot->settings[i].config;
+
+ /* A committed zero-size decoder maps no HPA. */
+ if (!(config->flags & CXL_DECODER_F_ENABLE) ||
+ !range_len(&config->hpa_range))
+ continue;
+
+ if (config->flags & CXL_DECODER_F_NORMALIZED_ADDRESSING)
+ return -EOPNOTSUPP;
+
+ rc = cxl_hdm_range_add(q, &config->hpa_range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_request(struct cxl_hdm_quiesce *q)
+{
+ struct cxl_hdm_range *range;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ list_for_each_entry(range, &q->ranges, list) {
+ const struct range *hpa_range = &range->hpa_range;
+
+ range->res = request_mem_region(hpa_range->start, range->len,
+ "cxl_reset");
+ if (!range->res)
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_invalidate(struct cxl_hdm_quiesce *q)
+{
+ struct cxl_hdm_range *range;
+ int rc = 0;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ if (!cpu_cache_has_invalidate_memregion() &&
+ IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST)) {
+ pr_info_once("CXL reset: bypassing CPU cache invalidation for testing\n");
+ return 0;
+ }
+
+ list_for_each_entry(range, &q->ranges, list) {
+ const struct range *hpa_range = &range->hpa_range;
+ int rc2;
+
+ rc2 = cpu_cache_invalidate_memregion(hpa_range->start, range->len);
+ rc = rc ?: rc2;
+ }
+
+ return rc;
+}
+
+/**
+ * cxl_hdm_quiesce_prepare() - take the HPA ranges of a device out of use
+ * @q: quiesce state to initialize
+ * @snapshot: HDM state from cxl_hdm_cache_snapshot()
+ *
+ * Reserve every enabled system-physical range in @snapshot exclusively, then
+ * write back and invalidate CPU caches for them. Call
+ * cxl_hdm_quiesce_destroy() afterwards whether or not this succeeds.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int cxl_hdm_quiesce_prepare(struct cxl_hdm_quiesce *q,
+ const struct cxl_hdm_info *snapshot)
+{
+ int rc;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+ INIT_LIST_HEAD(&q->ranges);
+
+ if (!cpu_cache_has_invalidate_memregion() &&
+ !IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST))
+ return -ENXIO;
+
+ rc = cxl_hdm_ranges_collect(q, snapshot);
+ if (rc)
+ return rc;
+
+ rc = cxl_hdm_ranges_request(q);
+ if (rc)
+ return rc;
+
+ return cxl_hdm_ranges_invalidate(q);
+}
+
+/* Evict lines fetched during reset before DMA exclusion ends. */
+int cxl_hdm_quiesce_flush(struct cxl_hdm_quiesce *q)
+{
+ return cxl_hdm_ranges_invalidate(q);
+}
+
+void cxl_hdm_quiesce_destroy(struct cxl_hdm_quiesce *q)
+{
+ struct cxl_hdm_range *range, *next;
+
+ list_for_each_entry_safe(range, next, &q->ranges, list) {
+ list_del(&range->list);
+ if (range->res)
+ release_mem_region(range->hpa_range.start,
+ resource_size(range->res));
+ kfree(range);
+ }
+}
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 8cfc06df3134..b06dd9b901f0 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -12,6 +12,7 @@
#include <linux/idr.h>
#include <linux/memory-tiers.h>
#include <linux/string_choices.h>
+#include <cxl/hdm.h>
#include <cxlmem.h>
#include <cxl.h>
#include "core.h"
@@ -3840,6 +3841,19 @@ int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
if (IS_ERR(cxlrd))
return PTR_ERR(cxlrd);
+ /*
+ * Translation setup may have marked the decoder as Normalized
+ * Addressing, after the PCI cache was filled. Record it for reset.
+ */
+ if (cxled->cxld.config.flags & CXL_DECODER_F_NORMALIZED_ADDRESSING) {
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(cxled_to_port(cxled));
+
+ if (pdev)
+ cxl_hdm_cache_set_normalized(pdev,
+ cxled->cxld.config.id);
+ }
+
/*
* Ensure that, if multiple threads race to construct_region()
* for the HPA range, one does the construction and the others
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 9ac5948f595b..af8ec8db20f1 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -15,6 +15,7 @@
#include <linux/iommu.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
+#include <linux/memregion.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -23,6 +24,13 @@
#include "pci.h"
+struct cxl_pci_cache {
+ struct cxl_hdm_quiesce quiesce;
+ u16 saved_ctrl2;
+ /* A cache-policy write was attempted and may need undoing. */
+ bool cache_disabled;
+};
+
/* Find the CXL Device DVSEC and read its capability register. */
static int cxl_pci_device_dvsec(struct pci_dev *pdev, u16 *dvsec, u16 *cap)
{
@@ -335,6 +343,23 @@ static int cxl_reset_restore_cache_policy(struct pci_dev *pdev, int dvsec,
return rc;
}
+/* Shared range reservation, host invalidation, and device cache writeback. */
+static int cxl_reset_cache_prepare(struct pci_dev *pdev, int dvsec, u16 cap,
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache)
+ __must_hold(cxl_region_quiesce)
+{
+ int rc;
+
+ rc = cxl_hdm_quiesce_prepare(&cache->quiesce, snapshot);
+ if (rc)
+ return rc;
+
+ /* Track an attempted change so partial failures still restore policy. */
+ cache->cache_disabled = true;
+ return cxl_reset_disable_cache(pdev, dvsec, cap);
+}
+
static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
{
unsigned long deadline;
@@ -389,7 +414,9 @@ static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
PCI_DVSEC_CXL_RST_MEM_CLR_EN);
}
-static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
+ struct cxl_hdm_quiesce *q)
+ __must_hold(cxl_region_quiesce)
{
int rc, rc2;
@@ -414,29 +441,61 @@ static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n", rc2);
rc = rc ?: rc2;
+ rc2 = cxl_hdm_quiesce_flush(q);
+ if (rc2)
+ pci_err(pdev, "failed to invalidate CPU cache after CXL reset: %d\n", rc2);
+ rc = rc ?: rc2;
+
pci_dev_reset_iommu_done(pdev);
return rc;
}
-static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
+ __must_hold(cxl_region_quiesce)
{
- u16 saved_ctrl2;
+ struct cxl_hdm_info *snapshot __free(kfree) = NULL;
+ struct cxl_pci_cache cache = {};
int rc, rc2;
- rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &saved_ctrl2);
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2,
+ &cache.saved_ctrl2);
if (rc)
return pcibios_err_to_errno(rc);
- if (PCI_POSSIBLE_ERROR(saved_ctrl2))
+ if (PCI_POSSIBLE_ERROR(cache.saved_ctrl2))
return -ENODEV;
- rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
+ if (IS_ERR(snapshot)) {
+ pci_err(pdev, "CXL HDM decoder state unavailable: %ld\n",
+ PTR_ERR(snapshot));
+ return PTR_ERR(snapshot);
+ }
+
+ rc = cxl_reset_cache_prepare(pdev, dvsec, cap, snapshot, &cache);
+ if (rc && !cache.cache_disabled) {
+ pci_err(pdev, "failed to prepare CXL reset ranges: %d\n", rc);
+ cxl_hdm_quiesce_destroy(&cache.quiesce);
+ return rc;
+ }
if (!rc)
- rc = __cxl_reset_execute(pdev, dvsec, cap);
+ rc = __cxl_reset_execute(pdev, dvsec, cap, &cache.quiesce);
/* Restore cache policy after any attempt to disable caching. */
- rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, saved_ctrl2);
+ rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
+ cxl_hdm_quiesce_destroy(&cache.quiesce);
return rc ?: rc2;
}
+static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc;
+
+ cxl_region_quiesce_lock();
+ rc = cxl_reset_quiesced(pdev, dvsec, cap);
+ cxl_region_quiesce_unlock();
+
+ return rc;
+}
+
int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
u16 dvsec, cap, ctrl;
@@ -463,5 +522,17 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (probe)
return 0;
+ /* The cache is owned by @pdev and does not require a bound CXL driver. */
+ if (!pdev->hdm)
+ return -ENOTTY;
+
+ /* Memory decoded through DVSEC ranges has no HPA ranges to quiesce. */
+ if (cxl_hdm_cache_dvsec_decode(&pdev->hdm))
+ return -ENOTTY;
+
+ if (!cpu_cache_has_invalidate_memregion() &&
+ !IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST))
+ return -ENOTTY;
+
return cxl_reset_execute(pdev, dvsec, cap);
}
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 0604dcc439c9..49b36a45c682 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -3,10 +3,14 @@
#ifndef __CXL_HDM_H__
#define __CXL_HDM_H__
+#include <linux/list.h>
#include <linux/types.h>
+
#include <cxl/cxl.h>
+struct device;
struct pci_dev;
+struct resource;
struct cxl_endpoint_decoder;
/**
@@ -45,7 +49,6 @@ struct cxl_hdm_info {
bool dvsec_ctrl_valid;
struct cxl_endpoint_decoder_settings settings[] __counted_by(decoder_count);
};
-struct device;
int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
resource_size_t size);
@@ -55,6 +58,48 @@ void cxl_hdm_cache_set_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl);
void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
struct cxl_endpoint_decoder *cxled);
int cxl_hdm_cache_check_decoder_count(struct pci_dev *pdev, int count);
+void cxl_hdm_cache_set_normalized(struct pci_dev *pdev, int id);
void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
+bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot);
+struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot);
+
+/*
+ * Region quiesce blocks CXL region commit and teardown, and with them decoder
+ * reprogramming, while reset handling captures or restores HDM decoder state.
+ */
+token_context_lock(cxl_region_quiesce);
+
+/**
+ * struct cxl_hdm_range - HPA range held exclusively across a reset
+ * @list: entry in &cxl_hdm_quiesce.ranges
+ * @hpa_range: system physical address range decoded by the device
+ * @len: length of @hpa_range
+ * @res: reservation of @hpa_range, once requested
+ */
+struct cxl_hdm_range {
+ struct list_head list;
+ struct range hpa_range;
+ u64 len;
+ struct resource *res;
+};
+
+/**
+ * struct cxl_hdm_quiesce - HPA ranges held exclusively across a reset
+ * @ranges: list of &struct cxl_hdm_range, see cxl_hdm_quiesce_prepare()
+ */
+struct cxl_hdm_quiesce {
+ struct list_head ranges;
+};
+
+void cxl_region_quiesce_lock(void)
+ __acquires(cxl_region_quiesce);
+void cxl_region_quiesce_unlock(void)
+ __releases(cxl_region_quiesce);
+int cxl_hdm_quiesce_prepare(struct cxl_hdm_quiesce *q,
+ const struct cxl_hdm_info *snapshot)
+ __must_hold(cxl_region_quiesce);
+int cxl_hdm_quiesce_flush(struct cxl_hdm_quiesce *q)
+ __must_hold(cxl_region_quiesce);
+void cxl_hdm_quiesce_destroy(struct cxl_hdm_quiesce *q);
#endif /* __CXL_HDM_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset
2026-11-05 18:59 ` [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
@ 2026-11-05 19:44 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:44 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] cxl: missed or cleared CXL_DECODER_F_NORMALIZED_ADDRESSING in HDM cache
- [High] PCI/CXL: cxl_reset_function() returns -ENOTTY on !probe safety check failures
- [High] cxl: stale global_ctrl cache and unserialized check in cxl_reset_function()
- [Low] PCI/CXL: split declaration and assignment of __free() in cxl_reset_quiesced()
--
commit 5c4d959ed1ab8877ba7835557e27e40030bf5a45
Author: Srirangan Madhavan <smadhavan@nvidia.com>
cxl: Validate and synchronize HDM ranges around reset
This commit validates and reserves enabled system-physical HDM ranges
around CXL reset, invalidating CPU caches before reset and again before
ending IOMMU exclusion while holding the global cxl_rwsem.region write
lock. It also rejects normalized-addressing and DVSEC-decoded memory
configurations and records Normalized Addressing in the HDM cache during
region discovery.
> diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
> index 9def4a7439c93..51df56377f7a9 100644
> --- a/drivers/cxl/core/hdm_state.c
> +++ b/drivers/cxl/core/hdm_state.c
[ ... ]
> +/**
> + * 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]
As reported by Sashiko on v16 [1], can info->global_ctrl become out of sync
with hardware across devm_cxl_enable_hdm() and disable_hdm(), and can
checking cxl_hdm_cache_dvsec_decode() outside cxl_region_quiesce_lock()
race with concurrent endpoint probe or unbind?
In drivers/cxl/core/pci.c, neither devm_cxl_enable_hdm() nor its devres
teardown callback disable_hdm() 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);
}
During cxl_endpoint_port_probe(), when firmware left
PCI_DVSEC_CXL_MEM_ENABLE clear at boot, cxl_hdm_decode_init() calls
devm_cxl_enable_hdm() (setting CXL_HDM_DECODER_ENABLE in MMIO without
updating pdev->hdm->global_ctrl) and then devm_cxl_enable_mem() (setting
PCI_DVSEC_CXL_MEM_ENABLE in pdev->hdm->dvsec_ctrl). Next,
devm_cxl_enumerate_decoders() calls cxl_settle_decoders(), which sleeps
for 20 ms before cxl_hdm_refresh_decoder() refreshes
pdev->hdm->global_ctrl. Throughout that 20 ms window, pdev->hdm has
PCI_DVSEC_CXL_MEM_ENABLE set in dvsec_ctrl and CXL_HDM_DECODER_ENABLE
clear in global_ctrl, so cxl_hdm_cache_dvsec_decode() falsely reports
DVSEC range decode mode.
Conversely, when the endpoint port or CXL driver unbinds, devres runs
clear_mem_enable() followed by disable_hdm(), which clears
CXL_HDM_DECODER_ENABLE in CXL_HDM_DECODER_CTRL_OFFSET without updating
pdev->hdm->global_ctrl. When a subsequent reset restores state from
pdev->hdm (in the later commits "PCI/CXL: Restore CXL state after PCI
reset" and "PCI/CXL: Flush host and device caches for CXL bus reset"),
cxl_hdm_restore_state() will write the stale snapshot->global_ctrl with
CXL_HDM_DECODER_ENABLE set back into hardware.
Additionally, cxl_reset_function() evaluates
cxl_hdm_cache_dvsec_decode(&pdev->hdm) in a standalone cxl_rwsem.dpa read
section before cxl_reset_execute() acquires cxl_region_quiesce_lock() and
calls cxl_hdm_cache_snapshot(&pdev->hdm) in cxl_reset_quiesced(), while
cxl_hdm_quiesce_prepare() never checks the captured snapshot for DVSEC
decode. Because endpoint port probe and unbind run under
device_lock(&port->dev) rather than device_lock(&pdev->dev) and modify
PCI_DVSEC_CXL_CTRL, CXL_HDM_DECODER_CTRL_OFFSET, and pdev->hdm without
holding cxl_rwsem.region, can the cached state change between
cxl_hdm_cache_dvsec_decode() and cxl_hdm_cache_snapshot()?
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index 8cfc06df31340..b06dd9b901f0c 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
> @@ -3840,6 +3841,19 @@ int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
> if (IS_ERR(cxlrd))
> return PTR_ERR(cxlrd);
>
> + /*
> + * Translation setup may have marked the decoder as Normalized
> + * Addressing, after the PCI cache was filled. Record it for reset.
> + */
> + if (cxled->cxld.config.flags & CXL_DECODER_F_NORMALIZED_ADDRESSING) {
> + struct pci_dev *pdev __free(pci_dev_put) =
> + cxl_port_get_uport_pci_dev(cxled_to_port(cxled));
> +
> + if (pdev)
> + cxl_hdm_cache_set_normalized(pdev,
> + cxled->cxld.config.id);
> + }
[Severity: High]
As reported by Sashiko on v16 [1], can CXL_DECODER_F_NORMALIZED_ADDRESSING
still be missed or cleared in pdev->hdm, causing cxl_hdm_ranges_collect()
to treat a device-local normalized address range as a system physical
address range during reset?
There appear to be two paths where the cached decoder loses this flag:
1. In cxl_add_to_region(), get_cxl_root_decoder(cxled, &ctx) runs before
the check above. Inside get_cxl_root_decoder():
drivers/cxl/core/region.c:get_cxl_root_decoder() {
...
rc = cxl_root_setup_translation(cxl_root, ctx);
if (rc) {
...
return ERR_PTR(rc);
}
cxlrd_dev = device_find_child(&cxl_root->port.dev, &ctx->hpa_range,
match_root_decoder);
if (!cxlrd_dev) {
...
return ERR_PTR(-ENXIO);
}
}
cxl_root_setup_translation() -> cxl_prm_setup_root() sets
CXL_DECODER_F_NORMALIZED_ADDRESSING on cxled->cxld.config.flags before
device_find_child(..., match_root_decoder) runs. If no root decoder window
matches (!cxlrd_dev), get_cxl_root_decoder() returns ERR_PTR(-ENXIO).
(Similarly, if cxl_prm_setup_root() detects Normalized Addressing via
hpa_range.start == cxled->dpa_res->start but fails translation before
setting the flag, get_cxl_root_decoder() returns ERR_PTR(-ENXIO).) Because
discover_region() treats errors from cxl_add_to_region() as non-fatal and
cxl_add_to_region() returns early on IS_ERR(cxlrd),
cxl_hdm_cache_set_normalized() is never called.
2. In devm_cxl_enumerate_decoders() -> init_hdm_decoder() ->
cxl_hdm_refresh_decoder() -> cxl_hdm_cache_refresh_decoder():
drivers/cxl/core/hdm_state.c:cxl_hdm_cache_refresh_decoder() {
...
*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;
}
}
Because hardware registers do not have a Normalized Addressing bit,
overwriting settings->config from cxld->config clears any previously
recorded CXL_DECODER_F_NORMALIZED_ADDRESSING bit when the endpoint port
driver is rebound or if enumeration fails on a later decoder before
discover_region() runs.
If a CXL reset then runs, will cxl_hdm_ranges_collect() fail to reject the
decoder with -EOPNOTSUPP and pass the untranslated normalized DPA range to
request_mem_region() and cpu_cache_invalidate_memregion()?
> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> index 9ac5948f595b1..af8ec8db20f12 100644
> --- a/drivers/pci/cxl.c
> +++ b/drivers/pci/cxl.c
[ ... ]
> -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);
[Severity: Low]
This isn't a bug, but per the Cleanup API conventions, should snapshot be
defined and initialized in a single statement at its point of assignment:
struct cxl_hdm_info *snapshot __free(kfree) =
cxl_hdm_cache_snapshot(&pdev->hdm);
rather than being declared with = NULL at the top of cxl_reset_quiesced()?
[ ... ]
> @@ -463,5 +522,17 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
> if (probe)
> return 0;
>
> + /* The cache is owned by @pdev and does not require a bound CXL driver. */
> + if (!pdev->hdm)
> + return -ENOTTY;
> +
> + /* Memory decoded through DVSEC ranges has no HPA ranges to quiesce. */
> + if (cxl_hdm_cache_dvsec_decode(&pdev->hdm))
> + return -ENOTTY;
> +
> + if (!cpu_cache_has_invalidate_memregion() &&
> + !IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST))
> + return -ENOTTY;
> +
> return cxl_reset_execute(pdev, dvsec, cap);
> }
[Severity: High]
When probe is false and CXL.cache and CXL.mem are enabled on the device,
does returning -ENOTTY from these precondition checks cause
__pci_reset_function_locked() to fall back to other PCI reset methods
(such as FLR or PM reset) instead of refusing the reset?
During PCI enumeration, pci_init_reset_methods() calls cxl_reset_function()
with probe == true, which returns 0 before reaching these checks and
records cxl_reset in dev->reset_methods[] ahead of flr and pm (when wired
up by commit "PCI/CXL: Expose CXL Reset as a PCI reset method"). Later,
when __pci_reset_function_locked() runs with PCI_RESET_DO_RESET:
drivers/pci/pci.c:__pci_reset_function_locked() {
...
rc = method->reset_fn(dev, PCI_RESET_DO_RESET);
if (!rc)
return 0;
pci_dbg(dev, "%s failed with %d\n", method->name, rc);
if (rc != -ENOTTY)
return rc;
}
Because -ENOTTY tells __pci_reset_function_locked() to try the next reset
method in dev->reset_methods[], failing any of these checks will proceed to
reset the active CXL.cache / CXL.mem function via FLR or PM reset without
reserving HPA ranges, flushing CPU or device caches, or restoring CXL
state.
Returning -ENOTTY here also makes the locked -ENXIO checks and pci_err()
diagnostics in cxl_hdm_cache_snapshot(), cxl_hdm_quiesce_prepare(), and
cxl_reset_quiesced() unreachable from cxl_reset_function(), while checking
!pdev->hdm without holding cxl_rwsem.dpa. Should these checks run under
cxl_region_quiesce_lock() and return non-ENOTTY errors (such as -ENXIO and
-EOPNOTSUPP, matching cxl_hdm_quiesce_prepare() and pci_cxl_reset_prepare()
in commit "PCI/CXL: Flush host and device caches for CXL bus reset") so the
reset is aborted?
[1] https://lore.kernel.org/r/20261029145703.2185231-14-smadhavan@nvidia.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=13
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (12 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 13/19] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:33 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
` (5 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
The per-device PCI reset interface must not reset sibling functions.
Reject cxl_reset and cxl_bus for VFs, multifunction devices, and PFs with
enabled VFs.
Suggested-by: Alex Williamson <alwilliamson@nvidia.com>
Link: https://lore.kernel.org/linux-cxl/20260626160846.426e71b6@nvidia.com/
Link: https://lore.kernel.org/linux-cxl/6a50441e2bb5e_3cabcb1003e@djbw-dev.notmuch/
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
Changes in v17:
- Move the function-scope checks ahead of the CXL DVSEC lookups, so a VF
returns -ENOTTY without config reads during reset-method probing
(Joshua)
https://lore.kernel.org/linux-cxl/20261105143611.31563-1-machaujo@amazon.de/
---
drivers/pci/cxl.c | 3 +++
drivers/pci/pci.c | 7 +++++++
2 files changed, 10 insertions(+)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index af8ec8db20f1..c07266ab0133 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -501,6 +501,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
u16 dvsec, cap, ctrl;
int rc;
+ if (pdev->is_virtfn || pdev->multifunction || pci_num_vf(pdev))
+ return -ENOTTY;
+
rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
if (rc)
return rc;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index b2879a6be5f8..aef976d0c850 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5018,6 +5018,13 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
u16 dvsec, reg, val;
int rc;
+ /*
+ * Reject VFs and multifunction devices before walking the bridge's
+ * capabilities: every VF behind the same bridge would repeat the walk.
+ */
+ if (dev->is_virtfn || dev->multifunction || pci_num_vf(dev))
+ return -ENOTTY;
+
bridge = pci_upstream_bridge(dev);
if (!bridge)
return -ENOTTY;
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope
2026-11-05 18:59 ` [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
@ 2026-11-05 19:33 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:33 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:
- [High] PCI/CXL: incomplete multifunction check in cxl_reset_function() and cxl_reset_bus_function()
--
commit 124136ec84376cd48b0955e35be2be62182793df
Author: Srirangan Madhavan <smadhavan@nvidia.com>
PCI/CXL: Reject reset with unsafe function scope
This commit rejects cxl_reset and cxl_bus reset methods for virtual
functions, multifunction devices, and physical functions with enabled
virtual functions so per-device resets do not reset sibling functions.
Link: https://lore.kernel.org/linux-cxl/20260626160846.426e71b6@nvidia.com/
Link: https://lore.kernel.org/linux-cxl/6a50441e2bb5e_3cabcb1003e@djbw-dev.notmuch/
> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> index af8ec8db20f12..c07266ab01336 100644
> --- a/drivers/pci/cxl.c
> +++ b/drivers/pci/cxl.c
> @@ -501,6 +501,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
> u16 dvsec, cap, ctrl;
> int rc;
>
> + if (pdev->is_virtfn || pdev->multifunction || pci_num_vf(pdev))
> + return -ENOTTY;
> +
> rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
> if (rc)
> return rc;
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index b2879a6be5f80..aef976d0c850e 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
> @@ -5018,6 +5018,13 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
> u16 dvsec, reg, val;
> int rc;
>
> + /*
> + * Reject VFs and multifunction devices before walking the bridge's
> + * capabilities: every VF behind the same bridge would repeat the walk.
> + */
> + if (dev->is_virtfn || dev->multifunction || pci_num_vf(dev))
> + return -ENOTTY;
[Severity: High]
Is checking dev->multifunction sufficient to catch all multifunction CXL
devices during reset probing and execution in both cxl_reset_function()
and cxl_reset_bus_function()?
During enumeration in drivers/pci/probe.c:pci_scan_slot(),
pci_scan_single_device() runs pci_init_reset_methods() before
dev->multifunction = 1 is set for non-zero functions:
drivers/pci/probe.c:pci_scan_slot()
pci_scan_single_device(bus, devfn + fn)
pci_device_add()
pci_init_capabilities()
pci_init_reset_methods()
if (fn > 0)
dev->multifunction = 1;
Since non-zero functions of conventional multifunction devices are not
required to set PCI_HEADER_TYPE_MFD in their header type register, and ARI
devices must have PCI_HEADER_TYPE_MFD cleared in all functions, any
non-zero function with PCI_HEADER_TYPE_MFD == 0 still has
dev->multifunction == 0 when pci_init_reset_methods() probes
cxl_reset_function() and cxl_reset_bus_function(). As a result, probe does
not reject those functions early, walking the bridge capabilities and
recording cxl_reset and cxl_bus in dev->reset_methods[].
Furthermore, for Function 0 of an ARI multifunction device,
PCI_HEADER_TYPE_MFD is 0 by specification and pci_scan_slot() only sets
dev->multifunction = 1 when fn > 0, leaving Function 0 with
dev->multifunction == 0 even after sibling functions are enumerated. Would
resetting Function 0 of an ARI multifunction CXL device via
cxl_reset_function() (or via cxl_reset_bus_function() when the slot reset
path is used) bypass this check and reset active sibling functions?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=14
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (13 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 14/19] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:34 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 16/19] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
` (4 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Restore the PCI configuration needed for HDM access, followed by cached
HDM decoder and CXL Device DVSEC state. Set Lock On Commit before
recommitting a cleared locked decoder; leave surviving locked state
unchanged.
Stop at the first restoration failure and preserve a disabled PCI command
snapshot. Keep IOMMU exclusion active through restoration and post-reset
cache invalidation.
Program the decoders from built-in CXL code that takes only a mapped
register block, and keep PCI state handling in drivers/pci/cxl.c.
The restore helper structure and diagnostics follow Dave Jiang's
reference implementation on his cxl-type2-reset branch.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Assisted-by: LLM
---
Changes in v17:
- Set Lock On Commit after programming the decoder registers and before
Commit when recommitting a locked decoder, so the lock takes effect at
commit (Alison)
https://lore.kernel.org/linux-cxl/auAg6MFDGyVQvQxM@aschofie-mobl2.lan/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=81fff5893a3140361cac625b09ada798afb92e03
---
drivers/cxl/core/core.h | 2 +-
drivers/cxl/core/hdm.c | 2 +-
drivers/cxl/core/hdm_regs.c | 177 +++++++++++++++++++++++++++++++---
drivers/pci/cxl.c | 185 ++++++++++++++++++++++++++++++++++--
include/cxl/hdm.h | 2 +
5 files changed, 346 insertions(+), 22 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index e34d574f6594..9fc9fd6c771e 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -145,7 +145,7 @@ void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *setti
void __iomem *hdm);
void cxl_commit_start_switch(const struct cxl_decoder_config *config,
u64 targets, void __iomem *hdm);
-int cxld_await_commit(void __iomem *hdm, int id);
+int cxld_await_commit(void __iomem *hdm, int id, bool committed);
int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
u32 ctrl, u64 base, u64 size);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index ec387070d7ad..ad8cb1e76afb 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -815,7 +815,7 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
scoped_guard(rwsem_read, &cxl_rwsem.dpa)
setup_hw_decoder(cxld, hdm);
- rc = cxld_await_commit(hdm, cxld->config.id);
+ rc = cxld_await_commit(hdm, cxld->config.id, true);
if (rc) {
dev_dbg(&port->dev, "%s: error %d committing decoder\n",
dev_name(&cxld->dev), rc);
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index c92dbb017162..4f5e843cd141 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -52,7 +52,7 @@ static void cxld_set_type(const struct cxl_decoder_config *config, u32 *ctrl)
* clock skew and other marginal behavior
*/
#define COMMIT_TIMEOUT_MS 20
-int cxld_await_commit(void __iomem *hdm, int id)
+int cxld_await_commit(void __iomem *hdm, int id, bool committed)
{
u32 ctrl;
int i;
@@ -64,7 +64,7 @@ int cxld_await_commit(void __iomem *hdm, int id)
writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
return -EIO;
}
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
+ if (!!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) == committed)
return 0;
fsleep(1000);
}
@@ -92,8 +92,12 @@ static u32 cxl_hdm_write_range(void __iomem *hdm,
return ctrl;
}
-void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
- void __iomem *hdm)
+/*
+ * Program every endpoint decoder register except Commit and Lock On Commit.
+ * Return the control value to write; Commit is not set in it.
+ */
+static u32 cxl_hdm_program_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm)
{
int id = settings->config.id;
u32 ctrl;
@@ -101,7 +105,17 @@ void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *setti
ctrl = cxl_hdm_write_range(hdm, &settings->config);
writel(upper_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_HIGH(id));
writel(lower_32_bits(settings->skip), hdm + CXL_HDM_DECODER0_SKIP_LOW(id));
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+
+ return ctrl & ~CXL_HDM_DECODER0_CTRL_COMMIT;
+}
+
+void cxl_commit_start_endpoint(const struct cxl_endpoint_decoder_settings *settings,
+ void __iomem *hdm)
+{
+ u32 ctrl = cxl_hdm_program_endpoint(settings, hdm);
+
+ writel(ctrl | CXL_HDM_DECODER0_CTRL_COMMIT,
+ hdm + CXL_HDM_DECODER0_CTRL_OFFSET(settings->config.id));
}
EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start_endpoint, "cxl_core");
@@ -220,8 +234,7 @@ int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
}
/* Read one endpoint decoder; a disabled decoder keeps only its id. */
-static int cxl_hdm_read_decoder_settings(struct device *dev,
- struct cxl_endpoint_decoder_settings *settings,
+static int cxl_hdm_read_decoder_settings(struct cxl_endpoint_decoder_settings *settings,
void __iomem *hdm, int id)
{
u64 lo, hi;
@@ -230,12 +243,8 @@ static int cxl_hdm_read_decoder_settings(struct device *dev,
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
rc = cxl_hdm_read_decoder(&settings->config, hdm, id, ctrl);
- if (rc) {
- dev_err(dev,
- "CXL HDM decoder %d has invalid configuration: %d\n",
- id, rc);
+ if (rc)
return rc;
- }
if (!(settings->config.flags & CXL_DECODER_F_ENABLE)) {
*settings = (struct cxl_endpoint_decoder_settings) {
@@ -267,10 +276,13 @@ int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
info->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
for (int i = 0; i < info->decoder_count; i++) {
- rc = cxl_hdm_read_decoder_settings(dev, &info->settings[i],
+ rc = cxl_hdm_read_decoder_settings(&info->settings[i],
hdm, i);
- if (rc)
+ if (rc) {
+ dev_err(dev, "CXL HDM decoder %d has invalid configuration: %d\n",
+ i, rc);
return rc;
+ }
}
/*
@@ -286,3 +298,140 @@ int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
return 0;
}
+
+static int cxl_hdm_decoder_uncommit(void __iomem *hdm, int id, bool *retained)
+{
+ u32 ctrl;
+
+ *retained = false;
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (ctrl == U32_MAX)
+ return -EIO;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMIT_ERROR)
+ return -EIO;
+
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK) {
+ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) {
+ *retained = true;
+ return 0;
+ }
+
+ return -EIO;
+ }
+
+ if (!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED))
+ return 0;
+
+ ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+
+ return cxld_await_commit(hdm, id, false);
+}
+
+static bool
+cxl_hdm_decoder_settings_match(const struct cxl_endpoint_decoder_settings *saved,
+ const struct cxl_endpoint_decoder_settings *active)
+{
+ const struct cxl_decoder_config *a = &saved->config;
+ const struct cxl_decoder_config *b = &active->config;
+
+ return a->id == b->id &&
+ a->hpa_range.start == b->hpa_range.start &&
+ a->hpa_range.end == b->hpa_range.end &&
+ a->interleave_ways == b->interleave_ways &&
+ a->interleave_granularity == b->interleave_granularity &&
+ a->target_type == b->target_type &&
+ (a->flags & CXL_DECODER_F_RESET_MASK) ==
+ (b->flags & CXL_DECODER_F_RESET_MASK) &&
+ saved->skip == active->skip;
+}
+
+static int
+cxl_hdm_restore_decoder(void __iomem *hdm,
+ const struct cxl_endpoint_decoder_settings *settings)
+{
+ const struct cxl_decoder_config *config = &settings->config;
+ bool retained;
+ u32 ctrl;
+ int rc;
+
+ rc = cxl_hdm_decoder_uncommit(hdm, config->id, &retained);
+ if (rc)
+ return rc;
+
+ if (retained) {
+ struct cxl_endpoint_decoder_settings active;
+
+ rc = cxl_hdm_read_decoder_settings(&active, hdm, config->id);
+ if (rc)
+ return rc;
+ if (!cxl_hdm_decoder_settings_match(settings, &active))
+ return -EIO;
+ return 0;
+ }
+
+ if (!(config->flags & CXL_DECODER_F_ENABLE))
+ return 0;
+
+ /*
+ * Configure every decoder register, then set Lock On Commit, then set
+ * Commit, as CXL describes. Setting Lock On Commit before the base and
+ * size are written can make a device drop those writes.
+ */
+ ctrl = cxl_hdm_program_endpoint(settings, hdm);
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ if (config->flags & CXL_DECODER_F_LOCK) {
+ ctrl |= CXL_HDM_DECODER0_CTRL_LOCK;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ }
+ writel(ctrl | CXL_HDM_DECODER0_CTRL_COMMIT,
+ hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ rc = cxld_await_commit(hdm, config->id, true);
+ if (rc)
+ return rc;
+
+ if (!(config->flags & CXL_DECODER_F_LOCK))
+ return 0;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ if (ctrl == U32_MAX || !(ctrl & CXL_HDM_DECODER0_CTRL_LOCK))
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * cxl_hdm_restore_state() - reprogram endpoint HDM decoders from a snapshot
+ * @hdm: mapped HDM decoder capability
+ * @snapshot: state from cxl_hdm_cache_snapshot()
+ *
+ * Restore global control, then recommit each enabled decoder in order. Leave
+ * a locked decoder that kept its programming across the reset unchanged,
+ * after checking that it still matches @snapshot.
+ *
+ * Return: 0 on success, or the first failure.
+ */
+int cxl_hdm_restore_state(void __iomem *hdm,
+ const struct cxl_hdm_info *snapshot)
+{
+ int count;
+ int rc;
+
+ count = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (count != snapshot->decoder_count)
+ return -ENXIO;
+
+ /* Restore global HDM control before committing individual decoders. */
+ writel(snapshot->global_ctrl, hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+
+ for (int i = 0; i < snapshot->decoder_count; i++) {
+ rc = cxl_hdm_restore_decoder(hdm, &snapshot->settings[i]);
+ if (rc)
+ return rc;
+ }
+
+ /* Flush posted HDM writes before restoring protocol enables. */
+ readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+
+ return 0;
+}
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index c07266ab0133..77d0e30841d9 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
/*
- * CXL reset support in the PCI core. Cache the HDM decoder state of a CXL
- * memory device at enumeration, so it can be restored after a reset without
- * a CXL driver, and implement CXL Device Reset. HDM register formats and
+ * CXL reset support in the PCI core: cache the HDM decoder state of a CXL
+ * memory device at enumeration, implement CXL Device Reset, and restore the
+ * cached state afterwards without a CXL driver. HDM register formats and
* programming live in drivers/cxl.
*/
#include <linux/bitfield.h>
@@ -16,6 +16,7 @@
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/memregion.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -236,6 +237,162 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
cxl_hdm_cache_release(&pdev->hdm);
}
+static void __iomem *cxl_pci_hdm_ioremap_current(struct pci_dev *pdev,
+ int bar,
+ resource_size_t offset,
+ resource_size_t size)
+{
+ struct resource *res;
+ resource_size_t hdm_start;
+ void __iomem *hdm;
+
+ if (bar < 0 || bar >= PCI_STD_NUM_BARS || !size)
+ return IOMEM_ERR_PTR(-EINVAL);
+ if (!cxl_pci_bar_assigned(pdev, bar))
+ return IOMEM_ERR_PTR(-ENODEV);
+
+ res = &pdev->resource[bar];
+ if (offset > resource_size(res) || size > resource_size(res) - offset)
+ return IOMEM_ERR_PTR(-ENODEV);
+ if (check_add_overflow(res->start, offset, &hdm_start))
+ return IOMEM_ERR_PTR(-EOVERFLOW);
+
+ hdm = ioremap(hdm_start, size);
+ if (!hdm) {
+ pci_err(pdev, "failed to remap CXL HDM decoder registers\n");
+ return IOMEM_ERR_PTR(-ENOMEM);
+ }
+
+ return hdm;
+}
+
+static void cxl_restore_pci_state_for_hdm_restore(struct pci_dev *pdev,
+ u16 *command)
+{
+ u32 saved_config = pdev->saved_config_space[PCI_COMMAND / 4];
+ u32 restore_config;
+
+ device_lock_assert(&pdev->dev);
+ *command = saved_config & 0xffff;
+
+ /*
+ * Restore BARs and the related PCI state needed to reach HDM MMIO.
+ * Temporarily alter the saved command value so that
+ * pci_restore_state() enables MSE but cannot enable BME or INTx before
+ * CXL state restoration completes. Preserve the original saved value
+ * for the outer PCI reset path.
+ */
+ restore_config = saved_config | PCI_COMMAND_MEMORY |
+ PCI_COMMAND_INTX_DISABLE;
+ restore_config &= ~PCI_COMMAND_MASTER;
+
+ pdev->saved_config_space[PCI_COMMAND / 4] = restore_config;
+ pci_restore_state(pdev);
+ pdev->saved_config_space[PCI_COMMAND / 4] = saved_config;
+}
+
+static int cxl_restore_dvsec_ctrl(struct pci_dev *pdev,
+ const struct cxl_hdm_info *snapshot)
+{
+ int rc;
+
+ if (!snapshot->dvsec_ctrl_valid) {
+ pci_err(pdev, "CXL DVSEC control snapshot is invalid\n");
+ return -ENXIO;
+ }
+
+ rc = pci_write_config_word(pdev, snapshot->dvsec + PCI_DVSEC_CXL_CTRL,
+ snapshot->dvsec_ctrl);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ pci_err(pdev, "failed to restore CXL DVSEC control: %d\n", rc);
+ }
+
+ return rc;
+}
+
+static int cxl_restore_state(struct pci_dev *pdev,
+ const struct cxl_hdm_info *snapshot)
+{
+ void __iomem *hdm;
+ int rc;
+
+ hdm = cxl_pci_hdm_ioremap_current(pdev, snapshot->hdm_bar,
+ snapshot->hdm_offset,
+ snapshot->hdm_size);
+ if (IS_ERR(hdm)) {
+ pci_err(pdev, "failed to map CXL HDM state for restore: %ld\n",
+ PTR_ERR(hdm));
+ return PTR_ERR(hdm);
+ }
+
+ rc = cxl_hdm_restore_state(hdm, snapshot);
+ iounmap(hdm);
+ if (rc) {
+ pci_err(pdev, "failed to restore CXL HDM state: %d\n", rc);
+ return rc;
+ }
+
+ return cxl_restore_dvsec_ctrl(pdev, snapshot);
+}
+
+static void cxl_reset_save_disabled_state(struct pci_dev *pdev)
+{
+ u32 *saved_command = &pdev->saved_config_space[PCI_COMMAND / 4];
+ int rc;
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
+ if (rc)
+ pci_err(pdev,
+ "failed to disable device after CXL reset failure: %d\n",
+ pcibios_err_to_errno(rc));
+
+ /* Preserve the pre-reset snapshot if PCI state has not been restored. */
+ if (!pdev->state_saved) {
+ rc = pci_save_state(pdev);
+ if (rc)
+ pci_err(pdev,
+ "failed to save disabled state after CXL reset failure: %d\n",
+ rc);
+ }
+
+ /* Ensure the outer PCI restore cannot re-enable MSE or BME. */
+ *saved_command &= GENMASK(31, 16);
+ *saved_command |= PCI_COMMAND_INTX_DISABLE;
+}
+
+static int cxl_reset_save_restored_state(struct pci_dev *pdev, u16 command)
+{
+ int rc;
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ pci_err(pdev, "failed to restore PCI command state: %d\n", rc);
+ cxl_reset_save_disabled_state(pdev);
+ return rc;
+ }
+
+ rc = pci_save_state(pdev);
+ if (rc) {
+ pci_err(pdev, "failed to save restored state after CXL reset: %d\n",
+ rc);
+ cxl_reset_save_disabled_state(pdev);
+ }
+
+ return rc;
+}
+
+/* Caller holds the region lock and keeps device traffic isolated. */
+static int pci_cxl_restore_state(struct pci_dev *pdev,
+ const struct cxl_hdm_info *snapshot, u16 *command)
+ __must_hold(cxl_region_quiesce)
+{
+ device_lock_assert(&pdev->dev);
+ cxl_restore_pci_state_for_hdm_restore(pdev, command);
+ return cxl_restore_state(pdev, snapshot);
+}
+
/*
* CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
* Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
@@ -415,7 +572,9 @@ static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
}
static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
- struct cxl_hdm_quiesce *q)
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_hdm_quiesce *q,
+ u16 *command)
__must_hold(cxl_region_quiesce)
{
int rc, rc2;
@@ -441,10 +600,15 @@ static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n", rc2);
rc = rc ?: rc2;
+ if (!rc)
+ rc = pci_cxl_restore_state(pdev, snapshot, command);
+
rc2 = cxl_hdm_quiesce_flush(q);
if (rc2)
pci_err(pdev, "failed to invalidate CPU cache after CXL reset: %d\n", rc2);
rc = rc ?: rc2;
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
pci_dev_reset_iommu_done(pdev);
return rc;
@@ -455,6 +619,7 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
{
struct cxl_hdm_info *snapshot __free(kfree) = NULL;
struct cxl_pci_cache cache = {};
+ u16 command;
int rc, rc2;
rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2,
@@ -478,11 +643,19 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
return rc;
}
if (!rc)
- rc = __cxl_reset_execute(pdev, dvsec, cap, &cache.quiesce);
+ rc = __cxl_reset_execute(pdev, dvsec, cap, snapshot, &cache.quiesce,
+ &command);
/* Restore cache policy after any attempt to disable caching. */
rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
cxl_hdm_quiesce_destroy(&cache.quiesce);
- return rc ?: rc2;
+ if (rc)
+ return rc;
+ if (rc2) {
+ cxl_reset_save_disabled_state(pdev);
+ return rc2;
+ }
+
+ return cxl_reset_save_restored_state(pdev, command);
}
static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 49b36a45c682..94a8f8a1a65a 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -54,6 +54,8 @@ int cxl_hdm_decoder_count_mapped(struct device *dev, void __iomem *hdm,
resource_size_t size);
int cxl_hdm_read_state(struct device *dev, void __iomem *hdm,
struct cxl_hdm_info *info);
+int cxl_hdm_restore_state(void __iomem *hdm,
+ const struct cxl_hdm_info *snapshot);
void cxl_hdm_cache_set_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl);
void cxl_hdm_cache_refresh_decoder(struct pci_dev *pdev, void __iomem *hdm,
struct cxl_endpoint_decoder *cxled);
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset
2026-11-05 18:59 ` [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
@ 2026-11-05 19:34 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:34 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:
- [Medium] PCI/CXL: cxl_reset_quiesced() skips disabling device if cache restore fails
--
commit 0833e3bda8d31d8ddf0c1310990a9ee4b81a1760
Author: Srirangan Madhavan <smadhavan@nvidia.com>
PCI/CXL: Restore CXL state after PCI reset
This commit restores PCI configuration, cached HDM decoder settings, and CXL
Device DVSEC control state after a CXL device reset. If any restoration step
fails, it disables the device and updates the saved PCI command state so the
outer PCI reset flow does not re-enable memory decoding or bus mastering.
> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> index c07266ab01336..77d0e30841d9f 100644
> --- a/drivers/pci/cxl.c
> +++ b/drivers/pci/cxl.c
[ ... ]
> @@ -415,7 +572,9 @@ static int cxl_clear_memory(struct pci_dev *pdev, int dvsec, bool initiate)
> }
>
> static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
> - struct cxl_hdm_quiesce *q)
> + const struct cxl_hdm_info *snapshot,
> + struct cxl_hdm_quiesce *q,
> + u16 *command)
> __must_hold(cxl_region_quiesce)
> {
> int rc, rc2;
> @@ -441,10 +600,15 @@ static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
> pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n", rc2);
> rc = rc ?: rc2;
>
> + if (!rc)
> + rc = pci_cxl_restore_state(pdev, snapshot, command);
> +
> rc2 = cxl_hdm_quiesce_flush(q);
> if (rc2)
> pci_err(pdev, "failed to invalidate CPU cache after CXL reset: %d\n", rc2);
> rc = rc ?: rc2;
> + if (rc)
> + cxl_reset_save_disabled_state(pdev);
>
> pci_dev_reset_iommu_done(pdev);
> return rc;
> @@ -455,6 +619,7 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
> {
> struct cxl_hdm_info *snapshot __free(kfree) = NULL;
> struct cxl_pci_cache cache = {};
> + u16 command;
> int rc, rc2;
>
> rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2,
> @@ -478,11 +643,19 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
> return rc;
> }
> if (!rc)
> - rc = __cxl_reset_execute(pdev, dvsec, cap, &cache.quiesce);
> + rc = __cxl_reset_execute(pdev, dvsec, cap, snapshot, &cache.quiesce,
> + &command);
> /* Restore cache policy after any attempt to disable caching. */
> rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
> cxl_hdm_quiesce_destroy(&cache.quiesce);
> - return rc ?: rc2;
> + if (rc)
> + return rc;
> + if (rc2) {
> + cxl_reset_save_disabled_state(pdev);
> + return rc2;
> + }
[Severity: Medium]
In drivers/pci/cxl.c:cxl_reset_quiesced(), can returning early on if (rc)
before checking if (rc2) skip calling cxl_reset_save_disabled_state(pdev) when
restoring the CXL cache policy also fails?
Specifically, rc can be non-zero before cxl_reset_save_disabled_state(pdev) is
reached in two cases after CXL caching has been disabled:
1. cxl_reset_cache_prepare() sets cache.cache_disabled = true and disables
caching via cxl_reset_disable_cache(), but cxl_reset_wait_cache_wbi() then
fails.
2. cxl_reset_cache_prepare() succeeds, and __cxl_reset_execute() returns early
from pci_wait_for_pending_transaction() or pci_dev_reset_iommu_prepare()
before reaching cxl_reset_save_disabled_state(pdev) at the end of
__cxl_reset_execute():
drivers/pci/cxl.c:__cxl_reset_execute() {
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;
}
...
}
In either case, pdev->saved_config_space[PCI_COMMAND / 4] still holds the
pre-reset MSE and BME bits. If cxl_reset_restore_cache_policy() then fails
(rc2 != 0), checking if (rc) first returns without calling
cxl_reset_save_disabled_state(pdev), allowing the outer PCI reset path to
re-enable memory decoding and bus mastering on a device whose CXL cache policy
in PCI_DVSEC_CXL_CTRL2 was not restored.
Should the if (rc2) check call cxl_reset_save_disabled_state(pdev) before
returning rc?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=15
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 16/19] PCI/CXL: Expose CXL Reset as a PCI reset method
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (14 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 15/19] PCI/CXL: Restore CXL state after PCI reset Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:35 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
` (3 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Register cxl_reset with the PCI reset-method interface and place it before
FLR, which does not reset CXL.cache or CXL.mem protocol state.
Return -ENOTTY when device capabilities, function scope, cached HDM state,
or required cache synchronization do not permit a safe reset.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Reviewed-by: Richard Cheng <icheng@nvidia.com>
---
drivers/pci/cxl.c | 12 +++++++++---
drivers/pci/pci.c | 1 +
include/linux/pci.h | 2 +-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index 77d0e30841d9..c68b08f7f95b 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -679,7 +679,7 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
if (rc)
- return rc;
+ return probe ? -ENOTTY : rc;
if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
!(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
!(cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
@@ -688,9 +688,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL, &ctrl);
if (rc)
- return pcibios_err_to_errno(rc);
+ return probe ? -ENOTTY : pcibios_err_to_errno(rc);
if (PCI_POSSIBLE_ERROR(ctrl))
- return -ENODEV;
+ return probe ? -ENOTTY : -ENODEV;
if (!(ctrl & PCI_DVSEC_CXL_CACHE_ENABLE) ||
!(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
return -ENOTTY;
@@ -710,5 +710,11 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
!IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST))
return -ENOTTY;
+ /* Raw reset callers may not have saved the PCI state needed for restore. */
+ if (!pdev->state_saved) {
+ pci_err(pdev, "CXL reset requires saved PCI state\n");
+ return -EINVAL;
+ }
+
return cxl_reset_execute(pdev, dvsec, cap);
}
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index aef976d0c850..426b5ee38d04 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5152,6 +5152,7 @@ const struct pci_reset_fn_method pci_reset_fn_methods[] = {
{ },
{ pci_dev_specific_reset, .name = "device_specific" },
{ pci_dev_acpi_reset, .name = "acpi" },
+ { cxl_reset_function, .name = "cxl_reset" },
{ pcie_reset_flr, .name = "flr" },
{ pci_af_flr, .name = "af_flr" },
{ pci_pm_reset, .name = "pm" },
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 7bb37fcb556d..743b57ff885a 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -52,7 +52,7 @@
PCI_STATUS_PARITY)
/* Number of reset methods used in pci_reset_fn_methods array in pci.c */
-#define PCI_NUM_RESET_METHODS 8
+#define PCI_NUM_RESET_METHODS 9
#define PCI_RESET_PROBE true
#define PCI_RESET_DO_RESET false
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (15 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 16/19] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:43 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 18/19] PCI/CXL: Flush host and device caches for " Srirangan Madhavan
` (2 subsequent siblings)
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
CXL bus reset can clear HDM decoder programming and CXL Device DVSEC
control state. Restore the cached state after a successful cxl_bus reset
while IOMMU exclusion remains active.
Restore PCI configuration first so HDM MMIO is accessible, and preserve a
disabled state if restoration fails.
Copy the cached state before the reset, then refresh its decoder settings
from the cache under the CXL region quiesce once the reset completes. A
region decommit during the reset updates the cache, and restoring the
earlier decoders would leave the endpoint decoding an HPA range that the
CXL core considers free. Keep global and DVSEC control from before the
reset, since a refresh during the reset can read them as all-ones.
The prepare/restore interface and decoder-refresh structure follow Dave
Jiang's cxl-type2-reset reference branch.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
Acked-by: Bjorn Helgaas <bhelgaas@google.com> # pci/pci.c
Assisted-by: LLM
---
Changes in v17:
- Enable bus mastering only after the IOMMU is restored, via a separate
cxl_restore_command_after_pci_reset() step (Richard)
https://lore.kernel.org/linux-cxl/auRhJa-nzR0CnKf3@MWDK4CY14F/
Reference branch: https://git.kernel.org/pub/scm/linux/kernel/git/djiang/linux.git/commit/?id=b93f0c8e67b198a63786fd3721b7177ab9df60be
---
drivers/cxl/core/hdm_state.c | 31 +++++++++++++
drivers/pci/cxl.c | 85 ++++++++++++++++++++++++++++++++++++
drivers/pci/pci.c | 14 ++++++
drivers/pci/pci.h | 24 ++++++++++
include/cxl/hdm.h | 2 +
5 files changed, 156 insertions(+)
diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
index 51df56377f7a..7e0903ce0b78 100644
--- a/drivers/cxl/core/hdm_state.c
+++ b/drivers/cxl/core/hdm_state.c
@@ -182,6 +182,37 @@ struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot)
return copy;
}
+/**
+ * cxl_hdm_cache_copy_decoders() - refresh the decoders of a snapshot
+ * @slot: cache pointer owned by the device
+ * @dst: snapshot of the same device from cxl_hdm_cache_snapshot()
+ *
+ * Copy only the per-decoder settings, which region commit and teardown keep
+ * current. Leave the rest of @dst, including global and DVSEC control, as
+ * captured: a refresh during a reset may have read those registers from a
+ * device that returned all-ones.
+ *
+ * Return: 0 on success, -ENXIO when nothing is published, or -EINVAL when
+ * @dst holds a different number of decoders.
+ */
+int cxl_hdm_cache_copy_decoders(struct cxl_hdm_info *const *slot,
+ struct cxl_hdm_info *dst)
+{
+ struct cxl_hdm_info *info;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = *slot;
+ if (!info)
+ return -ENXIO;
+ if (info->decoder_count != dst->decoder_count)
+ return -EINVAL;
+
+ memcpy(dst->settings, info->settings,
+ flex_array_size(info, settings, info->decoder_count));
+ return 0;
+}
+
void cxl_region_quiesce_lock(void)
__acquires(cxl_region_quiesce)
__context_unsafe(token maps to cxl_rwsem.region)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index c68b08f7f95b..a9aa2e1c631d 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -393,6 +393,91 @@ static int pci_cxl_restore_state(struct pci_dev *pdev,
return cxl_restore_state(pdev, snapshot);
}
+/**
+ * pci_cxl_reset_prepare() - allocate HDM state storage ahead of a bus reset
+ * @pdev: device about to be reset
+ *
+ * Copy the cached HDM state before the reset, so that restoring cannot fail
+ * for lack of memory. The decoder settings are refreshed when the state is
+ * restored.
+ *
+ * Return: storage for cxl_restore_state_after_pci_reset(), to be freed with
+ * kfree(); NULL when @pdev has no cached HDM state; or an ERR_PTR() on
+ * failure.
+ */
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *snapshot;
+
+ device_lock_assert(&pdev->dev);
+ snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
+ if (IS_ERR(snapshot))
+ return PTR_ERR(snapshot) == -ENXIO ? NULL : snapshot;
+
+ /* Raw reset callers may not have saved the PCI state needed for restore. */
+ if (!pdev->state_saved) {
+ pci_err(pdev, "CXL bus reset requires saved PCI state\n");
+ kfree(snapshot);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return snapshot;
+}
+
+/**
+ * cxl_restore_state_after_pci_reset() - restore CXL state after a bus reset
+ * @pdev: device that was reset
+ * @snapshot: storage from pci_cxl_reset_prepare()
+ * @command: returns the saved PCI_COMMAND value to apply once the IOMMU is
+ * restored
+ *
+ * Under the region quiesce, refresh the decoder settings in @snapshot from the
+ * cache and restore: first the PCI configuration needed to reach HDM MMIO,
+ * then the HDM decoder and CXL Device DVSEC state. Leave @pdev disabled on
+ * failure.
+ *
+ * Bus mastering stays off, because the caller has not yet restored IOMMU
+ * translation and a DMA request now would hit the blocking domain. The caller
+ * calls cxl_restore_command_after_pci_reset() once the IOMMU is restored.
+ *
+ * Refresh the decoders now rather than rely on the copy taken before the
+ * reset: a region commit or teardown during the reset has updated the cache,
+ * and restoring older decoders would leave the device decoding ranges the
+ * CXL core considers free.
+ */
+int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot,
+ u16 *command)
+{
+ int rc;
+
+ device_lock_assert(&pdev->dev);
+ cxl_region_quiesce_lock();
+
+ rc = cxl_hdm_cache_copy_decoders(&pdev->hdm, snapshot);
+ if (!rc)
+ rc = pci_cxl_restore_state(pdev, snapshot, command);
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
+
+ cxl_region_quiesce_unlock();
+ return rc;
+}
+
+/**
+ * cxl_restore_command_after_pci_reset() - re-enable the device after a bus reset
+ * @pdev: device that was reset
+ * @command: PCI_COMMAND value from cxl_restore_state_after_pci_reset()
+ *
+ * Call after the IOMMU is restored: this can enable bus mastering. Save the
+ * restored PCI state, and leave @pdev disabled on failure.
+ */
+int cxl_restore_command_after_pci_reset(struct pci_dev *pdev, u16 command)
+{
+ device_lock_assert(&pdev->dev);
+ return cxl_reset_save_restored_state(pdev, command);
+}
+
/*
* CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
* Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 426b5ee38d04..1d4ba90a6976 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5014,8 +5014,10 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
{
+ struct cxl_hdm_info *snapshot __free(kfree) = NULL;
struct pci_dev *bridge;
u16 dvsec, reg, val;
+ u16 command;
int rc;
/*
@@ -5040,6 +5042,10 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (rc)
return -ENOTTY;
+ snapshot = pci_cxl_reset_prepare(dev);
+ if (IS_ERR(snapshot))
+ return PTR_ERR(snapshot);
+
rc = pci_dev_reset_iommu_prepare(dev);
if (rc) {
pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
@@ -5060,7 +5066,15 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
reg);
+ if (!rc && snapshot)
+ rc = cxl_restore_state_after_pci_reset(dev, snapshot, &command);
+
pci_dev_reset_iommu_done(dev);
+
+ /* Enable bus mastering only once the IOMMU translates again. */
+ if (!rc && snapshot)
+ rc = cxl_restore_command_after_pci_reset(dev, command);
+
return rc;
}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 743e65abd758..2f819e1fbead 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -730,10 +730,16 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
+struct cxl_hdm_info;
#ifdef CONFIG_CXL_RESET
void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
int cxl_reset_function(struct pci_dev *pdev, bool probe);
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev);
+int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot,
+ u16 *command);
+int cxl_restore_command_after_pci_reset(struct pci_dev *pdev, u16 command);
#else
static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev) { }
@@ -741,6 +747,24 @@ static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
return -ENOTTY;
}
+
+static inline struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
+{
+ return NULL;
+}
+
+static inline int
+cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot, u16 *command)
+{
+ return 0;
+}
+
+static inline int cxl_restore_command_after_pci_reset(struct pci_dev *pdev,
+ u16 command)
+{
+ return 0;
+}
#endif
#ifdef CONFIG_PCI_NPEM
diff --git a/include/cxl/hdm.h b/include/cxl/hdm.h
index 94a8f8a1a65a..81767f21052a 100644
--- a/include/cxl/hdm.h
+++ b/include/cxl/hdm.h
@@ -64,6 +64,8 @@ void cxl_hdm_cache_set_normalized(struct pci_dev *pdev, int id);
void cxl_hdm_cache_release(struct cxl_hdm_info **slot);
bool cxl_hdm_cache_dvsec_decode(struct cxl_hdm_info *const *slot);
struct cxl_hdm_info *cxl_hdm_cache_snapshot(struct cxl_hdm_info *const *slot);
+int cxl_hdm_cache_copy_decoders(struct cxl_hdm_info *const *slot,
+ struct cxl_hdm_info *dst);
/*
* Region quiesce blocks CXL region commit and teardown, and with them decoder
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset
2026-11-05 18:59 ` [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
@ 2026-11-05 19:43 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:43 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: __free(kfree) variable declared as NULL in cxl_reset_bus_function()
--
commit 293a1729e6b1792018354518fc69bc55b2adb408
Author: Srirangan Madhavan <smadhavan@nvidia.com>
PCI/CXL: Restore CXL state after CXL bus reset
This commit restores cached HDM decoder programming and CXL Device DVSEC
control state after a CXL bus reset, refreshing decoder settings under
region quiesce and re-enabling bus mastering only after IOMMU translation
is restored.
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index 426b5ee38d04f..1d4ba90a69762 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
> @@ -5014,8 +5014,10 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
>
> static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
> {
> + struct cxl_hdm_info *snapshot __free(kfree) = NULL;
> struct pci_dev *bridge;
> u16 dvsec, reg, val;
> + u16 command;
> int rc;
>
> /*
> @@ -5040,6 +5042,10 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
> if (rc)
> return -ENOTTY;
>
> + snapshot = pci_cxl_reset_prepare(dev);
> + if (IS_ERR(snapshot))
> + return PTR_ERR(snapshot);
[Severity: Low]
This isn't a bug, but in cxl_reset_bus_function(), snapshot is declared
with __free(kfree) = NULL at the top of the function and only assigned
after the early return paths:
struct cxl_hdm_info *snapshot __free(kfree) = NULL;
...
snapshot = pci_cxl_reset_prepare(dev);
Following the cleanup helper conventions in include/linux/cleanup.h, could
snapshot be defined and initialized in a single statement where
pci_cxl_reset_prepare() is called?
struct cxl_hdm_info *snapshot __free(kfree) =
pci_cxl_reset_prepare(dev);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=17
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 18/19] PCI/CXL: Flush host and device caches for CXL bus reset
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (16 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 17/19] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:56 ` sashiko-bot
2026-11-05 18:59 ` [PATCH v17 19/19] cxl: Add KUnit checks for HDM decoder unpacking Srirangan Madhavan
2026-11-05 19:31 ` [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Alison Schofield
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
CXL bus reset can reset a Type 2 device while its host and device caches
still contain data for the device's HDM ranges. Flush the host caches,
disable device caching, and trigger device cache writeback and
invalidation when supported before reset. Invalidate the host caches
again before ending IOMMU exclusion.
Hold the CXL region quiesce from snapshot through restoration so region
commit and teardown cannot change the decoder state while the reset is
in progress. Reserve the affected HPA ranges and preserve the device's
cache policy across the operation.
If reset was not attempted, undo preparation without changing the saved
PCI command unless cleanup fails. After an attempted reset, keep the
device disabled on recovery failure and do not re-enable caching against
unrestored HDM state. Enable bus mastering only after the IOMMU is
restored, so that no DMA request reaches the blocking domain.
If no cached HDM state exists, retain the existing CXL bus reset path.
For a device with cached state, reject the reset if its cache-maintenance
state cannot be safely prepared or restored. An invalidation provider is
required unless the existing test-only CXL_REGION_INVALIDATION_TEST bypass
is enabled.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
Changes in v17:
- New in this series: folded into the CXL reset series instead of being
posted separately (Dave)
---
drivers/pci/cxl.c | 166 +++++++++++++++++++++++++++++++++++++---------
drivers/pci/pci.c | 11 +--
drivers/pci/pci.h | 19 ++++--
3 files changed, 154 insertions(+), 42 deletions(-)
diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
index a9aa2e1c631d..c5cdad4b0590 100644
--- a/drivers/pci/cxl.c
+++ b/drivers/pci/cxl.c
@@ -393,74 +393,174 @@ static int pci_cxl_restore_state(struct pci_dev *pdev,
return cxl_restore_state(pdev, snapshot);
}
+static int cxl_reset_restore_cache_policy(struct pci_dev *pdev, int dvsec,
+ u16 saved_ctrl2);
+static int cxl_reset_cache_prepare(struct pci_dev *pdev, int dvsec, u16 cap,
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache);
+
/**
- * pci_cxl_reset_prepare() - allocate HDM state storage ahead of a bus reset
+ * pci_cxl_reset_prepare() - prepare HDM state and caches before a bus reset
* @pdev: device about to be reset
+ * @cachep: receives cache state owning the region quiesce on success
*
- * Copy the cached HDM state before the reset, so that restoring cannot fail
- * for lack of memory. The decoder settings are refreshed when the state is
- * restored.
+ * Capture HDM state under the region quiesce, reserve its ranges, and complete
+ * host and device cache maintenance. Hold the quiesce through reset and
+ * cxl_restore_state_after_pci_reset(), which consumes *@cachep.
*
- * Return: storage for cxl_restore_state_after_pci_reset(), to be freed with
- * kfree(); NULL when @pdev has no cached HDM state; or an ERR_PTR() on
- * failure.
+ * Return: snapshot to be freed with kfree(), NULL when no cached HDM state is
+ * present, or an ERR_PTR() on failure. A cached device without a valid DVSEC
+ * snapshot is rejected because its cache-maintenance scope is unknown.
+ * Only a successful non-NULL snapshot returns with the region quiesce held
+ * and *@cachep set. The caller must pass both to
+ * cxl_restore_state_after_pci_reset() even if reset is never attempted.
+ * The snapshot remains caller-owned. All other returns leave the quiesce
+ * unheld and *@cachep NULL.
*/
-struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev,
+ struct cxl_pci_cache **cachep)
{
struct cxl_hdm_info *snapshot;
+ struct cxl_pci_cache *cache;
+ u16 dvsec, cap, command;
+ int rc;
device_lock_assert(&pdev->dev);
- snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
- if (IS_ERR(snapshot))
- return PTR_ERR(snapshot) == -ENXIO ? NULL : snapshot;
+ *cachep = NULL;
+ if (!pdev->hdm)
+ return NULL;
+ rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
+ if (rc == -ENOTTY)
+ return ERR_PTR(-ENODEV);
+ if (rc)
+ return ERR_PTR(rc);
/* Raw reset callers may not have saved the PCI state needed for restore. */
if (!pdev->state_saved) {
pci_err(pdev, "CXL bus reset requires saved PCI state\n");
- kfree(snapshot);
return ERR_PTR(-EINVAL);
}
+ cache = kzalloc_obj(*cache);
+ if (!cache)
+ return ERR_PTR(-ENOMEM);
+ cxl_region_quiesce_lock();
+ snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
+ if (IS_ERR(snapshot)) {
+ rc = PTR_ERR(snapshot);
+ goto unlock;
+ }
+ if (!snapshot->dvsec_ctrl_valid) {
+ rc = -ENXIO;
+ goto free_snapshot;
+ }
+ if (cxl_hdm_cache_dvsec_decode(&pdev->hdm) ||
+ (snapshot->dvsec_ctrl & PCI_DVSEC_CXL_CACHE_ENABLE &&
+ !(cap & PCI_DVSEC_CXL_CACHE_WBI_CAPABLE))) {
+ rc = -EOPNOTSUPP;
+ goto free_snapshot;
+ }
+
+ if (cap & PCI_DVSEC_CXL_CACHE_CAPABLE) {
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2,
+ &cache->saved_ctrl2);
+ if (rc || PCI_POSSIBLE_ERROR(cache->saved_ctrl2)) {
+ rc = rc ? pcibios_err_to_errno(rc) : -ENODEV;
+ goto free_snapshot;
+ }
+ }
+
+ rc = cxl_reset_cache_prepare(pdev, dvsec, cap, snapshot, cache);
+ if (rc) {
+ if (cache->cache_disabled)
+ goto finish;
+ pci_err(pdev, "failed to prepare CXL bus reset ranges: %d\n", rc);
+ cxl_hdm_quiesce_destroy(&cache->quiesce);
+ goto free_snapshot;
+ }
+ if (!pci_wait_for_pending_transaction(pdev)) {
+ rc = -ETIMEDOUT;
+ goto finish;
+ }
+ *cachep = cache;
return snapshot;
+
+finish:
+ rc = cxl_restore_state_after_pci_reset(pdev, snapshot, cache, false, rc,
+ &command);
+ kfree(snapshot);
+ return ERR_PTR(rc);
+free_snapshot:
+ kfree(snapshot);
+unlock:
+ cxl_region_quiesce_unlock();
+ kfree(cache);
+ return ERR_PTR(rc);
}
/**
- * cxl_restore_state_after_pci_reset() - restore CXL state after a bus reset
+ * cxl_restore_state_after_pci_reset() - finish a prepared reset and release its quiesce
* @pdev: device that was reset
* @snapshot: storage from pci_cxl_reset_prepare()
+ * @cache: quiesce state from pci_cxl_reset_prepare()
+ * @reset_attempted: reset helper was invoked and may have affected hardware
+ * @rc: preparation or PCI reset result
* @command: returns the saved PCI_COMMAND value to apply once the IOMMU is
* restored
*
- * Under the region quiesce, refresh the decoder settings in @snapshot from the
- * cache and restore: first the PCI configuration needed to reach HDM MMIO,
- * then the HDM decoder and CXL Device DVSEC state. Leave @pdev disabled on
- * failure.
+ * Restore state through pci_cxl_restore_state(), complete host invalidation,
+ * and restore the cache policy changed by planned-reset preparation. When
+ * @reset_attempted is false, undo preparation without changing the saved PCI
+ * command, unless cleanup itself fails. After an attempted reset, keep caching
+ * and the device disabled if state restoration or host invalidation fails.
+ * A reset-helper error does not prove that hardware was untouched.
+ * Unplanned recovery uses pci_cxl_restore_state() directly and owns its own
+ * quiesce and cache-maintenance sequencing.
*
* Bus mastering stays off, because the caller has not yet restored IOMMU
* translation and a DMA request now would hit the blocking domain. The caller
* calls cxl_restore_command_after_pci_reset() once the IOMMU is restored.
*
- * Refresh the decoders now rather than rely on the copy taken before the
- * reset: a region commit or teardown during the reset has updated the cache,
- * and restoring older decoders would leave the device decoding ranges the
- * CXL core considers free.
+ * The quiesce blocks region commit and teardown throughout reset. Complete
+ * host cache invalidation before PCI operation resumes, then release the
+ * quiesce and free @cache on every exit. @snapshot remains caller-owned.
*/
int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
- struct cxl_hdm_info *snapshot,
- u16 *command)
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache,
+ bool reset_attempted, int rc, u16 *command)
{
- int rc;
+ int rc2, policy_rc = 0;
+ *command = 0;
+ if (!cache)
+ return rc;
device_lock_assert(&pdev->dev);
- cxl_region_quiesce_lock();
-
- rc = cxl_hdm_cache_copy_decoders(&pdev->hdm, snapshot);
- if (!rc)
+ if (reset_attempted && !rc)
rc = pci_cxl_restore_state(pdev, snapshot, command);
- if (rc)
- cxl_reset_save_disabled_state(pdev);
+ /* Complete host invalidation before ending IOMMU exclusion. */
+ rc2 = cxl_hdm_quiesce_flush(&cache->quiesce);
+ if (rc2)
+ pci_err(pdev, "failed to invalidate CPU cache after CXL bus reset: %d\n", rc2);
+ /* Undo preparation on abort; enable caching after reset only if safe. */
+ if (cache->cache_disabled && (!reset_attempted || (!rc && !rc2)))
+ policy_rc = cxl_reset_restore_cache_policy(pdev, snapshot->dvsec,
+ cache->saved_ctrl2);
+ if (reset_attempted) {
+ rc = rc ?: rc2 ?: policy_rc;
+ if (rc)
+ cxl_reset_save_disabled_state(pdev);
+ } else {
+ /* An aborted reset alone must not poison the saved PCI command. */
+ if (rc2 || policy_rc)
+ cxl_reset_save_disabled_state(pdev);
+ rc = rc ?: rc2 ?: policy_rc;
+ }
+
+ cxl_hdm_quiesce_destroy(&cache->quiesce);
cxl_region_quiesce_unlock();
+ kfree(cache);
return rc;
}
@@ -594,7 +694,7 @@ static int cxl_reset_cache_prepare(struct pci_dev *pdev, int dvsec, u16 cap,
int rc;
rc = cxl_hdm_quiesce_prepare(&cache->quiesce, snapshot);
- if (rc)
+ if (rc || !(cap & PCI_DVSEC_CXL_CACHE_CAPABLE))
return rc;
/* Track an attempted change so partial failures still restore policy. */
@@ -731,7 +831,9 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
rc = __cxl_reset_execute(pdev, dvsec, cap, snapshot, &cache.quiesce,
&command);
/* Restore cache policy after any attempt to disable caching. */
- rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
+ rc2 = 0;
+ if (cache.cache_disabled)
+ rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
cxl_hdm_quiesce_destroy(&cache.quiesce);
if (rc)
return rc;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 1d4ba90a6976..b29a6a43249d 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5015,6 +5015,7 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
{
struct cxl_hdm_info *snapshot __free(kfree) = NULL;
+ struct cxl_pci_cache *cache;
struct pci_dev *bridge;
u16 dvsec, reg, val;
u16 command;
@@ -5042,14 +5043,15 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (rc)
return -ENOTTY;
- snapshot = pci_cxl_reset_prepare(dev);
+ snapshot = pci_cxl_reset_prepare(dev, &cache);
if (IS_ERR(snapshot))
return PTR_ERR(snapshot);
rc = pci_dev_reset_iommu_prepare(dev);
if (rc) {
pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
- return rc;
+ return cxl_restore_state_after_pci_reset(dev, snapshot, cache,
+ false, rc, &command);
}
if (reg & PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR) {
@@ -5066,8 +5068,9 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
reg);
- if (!rc && snapshot)
- rc = cxl_restore_state_after_pci_reset(dev, snapshot, &command);
+ /* Reset-helper failure does not prove that no hardware reset occurred. */
+ rc = cxl_restore_state_after_pci_reset(dev, snapshot, cache, true, rc,
+ &command);
pci_dev_reset_iommu_done(dev);
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 2f819e1fbead..7f6b81cf734b 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -731,14 +731,17 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
struct cxl_hdm_info;
+struct cxl_pci_cache;
#ifdef CONFIG_CXL_RESET
void pci_cxl_hdm_cache_init(struct pci_dev *pdev);
void pci_cxl_hdm_cache_release(struct pci_dev *pdev);
int cxl_reset_function(struct pci_dev *pdev, bool probe);
-struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev);
+struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev,
+ struct cxl_pci_cache **cachep);
int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
- struct cxl_hdm_info *snapshot,
- u16 *command);
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache,
+ bool reset_attempted, int rc, u16 *command);
int cxl_restore_command_after_pci_reset(struct pci_dev *pdev, u16 command);
#else
static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev) { }
@@ -748,16 +751,20 @@ static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
return -ENOTTY;
}
-static inline struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
+static inline struct cxl_hdm_info *
+pci_cxl_reset_prepare(struct pci_dev *pdev, struct cxl_pci_cache **cachep)
{
+ *cachep = NULL;
return NULL;
}
static inline int
cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
- struct cxl_hdm_info *snapshot, u16 *command)
+ const struct cxl_hdm_info *snapshot,
+ struct cxl_pci_cache *cache,
+ bool reset_attempted, int rc, u16 *command)
{
- return 0;
+ return rc;
}
static inline int cxl_restore_command_after_pci_reset(struct pci_dev *pdev,
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 18/19] PCI/CXL: Flush host and device caches for CXL bus reset
2026-11-05 18:59 ` [PATCH v17 18/19] PCI/CXL: Flush host and device caches for " Srirangan Madhavan
@ 2026-11-05 19:56 ` sashiko-bot
0 siblings, 0 replies; 40+ messages in thread
From: sashiko-bot @ 2026-11-05 19:56 UTC (permalink / raw)
To: Srirangan Madhavan, linux-cxl, linux-pci
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] PCI/CXL: cxl_restore_state_after_pci_reset() enables cache before flush
- [Medium] PCI/CXL: cxl_reset_quiesced() restores caching and skips disable on error
- [Medium] PCI/CXL: cxl_reset_bus_function() disables device on pre-reset -ENOTTY
- [Medium] PCI/CXL: TOCTOU race on snapshot in pci_cxl_reset_prepare()
- [Low] PCI/CXL: missing cxl_region_quiesce context annotations on reset helpers
- [Low] cxl: unused cxl_hdm_cache_copy_decoders() after bus reset quiesce change
--
commit 6351634e948c8a95d981c82600454247642192c2
Author: Srirangan Madhavan <smadhavan@nvidia.com>
PCI/CXL: Flush host and device caches for CXL bus reset
This commit adds host and device cache flushing, HPA range reservation, and
CXL region quiesce locking around CXL bus resets when cached HDM state is
present. It also preserves and restores the device cache policy across the
reset and leaves the device disabled if recovery fails after an attempted
reset.
> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> index a9aa2e1c631d1..c5cdad4b05906 100644
> --- a/drivers/pci/cxl.c
> +++ b/drivers/pci/cxl.c
> @@ -393,74 +393,174 @@ static int pci_cxl_restore_state(struct pci_dev *pdev,
> return cxl_restore_state(pdev, snapshot);
> }
>
> +static int cxl_reset_restore_cache_policy(struct pci_dev *pdev, int dvsec,
> + u16 saved_ctrl2);
> +static int cxl_reset_cache_prepare(struct pci_dev *pdev, int dvsec, u16 cap,
> + const struct cxl_hdm_info *snapshot,
> + struct cxl_pci_cache *cache);
[ ... ]
> +struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev,
> + struct cxl_pci_cache **cachep)
> {
> struct cxl_hdm_info *snapshot;
> + struct cxl_pci_cache *cache;
> + u16 dvsec, cap, command;
> + int rc;
>
> device_lock_assert(&pdev->dev);
> - snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
> - if (IS_ERR(snapshot))
> - return PTR_ERR(snapshot) == -ENXIO ? NULL : snapshot;
> + *cachep = NULL;
> + if (!pdev->hdm)
> + return NULL;
>
> + rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
> + if (rc == -ENOTTY)
> + return ERR_PTR(-ENODEV);
> + if (rc)
> + return ERR_PTR(rc);
> /* Raw reset callers may not have saved the PCI state needed for restore. */
> if (!pdev->state_saved) {
> pci_err(pdev, "CXL bus reset requires saved PCI state\n");
> - kfree(snapshot);
> return ERR_PTR(-EINVAL);
> }
>
> + cache = kzalloc_obj(*cache);
> + if (!cache)
> + return ERR_PTR(-ENOMEM);
> + cxl_region_quiesce_lock();
> + snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
> + if (IS_ERR(snapshot)) {
> + rc = PTR_ERR(snapshot);
> + goto unlock;
> + }
> + if (!snapshot->dvsec_ctrl_valid) {
> + rc = -ENXIO;
> + goto free_snapshot;
> + }
> + if (cxl_hdm_cache_dvsec_decode(&pdev->hdm) ||
> + (snapshot->dvsec_ctrl & PCI_DVSEC_CXL_CACHE_ENABLE &&
> + !(cap & PCI_DVSEC_CXL_CACHE_WBI_CAPABLE))) {
[Severity: Medium]
In drivers/pci/cxl.c, pci_cxl_reset_prepare() calls
cxl_hdm_cache_snapshot(&pdev->hdm) under a cxl_rwsem.dpa read lock to copy
dvsec_ctrl and global_ctrl into snapshot, and then calls
cxl_hdm_cache_dvsec_decode(&pdev->hdm) in a separate cxl_rwsem.dpa read
section on the live pdev->hdm pointer rather than checking snapshot.
Meanwhile, endpoint port probe and teardown on &port->dev run under
device_lock(&port->dev) without holding device_lock(&pdev->dev) or
cxl_rwsem.region:
clear_mem_enable()
-> cxl_set_mem_enable()
-> cxl_hdm_cache_set_dvsec_ctrl()
and
devm_cxl_enumerate_decoders()
-> cxl_hdm_refresh_decoder()
-> cxl_hdm_cache_refresh_decoder()
both mutate pdev->hdm->dvsec_ctrl and pdev->hdm->global_ctrl under the
cxl_rwsem.dpa write lock.
If clear_mem_enable() clears PCI_DVSEC_CXL_MEM_ENABLE in
pdev->hdm->dvsec_ctrl (or cxl_hdm_cache_refresh_decoder() sets
CXL_HDM_DECODER_ENABLE in pdev->hdm->global_ctrl) between
cxl_hdm_cache_snapshot() and cxl_hdm_cache_dvsec_decode(), wouldn't
cxl_hdm_cache_dvsec_decode(&pdev->hdm) return false even though snapshot
captured PCI_DVSEC_CXL_MEM_ENABLE set with CXL_HDM_DECODER_ENABLE clear,
allowing the reset to proceed and later write stale snapshot->global_ctrl
and snapshot->dvsec_ctrl back to hardware in cxl_restore_state()?
Should this check evaluate snapshot->dvsec_ctrl and snapshot->global_ctrl
directly instead of re-reading pdev->hdm?
> + rc = -EOPNOTSUPP;
> + goto free_snapshot;
> + }
[ ... ]
> int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
> - struct cxl_hdm_info *snapshot,
> - u16 *command)
> + const struct cxl_hdm_info *snapshot,
> + struct cxl_pci_cache *cache,
> + bool reset_attempted, int rc, u16 *command)
> {
> - int rc;
> + int rc2, policy_rc = 0;
>
> + *command = 0;
> + if (!cache)
> + return rc;
> device_lock_assert(&pdev->dev);
> - cxl_region_quiesce_lock();
> -
> - rc = cxl_hdm_cache_copy_decoders(&pdev->hdm, snapshot);
[Severity: Low]
Removing this call in cxl_restore_state_after_pci_reset() in
drivers/pci/cxl.c removes the only caller of cxl_hdm_cache_copy_decoders()
in the tree. Should the definition of cxl_hdm_cache_copy_decoders() in
drivers/cxl/core/hdm_state.c and its declaration in include/cxl/hdm.h also
be removed so dead code is not left behind?
> - if (!rc)
> + if (reset_attempted && !rc)
> rc = pci_cxl_restore_state(pdev, snapshot, command);
> - if (rc)
> - cxl_reset_save_disabled_state(pdev);
>
> + /* Complete host invalidation before ending IOMMU exclusion. */
> + rc2 = cxl_hdm_quiesce_flush(&cache->quiesce);
> + if (rc2)
> + pci_err(pdev, "failed to invalidate CPU cache after CXL bus reset: %d\n", rc2);
> + /* Undo preparation on abort; enable caching after reset only if safe. */
> + if (cache->cache_disabled && (!reset_attempted || (!rc && !rc2)))
> + policy_rc = cxl_reset_restore_cache_policy(pdev, snapshot->dvsec,
> + cache->saved_ctrl2);
[Severity: High]
During a Secondary Bus Reset (Hot Reset), the non-sticky Disable_Caching
bit (PCI_DVSEC_CXL_DISABLE_CACHING) in PCI_DVSEC_CXL_CTRL2 is reset to 0
by hardware along with PCI_DVSEC_CXL_CTRL.
In cxl_restore_state_after_pci_reset() in drivers/pci/cxl.c, when
reset_attempted is true and rc is 0, pci_cxl_restore_state() calls
cxl_restore_state() -> cxl_restore_dvsec_ctrl(), which writes
snapshot->dvsec_ctrl (including PCI_DVSEC_CXL_CACHE_ENABLE and
PCI_DVSEC_CXL_MEM_ENABLE) to PCI_DVSEC_CXL_CTRL before
cxl_hdm_quiesce_flush() and cxl_reset_restore_cache_policy() run.
Because PCI_DVSEC_CXL_DISABLE_CACHING was cleared to 0 by the bus reset,
doesn't writing snapshot->dvsec_ctrl inside pci_cxl_restore_state()
immediately enable device caching before cxl_hdm_quiesce_flush() has
invalidated the host CPU caches, transiently enable caching even when
cache->saved_ctrl2 originally had PCI_DVSEC_CXL_DISABLE_CACHING set, and
leave CXL.cache and CXL.mem enabled in hardware if cxl_hdm_quiesce_flush()
fails (since cxl_reset_save_disabled_state() only clears MSE and BME in
PCI_COMMAND)?
> + if (reset_attempted) {
> + rc = rc ?: rc2 ?: policy_rc;
> + if (rc)
> + cxl_reset_save_disabled_state(pdev);
> + } else {
> + /* An aborted reset alone must not poison the saved PCI command. */
> + if (rc2 || policy_rc)
> + cxl_reset_save_disabled_state(pdev);
> + rc = rc ?: rc2 ?: policy_rc;
> + }
> +
> + cxl_hdm_quiesce_destroy(&cache->quiesce);
> cxl_region_quiesce_unlock();
> + kfree(cache);
> return rc;
> }
[Severity: Low]
In include/cxl/hdm.h, cxl_region_quiesce is declared with
token_context_lock(cxl_region_quiesce), and cxl_region_quiesce_lock(),
cxl_region_quiesce_unlock(), cxl_hdm_quiesce_prepare(),
cxl_hdm_quiesce_flush(), pci_cxl_restore_state(), and the definition of
cxl_reset_cache_prepare() carry __acquires, __releases, or __must_hold
annotations for cxl_region_quiesce.
In drivers/pci/cxl.c:
1. The new forward declaration of cxl_reset_cache_prepare() omits the
__must_hold(cxl_region_quiesce) annotation present on its definition.
2. pci_cxl_reset_prepare() calls cxl_region_quiesce_lock() and returns with
cxl_region_quiesce held on its non-NULL success path (or releases it via
cxl_restore_state_after_pci_reset() on the finish path) without context
annotations.
3. cxl_restore_state_after_pci_reset() calls the __must_hold functions
pci_cxl_restore_state() and cxl_hdm_quiesce_flush() as well as
cxl_region_quiesce_unlock() when cache is non-NULL without context
annotations.
When building with Clang static context analysis enabled
(CONFIG_WARN_CONTEXT_ANALYSIS_ALL=y), won't these missing annotations
trigger -Wthread-safety warnings in drivers/pci/cxl.c?
[ ... ]
> @@ -731,7 +831,9 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
> rc = __cxl_reset_execute(pdev, dvsec, cap, snapshot, &cache.quiesce,
> &command);
> /* Restore cache policy after any attempt to disable caching. */
> - rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
> + rc2 = 0;
> + if (cache.cache_disabled)
> + rc2 = cxl_reset_restore_cache_policy(pdev, dvsec, cache.saved_ctrl2);
> cxl_hdm_quiesce_destroy(&cache.quiesce);
> if (rc)
> return rc;
[Severity: Medium]
In cxl_restore_state_after_pci_reset() in drivers/pci/cxl.c,
cxl_reset_restore_cache_policy() is guarded with
(!reset_attempted || (!rc && !rc2)) so caching is not re-enabled when an
attempted reset, HDM restoration, or post-reset cache flush fails, and on
an aborted reset cxl_reset_save_disabled_state() is called if
cxl_reset_restore_cache_policy() fails.
In cxl_reset_quiesced() in drivers/pci/cxl.c, however:
1. If __cxl_reset_execute() fails during CXL Reset, pci_cxl_restore_state(),
or cxl_hdm_quiesce_flush(), cxl_reset_quiesced() still unconditionally
calls cxl_reset_restore_cache_policy() whenever cache.cache_disabled is
true, re-enabling caching in PCI_DVSEC_CXL_CTRL2 against unrestored HDM
state after pci_dev_reset_iommu_done() has already ended IOMMU exclusion.
2. Because if (rc) return rc; runs before checking rc2:
if (rc)
return rc;
if (rc2) {
cxl_reset_save_disabled_state(pdev);
return rc2;
}
if cxl_reset_cache_prepare() fails after setting cache.cache_disabled =
true (or __cxl_reset_execute() fails in
pci_wait_for_pending_transaction() or pci_dev_reset_iommu_prepare()
before reaching cxl_reset_save_disabled_state()) and
cxl_reset_restore_cache_policy() also fails (rc2 != 0),
cxl_reset_quiesced() returns rc without calling
cxl_reset_save_disabled_state(pdev).
Should cxl_reset_quiesced() follow the same error-handling rules as
cxl_restore_state_after_pci_reset() for restoring the cache policy and
saving the disabled state?
> diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
> index 1d4ba90a69762..b29a6a43249dd 100644
> --- a/drivers/pci/pci.c
> +++ b/drivers/pci/pci.c
[ ... ]
> @@ -5066,8 +5068,9 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
> pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
> reg);
>
> - if (!rc && snapshot)
> - rc = cxl_restore_state_after_pci_reset(dev, snapshot, &command);
> + /* Reset-helper failure does not prove that no hardware reset occurred. */
> + rc = cxl_restore_state_after_pci_reset(dev, snapshot, cache, true, rc,
> + &command);
[Severity: Medium]
In cxl_reset_bus_function() in drivers/pci/pci.c, when probe is true, the
function only checks for VFs/multifunction, pci_upstream_bridge(dev), and
cxl_port_dvsec(bridge) without probing pci_dev_reset_slot_function() or
pci_parent_bus_reset().
When cxl_reset_bus_function(dev, false) runs on a CXL device with cached
HDM state, pci_cxl_reset_prepare() disables device caching in
PCI_DVSEC_CXL_CTRL2, and then pci_reset_bus_function(dev, false) is called.
If pci_reset_bus_function(dev, false) returns -ENOTTY from its prerequisite
checks without touching hardware (for example, when cxl_sbr_masked(bridge)
remains true because PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR is read-only, or
when both pci_dev_reset_slot_function() and pci_parent_bus_reset() return
-ENOTTY because PCI_DEV_FLAGS_NO_BUS_RESET is set, dev->subordinate is
non-NULL, or another device shares dev->bus->devices),
cxl_reset_bus_function() still passes reset_attempted = true and
rc = -ENOTTY to cxl_restore_state_after_pci_reset().
When reset_attempted is true and rc is non-zero,
cxl_restore_state_after_pci_reset() skips cxl_reset_restore_cache_policy()
and calls cxl_reset_save_disabled_state(dev), which leaves
PCI_DVSEC_CXL_DISABLE_CACHING set and clears MSE and BME in
dev->saved_config_space.
Should cxl_reset_bus_function() verify that slot or parent bus reset is
supported before preparing the reset (or treat -ENOTTY from
pci_reset_bus_function() as an unattempted reset) so an unsupported bus
reset does not disable an untouched device?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105185917.2667656-1-smadhavan@nvidia.com?part=18
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH v17 19/19] cxl: Add KUnit checks for HDM decoder unpacking
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (17 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 18/19] PCI/CXL: Flush host and device caches for " Srirangan Madhavan
@ 2026-11-05 18:59 ` Srirangan Madhavan
2026-11-05 19:38 ` sashiko-bot
2026-11-05 19:31 ` [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Alison Schofield
19 siblings, 1 reply; 40+ messages in thread
From: Srirangan Madhavan @ 2026-11-05 18:59 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Li Ming, Joshua Machauer, Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Test cxl_hdm_unpack_decoder() without a PCI device or HDM MMIO. Cover
range boundaries and overflow, an all-ones base, zero-size and uncommitted
decoders, and a locked host-only decoder with 12-way interleave.
Run the checks as a KUnit suite rather than during cxl_test module loading,
so a failure does not prevent unrelated CXL mock tests from running. An
uncommitted decoder has no meaningful target type, so do not check it.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/.kunitconfig | 4 ++
drivers/cxl/Kconfig | 10 +++
drivers/cxl/Makefile | 1 +
drivers/cxl/core/hdm_regs.c | 2 +-
drivers/cxl/tests/Makefile | 5 ++
drivers/cxl/tests/hdm_unpack_kunit.c | 91 ++++++++++++++++++++++++++++
6 files changed, 112 insertions(+), 1 deletion(-)
create mode 100644 drivers/cxl/.kunitconfig
create mode 100644 drivers/cxl/tests/Makefile
create mode 100644 drivers/cxl/tests/hdm_unpack_kunit.c
diff --git a/drivers/cxl/.kunitconfig b/drivers/cxl/.kunitconfig
new file mode 100644
index 000000000000..4fee684f5ff6
--- /dev/null
+++ b/drivers/cxl/.kunitconfig
@@ -0,0 +1,4 @@
+CONFIG_KUNIT=y
+CONFIG_PCI=y
+CONFIG_CXL_BUS=y
+CONFIG_CXL_KUNIT_TEST=y
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index e927e69a3f7b..7f70f6babd74 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -213,6 +213,16 @@ config CXL_REGION
If unsure say 'y'
+config CXL_KUNIT_TEST
+ tristate "KUnit tests for CXL" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Build the KUnit tests for the CXL subsystem. When built as modules,
+ each test suite is its own module.
+
+ If unsure, say N.
+
config CXL_REGION_INVALIDATION_TEST
bool "CXL: Region Cache Management Bypass (TEST)"
depends on CXL_REGION
diff --git a/drivers/cxl/Makefile b/drivers/cxl/Makefile
index 2caa90fa4bf2..d57fb0e9e7da 100644
--- a/drivers/cxl/Makefile
+++ b/drivers/cxl/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_CXL_ACPI) += cxl_acpi.o
obj-$(CONFIG_CXL_PMEM) += cxl_pmem.o
obj-$(CONFIG_CXL_MEM) += cxl_mem.o
obj-$(CONFIG_CXL_PCI) += cxl_pci.o
+obj-$(CONFIG_CXL_KUNIT_TEST) += tests/
cxl_port-y := port.o
cxl_acpi-y := acpi.o
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index 4f5e843cd141..98b37edf5590 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -188,7 +188,7 @@ int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,
return 0;
}
-EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core,cxl_hdm_unpack_kunit");
int cxl_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
int id, u32 ctrl)
diff --git a/drivers/cxl/tests/Makefile b/drivers/cxl/tests/Makefile
new file mode 100644
index 000000000000..c29dccfa9b24
--- /dev/null
+++ b/drivers/cxl/tests/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+ccflags-y += -I$(srctree)/drivers/cxl
+
+obj-$(CONFIG_CXL_KUNIT_TEST) += cxl_hdm_unpack_kunit.o
+cxl_hdm_unpack_kunit-y := hdm_unpack_kunit.o
diff --git a/drivers/cxl/tests/hdm_unpack_kunit.c b/drivers/cxl/tests/hdm_unpack_kunit.c
new file mode 100644
index 000000000000..c1131e145757
--- /dev/null
+++ b/drivers/cxl/tests/hdm_unpack_kunit.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <kunit/test.h>
+#include <linux/bitfield.h>
+#include <linux/sizes.h>
+
+#include "cxl.h"
+#include "../core/core.h"
+
+static void cxl_hdm_unpack_test(struct kunit *test)
+{
+ static const struct {
+ const char *name;
+ u32 ctrl;
+ u64 base;
+ u64 size;
+ int rc;
+ unsigned long flags;
+ int ways;
+ int granularity;
+ enum cxl_decoder_type target_type;
+ } cases[] = {
+ { "valid", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ SZ_1G, SZ_256M, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "overflow", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX - 127, 256, -ENXIO },
+ { "all-ones base", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX, SZ_256M, -ENXIO },
+ { "last address", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX - 255, 256, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "zero size", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ SZ_1G, 0, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "uncommitted stale size", 0,
+ SZ_1G, SZ_256M, 0, 0, 1, 256 },
+ { "locked host-only interleave",
+ CXL_HDM_DECODER0_CTRL_COMMITTED | CXL_HDM_DECODER0_CTRL_LOCK |
+ CXL_HDM_DECODER0_CTRL_HOSTONLY |
+ FIELD_PREP_CONST(CXL_HDM_DECODER0_CTRL_IW_MASK, 10) |
+ FIELD_PREP_CONST(CXL_HDM_DECODER0_CTRL_IG_MASK, 2),
+ SZ_1G, SZ_256M, 0, CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK,
+ 12, SZ_1K, CXL_DECODER_HOSTONLYMEM },
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(cases); i++) {
+ struct cxl_decoder_config config;
+ u64 size;
+ int rc;
+
+ rc = cxl_hdm_unpack_decoder(&config, 3, cases[i].ctrl,
+ cases[i].base, cases[i].size);
+ KUNIT_EXPECT_EQ_MSG(test, rc, cases[i].rc, "%s", cases[i].name);
+ if (rc || rc != cases[i].rc)
+ continue;
+ size = cases[i].size;
+ if (!(cases[i].flags & CXL_DECODER_F_ENABLE))
+ size = 0;
+ KUNIT_EXPECT_EQ_MSG(test, config.id, 3, "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.hpa_range.start, cases[i].base,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, range_len(&config.hpa_range), size,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.flags, cases[i].flags,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.interleave_ways, cases[i].ways,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.interleave_granularity,
+ cases[i].granularity, "%s", cases[i].name);
+ /* An uncommitted decoder has no meaningful target type. */
+ if (cases[i].flags & CXL_DECODER_F_ENABLE)
+ KUNIT_EXPECT_EQ_MSG(test, config.target_type,
+ cases[i].target_type, "%s", cases[i].name);
+ }
+}
+
+static struct kunit_case cxl_hdm_test_cases[] = {
+ KUNIT_CASE(cxl_hdm_unpack_test),
+ {}
+};
+
+static struct kunit_suite cxl_hdm_test_suite = {
+ .name = "cxl-hdm-unpack",
+ .test_cases = cxl_hdm_test_cases,
+};
+
+kunit_test_suite(cxl_hdm_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for CXL HDM decoder unpacking");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 40+ messages in thread* Re: [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices
2026-11-05 18:58 [PATCH v17 00/19] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (18 preceding siblings ...)
2026-11-05 18:59 ` [PATCH v17 19/19] cxl: Add KUnit checks for HDM decoder unpacking Srirangan Madhavan
@ 2026-11-05 19:31 ` Alison Schofield
19 siblings, 0 replies; 40+ messages in thread
From: Alison Schofield @ 2026-11-05 19:31 UTC (permalink / raw)
To: Srirangan Madhavan
Cc: Bjorn Helgaas, Dave Jiang, Davidlohr Bueso, Ira Weiny,
Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel, Li Ming, Joshua Machauer, Alex Williamson, vsethi,
alwilliamson, Sai Yashwanth Reddy Kancherla, Vishal Aslot,
Manish Honap, Jiandi An, Richard Cheng, linux-tegra
On Thu, Nov 05, 2026 at 06:58:58PM +0000, Srirangan Madhavan wrote:
> Hi folks,
snip
>
> The KUnit suite checks HDM range boundaries, zero-size and uncommitted
> state, lock and target-type decoding, and 12-way interleave.
There is a KUnit branch available now to base on: for-7.5/cxl-kunit
https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git/
-- Alison
^ permalink raw reply [flat|nested] 40+ messages in thread