* [PATCH v4 1/6] cxl/region: Warn on user region position mismatch
2026-08-20 23:31 [PATCH v4 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
@ 2026-08-20 23:31 ` Alison Schofield
2026-08-20 23:31 ` [PATCH v4 2/6] cxl/region: Generalize endpoint position mapping Alison Schofield
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Alison Schofield @ 2026-08-20 23:31 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
User region creation includes a self-test that checks the assigned
endpoint positions against the position calculation used by auto
region creation. A mismatch is reported with dev_dbg().
Promote that to a dev_warn() so bugs in the auto region position
calculation are easier to catch as that calculation is extended to
support more region configurations.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 1e211542b6b6..3b640c9ba5a0 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -2218,10 +2218,10 @@ static int cxl_region_attach(struct cxl_region *cxlr,
int test_pos;
test_pos = cxl_calc_interleave_pos(target, &cxlr->hpa_range);
- dev_dbg(&target->cxld.dev,
- "Test cxl_calc_interleave_pos(): %s test_pos:%d target->pos:%d\n",
- (test_pos == target->pos) ? "success" : "fail",
- test_pos, target->pos);
+ if (test_pos != target->pos)
+ dev_warn(&target->cxld.dev,
+ "position mismatch: calculated:%d programmed:%d\n",
+ test_pos, target->pos);
}
return 0;
--
2.37.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v4 2/6] cxl/region: Generalize endpoint position mapping
2026-08-20 23:31 [PATCH v4 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
2026-08-20 23:31 ` [PATCH v4 1/6] cxl/region: Warn on user region position mismatch Alison Schofield
@ 2026-08-20 23:31 ` Alison Schofield
2026-08-20 23:43 ` sashiko-bot
2026-08-20 23:31 ` [PATCH v4 3/6] cxl/region: Support mixed-granularity auto regions Alison Schofield
` (3 subsequent siblings)
5 siblings, 1 reply; 9+ messages in thread
From: Alison Schofield @ 2026-08-20 23:31 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
Endpoint position calculation currently relies on the requirement that
an interleaving root have the same granularity as the region. Auto
region creation builds the position by multiplying by each parent
decoder's ways, while user region creation selects the root target
with 'pos % ways'. Those calculations are sufficient under the current
granularity restriction.
In order to support mixed-granularity regions, that granularity
restriction will need to be removed so decoder granularity can change
between levels of the interleave hierarchy. The position calculation
needs to account for those changes to produce the correct endpoint
ordering.
Change the position calculation so each decoder's contribution is
weighted by its granularity relative to the region granularity:
position += target_pos *
(decoder_granularity / region_granularity)
Use the same relationship to select the root target during user region
creation.
For currently supported regions, the new weighted calculation reduces
to the existing position calculation and produces identical endpoint
positions.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 60 ++++++++++++++++++++++-----------------
1 file changed, 34 insertions(+), 26 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 3b640c9ba5a0..4f367feaf6c8 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1805,9 +1805,14 @@ static int cxl_region_attach_position(struct cxl_region *cxlr,
struct cxl_decoder *cxld = &cxlsd->cxld;
int iw = cxld->interleave_ways;
struct cxl_port *iter;
- int rc;
+ int root_pos = pos, rc;
- if (dport != cxlrd->cxlsd.target[pos % iw]) {
+ /* Root target selection advances at root-granularity intervals */
+ if (iw > 1)
+ root_pos = pos * cxlr->params.interleave_granularity /
+ cxld->interleave_granularity;
+
+ if (dport != cxlrd->cxlsd.target[root_pos % iw]) {
dev_dbg(&cxlr->dev, "%s:%s invalid target position for %s\n",
dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
dev_name(&cxlrd->cxlsd.cxld.dev));
@@ -1908,13 +1913,13 @@ static int match_switch_decoder_by_range(struct device *dev,
return (r1->start == r2->start && r1->end == r2->end);
}
-static int find_pos_and_ways(struct cxl_port *port, struct range *range,
- int *pos, int *ways)
+static int find_pos_and_gran(struct cxl_port *port, struct range *range,
+ int *pos, int *gran)
{
struct cxl_switch_decoder *cxlsd;
struct cxl_port *parent;
struct device *dev;
- int rc = -ENXIO;
+ int ways, rc = -ENXIO;
parent = parent_port_of(port);
if (!parent)
@@ -1929,9 +1934,10 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
return rc;
}
cxlsd = to_cxl_switch_decoder(dev);
- *ways = cxlsd->cxld.interleave_ways;
+ ways = cxlsd->cxld.interleave_ways;
+ *gran = cxlsd->cxld.interleave_granularity;
- for (int i = 0; i < *ways; i++) {
+ for (int i = 0; i < ways; i++) {
if (cxlsd->target[i] == port->parent_dport) {
*pos = i;
rc = 0;
@@ -1955,13 +1961,16 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
* @cxled: endpoint decoder member of given region
* @hpa_range: translated HPA range of the endpoint
*
- * The endpoint position is calculated by traversing the topology from
- * the endpoint to the root decoder and iteratively applying this
- * calculation:
+ * The endpoint position is calculated by traversing the topology from the
+ * endpoint to the root decoder and accumulating the contribution of each
+ * decoder level:
*
- * position = position * parent_ways + parent_pos;
+ * position += parent_pos * (parent_granularity / region_granularity);
*
- * ...where @position is inferred from switch and root decoder target lists.
+ * ...where @parent_pos is inferred from switch and root decoder target
+ * lists, and the multiplier is the number of region positions that the
+ * level's granularity spans. A level that selects a single target
+ * contributes nothing.
*
* Return: position >= 0 on success
* -ENXIO on failure
@@ -1971,7 +1980,8 @@ static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
{
struct cxl_port *iter, *port = cxled_to_port(cxled);
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
- int parent_ways = 0, parent_pos = 0, pos = 0;
+ int gran = cxled->cxld.interleave_granularity;
+ int parent_gran = 0, parent_pos = 0, pos = 0;
int rc;
/*
@@ -1984,20 +1994,18 @@ static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
* | | | |
* mem0 mem1 mem2 mem3
*
- * In the example the calculator will iterate twice. The first iteration
- * uses the mem position in the host-bridge and the ways of the host-
- * bridge to generate the first, or local, position. The second
- * iteration uses the host-bridge position in the root_port and the ways
- * of the root_port to refine the position.
+ * The region and the root decoder interleave at granularity g, so
+ * each host-bridge decoder interleaves at 2g and spans two region
+ * positions while the root decoder spans one.
*
* A trace of the calculation per endpoint looks like this:
- * mem0: pos = 0 * 2 + 0 mem2: pos = 0 * 2 + 0
- * pos = 0 * 2 + 0 pos = 0 * 2 + 1
+ * mem0: pos += 0 * 2 mem2: pos += 0 * 2
+ * pos += 0 * 1 pos += 1 * 1
* pos: 0 pos: 1
*
- * mem1: pos = 0 * 2 + 1 mem3: pos = 0 * 2 + 1
- * pos = 1 * 2 + 0 pos = 1 * 2 + 1
- * pos: 2 pos = 3
+ * mem1: pos += 1 * 2 mem3: pos += 1 * 2
+ * pos += 0 * 1 pos += 1 * 1
+ * pos: 2 pos: 3
*
* Note that while this example is simple, the method applies to more
* complex topologies, including those with switches.
@@ -2008,12 +2016,12 @@ static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
if (is_cxl_root(iter))
break;
- rc = find_pos_and_ways(iter, hpa_range, &parent_pos,
- &parent_ways);
+ rc = find_pos_and_gran(iter, hpa_range, &parent_pos,
+ &parent_gran);
if (rc)
return rc;
- pos = pos * parent_ways + parent_pos;
+ pos += parent_pos * (parent_gran / gran);
}
dev_dbg(&cxlmd->dev,
--
2.37.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH v4 2/6] cxl/region: Generalize endpoint position mapping
2026-08-20 23:31 ` [PATCH v4 2/6] cxl/region: Generalize endpoint position mapping Alison Schofield
@ 2026-08-20 23:43 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-08-20 23:43 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] Use-After-Free read of device structure in find_pos_and_gran error path.
--
--- Patch [2]: [PATCH v4 2/6] cxl/region: Generalize endpoint position mapping ---
commit 74f53df9f58a0339cb5f29038f2e514904e34cf5
Author: Alison Schofield <alison.schofield@intel.com>
cxl/region: Generalize endpoint position mapping
This commit alters the endpoint position calculation to support
mixed-granularity regions. It weights each decoder's contribution by its
relative granularity instead of assuming a uniform granularity across the
interleave hierarchy.
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index 3b640c9ba5a08..4f367feaf6c84 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
[ ... ]
> @@ -1929,9 +1934,10 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
> return rc;
> }
> cxlsd = to_cxl_switch_decoder(dev);
> - *ways = cxlsd->cxld.interleave_ways;
> + ways = cxlsd->cxld.interleave_ways;
> + *gran = cxlsd->cxld.interleave_granularity;
>
> - for (int i = 0; i < *ways; i++) {
> + for (int i = 0; i < ways; i++) {
> if (cxlsd->target[i] == port->parent_dport) {
> *pos = i;
> rc = 0;
[Severity: High]
This is a pre-existing issue, but could the error handling path just after
this loop trigger a use-after-free?
Looking at find_pos_and_gran(), if the loop completes without finding a
match, rc remains -ENXIO. Immediately after the loop, put_device(dev) is
called, and then dev_err() formats an error message using cxlsd:
put_device(dev);
if (rc)
dev_err(port->uport_dev,
"failed to find %s:%s in target list of %s\n",
dev_name(&port->dev),
dev_name(port->parent_dport->dport_dev),
dev_name(&cxlsd->cxld.dev));
Since cxlsd is derived from dev, does calling put_device() before dev_err()
release the structure prematurely and cause a read of freed memory?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1787255388.git.alison.schofield@intel.com?part=2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v4 3/6] cxl/region: Support mixed-granularity auto regions
2026-08-20 23:31 [PATCH v4 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
2026-08-20 23:31 ` [PATCH v4 1/6] cxl/region: Warn on user region position mismatch Alison Schofield
2026-08-20 23:31 ` [PATCH v4 2/6] cxl/region: Generalize endpoint position mapping Alison Schofield
@ 2026-08-20 23:31 ` Alison Schofield
2026-08-20 23:43 ` sashiko-bot
2026-08-20 23:31 ` [PATCH v4 4/6] cxl/region: Support mixed-granularity user created regions Alison Schofield
` (2 subsequent siblings)
5 siblings, 1 reply; 9+ messages in thread
From: Alison Schofield @ 2026-08-20 23:31 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
The CXL Specification permits a region's interleave granularity to
differ between levels of the decoder hierarchy. For an auto region,
the CXL driver reconstructs that hierarchy from the decoder
configuration programmed by platform firmware.
The CXL driver currently assumes the interleaving root has the same
granularity as the region, with each successive interleaving level
becoming coarser toward the endpoints. This prevents the driver from
assembling an auto region where the root granularity is coarser than
the region granularity.
Support mixed-granularity auto regions in the CXL driver, restricted
to coarse-to-fine layouts. Derive each interleaving decoder's
granularity from its parent:
child_ig = parent_ig / child_iw
The topology determines each decoder's interleave ways, so the parent
granularity and child ways determine the child granularity. Use that
relationship to validate the decoder geometry while assembling an auto
region.
Require the root and region to describe the same interleave span:
root_iw * root_ig == region_iw * region_ig
The same span relationship covers the CXL Specification's Mod3
configurations. For example, a 6-way region at IGB across three host
bridges uses a 3-way root interleave at 2 * IGB and a 2-way interleave
at IGB below it, as described in CXL 4.0 Section 9.13.1.1.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 114 ++++++++++++++++++++++++--------------
1 file changed, 73 insertions(+), 41 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 4f367feaf6c8..637d1b60a570 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1434,6 +1434,16 @@ static int check_interleave_cap(struct cxl_decoder *cxld, int iw, int ig)
return 0;
}
+/* Mixed granularity has a region IG finer than the interleaving root IG */
+static bool cxl_region_is_mixed_gran(struct cxl_region *cxlr)
+{
+ struct cxl_decoder *cxld = &cxlr->cxlrd->cxlsd.cxld;
+
+ return cxld->interleave_ways > 1 &&
+ cxld->interleave_granularity >
+ cxlr->params.interleave_granularity;
+}
+
static int cxl_port_setup_targets(struct cxl_port *port,
struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled)
@@ -1447,7 +1457,6 @@ 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;
@@ -1463,26 +1472,21 @@ static int cxl_port_setup_targets(struct cxl_port *port,
}
cxlsd = to_cxl_switch_decoder(&cxld->dev);
+ iw = cxl_rr->nr_targets;
+
if (cxl_rr->nr_targets_set) {
- int i, distance = 1;
- struct cxl_region_ref *cxl_rr_iter;
+ int i, distance;
/*
- * 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.
+ * @distance is the spacing between region positions sharing
+ * this dport. Mixed-granularity regions place those positions
+ * contiguously.
*/
- 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;
+ if (cxl_region_is_mixed_gran(cxlr))
+ distance = 1;
+ else
+ distance = cxld->interleave_granularity * iw /
+ p->interleave_granularity;
for (i = 0; i < cxl_rr->nr_targets_set; i++)
if (ep->dport == cxlsd->target[i]) {
@@ -1496,15 +1500,15 @@ static int cxl_port_setup_targets(struct cxl_port *port,
}
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;
+ /*
+ * A non-interleaving root does not contribute to the region
+ * interleave.
+ */
+ if (parent_iw > 1)
+ parent_ig = cxlrd->cxlsd.cxld.interleave_granularity;
+ else
+ parent_ig = p->interleave_granularity;
/*
* For purposes of address bit routing, use power-of-2 math for
* switch ports.
@@ -1537,7 +1541,6 @@ static int cxl_port_setup_targets(struct cxl_port *port,
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",
@@ -1545,23 +1548,15 @@ static int cxl_port_setup_targets(struct cxl_port *port,
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) {
- dev_dbg(&cxlr->dev,
- "%s: invalid granularity calculation (%d * %d)\n",
- dev_name(&parent_port->dev), parent_ig, parent_iw);
- return rc;
- }
+ if (cxl_region_is_mixed_gran(cxlr))
+ ig = parent_ig / iw;
+ else
+ ig = parent_ig * parent_iw;
- rc = eig_to_granularity(eig, &ig);
+ rc = granularity_to_eig(ig, &eig);
if (rc) {
- dev_dbg(&cxlr->dev, "%s:%s: invalid interleave: %d\n",
- dev_name(port->uport_dev), dev_name(&port->dev),
- 256 << eig);
+ dev_dbg(&cxlr->dev, "%s:%s: invalid granularity: %d\n",
+ dev_name(port->uport_dev), dev_name(&port->dev), ig);
return rc;
}
@@ -2058,6 +2053,39 @@ static int cxl_region_sort_targets(struct cxl_region *cxlr)
return rc;
}
+static int cxl_region_validate_interleave(struct cxl_region *cxlr)
+{
+ struct cxl_decoder *cxld = &cxlr->cxlrd->cxlsd.cxld;
+ struct cxl_region_params *p = &cxlr->params;
+ int root_iw = cxld->interleave_ways;
+ int root_ig = cxld->interleave_granularity;
+
+ if (root_iw == 1)
+ return 0;
+
+ if (p->interleave_granularity > root_ig) {
+ dev_dbg(&cxlr->dev,
+ "granularity %d exceeds root decoder granularity %d\n",
+ p->interleave_granularity, root_ig);
+ return -ENXIO;
+ }
+
+ /* Same-gran power-of-two regions may span multiple root targets */
+ if (is_power_of_2(root_iw) && p->interleave_granularity == root_ig)
+ return 0;
+
+ /* Mixed-gran regions must span exactly one root interleave */
+ if (root_iw * root_ig != p->interleave_ways * p->interleave_granularity) {
+ dev_dbg(&cxlr->dev,
+ "%d ways at %d does not span root decoder %d ways at %d\n",
+ p->interleave_ways, p->interleave_granularity, root_iw,
+ root_ig);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
static int cxl_region_attach(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled, int pos)
{
@@ -2100,6 +2128,10 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
+ rc = cxl_region_validate_interleave(cxlr);
+ if (rc)
+ return rc;
+
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] 9+ messages in thread* Re: [PATCH v4 3/6] cxl/region: Support mixed-granularity auto regions
2026-08-20 23:31 ` [PATCH v4 3/6] cxl/region: Support mixed-granularity auto regions Alison Schofield
@ 2026-08-20 23:43 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-08-20 23:43 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] Incorrect distance calculation for non-mixed-granularity regions with non-power-of-2 root interleaves causes a NULL pointer dereference.
--
--- Patch [3]: [PATCH v4 3/6] cxl/region: Support mixed-granularity auto regions ---
commit 13bc48fe2efef1744b5bbf29c756bd07f400ebe3
Author: Alison Schofield <alison.schofield@intel.com>
cxl/region: Support mixed-granularity auto regions
The CXL driver currently assumes the interleaving root has the same
granularity as the region, with each successive interleaving level
becoming coarser toward the endpoints. This patch allows the CXL driver
to assemble an auto region where the root granularity is coarser than
the region granularity.
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index 4f367feaf6c84..637d1b60a570b 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
[ ... ]
> @@ -1463,26 +1472,21 @@ static int cxl_port_setup_targets(struct cxl_port *port,
> }
>
> cxlsd = to_cxl_switch_decoder(&cxld->dev);
> + iw = cxl_rr->nr_targets;
> +
> if (cxl_rr->nr_targets_set) {
> - int i, distance = 1;
> - struct cxl_region_ref *cxl_rr_iter;
> + int i, distance;
>
> /*
> - * 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.
> + * @distance is the spacing between region positions sharing
> + * this dport. Mixed-granularity regions place those positions
> + * contiguously.
> */
> - 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;
> + if (cxl_region_is_mixed_gran(cxlr))
> + distance = 1;
> + else
> + distance = cxld->interleave_granularity * iw /
> + p->interleave_granularity;
[Severity: High]
Does this mathematical distance calculation correctly handle non-power-of-2
root interleaves?
When configuring a CXL region with a non-power-of-2 root interleave (like
3-way or 6-way) and multiple endpoints sharing a downstream port, this
formula seems to calculate a distance 3 times smaller than the true
topological stride. This appears to happen because parent_iw is divided
by 3 for address routing, which scales down cxld->interleave_granularity.
If the distance is undersized, check_last_peer() will check an endpoint on
a completely different host bridge:
drivers/cxl/core/region.c:check_last_peer() {
...
cxled_peer = p->targets[pos - distance];
cxlmd_peer = cxled_to_memdev(cxled_peer);
ep_peer = cxl_ep_load(port, cxlmd_peer);
if (ep->dport != ep_peer->dport) {
...
}
Since cxl_ep_load() returns NULL when selecting a peer on a different host
bridge, will ep_peer->dport trigger a NULL pointer dereference here?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1787255388.git.alison.schofield@intel.com?part=3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v4 4/6] cxl/region: Support mixed-granularity user created regions
2026-08-20 23:31 [PATCH v4 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
` (2 preceding siblings ...)
2026-08-20 23:31 ` [PATCH v4 3/6] cxl/region: Support mixed-granularity auto regions Alison Schofield
@ 2026-08-20 23:31 ` Alison Schofield
2026-08-20 23:31 ` [PATCH v4 5/6] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
2026-08-20 23:31 ` [PATCH v4 6/6] Documentation/cxl: Describe " Alison Schofield
5 siblings, 0 replies; 9+ messages in thread
From: Alison Schofield @ 2026-08-20 23:31 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
User region creation currently requires an interleaving root to have
the same granularity as the region.
That rejects valid mixed-granularity layouts where the region
granularity is finer than the root granularity. A region granularity
coarser than the root remains invalid because it produces incorrect
DPA translations.
Allow a region granularity finer than an interleaving root and
continue to reject a coarser granularity. Validate the complete
ways/granularity relationship when the region endpoints are attached.
For example, CXL 4.0 Section 9.13.1.1 Table 9-7 describes a 6-way
region at IGB across three host bridges as a 3-way root interleave at
2 * IGB with a 2-way interleave at IGB below it.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 637d1b60a570..4bbfb7cd7e01 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -571,15 +571,8 @@ 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)
+ /* Region granularity must not be coarser than an interleaving root's */
+ if (cxld->interleave_ways > 1 && val > cxld->interleave_granularity)
return -EINVAL;
lockdep_assert_held_write(&cxl_rwsem.region);
--
2.37.3
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v4 5/6] cxl/test: Add a topology to test mixed-granularity regions
2026-08-20 23:31 [PATCH v4 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
` (3 preceding siblings ...)
2026-08-20 23:31 ` [PATCH v4 4/6] cxl/region: Support mixed-granularity user created regions Alison Schofield
@ 2026-08-20 23:31 ` Alison Schofield
2026-08-20 23:31 ` [PATCH v4 6/6] Documentation/cxl: Describe " Alison Schofield
5 siblings, 0 replies; 9+ messages in thread
From: Alison Schofield @ 2026-08-20 23:31 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
Add a cxl_test topology for mixed-granularity region testing.
modprobe cxl_test mixed_gran_regions=1 creates three host bridges,
each with a passthrough host-bridge port above two switch levels
and four endpoints, for twelve endpoints total.
Two CFMWS windows are added:
1) A 2-way window at 4K gran over two host bridges, that hosts 8-way
regions at 1K.
2) A 3-way window at 512 gran over three host bridges, that hosts
6-way regions at 256.
Both windows also host the same-granularity regions their ways allow.
Register every mock device through cxl_mock_platform_device_add() so
the mock array is populated before platform_device_add(), matching the
ordering the rest of cxl_test relies on to avoid the endpoint-probe
race in commit d90f236f8b9e ("cxl/test: Update mock dev array before
calling platform_device_add()").
NULL-check the legacy topology arrays, which are left empty when
mixed_gran_regions=1 selects the new topology.
The default and existing special module params are unaffected.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
tools/testing/cxl/test/cxl.c | 496 ++++++++++++++++++++++++++++++++---
1 file changed, 458 insertions(+), 38 deletions(-)
diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index ef92dd35e030..0ec9b402e1af 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,14 @@ 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];
+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 +143,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)
@@ -122,15 +163,18 @@ static bool is_mock_dev(struct device *dev)
int i;
for (i = 0; i < ARRAY_SIZE(cxl_mem); i++)
- if (dev == &cxl_mem[i]->dev)
+ if (cxl_mem[i] && dev == &cxl_mem[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_mem_single); i++)
- if (dev == &cxl_mem_single[i]->dev)
+ if (cxl_mem_single[i] && dev == &cxl_mem_single[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_rcd); i++)
- if (dev == &cxl_rcd[i]->dev)
+ if (cxl_rcd[i] && dev == &cxl_rcd[i]->dev)
return true;
- if (dev == &cxl_acpi->dev)
+ 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 (cxl_acpi && dev == &cxl_acpi->dev)
return true;
return false;
}
@@ -188,6 +232,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 +284,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 +448,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 +502,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 +513,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,
@@ -479,9 +591,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;
@@ -538,12 +659,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++) {
@@ -567,13 +699,16 @@ static bool is_mock_bridge(struct device *dev)
int i;
for (i = 0; i < ARRAY_SIZE(cxl_host_bridge); i++)
- if (dev == &cxl_host_bridge[i]->dev)
+ if (cxl_host_bridge[i] && dev == &cxl_host_bridge[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_hb_single); i++)
- if (dev == &cxl_hb_single[i]->dev)
+ if (cxl_hb_single[i] && dev == &cxl_hb_single[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_rch); i++)
- if (dev == &cxl_rch[i]->dev)
+ if (cxl_rch[i] && 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;
@@ -587,27 +722,52 @@ static bool is_mock_port(struct device *dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_root_port); i++)
- if (dev == &cxl_root_port[i]->dev)
+ if (cxl_root_port[i] && dev == &cxl_root_port[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_switch_uport); i++)
- if (dev == &cxl_switch_uport[i]->dev)
+ if (cxl_switch_uport[i] && dev == &cxl_switch_uport[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_switch_dport); i++)
- if (dev == &cxl_switch_dport[i]->dev)
+ if (cxl_switch_dport[i] && dev == &cxl_switch_dport[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_root_single); i++)
- if (dev == &cxl_root_single[i]->dev)
+ if (cxl_root_single[i] && dev == &cxl_root_single[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_swu_single); i++)
- if (dev == &cxl_swu_single[i]->dev)
+ if (cxl_swu_single[i] && dev == &cxl_swu_single[i]->dev)
return true;
for (i = 0; i < ARRAY_SIZE(cxl_swd_single); i++)
- if (dev == &cxl_swd_single[i]->dev)
+ if (cxl_swd_single[i] && 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))
@@ -678,7 +838,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)
@@ -1099,7 +1267,8 @@ static bool mock_init_hdm_decoder(struct cxl_decoder *cxld)
/* check is endpoint is attach to host-bridge0 */
port = cxled_to_port(cxled);
do {
- if (port->uport_dev == &cxl_host_bridge[0]->dev) {
+ if (cxl_host_bridge[0] &&
+ port->uport_dev == &cxl_host_bridge[0]->dev) {
hb0 = true;
break;
}
@@ -1242,6 +1411,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
@@ -1341,6 +1512,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);
@@ -1724,10 +1916,182 @@ 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]);
+ cxl_mix_gran_l2_dport[i] = NULL;
+ }
+ for (i = NR_CXL_MIX_GRAN_L2_SWITCHES - 1; i >= 0; i--) {
+ platform_device_unregister(cxl_mix_gran_l2_uport[i]);
+ cxl_mix_gran_l2_uport[i] = NULL;
+ }
+ for (i = NR_CXL_MIX_GRAN_L1_DPORTS - 1; i >= 0; i--) {
+ platform_device_unregister(cxl_mix_gran_l1_dport[i]);
+ cxl_mix_gran_l1_dport[i] = NULL;
+ }
+ for (i = NR_CXL_MIX_GRAN_ROOT_PORTS - 1; i >= 0; i--) {
+ platform_device_unregister(cxl_mix_gran_l1_uport[i]);
+ cxl_mix_gran_l1_uport[i] = NULL;
+ }
+ for (i = NR_CXL_MIX_GRAN_ROOT_PORTS - 1; i >= 0; i--) {
+ platform_device_unregister(cxl_mix_gran_root_port[i]);
+ cxl_mix_gran_root_port[i] = NULL;
+ }
+ 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);
+ cxl_mix_gran_hb[i] = NULL;
+ }
+}
+
+static __init int cxl_mix_gran_topo_init(void)
+{
+ int i, rc;
+
+ 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 = cxl_mock_platform_device_add(pdev, &cxl_mix_gran_hb[i]);
+ if (rc)
+ goto err;
+
+ 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 = cxl_mock_platform_device_add(pdev, &cxl_mix_gran_root_port[i]);
+ if (rc)
+ goto err;
+ }
+
+ /* 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 = cxl_mock_platform_device_add(pdev, &cxl_mix_gran_l1_uport[i]);
+ if (rc)
+ goto err;
+ }
+
+ /* 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 = cxl_mock_platform_device_add(pdev, &cxl_mix_gran_l1_dport[i]);
+ if (rc)
+ goto err;
+ }
+
+ /* 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 = cxl_mock_platform_device_add(pdev, &cxl_mix_gran_l2_uport[i]);
+ if (rc)
+ goto err;
+ }
+
+ /* 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 = cxl_mock_platform_device_add(pdev, &cxl_mix_gran_l2_dport[i]);
+ if (rc)
+ goto err;
+ }
+
+ 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--)
@@ -1736,10 +2100,42 @@ 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);
+
+ rc = cxl_mock_platform_device_add(pdev, &cxl_mem_mix_gran[i]);
+ if (rc)
+ 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;
@@ -1860,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 {
@@ -1872,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 +2351,12 @@ 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;
+ if (!cxl_acpi) {
+ rc = -ENOMEM;
+ goto err_topo;
+ }
mock_companion(&acpi0017_mock, &cxl_acpi->dev);
acpi0017_mock.dev.bus = &platform_bus_type;
@@ -1955,7 +2364,7 @@ static __init int cxl_test_init(void)
rc = cxl_mock_platform_device_add(cxl_acpi, NULL);
if (rc)
- goto err_rch;
+ goto err_topo;
rc = cxl_mem_init();
if (rc)
@@ -1971,7 +2380,11 @@ static __init int cxl_test_init(void)
cxl_mem_exit();
err_root:
platform_device_unregister(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 +2433,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 +2466,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] 9+ messages in thread* [PATCH v4 6/6] Documentation/cxl: Describe mixed-granularity regions
2026-08-20 23:31 [PATCH v4 0/6] cxl: Support mixed-granularity region interleaves Alison Schofield
` (4 preceding siblings ...)
2026-08-20 23:31 ` [PATCH v4 5/6] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
@ 2026-08-20 23:31 ` Alison Schofield
5 siblings, 0 replies; 9+ messages in thread
From: Alison Schofield @ 2026-08-20 23:31 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
Mixed-granularity region support introduces interleave relationships
that are not obvious from the existing CXL documentation. This is
particularly true for the 3-, 6-, and 12-way configurations and for
understanding which configurations permitted by the CXL Specification
are supported by Linux.
Document the mixed-granularity model and Linux's coarse-to-fine
restriction. Include the relevant configurations from CXL 4.0 Section
9.13.1.1 and annotate their Linux support status.
This intentionally repeats information from the CXL Specification.
The specification remains authoritative, but showing the Linux support
policy alongside the legal configurations ensures readers do not
mistake an unsupported Linux setup for a configuration unsupported by
the CXL Specification.
Assisted-by: Claude:Opus-5
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
.../driver-api/cxl/linux/cxl-driver.rst | 135 ++++++++++++++++++
1 file changed, 135 insertions(+)
diff --git a/Documentation/driver-api/cxl/linux/cxl-driver.rst b/Documentation/driver-api/cxl/linux/cxl-driver.rst
index dd6dd17dc536..4e56c18294ef 100644
--- a/Documentation/driver-api/cxl/linux/cxl-driver.rst
+++ b/Documentation/driver-api/cxl/linux/cxl-driver.rst
@@ -602,6 +602,11 @@ derived from their upstream port connections. In `Cross-Link First` interleave
configurations, the :code:`interleave_granularity` of a decoder is equal to
:code:`parent_interleave_granularity * parent_interleave_ways`.
+When the region granularity is finer than the granularity of an interleaving
+root decoder, the relation inverts: the :code:`interleave_granularity` of a
+decoder is equal to :code:`parent_interleave_granularity / interleave_ways`.
+See `Mixed Granularity`_.
+
At Endpoint
~~~~~~~~~~~
`Endpoint Decoders` are programmed similar to Host Bridge and Switch decoders,
@@ -619,6 +624,136 @@ from HPA to DPA. This is why they must be aware of the entire interleave set.
Linux does not support unbalanced interleave configurations. As a result, all
endpoints in an interleave set must have the same ways and granularity.
+Mixed Granularity
+~~~~~~~~~~~~~~~~~
+Every decoder advances one target every multiple of its own granularity, and
+the decoders below it subdivide the span their parent assigns to a single
+target. Linux supports two orderings of granularity down the hierarchy.
+
+The `Cross-Link First` example above shows the first ordering, where the region
+granularity equals the granularity of the root decoder and granularity coarsens
+toward the endpoints. In the second ordering the region granularity is finer
+than the root decoder's and granularity refines toward the endpoints, reaching
+the region granularity at the innermost interleaving decoder. A region using
+that ordering is a *mixed-granularity* region. A mixed-granularity region
+requires an interleaving root decoder.
+
+Linux supports only monotonic granularity hierarchies, either coarsening or
+refining from the root toward the endpoints. The CXL Specification does not
+require a monotonic ordering, see `Mod3 Interleave Configurations`_.
+
+For an 8-way mixed-granularity region below a 2-way interleaving root decoder
+at 4096, where each host bridge routes through two levels of switch, Linux
+programs::
+
+ Level Ways Granularity
+ ----- ---- -----------
+ Root 2 4096
+ Host bridge 1 4096
+ Upper switch 2 2048
+ Lower switch 2 1024
+ Endpoint 8 1024
+
+Each decoder contributes to an endpoint's region position in proportion to its
+granularity::
+
+ position += target_position *
+ decoder_granularity / region_granularity
+
+The root above selects a host bridge every 4096 bytes, so it advances one
+target every four region positions, while the lower switch advances one target
+every position. When the region granularity equals the root granularity, the
+root advances one target per region position and each level's weight is the
+number of ways below it.
+
+The ways and granularity of a mixed-granularity region must describe the same
+interleave span as the root decoder::
+
+ root_ways * root_granularity == region_ways * region_granularity
+
+A region that does not describe that span either leaves part of the range
+unclaimed or reaches beyond it, and Linux rejects it. A same-granularity
+region below a power-of-two root decoder spans a multiple of the root's range
+rather than one target's share of it, and is not subject to this relationship.
+
+Mod3 Interleave Configurations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A 3-way, 6-way, or 12-way interleave, known as a Mod3 interleave, selects its
+target with a factor-of-three selection rather than from binary interleave
+selector bits alone. CXL 4.0 Section 9.13.1.1 defines the 3-way selection as
+the address above the decoder granularity taken modulo 3. A 6-way selection
+claims one binary HPA bit at the decoder granularity and takes the modulo 3 of
+the address above that bit, and a 12-way selection claims two.
+
+The factor-of-three selection is not an additional binary selector bit, so a
+Mod3 interleave distributes across the hierarchy as::
+
+ 3 = 3
+ 6 = 3 * 2
+ 12 = 3 * 4
+
+The cross-host bridge selection carries the factor of three, and the remaining
+x2 or x4 is binary interleave selection below it. A 6-way region at IGB across
+three host bridges is therefore::
+
+ Device-level region: 6-way @ IGB
+ Cross-host bridge: 3-way @ 2*IGB
+ Below the root: 2-way @ IGB
+
+where both levels describe the same interleave span::
+
+ 3 * (2 * IGB) == 6 * IGB
+
+The CXL Specification defines the legal Mod3 compositions and is normative.
+CXL 4.0 Section 9.13.1.1, "Legal Interleaving Configurations: 12-way, 6-way,
+and 3-way", Tables 9-6, 9-7, and 9-8 list them for a 12-way, 6-way, and 3-way
+device-level interleave at IGB. Those tables are summarized below, annotated
+with the subset Linux supports.
+
+CXL 4.0 Table 9-8, 3-way device-level interleave at IGB::
+
+ Row Cross-host bridge Host bridge Switch Linux
+ --- ----------------- ----------- ------ -----
+ 1 3-way @ IGB none none supported
+
+CXL 4.0 Table 9-7, 6-way device-level interleave at IGB::
+
+ Row Cross-host bridge Host bridge Switch Linux
+ --- ----------------- ----------- ------ -----
+ 1 6-way @ IGB none none supported
+ 2 3-way @ 2*IGB 2-way @ IGB none supported
+ 3 3-way @ 2*IGB none 2-way @ IGB supported
+
+CXL 4.0 Table 9-6, 12-way device-level interleave at IGB::
+
+ Row Cross-host bridge Host bridge Switch Linux
+ --- ----------------- ----------- ------ -----
+ 1 12-way @ IGB none none supported
+ 2 6-way @ 2*IGB 2-way @ IGB none supported
+ 3 6-way @ 2*IGB none 2-way @ IGB supported
+ 4 3-way @ 4*IGB 4-way @ IGB none supported
+ 5 3-way @ 4*IGB none 4-way @ IGB supported
+ 6 3-way @ 4*IGB 2-way @ IGB 2-way @ 2*IGB unsupported
+ 7 3-way @ 4*IGB 2-way @ 2*IGB 2-way @ IGB supported
+
+Table 9-6 row 6 is legal per the CXL Specification and unsupported by Linux.
+Walking it from the root toward the endpoints, granularity goes::
+
+ 4*IGB -> IGB -> 2*IGB
+
+which refines and then coarsens. Row 7 interleaves the same 12 endpoints at
+the same granularity with those two levels exchanged::
+
+ 4*IGB -> 2*IGB -> IGB
+
+which is monotonic. Linux programs row 7 for a user region and assembles an
+auto region whose decoders are programmed that way. An auto region matching
+row 6 is not assembled.
+
+Leaving row 6 unsupported does not prevent a 12-way device-level interleave.
+The specification defines six other legal compositions, all monotonic and
+supported by Linux.
+
Example Configurations
======================
.. toctree::
--
2.37.3
^ permalink raw reply related [flat|nested] 9+ messages in thread