A Secondary Bus Reset of a CXL Downstream Port removes the downstream
component from the bus, so a CXL region reached through that Port has to
be disabled while the reset runs.

Add cxl_region_disable() to offline memory, invalidate the CPU caches
for its range and release its driver, and cxl_region_enable() to
re-attach the driver afterwards.

Neither function has a caller yet. Both are driven by the reset once the
set of regions routed through the Port is known.

Signed-off-by: Fabio M. De Francesco <[email protected]>
---
 drivers/cxl/core/core.h      |  3 ++
 drivers/cxl/core/dport_sbr.c | 79 ++++++++++++++++++++++++++++++++++++
 drivers/cxl/core/region.c    |  3 +-
 3 files changed, 84 insertions(+), 1 deletion(-)

diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
index a8b9d7990d32..ac55f9e8160e 100644
--- a/drivers/cxl/core/core.h
+++ b/drivers/cxl/core/core.h
@@ -56,6 +56,9 @@ u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, const struct 
cxl_memdev *cxlmd,
 int devm_cxl_add_dax_region(struct cxl_region *cxlr);
 int devm_cxl_add_pmem_region(struct cxl_region *cxlr);
 void kill_regions(struct cxl_root_decoder *cxlrd);
+int cxl_region_invalidate_memregion(struct cxl_region *cxlr);
+int cxl_region_disable(struct cxl_region *cxlr);
+void cxl_region_enable(struct cxl_region *cxlr);
 struct pci_dev;
 void cxl_sbr_recommit_decoders(struct pci_dev *dport_pci,
                               struct xarray *hdm_state);
diff --git a/drivers/cxl/core/dport_sbr.c b/drivers/cxl/core/dport_sbr.c
index 55c35386d0cb..0f398ab0d3a4 100644
--- a/drivers/cxl/core/dport_sbr.c
+++ b/drivers/cxl/core/dport_sbr.c
@@ -1,11 +1,90 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright(c) 2026 Intel Corporation. */
 
+#include <linux/memregion.h>
+#include <linux/memory_hotplug.h>
+#include <linux/memory.h>
 #include <linux/device.h>
 #include <linux/pci.h>
 #include <cxl.h>
 #include "core.h"
 
+/*
+ * cxl_region_disable - make a region inactive ahead of a Secondary Bus Reset
+ * @cxlr: region routed through the CXL Downstream Port being reset
+ *
+ * Offline the memory blocks the region owns and unbind its driver. An SBR
+ * zeroes the downstream bus number, so a region left live as System RAM would
+ * be accessed while the device is in reset. On offline failure return the 
error
+ * so the caller aborts the reset; the memory is never force-removed.
+ *
+ * Context: process context. Offlining and driver unbind sleep and take the
+ * memory hotplug lock, so this cannot run in atomic context.
+ */
+int cxl_region_disable(struct cxl_region *cxlr)
+{
+       struct cxl_region_params *p = &cxlr->params;
+       unsigned long block_size;
+       u64 start, end;
+       int rc;
+
+       /*
+        * Per CXL r4.0 sec 9.13.1 an Interleave Set has a Base HPA and a Size
+        * that are multiples of 256 MB, while a memory block spans up to 2 GB.
+        * A block overlapping either end of the range therefore also covers
+        * memory outside this region, so round the range inward to block
+        * granularity as dax_kmem did when it onlined the range. Offlining a
+        * straddling block would migrate pages that the reset does not affect.
+        */
+       block_size = memory_block_size_bytes();
+       start = ALIGN(p->res->start, block_size);
+       end = ALIGN_DOWN(p->res->end + 1, block_size);
+       if (start >= end) {
+               dev_dbg(&cxlr->dev, "%s: HPA %pr spans no whole memory block, 
no System RAM to offline\n",
+                       __func__, p->res);
+       } else {
+               rc = cxl_offline_memory(start, end - start);
+               if (rc) {
+                       dev_warn(&cxlr->dev, "offline System RAM failed before 
reset: %d\n",
+                                rc);
+                       return rc;
+               }
+       }
+
+       rc = cxl_region_invalidate_memregion(cxlr);
+       if (rc) {
+               dev_warn(&cxlr->dev, "CPU cache invalidate failed before reset: 
%d\n",
+                        rc);
+               return rc;
+       }
+
+       device_release_driver(&cxlr->dev);
+       dev_dbg(&cxlr->dev, "%s: System RAM offline, region disabled before 
reset, HPA %pr\n",
+               __func__, p->res);
+
+       return 0;
+}
+
+/*
+ * cxl_region_enable - restore a region after a Secondary Bus Reset
+ * @cxlr: region disabled by cxl_region_disable() before the reset
+ *
+ * Rebind the region driver. The System RAM is left offline; bringing it back
+ * online is a separate administrative step.
+ */
+void cxl_region_enable(struct cxl_region *cxlr)
+{
+       struct cxl_region_params *p = &cxlr->params;
+
+       if (device_attach(&cxlr->dev) < 0) {
+               dev_dbg(&cxlr->dev, "driver re-attach failed after reset\n");
+               return;
+       }
+
+       dev_dbg(&cxlr->dev, "%s: region re-enabled after reset, HPA %pr, IW %d, 
IG %d\n",
+               __func__, p->res, p->interleave_ways, 
p->interleave_granularity);
+}
+
 /*
  * The reset cleared the HDM Decoder registers of every CXL component below
  * @dport_pci, so restore them from the settings the driver holds and from
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 1e211542b6b6..fc0bec991a69 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -222,7 +222,7 @@ static struct cxl_region_ref *cxl_rr_load(struct cxl_port 
*port,
        return xa_load(&port->regions, (unsigned long)cxlr);
 }
 
-static int cxl_region_invalidate_memregion(struct cxl_region *cxlr)
+int cxl_region_invalidate_memregion(struct cxl_region *cxlr)
 {
        if (!cpu_cache_has_invalidate_memregion()) {
                if (IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST)) {
@@ -4273,4 +4273,5 @@ void cxl_region_exit(void)
 
 MODULE_IMPORT_NS("CXL");
 MODULE_IMPORT_NS("DEVMEM");
+MODULE_IMPORT_NS("CXL_MHP");
 MODULE_ALIAS_CXL(CXL_DEVICE_REGION);
-- 
2.55.0


Reply via email to