[PATCH v3 5/9] cxl/region: Allow mixed-granularity regions

Alison Schofield <[email protected]> Thu, 30 Jul 2026 15:20:25 -0700
Newsgroups org.kernel.vger.linux-cxl
Message-ID <93bd16e6a327fcd037b5f1206c7bc15f5aa9b305.1785444498.git.alison.schofield@intel.com>
The sysfs granularity rule required region_gran == root_gran whenever
the root decoder interleaves. That blocked the legal mixed-granularity
layouts permitted by CXL Spec 4.0 Section 9.13.1 and made every 6-way
and 12-way configuration in Tables 9-6, 9-7, and 9-8 impossible to
create. Auto regions skipped the sysfs path entirely and so had no
equivalent gate.

Replace the equality rule with is_ig_allowed(), which permits any
region granularity not greater than the root's, and apply it at attach
time so auto regions get the same rule. Add a ways*gran span identity
check for 3-way-family roots: a 3-way interleave consumes no HPA
selector bits, so selector containment cannot prove that the root and
region cover the same address span, and the identity restores that
constraint. Power-of-2 roots get the equivalent constraint from
selector containment for free.

Signed-off-by: Alison Schofield <[email protected]>
---
 drivers/cxl/core/region.c | 53 +++++++++++++++++++++++++++++++--------
 1 file changed, 43 insertions(+), 10 deletions(-)

diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 938781c9bc5c..3919f64e2467 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -559,10 +559,27 @@ static ssize_t interleave_granularity_show(struct device *dev,
 	return sysfs_emit(buf, "%d\n", p->interleave_granularity);
 }
 
+/**
+ * is_ig_allowed() - Check region granularity against the root decoder
+ * @cxlrd: root decoder
+ * @ig: proposed region granularity
+ *
+ * Return: true when the root does not interleave or @ig is no greater than
+ * the root decoder granularity.
+ */
+static inline bool is_ig_allowed(struct cxl_root_decoder *cxlrd, int ig)
+{
+	struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
+
+	if (cxld->interleave_ways <= 1)
+		return true;
+
+	return ig <= cxld->interleave_granularity;
+}
+
 static int set_interleave_granularity(struct cxl_region *cxlr, int val)
 {
 	struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
-	struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
 	struct cxl_region_params *p = &cxlr->params;
 	int rc;
 	u16 ig;
@@ -571,15 +588,7 @@ static int set_interleave_granularity(struct cxl_region *cxlr, int val)
 	if (rc)
 		return rc;
 
-	/*
-	 * When the host-bridge is interleaved, disallow region granularity !=
-	 * root granularity. Regions with a granularity less than the root
-	 * interleave result in needing multiple endpoints to support a single
-	 * slot in the interleave (possible to support in the future). Regions
-	 * with a granularity greater than the root interleave result in invalid
-	 * DPA translations (invalid to support).
-	 */
-	if (cxld->interleave_ways > 1 && val != cxld->interleave_granularity)
+	if (!is_ig_allowed(cxlrd, val))
 		return -EINVAL;
 
 	lockdep_assert_held_write(&cxl_rwsem.region);
@@ -2278,6 +2287,8 @@ static int cxl_region_attach(struct cxl_region *cxlr,
 	struct cxl_dport *dport;
 	int rc = -ENXIO;
 	int root_pos_per_target;
+	int root_ways = cxlrd->cxlsd.cxld.interleave_ways;
+	int root_gran = cxlrd->cxlsd.cxld.interleave_granularity;
 
 	rc = check_interleave_cap(&cxled->cxld, p->interleave_ways,
 				  p->interleave_granularity);
@@ -2310,6 +2321,28 @@ static int cxl_region_attach(struct cxl_region *cxlr,
 		return -ENXIO;
 	}
 
+	if (!is_ig_allowed(cxlrd, p->interleave_granularity)) {
+		dev_dbg(&cxlr->dev,
+			"ig %d incompatible with root ways %d ig %d\n",
+			p->interleave_granularity, root_ways, root_gran);
+		return -ENXIO;
+	}
+
+	/*
+	 * A 3-way-family root contributes no selector bits, so selector
+	 * validation cannot confirm that the root and region cover the same
+	 * interleave span. Check the span explicitly.
+	 */
+	if (!is_power_of_2(root_ways) &&
+	    (u64)p->interleave_ways * p->interleave_granularity !=
+	    (u64)root_ways * root_gran) {
+		dev_dbg(&cxlr->dev,
+			"region ways*gran (%d*%d) != root ways*gran (%d*%d)\n",
+			p->interleave_ways, p->interleave_granularity,
+			root_ways, root_gran);
+		return -ENXIO;
+	}
+
 	if (p->nr_targets >= p->interleave_ways) {
 		dev_dbg(&cxlr->dev, "region already has %d endpoints\n",
 			p->nr_targets);
-- 
2.37.3