[PATCH v7 06/11] cxl: Validate HDM ranges before CXL reset

From: Srirangan Madhavan

Date: Mon Jun 22 2026 - 23:28:08 EST


Before reset, collect enabled cached HDM decoder ranges, reserve them with
request_mem_region(), and invalidate CPU caches. This rejects reset while
affected CXL memory is busy and keeps the validation stable through reset.

Signed-off-by: Srirangan Madhavan <smadhavan@xxxxxxxxxx>
---
drivers/cxl/core/reset.c | 239 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 238 insertions(+), 1 deletion(-)

diff --git a/drivers/cxl/core/reset.c b/drivers/cxl/core/reset.c
index fdfcc9e825e0..786d1060e40d 100644
--- a/drivers/cxl/core/reset.c
+++ b/drivers/cxl/core/reset.c
@@ -10,6 +10,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>

@@ -336,6 +338,230 @@ 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;
+ struct resource *res;
+};
+
+struct cxl_hdm_range_context {
+ struct list_head ranges;
+};
+
+static void cxl_hdm_range_context_init(struct cxl_hdm_range_context *ctx)
+{
+ INIT_LIST_HEAD(&ctx->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);
+ }
+}
+
+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;
+
+ if (hpa_range->end < hpa_range->start)
+ return -EINVAL;
+
+ 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;
+ 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 = READ_ONCE(pdev->hdm);
+ int rc;
+
+ 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_settings *settings = &info->settings[i];
+
+ if (!(settings->flags & CXL_DECODER_F_ENABLE))
+ continue;
+
+ if (settings->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, &settings->hpa_range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_range_len(struct pci_dev *pdev,
+ const struct range *hpa_range, u64 *len)
+{
+ if (sizeof(resource_size_t) < sizeof(hpa_range->start) &&
+ (hpa_range->start > (resource_size_t)~0ULL ||
+ hpa_range->end > (resource_size_t)~0ULL)) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource address size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (hpa_range->end < hpa_range->start)
+ return -EINVAL;
+
+ if (!hpa_range->start && hpa_range->end == U64_MAX) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ *len = range_len(hpa_range);
+ if (sizeof(resource_size_t) < sizeof(*len) &&
+ *len > (resource_size_t)~0ULL) {
+ pci_err(pdev,
+ "CXL reset range [%#llx-%#llx] exceeds resource size\n",
+ hpa_range->start, hpa_range->end);
+ return -EOVERFLOW;
+ }
+
+ if (sizeof(size_t) < sizeof(*len) && *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_request(struct cxl_hdm_range *range)
+{
+ struct pci_dev *pdev = range->pdev;
+ const struct range *hpa_range = &range->hpa_range;
+ u64 len;
+ int rc;
+
+ rc = cxl_hdm_range_len(pdev, hpa_range, &len);
+ if (rc)
+ return rc;
+
+ range->res = request_mem_region(hpa_range->start, len, "cxl_reset");
+ if (!range->res) {
+ pci_err(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_request(struct cxl_hdm_range_context *ctx)
+{
+ struct cxl_hdm_range *range;
+ int rc;
+
+ lockdep_assert_held_write(&cxl_rwsem.region);
+
+ list_for_each_entry(range, &ctx->ranges, list) {
+ rc = cxl_hdm_range_request(range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cxl_hdm_range_flush_cache(struct cxl_hdm_range *range)
+{
+ struct pci_dev *pdev = range->pdev;
+ const struct range *hpa_range = &range->hpa_range;
+ u64 len;
+ int rc;
+
+ rc = cxl_hdm_range_len(pdev, hpa_range, &len);
+ if (rc)
+ return rc;
+
+ rc = cpu_cache_invalidate_memregion(hpa_range->start, len);
+ if (rc)
+ pci_err(pdev,
+ "failed to invalidate CPU cache [%#llx-%#llx]: %d\n",
+ hpa_range->start, hpa_range->end, rc);
+
+ return rc;
+}
+
+static int cxl_hdm_ranges_flush_cpu_caches(struct cxl_hdm_range_context *ctx,
+ struct pci_dev *pdev)
+{
+ struct cxl_hdm_range *range;
+ int rc;
+
+ if (list_empty(&ctx->ranges))
+ return 0;
+
+ if (!cpu_cache_has_invalidate_memregion()) {
+ pci_err(pdev, "failed to synchronize CPU cache state\n");
+ return -ENXIO;
+ }
+
+ list_for_each_entry(range, &ctx->ranges, list) {
+ rc = cxl_hdm_range_flush_cache(range);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+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);
+
+ 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_flush_cpu_caches(ctx, pdev);
+}
+
static int cxl_reset_dvsec(struct pci_dev *pdev)
{
int dvsec, rc;
@@ -528,7 +754,9 @@ static int cxl_reset_execute(struct pci_dev *pdev, int dvsec)

int cxl_reset_function(struct pci_dev *pdev, bool probe)
{
+ struct cxl_hdm_range_context range_ctx;
int dvsec;
+ int rc;

dvsec = cxl_reset_dvsec(pdev);
if (dvsec < 0)
@@ -537,5 +765,14 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
if (probe)
return 0;

- return cxl_reset_execute(pdev, dvsec);
+ cxl_hdm_range_context_init(&range_ctx);
+
+ scoped_guard(rwsem_write, &cxl_rwsem.region) {
+ rc = cxl_hdm_ranges_prepare(&range_ctx, pdev);
+ if (!rc)
+ rc = cxl_reset_execute(pdev, dvsec);
+ }
+
+ cxl_hdm_range_context_destroy(&range_ctx);
+ return rc;
}
--
2.43.0