* [PATCH v13 01/15] cxl: Drop stale decoder interleave limit comment
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 02/15] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
` (13 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
CXL 2.0 added 16-way memory interleaving, making the comment that the
specification supports only 8-way interleaving stale. Remove the comment
without changing CXL_DECODER_MAX_INTERLEAVE.
Suggested-by: Jonathan Cameron <jic23@kernel.org>
Link: https://lore.kernel.org/linux-cxl/20260912003057.3db9335c@jic23-hlaptop/
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/cxl.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 3ef0810ab86b..9e0500d56866 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -269,10 +269,6 @@ enum cxl_decoder_type {
CXL_DECODER_HOSTONLYMEM = 3,
};
-/*
- * Current specification goes up to 8, double that seems a reasonable
- * software max for the foreseeable future
- */
#define CXL_DECODER_MAX_INTERLEAVE 16
#define CXL_QOS_CLASS_INVALID -1
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 02/15] cxl: Share CXL port upstream PCI device lookup
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 01/15] cxl: Drop stale decoder interleave limit comment Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 03/15] cxl: Move HDM decoder programming helpers Srirangan Madhavan
` (12 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
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.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/core.h | 1 +
drivers/cxl/core/pci.c | 23 ++++++-----------------
drivers/cxl/core/port.c | 26 ++++++++++++++++++++++++++
3 files changed, 33 insertions(+), 17 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..bf7fc77626d9 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;
}
/*
@@ -625,11 +612,13 @@ void read_cdat_data(struct cxl_port *port)
port->cdat.table = buf->data;
port->cdat.length = length;
- return;
+ goto out;
err:
/* Don't leave table data allocated on error */
devm_kfree(dev, buf);
dev_err(dev, "Failed to read/validate CDAT.\n");
+out:
+ pci_dev_put(pdev);
}
EXPORT_SYMBOL_NS_GPL(read_cdat_data, "CXL");
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 625e4aa427db..5dc2815d82d8 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -34,6 +34,32 @@
static DEFINE_IDA(cxl_port_ida);
static DEFINE_XARRAY(cxl_root_buses);
+/**
+ * cxl_port_get_uport_pci_dev - get the PCI device for a port's upstream device
+ * @port: CXL port to map to a PCI device
+ *
+ * Return: A referenced PCI device, or NULL if the upstream device is not PCI.
+ * The caller must release the returned reference with pci_dev_put().
+ */
+struct pci_dev *cxl_port_get_uport_pci_dev(struct cxl_port *port)
+{
+ struct device *uport = port->uport_dev;
+ struct device *host;
+
+ if (is_cxl_memdev(uport)) {
+ struct cxl_memdev *cxlmd = to_cxl_memdev(uport);
+
+ host = cxlmd->dev.parent;
+ } else {
+ host = uport;
+ }
+
+ if (!host || !dev_is_pci(host))
+ return NULL;
+
+ return pci_dev_get(to_pci_dev(host));
+}
+
/*
* The terminal device in PCI is NULL and @platform_bus
* for platform devices (for cxl_test)
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 03/15] cxl: Move HDM decoder programming helpers
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 01/15] cxl: Drop stale decoder interleave limit comment Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 02/15] cxl: Share CXL port upstream PCI device lookup Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 04/15] cxl: Move decoder declarations to shared header Srirangan Madhavan
` (11 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Move the HDM decoder programming helpers and cxl_rwsem from hdm.c to
resource.c for reuse by reset restoration. Keep their behavior unchanged
and expose only the cross-object helpers within cxl_core.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/Makefile | 1 +
drivers/cxl/core/core.h | 4 ++
drivers/cxl/core/hdm.c | 103 +--------------------------------
drivers/cxl/core/resource.c | 110 ++++++++++++++++++++++++++++++++++++
tools/testing/cxl/Kbuild | 1 +
5 files changed, 117 insertions(+), 102 deletions(-)
create mode 100644 drivers/cxl/core/resource.c
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index ce7213818d3c..57995777af0d 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -6,6 +6,7 @@ ccflags-y += -I$(srctree)/drivers/cxl
CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
+cxl_core-y += resource.o
cxl_core-y += pmem.o
cxl_core-y += regs.o
cxl_core-y += memdev.o
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 983d7690c3a5..9c6d4b99f787 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -139,6 +139,10 @@ struct cxl_rwsem {
extern struct cxl_rwsem cxl_rwsem;
+int cxld_await_commit(void __iomem *hdm, int id);
+void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt);
+void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm);
+
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 39fe283cbc74..008f04f0ca25 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -16,11 +16,6 @@
* for enumerating these registers and capabilities.
*/
-struct cxl_rwsem cxl_rwsem = {
- .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
- .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
-};
-
static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld)
{
int rc;
@@ -707,36 +702,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)
-{
- u16 eig;
- u8 eiw;
-
- /*
- * Input validation ensures these warns never fire, but otherwise
- * suppress unititalized variable usage warnings.
- */
- if (WARN_ONCE(ways_to_eiw(cxld->interleave_ways, &eiw),
- "invalid interleave_ways: %d\n", cxld->interleave_ways))
- return;
- if (WARN_ONCE(granularity_to_eig(cxld->interleave_granularity, &eig),
- "invalid interleave_granularity: %d\n",
- cxld->interleave_granularity))
- return;
-
- u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
- u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
- *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
-}
-
-static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
-{
- u32p_replace_bits(ctrl,
- !!(cxld->target_type == CXL_DECODER_HOSTONLYMEM),
- CXL_HDM_DECODER0_CTRL_HOSTONLY);
-}
-
-static void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
+void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
{
struct cxl_dport **t = &cxlsd->target[0];
int ways = cxlsd->cxld.interleave_ways;
@@ -758,73 +724,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
-static int cxld_await_commit(void __iomem *hdm, int id)
-{
- u32 ctrl;
- int i;
-
- for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
- ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
- return -EIO;
- }
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
- return 0;
- fsleep(1000);
- }
-
- return -ETIMEDOUT;
-}
-
-static void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
-{
- int id = cxld->id;
- u64 base, size;
- u32 ctrl;
-
- /* common decoder settings */
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
- cxld_set_interleave(cxld, &ctrl);
- cxld_set_type(cxld, &ctrl);
- base = cxld->hpa_range.start;
- size = range_len(&cxld->hpa_range);
-
- writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
- writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
- writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
- writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
-
- if (is_switch_decoder(&cxld->dev)) {
- struct cxl_switch_decoder *cxlsd =
- to_cxl_switch_decoder(&cxld->dev);
- void __iomem *tl_hi = hdm + CXL_HDM_DECODER0_TL_HIGH(id);
- void __iomem *tl_lo = hdm + CXL_HDM_DECODER0_TL_LOW(id);
- u64 targets;
-
- cxlsd_set_targets(cxlsd, &targets);
- writel(upper_32_bits(targets), tl_hi);
- writel(lower_32_bits(targets), tl_lo);
- } else {
- struct cxl_endpoint_decoder *cxled =
- to_cxl_endpoint_decoder(&cxld->dev);
- void __iomem *sk_hi = hdm + CXL_HDM_DECODER0_SKIP_HIGH(id);
- void __iomem *sk_lo = hdm + CXL_HDM_DECODER0_SKIP_LOW(id);
-
- writel(upper_32_bits(cxled->skip), sk_hi);
- writel(lower_32_bits(cxled->skip), sk_lo);
- }
-
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
-}
-
static int cxl_decoder_commit(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
new file mode 100644
index 000000000000..68d40f745acc
--- /dev/null
+++ b/drivers/cxl/core/resource.c
@@ -0,0 +1,110 @@
+// 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/kernel.h>
+
+#include "cxl.h"
+#include "core.h"
+
+struct cxl_rwsem cxl_rwsem = {
+ .region = __RWSEM_INITIALIZER(cxl_rwsem.region),
+ .dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
+};
+
+static void cxld_set_interleave(struct cxl_decoder *cxld, 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(cxld->interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n", cxld->interleave_ways))
+ return;
+ if (WARN_ONCE(granularity_to_eig(cxld->interleave_granularity, &eig),
+ "invalid interleave_granularity: %d\n",
+ cxld->interleave_granularity))
+ return;
+
+ u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
+ u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK);
+ *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT;
+}
+
+static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
+{
+ u32p_replace_bits(ctrl,
+ !!(cxld->target_type == CXL_DECODER_HOSTONLYMEM),
+ CXL_HDM_DECODER0_CTRL_HOSTONLY);
+}
+
+/*
+ * 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;
+}
+
+void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
+{
+ int id = cxld->id;
+ u64 base, size;
+ u32 ctrl;
+
+ /* common decoder settings */
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
+ cxld_set_interleave(cxld, &ctrl);
+ cxld_set_type(cxld, &ctrl);
+ base = cxld->hpa_range.start;
+ size = range_len(&cxld->hpa_range);
+
+ writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
+ writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
+ writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+
+ if (is_switch_decoder(&cxld->dev)) {
+ struct cxl_switch_decoder *cxlsd =
+ to_cxl_switch_decoder(&cxld->dev);
+ void __iomem *tl_hi = hdm + CXL_HDM_DECODER0_TL_HIGH(id);
+ void __iomem *tl_lo = hdm + CXL_HDM_DECODER0_TL_LOW(id);
+ u64 targets;
+
+ cxlsd_set_targets(cxlsd, &targets);
+ writel(upper_32_bits(targets), tl_hi);
+ writel(lower_32_bits(targets), tl_lo);
+ } else {
+ struct cxl_endpoint_decoder *cxled =
+ to_cxl_endpoint_decoder(&cxld->dev);
+ void __iomem *sk_hi = hdm + CXL_HDM_DECODER0_SKIP_HIGH(id);
+ void __iomem *sk_lo = hdm + CXL_HDM_DECODER0_SKIP_LOW(id);
+
+ writel(upper_32_bits(cxled->skip), sk_hi);
+ writel(lower_32_bits(cxled->skip), sk_lo);
+ }
+
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild
index 2be1df80fcc9..6c09932587ce 100644
--- a/tools/testing/cxl/Kbuild
+++ b/tools/testing/cxl/Kbuild
@@ -60,6 +60,7 @@ cxl_core-y += $(CXL_CORE_SRC)/memdev.o
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
cxl_core-y += $(CXL_CORE_SRC)/pci.o
cxl_core-y += $(CXL_CORE_SRC)/hdm.o
+cxl_core-y += $(CXL_CORE_SRC)/resource.o
cxl_core-y += $(CXL_CORE_SRC)/pmu.o
cxl_core-y += $(CXL_CORE_SRC)/cdat.o
cxl_core-$(CONFIG_TRACING) += $(CXL_CORE_SRC)/trace.o
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 04/15] cxl: Move decoder declarations to shared header
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (2 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 03/15] cxl: Move HDM decoder programming helpers Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 05/15] cxl: Introduce reusable HDM decoder settings Srirangan Madhavan
` (10 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
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>
---
drivers/cxl/cxl.h | 37 -------------------------------------
include/cxl/cxl.h | 41 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 40 insertions(+), 38 deletions(-)
diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
index 9e0500d56866..44ff3334648c 100644
--- a/drivers/cxl/cxl.h
+++ b/drivers/cxl/cxl.h
@@ -264,45 +264,8 @@ int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
#define CXL_DECODER_F_NORMALIZED_ADDRESSING BIT(6)
#define CXL_DECODER_F_RESET_MASK (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK)
-enum cxl_decoder_type {
- CXL_DECODER_DEVMEM = 2,
- CXL_DECODER_HOSTONLYMEM = 3,
-};
-
-#define CXL_DECODER_MAX_INTERLEAVE 16
-
#define CXL_QOS_CLASS_INVALID -1
-/**
- * struct cxl_decoder - Common CXL HDM Decoder Attributes
- * @dev: this decoder's device
- * @id: kernel device name id
- * @hpa_range: Host physical address range mapped by this decoder
- * @interleave_ways: number of cxl_dports in this decode
- * @interleave_granularity: data stride per dport
- * @target_type: accelerator vs expander (type2 vs type3) selector
- * @region: currently assigned region for this decoder
- * @flags: memory type capabilities and locking
- * @target_map: cached copy of hardware port-id list, available at init
- * before all @dport objects have been instantiated. While
- * dport id is 8bit, CFMWS interleave targets are 32bits.
- * @commit: device/decoder-type specific callback to commit settings to hw
- * @reset: device/decoder-type specific callback to reset hw settings
-*/
-struct cxl_decoder {
- struct device dev;
- int id;
- struct range hpa_range;
- int interleave_ways;
- int interleave_granularity;
- enum cxl_decoder_type target_type;
- struct cxl_region *region;
- unsigned long flags;
- u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
- int (*commit)(struct cxl_decoder *cxld);
- void (*reset)(struct cxl_decoder *cxld);
-};
-
/*
* Track whether this decoder is free for userspace provisioning, reserved for
* region autodiscovery, whether it is started connecting (awaiting other
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 802b143de83d..69941f7fdb76 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -5,8 +5,10 @@
#ifndef __CXL_CXL_H__
#define __CXL_CXL_H__
+#include <linux/device.h>
#include <linux/node.h>
#include <linux/ioport.h>
+#include <linux/range.h>
#include <cxl/mailbox.h>
/**
@@ -23,7 +25,44 @@ enum cxl_devtype {
CXL_DEVTYPE_CLASSMEM,
};
-struct device;
+struct cxl_region;
+
+enum cxl_decoder_type {
+ CXL_DECODER_DEVMEM = 2,
+ CXL_DECODER_HOSTONLYMEM = 3,
+};
+
+#define CXL_DECODER_MAX_INTERLEAVE 16
+
+/**
+ * struct cxl_decoder - Common CXL HDM Decoder Attributes
+ * @dev: this decoder's device
+ * @id: kernel device name id
+ * @hpa_range: Host physical address range mapped by this decoder
+ * @interleave_ways: number of cxl_dports in this decode
+ * @interleave_granularity: data stride per dport
+ * @target_type: accelerator vs expander (type2 vs type3) selector
+ * @region: currently assigned region for this decoder
+ * @flags: memory type capabilities and locking
+ * @target_map: cached copy of hardware port-id list, available at init
+ * before all @dport objects have been instantiated. While
+ * dport id is 8bit, CFMWS interleave targets are 32bits.
+ * @commit: device/decoder-type specific callback to commit settings to hw
+ * @reset: device/decoder-type specific callback to reset hw settings
+*/
+struct cxl_decoder {
+ struct device dev;
+ int id;
+ struct range hpa_range;
+ int interleave_ways;
+ int interleave_granularity;
+ enum cxl_decoder_type target_type;
+ struct cxl_region *region;
+ unsigned long flags;
+ u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
+ int (*commit)(struct cxl_decoder *cxld);
+ void (*reset)(struct cxl_decoder *cxld);
+};
/*
* Using struct_group() allows for per register-block-type helper routines,
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 05/15] cxl: Introduce reusable HDM decoder settings
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (3 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 04/15] cxl: Move decoder declarations to shared header Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 06/15] cxl: Make HDM reset helpers available to built-in PCI code Srirangan Madhavan
` (9 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Represent HDM programming state with struct cxl_decoder_settings and pass
it to the commit helpers. Keep endpoint skip and switch targets at their
call sites, place flags with the programming state, and leave runtime
region ownership outside the snapshot.
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>
---
drivers/cxl/core/core.h | 5 ++-
drivers/cxl/core/hdm.c | 40 +++++++++++++++++++---
drivers/cxl/core/resource.c | 67 ++++++++++++++++++-------------------
include/cxl/cxl.h | 27 +++++++++++----
4 files changed, 90 insertions(+), 49 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 9c6d4b99f787..3444479cbade 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -139,9 +139,8 @@ struct cxl_rwsem {
extern struct cxl_rwsem cxl_rwsem;
-int cxld_await_commit(void __iomem *hdm, int id);
-void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt);
-void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm);
+void cxl_commit_start(void __iomem *hdm, struct cxl_decoder_settings *settings);
+int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings);
int cxl_memdev_init(void);
void cxl_memdev_exit(void);
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 008f04f0ca25..b3ddc02df65e 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -702,7 +702,7 @@ int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size)
return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled);
}
-void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
+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;
@@ -724,11 +724,41 @@ void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt)
*tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id);
}
+/*
+ * Endpoint skip and switch targets have different owners. Keep that state with
+ * its owning type and flatten it only into a transient register-programming
+ * snapshot.
+ */
+static void cxl_decoder_snapshot(struct cxl_decoder *cxld,
+ struct cxl_decoder_settings *settings)
+{
+ lockdep_assert_held(&cxl_rwsem.dpa);
+
+ *settings = (struct cxl_decoder_settings) {
+ .config = cxld->config,
+ };
+
+ if (is_switch_decoder(&cxld->dev)) {
+ struct cxl_switch_decoder *cxlsd =
+ to_cxl_switch_decoder(&cxld->dev);
+ u64 targets;
+
+ cxlsd_set_targets(cxlsd, &targets);
+ settings->target_or_skip_reg_val = targets;
+ } else {
+ struct cxl_endpoint_decoder *cxled =
+ to_cxl_endpoint_decoder(&cxld->dev);
+
+ settings->target_or_skip_reg_val = cxled->skip;
+ }
+}
+
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;
+ struct cxl_decoder_settings settings;
int id = cxld->id, rc;
if (cxld->flags & CXL_DECODER_F_ENABLE)
@@ -761,10 +791,12 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
}
}
- scoped_guard(rwsem_read, &cxl_rwsem.dpa)
- setup_hw_decoder(cxld, hdm);
+ scoped_guard(rwsem_read, &cxl_rwsem.dpa) {
+ cxl_decoder_snapshot(cxld, &settings);
+ cxl_commit_start(hdm, &settings);
+ }
- rc = cxld_await_commit(hdm, cxld->id);
+ rc = cxl_commit_wait(hdm, &settings);
if (rc) {
dev_dbg(&port->dev, "%s: error %d committing decoder\n",
dev_name(&cxld->dev), rc);
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 68d40f745acc..ea0d855e5549 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -13,7 +13,7 @@ struct cxl_rwsem cxl_rwsem = {
.dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
};
-static void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl)
+static void cxld_set_interleave(struct cxl_decoder_config *config, u32 *ctrl)
{
u16 eig;
u8 eiw;
@@ -22,12 +22,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(config->interleave_ways, &eiw),
+ "invalid interleave_ways: %d\n", config->interleave_ways))
return;
- if (WARN_ONCE(granularity_to_eig(cxld->interleave_granularity, &eig),
+ if (WARN_ONCE(granularity_to_eig(config->interleave_granularity, &eig),
"invalid interleave_granularity: %d\n",
- cxld->interleave_granularity))
+ config->interleave_granularity))
return;
u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK);
@@ -35,10 +35,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(struct cxl_decoder_config *config, u32 *ctrl)
{
u32p_replace_bits(ctrl,
- !!(cxld->target_type == CXL_DECODER_HOSTONLYMEM),
+ !!(config->target_type == CXL_DECODER_HOSTONLYMEM),
CXL_HDM_DECODER0_CTRL_HOSTONLY);
}
@@ -48,7 +48,7 @@ static void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl)
* clock skew and other marginal behavior
*/
#define COMMIT_TIMEOUT_MS 20
-int cxld_await_commit(void __iomem *hdm, int id)
+static int cxld_await_commit(void __iomem *hdm, int id)
{
u32 ctrl;
int i;
@@ -68,43 +68,40 @@ int cxld_await_commit(void __iomem *hdm, int id)
return -ETIMEDOUT;
}
-void setup_hw_decoder(struct cxl_decoder *cxld, void __iomem *hdm)
+static void setup_hw_decoder(void __iomem *hdm,
+ struct cxl_decoder_settings *settings)
{
- int id = cxld->id;
+ struct cxl_decoder_config *config = &settings->config;
+ int id = config->id;
+ u64 target_or_skip_reg_val;
u64 base, size;
u32 ctrl;
- /* common decoder settings */
- ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
- cxld_set_interleave(cxld, &ctrl);
- cxld_set_type(cxld, &ctrl);
- base = cxld->hpa_range.start;
- size = range_len(&cxld->hpa_range);
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ cxld_set_interleave(config, &ctrl);
+ cxld_set_type(config, &ctrl);
+ base = config->hpa_range.start;
+ size = range_len(&config->hpa_range);
+ target_or_skip_reg_val = settings->target_or_skip_reg_val;
writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id));
writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id));
writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ /* Target-list and endpoint-skip registers alias the same slot. */
+ writel(upper_32_bits(target_or_skip_reg_val), hdm + CXL_HDM_DECODER0_TL_HIGH(id));
+ writel(lower_32_bits(target_or_skip_reg_val), hdm + CXL_HDM_DECODER0_TL_LOW(id));
- if (is_switch_decoder(&cxld->dev)) {
- struct cxl_switch_decoder *cxlsd =
- to_cxl_switch_decoder(&cxld->dev);
- void __iomem *tl_hi = hdm + CXL_HDM_DECODER0_TL_HIGH(id);
- void __iomem *tl_lo = hdm + CXL_HDM_DECODER0_TL_LOW(id);
- u64 targets;
-
- cxlsd_set_targets(cxlsd, &targets);
- writel(upper_32_bits(targets), tl_hi);
- writel(lower_32_bits(targets), tl_lo);
- } else {
- struct cxl_endpoint_decoder *cxled =
- to_cxl_endpoint_decoder(&cxld->dev);
- void __iomem *sk_hi = hdm + CXL_HDM_DECODER0_SKIP_HIGH(id);
- void __iomem *sk_lo = hdm + CXL_HDM_DECODER0_SKIP_LOW(id);
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+}
- writel(upper_32_bits(cxled->skip), sk_hi);
- writel(lower_32_bits(cxled->skip), sk_lo);
- }
+void cxl_commit_start(void __iomem *hdm, struct cxl_decoder_settings *settings)
+{
+ lockdep_assert_held(&cxl_rwsem.dpa);
+ setup_hw_decoder(hdm, settings);
+}
- writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings)
+{
+ return cxld_await_commit(hdm, settings->config.id);
}
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 69941f7fdb76..1acdf537e4e9 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -42,8 +42,8 @@ enum cxl_decoder_type {
* @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
+ * @region: currently assigned region for this decoder
* @target_map: cached copy of hardware port-id list, available at init
* before all @dport objects have been instantiated. While
* dport id is 8bit, CFMWS interleave targets are 32bits.
@@ -52,18 +52,31 @@ 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_group_tagged(cxl_decoder_config, config,
+ int id;
+ struct range hpa_range;
+ int interleave_ways;
+ int interleave_granularity;
+ enum cxl_decoder_type target_type;
+ unsigned long flags;
+ );
struct cxl_region *region;
- unsigned long flags;
u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
int (*commit)(struct cxl_decoder *cxld);
void (*reset)(struct cxl_decoder *cxld);
};
+/**
+ * struct cxl_decoder_settings - CXL HDM decoder programming snapshot
+ * @config: common decoder configuration
+ * @target_or_skip_reg_val: switch target list or endpoint skip register value
+ */
+struct cxl_decoder_settings {
+ struct cxl_decoder_config config;
+ u64 target_or_skip_reg_val;
+};
+
/*
* Using struct_group() allows for per register-block-type helper routines,
* without requiring block-type agnostic code to include the prefix.
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 06/15] cxl: Make HDM reset helpers available to built-in PCI code
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (4 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 05/15] cxl: Introduce reusable HDM decoder settings Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 07/15] cxl: Share HDM decoder register unpacking Srirangan Madhavan
` (8 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
PCI can be built in while CXL_BUS is modular, so PCI reset code cannot
depend on helpers provided by the cxl_core module.
Build the HDM resource helpers under CONFIG_CXL_RESET and restrict their
exports to cxl_core.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/Kconfig | 4 ++++
drivers/cxl/core/Makefile | 2 +-
drivers/cxl/core/resource.c | 4 ++++
tools/testing/cxl/Kbuild | 1 -
4 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index 80aeb0d556bd..0cd704670735 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,6 @@ config CXL_ATL
depends on ACPI_PRMT && AMD_NB
endif
+
+config CXL_RESET
+ bool
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 57995777af0d..30470799e059 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,12 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
+obj-$(CONFIG_CXL_RESET) += resource.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
-cxl_core-y += resource.o
cxl_core-y += pmem.o
cxl_core-y += regs.o
cxl_core-y += memdev.o
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index ea0d855e5549..1b4742622054 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -3,6 +3,7 @@
#include <linux/delay.h>
#include <linux/bug.h>
#include <linux/errno.h>
+#include <linux/export.h>
#include <linux/kernel.h>
#include "cxl.h"
@@ -12,6 +13,7 @@ struct cxl_rwsem cxl_rwsem = {
.region = __RWSEM_INITIALIZER(cxl_rwsem.region),
.dpa = __RWSEM_INITIALIZER(cxl_rwsem.dpa),
};
+EXPORT_SYMBOL_FOR_MODULES(cxl_rwsem, "cxl_core");
static void cxld_set_interleave(struct cxl_decoder_config *config, u32 *ctrl)
{
@@ -100,8 +102,10 @@ void cxl_commit_start(void __iomem *hdm, struct cxl_decoder_settings *settings)
lockdep_assert_held(&cxl_rwsem.dpa);
setup_hw_decoder(hdm, settings);
}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit_start, "cxl_core");
int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings)
{
return cxld_await_commit(hdm, settings->config.id);
}
+EXPORT_SYMBOL_FOR_MODULES(cxl_commit_wait, "cxl_core");
diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild
index 6c09932587ce..2be1df80fcc9 100644
--- a/tools/testing/cxl/Kbuild
+++ b/tools/testing/cxl/Kbuild
@@ -60,7 +60,6 @@ cxl_core-y += $(CXL_CORE_SRC)/memdev.o
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
cxl_core-y += $(CXL_CORE_SRC)/pci.o
cxl_core-y += $(CXL_CORE_SRC)/hdm.o
-cxl_core-y += $(CXL_CORE_SRC)/resource.o
cxl_core-y += $(CXL_CORE_SRC)/pmu.o
cxl_core-y += $(CXL_CORE_SRC)/cdat.o
cxl_core-$(CONFIG_TRACING) += $(CXL_CORE_SRC)/trace.o
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 07/15] cxl: Share HDM decoder register unpacking
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (5 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 06/15] cxl: Make HDM reset helpers available to built-in PCI code Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 08/15] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
` (7 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Factor HDM register parsing into cxl_hdm_unpack_decoder(). Validate local
values before publishing complete settings, including committed, locked,
and target-or-skip state.
Pass endpoint skip state to the DPA reservation helper instead of
updating live endpoint state, preserving its ownership.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/core.h | 4 ++
drivers/cxl/core/hdm.c | 74 +++++++++++++------------------------
drivers/cxl/core/resource.c | 55 +++++++++++++++++++++++++++
3 files changed, 85 insertions(+), 48 deletions(-)
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index 3444479cbade..7e2723d1f87d 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -142,6 +142,10 @@ extern struct cxl_rwsem cxl_rwsem;
void cxl_commit_start(void __iomem *hdm, struct cxl_decoder_settings *settings);
int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings);
+int cxl_hdm_unpack_decoder(struct cxl_decoder_settings *settings, int id,
+ u32 ctrl, u64 base, u64 size,
+ u64 target_or_skip_reg_val);
+
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 b3ddc02df65e..98268e8e15b2 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -932,8 +932,8 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
u64 *dpa_base, struct cxl_endpoint_dvsec_info *info)
{
struct cxl_endpoint_decoder *cxled = NULL;
- u64 size, base, skip, dpa_size, lo, hi;
- bool committed;
+ struct cxl_decoder_settings settings;
+ u64 size, base, skip, dpa_size, lo, hi, target_or_skip_reg_val;
u32 remainder;
int i, rc;
u32 ctrl;
@@ -953,35 +953,34 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
lo = readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(which));
hi = readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(which));
size = (hi << 32) + lo;
- committed = !!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED);
+ lo = readl(hdm + CXL_HDM_DECODER0_TL_LOW(which));
+ hi = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(which));
+ target_or_skip_reg_val = (hi << 32) + lo;
+ rc = cxl_hdm_unpack_decoder(&settings, which, ctrl, base, size,
+ target_or_skip_reg_val);
+ if (rc) {
+ dev_warn(&port->dev,
+ "decoder%d.%d: Invalid decoder configuration (ctrl: %#x): %d\n",
+ port->id, cxld->id, ctrl, rc);
+ return rc;
+ }
+
cxld->commit = cxl_decoder_commit;
cxld->reset = cxl_decoder_reset;
-
- if (!committed)
- size = 0;
- if (base == U64_MAX || size == U64_MAX) {
- dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n",
- port->id, cxld->id);
- return -ENXIO;
- }
+ cxld->config = settings.config;
+ size = range_len(&cxld->hpa_range);
if (info)
cxled = to_cxl_endpoint_decoder(&cxld->dev);
- cxld->hpa_range = (struct range) {
- .start = base,
- .end = base + size - 1,
- };
+ if (!cxled && cxld->interleave_ways > 8) {
+ dev_warn(&port->dev,
+ "decoder%d.%d: Invalid target count: %d\n",
+ port->id, cxld->id, cxld->interleave_ways);
+ return -ENXIO;
+ }
/* decoders are enabled if committed */
- if (committed) {
- cxld->flags |= CXL_DECODER_F_ENABLE;
- if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
- cxld->flags |= CXL_DECODER_F_LOCK;
- if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl))
- cxld->target_type = CXL_DECODER_HOSTONLYMEM;
- else
- cxld->target_type = CXL_DECODER_DEVMEM;
-
+ if (cxld->flags & CXL_DECODER_F_ENABLE) {
guard(rwsem_write)(&cxl_rwsem.region);
if (cxld->id != cxl_num_decoders_committed(port)) {
dev_warn(&port->dev,
@@ -1015,38 +1014,19 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which));
}
}
- rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl),
- &cxld->interleave_ways);
- if (rc) {
- dev_warn(&port->dev,
- "decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n",
- port->id, cxld->id, ctrl);
- return rc;
- }
- rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl),
- &cxld->interleave_granularity);
- if (rc) {
- dev_warn(&port->dev,
- "decoder%d.%d: Invalid interleave granularity (ctrl: %#x)\n",
- port->id, cxld->id, ctrl);
- return rc;
- }
-
dev_dbg(&port->dev, "decoder%d.%d: range: %#llx-%#llx iw: %d ig: %d\n",
port->id, cxld->id, cxld->hpa_range.start, cxld->hpa_range.end,
cxld->interleave_ways, cxld->interleave_granularity);
if (!cxled) {
- lo = readl(hdm + CXL_HDM_DECODER0_TL_LOW(which));
- hi = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(which));
- target_list.value = (hi << 32) + lo;
+ target_list.value = settings.target_or_skip_reg_val;
for (i = 0; i < cxld->interleave_ways; i++)
cxld->target_map[i] = target_list.target_id[i];
return 0;
}
- if (!committed)
+ if (!(cxld->flags & CXL_DECODER_F_ENABLE))
return 0;
dpa_size = div_u64_rem(size, cxld->interleave_ways, &remainder);
@@ -1056,9 +1036,7 @@ static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld,
port->id, cxld->id, size, cxld->interleave_ways);
return -ENXIO;
}
- lo = readl(hdm + CXL_HDM_DECODER0_SKIP_LOW(which));
- hi = readl(hdm + CXL_HDM_DECODER0_SKIP_HIGH(which));
- skip = (hi << 32) + lo;
+ skip = settings.target_or_skip_reg_val;
rc = devm_cxl_dpa_reserve(cxled, *dpa_base + skip, dpa_size, skip);
if (rc) {
dev_err(&port->dev,
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 1b4742622054..7eea2bc38d94 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -109,3 +109,58 @@ int cxl_commit_wait(void __iomem *hdm, struct cxl_decoder_settings *settings)
return cxld_await_commit(hdm, settings->config.id);
}
EXPORT_SYMBOL_FOR_MODULES(cxl_commit_wait, "cxl_core");
+
+int cxl_hdm_unpack_decoder(struct cxl_decoder_settings *settings, int id,
+ u32 ctrl, u64 base, u64 size,
+ u64 target_or_skip_reg_val)
+{
+ bool committed = FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
+ enum cxl_decoder_type target_type;
+ int interleave_granularity;
+ int interleave_ways;
+ unsigned long flags = 0;
+ struct range hpa_range;
+ int rc;
+
+ if (!committed)
+ size = 0;
+ if (base == U64_MAX || size == U64_MAX ||
+ (size && base > U64_MAX - (size - 1)))
+ return -ENXIO;
+
+ hpa_range = (struct range) {
+ .start = base,
+ .end = base + size - 1,
+ };
+ target_type = FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl) ?
+ CXL_DECODER_HOSTONLYMEM : CXL_DECODER_DEVMEM;
+ if (committed) {
+ flags |= CXL_DECODER_F_ENABLE;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK)
+ flags |= CXL_DECODER_F_LOCK;
+ }
+
+ 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;
+
+ *settings = (struct cxl_decoder_settings) {
+ .config = {
+ .id = id,
+ .hpa_range = hpa_range,
+ .interleave_ways = interleave_ways,
+ .interleave_granularity = interleave_granularity,
+ .target_type = target_type,
+ .flags = flags,
+ },
+ .target_or_skip_reg_val = target_or_skip_reg_val,
+ };
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 08/15] cxl: Refresh cached PCI HDM decoder settings
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (6 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 07/15] cxl: Share HDM decoder register unpacking Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 09/15] cxl: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
` (6 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Early PCI discovery creates the HDM cache, while later CXL enumeration and
decoder operations provide updated programming state.
Refresh the PCI snapshot when decoders are enumerated, committed, or reset
so reset recovery need not walk the CXL topology. Ignore updates when no
cache exists and reject decoder-count mismatches.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/hdm.c | 66 ++++++++++++++++++++++++++++++++++++++++++
include/cxl/cxl.h | 22 ++++++++++++++
include/linux/pci.h | 6 ++++
3 files changed, 94 insertions(+)
diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c
index 98268e8e15b2..0ae250ea4ce3 100644
--- a/drivers/cxl/core/hdm.c
+++ b/drivers/cxl/core/hdm.c
@@ -753,6 +753,51 @@ static void cxl_decoder_snapshot(struct cxl_decoder *cxld,
}
}
+#ifdef CONFIG_CXL_RESET
+static void cxl_hdm_refresh_decoder(struct cxl_hdm *cxlhdm,
+ struct cxl_decoder *cxld)
+{
+ struct cxl_port *port = cxlhdm->port;
+ void __iomem *hdm = cxlhdm->regs.hdm_decoder;
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(port);
+ struct cxl_decoder_settings *settings;
+ struct cxl_hdm_info *info;
+
+ if (!pdev || !hdm)
+ return;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ if (!info)
+ return;
+ if (cxld->id < 0 || cxld->id >= info->decoder_count) {
+ pci_warn(pdev, "CXL HDM decoder %d exceeds cached count %d\n",
+ cxld->id, info->decoder_count);
+ return;
+ }
+
+ info->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ settings = &info->settings[cxld->id];
+
+ /*
+ * A disabled decoder's software object may retain its old range and
+ * target state. Leave only the decoder id in the cached settings so stale
+ * state is not restored as an enabled decode.
+ */
+ *settings = (struct cxl_decoder_settings) {
+ .config.id = cxld->id,
+ };
+ if (cxld->flags & CXL_DECODER_F_ENABLE)
+ cxl_decoder_snapshot(cxld, settings);
+}
+#else
+static void cxl_hdm_refresh_decoder(struct cxl_hdm *cxlhdm,
+ struct cxl_decoder *cxld)
+{
+}
+#endif
+
static int cxl_decoder_commit(struct cxl_decoder *cxld)
{
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
@@ -804,6 +849,7 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
}
port->commit_end++;
cxld->flags |= CXL_DECODER_F_ENABLE;
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
return 0;
}
@@ -876,6 +922,7 @@ static void cxl_decoder_reset(struct cxl_decoder *cxld)
writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
cxld->flags &= ~CXL_DECODER_F_ENABLE;
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
/* Userspace is now responsible for reconfiguring this decoder */
if (is_endpoint_decoder(&cxld->dev)) {
@@ -1089,9 +1136,27 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
{
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
struct cxl_port *port = cxlhdm->port;
+#ifdef CONFIG_CXL_RESET
+ struct pci_dev *pdev __free(pci_dev_put) =
+ cxl_port_get_uport_pci_dev(port);
+#endif
int i;
u64 dpa_base = 0;
+#ifdef CONFIG_CXL_RESET
+ if (is_cxl_endpoint(port) && pdev && hdm) {
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+
+ if (pdev->hdm &&
+ pdev->hdm->decoder_count != cxlhdm->decoder_count) {
+ pci_warn(pdev,
+ "CXL HDM cache decoder count mismatch: cached=%d hdm=%d\n",
+ pdev->hdm->decoder_count, cxlhdm->decoder_count);
+ return -ENXIO;
+ }
+ }
+#endif
+
cxl_settle_decoders(cxlhdm);
for (i = 0; i < cxlhdm->decoder_count; i++) {
@@ -1130,6 +1195,7 @@ static int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
put_device(&cxld->dev);
return rc;
}
+ cxl_hdm_refresh_decoder(cxlhdm, cxld);
rc = add_hdm_decoder(port, cxld);
if (rc) {
dev_warn(&port->dev,
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 1acdf537e4e9..005259e38f4b 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -77,6 +77,28 @@ struct cxl_decoder_settings {
u64 target_or_skip_reg_val;
};
+/**
+ * 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-decoder programming state
+ */
+struct cxl_hdm_info {
+ int decoder_count;
+ int hdm_bar;
+ resource_size_t hdm_offset;
+ resource_size_t hdm_size;
+ u32 global_ctrl;
+ u16 dvsec_ctrl;
+ bool dvsec_ctrl_valid;
+ struct cxl_decoder_settings settings[];
+};
+
/*
* Using struct_group() allows for per register-block-type helper routines,
* without requiring block-type agnostic code to include the prefix.
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d31a8d107b1e..7bb37fcb556d 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -339,6 +339,9 @@ struct pcie_link_state;
struct pci_sriov;
struct pci_p2pdma;
struct rcec_ea;
+#ifdef CONFIG_CXL_RESET
+struct cxl_hdm_info;
+#endif
/* struct pci_dev - describes a PCI device
*
@@ -566,6 +569,9 @@ struct pci_dev {
#ifdef CONFIG_PCI_DOE
struct xarray doe_mbs; /* Data Object Exchange mailboxes */
#endif
+#ifdef CONFIG_CXL_RESET
+ struct cxl_hdm_info *hdm; /* CXL HDM decoder state */
+#endif
#ifdef CONFIG_PCI_NPEM
struct npem *npem; /* Native PCIe Enclosure Management */
#endif
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 09/15] cxl: Cache endpoint HDM state during PCI enumeration
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (7 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 08/15] cxl: Refresh cached PCI HDM decoder settings Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 10/15] cxl: Add CXL Device Reset sequencing Srirangan Madhavan
` (5 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
PCI capability initialization runs before BAR resources are finalized,
while driver binding is too late for driver-independent reset support.
Create pci_dev->hdm during pci_bus_add_device(), after PCI resource setup
and before driver binding.
Cache BAR-relative HDM location, global control, decoder settings, and CXL
Device DVSEC Control, then publish the completed cache under cxl_rwsem.dpa.
Restore PCI_COMMAND after temporary MMIO access and reject decoder-count
changes.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/Makefile | 3 +-
drivers/cxl/core/pci.c | 15 ++-
drivers/cxl/core/regs.c | 9 ++
drivers/cxl/core/resource.c | 255 ++++++++++++++++++++++++++++++++++++
drivers/pci/bus.c | 2 +
drivers/pci/probe.c | 2 +
include/cxl/cxl.h | 21 +++
tools/testing/cxl/Kbuild | 1 -
8 files changed, 304 insertions(+), 4 deletions(-)
diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
index 30470799e059..2335059f687a 100644
--- a/drivers/cxl/core/Makefile
+++ b/drivers/cxl/core/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
-obj-$(CONFIG_CXL_RESET) += resource.o
+obj-$(CONFIG_CXL_RESET) += regs.o resource.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
@@ -8,7 +8,6 @@ CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
cxl_core-y += pmem.o
-cxl_core-y += regs.o
cxl_core-y += memdev.o
cxl_core-y += mbox.o
cxl_core-y += pci.o
diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
index bf7fc77626d9..cf7fefd595a2 100644
--- a/drivers/cxl/core/pci.c
+++ b/drivers/cxl/core/pci.c
@@ -180,6 +180,16 @@ int cxl_await_media_ready(struct cxl_dev_state *cxlds)
}
EXPORT_SYMBOL_NS_GPL(cxl_await_media_ready, "CXL");
+static void cxl_hdm_cache_dvsec_ctrl(struct pci_dev *pdev, u16 ctrl)
+{
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ if (!pdev->hdm)
+ return;
+
+ pdev->hdm->dvsec_ctrl = ctrl;
+ pdev->hdm->dvsec_ctrl_valid = true;
+}
+
static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
@@ -191,8 +201,10 @@ static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
if (rc)
return pcibios_err_to_errno(rc);
- if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) == val)
+ if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) == val) {
+ cxl_hdm_cache_dvsec_ctrl(pdev, ctrl);
return 1;
+ }
ctrl &= ~PCI_DVSEC_CXL_MEM_ENABLE;
ctrl |= val;
@@ -200,6 +212,7 @@ static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
if (rc)
return pcibios_err_to_errno(rc);
+ cxl_hdm_cache_dvsec_ctrl(pdev, ctrl);
return 0;
}
diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c
index 20c2d9fbcfe7..ba1f942d20e1 100644
--- a/drivers/cxl/core/regs.c
+++ b/drivers/cxl/core/regs.c
@@ -200,6 +200,7 @@ void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr,
return ret_val;
}
+EXPORT_SYMBOL_NS_GPL(devm_cxl_iomap_block, "CXL");
int cxl_map_component_regs(const struct cxl_register_map *map,
struct cxl_component_regs *regs,
@@ -285,6 +286,8 @@ static bool cxl_decode_regblock(struct pci_dev *pdev, u32 reg_lo, u32 reg_hi,
}
map->reg_type = reg_type;
+ map->bar = bar;
+ map->bar_offset = offset;
map->resource = pci_resource_start(pdev, bar) + offset;
map->max_size = pci_resource_len(pdev, bar) - offset;
return true;
@@ -309,6 +312,7 @@ static int __cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_ty
*map = (struct cxl_register_map) {
.host = &pdev->dev,
.resource = CXL_RESOURCE_NONE,
+ .bar = -1,
};
regloc = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
@@ -339,6 +343,8 @@ static int __cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_ty
}
map->resource = CXL_RESOURCE_NONE;
+ map->bar = -1;
+ map->bar_offset = 0;
if (index == CXL_INSTANCES_COUNT)
return instance;
@@ -518,6 +524,7 @@ u16 cxl_rcrb_to_aer(struct device *dev, resource_size_t rcrb)
return offset;
}
+EXPORT_SYMBOL_NS_GPL(cxl_rcrb_to_aer, "CXL");
static resource_size_t cxl_rcrb_to_linkcap(struct device *dev, struct cxl_dport *dport)
{
@@ -634,6 +641,7 @@ resource_size_t __rcrb_to_component(struct device *dev, struct cxl_rcrb_info *ri
return component_reg_phys;
}
+EXPORT_SYMBOL_NS_GPL(__rcrb_to_component, "CXL");
resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
struct cxl_dport *dport)
@@ -642,3 +650,4 @@ resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
return CXL_RESOURCE_NONE;
return __rcrb_to_component(dev, &dport->rcrb, CXL_RCRB_UPSTREAM);
}
+EXPORT_SYMBOL_NS_GPL(cxl_rcd_component_reg_phys, "CXL");
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 7eea2bc38d94..b5f8c28e613f 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -2,9 +2,17 @@
/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
#include <linux/delay.h>
#include <linux/bug.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/errno.h>
#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include <cxl/pci.h>
#include "cxl.h"
#include "core.h"
@@ -164,3 +172,250 @@ int cxl_hdm_unpack_decoder(struct cxl_decoder_settings *settings, int id,
return 0;
}
EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
+
+static int cxl_pci_hdm_dvsec(struct pci_dev *pdev)
+{
+ u16 cap;
+ int dvsec;
+ int rc;
+
+ dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!dvsec)
+ return -ENOTTY;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, &cap);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (!(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
+ !FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap))
+ return -ENOTTY;
+
+ return dvsec;
+}
+
+static int cxl_pci_hdm_read_decoder(struct pci_dev *pdev,
+ struct cxl_decoder_settings *settings,
+ void __iomem *hdm, int id)
+{
+ u64 target_or_skip, base, size;
+ u32 ctrl;
+ int rc;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ base = ((u64)readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id)) << 32) |
+ readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
+ size = ((u64)readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id)) << 32) |
+ readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id));
+ target_or_skip =
+ ((u64)readl(hdm + CXL_HDM_DECODER0_TL_HIGH(id)) << 32) |
+ readl(hdm + CXL_HDM_DECODER0_TL_LOW(id));
+
+ rc = cxl_hdm_unpack_decoder(settings, id, ctrl, base, size,
+ target_or_skip);
+ if (rc) {
+ pci_err(pdev,
+ "CXL HDM decoder %d has invalid configuration: %d\n",
+ id, rc);
+ return rc;
+ }
+
+ if (!(settings->config.flags & CXL_DECODER_F_ENABLE))
+ *settings = (struct cxl_decoder_settings) {
+ .config.id = id,
+ };
+
+ return 0;
+}
+
+static int __cxl_pci_hdm_read_info(struct pci_dev *pdev,
+ struct cxl_register_map *map, int dvsec,
+ struct cxl_hdm_info **out)
+{
+ struct cxl_reg_map *hdm_map = &map->component_map.hdm_decoder;
+ struct cxl_hdm_info *info = NULL;
+ resource_size_t hdm_offset;
+ void __iomem *hdm;
+ int decoder_count;
+ size_t required;
+ u16 dvsec_ctrl;
+ int rc;
+
+ rc = cxl_setup_regs(map);
+ if (rc)
+ return rc;
+ if (!hdm_map->valid)
+ return -ENODEV;
+ if (hdm_map->offset > map->max_size ||
+ hdm_map->size > map->max_size - hdm_map->offset)
+ return -ENXIO;
+ if (check_add_overflow(map->bar_offset,
+ (resource_size_t)hdm_map->offset, &hdm_offset))
+ return -EOVERFLOW;
+
+ hdm = ioremap(map->resource + hdm_map->offset, hdm_map->size);
+ if (!hdm)
+ return -ENOMEM;
+
+ decoder_count = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (decoder_count < 0) {
+ rc = decoder_count;
+ goto out_unmap;
+ }
+
+ required = CXL_HDM_DECODER0_TL_HIGH(decoder_count - 1) +
+ sizeof(u32);
+ if (required > hdm_map->size) {
+ pci_err(pdev,
+ "CXL HDM decoder count exceeds mapped register block\n");
+ rc = -ENXIO;
+ goto out_unmap;
+ }
+
+ 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(struct_size(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->global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+ info->dvsec_ctrl = dvsec_ctrl;
+ info->dvsec_ctrl_valid = true;
+
+ for (int i = 0; i < decoder_count; i++) {
+ rc = cxl_pci_hdm_read_decoder(pdev, &info->settings[i], hdm, i);
+ if (rc)
+ goto out_unmap;
+ }
+
+ /*
+ * A changed decoder count means the snapshot no longer describes the
+ * complete hardware state. Reject it rather than publish a partial cache.
+ */
+ rc = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (rc != decoder_count) {
+ pci_err(pdev, "CXL HDM decoder count changed from %d to %d\n",
+ decoder_count, rc);
+ rc = -ENXIO;
+ goto out_unmap;
+ }
+
+ *out = info;
+ info = NULL;
+ rc = 0;
+
+out_unmap:
+ kfree(info);
+ iounmap(hdm);
+ return rc;
+}
+
+static int cxl_pci_hdm_read_info(struct pci_dev *pdev,
+ struct cxl_register_map *map, int dvsec,
+ struct cxl_hdm_info **out)
+{
+ bool restore_command = false;
+ u16 command;
+ int rc, rc2;
+
+ guard(pci_dev)(pdev);
+
+ rc = pci_read_config_word(pdev, PCI_COMMAND, &command);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ if (!(command & PCI_COMMAND_MEMORY)) {
+ rc = pci_write_config_word(pdev, PCI_COMMAND,
+ command | PCI_COMMAND_MEMORY);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ restore_command = true;
+ }
+
+ rc = __cxl_pci_hdm_read_info(pdev, map, dvsec, out);
+ if (!restore_command)
+ return rc;
+
+ if (!rc) {
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ return pcibios_err_to_errno(rc);
+ }
+
+ rc2 = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc2)
+ pci_err(pdev,
+ "failed to restore PCI_COMMAND after CXL HDM cache init: %d\n",
+ pcibios_err_to_errno(rc2));
+
+ return rc;
+}
+
+static int __pci_cxl_hdm_cache_init(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *info __free(kfree) = NULL;
+ struct cxl_register_map map = { };
+ struct resource *bar;
+ int dvsec;
+ int rc;
+
+ scoped_guard(rwsem_read, &cxl_rwsem.dpa)
+ if (pdev->hdm)
+ return 0;
+
+ dvsec = cxl_pci_hdm_dvsec(pdev);
+ if (dvsec < 0)
+ return dvsec;
+
+ rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
+ if (rc)
+ return rc;
+ if (map.bar < 0 || map.bar >= PCI_STD_NUM_BARS)
+ return -ENODEV;
+
+ bar = &pdev->resource[map.bar];
+ if (!pci_resource_len(pdev, map.bar) ||
+ (bar->flags & (IORESOURCE_UNSET | IORESOURCE_DISABLED)) ||
+ resource_type(bar) != IORESOURCE_MEM || !bar->start || !bar->end)
+ return -ENODEV;
+
+ rc = cxl_pci_hdm_read_info(pdev, &map, dvsec, &info);
+ if (rc)
+ return rc;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ if (!pdev->hdm)
+ pdev->hdm = no_free_ptr(info);
+
+ return 0;
+}
+
+void pci_cxl_hdm_cache_init(struct pci_dev *pdev)
+{
+ int rc;
+
+ rc = __pci_cxl_hdm_cache_init(pdev);
+ if (rc && rc != -ENOTTY && rc != -ENODEV)
+ pci_dbg(pdev, "CXL HDM cache init failed: %d\n", rc);
+}
+EXPORT_SYMBOL_FOR_MODULES(pci_cxl_hdm_cache_init, "cxl_core");
+
+void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *info;
+
+ guard(rwsem_write)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ pdev->hdm = NULL;
+ kfree(info);
+}
diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
index 655ed53436d3..d866452222b6 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -18,6 +18,7 @@
#include <linux/pm_runtime.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
+#include <cxl/cxl.h>
#include "pci.h"
@@ -359,6 +360,7 @@ void pci_bus_add_device(struct pci_dev *dev)
/* Save config space for error recoverability */
pci_save_state(dev);
+ pci_cxl_hdm_cache_init(dev);
/*
* Enable runtime PM, which potentially allows the device to
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 27008e2ea5af..752347464a4d 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -24,6 +24,7 @@
#include <linux/pm_runtime.h>
#include <linux/bitfield.h>
#include <trace/events/pci.h>
+#include <cxl/cxl.h>
#include "pci.h"
static struct resource busn_resource = {
@@ -2484,6 +2485,7 @@ static void pci_release_dev(struct device *dev)
struct pci_dev *pci_dev;
pci_dev = to_pci_dev(dev);
+ pci_cxl_hdm_cache_release(pci_dev);
pci_release_capabilities(pci_dev);
pci_release_of_node(pci_dev);
pcibios_release_device(pci_dev);
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index 005259e38f4b..4bad41e74f74 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -26,6 +26,7 @@ enum cxl_devtype {
};
struct cxl_region;
+struct pci_dev;
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
@@ -87,6 +88,9 @@ struct cxl_decoder_settings {
* @dvsec_ctrl: CXL DVSEC control register
* @dvsec_ctrl_valid: whether @dvsec_ctrl contains valid state
* @settings: per-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;
@@ -172,6 +176,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
@@ -182,6 +188,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;
@@ -190,6 +198,19 @@ struct cxl_register_map {
};
};
+#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
+
/**
* struct cxl_dpa_perf - DPA performance property entry
* @dpa_range: range for DPA address
diff --git a/tools/testing/cxl/Kbuild b/tools/testing/cxl/Kbuild
index 2be1df80fcc9..e80500f457a9 100644
--- a/tools/testing/cxl/Kbuild
+++ b/tools/testing/cxl/Kbuild
@@ -55,7 +55,6 @@ obj-m += cxl_core.o
cxl_core-y := $(CXL_CORE_SRC)/port.o
cxl_core-y += $(CXL_CORE_SRC)/pmem.o
-cxl_core-y += $(CXL_CORE_SRC)/regs.o
cxl_core-y += $(CXL_CORE_SRC)/memdev.o
cxl_core-y += $(CXL_CORE_SRC)/mbox.o
cxl_core-y += $(CXL_CORE_SRC)/pci.o
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 10/15] cxl: Add CXL Device Reset sequencing
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (8 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 09/15] cxl: Cache endpoint HDM state during PCI enumeration Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 11/15] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
` (4 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Implement CXL Device Reset for eligible Type 2 devices: disable CXL.cache,
perform supported writeback, drain transactions, prepare the IOMMU,
initiate reset with Memory Clear enabled, and wait for completion.
Observe the 100 ms post-reset quiet period before polling Status2, and
treat an all-ones read as not ready. Treat Control2 command bits as
transient state, restore cache policy on exit, preserve the primary error,
and abort when IOMMU preparation fails.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/resource.c | 229 ++++++++++++++++++++++++++++++++++
include/cxl/cxl.h | 7 ++
include/uapi/linux/pci_regs.h | 14 +++
3 files changed, 250 insertions(+)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index b5f8c28e613f..57eec77cbb21 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -8,6 +8,8 @@
#include <linux/export.h>
#include <linux/io.h>
#include <linux/ioport.h>
+#include <linux/iommu.h>
+#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -419,3 +421,230 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
pdev->hdm = NULL;
kfree(info);
}
+
+/*
+ * CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
+ * Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
+ * initiating CXL Reset, then limits software to CXL Status2 access until
+ * reset completion, timeout, or error.
+ */
+#define CXL_RESET_RRS_WAIT_MS 100
+#define CXL_RESET_STATUS_POLL_MS 20
+static const u32 cxl_reset_timeout_ms[] = {
+ 10, 100, 1000, 10000, 100000,
+};
+
+#define CXL_CACHE_WBI_TIMEOUT_US 100000
+#define CXL_CACHE_WBI_POLL_US 100
+
+#define CXL_RESET_CTRL2_CMD_MASK \
+ (PCI_DVSEC_CXL_INIT_CACHE_WBI | PCI_DVSEC_CXL_INIT_CXL_RST)
+
+static int cxl_reset_update_ctrl2_no_replay(struct pci_dev *pdev, int dvsec,
+ u16 set, u16 clear)
+{
+ u16 ctrl2;
+ int rc;
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, &ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(ctrl2))
+ return -ENODEV;
+
+ /*
+ * INIT_CACHE_WBI and INIT_CXL_RST are commands. Do not replay a command
+ * that remains set in the value read back while updating persistent
+ * Control2 state.
+ */
+ ctrl2 &= ~CXL_RESET_CTRL2_CMD_MASK;
+ ctrl2 &= ~clear;
+ ctrl2 |= set;
+
+ rc = pci_write_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL2, ctrl2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ return 0;
+}
+
+static int cxl_reset_wait_cache_wbi(struct pci_dev *pdev, int dvsec)
+{
+ unsigned long deadline;
+ u16 status2;
+ int rc;
+
+ rc = cxl_reset_update_ctrl2_no_replay(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CACHE_WBI, 0);
+ if (rc)
+ return rc;
+
+ deadline = jiffies + usecs_to_jiffies(CXL_CACHE_WBI_TIMEOUT_US);
+ while (time_before(jiffies, deadline)) {
+ usleep_range(CXL_CACHE_WBI_POLL_US,
+ CXL_CACHE_WBI_POLL_US * 2);
+
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(status2))
+ return -ENODEV;
+ if (status2 & PCI_DVSEC_CXL_CACHE_INV)
+ return 0;
+ }
+
+ /* Do not miss completion concurrent with the polling deadline. */
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+ if (PCI_POSSIBLE_ERROR(status2))
+ return -ENODEV;
+ if (status2 & PCI_DVSEC_CXL_CACHE_INV)
+ return 0;
+
+ return -ETIMEDOUT;
+}
+
+static int cxl_reset_disable_cache(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc;
+
+ rc = cxl_reset_update_ctrl2_no_replay(pdev, dvsec,
+ PCI_DVSEC_CXL_DISABLE_CACHING, 0);
+ if (rc || !(cap & PCI_DVSEC_CXL_CACHE_WBI_CAPABLE))
+ return rc;
+
+ return cxl_reset_wait_cache_wbi(pdev, dvsec);
+}
+
+static int cxl_reset_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)) {
+ int last = ARRAY_SIZE(cxl_reset_timeout_ms) - 1;
+
+ pci_warn(pdev,
+ "unknown CXL reset timeout encoding %d; using %u ms\n",
+ idx, cxl_reset_timeout_ms[last]);
+ idx = last;
+ }
+
+ timeout_ms = max_t(u32, cxl_reset_timeout_ms[idx],
+ CXL_RESET_RRS_WAIT_MS);
+ msleep(CXL_RESET_RRS_WAIT_MS);
+ deadline = jiffies + msecs_to_jiffies(timeout_ms -
+ CXL_RESET_RRS_WAIT_MS);
+
+ for (;;) {
+ rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_STATUS2,
+ &status2);
+ if (rc || status2 == U16_MAX)
+ goto not_ready;
+ if (status2 & PCI_DVSEC_CXL_RST_ERR)
+ return -EIO;
+ if (status2 & PCI_DVSEC_CXL_RST_DONE)
+ return 0;
+
+not_ready:
+ if (time_after_eq(jiffies, deadline))
+ return -ETIMEDOUT;
+
+ msleep(CXL_RESET_STATUS_POLL_MS);
+ }
+}
+
+static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
+{
+ int rc, rc2;
+
+ rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ if (rc)
+ goto out_enable_cache;
+
+ if (!pci_wait_for_pending_transaction(pdev)) {
+ pci_err(pdev, "timed out waiting for pending transactions\n");
+ rc = -ETIMEDOUT;
+ goto out_enable_cache;
+ }
+
+ rc = pci_dev_reset_iommu_prepare(pdev);
+ if (rc) {
+ pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
+ goto out_enable_cache;
+ }
+
+ /* Clear Memory Clear again even if this command write reports failure. */
+ rc = cxl_reset_update_ctrl2_no_replay(pdev, dvsec,
+ PCI_DVSEC_CXL_INIT_CXL_RST |
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN, 0);
+ if (rc)
+ goto out_clear_memory;
+
+ rc = cxl_reset_wait_done(pdev, dvsec, cap);
+
+out_clear_memory:
+ rc2 = cxl_reset_update_ctrl2_no_replay(
+ pdev, dvsec, 0, PCI_DVSEC_CXL_RST_MEM_CLR_EN);
+ if (rc2)
+ pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n",
+ rc2);
+ rc = rc ?: rc2;
+
+ pci_dev_reset_iommu_done(pdev);
+
+out_enable_cache:
+ /*
+ * DISABLE_CACHING was the first preparation step. Restore the original
+ * cache policy last, after reset exclusion has ended.
+ */
+ rc2 = cxl_reset_update_ctrl2_no_replay(
+ pdev, dvsec, 0, PCI_DVSEC_CXL_DISABLE_CACHING);
+ if (rc2)
+ pci_err(pdev, "failed to re-enable CXL caching: %d\n", rc2);
+ rc = rc ?: rc2;
+
+ return rc;
+}
+
+int cxl_reset_function(struct pci_dev *pdev, bool probe)
+{
+ int dvsec, rc;
+ u16 cap, ctrl;
+
+ 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;
+ 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/include/cxl/cxl.h b/include/cxl/cxl.h
index 4bad41e74f74..a07dbf7463c0 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -6,6 +6,7 @@
#define __CXL_CXL_H__
#include <linux/device.h>
+#include <linux/errno.h>
#include <linux/node.h>
#include <linux/ioport.h>
#include <linux/range.h>
@@ -201,6 +202,7 @@ struct cxl_register_map {
#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)
{
@@ -209,6 +211,11 @@ 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
/**
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
index facaa324bd86..7224857c7b30 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1352,8 +1352,22 @@
#define PCI_DVSEC_CXL_CACHE_CAPABLE _BITUL(0)
#define PCI_DVSEC_CXL_MEM_CAPABLE _BITUL(2)
#define PCI_DVSEC_CXL_HDM_COUNT __GENMASK(5, 4)
+#define PCI_DVSEC_CXL_CACHE_WBI_CAPABLE _BITUL(6)
+#define PCI_DVSEC_CXL_RST_CAPABLE _BITUL(7)
+#define PCI_DVSEC_CXL_RST_TIMEOUT __GENMASK(10, 8)
+#define PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE _BITUL(11)
#define PCI_DVSEC_CXL_CTRL 0xC
+#define PCI_DVSEC_CXL_CACHE_ENABLE _BITUL(0)
#define PCI_DVSEC_CXL_MEM_ENABLE _BITUL(2)
+#define PCI_DVSEC_CXL_CTRL2 0x10
+#define PCI_DVSEC_CXL_DISABLE_CACHING _BITUL(0)
+#define PCI_DVSEC_CXL_INIT_CACHE_WBI _BITUL(1)
+#define PCI_DVSEC_CXL_INIT_CXL_RST _BITUL(2)
+#define PCI_DVSEC_CXL_RST_MEM_CLR_EN _BITUL(3)
+#define PCI_DVSEC_CXL_STATUS2 0x12
+#define PCI_DVSEC_CXL_CACHE_INV _BITUL(0)
+#define PCI_DVSEC_CXL_RST_DONE _BITUL(1)
+#define PCI_DVSEC_CXL_RST_ERR _BITUL(2)
#define PCI_DVSEC_CXL_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10))
#define PCI_DVSEC_CXL_RANGE_SIZE_LOW(i) (0x1C + (i * 0x10))
#define PCI_DVSEC_CXL_MEM_INFO_VALID _BITUL(0)
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 11/15] cxl: Validate and synchronize HDM ranges around reset
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (9 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 10/15] cxl: Add CXL Device Reset sequencing Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 12/15] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
` (3 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Refuse reset unless enabled system-physical HDM ranges can be reserved
exclusively and CPU-cache invalidation is available. Invalidate before
reset and again after restoration, holding range reservations and IOMMU
exclusion until the second invalidation completes.
Reject normalized-addressing decoders because their cached ranges are not
system physical addresses. Ignore zero-size decoders because they map no
address range.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/resource.c | 244 +++++++++++++++++++++++++++++++++++-
1 file changed, 239 insertions(+), 5 deletions(-)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 57eec77cbb21..85a554b6236c 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -11,6 +11,8 @@
#include <linux/iommu.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/memregion.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -437,6 +439,211 @@ static const u32 cxl_reset_timeout_ms[] = {
#define CXL_CACHE_WBI_TIMEOUT_US 100000
#define CXL_CACHE_WBI_POLL_US 100
+struct cxl_hdm_range {
+ struct list_head list;
+ struct pci_dev *pdev;
+ struct range hpa_range;
+ u64 len;
+ struct resource *res;
+};
+
+struct cxl_hdm_range_context {
+ struct list_head ranges;
+};
+
+static void cxl_hdm_range_context_destroy(struct cxl_hdm_range_context *ctx)
+{
+ struct cxl_hdm_range *range, *next;
+
+ list_for_each_entry_safe(range, next, &ctx->ranges, list) {
+ list_del(&range->list);
+ if (range->res)
+ release_mem_region(range->hpa_range.start,
+ resource_size(range->res));
+ kfree(range);
+ }
+}
+
+/*
+ * Bound the range twice: request_mem_region() takes resource_size_t while
+ * cpu_cache_invalidate_memregion() takes size_t, and the two differ on
+ * 32-bit builds with CONFIG_PHYS_ADDR_T_64BIT. range_len() can also reach
+ * RESOURCE_SIZE_MAX + 1 for a full-width range, and wraps to zero when
+ * resource_size_t is 64-bit, which the !len test catches.
+ */
+static int cxl_hdm_range_validate(struct pci_dev *pdev,
+ const struct range *hpa_range)
+{
+ u64 len = range_len(hpa_range);
+
+ if (!len)
+ return -EINVAL;
+
+ if (hpa_range->end > RESOURCE_SIZE_MAX) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource address size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (len > RESOURCE_SIZE_MAX) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (len > SIZE_MAX) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds cache flush size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_range_add(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev, const struct range *hpa_range)
+{
+ struct cxl_hdm_range *range;
+ int rc;
+
+ rc = cxl_hdm_range_validate(pdev, hpa_range);
+ if (rc)
+ return rc;
+
+ list_for_each_entry(range, &ctx->ranges, list)
+ if (range->hpa_range.start == hpa_range->start &&
+ range->hpa_range.end == hpa_range->end)
+ return 0;
+
+ range = kzalloc_obj(*range);
+ if (!range)
+ return -ENOMEM;
+
+ range->pdev = pdev;
+ range->hpa_range = *hpa_range;
+ range->len = range_len(hpa_range);
+ list_add_tail(&range->list, &ctx->ranges);
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_collect(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev)
+{
+ struct cxl_hdm_info *info;
+ int rc;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ if (!info) {
+ pci_err(pdev, "CXL HDM decoder state unavailable\n");
+ return -ENXIO;
+ }
+
+ for (int i = 0; i < info->decoder_count; i++) {
+ struct cxl_decoder_config *config = &info->settings[i].config;
+
+ if (!(config->flags & CXL_DECODER_F_ENABLE))
+ continue;
+
+ /* A committed zero-size decoder maps no HPA. */
+ if (!range_len(&config->hpa_range))
+ continue;
+
+ if (config->flags & CXL_DECODER_F_NORMALIZED_ADDRESSING) {
+ pci_err(pdev,
+ "CXL reset does not support normalized address decoders\n");
+ return -EOPNOTSUPP;
+ }
+
+ rc = cxl_hdm_range_add(ctx, pdev, &config->hpa_range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_request(struct cxl_hdm_range_context *ctx)
+{
+ struct cxl_hdm_range *range;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ list_for_each_entry(range, &ctx->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) {
+ pci_err(range->pdev,
+ "cannot reset while CXL memory range is busy [%#llx-%#llx]\n",
+ hpa_range->start, hpa_range->end);
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_ranges_invalidate(struct cxl_hdm_range_context *ctx)
+{
+ struct cxl_hdm_range *range;
+ int rc = 0;
+
+ list_for_each_entry(range, &ctx->ranges, list) {
+ const struct range *hpa_range = &range->hpa_range;
+ int rc2;
+
+ rc2 = cpu_cache_invalidate_memregion(hpa_range->start, range->len);
+ if (rc2)
+ pci_err(range->pdev,
+ "failed to invalidate CPU cache [%#llx-%#llx]: %d\n",
+ hpa_range->start, hpa_range->end, rc2);
+ rc = rc ?: rc2;
+ }
+
+ return rc;
+}
+
+static int cxl_hdm_ranges_prepare(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev)
+{
+ int rc;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ if (!cpu_cache_has_invalidate_memregion()) {
+ pci_err(pdev, "CPU cache invalidation unavailable\n");
+ return -ENXIO;
+ }
+
+ rc = cxl_hdm_ranges_collect(ctx, pdev);
+ if (rc)
+ return rc;
+
+ rc = cxl_hdm_ranges_request(ctx);
+ if (rc)
+ return rc;
+
+ return cxl_hdm_ranges_invalidate(ctx);
+}
+
+static int cxl_hdm_ranges_finish(struct cxl_hdm_range_context *ctx)
+{
+ int rc;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ rc = cxl_hdm_ranges_invalidate(ctx);
+ cxl_hdm_range_context_destroy(ctx);
+
+ return rc;
+}
+
#define CXL_RESET_CTRL2_CMD_MASK \
(PCI_DVSEC_CXL_INIT_CACHE_WBI | PCI_DVSEC_CXL_INIT_CXL_RST)
@@ -560,24 +767,25 @@ static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
}
}
-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_range_context *range_ctx)
{
int rc, rc2;
rc = cxl_reset_disable_cache(pdev, dvsec, cap);
if (rc)
- goto out_enable_cache;
+ goto out_destroy_ranges;
if (!pci_wait_for_pending_transaction(pdev)) {
pci_err(pdev, "timed out waiting for pending transactions\n");
rc = -ETIMEDOUT;
- goto out_enable_cache;
+ goto out_destroy_ranges;
}
rc = pci_dev_reset_iommu_prepare(pdev);
if (rc) {
pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
- goto out_enable_cache;
+ goto out_destroy_ranges;
}
/* Clear Memory Clear again even if this command write reports failure. */
@@ -597,7 +805,14 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
rc2);
rc = rc ?: rc2;
+ /* Evict lines fetched during reset before ending DMA exclusion. */
+ rc2 = cxl_hdm_ranges_finish(range_ctx);
+ rc = rc ?: rc2;
pci_dev_reset_iommu_done(pdev);
+ goto out_enable_cache;
+
+out_destroy_ranges:
+ cxl_hdm_range_context_destroy(range_ctx);
out_enable_cache:
/*
@@ -615,6 +830,7 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
+ struct cxl_hdm_range_context range_ctx;
int dvsec, rc;
u16 cap, ctrl;
@@ -646,5 +862,23 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (probe)
return 0;
- return cxl_reset_execute(pdev, dvsec, cap);
+ /* The cache is owned by @pdev and does not require a bound CXL driver. */
+ scoped_guard(rwsem_read, &cxl_rwsem.dpa)
+ if (!pdev->hdm || !pdev->hdm->hdm_size)
+ return -ENOTTY;
+
+ if (!cpu_cache_has_invalidate_memregion())
+ return -ENOTTY;
+
+ INIT_LIST_HEAD(&range_ctx.ranges);
+
+ scoped_guard(rwsem_write, &cxl_rwsem.region) {
+ rc = cxl_hdm_ranges_prepare(&range_ctx, pdev);
+ if (!rc)
+ rc = cxl_reset_execute(pdev, dvsec, cap, &range_ctx);
+ else
+ cxl_hdm_range_context_destroy(&range_ctx);
+ }
+
+ return rc;
}
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 12/15] PCI/CXL: Reject reset with unsafe function scope
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (10 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 11/15] cxl: Validate and synchronize HDM ranges around reset Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 13/15] cxl: Restore CXL state after PCI reset Srirangan Madhavan
` (2 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
The per-device PCI reset interface must not reset sibling functions.
Reject cxl_reset and cxl_bus for multifunction devices and PFs with
enabled VFs.
Reported-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>
---
drivers/cxl/core/resource.c | 3 +++
drivers/pci/pci.c | 3 +++
2 files changed, 6 insertions(+)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 85a554b6236c..249b1280f3d3 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -859,6 +859,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
!(ctrl & PCI_DVSEC_CXL_MEM_ENABLE))
return -ENOTTY;
+ if (pdev->multifunction || pci_num_vf(pdev))
+ return -ENOTTY;
+
if (probe)
return 0;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index b2879a6be5f8..fe611340f5ab 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5026,6 +5026,9 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (!dvsec)
return -ENOTTY;
+ if (dev->multifunction || pci_num_vf(dev))
+ return -ENOTTY;
+
if (probe)
return 0;
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 13/15] cxl: Restore CXL state after PCI reset
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (11 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 12/15] PCI/CXL: Reject reset with unsafe function scope Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 14/15] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 15/15] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Restore the PCI configuration needed for HDM access, followed by cached
HDM decoder and CXL Device DVSEC state. Recommit a cleared locked decoder
before restoring its lock, but leave surviving locked state unchanged.
Stop at the first restoration failure and preserve a disabled PCI command
snapshot. Keep IOMMU exclusion active through restoration and post-reset
cache invalidation.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/resource.c | 442 +++++++++++++++++++++++++++++++++---
1 file changed, 408 insertions(+), 34 deletions(-)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 249b1280f3d3..79ed12ecea81 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/memregion.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/slab.h>
@@ -82,6 +83,26 @@ static int cxld_await_commit(void __iomem *hdm, int id)
return -ETIMEDOUT;
}
+static int cxld_await_uncommit(void __iomem *hdm, int id)
+{
+ u32 ctrl;
+ int i;
+
+ for (i = 0; i < COMMIT_TIMEOUT_MS; i++) {
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) {
+ ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ return -EIO;
+ }
+ if (!FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl))
+ return 0;
+ fsleep(1000);
+ }
+
+ return -ETIMEDOUT;
+}
+
static void setup_hw_decoder(void __iomem *hdm,
struct cxl_decoder_settings *settings)
{
@@ -424,6 +445,323 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
kfree(info);
}
+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 ERR_PTR(-EINVAL);
+
+ res = &pdev->resource[bar];
+ if (!pci_resource_len(pdev, bar) ||
+ (res->flags & (IORESOURCE_UNSET | IORESOURCE_DISABLED)) ||
+ resource_type(res) != IORESOURCE_MEM || !res->start || !res->end)
+ return ERR_PTR(-ENODEV);
+ if (offset > resource_size(res) || size > resource_size(res) - offset)
+ return ERR_PTR(-ENODEV);
+ if (check_add_overflow(res->start, offset, &hdm_start))
+ return ERR_PTR(-EOVERFLOW);
+
+ hdm = ioremap(hdm_start, size);
+ if (!hdm) {
+ pci_err(pdev, "failed to remap CXL HDM decoder registers\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return hdm;
+}
+
+static int cxl_hdm_decoder_uncommit(struct pci_dev *pdev, void __iomem *hdm,
+ int id, bool *retained)
+{
+ u32 ctrl;
+ int rc;
+
+ *retained = false;
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return -EIO;
+ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMIT_ERROR) {
+ pci_err(pdev, "CXL HDM decoder %d has commit error set\n", id);
+ return -EIO;
+ }
+
+ if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK) {
+ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) {
+ *retained = true;
+ return 0;
+ }
+
+ pci_err(pdev, "CXL HDM decoder %d is locked and uncommitted\n",
+ id);
+ return -EIO;
+ }
+
+ if (!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED))
+ return 0;
+
+ ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
+
+ rc = cxld_await_uncommit(hdm, id);
+ if (rc)
+ pci_err(pdev, "CXL HDM decoder %d uncommit failed: %d\n", id,
+ rc);
+
+ return rc;
+}
+
+static bool cxl_hdm_decoder_settings_match(
+ const struct cxl_decoder_settings *saved,
+ const struct cxl_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->target_or_skip_reg_val == active->target_or_skip_reg_val;
+}
+
+static int cxl_restore_hdm_decoder_commit(
+ void __iomem *hdm, struct cxl_decoder_settings *settings)
+{
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+
+ cxl_commit_start(hdm, settings);
+ return cxl_commit_wait(hdm, settings);
+}
+
+static int cxl_restore_hdm_decoder(struct pci_dev *pdev, void __iomem *hdm,
+ struct cxl_decoder_settings *settings)
+{
+ struct cxl_decoder_config *config = &settings->config;
+ bool retained;
+ u32 ctrl;
+ int rc;
+
+ rc = cxl_hdm_decoder_uncommit(pdev, hdm, config->id, &retained);
+ if (rc)
+ return rc;
+
+ if (retained) {
+ struct cxl_decoder_settings active;
+
+ rc = cxl_pci_hdm_read_decoder(pdev, &active, hdm,
+ config->id);
+ if (rc)
+ return rc;
+ if (!cxl_hdm_decoder_settings_match(settings, &active)) {
+ pci_err(pdev,
+ "CXL HDM decoder %d retained unexpected locked state\n",
+ config->id);
+ return -EIO;
+ }
+ return 0;
+ }
+
+ if (!(config->flags & CXL_DECODER_F_ENABLE))
+ return 0;
+
+ rc = cxl_restore_hdm_decoder_commit(hdm, settings);
+ if (rc) {
+ pci_err(pdev, "CXL HDM decoder %d restore failed: %d\n",
+ config->id, rc);
+ return rc;
+ }
+
+ if (!(config->flags & CXL_DECODER_F_LOCK))
+ return 0;
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ if (PCI_POSSIBLE_ERROR(ctrl))
+ return -EIO;
+ ctrl |= CXL_HDM_DECODER0_CTRL_LOCK;
+ writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+
+ ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(config->id));
+ if (PCI_POSSIBLE_ERROR(ctrl) ||
+ !(ctrl & CXL_HDM_DECODER0_CTRL_LOCK)) {
+ pci_err(pdev, "CXL HDM decoder %d failed to restore lock\n",
+ config->id);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int cxl_snapshot_hdm(struct pci_dev *pdev,
+ struct cxl_hdm_info **snapshot)
+{
+ struct cxl_hdm_info *info;
+ size_t size;
+
+ guard(rwsem_read)(&cxl_rwsem.dpa);
+ info = pdev->hdm;
+ if (!info)
+ return -ENXIO;
+ if (info->decoder_count <= 0)
+ return -EINVAL;
+
+ size = struct_size(info, settings, info->decoder_count);
+ if (size == SIZE_MAX)
+ return -EOVERFLOW;
+ *snapshot = kmemdup(info, size, GFP_KERNEL);
+ if (!*snapshot)
+ return -ENOMEM;
+
+ return 0;
+}
+
+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;
+
+ /*
+ * pci_restore_state() is the available interface for restoring BARs and
+ * related PCI state needed to reach HDM MMIO. Temporarily alter its saved
+ * command value so that it 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, u16 saved_ctrl)
+{
+ int dvsec;
+ int rc;
+
+ dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!dvsec)
+ return -ENOTTY;
+
+ rc = pci_write_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL,
+ saved_ctrl);
+ if (rc)
+ return pcibios_err_to_errno(rc);
+
+ return 0;
+}
+
+static int cxl_restore_state(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot)
+{
+ void __iomem *hdm;
+ int count;
+ 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);
+ }
+
+ count = cxl_hdm_decoder_count(readl(hdm + CXL_HDM_DECODER_CAP_OFFSET));
+ if (count != snapshot->decoder_count) {
+ pci_err(pdev, "CXL HDM decoder count changed from %d to %d\n",
+ snapshot->decoder_count, count);
+ rc = -ENXIO;
+ goto out_unmap;
+ }
+
+ /* 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_restore_hdm_decoder(pdev, hdm,
+ &snapshot->settings[i]);
+ if (rc)
+ goto out_unmap;
+ }
+
+ /* Flush posted HDM writes before restoring protocol enables. */
+ readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
+
+ if (!snapshot->dvsec_ctrl_valid) {
+ pci_err(pdev, "CXL DVSEC control snapshot is invalid\n");
+ rc = -ENXIO;
+ goto out_unmap;
+ }
+ rc = cxl_restore_dvsec_ctrl(pdev, snapshot->dvsec_ctrl);
+ if (rc)
+ pci_err(pdev, "failed to restore CXL DVSEC control: %d\n", rc);
+
+out_unmap:
+ iounmap(hdm);
+ return rc;
+}
+
+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));
+
+ rc = pci_save_state(pdev);
+ if (rc)
+ pci_err(pdev,
+ "failed to save disabled state after CXL reset failure: %d\n",
+ rc);
+
+ /* Ensure the outer PCI restore cannot re-enable MSE or BME. */
+ *saved_command &= GENMASK(31, 16);
+ *saved_command |= PCI_COMMAND_INTX_DISABLE;
+}
+
+static int cxl_reset_save_restored_state(struct pci_dev *pdev, u16 command)
+{
+ int rc;
+
+ rc = pci_write_config_word(pdev, PCI_COMMAND, command);
+ if (rc) {
+ rc = pcibios_err_to_errno(rc);
+ pci_err(pdev, "failed to restore PCI command state: %d\n", rc);
+ cxl_reset_save_disabled_state(pdev);
+ return rc;
+ }
+
+ rc = pci_save_state(pdev);
+ if (rc) {
+ pci_err(pdev, "failed to save restored state after CXL reset: %d\n",
+ rc);
+ cxl_reset_save_disabled_state(pdev);
+ }
+
+ return rc;
+}
+
/*
* CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
* Sec 9.7.3 leaves config-space access behavior undefined for 100 ms after
@@ -726,6 +1064,18 @@ static int cxl_reset_disable_cache(struct pci_dev *pdev, int dvsec, u16 cap)
return cxl_reset_wait_cache_wbi(pdev, dvsec);
}
+static int cxl_reset_restore_cache_policy(struct pci_dev *pdev, int dvsec)
+{
+ int rc;
+
+ rc = cxl_reset_update_ctrl2_no_replay(pdev, dvsec, 0,
+ PCI_DVSEC_CXL_DISABLE_CACHING);
+ if (rc)
+ pci_err(pdev, "failed to re-enable CXL caching: %d\n", rc);
+
+ return rc;
+}
+
static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
{
unsigned long deadline;
@@ -767,28 +1117,41 @@ static int cxl_reset_wait_done(struct pci_dev *pdev, int dvsec, u16 cap)
}
}
-static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
- struct cxl_hdm_range_context *range_ctx)
+static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
{
+ struct cxl_hdm_info *snapshot = NULL;
+ struct cxl_hdm_range_context range_ctx;
+ u16 command;
int rc, rc2;
- rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ down_write(&cxl_rwsem.region);
+ INIT_LIST_HEAD(&range_ctx.ranges);
+
+ rc = cxl_hdm_ranges_prepare(&range_ctx, pdev);
+ if (rc)
+ goto out_destroy_ranges;
+
+ rc = cxl_snapshot_hdm(pdev, &snapshot);
if (rc)
goto out_destroy_ranges;
+ rc = cxl_reset_disable_cache(pdev, dvsec, cap);
+ if (rc)
+ goto out_destroy_enable_cache;
+
if (!pci_wait_for_pending_transaction(pdev)) {
pci_err(pdev, "timed out waiting for pending transactions\n");
rc = -ETIMEDOUT;
- goto out_destroy_ranges;
+ goto out_destroy_enable_cache;
}
rc = pci_dev_reset_iommu_prepare(pdev);
if (rc) {
pci_err(pdev, "failed to stop IOMMU for CXL reset: %d\n", rc);
- goto out_destroy_ranges;
+ goto out_destroy_enable_cache;
}
- /* Clear Memory Clear again even if this command write reports failure. */
+ /* Clear Memory Clear below even if this command write reports failure. */
rc = cxl_reset_update_ctrl2_no_replay(pdev, dvsec,
PCI_DVSEC_CXL_INIT_CXL_RST |
PCI_DVSEC_CXL_RST_MEM_CLR_EN, 0);
@@ -798,39 +1161,60 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
rc = cxl_reset_wait_done(pdev, dvsec, cap);
out_clear_memory:
- rc2 = cxl_reset_update_ctrl2_no_replay(
- pdev, dvsec, 0, PCI_DVSEC_CXL_RST_MEM_CLR_EN);
+ rc2 = cxl_reset_update_ctrl2_no_replay(pdev, dvsec, 0,
+ PCI_DVSEC_CXL_RST_MEM_CLR_EN);
if (rc2)
pci_err(pdev, "failed to clear CXL Reset Memory Clear: %d\n",
- rc2);
+ rc2);
rc = rc ?: rc2;
+ if (rc)
+ goto out_finish_ranges;
+
+ cxl_restore_pci_state_for_hdm_restore(pdev, &command);
+ rc = cxl_restore_state(pdev, snapshot);
+out_finish_ranges:
/* Evict lines fetched during reset before ending DMA exclusion. */
- rc2 = cxl_hdm_ranges_finish(range_ctx);
+ rc2 = cxl_hdm_ranges_finish(&range_ctx);
rc = rc ?: rc2;
+ if (rc)
+ goto out_fail_closed;
+
pci_dev_reset_iommu_done(pdev);
- goto out_enable_cache;
-out_destroy_ranges:
- cxl_hdm_range_context_destroy(range_ctx);
+ rc = cxl_reset_restore_cache_policy(pdev, dvsec);
+ if (rc) {
+ cxl_reset_save_disabled_state(pdev);
+ goto out_unlock;
+ }
-out_enable_cache:
- /*
- * DISABLE_CACHING was the first preparation step. Restore the original
- * cache policy last, after reset exclusion has ended.
- */
- rc2 = cxl_reset_update_ctrl2_no_replay(
- pdev, dvsec, 0, PCI_DVSEC_CXL_DISABLE_CACHING);
- if (rc2)
- pci_err(pdev, "failed to re-enable CXL caching: %d\n", rc2);
+ rc = cxl_reset_save_restored_state(pdev, command);
+ goto out_unlock;
+
+out_fail_closed:
+ cxl_reset_save_disabled_state(pdev);
+ pci_dev_reset_iommu_done(pdev);
+ rc2 = cxl_reset_restore_cache_policy(pdev, dvsec);
rc = rc ?: rc2;
+ goto out_unlock;
+
+out_destroy_enable_cache:
+ cxl_hdm_range_context_destroy(&range_ctx);
+ rc2 = cxl_reset_restore_cache_policy(pdev, dvsec);
+ rc = rc ?: rc2;
+ goto out_unlock;
+
+out_destroy_ranges:
+ cxl_hdm_range_context_destroy(&range_ctx);
+out_unlock:
+ kfree(snapshot);
+ up_write(&cxl_rwsem.region);
return rc;
}
int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
- struct cxl_hdm_range_context range_ctx;
int dvsec, rc;
u16 cap, ctrl;
@@ -873,15 +1257,5 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (!cpu_cache_has_invalidate_memregion())
return -ENOTTY;
- INIT_LIST_HEAD(&range_ctx.ranges);
-
- scoped_guard(rwsem_write, &cxl_rwsem.region) {
- rc = cxl_hdm_ranges_prepare(&range_ctx, pdev);
- if (!rc)
- rc = cxl_reset_execute(pdev, dvsec, cap, &range_ctx);
- else
- cxl_hdm_range_context_destroy(&range_ctx);
- }
-
- return rc;
+ return cxl_reset_execute(pdev, dvsec, cap);
}
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 14/15] PCI/CXL: Expose CXL Reset as a PCI reset method
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (12 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 13/15] cxl: Restore CXL state after PCI reset Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
2026-09-22 8:39 ` [PATCH v13 15/15] PCI/CXL: Restore CXL state after CXL bus reset Srirangan Madhavan
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
Register cxl_reset with the PCI reset-method interface and place it before
FLR, which does not reset CXL.cache or CXL.mem protocol state.
Return -ENOTTY when device capabilities, function scope, cached HDM state,
or required cache synchronization do not permit a safe reset.
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/resource.c | 8 ++++----
drivers/pci/pci.c | 2 ++
include/linux/pci.h | 2 +-
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 79ed12ecea81..6bf503e498e1 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -1225,9 +1225,9 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, &cap);
if (rc)
- return pcibios_err_to_errno(rc);
+ return probe ? -ENOTTY : pcibios_err_to_errno(rc);
if (PCI_POSSIBLE_ERROR(cap))
- return -ENODEV;
+ return probe ? -ENOTTY : -ENODEV;
if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
!(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
!(cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
@@ -1236,9 +1236,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;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index fe611340f5ab..dc57a202c91f 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -35,6 +35,7 @@
#include <linux/aer.h>
#include <linux/bitfield.h>
#include <linux/suspend.h>
+#include <cxl/cxl.h>
#include "pci.h"
DEFINE_MUTEX(pci_slot_mutex);
@@ -5148,6 +5149,7 @@ const struct pci_reset_fn_method pci_reset_fn_methods[] = {
{ },
{ pci_dev_specific_reset, .name = "device_specific" },
{ pci_dev_acpi_reset, .name = "acpi" },
+ { cxl_reset_function, .name = "cxl_reset" },
{ pcie_reset_flr, .name = "flr" },
{ pci_af_flr, .name = "af_flr" },
{ pci_pm_reset, .name = "pm" },
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 7bb37fcb556d..743b57ff885a 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -52,7 +52,7 @@
PCI_STATUS_PARITY)
/* Number of reset methods used in pci_reset_fn_methods array in pci.c */
-#define PCI_NUM_RESET_METHODS 8
+#define PCI_NUM_RESET_METHODS 9
#define PCI_RESET_PROBE true
#define PCI_RESET_DO_RESET false
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH v13 15/15] PCI/CXL: Restore CXL state after CXL bus reset
2026-09-22 8:39 [PATCH v13 00/15] PCI/CXL: Add CXL reset support for Type 2 devices Srirangan Madhavan
` (13 preceding siblings ...)
2026-09-22 8:39 ` [PATCH v13 14/15] PCI/CXL: Expose CXL Reset as a PCI reset method Srirangan Madhavan
@ 2026-09-22 8:39 ` Srirangan Madhavan
14 siblings, 0 replies; 16+ messages in thread
From: Srirangan Madhavan @ 2026-09-22 8:39 UTC (permalink / raw)
To: Alison Schofield, Bjorn Helgaas, Dave Jiang, Davidlohr Bueso,
Ira Weiny, Jonathan Cameron, Vishal Verma, linux-cxl, linux-pci,
linux-kernel
Cc: Alex Williamson, vsethi, alwilliamson,
Sai Yashwanth Reddy Kancherla, Vishal Aslot, Manish Honap,
Jiandi An, Richard Cheng, linux-tegra, Srirangan Madhavan
CXL bus reset can clear HDM decoder programming and CXL Device DVSEC
control state. Restore the cached state after a successful cxl_bus reset
while IOMMU exclusion remains active.
Restore PCI configuration first so HDM MMIO is accessible, and preserve a
disabled state if restoration fails.
Tested-by: Richard Cheng <icheng@nvidia.com>
Signed-off-by: Srirangan Madhavan <smadhavan@nvidia.com>
---
drivers/cxl/core/resource.c | 23 +++++++++++++++++++++--
drivers/pci/pci.c | 8 ++++++++
include/cxl/cxl.h | 17 +++++++++++++++++
3 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 6bf503e498e1..0af0f96d5526 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -601,12 +601,12 @@ static int cxl_restore_hdm_decoder(struct pci_dev *pdev, void __iomem *hdm,
return 0;
}
-static int cxl_snapshot_hdm(struct pci_dev *pdev,
- struct cxl_hdm_info **snapshot)
+int cxl_snapshot_hdm(struct pci_dev *pdev, struct cxl_hdm_info **snapshot)
{
struct cxl_hdm_info *info;
size_t size;
+ device_lock_assert(&pdev->dev);
guard(rwsem_read)(&cxl_rwsem.dpa);
info = pdev->hdm;
if (!info)
@@ -762,6 +762,25 @@ static int cxl_reset_save_restored_state(struct pci_dev *pdev, u16 command)
return rc;
}
+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);
+ guard(rwsem_write)(&cxl_rwsem.region);
+
+ cxl_restore_pci_state_for_hdm_restore(pdev, &command);
+ rc = cxl_restore_state(pdev, snapshot);
+ if (rc) {
+ cxl_reset_save_disabled_state(pdev);
+ return rc;
+ }
+
+ 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 dc57a202c91f..6928c99aa785 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 pci_dev *bridge;
u16 dvsec, reg, val;
int rc;
@@ -5037,6 +5038,10 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
if (rc)
return -ENOTTY;
+ rc = cxl_snapshot_hdm(dev, &snapshot);
+ if (rc && rc != -ENXIO)
+ return rc;
+
rc = pci_dev_reset_iommu_prepare(dev);
if (rc) {
pci_err(dev, "failed to stop IOMMU for a PCI reset: %d\n", rc);
@@ -5057,6 +5062,9 @@ static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
pci_write_config_word(bridge, dvsec + PCI_DVSEC_CXL_PORT_CTL,
reg);
+ if (!rc && snapshot)
+ rc = cxl_restore_state_after_pci_reset(dev, snapshot);
+
pci_dev_reset_iommu_done(dev);
return rc;
}
diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
index a07dbf7463c0..2ff5bb117942 100644
--- a/include/cxl/cxl.h
+++ b/include/cxl/cxl.h
@@ -202,6 +202,9 @@ struct cxl_register_map {
#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_snapshot_hdm(struct pci_dev *pdev, struct cxl_hdm_info **snapshot);
+int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot);
int cxl_reset_function(struct pci_dev *pdev, bool probe);
#else
static inline void pci_cxl_hdm_cache_init(struct pci_dev *pdev)
@@ -212,6 +215,20 @@ static inline void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
{
}
+static inline int cxl_snapshot_hdm(struct pci_dev *pdev,
+ struct cxl_hdm_info **snapshot)
+{
+ *snapshot = NULL;
+ return -ENXIO;
+}
+
+static inline int
+cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
+ struct cxl_hdm_info *snapshot)
+{
+ return 0;
+}
+
static inline int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
return -ENOTTY;
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread