* [PATCH 0/6] cxl: Support mixed-granularity region interleaves
@ 2026-05-30 0:00 Alison Schofield
2026-05-30 0:00 ` [PATCH 1/6] cxl/region: Validate interleave selector bits Alison Schofield
` (5 more replies)
0 siblings, 6 replies; 15+ messages in thread
From: Alison Schofield @ 2026-05-30 0:00 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
A CXL region interleaves across decoder levels (root, optional
switches, endpoint). CXL Spec 4.0 Section 9.13.1 requires only
that each level use a different, consecutive range of HPA bits to
select its target. The driver has historically required equal
region and root decoder granularities. That blocks the legal
mixed-granularity arrangements permitted by Section 9.13.1, and
makes the 6-way and 12-way configurations defined in Section
9.13.1.1 (Tables 9-6, 9-7, and 9-8) impossible to create.
Two prior proposals addressed parts of this gap:
AlisonS added position arithmetic and sysfs gating to allow
auto and user-created regions for the 6-way and 12-way
configurations that have no same-granularity alternative:
https://lore.kernel.org/all/20250306232239.2609017-1-alison.schofield@intel.com/
RobertR introduced an HPA selector-bit model to allow multi-level
regions regardless of granularity ordering for auto regions:
https://lore.kernel.org/all/20251028094754.72816-1-rrichter@amd.com/
This series combines those two approaches into a complete mixed-
granularity implementation. It extends Robert's selector-bit model
from auto regions to user-created regions, extends AlisonS's
position-arithmetic and gating work to all mixed-gran layouts, and
adds the remaining validation needed for both paths. The result is
support for every mixed-granularity arrangement defined by the CXL
specification.
Series structure
----------------
Patches 1 and 2 replace the old granularity ordering rule with
selector-bit validation. Patches 3 and 4 propagate that model through
the remaining region-creation paths. Patch 5 adds cxl_test coverage
for the new layouts. Patch 6 documents the region granularity model
and multi-level interleaving rules.
A companion NDCTL patchset that allows mixed-gran 'cxl create-region'
and adds the unit test is posted separately.
Alison Schofield (6):
cxl/region: Validate interleave selector bits
cxl/region: Derive port granularity from selector bits
cxl/region: Account for mixed-granularity in position calculations
cxl/region: Validate mixed-granularity at sysfs and attach gates
cxl/test: Add a topology to test mixed-granularity regions
Documentation/cxl: Add region granularity and multi-level interleave
guide
Documentation/driver-api/cxl/index.rst | 1 +
.../cxl/linux/region-granularity.rst | 486 ++++++++++++++++++
drivers/cxl/core/region.c | 314 +++++++----
tools/testing/cxl/test/cxl.c | 474 ++++++++++++++++-
4 files changed, 1146 insertions(+), 129 deletions(-)
create mode 100644 Documentation/driver-api/cxl/linux/region-granularity.rst
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
--
2.37.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/6] cxl/region: Validate interleave selector bits
2026-05-30 0:00 [PATCH 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
@ 2026-05-30 0:00 ` Alison Schofield
2026-05-30 0:19 ` sashiko-bot
2026-05-30 0:00 ` [PATCH 2/6] cxl/region: Derive port granularity from " Alison Schofield
` (4 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Alison Schofield @ 2026-05-30 0:00 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
Multi-level interleaves are valid when each decoder level claims a
distinct portion of the region HPA selector. The existing setup is
too restrictive as it requires that each level's granularity must
equal the parent granularity multiplied by the parent ways. That
forces the root to sit at the innermost selector bit and rejects
legal layouts where the root claims an outer bit.
Add selector-bit accounting to cxl_port_setup_targets(). Accumulate
selector bits from the root toward the current port, reject overlap,
and reject any accumulated selector that does not fit within the
region selector.
Preserve the existing same-granularity behavior using values derived
from the selector walk.
Remove the auto-region granularity equality check that duplicates
the old ordering rule. The selector walk now validates auto region
granularities.
Block mixed-granularity attachments until position arithmetic is
updated by a later patch.
Originally-by: Robert Richter <rrichter@amd.com>
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 196 +++++++++++++++++++-------------------
1 file changed, 99 insertions(+), 97 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index e50dc716d4e8..87fb1a9f2683 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1423,12 +1423,34 @@ static int check_interleave_cap(struct cxl_decoder *cxld, int iw, int ig)
return 0;
}
+/**
+ * get_selector() - Return the HPA selector mask for one level
+ * @ways: interleave ways
+ * @gran: interleave granularity in bytes
+ *
+ * Return: ``(ways - 1) * gran`` for power-of-2 interleaves, or the
+ * selector for the power-of-2 component of a 6-way or 12-way interleave.
+ *
+ * A 3-way interleave consumes no HPA selector bits.
+ */
+static inline u64 get_selector(u64 ways, u64 gran)
+{
+ if (!is_power_of_2(ways))
+ ways /= 3;
+
+ if (!is_power_of_2(ways) || !is_power_of_2(gran))
+ return 0;
+
+ return (ways - 1) * gran;
+}
+
static int cxl_port_setup_targets(struct cxl_port *port,
struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled)
{
struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
- int parent_iw, parent_ig, ig, iw, rc, pos = cxled->pos;
+ int distance, parent_distance;
+ int ig, iw, rc, pos = cxled->pos;
struct cxl_port *parent_port = to_cxl_port(port->dev.parent);
struct cxl_region_ref *cxl_rr = cxl_rr_load(port, cxlr);
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
@@ -1436,9 +1458,10 @@ static int cxl_port_setup_targets(struct cxl_port *port,
struct cxl_region_params *p = &cxlr->params;
struct cxl_decoder *cxld = cxl_rr->decoder;
struct cxl_switch_decoder *cxlsd;
- struct cxl_port *iter = port;
- u16 eig, peig;
- u8 eiw, peiw;
+ u64 selector, cxlr_sel;
+ struct cxl_port *iter;
+ u16 eig;
+ u8 eiw;
/*
* While root level decoders support x3, x6, x12, switch level
@@ -1452,28 +1475,69 @@ static int cxl_port_setup_targets(struct cxl_port *port,
}
cxlsd = to_cxl_switch_decoder(&cxld->dev);
+ iw = cxl_rr->nr_targets;
+
+ if (iw > 8 || iw > cxlsd->nr_targets) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s:%s: ways: %d overflows targets: %d\n",
+ dev_name(port->uport_dev), dev_name(&port->dev),
+ dev_name(&cxld->dev), iw, cxlsd->nr_targets);
+ return -ENXIO;
+ }
+
+ /* Track selector bits already claimed by ancestor decoders */
+ selector = get_selector(cxlrd->cxlsd.cxld.interleave_ways,
+ cxlrd->cxlsd.cxld.interleave_granularity);
+
+ /* Track distances used in passthrough granularity and peer checks */
+ parent_distance = cxlrd->cxlsd.cxld.interleave_ways;
+ if (!is_power_of_2(parent_distance))
+ parent_distance /= 3;
+ distance = cxlrd->cxlsd.cxld.interleave_ways;
+
+ for (iter = parent_port; !is_cxl_root(iter);
+ iter = to_cxl_port(iter->dev.parent)) {
+ struct cxl_region_ref *cxl_rr_iter = cxl_rr_load(iter, cxlr);
+ struct cxl_decoder *cxld_iter = cxl_rr_iter->decoder;
+ u64 cxld_sel;
+
+ if (cxld_iter->interleave_ways == 1)
+ continue;
+
+ cxld_sel = get_selector(cxld_iter->interleave_ways,
+ cxld_iter->interleave_granularity);
+
+ if (cxld_sel & selector) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s: overlapping selectors: %#llx:%#llx\n",
+ dev_name(iter->uport_dev),
+ dev_name(&iter->dev), cxld_sel, selector);
+ return -ENXIO;
+ }
+
+ selector |= cxld_sel;
+
+ parent_distance *= cxl_rr_iter->nr_targets;
+ distance *= cxl_rr_iter->nr_targets;
+ }
+
+ distance *= iw;
+
+ /* Accumulated bits must fit within the region selector */
+ cxlr_sel = get_selector(p->interleave_ways, p->interleave_granularity);
+ if ((cxlr_sel & selector) != selector) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s: invalid selectors: cxlr %#llx accum %#llx\n",
+ dev_name(port->uport_dev), dev_name(&port->dev),
+ cxlr_sel, selector);
+ return -ENXIO;
+ }
+
+ /* Selector bits still available to this port. */
+ selector = cxlr_sel & ~selector;
+
if (cxl_rr->nr_targets_set) {
- int i, distance = 1;
- struct cxl_region_ref *cxl_rr_iter;
-
- /*
- * The "distance" between peer downstream ports represents which
- * endpoint positions in the region interleave a given port can
- * host.
- *
- * For example, at the root of a hierarchy the distance is
- * always 1 as every index targets a different host-bridge. At
- * each subsequent switch level those ports map every Nth region
- * position where N is the width of the switch == distance.
- */
- do {
- cxl_rr_iter = cxl_rr_load(iter, cxlr);
- distance *= cxl_rr_iter->nr_targets;
- iter = to_cxl_port(iter->dev.parent);
- } while (!is_cxl_root(iter));
- distance *= cxlrd->cxlsd.cxld.interleave_ways;
-
- for (i = 0; i < cxl_rr->nr_targets_set; i++)
+ for (int i = 0; i < cxl_rr->nr_targets_set; i++)
if (ep->dport == cxlsd->target[i]) {
rc = check_last_peer(cxled, ep, cxl_rr,
distance);
@@ -1484,87 +1548,20 @@ static int cxl_port_setup_targets(struct cxl_port *port,
goto add_target;
}
- if (is_cxl_root(parent_port)) {
- /*
- * Root decoder IG is always set to value in CFMWS which
- * may be different than this region's IG. We can use the
- * region's IG here since interleave_granularity_store()
- * does not allow interleaved host-bridges with
- * root IG != region IG.
- */
- parent_ig = p->interleave_granularity;
- parent_iw = cxlrd->cxlsd.cxld.interleave_ways;
- /*
- * For purposes of address bit routing, use power-of-2 math for
- * switch ports.
- */
- if (!is_power_of_2(parent_iw))
- parent_iw /= 3;
- } else {
- struct cxl_region_ref *parent_rr;
- struct cxl_decoder *parent_cxld;
+ ig = p->interleave_granularity * parent_distance;
- parent_rr = cxl_rr_load(parent_port, cxlr);
- parent_cxld = parent_rr->decoder;
- parent_ig = parent_cxld->interleave_granularity;
- parent_iw = parent_cxld->interleave_ways;
- }
-
- rc = granularity_to_eig(parent_ig, &peig);
- if (rc) {
- dev_dbg(&cxlr->dev, "%s:%s: invalid parent granularity: %d\n",
- dev_name(parent_port->uport_dev),
- dev_name(&parent_port->dev), parent_ig);
- return rc;
- }
-
- rc = ways_to_eiw(parent_iw, &peiw);
- if (rc) {
- dev_dbg(&cxlr->dev, "%s:%s: invalid parent interleave: %d\n",
- dev_name(parent_port->uport_dev),
- dev_name(&parent_port->dev), parent_iw);
- return rc;
- }
-
- iw = cxl_rr->nr_targets;
rc = ways_to_eiw(iw, &eiw);
- if (rc) {
- dev_dbg(&cxlr->dev, "%s:%s: invalid port interleave: %d\n",
- dev_name(port->uport_dev), dev_name(&port->dev), iw);
- return rc;
- }
-
- /*
- * Interleave granularity is a multiple of @parent_port granularity.
- * Multiplier is the parent port interleave ways.
- */
- rc = granularity_to_eig(parent_ig * parent_iw, &eig);
+ if (!rc)
+ rc = granularity_to_eig(ig, &eig);
if (rc) {
dev_dbg(&cxlr->dev,
- "%s: invalid granularity calculation (%d * %d)\n",
- dev_name(&parent_port->dev), parent_ig, parent_iw);
+ "%s:%s: derived ig %d not a valid granularity (iw %d)\n",
+ dev_name(port->uport_dev), dev_name(&port->dev), ig, iw);
return rc;
}
- rc = eig_to_granularity(eig, &ig);
- if (rc) {
- dev_dbg(&cxlr->dev, "%s:%s: invalid interleave: %d\n",
- dev_name(port->uport_dev), dev_name(&port->dev),
- 256 << eig);
- return rc;
- }
-
- if (iw > 8 || iw > cxlsd->nr_targets) {
- dev_dbg(&cxlr->dev,
- "%s:%s:%s: ways: %d overflows targets: %d\n",
- dev_name(port->uport_dev), dev_name(&port->dev),
- dev_name(&cxld->dev), iw, cxlsd->nr_targets);
- return -ENXIO;
- }
-
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
if (cxld->interleave_ways != iw ||
- (iw > 1 && cxld->interleave_granularity != ig) ||
!spa_maps_hpa(p, &cxld->hpa_range) ||
((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
dev_err(&cxlr->dev,
@@ -2067,6 +2064,11 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
+ /* Keep this patch bisectable until position arithmetic is updated */
+ if (cxlrd->cxlsd.cxld.interleave_granularity >
+ p->interleave_granularity)
+ 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
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/6] cxl/region: Derive port granularity from selector bits
2026-05-30 0:00 [PATCH 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
2026-05-30 0:00 ` [PATCH 1/6] cxl/region: Validate interleave selector bits Alison Schofield
@ 2026-05-30 0:00 ` Alison Schofield
2026-05-30 0:26 ` sashiko-bot
2026-06-03 23:51 ` Dave Jiang
2026-05-30 0:00 ` [PATCH 3/6] cxl/region: Account for mixed-granularity in position calculations Alison Schofield
` (3 subsequent siblings)
5 siblings, 2 replies; 15+ messages in thread
From: Alison Schofield @ 2026-05-30 0:00 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
Selector-bit validation uses a single same-granularity formula
for user-created regions. Mixed-granularity layouts need the
programmed granularity to depend on the type of decoder being
configured.
Derive port granularity from the selector state:
AUTO: keep the firmware-programmed value; the walk validated it.
iw == 1: passthrough; consume no selector bits and span the parent
stride.
iw > 1: claim the lowest selector bit still available to this
level.
This lets an interleaving switch beneath a mixed-granularity root
claim an inner selector bit instead of colliding with the outer bit
already owned by the root.
Add a final check that the derived selector fits within the
remaining selector bits.
Originally-by: Robert Richter <rrichter@amd.com>
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 87fb1a9f2683..9d310287d48d 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1548,7 +1548,23 @@ static int cxl_port_setup_targets(struct cxl_port *port,
goto add_target;
}
- ig = p->interleave_granularity * parent_distance;
+ /*
+ * Auto regions keep the firmware value, passthrough decoders consume
+ * no selector bits, interleaving decoders claim the lowest available
+ * selector bit.
+ */
+ if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
+ ig = cxld->interleave_granularity;
+ } else if (iw == 1) {
+ ig = p->interleave_granularity * parent_distance;
+ } else if (selector) {
+ ig = 1ULL << __ffs64(selector);
+ } else {
+ dev_dbg(&cxlr->dev,
+ "%s:%s: no selector bits available for iw %d\n",
+ dev_name(port->uport_dev), dev_name(&port->dev), iw);
+ return -ENXIO;
+ }
rc = ways_to_eiw(iw, &eiw);
if (!rc)
@@ -1560,6 +1576,14 @@ static int cxl_port_setup_targets(struct cxl_port *port,
return rc;
}
+ if (iw > 1 && (~selector & get_selector(iw, ig))) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s: derived selector %#llx exceeds remaining %#llx (iw %d ig %d)\n",
+ dev_name(port->uport_dev), dev_name(&port->dev),
+ get_selector(iw, ig), selector, iw, ig);
+ return -ENXIO;
+ }
+
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
if (cxld->interleave_ways != iw ||
!spa_maps_hpa(p, &cxld->hpa_range) ||
--
2.37.3
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/6] cxl/region: Account for mixed-granularity in position calculations
2026-05-30 0:00 [PATCH 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
2026-05-30 0:00 ` [PATCH 1/6] cxl/region: Validate interleave selector bits Alison Schofield
2026-05-30 0:00 ` [PATCH 2/6] cxl/region: Derive port granularity from " Alison Schofield
@ 2026-05-30 0:00 ` Alison Schofield
2026-05-30 0:00 ` [PATCH 4/6] cxl/region: Validate mixed-granularity at sysfs and attach gates Alison Schofield
` (2 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Alison Schofield @ 2026-05-30 0:00 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
Mixed-granularity regions place the root decoder at outer HPA bits.
A root target then owns root_gran / region_gran inner positions
before the root advances to the next target. That ratio becomes the
root target stride in region positions.
Account for that stride in endpoint placement, position calculation,
and peer-distance checks.
Remove the temporary mixed-granularity attach reject now that position
arithmetic matches the selector layout.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 79 ++++++++++++++++++++++++++++-----------
1 file changed, 58 insertions(+), 21 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 9d310287d48d..cc8ab2ad5228 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1444,12 +1444,34 @@ static inline u64 get_selector(u64 ways, u64 gran)
return (ways - 1) * gran;
}
+/**
+ * root_pos_stride() - Return root target stride in region positions
+ * @cxlr: region
+ *
+ * Return: root_gran / region_gran when the root granularity is larger
+ * than the region granularity, or 1 otherwise.
+ */
+static inline int root_pos_stride(struct cxl_region *cxlr)
+{
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
+ int root_gran = cxlrd->cxlsd.cxld.interleave_granularity;
+ int region_gran = cxlr->params.interleave_granularity;
+
+ if (cxlrd->cxlsd.cxld.interleave_ways <= 1)
+ return 1;
+
+ if (region_gran == 0 || root_gran <= region_gran)
+ return 1;
+
+ return root_gran / region_gran;
+}
+
static int cxl_port_setup_targets(struct cxl_port *port,
struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled)
{
struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
- int distance, parent_distance;
+ int sel_distance, pos_distance, stride;
int ig, iw, rc, pos = cxled->pos;
struct cxl_port *parent_port = to_cxl_port(port->dev.parent);
struct cxl_region_ref *cxl_rr = cxl_rr_load(port, cxlr);
@@ -1490,10 +1512,11 @@ static int cxl_port_setup_targets(struct cxl_port *port,
cxlrd->cxlsd.cxld.interleave_granularity);
/* Track distances used in passthrough granularity and peer checks */
- parent_distance = cxlrd->cxlsd.cxld.interleave_ways;
- if (!is_power_of_2(parent_distance))
- parent_distance /= 3;
- distance = cxlrd->cxlsd.cxld.interleave_ways;
+ stride = root_pos_stride(cxlr);
+ sel_distance = cxlrd->cxlsd.cxld.interleave_ways;
+ if (!is_power_of_2(sel_distance))
+ sel_distance /= 3;
+ pos_distance = stride > 1 ? 1 : cxlrd->cxlsd.cxld.interleave_ways;
for (iter = parent_port; !is_cxl_root(iter);
iter = to_cxl_port(iter->dev.parent)) {
@@ -1517,11 +1540,11 @@ static int cxl_port_setup_targets(struct cxl_port *port,
selector |= cxld_sel;
- parent_distance *= cxl_rr_iter->nr_targets;
- distance *= cxl_rr_iter->nr_targets;
+ sel_distance *= cxl_rr_iter->nr_targets;
+ pos_distance *= cxl_rr_iter->nr_targets;
}
- distance *= iw;
+ pos_distance *= iw;
/* Accumulated bits must fit within the region selector */
cxlr_sel = get_selector(p->interleave_ways, p->interleave_granularity);
@@ -1540,7 +1563,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
for (int i = 0; i < cxl_rr->nr_targets_set; i++)
if (ep->dport == cxlsd->target[i]) {
rc = check_last_peer(cxled, ep, cxl_rr,
- distance);
+ pos_distance);
if (rc)
return rc;
goto out_target_set;
@@ -1556,7 +1579,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
ig = cxld->interleave_granularity;
} else if (iw == 1) {
- ig = p->interleave_granularity * parent_distance;
+ ig = p->interleave_granularity * sel_distance;
} else if (selector) {
ig = 1ULL << __ffs64(selector);
} else {
@@ -1813,14 +1836,17 @@ static int cxl_region_attach_position(struct cxl_region *cxlr,
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_switch_decoder *cxlsd = &cxlrd->cxlsd;
struct cxl_decoder *cxld = &cxlsd->cxld;
+ int stride = root_pos_stride(cxlr);
int iw = cxld->interleave_ways;
struct cxl_port *iter;
int rc;
- if (dport != cxlrd->cxlsd.target[pos % iw]) {
- dev_dbg(&cxlr->dev, "%s:%s invalid target position for %s\n",
+ /* Mixed-granularity roots advance every stride positions */
+ if (dport != cxlrd->cxlsd.target[pos / stride % iw]) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s invalid target position for %s (stride %d, check root/region gran ratio)\n",
dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
- dev_name(&cxlrd->cxlsd.cxld.dev));
+ dev_name(&cxlrd->cxlsd.cxld.dev), stride);
return -ENXIO;
}
@@ -1951,6 +1977,7 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
* cxl_calc_interleave_pos() - calculate an endpoint position in a region
* @cxled: endpoint decoder member of given region
* @hpa_range: translated HPA range of the endpoint
+ * @stride: root target stride in region positions
*
* The endpoint position is calculated by traversing the topology from
* the endpoint to the root decoder and iteratively applying this
@@ -1960,11 +1987,16 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
*
* ...where @position is inferred from switch and root decoder target lists.
*
+ * For mixed-granularity regions the root contributes outer position
+ * bits, so the root step is:
+ *
+ * position = position + stride * parent_pos
+ *
* Return: position >= 0 on success
* -ENXIO on failure
*/
static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
- struct range *hpa_range)
+ struct range *hpa_range, int stride)
{
struct cxl_port *iter, *port = cxled_to_port(cxled);
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
@@ -2010,6 +2042,11 @@ static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
if (rc)
return rc;
+ if (stride > 1 && is_cxl_root(parent_port_of(iter))) {
+ pos = pos + stride * parent_pos;
+ break;
+ }
+
pos = pos * parent_ways + parent_pos;
}
@@ -2024,12 +2061,14 @@ static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
static int cxl_region_sort_targets(struct cxl_region *cxlr)
{
struct cxl_region_params *p = &cxlr->params;
+ int stride = root_pos_stride(cxlr);
int i, rc = 0;
for (i = 0; i < p->nr_targets; i++) {
struct cxl_endpoint_decoder *cxled = p->targets[i];
- cxled->pos = cxl_calc_interleave_pos(cxled, &cxlr->hpa_range);
+ cxled->pos = cxl_calc_interleave_pos(cxled, &cxlr->hpa_range,
+ stride);
/*
* Record that sorting failed, but still continue to calc
* cxled->pos so that follow-on code paths can reliably
@@ -2056,6 +2095,7 @@ static int cxl_region_attach(struct cxl_region *cxlr,
struct cxl_port *ep_port, *root_port;
struct cxl_dport *dport;
int rc = -ENXIO;
+ int stride;
rc = check_interleave_cap(&cxled->cxld, p->interleave_ways,
p->interleave_granularity);
@@ -2088,11 +2128,6 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
- /* Keep this patch bisectable until position arithmetic is updated */
- if (cxlrd->cxlsd.cxld.interleave_granularity >
- p->interleave_granularity)
- return -ENXIO;
-
if (p->nr_targets >= p->interleave_ways) {
dev_dbg(&cxlr->dev, "region already has %d endpoints\n",
p->nr_targets);
@@ -2214,11 +2249,13 @@ static int cxl_region_attach(struct cxl_region *cxlr,
* A fail message here means that this interleave config
* will fail when presented as CXL_REGION_F_AUTO.
*/
+ stride = root_pos_stride(cxlr);
for (int i = 0; i < p->nr_targets; i++) {
struct cxl_endpoint_decoder *cxled = p->targets[i];
int test_pos;
- test_pos = cxl_calc_interleave_pos(cxled, &cxlr->hpa_range);
+ test_pos = cxl_calc_interleave_pos(cxled, &cxlr->hpa_range,
+ stride);
dev_dbg(&cxled->cxld.dev,
"Test cxl_calc_interleave_pos(): %s test_pos:%d cxled->pos:%d\n",
(test_pos == cxled->pos) ? "success" : "fail",
--
2.37.3
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 4/6] cxl/region: Validate mixed-granularity at sysfs and attach gates
2026-05-30 0:00 [PATCH 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
` (2 preceding siblings ...)
2026-05-30 0:00 ` [PATCH 3/6] cxl/region: Account for mixed-granularity in position calculations Alison Schofield
@ 2026-05-30 0:00 ` Alison Schofield
2026-06-03 23:55 ` Dave Jiang
2026-05-30 0:00 ` [PATCH 5/6] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
2026-05-30 0:00 ` [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide Alison Schofield
5 siblings, 1 reply; 15+ messages in thread
From: Alison Schofield @ 2026-05-30 0:00 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
User-created regions are validated at sysfs write through
set_interleave_granularity(). Auto regions skip that path. Both
need the same mixed-granularity rule applied before the per-port
selector walk runs.
Replace the same-granularity sysfs check with is_ig_allowed(),
which permits any region granularity not greater than the root
granularity. Call the same helper at attach time so auto regions
get the same rule.
3-way-family roots (3, 6, and 12 ways) need an additional check.
A 3-way interleave consumes no selector bits, so selector containment
alone cannot prove that the root and region cover the same address
span. Add an explicit ways*gran span equality check in
cxl_region_attach() for these roots.
Endpoint decoder capability is still checked at attach time by
check_interleave_cap().
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 57 ++++++++++++++++++++++++++++++++-------
1 file changed, 47 insertions(+), 10 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index cc8ab2ad5228..61e6bb73249e 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -559,10 +559,29 @@ static ssize_t interleave_granularity_show(struct device *dev,
return sysfs_emit(buf, "%d\n", p->interleave_granularity);
}
+/**
+ * is_ig_allowed() - Validate region interleave granularity
+ * @cxlrd: root decoder
+ * @ig: proposed region interleave granularity in bytes
+ *
+ * Callers must have validated @ig through granularity_to_eig() first.
+ *
+ * Return: true for a non-interleaving root or any region granularity
+ * not larger than the root 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 +590,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);
@@ -2128,6 +2139,32 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
+ /* Apply the sysfs granularity rule to auto regions too. */
+ 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,
+ cxlrd->cxlsd.cxld.interleave_ways,
+ cxlrd->cxlsd.cxld.interleave_granularity);
+ return -ENXIO;
+ }
+
+ /*
+ * Selector containment cannot prove span equality for 3-way family
+ * roots because a 3-way interleave consumes no selector bits.
+ */
+ if (!is_power_of_2(cxlrd->cxlsd.cxld.interleave_ways) &&
+ (u64)p->interleave_ways * p->interleave_granularity !=
+ (u64)cxlrd->cxlsd.cxld.interleave_ways *
+ cxlrd->cxlsd.cxld.interleave_granularity) {
+ dev_dbg(&cxlr->dev,
+ "region ways*gran (%d*%d) != root ways*gran (%d*%d)\n",
+ p->interleave_ways, p->interleave_granularity,
+ cxlrd->cxlsd.cxld.interleave_ways,
+ cxlrd->cxlsd.cxld.interleave_granularity);
+ 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
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5/6] cxl/test: Add a topology to test mixed-granularity regions
2026-05-30 0:00 [PATCH 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
` (3 preceding siblings ...)
2026-05-30 0:00 ` [PATCH 4/6] cxl/region: Validate mixed-granularity at sysfs and attach gates Alison Schofield
@ 2026-05-30 0:00 ` Alison Schofield
2026-05-30 5:17 ` sashiko-bot
2026-06-04 0:04 ` Dave Jiang
2026-05-30 0:00 ` [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide Alison Schofield
5 siblings, 2 replies; 15+ messages in thread
From: Alison Schofield @ 2026-05-30 0:00 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
Add a cxl_test topology for mixed-granularity region coverage.
modprobe cxl_test mixed_gran_regions=1 creates three host bridges
with two switch levels below each bridge and twelve endpoints total.
The topology advertises one 2-way root CFMWS for power-of-2
mixed-granularity tests and one 3-way root CFMWS for 3-way-family
tests.
Leave the default and existing specialty topologies unchanged.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
tools/testing/cxl/test/cxl.c | 474 +++++++++++++++++++++++++++++++++--
1 file changed, 452 insertions(+), 22 deletions(-)
diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index 418669927fb0..b44db749873f 100644
--- a/tools/testing/cxl/test/cxl.c
+++ b/tools/testing/cxl/test/cxl.c
@@ -17,6 +17,7 @@
static int interleave_arithmetic;
static bool extended_linear_cache;
static bool fail_autoassemble;
+static bool mixed_gran_regions;
#define FAKE_QTG_ID 42
@@ -26,7 +27,26 @@ static bool fail_autoassemble;
#define NR_CXL_ROOT_PORTS 2
#define NR_CXL_SWITCH_PORTS 2
#define NR_CXL_PORT_DECODERS 8
-#define NR_BRIDGES (NR_CXL_HOST_BRIDGES + NR_CXL_SINGLE_HOST + NR_CXL_RCH)
+
+/*
+ * mixed_gran_regions=1: three host bridges, two switch levels, and
+ * twelve endpoints for power-of-2 and 3-way-family region layouts.
+ */
+#define NR_CXL_MIX_GRAN_HB 3
+#define NR_CXL_MIX_GRAN_ROOT_PORTS NR_CXL_MIX_GRAN_HB /* 1 per HB */
+#define NR_CXL_MIX_GRAN_L1_DPORTS (NR_CXL_MIX_GRAN_HB * 2)
+#define NR_CXL_MIX_GRAN_L2_SWITCHES NR_CXL_MIX_GRAN_L1_DPORTS
+#define NR_CXL_MIX_GRAN_L2_DPORTS (NR_CXL_MIX_GRAN_L2_SWITCHES * 2)
+
+#define NR_DEFAULT_BRIDGES \
+ (NR_CXL_HOST_BRIDGES + NR_CXL_SINGLE_HOST + NR_CXL_RCH)
+#define NR_BRIDGES (NR_DEFAULT_BRIDGES + NR_CXL_MIX_GRAN_HB)
+
+/* CHBS index ranges within mock_cedt.chbs[] */
+#define CHBS_DEFAULT_START 0
+#define CHBS_DEFAULT_END (NR_DEFAULT_BRIDGES - 1)
+#define CHBS_MIX_GRAN_START NR_DEFAULT_BRIDGES
+#define CHBS_MIX_GRAN_END (NR_BRIDGES - 1)
#define MOCK_AUTO_REGION_SIZE_DEFAULT SZ_512M
static int mock_auto_region_size = MOCK_AUTO_REGION_SIZE_DEFAULT;
@@ -52,6 +72,15 @@ struct platform_device *cxl_mem_single[NR_MEM_SINGLE];
static struct platform_device *cxl_rch[NR_CXL_RCH];
static struct platform_device *cxl_rcd[NR_CXL_RCH];
+/* Dedicated mixed-granularity topology */
+static struct platform_device *cxl_mix_gran_hb[NR_CXL_MIX_GRAN_HB];
+static struct platform_device *cxl_mix_gran_root_port[NR_CXL_MIX_GRAN_ROOT_PORTS];
+static struct platform_device *cxl_mix_gran_l1_uport[NR_CXL_MIX_GRAN_ROOT_PORTS];
+static struct platform_device *cxl_mix_gran_l1_dport[NR_CXL_MIX_GRAN_L1_DPORTS];
+static struct platform_device *cxl_mix_gran_l2_uport[NR_CXL_MIX_GRAN_L2_SWITCHES];
+static struct platform_device *cxl_mix_gran_l2_dport[NR_CXL_MIX_GRAN_L2_DPORTS];
+static struct platform_device *cxl_mem_mix_gran[NR_CXL_MIX_GRAN_L2_DPORTS];
+
/*
* Decoder registry
*
@@ -115,6 +144,19 @@ static struct acpi_device host_bridge[NR_BRIDGES] = {
.handle = &host_bridge[3],
.pnp.unique_id = "3",
},
+ /* Dedicated mixed-granularity topology */
+ [4] = {
+ .handle = &host_bridge[4],
+ .pnp.unique_id = "4",
+ },
+ [5] = {
+ .handle = &host_bridge[5],
+ .pnp.unique_id = "5",
+ },
+ [6] = {
+ .handle = &host_bridge[6],
+ .pnp.unique_id = "6",
+ },
};
static bool is_mock_dev(struct device *dev)
@@ -130,6 +172,9 @@ static bool is_mock_dev(struct device *dev)
for (i = 0; i < ARRAY_SIZE(cxl_rcd); i++)
if (dev == &cxl_rcd[i]->dev)
return true;
+ for (i = 0; i < ARRAY_SIZE(cxl_mem_mix_gran); i++)
+ if (cxl_mem_mix_gran[i] && dev == &cxl_mem_mix_gran[i]->dev)
+ return true;
if (dev == &cxl_acpi->dev)
return true;
return false;
@@ -188,6 +233,14 @@ static struct {
struct acpi_cedt_cfmws cfmws;
u32 target[3];
} cfmws8;
+ struct {
+ struct acpi_cedt_cfmws cfmws;
+ u32 target[2];
+ } cfmws9;
+ struct {
+ struct acpi_cedt_cfmws cfmws;
+ u32 target[3];
+ } cfmws10;
struct {
struct acpi_cedt_cxims cxims;
u64 xormap_list[2];
@@ -232,6 +285,31 @@ static struct {
.uid = 3,
.cxl_version = ACPI_CEDT_CHBS_VERSION_CXL11,
},
+ /* Dedicated mixed-granularity topology */
+ .chbs[4] = {
+ .header = {
+ .type = ACPI_CEDT_TYPE_CHBS,
+ .length = sizeof(mock_cedt.chbs[0]),
+ },
+ .uid = 4,
+ .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20,
+ },
+ .chbs[5] = {
+ .header = {
+ .type = ACPI_CEDT_TYPE_CHBS,
+ .length = sizeof(mock_cedt.chbs[0]),
+ },
+ .uid = 5,
+ .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20,
+ },
+ .chbs[6] = {
+ .header = {
+ .type = ACPI_CEDT_TYPE_CHBS,
+ .length = sizeof(mock_cedt.chbs[0]),
+ },
+ .uid = 6,
+ .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20,
+ },
.cfmws0 = {
.cfmws = {
.header = {
@@ -371,6 +449,36 @@ static struct {
},
.target = { 0, 1, 2, },
},
+ .cfmws9 = {
+ .cfmws = {
+ .header = {
+ .type = ACPI_CEDT_TYPE_CFMWS,
+ .length = sizeof(mock_cedt.cfmws9),
+ },
+ .interleave_ways = 1,
+ .granularity = 4,
+ .restrictions = ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM |
+ ACPI_CEDT_CFMWS_RESTRICT_PMEM,
+ .qtg_id = FAKE_QTG_ID,
+ .window_size = SZ_512M * 4UL,
+ },
+ .target = { 4, 5, },
+ },
+ .cfmws10 = {
+ .cfmws = {
+ .header = {
+ .type = ACPI_CEDT_TYPE_CFMWS,
+ .length = sizeof(mock_cedt.cfmws10),
+ },
+ .interleave_ways = 8,
+ .granularity = 1,
+ .restrictions = ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM |
+ ACPI_CEDT_CFMWS_RESTRICT_PMEM,
+ .qtg_id = FAKE_QTG_ID,
+ .window_size = SZ_512M * 6UL,
+ },
+ .target = { 4, 5, 6, },
+ },
.cxims0 = {
.cxims = {
.header = {
@@ -395,6 +503,9 @@ struct acpi_cedt_cfmws *mock_cfmws[] = {
[6] = &mock_cedt.cfmws6.cfmws,
[7] = &mock_cedt.cfmws7.cfmws,
[8] = &mock_cedt.cfmws8.cfmws,
+ /* Dedicated mixed-granularity topology */
+ [9] = &mock_cedt.cfmws9.cfmws,
+ [10] = &mock_cedt.cfmws10.cfmws,
};
static int cfmws_start;
@@ -403,6 +514,8 @@ static int cfmws_end;
#define CFMWS_MOD_ARRAY_END 5
#define CFMWS_XOR_ARRAY_START 6
#define CFMWS_XOR_ARRAY_END 8
+#define CFMWS_MIX_GRAN_ARRAY_START 9
+#define CFMWS_MIX_GRAN_ARRAY_END 10
struct acpi_cedt_cxims *mock_cxims[1] = {
[0] = &mock_cedt.cxims0.cxims,
@@ -475,9 +588,18 @@ static void cfmws_elc_update(struct acpi_cedt_cfmws *window, int index)
static int populate_cedt(void)
{
struct cxl_mock_res *res;
+ int chbs_lo, chbs_hi;
int i;
- for (i = 0; i < ARRAY_SIZE(mock_cedt.chbs); i++) {
+ if (mixed_gran_regions) {
+ chbs_lo = CHBS_MIX_GRAN_START;
+ chbs_hi = CHBS_MIX_GRAN_END;
+ } else {
+ chbs_lo = CHBS_DEFAULT_START;
+ chbs_hi = CHBS_DEFAULT_END;
+ }
+
+ for (i = chbs_lo; i <= chbs_hi; i++) {
struct acpi_cedt_chbs *chbs = &mock_cedt.chbs[i];
resource_size_t size;
@@ -531,12 +653,23 @@ static int mock_acpi_table_parse_cedt(enum acpi_cedt_type id,
if (!is_mock_port(dev) && !is_mock_dev(dev))
return acpi_table_parse_cedt(id, handler_arg, arg);
- if (id == ACPI_CEDT_TYPE_CHBS)
- for (i = 0; i < ARRAY_SIZE(mock_cedt.chbs); i++) {
+ if (id == ACPI_CEDT_TYPE_CHBS) {
+ int chbs_lo, chbs_hi;
+
+ if (mixed_gran_regions) {
+ chbs_lo = CHBS_MIX_GRAN_START;
+ chbs_hi = CHBS_MIX_GRAN_END;
+ } else {
+ chbs_lo = CHBS_DEFAULT_START;
+ chbs_hi = CHBS_DEFAULT_END;
+ }
+
+ for (i = chbs_lo; i <= chbs_hi; i++) {
h = (union acpi_subtable_headers *)&mock_cedt.chbs[i];
end = (unsigned long)&mock_cedt.chbs[i + 1];
handler_arg(h, arg, end);
}
+ }
if (id == ACPI_CEDT_TYPE_CFMWS)
for (i = cfmws_start; i <= cfmws_end; i++) {
@@ -568,6 +701,9 @@ static bool is_mock_bridge(struct device *dev)
for (i = 0; i < ARRAY_SIZE(cxl_rch); i++)
if (dev == &cxl_rch[i]->dev)
return true;
+ for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_hb); i++)
+ if (cxl_mix_gran_hb[i] && dev == &cxl_mix_gran_hb[i]->dev)
+ return true;
return false;
}
@@ -603,6 +739,31 @@ static bool is_mock_port(struct device *dev)
if (dev == &cxl_swd_single[i]->dev)
return true;
+ for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_root_port); i++)
+ if (cxl_mix_gran_root_port[i] &&
+ dev == &cxl_mix_gran_root_port[i]->dev)
+ return true;
+
+ for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_l1_uport); i++)
+ if (cxl_mix_gran_l1_uport[i] &&
+ dev == &cxl_mix_gran_l1_uport[i]->dev)
+ return true;
+
+ for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_l1_dport); i++)
+ if (cxl_mix_gran_l1_dport[i] &&
+ dev == &cxl_mix_gran_l1_dport[i]->dev)
+ return true;
+
+ for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_l2_uport); i++)
+ if (cxl_mix_gran_l2_uport[i] &&
+ dev == &cxl_mix_gran_l2_uport[i]->dev)
+ return true;
+
+ for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_l2_dport); i++)
+ if (cxl_mix_gran_l2_dport[i] &&
+ dev == &cxl_mix_gran_l2_dport[i]->dev)
+ return true;
+
if (is_cxl_memdev(dev))
return is_mock_dev(dev->parent);
@@ -671,7 +832,15 @@ static struct acpi_pci_root mock_pci_root[ARRAY_SIZE(mock_pci_bus)] = {
[3] = {
.bus = &mock_pci_bus[3],
},
-
+ [4] = {
+ .bus = &mock_pci_bus[4],
+ },
+ [5] = {
+ .bus = &mock_pci_bus[5],
+ },
+ [6] = {
+ .bus = &mock_pci_bus[6],
+ },
};
static bool is_mock_bus(struct pci_bus *bus)
@@ -1229,6 +1398,8 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
if (is_cxl_endpoint(port))
target_count = 0;
+ else if (mixed_gran_regions && is_cxl_root(parent_port))
+ target_count = NR_CXL_MIX_GRAN_ROOT_PORTS / NR_CXL_MIX_GRAN_HB;
else if (is_cxl_root(parent_port))
target_count = NR_CXL_ROOT_PORTS;
else
@@ -1328,6 +1499,27 @@ static int get_port_array(struct cxl_port *port,
struct platform_device **array;
int array_size;
+ if (mixed_gran_regions) {
+ if (port->depth == 1) {
+ array_size = ARRAY_SIZE(cxl_mix_gran_root_port);
+ array = cxl_mix_gran_root_port;
+ } else if (port->depth == 2) {
+ array_size = ARRAY_SIZE(cxl_mix_gran_l1_dport);
+ array = cxl_mix_gran_l1_dport;
+ } else if (port->depth == 3) {
+ array_size = ARRAY_SIZE(cxl_mix_gran_l2_dport);
+ array = cxl_mix_gran_l2_dport;
+ } else {
+ dev_WARN_ONCE(&port->dev, 1, "unexpected depth %d\n",
+ port->depth);
+ return -ENXIO;
+ }
+
+ *port_array = array;
+ *port_array_size = array_size;
+ return 0;
+ }
+
if (port->depth == 1) {
if (is_multi_bridge(port->uport_dev)) {
array_size = ARRAY_SIZE(cxl_root_port);
@@ -1709,10 +1901,190 @@ static void cxl_single_topo_exit(void)
}
}
+#define MIX_GRAN_RP_ID_BASE (NR_MULTI_ROOT + NR_CXL_SINGLE_HOST)
+#define MIX_GRAN_L1U_ID_BASE MIX_GRAN_RP_ID_BASE
+#define MIX_GRAN_L1D_ID_BASE (NR_MEM_MULTI + NR_MEM_SINGLE)
+#define MIX_GRAN_L2U_ID_BASE MIX_GRAN_L1D_ID_BASE
+#define MIX_GRAN_L2D_ID_BASE \
+ (MIX_GRAN_L1D_ID_BASE + NR_CXL_MIX_GRAN_L1_DPORTS)
+#define MIX_GRAN_MEM_ID_BASE (NR_MEM_MULTI + NR_MEM_SINGLE + NR_CXL_RCH)
+
+static void cxl_mix_gran_topo_exit(void)
+{
+ int i;
+
+ for (i = NR_CXL_MIX_GRAN_L2_DPORTS - 1; i >= 0; i--)
+ platform_device_unregister(cxl_mix_gran_l2_dport[i]);
+ for (i = NR_CXL_MIX_GRAN_L2_SWITCHES - 1; i >= 0; i--)
+ platform_device_unregister(cxl_mix_gran_l2_uport[i]);
+ for (i = NR_CXL_MIX_GRAN_L1_DPORTS - 1; i >= 0; i--)
+ platform_device_unregister(cxl_mix_gran_l1_dport[i]);
+ for (i = NR_CXL_MIX_GRAN_ROOT_PORTS - 1; i >= 0; i--)
+ platform_device_unregister(cxl_mix_gran_l1_uport[i]);
+ for (i = NR_CXL_MIX_GRAN_ROOT_PORTS - 1; i >= 0; i--)
+ platform_device_unregister(cxl_mix_gran_root_port[i]);
+ for (i = NR_CXL_MIX_GRAN_HB - 1; i >= 0; i--) {
+ struct platform_device *pdev = cxl_mix_gran_hb[i];
+
+ if (!pdev)
+ continue;
+ sysfs_remove_link(&pdev->dev.kobj, "physical_node");
+ platform_device_unregister(pdev);
+ }
+}
+
+static __init int cxl_mix_gran_topo_init(void)
+{
+ int i, rc;
+
+ /* 3 host bridges */
+ for (i = 0; i < NR_CXL_MIX_GRAN_HB; i++) {
+ struct acpi_device *adev = &host_bridge[NR_DEFAULT_BRIDGES + i];
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("cxl_host_bridge",
+ NR_DEFAULT_BRIDGES + i);
+ if (!pdev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ mock_companion(adev, &pdev->dev);
+ rc = platform_device_add(pdev);
+ if (rc) {
+ platform_device_put(pdev);
+ goto err;
+ }
+
+ cxl_mix_gran_hb[i] = pdev;
+ mock_pci_bus[NR_DEFAULT_BRIDGES + i].bridge = &pdev->dev;
+ rc = sysfs_create_link(&pdev->dev.kobj, &pdev->dev.kobj,
+ "physical_node");
+ if (rc)
+ goto err;
+ }
+
+ /* 1 root port per host bridge */
+ for (i = 0; i < NR_CXL_MIX_GRAN_ROOT_PORTS; i++) {
+ struct platform_device *bridge = cxl_mix_gran_hb[i];
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("cxl_root_port",
+ MIX_GRAN_RP_ID_BASE + i);
+ if (!pdev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ pdev->dev.parent = &bridge->dev;
+
+ rc = platform_device_add(pdev);
+ if (rc) {
+ platform_device_put(pdev);
+ goto err;
+ }
+ cxl_mix_gran_root_port[i] = pdev;
+ }
+
+ /* L1 switch uport under each root port */
+ for (i = 0; i < NR_CXL_MIX_GRAN_ROOT_PORTS; i++) {
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("cxl_switch_uport",
+ MIX_GRAN_L1U_ID_BASE + i);
+ if (!pdev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ pdev->dev.parent = &cxl_mix_gran_root_port[i]->dev;
+
+ rc = platform_device_add(pdev);
+ if (rc) {
+ platform_device_put(pdev);
+ goto err;
+ }
+ cxl_mix_gran_l1_uport[i] = pdev;
+ }
+
+ /* 2 L1 dports per L1 switch */
+ for (i = 0; i < NR_CXL_MIX_GRAN_L1_DPORTS; i++) {
+ struct platform_device *uport =
+ cxl_mix_gran_l1_uport[i / NR_CXL_SWITCH_PORTS];
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("cxl_switch_dport",
+ MIX_GRAN_L1D_ID_BASE + i);
+ if (!pdev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ pdev->dev.parent = &uport->dev;
+
+ rc = platform_device_add(pdev);
+ if (rc) {
+ platform_device_put(pdev);
+ goto err;
+ }
+ cxl_mix_gran_l1_dport[i] = pdev;
+ }
+
+ /* L2 switch uport under each L1 dport */
+ for (i = 0; i < NR_CXL_MIX_GRAN_L2_SWITCHES; i++) {
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("cxl_switch_uport",
+ MIX_GRAN_L2U_ID_BASE + i);
+ if (!pdev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ pdev->dev.parent = &cxl_mix_gran_l1_dport[i]->dev;
+
+ rc = platform_device_add(pdev);
+ if (rc) {
+ platform_device_put(pdev);
+ goto err;
+ }
+ cxl_mix_gran_l2_uport[i] = pdev;
+ }
+
+ /* 2 L2 dports per L2 switch */
+ for (i = 0; i < NR_CXL_MIX_GRAN_L2_DPORTS; i++) {
+ struct platform_device *uport =
+ cxl_mix_gran_l2_uport[i / NR_CXL_SWITCH_PORTS];
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("cxl_switch_dport",
+ MIX_GRAN_L2D_ID_BASE + i);
+ if (!pdev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ pdev->dev.parent = &uport->dev;
+
+ rc = platform_device_add(pdev);
+ if (rc) {
+ platform_device_put(pdev);
+ goto err;
+ }
+ cxl_mix_gran_l2_dport[i] = pdev;
+ }
+
+ return 0;
+err:
+ cxl_mix_gran_topo_exit();
+ return rc;
+}
+
static void cxl_mem_exit(void)
{
int i;
+ if (mixed_gran_regions) {
+ for (i = NR_CXL_MIX_GRAN_L2_DPORTS - 1; i >= 0; i--)
+ platform_device_unregister(cxl_mem_mix_gran[i]);
+ return;
+ }
+
for (i = ARRAY_SIZE(cxl_rcd) - 1; i >= 0; i--)
platform_device_unregister(cxl_rcd[i]);
for (i = ARRAY_SIZE(cxl_mem_single) - 1; i >= 0; i--)
@@ -1721,10 +2093,46 @@ static void cxl_mem_exit(void)
platform_device_unregister(cxl_mem[i]);
}
+static int cxl_mem_mix_gran_init(void)
+{
+ int i, rc;
+
+ for (i = 0; i < NR_CXL_MIX_GRAN_L2_DPORTS; i++) {
+ struct platform_device *dport = cxl_mix_gran_l2_dport[i];
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("cxl_mem",
+ MIX_GRAN_MEM_ID_BASE + i);
+ if (!pdev) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ pdev->dev.parent = &dport->dev;
+ set_dev_node(&pdev->dev, i % 2);
+
+ cxl_mem_mix_gran[i] = pdev;
+ rc = platform_device_add(pdev);
+ if (rc) {
+ platform_device_put(pdev);
+ cxl_mem_mix_gran[i] = NULL;
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ for (i = NR_CXL_MIX_GRAN_L2_DPORTS - 1; i >= 0; i--)
+ platform_device_unregister(cxl_mem_mix_gran[i]);
+ return rc;
+}
+
static int cxl_mem_init(void)
{
int i, rc;
+ if (mixed_gran_regions)
+ return cxl_mem_mix_gran_init();
+
for (i = 0; i < ARRAY_SIZE(cxl_mem); i++) {
struct platform_device *dport = cxl_switch_dport[i];
struct platform_device *pdev;
@@ -1848,7 +2256,10 @@ static __init int cxl_test_init(void)
if (rc)
goto err_gen_pool_add;
- if (interleave_arithmetic == 1) {
+ if (mixed_gran_regions) {
+ cfmws_start = CFMWS_MIX_GRAN_ARRAY_START;
+ cfmws_end = CFMWS_MIX_GRAN_ARRAY_END;
+ } else if (interleave_arithmetic == 1) {
cfmws_start = CFMWS_XOR_ARRAY_START;
cfmws_end = CFMWS_XOR_ARRAY_END;
} else {
@@ -1860,6 +2271,13 @@ static __init int cxl_test_init(void)
if (rc)
goto err_populate;
+ if (mixed_gran_regions) {
+ rc = cxl_mix_gran_topo_init();
+ if (rc)
+ goto err_populate;
+ goto topo_done;
+ }
+
for (i = 0; i < ARRAY_SIZE(cxl_host_bridge); i++) {
struct acpi_device *adev = &host_bridge[i];
struct platform_device *pdev;
@@ -1945,9 +2363,10 @@ static __init int cxl_test_init(void)
if (rc)
goto err_single;
+topo_done:
cxl_acpi = platform_device_alloc("cxl_acpi", 0);
if (!cxl_acpi)
- goto err_rch;
+ goto err_topo;
mock_companion(&acpi0017_mock, &cxl_acpi->dev);
acpi0017_mock.dev.bus = &platform_bus_type;
@@ -1971,7 +2390,11 @@ static __init int cxl_test_init(void)
cxl_mem_exit();
err_root:
platform_device_put(cxl_acpi);
-err_rch:
+err_topo:
+ if (mixed_gran_regions) {
+ cxl_mix_gran_topo_exit();
+ goto err_populate;
+ }
cxl_rch_topo_exit();
err_single:
cxl_single_topo_exit();
@@ -2020,21 +2443,25 @@ static __exit void cxl_test_exit(void)
hmem_test_exit();
cxl_mem_exit();
platform_device_unregister(cxl_acpi);
- cxl_rch_topo_exit();
- cxl_single_topo_exit();
- for (i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--)
- platform_device_unregister(cxl_switch_dport[i]);
- for (i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--)
- platform_device_unregister(cxl_switch_uport[i]);
- for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--)
- platform_device_unregister(cxl_root_port[i]);
- for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) {
- struct platform_device *pdev = cxl_host_bridge[i];
+ if (mixed_gran_regions) {
+ cxl_mix_gran_topo_exit();
+ } else {
+ cxl_rch_topo_exit();
+ cxl_single_topo_exit();
+ for (i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--)
+ platform_device_unregister(cxl_switch_dport[i]);
+ for (i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--)
+ platform_device_unregister(cxl_switch_uport[i]);
+ for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--)
+ platform_device_unregister(cxl_root_port[i]);
+ for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) {
+ struct platform_device *pdev = cxl_host_bridge[i];
- if (!pdev)
- continue;
- sysfs_remove_link(&pdev->dev.kobj, "physical_node");
- platform_device_unregister(cxl_host_bridge[i]);
+ if (!pdev)
+ continue;
+ sysfs_remove_link(&pdev->dev.kobj, "physical_node");
+ platform_device_unregister(cxl_host_bridge[i]);
+ }
}
depopulate_all_mock_resources();
gen_pool_destroy(cxl_mock_pool);
@@ -2049,6 +2476,9 @@ module_param(extended_linear_cache, bool, 0444);
MODULE_PARM_DESC(extended_linear_cache, "Enable extended linear cache support");
module_param(fail_autoassemble, bool, 0444);
MODULE_PARM_DESC(fail_autoassemble, "Simulate missing member of an auto-region");
+module_param(mixed_gran_regions, bool, 0444);
+MODULE_PARM_DESC(mixed_gran_regions,
+ "Topology supporting mixed granularity regions");
module_init(cxl_test_init);
module_exit(cxl_test_exit);
MODULE_LICENSE("GPL v2");
--
2.37.3
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide
2026-05-30 0:00 [PATCH 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
` (4 preceding siblings ...)
2026-05-30 0:00 ` [PATCH 5/6] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
@ 2026-05-30 0:00 ` Alison Schofield
2026-06-03 23:57 ` Dave Jiang
2026-06-04 3:47 ` kernel test robot
5 siblings, 2 replies; 15+ messages in thread
From: Alison Schofield @ 2026-05-30 0:00 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
Document how region and root decoder granularities interact for
same-granularity, power-of-2 mixed-granularity, and 3-way-family
interleaves.
Explain the selector-bit model used by the CXL specification, the
legal layouts that result from it, the additional span constraint
required by 3-way-family roots, and how the Linux driver validates
those rules for both auto and user-created regions.
Assisted-by: Claude Opus 4.7
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
Documentation/driver-api/cxl/index.rst | 1 +
.../cxl/linux/region-granularity.rst | 486 ++++++++++++++++++
2 files changed, 487 insertions(+)
create mode 100644 Documentation/driver-api/cxl/linux/region-granularity.rst
diff --git a/Documentation/driver-api/cxl/index.rst b/Documentation/driver-api/cxl/index.rst
index 3dfae1d310ca..1ae193ea4020 100644
--- a/Documentation/driver-api/cxl/index.rst
+++ b/Documentation/driver-api/cxl/index.rst
@@ -42,6 +42,7 @@ that have impacts on each other. The docs here break up configurations steps.
linux/dax-driver
linux/memory-hotplug
linux/access-coordinates
+ linux/region-granularity
.. toctree::
:maxdepth: 2
diff --git a/Documentation/driver-api/cxl/linux/region-granularity.rst b/Documentation/driver-api/cxl/linux/region-granularity.rst
new file mode 100644
index 000000000000..7d0d12e6e2bd
--- /dev/null
+++ b/Documentation/driver-api/cxl/linux/region-granularity.rst
@@ -0,0 +1,486 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==============================================
+Region Granularity and Multi-Level Interleave
+==============================================
+
+CXL memory regions stripe accesses across one or more endpoints by
+routing transactions through a tree of decoders: a root decoder, zero
+or more switch decoders, and an endpoint decoder at each leaf. Each
+decoder in that path uses a contiguous range of HPA address bits to
+pick its target. This document describes how those bit ranges combine
+across levels, the constraints CXL Spec 4.0 places on legal
+combinations, and how the Linux CXL driver validates and programs
+them.
+
+The relevant spec material is CXL Spec 4.0 Section 9.13.1, especially
+Section 9.13.1.1 ("Legal Interleaving Configurations") and CXL Spec
+4.0 Tables 9-6, 9-7, and 9-8. CXL Spec 4.0 Figure 9-17 (two-level)
+and Figure 9-18 (three-level) illustrate the bit-routing intuition
+this document builds on.
+
+
+HPA Selector Bits
+=================
+
+For a single interleave level with ``W`` ways and granularity ``G``
+bytes, the **selector bits** are the ``log2(W)`` HPA bits the decoder
+inspects to choose among its ``W`` targets. The lowest of those bits
+sits at position ``log2(G)``, so the selector spans
+``[log2(G) + log2(W) - 1 : log2(G)]``. Equivalently, the bitmask is
+``(W - 1) * G``.
+
+A few examples make the geometry concrete::
+
+ 2-way at 256B -> selector bit 8 (one bit)
+ 4-way at 1KB -> selector bits[11:10] (two bits)
+ 2-way at 4KB -> selector bit 12 (one bit)
+ 8-way at 1KB -> selector bits[12:10] (three bits)
+
+Throughout this document "inner bit" means a lower-numbered HPA bit
+(smaller stride, faster cycling among targets) and "outer bit" means
+a higher-numbered HPA bit (larger stride, slower cycling). HPA[8] is
+the innermost legal CXL selector bit; HPA[14] is the outermost.
+
+
+The Multi-Level Rule
+====================
+
+When a region's interleave is split across multiple decoder levels,
+CXL Spec 4.0 Section 9.13.1 requires that:
+
+ "all the levels use different, but consecutive, HPA bits to
+ select the target and no Interleave Set has more than eight
+ devices."
+
+Two requirements live in that sentence:
+
+* **Disjoint** - each level's selector bits must not overlap any
+ other level's bits.
+* **Consecutive** - the union of all level selectors must be a single
+ contiguous run of HPA bits with no gap.
+
+The union of level selectors is the **region selector**, and it must
+equal ``(region_ways - 1) * region_gran``. Because that mask is itself
+a contiguous run by construction, the levels just need to partition
+it cleanly.
+
+The seven legal interleave granularity values split into two groups
+(see CXL Spec 4.0 Section 9.13.1 and Table 8-116):
+
+* **Group 1** - 256B, 512B, 1024B, 2048B (interleaving on HPA[8..11])
+* **Group 2** - 4096B, 8192B, 16384B (interleaving on HPA[12..14])
+
+CXL host bridges and switches must support every value in both
+groups. CXL memory devices must support at least one group, advertised
+through their HDM Decoder Capability register.
+
+
+Same-Granularity Interleave
+===========================
+
+The simplest case is ``region_gran == root_gran``. Each level's
+granularity equals the next inner level's IG times that level's
+ways. The region selector bits stack inside-out from the root.
+
+Example: 4-way region on a 2-way root, 256B everywhere::
+
+ Level Ways Granularity Selector bits
+ ------ ---- ----------- -------------
+ Root 2 256B bit 8
+ Switch 2 512B bit 9
+ Region 4 256B bits[9:8]
+
+Each level's IG sits one bit position above the next inner level's
+selector. The region's effective selector spans ``[9:8]``, two bits,
+matching ``(4 - 1) * 256``.
+
+
+Mixed-Granularity Interleave (Power-of-2)
+=========================================
+
+A region's granularity may be **less than** the root decoder's
+granularity. In that case the root claims an outer selector bit and
+the switches beneath it claim inner bits. The arrangement is legal
+whenever both granularities are powers of 2 and the resulting bits
+remain disjoint and consecutive.
+
+Two-Level Example (CXL Spec 4.0 Figure 9-17)
+--------------------------------------------
+
+8-way region, 2-way root at 4KB, 4-way switch at 1KB::
+
+ Level Ways Granularity Selector bits
+ ------ ---- ----------- -------------
+ Root 2 4KB bit 12
+ Switch 4 1KB bits[11:10]
+ Region 8 1KB bits[12:10]
+
+The host bridge bisects the address range at 4KB granularity, sending
+one half to each root port. Beneath each root port a 4-way switch
+splits its half into four 1KB chunks. To each endpoint the region
+appears as an 8-way interleave at 1KB based on bits[12:10]; the
+leftmost endpoint receives 1KB chunks at HPA bases 0, 8KB, 16KB, ...
+
+Three-Level Example (CXL Spec 4.0 Figure 9-18)
+----------------------------------------------
+
+8-way region across cross-HB / HB / switch, all 2-way::
+
+ Level Ways Granularity Selector bits
+ ----------- ---- ----------- -------------
+ Cross-HB 2 4KB bit 12
+ Host bridge 2 2KB bit 11
+ Switch 2 1KB bit 10
+ Region 8 1KB bits[12:10]
+
+Three levels of 2-way interleave each contribute one bit. Total
+endpoints: ``2 * 2 * 2 = 8``. Selector union:
+``bit 12 | bit 11 | bit 10`` = ``bits[12:10]``. Disjoint and
+consecutive.
+
+General Power-of-2 Rules
+------------------------
+
+For power-of-2 ways at every level:
+
+* ``region_gran <= root_gran`` is allowed; ``region_gran > root_gran``
+ is not (the root's selector would land inside one region stride).
+* Both granularities must be powers of 2.
+* The ratio ``root_gran / region_gran`` is the number of inner
+ position slots packed beneath one root target. Endpoint position is
+ partitioned by root target rather than interleaved across them.
+* Mixed-granularity is always optional for power-of-2 topologies. A
+ same-granularity arrangement always exists for the same set of
+ endpoints. Firmware may prefer mixed-granularity to expose a finer
+ switch-level interleave to the CPU.
+
+
+3-Way Family Interleave
+=======================
+
+CXL Spec 4.0 Section 9.13.1.1 defines a non-power-of-2 family of
+interleave widths: 3, 6, and 12. These are not pure bit selectors.
+A 3-way root divides the address by 3 in value-space (modulo or XOR
+arithmetic, see CXL Spec 4.0 Section 9.18) rather than picking a
+target from a contiguous bit range. As a consequence:
+
+* A strict 3-way root consumes **zero** HPA selector bits of its own.
+* Only the power-of-2 component of the region width contributes
+ selector bits. For a 6-way region, the bit-selector component is
+ 2-way; for 12-way, 4-way.
+* Mixed-granularity is **required** for any 6-way or 12-way region on
+ a 3-way root. There is no same-granularity alternative.
+
+Legal 3-Way-Family Configurations
+---------------------------------
+
+CXL Spec 4.0 Tables 9-6, 9-7, and 9-8 enumerate the legal
+configurations. IGB is the device-level interleave granularity.
+
+Table 9-6: 12-way device-level interleave at IGB::
+
+ Cross-host bridge Root complex CXL switch
+ ----------------- -------------- --------------
+ 12-way at IGB no interleave no interleave
+ 6-way at 2*IGB 2-way at IGB no interleave
+ 6-way at 2*IGB no interleave 2-way at IGB
+ 3-way at 4*IGB 4-way at IGB no interleave
+ 3-way at 4*IGB no interleave 4-way at IGB
+ 3-way at 4*IGB 2-way at IGB 2-way at 2*IGB
+ 3-way at 4*IGB 2-way at 2*IGB 2-way at IGB
+
+Table 9-7: 6-way device-level interleave at IGB::
+
+ Cross-host bridge CXL host bridge CXL switch
+ ----------------- --------------- --------------
+ 6-way at IGB no interleave no interleave
+ 3-way at 2*IGB 2-way at IGB no interleave
+ 3-way at 2*IGB no interleave 2-way at IGB
+
+Table 9-8: 3-way device-level interleave at IGB::
+
+ Cross-host bridge CXL host bridge CXL switch
+ ----------------- --------------- --------------
+ 3-way at IGB no interleave no interleave
+
+Span Identity for 3-Way-Family Roots
+------------------------------------
+
+For a 3-way (or 6-way, 12-way) root, the region must occupy exactly
+one full cycle of the root's divide-by-3 pattern::
+
+ region_ways * region_gran == root_ways * root_gran
+
+Every legal row of CXL Spec 4.0 Tables 9-6, 9-7, and 9-8 satisfies
+this identity.
+
+Why power-of-2 roots do not need this check explicitly, and why
+3-way roots do, comes down to how each cycles through its targets:
+
+* A power-of-2 root selects targets purely by HPA bits. The bits it
+ owns are part of the region selector mask
+ ``(region_ways - 1) * region_gran``. The disjoint and containment
+ checks on selector bits inside the region selector force
+ ``root_ways * root_gran`` to be a power-of-2 divisor of
+ ``region_ways * region_gran``, and the region size constraint
+ forces it to be exactly one such divisor. Span equality is a
+ by-product of bit accounting.
+
+* A 3-way root divides the address by 3 in value-space. It cycles
+ through three targets at every ``root_gran``-byte stride, but
+ consumes no HPA selector bits at all. The selector mask returned
+ by ``get_selector()`` for a strict 3-way is zero. Selector bit
+ accounting cannot see the address space the root reserves at
+ ``log2(root_gran)``, so it cannot rule out arrangements where
+ ``region_ways * region_gran`` is some multiple of
+ ``root_ways * root_gran`` other than one. (For example, a
+ same-granularity 6-way region on a 3-way root would pass selector
+ checks while failing to cover one root cycle.)
+
+The span identity restores that constraint for non-power-of-2 roots.
+The driver enforces it as a pre-flight check in
+``cxl_region_attach()``, before the per-port selector walk runs.
+A failed span check is rejected with the dmesg message
+``region ways*gran (W*G) != root ways*gran (W*G)``.
+
+Mixed-Granularity Rules for 3-Way Roots
+---------------------------------------
+
+The general power-of-2 mixed-gran rules apply to the 3-way family
+with two adjustments:
+
+* ``region_gran <= root_gran`` is allowed; ``region_gran > root_gran``
+ is not. (Same as power-of-2.)
+* Both granularities must be powers of 2. (Same as power-of-2; the
+ 3-way component is in the *ways*, not the granularity.)
+* The ratio ``root_gran / region_gran`` is the number of inner
+ position slots packed beneath one root target. (Same as
+ power-of-2; the value-space root advances by ``root_gran``
+ whether or not it consumes selector bits.)
+* ``region_ways * region_gran == root_ways * root_gran`` must hold.
+ (3-way only. Power-of-2 roots get this for free from selector
+ containment.)
+* Mixed-granularity is **required** for any 6-way or 12-way region
+ on a 3-way root, because no same-granularity arrangement can hold
+ the span identity. (3-way only; for power-of-2 mixed-gran is
+ optional.)
+
+
+Position Arithmetic
+===================
+
+Position is the index ``[0, region_ways)`` that orders an endpoint
+within the region. The driver derives an endpoint's position by
+walking the topology from endpoint up to root.
+
+For same-granularity regions the standard recurrence applies at every
+level::
+
+ pos = pos * parent_ways + parent_pos
+
+The root's targets cycle at the innermost selector bit, so the root
+contributes the position MSBs naturally.
+
+For mixed-granularity regions the root's targets cycle more slowly
+than the switches' targets - by exactly ``stride = root_gran /
+region_gran`` inner positions per root target. Endpoint positions
+within one root target are packed consecutively, so the root's
+contribution is rescaled at the root boundary::
+
+ pos = pos + stride * parent_pos (root iteration only)
+
+For a 3-way root the same packing applies; ``stride`` is still
+``root_gran / region_gran`` whenever the granularities differ.
+
+Peer-Distance and the check_last_peer Walk
+------------------------------------------
+
+When two endpoints share a downstream port at some intermediate
+level, ``check_last_peer()`` verifies that the peer at
+``pos - peer_distance`` lives behind the same dport as the endpoint
+being attached. ``peer_distance`` is the number of region positions
+between two consecutive endpoints that share a dport at the port
+under inspection.
+
+The seed of ``peer_distance`` depends on the layout the position
+recurrence builds:
+
+* Same-granularity layout (``stride == 1``) is multiplicative at the
+ root: ``pos = pos * root_ways + parent_pos``. Changing the root's
+ ``parent_pos`` by 1 shifts each inner-position group by ``root_ways``
+ region positions, so peer distance at any sub-root port carries a
+ factor of ``root_ways``. Seed: ``root_ways``.
+
+* Mixed-granularity layout (``stride > 1``) is additive at the root:
+ ``pos = pos + stride * parent_pos``. Changing the root's
+ ``parent_pos`` shifts every endpoint by the same constant ``stride``;
+ peer distances among endpoints under one root target do not depend
+ on ``root_ways``. Seed: ``1``.
+
+Inner ports contribute their ``nr_targets`` multiplicatively as the
+walk descends, and the current port's ``iw`` folds in at the end.
+
+Example: 6-way region on a 3-way root (CXL Spec 4.0 Table 9-7)
+--------------------------------------------------------------
+
+Configuration: ``root_ways = 3`` at ``2*IGB``, ``region_ways = 6``
+at ``IGB``, with a 2-way host-bridge interleave at ``IGB`` beneath
+each root target. ``stride = 2*IGB / IGB = 2``.
+
+Position layout::
+
+ slot: 0 1 2 3 4 5
+ target: H0,a H0,b H1,a H1,b H2,a H2,b
+
+Positions {0,1} share root target H0, {2,3} share H1, {4,5} share
+H2. ``cxl_calc_interleave_pos()`` produces these directly via the
+mixed-gran root branch ``pos = pos + stride * parent_pos``: for the
+endpoint at H1 inner-slot ``b`` (HB-position 1), the recurrence
+yields ``pos = 1 + 2 * 1 = 3``.
+
+The 3-way root consumes zero HPA selector bits; the host-bridge
+2-way interleave contributes the region's only selector bit. The
+span identity ``6 * IGB == 3 * 2*IGB`` holds.
+
+Example: 8-way mixed-gran region on a 4-way power-of-2 root
+-----------------------------------------------------------
+
+Configuration: ``root_ways = 4``, ``root_gran = 4096``,
+``region_ways = 8``, ``region_gran = 2048``, with a 2-way
+host-bridge interleave at 2KB beneath each root target.
+``stride = 4096 / 2048 = 2``.
+
+Position layout (one root cycle covers 16KB)::
+
+ HPA byte: 0 2KB 4KB 6KB 8KB 10KB 12KB 14KB
+ position: 0 1 2 3 4 5 6 7
+ target: H0,a H0,b H1,a H1,b H2,a H2,b H3,a H3,b
+
+Positions {0,1} share host bridge H0, {2,3} share H1, and so on.
+For the endpoint at H2 inner-slot ``b`` (HB-position 1), the
+recurrence yields ``pos = 1 + 2 * 2 = 5``.
+
+A same-granularity 8-way region on the same root would use the
+standard multiplicative recurrence at every level. The additive
+root branch is what places endpoints correctly when the root sits
+at outer HPA bits and switches beneath it interleave at finer
+granularity.
+
+
+Linux Driver Implementation
+===========================
+
+Region geometry validation lives in ``drivers/cxl/core/region.c``.
+The helpers and enforcement points below implement the rules
+described in the preceding sections.
+
+``get_selector(ways, gran)``
+ Each interleave level consumes a contiguous range of HPA bits to
+ pick its target. ``get_selector()`` models that range as a bitmask:
+ ``(ways - 1) * gran`` for power-of-2 interleaves. A strict 3-way
+ interleave consumes no HPA bits and the helper returns zero;
+ 6-way and 12-way are folded to expose only their power-of-2
+ component.
+
+ Kernel-doc: :c:func:`get_selector`.
+
+``root_pos_stride(cxlr)``
+ When ``region_gran < root_gran``, each root target owns more than
+ one region position. The number of positions packed under one root
+ target is ``root_gran / region_gran``: the root advances by
+ ``root_gran`` bytes per target, while region positions advance by
+ ``region_gran`` bytes. Same-granularity regions have a stride of
+ one because the root advances on every region position.
+
+ ``root_pos_stride()`` returns this ratio for the position walk and
+ the peer-distance computation. The same value applies to 3-way
+ family roots: in value space they advance by ``root_gran`` per
+ target whether or not they consume HPA selector bits.
+
+ Kernel-doc: :c:func:`root_pos_stride`.
+
+``is_ig_allowed(cxlrd, ig)``
+ The region granularity must not exceed the root granularity.
+ ``region_gran > root_gran`` would place the root selector inside a
+ single region stride, leaving the root no address bits to choose
+ among its targets. ``region_gran <= root_gran`` is required for
+ every legal layout.
+
+ ``is_ig_allowed()`` enforces that geometric rule. It returns true
+ when the root is not interleaving, or when ``ig`` does not exceed
+ the root granularity. Endpoint capability (the IG groups a memdev
+ advertises) is checked separately at attach time via
+ ``check_interleave_cap()``.
+
+ Kernel-doc: :c:func:`is_ig_allowed`.
+
+``cxl_port_setup_targets()``
+ This is the per-port enforcement point for the multi-level rule.
+ For each level between the current port and the root the function:
+
+ * Rejects overlap. Two levels that share a selector bit fail the
+ disjoint requirement.
+ * Rejects escapes. The accumulated selector must fit within the
+ region selector. A level whose claimed bits land outside the
+ region selector fails the containment requirement.
+ * Derives the granularity to program at the current port for
+ user-created regions. Each interleaving level claims the lowest
+ selector bit still available within the region selector;
+ passthrough levels consume no selector bit and inherit a scaled
+ granularity from their ancestors.
+
+ Auto regions reuse the same selector checks but trust the
+ granularity firmware already programmed at each level.
+
+``cxl_region_attach()``
+ Pre-flight gates that run once per region attach, before the
+ per-port selector walk.
+
+ * Auto regions bypass the sysfs granularity gate, so
+ ``is_ig_allowed()`` is applied here to give them the same
+ geometric check as user-created regions.
+ * 3-way-family roots require an explicit span check. A strict
+ 3-way root consumes no HPA selector bits, so selector containment
+ cannot prove that the root and region cover the same address
+ span. The span identity
+ ``region_ways * region_gran == root_ways * root_gran`` restores
+ that constraint. Power-of-2 roots get the equivalent constraint
+ from selector containment and are not affected by this check.
+ See `Span Identity for 3-Way-Family Roots`_ for the full
+ derivation.
+
+Position calculations use ``root_pos_stride()`` so that both
+same-granularity and mixed-granularity regions produce the correct
+endpoint ordering. The position walk itself lives in
+``cxl_calc_interleave_pos()``.
+
+Kernel-doc: :c:func:`cxl_calc_interleave_pos`.
+
+
+Auto vs User-Created Regions
+============================
+
+**Auto regions** are reconstructed at boot from the decoder values
+firmware programmed. The driver reads each decoder's ``ways`` and
+``granularity``, runs them through the selector walk, and accepts
+the configuration if it passes the disjoint and containment checks
+(plus the span identity for 3-way roots). Firmware is the source of
+truth for the granularity at each level.
+
+**User-created regions** specify ``interleave_ways`` and
+``interleave_granularity`` through sysfs; the driver derives each
+intermediate decoder's granularity. The sysfs path runs
+``is_ig_allowed()`` at granularity-write time, ``set_interleave_ways()``
+at ways-write time, and the full selector walk when the region is
+attached and committed.
+
+Both paths converge on the same geometry validation rules.
+Differences between the paths are limited to how region parameters
+are obtained and when they are validated.
+
+* Whether each intermediate decoder's granularity is read from
+ hardware (auto) or derived (user).
+* Whether the membership of the region is established by walking
+ firmware-programmed decoders (auto) or by user writes to ``targetN``
+ attributes (user).
--
2.37.3
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 1/6] cxl/region: Validate interleave selector bits
2026-05-30 0:00 ` [PATCH 1/6] cxl/region: Validate interleave selector bits Alison Schofield
@ 2026-05-30 0:19 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-05-30 0:19 UTC (permalink / raw)
To: Alison Schofield; +Cc: linux-cxl
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The patch removes interleave granularity validation for auto-discovered regions, leaving the current port's granularity completely unvalidated.
--
commit a2adec3549842d4a09c81232586828cb3b57099d
Author: Alison Schofield <alison.schofield@intel.com>
cxl/region: Validate interleave selector bits
This commit updates multi-level interleave validation to account for
selector bits from the root toward the current port. It accumulates
selector bits, rejects overlaps, and removes the auto-region granularity
equality check, relying on the new selector walk instead.
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index e50dc716d4e82..87fb1a9f2683f 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
[ ... ]
> @@ -1452,28 +1475,69 @@ static int cxl_port_setup_targets(struct cxl_port *port,
> }
>
> cxlsd = to_cxl_switch_decoder(&cxld->dev);
> - if (cxl_rr->nr_targets_set) {
> - int i, distance = 1;
> - struct cxl_region_ref *cxl_rr_iter;
> + iw = cxl_rr->nr_targets;
[ ... ]
> + /* Accumulated bits must fit within the region selector */
> + cxlr_sel = get_selector(p->interleave_ways, p->interleave_granularity);
> + if ((cxlr_sel & selector) != selector) {
> + dev_dbg(&cxlr->dev,
> + "%s:%s: invalid selectors: cxlr %#llx accum %#llx\n",
> + dev_name(port->uport_dev), dev_name(&port->dev),
> + cxlr_sel, selector);
> + return -ENXIO;
> + }
>
> - for (i = 0; i < cxl_rr->nr_targets_set; i++)
> + /* Selector bits still available to this port. */
> + selector = cxlr_sel & ~selector;
[Severity: High]
Does this code leave the current port's granularity unvalidated?
The available selector bits are calculated here, but the selector variable
appears to act as a dead store and is never used to validate the current
port's granularity.
I understand this is fixed in a later patch in the series (commit
0b56c23b98964cba7ba9487773eeb9e989d957d3 cxl/region: Derive port
granularity from selector bits). However, shouldn't each patch in the
series be self-contained to avoid introducing temporary regressions
during bisection?
> +
> + if (cxl_rr->nr_targets_set) {
> + for (int i = 0; i < cxl_rr->nr_targets_set; i++)
[ ... ]
> @@ -1484,87 +1548,20 @@ static int cxl_port_setup_targets(struct cxl_port *port,
> goto add_target;
> }
[ ... ]
> - if (iw > 8 || iw > cxlsd->nr_targets) {
> - dev_dbg(&cxlr->dev,
> - "%s:%s:%s: ways: %d overflows targets: %d\n",
> - dev_name(port->uport_dev), dev_name(&port->dev),
> - dev_name(&cxld->dev), iw, cxlsd->nr_targets);
> - return -ENXIO;
> - }
> -
> if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
> if (cxld->interleave_ways != iw ||
> - (iw > 1 && cxld->interleave_granularity != ig) ||
> !spa_maps_hpa(p, &cxld->hpa_range) ||
> ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
> dev_err(&cxlr->dev,
[Severity: High]
Is the removal of this explicit granularity check intentional before the
functional replacement using the selector mask is fully implemented?
By removing this check and not using the calculated selector mask
for the current port in cxl_port_setup_targets(), the lowest switch port's
firmware-programmed granularity goes completely unvalidated.
Could this silently accept overlapping or mismatched interleave
granularities, potentially causing incorrect CXL memory region routing?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780095671.git.alison.schofield@intel.com?part=1
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/6] cxl/region: Derive port granularity from selector bits
2026-05-30 0:00 ` [PATCH 2/6] cxl/region: Derive port granularity from " Alison Schofield
@ 2026-05-30 0:26 ` sashiko-bot
2026-06-03 23:51 ` Dave Jiang
1 sibling, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-05-30 0:26 UTC (permalink / raw)
To: Alison Schofield; +Cc: linux-cxl
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Passthrough decoders (`iw == 1`) in deep topologies fail to configure because calculated `ig` exceeds software limits, breaking valid configurations.
--
commit 0b56c23b98964cba7ba9487773eeb9e989d957d3
Author: Alison Schofield <alison.schofield@intel.com>
cxl/region: Derive port granularity from selector bits
Selector-bit validation uses a single same-granularity formula for
user-created regions. This commit changes the layout rules to derive
the programmed port granularity from selector bits, which lets an
interleaving switch beneath a mixed-granularity root claim inner
selector bits instead of colliding.
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index 87fb1a9f2683f..9d310287d48dc 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
> @@ -1548,7 +1548,23 @@ static int cxl_port_setup_targets(struct cxl_port *port,
> goto add_target;
> }
>
> - ig = p->interleave_granularity * parent_distance;
> + /*
> + * Auto regions keep the firmware value, passthrough decoders consume
> + * no selector bits, interleaving decoders claim the lowest available
> + * selector bit.
> + */
> + if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
> + ig = cxld->interleave_granularity;
> + } else if (iw == 1) {
> + ig = p->interleave_granularity * parent_distance;
[Severity: High]
This is a pre-existing issue, but does this calculation break passthrough
decoders in deep topologies?
If we have a large root interleave width (e.g., an 8-way root),
parent_distance evaluates to 8. For a standard 4K base region granularity,
ig is calculated as 4096 * 8 = 32768.
Later in cxl_port_setup_targets(), we unconditionally call:
rc = ways_to_eiw(iw, &eiw);
if (!rc)
rc = granularity_to_eig(ig, &eig);
if (rc) {
...
return rc;
}
And granularity_to_eig() explicitly checks for `granularity > SZ_16K`
returning -EINVAL. Since hardware ignores interleave granularity for
passthrough decoders (iw == 1), this seems to artificially reject valid
hardware topologies by failing the configuration.
> + } else if (selector) {
> + ig = 1ULL << __ffs64(selector);
> + } else {
> + dev_dbg(&cxlr->dev,
> + "%s:%s: no selector bits available for iw %d\n",
> + dev_name(port->uport_dev), dev_name(&port->dev), iw);
> + return -ENXIO;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780095671.git.alison.schofield@intel.com?part=2
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5/6] cxl/test: Add a topology to test mixed-granularity regions
2026-05-30 0:00 ` [PATCH 5/6] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
@ 2026-05-30 5:17 ` sashiko-bot
2026-06-04 0:04 ` Dave Jiang
1 sibling, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-05-30 5:17 UTC (permalink / raw)
To: Alison Schofield; +Cc: linux-cxl
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Evaluating `&array[i]->dev` on uninitialized legacy topology arrays causes Undefined Behavior when `mixed_gran_regions=1`.
--
commit 8ff651f212fbb657c03e4fc54d032f721af040e6
Author: Alison Schofield <alison.schofield@intel.com>
cxl/test: Add a topology to test mixed-granularity regions
This commit adds a cxl_test topology for mixed-granularity region coverage.
Setting the module parameter mixed_gran_regions=1 creates three host bridges
with two switch levels below each bridge and twelve endpoints total.
> diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
> index 418669927fb00..b44db749873f3 100644
> --- a/tools/testing/cxl/test/cxl.c
> +++ b/tools/testing/cxl/test/cxl.c
[ ... ]
> @@ -130,6 +172,9 @@ static bool is_mock_dev(struct device *dev)
> for (i = 0; i < ARRAY_SIZE(cxl_rcd); i++)
> if (dev == &cxl_rcd[i]->dev)
> return true;
[Severity: Medium]
When mixed_gran_regions is enabled, cxl_test_init() skips initializing the
legacy topology arrays by jumping to the topo_done label. Could evaluating
&cxl_rcd[i]->dev (and similar legacy arrays like cxl_mem, cxl_host_bridge)
cause undefined behavior here since those arrays are now left filled with
NULL pointers?
> + for (i = 0; i < ARRAY_SIZE(cxl_mem_mix_gran); i++)
> + if (cxl_mem_mix_gran[i] && dev == &cxl_mem_mix_gran[i]->dev)
> + return true;
The new arrays correctly check for NULL before evaluating the dev pointer
offset, but the legacy array loops preceding it in is_mock_dev(),
is_mock_bridge(), and is_mock_port() do not. This also affects functions
like mock_init_hdm_decoder() which unconditionally checks against
&cxl_host_bridge[0]->dev.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780095671.git.alison.schofield@intel.com?part=5
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/6] cxl/region: Derive port granularity from selector bits
2026-05-30 0:00 ` [PATCH 2/6] cxl/region: Derive port granularity from " Alison Schofield
2026-05-30 0:26 ` sashiko-bot
@ 2026-06-03 23:51 ` Dave Jiang
1 sibling, 0 replies; 15+ messages in thread
From: Dave Jiang @ 2026-06-03 23:51 UTC (permalink / raw)
To: Alison Schofield, Davidlohr Bueso, Jonathan Cameron, Vishal Verma,
Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
On 5/29/26 5:00 PM, Alison Schofield wrote:
> Selector-bit validation uses a single same-granularity formula
> for user-created regions. Mixed-granularity layouts need the
> programmed granularity to depend on the type of decoder being
> configured.
>
> Derive port granularity from the selector state:
>
> AUTO: keep the firmware-programmed value; the walk validated it.
> iw == 1: passthrough; consume no selector bits and span the parent
> stride.
> iw > 1: claim the lowest selector bit still available to this
> level.
>
> This lets an interleaving switch beneath a mixed-granularity root
> claim an inner selector bit instead of colliding with the outer bit
> already owned by the root.
>
> Add a final check that the derived selector fits within the
> remaining selector bits.
>
> Originally-by: Robert Richter <rrichter@amd.com>
> Signed-off-by: Alison Schofield <alison.schofield@intel.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
> ---
> drivers/cxl/core/region.c | 26 +++++++++++++++++++++++++-
> 1 file changed, 25 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index 87fb1a9f2683..9d310287d48d 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
> @@ -1548,7 +1548,23 @@ static int cxl_port_setup_targets(struct cxl_port *port,
> goto add_target;
> }
>
> - ig = p->interleave_granularity * parent_distance;
> + /*
> + * Auto regions keep the firmware value, passthrough decoders consume
> + * no selector bits, interleaving decoders claim the lowest available
> + * selector bit.
> + */
> + if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
> + ig = cxld->interleave_granularity;
> + } else if (iw == 1) {
> + ig = p->interleave_granularity * parent_distance;
> + } else if (selector) {
> + ig = 1ULL << __ffs64(selector);
> + } else {
> + dev_dbg(&cxlr->dev,
> + "%s:%s: no selector bits available for iw %d\n",
> + dev_name(port->uport_dev), dev_name(&port->dev), iw);
> + return -ENXIO;
> + }
>
> rc = ways_to_eiw(iw, &eiw);
> if (!rc)
> @@ -1560,6 +1576,14 @@ static int cxl_port_setup_targets(struct cxl_port *port,
> return rc;
> }
>
> + if (iw > 1 && (~selector & get_selector(iw, ig))) {
> + dev_dbg(&cxlr->dev,
> + "%s:%s: derived selector %#llx exceeds remaining %#llx (iw %d ig %d)\n",
> + dev_name(port->uport_dev), dev_name(&port->dev),
> + get_selector(iw, ig), selector, iw, ig);
> + return -ENXIO;
> + }
> +
> if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
> if (cxld->interleave_ways != iw ||
> !spa_maps_hpa(p, &cxld->hpa_range) ||
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/6] cxl/region: Validate mixed-granularity at sysfs and attach gates
2026-05-30 0:00 ` [PATCH 4/6] cxl/region: Validate mixed-granularity at sysfs and attach gates Alison Schofield
@ 2026-06-03 23:55 ` Dave Jiang
0 siblings, 0 replies; 15+ messages in thread
From: Dave Jiang @ 2026-06-03 23:55 UTC (permalink / raw)
To: Alison Schofield, Davidlohr Bueso, Jonathan Cameron, Vishal Verma,
Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
On 5/29/26 5:00 PM, Alison Schofield wrote:
> User-created regions are validated at sysfs write through
> set_interleave_granularity(). Auto regions skip that path. Both
> need the same mixed-granularity rule applied before the per-port
> selector walk runs.
>
> Replace the same-granularity sysfs check with is_ig_allowed(),
> which permits any region granularity not greater than the root
> granularity. Call the same helper at attach time so auto regions
> get the same rule.
>
> 3-way-family roots (3, 6, and 12 ways) need an additional check.
> A 3-way interleave consumes no selector bits, so selector containment
> alone cannot prove that the root and region cover the same address
> span. Add an explicit ways*gran span equality check in
> cxl_region_attach() for these roots.
>
> Endpoint decoder capability is still checked at attach time by
> check_interleave_cap().
>
> Signed-off-by: Alison Schofield <alison.schofield@intel.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
A nit below.
> ---
> drivers/cxl/core/region.c | 57 ++++++++++++++++++++++++++++++++-------
> 1 file changed, 47 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index cc8ab2ad5228..61e6bb73249e 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
> @@ -559,10 +559,29 @@ static ssize_t interleave_granularity_show(struct device *dev,
> return sysfs_emit(buf, "%d\n", p->interleave_granularity);
> }
>
> +/**
> + * is_ig_allowed() - Validate region interleave granularity
> + * @cxlrd: root decoder
> + * @ig: proposed region interleave granularity in bytes
> + *
> + * Callers must have validated @ig through granularity_to_eig() first.
> + *
> + * Return: true for a non-interleaving root or any region granularity
> + * not larger than the root 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 +590,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);
> @@ -2128,6 +2139,32 @@ static int cxl_region_attach(struct cxl_region *cxlr,
> return -ENXIO;
> }
>
> + /* Apply the sysfs granularity rule to auto regions too. */
> + 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,
> + cxlrd->cxlsd.cxld.interleave_ways,
> + cxlrd->cxlsd.cxld.interleave_granularity);
> + return -ENXIO;
> + }
> +
> + /*
> + * Selector containment cannot prove span equality for 3-way family
> + * roots because a 3-way interleave consumes no selector bits.
> + */
> + if (!is_power_of_2(cxlrd->cxlsd.cxld.interleave_ways) &&
> + (u64)p->interleave_ways * p->interleave_granularity !=
> + (u64)cxlrd->cxlsd.cxld.interleave_ways *
> + cxlrd->cxlsd.cxld.interleave_granularity) {
Looks like cxlrd->cxlsd.cxld.interleave_ways and interleave_granularity used in a few different places. Maybe use a local var to not having to keep referencing them?
DJ
> + dev_dbg(&cxlr->dev,
> + "region ways*gran (%d*%d) != root ways*gran (%d*%d)\n",
> + p->interleave_ways, p->interleave_granularity,
> + cxlrd->cxlsd.cxld.interleave_ways,
> + cxlrd->cxlsd.cxld.interleave_granularity);
> + return -ENXIO;
> + }
> +
> if (p->nr_targets >= p->interleave_ways) {
> dev_dbg(&cxlr->dev, "region already has %d endpoints\n",
> p->nr_targets);
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide
2026-05-30 0:00 ` [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide Alison Schofield
@ 2026-06-03 23:57 ` Dave Jiang
2026-06-04 3:47 ` kernel test robot
1 sibling, 0 replies; 15+ messages in thread
From: Dave Jiang @ 2026-06-03 23:57 UTC (permalink / raw)
To: Alison Schofield, Davidlohr Bueso, Jonathan Cameron, Vishal Verma,
Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
On 5/29/26 5:00 PM, Alison Schofield wrote:
> Document how region and root decoder granularities interact for
> same-granularity, power-of-2 mixed-granularity, and 3-way-family
> interleaves.
>
> Explain the selector-bit model used by the CXL specification, the
> legal layouts that result from it, the additional span constraint
> required by 3-way-family roots, and how the Linux driver validates
> those rules for both auto and user-created regions.
>
> Assisted-by: Claude Opus 4.7
> Signed-off-by: Alison Schofield <alison.schofield@intel.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
> ---
> Documentation/driver-api/cxl/index.rst | 1 +
> .../cxl/linux/region-granularity.rst | 486 ++++++++++++++++++
> 2 files changed, 487 insertions(+)
> create mode 100644 Documentation/driver-api/cxl/linux/region-granularity.rst
>
> diff --git a/Documentation/driver-api/cxl/index.rst b/Documentation/driver-api/cxl/index.rst
> index 3dfae1d310ca..1ae193ea4020 100644
> --- a/Documentation/driver-api/cxl/index.rst
> +++ b/Documentation/driver-api/cxl/index.rst
> @@ -42,6 +42,7 @@ that have impacts on each other. The docs here break up configurations steps.
> linux/dax-driver
> linux/memory-hotplug
> linux/access-coordinates
> + linux/region-granularity
>
> .. toctree::
> :maxdepth: 2
> diff --git a/Documentation/driver-api/cxl/linux/region-granularity.rst b/Documentation/driver-api/cxl/linux/region-granularity.rst
> new file mode 100644
> index 000000000000..7d0d12e6e2bd
> --- /dev/null
> +++ b/Documentation/driver-api/cxl/linux/region-granularity.rst
> @@ -0,0 +1,486 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +==============================================
> +Region Granularity and Multi-Level Interleave
> +==============================================
> +
> +CXL memory regions stripe accesses across one or more endpoints by
> +routing transactions through a tree of decoders: a root decoder, zero
> +or more switch decoders, and an endpoint decoder at each leaf. Each
> +decoder in that path uses a contiguous range of HPA address bits to
> +pick its target. This document describes how those bit ranges combine
> +across levels, the constraints CXL Spec 4.0 places on legal
> +combinations, and how the Linux CXL driver validates and programs
> +them.
> +
> +The relevant spec material is CXL Spec 4.0 Section 9.13.1, especially
> +Section 9.13.1.1 ("Legal Interleaving Configurations") and CXL Spec
> +4.0 Tables 9-6, 9-7, and 9-8. CXL Spec 4.0 Figure 9-17 (two-level)
> +and Figure 9-18 (three-level) illustrate the bit-routing intuition
> +this document builds on.
> +
> +
> +HPA Selector Bits
> +=================
> +
> +For a single interleave level with ``W`` ways and granularity ``G``
> +bytes, the **selector bits** are the ``log2(W)`` HPA bits the decoder
> +inspects to choose among its ``W`` targets. The lowest of those bits
> +sits at position ``log2(G)``, so the selector spans
> +``[log2(G) + log2(W) - 1 : log2(G)]``. Equivalently, the bitmask is
> +``(W - 1) * G``.
> +
> +A few examples make the geometry concrete::
> +
> + 2-way at 256B -> selector bit 8 (one bit)
> + 4-way at 1KB -> selector bits[11:10] (two bits)
> + 2-way at 4KB -> selector bit 12 (one bit)
> + 8-way at 1KB -> selector bits[12:10] (three bits)
> +
> +Throughout this document "inner bit" means a lower-numbered HPA bit
> +(smaller stride, faster cycling among targets) and "outer bit" means
> +a higher-numbered HPA bit (larger stride, slower cycling). HPA[8] is
> +the innermost legal CXL selector bit; HPA[14] is the outermost.
> +
> +
> +The Multi-Level Rule
> +====================
> +
> +When a region's interleave is split across multiple decoder levels,
> +CXL Spec 4.0 Section 9.13.1 requires that:
> +
> + "all the levels use different, but consecutive, HPA bits to
> + select the target and no Interleave Set has more than eight
> + devices."
> +
> +Two requirements live in that sentence:
> +
> +* **Disjoint** - each level's selector bits must not overlap any
> + other level's bits.
> +* **Consecutive** - the union of all level selectors must be a single
> + contiguous run of HPA bits with no gap.
> +
> +The union of level selectors is the **region selector**, and it must
> +equal ``(region_ways - 1) * region_gran``. Because that mask is itself
> +a contiguous run by construction, the levels just need to partition
> +it cleanly.
> +
> +The seven legal interleave granularity values split into two groups
> +(see CXL Spec 4.0 Section 9.13.1 and Table 8-116):
> +
> +* **Group 1** - 256B, 512B, 1024B, 2048B (interleaving on HPA[8..11])
> +* **Group 2** - 4096B, 8192B, 16384B (interleaving on HPA[12..14])
> +
> +CXL host bridges and switches must support every value in both
> +groups. CXL memory devices must support at least one group, advertised
> +through their HDM Decoder Capability register.
> +
> +
> +Same-Granularity Interleave
> +===========================
> +
> +The simplest case is ``region_gran == root_gran``. Each level's
> +granularity equals the next inner level's IG times that level's
> +ways. The region selector bits stack inside-out from the root.
> +
> +Example: 4-way region on a 2-way root, 256B everywhere::
> +
> + Level Ways Granularity Selector bits
> + ------ ---- ----------- -------------
> + Root 2 256B bit 8
> + Switch 2 512B bit 9
> + Region 4 256B bits[9:8]
> +
> +Each level's IG sits one bit position above the next inner level's
> +selector. The region's effective selector spans ``[9:8]``, two bits,
> +matching ``(4 - 1) * 256``.
> +
> +
> +Mixed-Granularity Interleave (Power-of-2)
> +=========================================
> +
> +A region's granularity may be **less than** the root decoder's
> +granularity. In that case the root claims an outer selector bit and
> +the switches beneath it claim inner bits. The arrangement is legal
> +whenever both granularities are powers of 2 and the resulting bits
> +remain disjoint and consecutive.
> +
> +Two-Level Example (CXL Spec 4.0 Figure 9-17)
> +--------------------------------------------
> +
> +8-way region, 2-way root at 4KB, 4-way switch at 1KB::
> +
> + Level Ways Granularity Selector bits
> + ------ ---- ----------- -------------
> + Root 2 4KB bit 12
> + Switch 4 1KB bits[11:10]
> + Region 8 1KB bits[12:10]
> +
> +The host bridge bisects the address range at 4KB granularity, sending
> +one half to each root port. Beneath each root port a 4-way switch
> +splits its half into four 1KB chunks. To each endpoint the region
> +appears as an 8-way interleave at 1KB based on bits[12:10]; the
> +leftmost endpoint receives 1KB chunks at HPA bases 0, 8KB, 16KB, ...
> +
> +Three-Level Example (CXL Spec 4.0 Figure 9-18)
> +----------------------------------------------
> +
> +8-way region across cross-HB / HB / switch, all 2-way::
> +
> + Level Ways Granularity Selector bits
> + ----------- ---- ----------- -------------
> + Cross-HB 2 4KB bit 12
> + Host bridge 2 2KB bit 11
> + Switch 2 1KB bit 10
> + Region 8 1KB bits[12:10]
> +
> +Three levels of 2-way interleave each contribute one bit. Total
> +endpoints: ``2 * 2 * 2 = 8``. Selector union:
> +``bit 12 | bit 11 | bit 10`` = ``bits[12:10]``. Disjoint and
> +consecutive.
> +
> +General Power-of-2 Rules
> +------------------------
> +
> +For power-of-2 ways at every level:
> +
> +* ``region_gran <= root_gran`` is allowed; ``region_gran > root_gran``
> + is not (the root's selector would land inside one region stride).
> +* Both granularities must be powers of 2.
> +* The ratio ``root_gran / region_gran`` is the number of inner
> + position slots packed beneath one root target. Endpoint position is
> + partitioned by root target rather than interleaved across them.
> +* Mixed-granularity is always optional for power-of-2 topologies. A
> + same-granularity arrangement always exists for the same set of
> + endpoints. Firmware may prefer mixed-granularity to expose a finer
> + switch-level interleave to the CPU.
> +
> +
> +3-Way Family Interleave
> +=======================
> +
> +CXL Spec 4.0 Section 9.13.1.1 defines a non-power-of-2 family of
> +interleave widths: 3, 6, and 12. These are not pure bit selectors.
> +A 3-way root divides the address by 3 in value-space (modulo or XOR
> +arithmetic, see CXL Spec 4.0 Section 9.18) rather than picking a
> +target from a contiguous bit range. As a consequence:
> +
> +* A strict 3-way root consumes **zero** HPA selector bits of its own.
> +* Only the power-of-2 component of the region width contributes
> + selector bits. For a 6-way region, the bit-selector component is
> + 2-way; for 12-way, 4-way.
> +* Mixed-granularity is **required** for any 6-way or 12-way region on
> + a 3-way root. There is no same-granularity alternative.
> +
> +Legal 3-Way-Family Configurations
> +---------------------------------
> +
> +CXL Spec 4.0 Tables 9-6, 9-7, and 9-8 enumerate the legal
> +configurations. IGB is the device-level interleave granularity.
> +
> +Table 9-6: 12-way device-level interleave at IGB::
> +
> + Cross-host bridge Root complex CXL switch
> + ----------------- -------------- --------------
> + 12-way at IGB no interleave no interleave
> + 6-way at 2*IGB 2-way at IGB no interleave
> + 6-way at 2*IGB no interleave 2-way at IGB
> + 3-way at 4*IGB 4-way at IGB no interleave
> + 3-way at 4*IGB no interleave 4-way at IGB
> + 3-way at 4*IGB 2-way at IGB 2-way at 2*IGB
> + 3-way at 4*IGB 2-way at 2*IGB 2-way at IGB
> +
> +Table 9-7: 6-way device-level interleave at IGB::
> +
> + Cross-host bridge CXL host bridge CXL switch
> + ----------------- --------------- --------------
> + 6-way at IGB no interleave no interleave
> + 3-way at 2*IGB 2-way at IGB no interleave
> + 3-way at 2*IGB no interleave 2-way at IGB
> +
> +Table 9-8: 3-way device-level interleave at IGB::
> +
> + Cross-host bridge CXL host bridge CXL switch
> + ----------------- --------------- --------------
> + 3-way at IGB no interleave no interleave
> +
> +Span Identity for 3-Way-Family Roots
> +------------------------------------
> +
> +For a 3-way (or 6-way, 12-way) root, the region must occupy exactly
> +one full cycle of the root's divide-by-3 pattern::
> +
> + region_ways * region_gran == root_ways * root_gran
> +
> +Every legal row of CXL Spec 4.0 Tables 9-6, 9-7, and 9-8 satisfies
> +this identity.
> +
> +Why power-of-2 roots do not need this check explicitly, and why
> +3-way roots do, comes down to how each cycles through its targets:
> +
> +* A power-of-2 root selects targets purely by HPA bits. The bits it
> + owns are part of the region selector mask
> + ``(region_ways - 1) * region_gran``. The disjoint and containment
> + checks on selector bits inside the region selector force
> + ``root_ways * root_gran`` to be a power-of-2 divisor of
> + ``region_ways * region_gran``, and the region size constraint
> + forces it to be exactly one such divisor. Span equality is a
> + by-product of bit accounting.
> +
> +* A 3-way root divides the address by 3 in value-space. It cycles
> + through three targets at every ``root_gran``-byte stride, but
> + consumes no HPA selector bits at all. The selector mask returned
> + by ``get_selector()`` for a strict 3-way is zero. Selector bit
> + accounting cannot see the address space the root reserves at
> + ``log2(root_gran)``, so it cannot rule out arrangements where
> + ``region_ways * region_gran`` is some multiple of
> + ``root_ways * root_gran`` other than one. (For example, a
> + same-granularity 6-way region on a 3-way root would pass selector
> + checks while failing to cover one root cycle.)
> +
> +The span identity restores that constraint for non-power-of-2 roots.
> +The driver enforces it as a pre-flight check in
> +``cxl_region_attach()``, before the per-port selector walk runs.
> +A failed span check is rejected with the dmesg message
> +``region ways*gran (W*G) != root ways*gran (W*G)``.
> +
> +Mixed-Granularity Rules for 3-Way Roots
> +---------------------------------------
> +
> +The general power-of-2 mixed-gran rules apply to the 3-way family
> +with two adjustments:
> +
> +* ``region_gran <= root_gran`` is allowed; ``region_gran > root_gran``
> + is not. (Same as power-of-2.)
> +* Both granularities must be powers of 2. (Same as power-of-2; the
> + 3-way component is in the *ways*, not the granularity.)
> +* The ratio ``root_gran / region_gran`` is the number of inner
> + position slots packed beneath one root target. (Same as
> + power-of-2; the value-space root advances by ``root_gran``
> + whether or not it consumes selector bits.)
> +* ``region_ways * region_gran == root_ways * root_gran`` must hold.
> + (3-way only. Power-of-2 roots get this for free from selector
> + containment.)
> +* Mixed-granularity is **required** for any 6-way or 12-way region
> + on a 3-way root, because no same-granularity arrangement can hold
> + the span identity. (3-way only; for power-of-2 mixed-gran is
> + optional.)
> +
> +
> +Position Arithmetic
> +===================
> +
> +Position is the index ``[0, region_ways)`` that orders an endpoint
> +within the region. The driver derives an endpoint's position by
> +walking the topology from endpoint up to root.
> +
> +For same-granularity regions the standard recurrence applies at every
> +level::
> +
> + pos = pos * parent_ways + parent_pos
> +
> +The root's targets cycle at the innermost selector bit, so the root
> +contributes the position MSBs naturally.
> +
> +For mixed-granularity regions the root's targets cycle more slowly
> +than the switches' targets - by exactly ``stride = root_gran /
> +region_gran`` inner positions per root target. Endpoint positions
> +within one root target are packed consecutively, so the root's
> +contribution is rescaled at the root boundary::
> +
> + pos = pos + stride * parent_pos (root iteration only)
> +
> +For a 3-way root the same packing applies; ``stride`` is still
> +``root_gran / region_gran`` whenever the granularities differ.
> +
> +Peer-Distance and the check_last_peer Walk
> +------------------------------------------
> +
> +When two endpoints share a downstream port at some intermediate
> +level, ``check_last_peer()`` verifies that the peer at
> +``pos - peer_distance`` lives behind the same dport as the endpoint
> +being attached. ``peer_distance`` is the number of region positions
> +between two consecutive endpoints that share a dport at the port
> +under inspection.
> +
> +The seed of ``peer_distance`` depends on the layout the position
> +recurrence builds:
> +
> +* Same-granularity layout (``stride == 1``) is multiplicative at the
> + root: ``pos = pos * root_ways + parent_pos``. Changing the root's
> + ``parent_pos`` by 1 shifts each inner-position group by ``root_ways``
> + region positions, so peer distance at any sub-root port carries a
> + factor of ``root_ways``. Seed: ``root_ways``.
> +
> +* Mixed-granularity layout (``stride > 1``) is additive at the root:
> + ``pos = pos + stride * parent_pos``. Changing the root's
> + ``parent_pos`` shifts every endpoint by the same constant ``stride``;
> + peer distances among endpoints under one root target do not depend
> + on ``root_ways``. Seed: ``1``.
> +
> +Inner ports contribute their ``nr_targets`` multiplicatively as the
> +walk descends, and the current port's ``iw`` folds in at the end.
> +
> +Example: 6-way region on a 3-way root (CXL Spec 4.0 Table 9-7)
> +--------------------------------------------------------------
> +
> +Configuration: ``root_ways = 3`` at ``2*IGB``, ``region_ways = 6``
> +at ``IGB``, with a 2-way host-bridge interleave at ``IGB`` beneath
> +each root target. ``stride = 2*IGB / IGB = 2``.
> +
> +Position layout::
> +
> + slot: 0 1 2 3 4 5
> + target: H0,a H0,b H1,a H1,b H2,a H2,b
> +
> +Positions {0,1} share root target H0, {2,3} share H1, {4,5} share
> +H2. ``cxl_calc_interleave_pos()`` produces these directly via the
> +mixed-gran root branch ``pos = pos + stride * parent_pos``: for the
> +endpoint at H1 inner-slot ``b`` (HB-position 1), the recurrence
> +yields ``pos = 1 + 2 * 1 = 3``.
> +
> +The 3-way root consumes zero HPA selector bits; the host-bridge
> +2-way interleave contributes the region's only selector bit. The
> +span identity ``6 * IGB == 3 * 2*IGB`` holds.
> +
> +Example: 8-way mixed-gran region on a 4-way power-of-2 root
> +-----------------------------------------------------------
> +
> +Configuration: ``root_ways = 4``, ``root_gran = 4096``,
> +``region_ways = 8``, ``region_gran = 2048``, with a 2-way
> +host-bridge interleave at 2KB beneath each root target.
> +``stride = 4096 / 2048 = 2``.
> +
> +Position layout (one root cycle covers 16KB)::
> +
> + HPA byte: 0 2KB 4KB 6KB 8KB 10KB 12KB 14KB
> + position: 0 1 2 3 4 5 6 7
> + target: H0,a H0,b H1,a H1,b H2,a H2,b H3,a H3,b
> +
> +Positions {0,1} share host bridge H0, {2,3} share H1, and so on.
> +For the endpoint at H2 inner-slot ``b`` (HB-position 1), the
> +recurrence yields ``pos = 1 + 2 * 2 = 5``.
> +
> +A same-granularity 8-way region on the same root would use the
> +standard multiplicative recurrence at every level. The additive
> +root branch is what places endpoints correctly when the root sits
> +at outer HPA bits and switches beneath it interleave at finer
> +granularity.
> +
> +
> +Linux Driver Implementation
> +===========================
> +
> +Region geometry validation lives in ``drivers/cxl/core/region.c``.
> +The helpers and enforcement points below implement the rules
> +described in the preceding sections.
> +
> +``get_selector(ways, gran)``
> + Each interleave level consumes a contiguous range of HPA bits to
> + pick its target. ``get_selector()`` models that range as a bitmask:
> + ``(ways - 1) * gran`` for power-of-2 interleaves. A strict 3-way
> + interleave consumes no HPA bits and the helper returns zero;
> + 6-way and 12-way are folded to expose only their power-of-2
> + component.
> +
> + Kernel-doc: :c:func:`get_selector`.
> +
> +``root_pos_stride(cxlr)``
> + When ``region_gran < root_gran``, each root target owns more than
> + one region position. The number of positions packed under one root
> + target is ``root_gran / region_gran``: the root advances by
> + ``root_gran`` bytes per target, while region positions advance by
> + ``region_gran`` bytes. Same-granularity regions have a stride of
> + one because the root advances on every region position.
> +
> + ``root_pos_stride()`` returns this ratio for the position walk and
> + the peer-distance computation. The same value applies to 3-way
> + family roots: in value space they advance by ``root_gran`` per
> + target whether or not they consume HPA selector bits.
> +
> + Kernel-doc: :c:func:`root_pos_stride`.
> +
> +``is_ig_allowed(cxlrd, ig)``
> + The region granularity must not exceed the root granularity.
> + ``region_gran > root_gran`` would place the root selector inside a
> + single region stride, leaving the root no address bits to choose
> + among its targets. ``region_gran <= root_gran`` is required for
> + every legal layout.
> +
> + ``is_ig_allowed()`` enforces that geometric rule. It returns true
> + when the root is not interleaving, or when ``ig`` does not exceed
> + the root granularity. Endpoint capability (the IG groups a memdev
> + advertises) is checked separately at attach time via
> + ``check_interleave_cap()``.
> +
> + Kernel-doc: :c:func:`is_ig_allowed`.
> +
> +``cxl_port_setup_targets()``
> + This is the per-port enforcement point for the multi-level rule.
> + For each level between the current port and the root the function:
> +
> + * Rejects overlap. Two levels that share a selector bit fail the
> + disjoint requirement.
> + * Rejects escapes. The accumulated selector must fit within the
> + region selector. A level whose claimed bits land outside the
> + region selector fails the containment requirement.
> + * Derives the granularity to program at the current port for
> + user-created regions. Each interleaving level claims the lowest
> + selector bit still available within the region selector;
> + passthrough levels consume no selector bit and inherit a scaled
> + granularity from their ancestors.
> +
> + Auto regions reuse the same selector checks but trust the
> + granularity firmware already programmed at each level.
> +
> +``cxl_region_attach()``
> + Pre-flight gates that run once per region attach, before the
> + per-port selector walk.
> +
> + * Auto regions bypass the sysfs granularity gate, so
> + ``is_ig_allowed()`` is applied here to give them the same
> + geometric check as user-created regions.
> + * 3-way-family roots require an explicit span check. A strict
> + 3-way root consumes no HPA selector bits, so selector containment
> + cannot prove that the root and region cover the same address
> + span. The span identity
> + ``region_ways * region_gran == root_ways * root_gran`` restores
> + that constraint. Power-of-2 roots get the equivalent constraint
> + from selector containment and are not affected by this check.
> + See `Span Identity for 3-Way-Family Roots`_ for the full
> + derivation.
> +
> +Position calculations use ``root_pos_stride()`` so that both
> +same-granularity and mixed-granularity regions produce the correct
> +endpoint ordering. The position walk itself lives in
> +``cxl_calc_interleave_pos()``.
> +
> +Kernel-doc: :c:func:`cxl_calc_interleave_pos`.
> +
> +
> +Auto vs User-Created Regions
> +============================
> +
> +**Auto regions** are reconstructed at boot from the decoder values
> +firmware programmed. The driver reads each decoder's ``ways`` and
> +``granularity``, runs them through the selector walk, and accepts
> +the configuration if it passes the disjoint and containment checks
> +(plus the span identity for 3-way roots). Firmware is the source of
> +truth for the granularity at each level.
> +
> +**User-created regions** specify ``interleave_ways`` and
> +``interleave_granularity`` through sysfs; the driver derives each
> +intermediate decoder's granularity. The sysfs path runs
> +``is_ig_allowed()`` at granularity-write time, ``set_interleave_ways()``
> +at ways-write time, and the full selector walk when the region is
> +attached and committed.
> +
> +Both paths converge on the same geometry validation rules.
> +Differences between the paths are limited to how region parameters
> +are obtained and when they are validated.
> +
> +* Whether each intermediate decoder's granularity is read from
> + hardware (auto) or derived (user).
> +* Whether the membership of the region is established by walking
> + firmware-programmed decoders (auto) or by user writes to ``targetN``
> + attributes (user).
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5/6] cxl/test: Add a topology to test mixed-granularity regions
2026-05-30 0:00 ` [PATCH 5/6] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
2026-05-30 5:17 ` sashiko-bot
@ 2026-06-04 0:04 ` Dave Jiang
1 sibling, 0 replies; 15+ messages in thread
From: Dave Jiang @ 2026-06-04 0:04 UTC (permalink / raw)
To: Alison Schofield, Davidlohr Bueso, Jonathan Cameron, Vishal Verma,
Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: linux-cxl
On 5/29/26 5:00 PM, Alison Schofield wrote:
> Add a cxl_test topology for mixed-granularity region coverage.
>
> modprobe cxl_test mixed_gran_regions=1 creates three host bridges
> with two switch levels below each bridge and twelve endpoints total.
> The topology advertises one 2-way root CFMWS for power-of-2
> mixed-granularity tests and one 3-way root CFMWS for 3-way-family
> tests.
>
> Leave the default and existing specialty topologies unchanged.
>
> Signed-off-by: Alison Schofield <alison.schofield@intel.com>
$ make -j `nproc` M=tools/testing/cxl
make[1]: Entering directory '/home/djiang5/git/kernel-review/tools/testing/cxl'
CC [M] test/cxl.o
test/cxl.c: In function ‘cxl_test_init’:
test/cxl.c:2358:17: error: label ‘err_rch’ used but not defined
2358 | goto err_rch;
|
Applied against 7.1-rc6.
> ---
> tools/testing/cxl/test/cxl.c | 474 +++++++++++++++++++++++++++++++++--
> 1 file changed, 452 insertions(+), 22 deletions(-)
>
> diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
> index 418669927fb0..b44db749873f 100644
> --- a/tools/testing/cxl/test/cxl.c
> +++ b/tools/testing/cxl/test/cxl.c
> @@ -17,6 +17,7 @@
> static int interleave_arithmetic;
> static bool extended_linear_cache;
> static bool fail_autoassemble;
> +static bool mixed_gran_regions;
>
> #define FAKE_QTG_ID 42
>
> @@ -26,7 +27,26 @@ static bool fail_autoassemble;
> #define NR_CXL_ROOT_PORTS 2
> #define NR_CXL_SWITCH_PORTS 2
> #define NR_CXL_PORT_DECODERS 8
> -#define NR_BRIDGES (NR_CXL_HOST_BRIDGES + NR_CXL_SINGLE_HOST + NR_CXL_RCH)
> +
> +/*
> + * mixed_gran_regions=1: three host bridges, two switch levels, and
> + * twelve endpoints for power-of-2 and 3-way-family region layouts.
> + */
> +#define NR_CXL_MIX_GRAN_HB 3
> +#define NR_CXL_MIX_GRAN_ROOT_PORTS NR_CXL_MIX_GRAN_HB /* 1 per HB */
> +#define NR_CXL_MIX_GRAN_L1_DPORTS (NR_CXL_MIX_GRAN_HB * 2)
> +#define NR_CXL_MIX_GRAN_L2_SWITCHES NR_CXL_MIX_GRAN_L1_DPORTS
> +#define NR_CXL_MIX_GRAN_L2_DPORTS (NR_CXL_MIX_GRAN_L2_SWITCHES * 2)
> +
> +#define NR_DEFAULT_BRIDGES \
> + (NR_CXL_HOST_BRIDGES + NR_CXL_SINGLE_HOST + NR_CXL_RCH)
> +#define NR_BRIDGES (NR_DEFAULT_BRIDGES + NR_CXL_MIX_GRAN_HB)
> +
> +/* CHBS index ranges within mock_cedt.chbs[] */
> +#define CHBS_DEFAULT_START 0
> +#define CHBS_DEFAULT_END (NR_DEFAULT_BRIDGES - 1)
> +#define CHBS_MIX_GRAN_START NR_DEFAULT_BRIDGES
> +#define CHBS_MIX_GRAN_END (NR_BRIDGES - 1)
>
> #define MOCK_AUTO_REGION_SIZE_DEFAULT SZ_512M
> static int mock_auto_region_size = MOCK_AUTO_REGION_SIZE_DEFAULT;
> @@ -52,6 +72,15 @@ struct platform_device *cxl_mem_single[NR_MEM_SINGLE];
> static struct platform_device *cxl_rch[NR_CXL_RCH];
> static struct platform_device *cxl_rcd[NR_CXL_RCH];
>
> +/* Dedicated mixed-granularity topology */
> +static struct platform_device *cxl_mix_gran_hb[NR_CXL_MIX_GRAN_HB];
> +static struct platform_device *cxl_mix_gran_root_port[NR_CXL_MIX_GRAN_ROOT_PORTS];
> +static struct platform_device *cxl_mix_gran_l1_uport[NR_CXL_MIX_GRAN_ROOT_PORTS];
> +static struct platform_device *cxl_mix_gran_l1_dport[NR_CXL_MIX_GRAN_L1_DPORTS];
> +static struct platform_device *cxl_mix_gran_l2_uport[NR_CXL_MIX_GRAN_L2_SWITCHES];
> +static struct platform_device *cxl_mix_gran_l2_dport[NR_CXL_MIX_GRAN_L2_DPORTS];
> +static struct platform_device *cxl_mem_mix_gran[NR_CXL_MIX_GRAN_L2_DPORTS];
> +
> /*
> * Decoder registry
> *
> @@ -115,6 +144,19 @@ static struct acpi_device host_bridge[NR_BRIDGES] = {
> .handle = &host_bridge[3],
> .pnp.unique_id = "3",
> },
> + /* Dedicated mixed-granularity topology */
> + [4] = {
> + .handle = &host_bridge[4],
> + .pnp.unique_id = "4",
> + },
> + [5] = {
> + .handle = &host_bridge[5],
> + .pnp.unique_id = "5",
> + },
> + [6] = {
> + .handle = &host_bridge[6],
> + .pnp.unique_id = "6",
> + },
> };
>
> static bool is_mock_dev(struct device *dev)
> @@ -130,6 +172,9 @@ static bool is_mock_dev(struct device *dev)
> for (i = 0; i < ARRAY_SIZE(cxl_rcd); i++)
> if (dev == &cxl_rcd[i]->dev)
> return true;
> + for (i = 0; i < ARRAY_SIZE(cxl_mem_mix_gran); i++)
> + if (cxl_mem_mix_gran[i] && dev == &cxl_mem_mix_gran[i]->dev)
> + return true;
> if (dev == &cxl_acpi->dev)
> return true;
> return false;
> @@ -188,6 +233,14 @@ static struct {
> struct acpi_cedt_cfmws cfmws;
> u32 target[3];
> } cfmws8;
> + struct {
> + struct acpi_cedt_cfmws cfmws;
> + u32 target[2];
> + } cfmws9;
> + struct {
> + struct acpi_cedt_cfmws cfmws;
> + u32 target[3];
> + } cfmws10;
> struct {
> struct acpi_cedt_cxims cxims;
> u64 xormap_list[2];
> @@ -232,6 +285,31 @@ static struct {
> .uid = 3,
> .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL11,
> },
> + /* Dedicated mixed-granularity topology */
> + .chbs[4] = {
> + .header = {
> + .type = ACPI_CEDT_TYPE_CHBS,
> + .length = sizeof(mock_cedt.chbs[0]),
> + },
> + .uid = 4,
> + .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20,
> + },
> + .chbs[5] = {
> + .header = {
> + .type = ACPI_CEDT_TYPE_CHBS,
> + .length = sizeof(mock_cedt.chbs[0]),
> + },
> + .uid = 5,
> + .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20,
> + },
> + .chbs[6] = {
> + .header = {
> + .type = ACPI_CEDT_TYPE_CHBS,
> + .length = sizeof(mock_cedt.chbs[0]),
> + },
> + .uid = 6,
> + .cxl_version = ACPI_CEDT_CHBS_VERSION_CXL20,
> + },
> .cfmws0 = {
> .cfmws = {
> .header = {
> @@ -371,6 +449,36 @@ static struct {
> },
> .target = { 0, 1, 2, },
> },
> + .cfmws9 = {
> + .cfmws = {
> + .header = {
> + .type = ACPI_CEDT_TYPE_CFMWS,
> + .length = sizeof(mock_cedt.cfmws9),
> + },
> + .interleave_ways = 1,
> + .granularity = 4,
> + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM |
> + ACPI_CEDT_CFMWS_RESTRICT_PMEM,
> + .qtg_id = FAKE_QTG_ID,
> + .window_size = SZ_512M * 4UL,
> + },
> + .target = { 4, 5, },
> + },
> + .cfmws10 = {
> + .cfmws = {
> + .header = {
> + .type = ACPI_CEDT_TYPE_CFMWS,
> + .length = sizeof(mock_cedt.cfmws10),
> + },
> + .interleave_ways = 8,
> + .granularity = 1,
> + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_HOSTONLYMEM |
> + ACPI_CEDT_CFMWS_RESTRICT_PMEM,
> + .qtg_id = FAKE_QTG_ID,
> + .window_size = SZ_512M * 6UL,
> + },
> + .target = { 4, 5, 6, },
> + },
> .cxims0 = {
> .cxims = {
> .header = {
> @@ -395,6 +503,9 @@ struct acpi_cedt_cfmws *mock_cfmws[] = {
> [6] = &mock_cedt.cfmws6.cfmws,
> [7] = &mock_cedt.cfmws7.cfmws,
> [8] = &mock_cedt.cfmws8.cfmws,
> + /* Dedicated mixed-granularity topology */
> + [9] = &mock_cedt.cfmws9.cfmws,
> + [10] = &mock_cedt.cfmws10.cfmws,
> };
>
> static int cfmws_start;
> @@ -403,6 +514,8 @@ static int cfmws_end;
> #define CFMWS_MOD_ARRAY_END 5
> #define CFMWS_XOR_ARRAY_START 6
> #define CFMWS_XOR_ARRAY_END 8
> +#define CFMWS_MIX_GRAN_ARRAY_START 9
> +#define CFMWS_MIX_GRAN_ARRAY_END 10
>
> struct acpi_cedt_cxims *mock_cxims[1] = {
> [0] = &mock_cedt.cxims0.cxims,
> @@ -475,9 +588,18 @@ static void cfmws_elc_update(struct acpi_cedt_cfmws *window, int index)
> static int populate_cedt(void)
> {
> struct cxl_mock_res *res;
> + int chbs_lo, chbs_hi;
> int i;
>
> - for (i = 0; i < ARRAY_SIZE(mock_cedt.chbs); i++) {
> + if (mixed_gran_regions) {
> + chbs_lo = CHBS_MIX_GRAN_START;
> + chbs_hi = CHBS_MIX_GRAN_END;
> + } else {
> + chbs_lo = CHBS_DEFAULT_START;
> + chbs_hi = CHBS_DEFAULT_END;
> + }
> +
> + for (i = chbs_lo; i <= chbs_hi; i++) {
> struct acpi_cedt_chbs *chbs = &mock_cedt.chbs[i];
> resource_size_t size;
>
> @@ -531,12 +653,23 @@ static int mock_acpi_table_parse_cedt(enum acpi_cedt_type id,
> if (!is_mock_port(dev) && !is_mock_dev(dev))
> return acpi_table_parse_cedt(id, handler_arg, arg);
>
> - if (id == ACPI_CEDT_TYPE_CHBS)
> - for (i = 0; i < ARRAY_SIZE(mock_cedt.chbs); i++) {
> + if (id == ACPI_CEDT_TYPE_CHBS) {
> + int chbs_lo, chbs_hi;
> +
> + if (mixed_gran_regions) {
> + chbs_lo = CHBS_MIX_GRAN_START;
> + chbs_hi = CHBS_MIX_GRAN_END;
> + } else {
> + chbs_lo = CHBS_DEFAULT_START;
> + chbs_hi = CHBS_DEFAULT_END;
> + }
> +
> + for (i = chbs_lo; i <= chbs_hi; i++) {
> h = (union acpi_subtable_headers *)&mock_cedt.chbs[i];
> end = (unsigned long)&mock_cedt.chbs[i + 1];
> handler_arg(h, arg, end);
> }
> + }
>
> if (id == ACPI_CEDT_TYPE_CFMWS)
> for (i = cfmws_start; i <= cfmws_end; i++) {
> @@ -568,6 +701,9 @@ static bool is_mock_bridge(struct device *dev)
> for (i = 0; i < ARRAY_SIZE(cxl_rch); i++)
> if (dev == &cxl_rch[i]->dev)
> return true;
> + for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_hb); i++)
> + if (cxl_mix_gran_hb[i] && dev == &cxl_mix_gran_hb[i]->dev)
> + return true;
>
> return false;
> }
> @@ -603,6 +739,31 @@ static bool is_mock_port(struct device *dev)
> if (dev == &cxl_swd_single[i]->dev)
> return true;
>
> + for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_root_port); i++)
> + if (cxl_mix_gran_root_port[i] &&
> + dev == &cxl_mix_gran_root_port[i]->dev)
> + return true;
> +
> + for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_l1_uport); i++)
> + if (cxl_mix_gran_l1_uport[i] &&
> + dev == &cxl_mix_gran_l1_uport[i]->dev)
> + return true;
> +
> + for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_l1_dport); i++)
> + if (cxl_mix_gran_l1_dport[i] &&
> + dev == &cxl_mix_gran_l1_dport[i]->dev)
> + return true;
> +
> + for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_l2_uport); i++)
> + if (cxl_mix_gran_l2_uport[i] &&
> + dev == &cxl_mix_gran_l2_uport[i]->dev)
> + return true;
> +
> + for (i = 0; i < ARRAY_SIZE(cxl_mix_gran_l2_dport); i++)
> + if (cxl_mix_gran_l2_dport[i] &&
> + dev == &cxl_mix_gran_l2_dport[i]->dev)
> + return true;
> +
> if (is_cxl_memdev(dev))
> return is_mock_dev(dev->parent);
>
> @@ -671,7 +832,15 @@ static struct acpi_pci_root mock_pci_root[ARRAY_SIZE(mock_pci_bus)] = {
> [3] = {
> .bus = &mock_pci_bus[3],
> },
> -
> + [4] = {
> + .bus = &mock_pci_bus[4],
> + },
> + [5] = {
> + .bus = &mock_pci_bus[5],
> + },
> + [6] = {
> + .bus = &mock_pci_bus[6],
> + },
> };
>
> static bool is_mock_bus(struct pci_bus *bus)
> @@ -1229,6 +1398,8 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
>
> if (is_cxl_endpoint(port))
> target_count = 0;
> + else if (mixed_gran_regions && is_cxl_root(parent_port))
> + target_count = NR_CXL_MIX_GRAN_ROOT_PORTS / NR_CXL_MIX_GRAN_HB;
> else if (is_cxl_root(parent_port))
> target_count = NR_CXL_ROOT_PORTS;
> else
> @@ -1328,6 +1499,27 @@ static int get_port_array(struct cxl_port *port,
> struct platform_device **array;
> int array_size;
>
> + if (mixed_gran_regions) {
> + if (port->depth == 1) {
> + array_size = ARRAY_SIZE(cxl_mix_gran_root_port);
> + array = cxl_mix_gran_root_port;
> + } else if (port->depth == 2) {
> + array_size = ARRAY_SIZE(cxl_mix_gran_l1_dport);
> + array = cxl_mix_gran_l1_dport;
> + } else if (port->depth == 3) {
> + array_size = ARRAY_SIZE(cxl_mix_gran_l2_dport);
> + array = cxl_mix_gran_l2_dport;
> + } else {
> + dev_WARN_ONCE(&port->dev, 1, "unexpected depth %d\n",
> + port->depth);
> + return -ENXIO;
> + }
> +
> + *port_array = array;
> + *port_array_size = array_size;
> + return 0;
> + }
> +
> if (port->depth == 1) {
> if (is_multi_bridge(port->uport_dev)) {
> array_size = ARRAY_SIZE(cxl_root_port);
> @@ -1709,10 +1901,190 @@ static void cxl_single_topo_exit(void)
> }
> }
>
> +#define MIX_GRAN_RP_ID_BASE (NR_MULTI_ROOT + NR_CXL_SINGLE_HOST)
> +#define MIX_GRAN_L1U_ID_BASE MIX_GRAN_RP_ID_BASE
> +#define MIX_GRAN_L1D_ID_BASE (NR_MEM_MULTI + NR_MEM_SINGLE)
> +#define MIX_GRAN_L2U_ID_BASE MIX_GRAN_L1D_ID_BASE
> +#define MIX_GRAN_L2D_ID_BASE \
> + (MIX_GRAN_L1D_ID_BASE + NR_CXL_MIX_GRAN_L1_DPORTS)
> +#define MIX_GRAN_MEM_ID_BASE (NR_MEM_MULTI + NR_MEM_SINGLE + NR_CXL_RCH)
> +
> +static void cxl_mix_gran_topo_exit(void)
> +{
> + int i;
> +
> + for (i = NR_CXL_MIX_GRAN_L2_DPORTS - 1; i >= 0; i--)
> + platform_device_unregister(cxl_mix_gran_l2_dport[i]);
> + for (i = NR_CXL_MIX_GRAN_L2_SWITCHES - 1; i >= 0; i--)
> + platform_device_unregister(cxl_mix_gran_l2_uport[i]);
> + for (i = NR_CXL_MIX_GRAN_L1_DPORTS - 1; i >= 0; i--)
> + platform_device_unregister(cxl_mix_gran_l1_dport[i]);
> + for (i = NR_CXL_MIX_GRAN_ROOT_PORTS - 1; i >= 0; i--)
> + platform_device_unregister(cxl_mix_gran_l1_uport[i]);
> + for (i = NR_CXL_MIX_GRAN_ROOT_PORTS - 1; i >= 0; i--)
> + platform_device_unregister(cxl_mix_gran_root_port[i]);
> + for (i = NR_CXL_MIX_GRAN_HB - 1; i >= 0; i--) {
> + struct platform_device *pdev = cxl_mix_gran_hb[i];
> +
> + if (!pdev)
> + continue;
> + sysfs_remove_link(&pdev->dev.kobj, "physical_node");
> + platform_device_unregister(pdev);
> + }
> +}
> +
> +static __init int cxl_mix_gran_topo_init(void)
> +{
> + int i, rc;
> +
> + /* 3 host bridges */
> + for (i = 0; i < NR_CXL_MIX_GRAN_HB; i++) {
> + struct acpi_device *adev = &host_bridge[NR_DEFAULT_BRIDGES + i];
> + struct platform_device *pdev;
> +
> + pdev = platform_device_alloc("cxl_host_bridge",
> + NR_DEFAULT_BRIDGES + i);
> + if (!pdev) {
> + rc = -ENOMEM;
> + goto err;
> + }
> +
> + mock_companion(adev, &pdev->dev);
> + rc = platform_device_add(pdev);
> + if (rc) {
> + platform_device_put(pdev);
> + goto err;
> + }
> +
> + cxl_mix_gran_hb[i] = pdev;
> + mock_pci_bus[NR_DEFAULT_BRIDGES + i].bridge = &pdev->dev;
> + rc = sysfs_create_link(&pdev->dev.kobj, &pdev->dev.kobj,
> + "physical_node");
> + if (rc)
> + goto err;
> + }
> +
> + /* 1 root port per host bridge */
> + for (i = 0; i < NR_CXL_MIX_GRAN_ROOT_PORTS; i++) {
> + struct platform_device *bridge = cxl_mix_gran_hb[i];
> + struct platform_device *pdev;
> +
> + pdev = platform_device_alloc("cxl_root_port",
> + MIX_GRAN_RP_ID_BASE + i);
> + if (!pdev) {
> + rc = -ENOMEM;
> + goto err;
> + }
> + pdev->dev.parent = &bridge->dev;
> +
> + rc = platform_device_add(pdev);
> + if (rc) {
> + platform_device_put(pdev);
> + goto err;
> + }
> + cxl_mix_gran_root_port[i] = pdev;
> + }
> +
> + /* L1 switch uport under each root port */
> + for (i = 0; i < NR_CXL_MIX_GRAN_ROOT_PORTS; i++) {
> + struct platform_device *pdev;
> +
> + pdev = platform_device_alloc("cxl_switch_uport",
> + MIX_GRAN_L1U_ID_BASE + i);
> + if (!pdev) {
> + rc = -ENOMEM;
> + goto err;
> + }
> + pdev->dev.parent = &cxl_mix_gran_root_port[i]->dev;
> +
> + rc = platform_device_add(pdev);
> + if (rc) {
> + platform_device_put(pdev);
> + goto err;
> + }
> + cxl_mix_gran_l1_uport[i] = pdev;
> + }
> +
> + /* 2 L1 dports per L1 switch */
> + for (i = 0; i < NR_CXL_MIX_GRAN_L1_DPORTS; i++) {
> + struct platform_device *uport =
> + cxl_mix_gran_l1_uport[i / NR_CXL_SWITCH_PORTS];
> + struct platform_device *pdev;
> +
> + pdev = platform_device_alloc("cxl_switch_dport",
> + MIX_GRAN_L1D_ID_BASE + i);
> + if (!pdev) {
> + rc = -ENOMEM;
> + goto err;
> + }
> + pdev->dev.parent = &uport->dev;
> +
> + rc = platform_device_add(pdev);
> + if (rc) {
> + platform_device_put(pdev);
> + goto err;
> + }
> + cxl_mix_gran_l1_dport[i] = pdev;
> + }
> +
> + /* L2 switch uport under each L1 dport */
> + for (i = 0; i < NR_CXL_MIX_GRAN_L2_SWITCHES; i++) {
> + struct platform_device *pdev;
> +
> + pdev = platform_device_alloc("cxl_switch_uport",
> + MIX_GRAN_L2U_ID_BASE + i);
> + if (!pdev) {
> + rc = -ENOMEM;
> + goto err;
> + }
> + pdev->dev.parent = &cxl_mix_gran_l1_dport[i]->dev;
> +
> + rc = platform_device_add(pdev);
> + if (rc) {
> + platform_device_put(pdev);
> + goto err;
> + }
> + cxl_mix_gran_l2_uport[i] = pdev;
> + }
> +
> + /* 2 L2 dports per L2 switch */
> + for (i = 0; i < NR_CXL_MIX_GRAN_L2_DPORTS; i++) {
> + struct platform_device *uport =
> + cxl_mix_gran_l2_uport[i / NR_CXL_SWITCH_PORTS];
> + struct platform_device *pdev;
> +
> + pdev = platform_device_alloc("cxl_switch_dport",
> + MIX_GRAN_L2D_ID_BASE + i);
> + if (!pdev) {
> + rc = -ENOMEM;
> + goto err;
> + }
> + pdev->dev.parent = &uport->dev;
> +
> + rc = platform_device_add(pdev);
> + if (rc) {
> + platform_device_put(pdev);
> + goto err;
> + }
> + cxl_mix_gran_l2_dport[i] = pdev;
> + }
> +
> + return 0;
> +err:
> + cxl_mix_gran_topo_exit();
> + return rc;
> +}
> +
> static void cxl_mem_exit(void)
> {
> int i;
>
> + if (mixed_gran_regions) {
> + for (i = NR_CXL_MIX_GRAN_L2_DPORTS - 1; i >= 0; i--)
> + platform_device_unregister(cxl_mem_mix_gran[i]);
> + return;
> + }
> +
> for (i = ARRAY_SIZE(cxl_rcd) - 1; i >= 0; i--)
> platform_device_unregister(cxl_rcd[i]);
> for (i = ARRAY_SIZE(cxl_mem_single) - 1; i >= 0; i--)
> @@ -1721,10 +2093,46 @@ static void cxl_mem_exit(void)
> platform_device_unregister(cxl_mem[i]);
> }
>
> +static int cxl_mem_mix_gran_init(void)
> +{
> + int i, rc;
> +
> + for (i = 0; i < NR_CXL_MIX_GRAN_L2_DPORTS; i++) {
> + struct platform_device *dport = cxl_mix_gran_l2_dport[i];
> + struct platform_device *pdev;
> +
> + pdev = platform_device_alloc("cxl_mem",
> + MIX_GRAN_MEM_ID_BASE + i);
> + if (!pdev) {
> + rc = -ENOMEM;
> + goto err;
> + }
> + pdev->dev.parent = &dport->dev;
> + set_dev_node(&pdev->dev, i % 2);
> +
> + cxl_mem_mix_gran[i] = pdev;
> + rc = platform_device_add(pdev);
> + if (rc) {
> + platform_device_put(pdev);
> + cxl_mem_mix_gran[i] = NULL;
> + goto err;
> + }
> + }
> +
> + return 0;
> +err:
> + for (i = NR_CXL_MIX_GRAN_L2_DPORTS - 1; i >= 0; i--)
> + platform_device_unregister(cxl_mem_mix_gran[i]);
> + return rc;
> +}
> +
> static int cxl_mem_init(void)
> {
> int i, rc;
>
> + if (mixed_gran_regions)
> + return cxl_mem_mix_gran_init();
> +
> for (i = 0; i < ARRAY_SIZE(cxl_mem); i++) {
> struct platform_device *dport = cxl_switch_dport[i];
> struct platform_device *pdev;
> @@ -1848,7 +2256,10 @@ static __init int cxl_test_init(void)
> if (rc)
> goto err_gen_pool_add;
>
> - if (interleave_arithmetic == 1) {
> + if (mixed_gran_regions) {
> + cfmws_start = CFMWS_MIX_GRAN_ARRAY_START;
> + cfmws_end = CFMWS_MIX_GRAN_ARRAY_END;
> + } else if (interleave_arithmetic == 1) {
> cfmws_start = CFMWS_XOR_ARRAY_START;
> cfmws_end = CFMWS_XOR_ARRAY_END;
> } else {
> @@ -1860,6 +2271,13 @@ static __init int cxl_test_init(void)
> if (rc)
> goto err_populate;
>
> + if (mixed_gran_regions) {
> + rc = cxl_mix_gran_topo_init();
> + if (rc)
> + goto err_populate;
> + goto topo_done;
> + }
> +
> for (i = 0; i < ARRAY_SIZE(cxl_host_bridge); i++) {
> struct acpi_device *adev = &host_bridge[i];
> struct platform_device *pdev;
> @@ -1945,9 +2363,10 @@ static __init int cxl_test_init(void)
> if (rc)
> goto err_single;
>
> +topo_done:
> cxl_acpi = platform_device_alloc("cxl_acpi", 0);
> if (!cxl_acpi)
> - goto err_rch;
> + goto err_topo;
>
> mock_companion(&acpi0017_mock, &cxl_acpi->dev);
> acpi0017_mock.dev.bus = &platform_bus_type;
> @@ -1971,7 +2390,11 @@ static __init int cxl_test_init(void)
> cxl_mem_exit();
> err_root:
> platform_device_put(cxl_acpi);
> -err_rch:
> +err_topo:
> + if (mixed_gran_regions) {
> + cxl_mix_gran_topo_exit();
> + goto err_populate;
> + }
> cxl_rch_topo_exit();
> err_single:
> cxl_single_topo_exit();
> @@ -2020,21 +2443,25 @@ static __exit void cxl_test_exit(void)
> hmem_test_exit();
> cxl_mem_exit();
> platform_device_unregister(cxl_acpi);
> - cxl_rch_topo_exit();
> - cxl_single_topo_exit();
> - for (i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--)
> - platform_device_unregister(cxl_switch_dport[i]);
> - for (i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--)
> - platform_device_unregister(cxl_switch_uport[i]);
> - for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--)
> - platform_device_unregister(cxl_root_port[i]);
> - for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) {
> - struct platform_device *pdev = cxl_host_bridge[i];
> + if (mixed_gran_regions) {
> + cxl_mix_gran_topo_exit();
> + } else {
> + cxl_rch_topo_exit();
> + cxl_single_topo_exit();
> + for (i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--)
> + platform_device_unregister(cxl_switch_dport[i]);
> + for (i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--)
> + platform_device_unregister(cxl_switch_uport[i]);
> + for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--)
> + platform_device_unregister(cxl_root_port[i]);
> + for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) {
> + struct platform_device *pdev = cxl_host_bridge[i];
>
> - if (!pdev)
> - continue;
> - sysfs_remove_link(&pdev->dev.kobj, "physical_node");
> - platform_device_unregister(cxl_host_bridge[i]);
> + if (!pdev)
> + continue;
> + sysfs_remove_link(&pdev->dev.kobj, "physical_node");
> + platform_device_unregister(cxl_host_bridge[i]);
> + }
> }
> depopulate_all_mock_resources();
> gen_pool_destroy(cxl_mock_pool);
> @@ -2049,6 +2476,9 @@ module_param(extended_linear_cache, bool, 0444);
> MODULE_PARM_DESC(extended_linear_cache, "Enable extended linear cache support");
> module_param(fail_autoassemble, bool, 0444);
> MODULE_PARM_DESC(fail_autoassemble, "Simulate missing member of an auto-region");
> +module_param(mixed_gran_regions, bool, 0444);
> +MODULE_PARM_DESC(mixed_gran_regions,
> + "Topology supporting mixed granularity regions");
> module_init(cxl_test_init);
> module_exit(cxl_test_exit);
> MODULE_LICENSE("GPL v2");
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide
2026-05-30 0:00 ` [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide Alison Schofield
2026-06-03 23:57 ` Dave Jiang
@ 2026-06-04 3:47 ` kernel test robot
1 sibling, 0 replies; 15+ messages in thread
From: kernel test robot @ 2026-06-04 3:47 UTC (permalink / raw)
To: Alison Schofield, Davidlohr Bueso, Jonathan Cameron, Dave Jiang,
Vishal Verma, Ira Weiny, Dan Williams, Li Ming, Robert Richter
Cc: oe-kbuild-all, linux-cxl
Hi Alison,
kernel test robot noticed the following build errors:
[auto build test ERROR on 254f49634ee16a731174d2ae34bc50bd5f45e731]
url: https://github.com/intel-lab-lkp/linux/commits/Alison-Schofield/cxl-region-Validate-interleave-selector-bits/20260530-080529
base: 254f49634ee16a731174d2ae34bc50bd5f45e731
patch link: https://lore.kernel.org/r/ef2ba341a7ed6f754012e11110750d57929575b2.1780095671.git.alison.schofield%40intel.com
patch subject: [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide
config: i386-randconfig-012-20250916 (https://download.01.org/0day-ci/archive/20260604/202606041115.8RwB2Cuv-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260604/202606041115.8RwB2Cuv-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202606041115.8RwB2Cuv-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
>> ERROR: modpost: "__udivdi3" [drivers/cxl/core/cxl_core.ko] undefined!
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-06-04 3:48 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-30 0:00 [PATCH 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
2026-05-30 0:00 ` [PATCH 1/6] cxl/region: Validate interleave selector bits Alison Schofield
2026-05-30 0:19 ` sashiko-bot
2026-05-30 0:00 ` [PATCH 2/6] cxl/region: Derive port granularity from " Alison Schofield
2026-05-30 0:26 ` sashiko-bot
2026-06-03 23:51 ` Dave Jiang
2026-05-30 0:00 ` [PATCH 3/6] cxl/region: Account for mixed-granularity in position calculations Alison Schofield
2026-05-30 0:00 ` [PATCH 4/6] cxl/region: Validate mixed-granularity at sysfs and attach gates Alison Schofield
2026-06-03 23:55 ` Dave Jiang
2026-05-30 0:00 ` [PATCH 5/6] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
2026-05-30 5:17 ` sashiko-bot
2026-06-04 0:04 ` Dave Jiang
2026-05-30 0:00 ` [PATCH 6/6] Documentation/cxl: Add region granularity and multi-level interleave guide Alison Schofield
2026-06-03 23:57 ` Dave Jiang
2026-06-04 3:47 ` kernel test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox