[RFC PATCH v2 2/5] PCI/CXL: Populate and insert/remove pdev->coh_resource[]

From: Ankit Agrawal

Date: Thu Nov 05 2026 - 10:40:22 EST


On systems with precommitted HDM decoders, the firmware programs
the decoders before any software runs. This range also stays constant
over the runs.

Snapshot precommitted CXL HDM decoder ranges into a new
pdev->coh_resource[] array, so later code has a known-good source for
a device coherent (CXL) windows without re-scanning decoder
registers.

Only decoders that are enabled and locked are recorded, since an
unlocked decoder can be reprogrammed and leave the snapshot stale.
Zero-sized decoders are skipped. Decoders found to use Normalized
Addressing (a DPA rather than an HPA) are dropped later, once the flag is
known in a follow-up patch.

Signed-off-by: Ankit Agrawal <ankita@xxxxxxxxxx>
---
drivers/cxl/core/resource.c | 60 ++++++++++++++++++++++++++++++++++++-
include/linux/pci.h | 14 +++++++++
2 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/drivers/cxl/core/resource.c b/drivers/cxl/core/resource.c
index 3422139ae3ab..d837f8e952a9 100644
--- a/drivers/cxl/core/resource.c
+++ b/drivers/cxl/core/resource.c
@@ -404,6 +404,54 @@ static struct cxl_hdm_info *cxl_pci_hdm_read_info(struct pci_dev *pdev,
return ERR_PTR(pcibios_err_to_errno(rc));
}

+/*
+ * Snapshot committed decoders into pdev->coh_resource[] which is called
+ * once per pdev before any driver can bind, so a populated entry is
+ * always firmware-committed and not driver-committed.
+ *
+ * Insert each entry into iomem_resource for /proc/iomem visibility and
+ * conflict detection.
+ */
+static void cxl_populate_coh_resource(struct pci_dev *pdev,
+ struct cxl_hdm_info *info)
+{
+ int count = min(info->decoder_count, PCI_CXL_MAX_COHERENT_RANGES);
+
+ for (int i = 0; i < count; i++) {
+ struct cxl_decoder_config *config = &info->settings[i].config;
+ struct resource *res = &pdev->coh_resource[i];
+ struct resource *conflict;
+ u64 size = range_len(&config->hpa_range);
+
+ /*
+ * Only a decoder that is both enabled and locked has a fixed
+ * mapping that won't change under the snapshot. Also skip a
+ * zero-sized decoder, whose resource end would wrap.
+ */
+ if ((config->flags &
+ (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK)) !=
+ (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK) ||
+ !size)
+ continue;
+
+ *res = DEFINE_RES_NAMED_DESC(config->hpa_range.start, size,
+ "CXL coherent memory",
+ IORESOURCE_MEM, IORES_DESC_NONE);
+
+ conflict = insert_resource_conflict(&iomem_resource, res);
+ if (conflict) {
+ pci_warn(pdev,
+ "CXL coherent range: decoder%d %pR conflicts with %s %pR, leaving unpopulated\n",
+ i, res, conflict->name, conflict);
+ memset(res, 0, sizeof(*res));
+ continue;
+ }
+
+ pci_info(pdev, "CXL coherent range: decoder%d precommitted, populated %pR\n",
+ i, res);
+ }
+}
+
static int __pci_cxl_hdm_cache_init(struct pci_dev *pdev)
{
struct cxl_register_map map = { };
@@ -442,8 +490,10 @@ static int __pci_cxl_hdm_cache_init(struct pci_dev *pdev)
struct cxl_hdm_info *info __free(kfree) = read_info;
guard(rwsem_write)(&cxl_rwsem.dpa);
/* Another initializer may have published while we read MMIO. */
- if (!pdev->hdm)
+ if (!pdev->hdm) {
+ cxl_populate_coh_resource(pdev, info);
pdev->hdm = no_free_ptr(info);
+ }

return 0;
}
@@ -466,6 +516,14 @@ void pci_cxl_hdm_cache_release(struct pci_dev *pdev)
info = pdev->hdm;
/* Unpublish before freeing so subsequent readers cannot use stale state. */
pdev->hdm = NULL;
+
+ for (int i = 0; i < PCI_CXL_MAX_COHERENT_RANGES; i++) {
+ struct resource *res = &pdev->coh_resource[i];
+
+ if (res->parent)
+ remove_resource(res);
+ }
+
kfree(info);
}

diff --git a/include/linux/pci.h b/include/linux/pci.h
index 0d24fc65c049..ad7917895f4c 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -148,6 +148,13 @@ enum {
DEVICE_COUNT_RESOURCE = PCI_NUM_RESOURCES,
};

+/*
+ * Max CXL HDM decoders tracked per endpoint in pci_dev->coh_resource[].
+ * CXL 3.1 spec section 8.2.4.20.1 - CXL HDM Decoder Capability Register,
+ * mention a max of 10 decoders for a CXL device.
+ */
+#define PCI_CXL_MAX_COHERENT_RANGES 10
+
/**
* enum pci_interrupt_pin - PCI INTx interrupt values
* @PCI_INTERRUPT_UNKNOWN: Unknown or unassigned interrupt
@@ -573,6 +580,13 @@ struct pci_dev {
#endif
#ifdef CONFIG_CXL_RESET
struct cxl_hdm_info *hdm; /* CXL HDM decoder state */
+ /*
+ * Precommitted CXL HDM decoder coherent ranges, snapshotted before
+ * any driver binds. One entry per decoder index. Only written by
+ * pci_cxl_hdm_cache_init()/_release(), outside any bound driver's
+ * read window.
+ */
+ struct resource coh_resource[PCI_CXL_MAX_COHERENT_RANGES];
#endif
#ifdef CONFIG_PCI_NPEM
struct npem *npem; /* Native PCIe Enclosure Management */
--
2.53.0