* [PATCH v3 1/9] cxl/region: Factor port target calculations
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
2026-07-30 22:36 ` sashiko-bot
2026-07-30 22:20 ` [PATCH v3 2/9] cxl/region: Validate interleave selector bits Alison Schofield
` (7 subsequent siblings)
8 siblings, 1 reply; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
cxl_port_setup_targets() calculates the interleave fan-out above a
port, the port decoder granularity, and the distance between
endpoints routed through the same downstream port.
Factor those calculations into helpers so the target setup path can
be extended. Validation of parent decoder values is dropped since
those values are validated where they are set, before this port's
setup runs. A configuration that is invalid in more than one way may
report a different error first.
Suggested-by: Originally-by: Robert Richter <rrichter@amd.com>
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 197 +++++++++++++++++++-------------------
1 file changed, 96 insertions(+), 101 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 1e211542b6b6..1082db7b2cca 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1350,6 +1350,19 @@ static void cxl_port_detach_region(struct cxl_port *port,
free_region_ref(cxl_rr);
}
+/**
+ * check_last_peer() - Verify the previous endpoint routed to this dport
+ * @cxled: endpoint decoder being placed
+ * @ep: this endpoint's entry for the port
+ * @cxl_rr: region reference for the port
+ * @distance: distance to the previous endpoint routed to this dport
+ *
+ * Endpoints routed through the same dport recur at @distance intervals in
+ * region-position order. Verify that the endpoint at ``pos - distance`` used
+ * the same dport.
+ *
+ * Return: 0 on success, -ENXIO on a routing mismatch.
+ */
static int check_last_peer(struct cxl_endpoint_decoder *cxled,
struct cxl_ep *ep, struct cxl_region_ref *cxl_rr,
int distance)
@@ -1434,60 +1447,106 @@ static int check_interleave_cap(struct cxl_decoder *cxld, int iw, int ig)
return 0;
}
+/**
+ * get_parent_fanout() - Calculate the switch fan-out above a port
+ * @parent_port: first ancestor port
+ * @cxlr: region under construction
+ * @fanout: filled with the product of the ancestor switch ways
+ *
+ * Walk from @parent_port to the root and multiply the interleave ways of
+ * each switch decoder. Root decoder ways are not included.
+ *
+ * Return: 0 on success.
+ */
+static int get_parent_fanout(struct cxl_port *parent_port,
+ struct cxl_region *cxlr, int *fanout)
+{
+ int distance = 1;
+ struct cxl_port *iter;
+
+ 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);
+
+ distance *= cxl_rr_iter->nr_targets;
+ }
+
+ *fanout = distance;
+ return 0;
+}
+
+/**
+ * derive_port_granularity() - Calculate the granularity for a port decoder
+ * @cxlr: region under construction
+ * @fanout: product of the ancestor switch ways
+ * @ig: filled with the port decoder granularity
+ *
+ * Preserve the existing parent-granularity times parent-ways recurrence in
+ * terms of the region granularity and the fan-out above this port.
+ *
+ * Return: 0 on success.
+ */
+static int derive_port_granularity(struct cxl_region *cxlr, int fanout,
+ int *ig)
+{
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
+ int root_iw = cxlrd->cxlsd.cxld.interleave_ways;
+ struct cxl_region_params *p = &cxlr->params;
+ int sel_distance;
+
+ sel_distance = is_power_of_2(root_iw) ? root_iw : root_iw / 3;
+ sel_distance *= fanout;
+ *ig = p->interleave_granularity * sel_distance;
+
+ return 0;
+}
+
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 root_iw = cxlrd->cxlsd.cxld.interleave_ways;
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);
struct cxl_ep *ep = cxl_ep_load(port, cxlmd);
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;
+ struct cxl_switch_decoder *cxlsd = to_cxl_switch_decoder(&cxld->dev);
+ int ig, iw = cxl_rr->nr_targets;
+ int fanout, rc;
+ int pos = cxled->pos;
+ u16 eig;
+ u8 eiw;
/*
* While root level decoders support x3, x6, x12, switch level
* decoders only support powers of 2 up to x16.
*/
- if (!is_power_of_2(cxl_rr->nr_targets)) {
+ if (!is_power_of_2(iw)) {
dev_dbg(&cxlr->dev, "%s:%s: invalid target count %d\n",
- dev_name(port->uport_dev), dev_name(&port->dev),
- cxl_rr->nr_targets);
+ dev_name(port->uport_dev), dev_name(&port->dev), iw);
return -EINVAL;
}
- cxlsd = to_cxl_switch_decoder(&cxld->dev);
+ 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;
+ }
+
+ rc = get_parent_fanout(parent_port, cxlr, &fanout);
+ if (rc)
+ return rc;
+
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);
+ root_iw * fanout * iw);
if (rc)
return rc;
goto out_target_set;
@@ -1495,84 +1554,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;
-
- 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);
+ rc = derive_port_granularity(cxlr, fanout, &ig);
+ if (rc)
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) ||
--
2.37.3
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v3 1/9] cxl/region: Factor port target calculations
2026-07-30 22:20 ` [PATCH v3 1/9] cxl/region: Factor port target calculations Alison Schofield
@ 2026-07-30 22:36 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-30 22:36 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] The target count check `iw > 8` incorrectly blocks 16-way interleaving for switch decoders, contradicting its own documentation.
--
--- Patch [1]: [PATCH v3 1/9] cxl/region: Factor port target calculations ---
commit a455145f1f8fa170cab75f1f658966ee9cf551ab
Author: Alison Schofield <alison.schofield@intel.com>
cxl/region: Factor port target calculations
This commit refactors calculations for interleave fan-out, port decoder
granularity, and endpoint distance into dedicated helpers. It also removes
parent decoder validation from this path.
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index 1e211542b6b64..1082db7b2cca9 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
[ ... ]
> static int cxl_port_setup_targets(struct cxl_port *port,
> struct cxl_region *cxlr,
> struct cxl_endpoint_decoder *cxled)
> {
[ ... ]
> /*
> * While root level decoders support x3, x6, x12, switch level
> * decoders only support powers of 2 up to x16.
> */
> - if (!is_power_of_2(cxl_rr->nr_targets)) {
> + if (!is_power_of_2(iw)) {
> dev_dbg(&cxlr->dev, "%s:%s: invalid target count %d\n",
> - dev_name(port->uport_dev), dev_name(&port->dev),
> - cxl_rr->nr_targets);
> + dev_name(port->uport_dev), dev_name(&port->dev), iw);
> return -EINVAL;
> }
[ ... ]
> + 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;
> + }
[Severity: High]
This is a pre-existing issue, but does this hardcoded limit incorrectly
prevent 16-way interleaving for switch decoders?
The comment above this validation states that switch level decoders support
powers of 2 up to x16. However, if iw is 16, wouldn't the iw > 8 condition
evaluate to true and unconditionally fail the region setup by returning
-ENXIO?
Could this limit be updated to allow iw up to 16 to match the hardware
capabilities and documentation?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1785444498.git.alison.schofield@intel.com?part=1
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 2/9] cxl/region: Validate interleave selector bits
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
2026-07-30 22:20 ` [PATCH v3 1/9] cxl/region: Factor port target calculations Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
2026-07-30 22:20 ` [PATCH v3 3/9] cxl/region: Derive port granularity from " Alison Schofield
` (6 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
Each decoder level in an interleave uses a field of host physical
address (HPA) bits, called a selector, to choose which of its
downstream targets a given address routes to. In a multi-level
interleave the selectors of the root, the switches, and the endpoints
must occupy distinct address bits so that every level makes an
independent routing decision.
The existing setup does not check the selectors directly. It instead
requires each level's granularity to equal the parent granularity
multiplied by the parent ways, which only holds for a subset of the
legal selector layouts. Layouts that place non-overlapping selectors
in a different order, as mixed-granularity regions do, are rejected
even though they are valid.
Add selector-bit accounting to cxl_port_setup_targets(). Accumulate
the selector of each level from the root toward the current port,
reject any selector that overlaps a bit already claimed by another
level, and reject an accumulated selector that does not fit within
the region selector. The root decoder's selector is walked alongside
the switch levels rather than tracked separately.
This patch adds selector validation but does not yet enable the
mixed-granularity layouts. A temporary gate rejects region granularity
finer than the root granularity until the position arithmetic is
updated.
Originally-by: Robert Richter <rrichter@amd.com>
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 103 +++++++++++++++++++++++++++++++++++---
1 file changed, 96 insertions(+), 7 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 1082db7b2cca..b0061f03892a 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1448,33 +1448,110 @@ static int check_interleave_cap(struct cxl_decoder *cxld, int iw, int ig)
}
/**
- * get_parent_fanout() - Calculate the switch fan-out above a port
+ * get_selector() - Build the HPA selector mask for an interleave
+ * @ways: interleave ways
+ * @gran: interleave granularity in bytes
+ *
+ * Power-of-2 interleaves select targets with contiguous HPA bits beginning
+ * at @gran. For 6-way and 12-way interleaves, only the power-of-2 factor
+ * contributes selector bits. A 3-way interleave contributes no selector
+ * bits.
+ *
+ * Return: the HPA selector mask, or 0 when no selector bits are used.
+ */
+static inline u64 get_selector(int ways, int gran)
+{
+ if (!is_power_of_2(ways))
+ ways /= 3;
+
+ if (!is_power_of_2(ways) || !is_power_of_2(gran))
+ return 0;
+
+ return (u64)(ways - 1) * gran;
+}
+
+/**
+ * get_parent_selectors() - Collect selectors and fan-out above a port
* @parent_port: first ancestor port
* @cxlr: region under construction
+ * @cxlrd: region root decoder
+ * @accum: filled with the selectors claimed by the ancestors
* @fanout: filled with the product of the ancestor switch ways
*
- * Walk from @parent_port to the root and multiply the interleave ways of
- * each switch decoder. Root decoder ways are not included.
+ * Start with the root decoder selector and walk the ancestor switch
+ * decoders. Reject selectors that overlap. Passthrough decoders contribute
+ * neither selector bits nor fan-out.
*
- * Return: 0 on success.
+ * Root decoder ways are not included in @fanout.
+ *
+ * Return: 0 on success, -ENXIO on selector overlap.
*/
-static int get_parent_fanout(struct cxl_port *parent_port,
- struct cxl_region *cxlr, int *fanout)
+static int get_parent_selectors(struct cxl_port *parent_port,
+ struct cxl_region *cxlr,
+ struct cxl_root_decoder *cxlrd, u64 *accum,
+ int *fanout)
{
+ u64 selector = get_selector(cxlrd->cxlsd.cxld.interleave_ways,
+ cxlrd->cxlsd.cxld.interleave_granularity);
int distance = 1;
struct cxl_port *iter;
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;
distance *= cxl_rr_iter->nr_targets;
}
+ *accum = selector;
*fanout = distance;
return 0;
}
+/**
+ * region_selectors_fit() - Check ancestor selectors against the region
+ * @port: port being configured
+ * @cxlr: region under construction
+ * @accum: selectors used by the ancestor decoders
+ *
+ * Return: true when every accumulated selector bit is present in the region
+ * selector.
+ */
+static bool region_selectors_fit(struct cxl_port *port,
+ struct cxl_region *cxlr, u64 accum)
+{
+ struct cxl_region_params *p = &cxlr->params;
+ u64 cxlr_sel = get_selector(p->interleave_ways,
+ p->interleave_granularity);
+
+ if ((cxlr_sel & accum) != accum) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s: invalid selectors: cxlr %#llx accum %#llx\n",
+ dev_name(port->uport_dev), dev_name(&port->dev),
+ cxlr_sel, accum);
+ return false;
+ }
+
+ return true;
+}
+
/**
* derive_port_granularity() - Calculate the granularity for a port decoder
* @cxlr: region under construction
@@ -1517,6 +1594,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
int ig, iw = cxl_rr->nr_targets;
int fanout, rc;
int pos = cxled->pos;
+ u64 accum;
u16 eig;
u8 eiw;
@@ -1538,10 +1616,13 @@ static int cxl_port_setup_targets(struct cxl_port *port,
return -ENXIO;
}
- rc = get_parent_fanout(parent_port, cxlr, &fanout);
+ rc = get_parent_selectors(parent_port, cxlr, cxlrd, &accum, &fanout);
if (rc)
return rc;
+ if (!region_selectors_fit(port, cxlr, accum))
+ return -ENXIO;
+
if (cxl_rr->nr_targets_set) {
for (int i = 0; i < cxl_rr->nr_targets_set; i++)
if (ep->dport == cxlsd->target[i]) {
@@ -2087,6 +2168,14 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
+ /*
+ * Mixed-granularity position calculation is added by the next patch.
+ * Reject it until then so this intermediate state remains bisectable.
+ */
+ 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] 12+ messages in thread* [PATCH v3 3/9] cxl/region: Derive port granularity from selector bits
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
2026-07-30 22:20 ` [PATCH v3 1/9] cxl/region: Factor port target calculations Alison Schofield
2026-07-30 22:20 ` [PATCH v3 2/9] cxl/region: Validate interleave selector bits Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
2026-07-30 22:20 ` [PATCH v3 4/9] cxl/region: Account for mixed-granularity in position calculations Alison Schofield
` (5 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
A user-created region currently derives each port decoder granularity
from the parent granularity and ways. That recurrence assumes the
selector bits follow the existing same-granularity ordering and cannot
derive the decoder settings for a mixed-granularity region.
Add cxl_region_is_mixed_gran() as the common predicate for regions
whose granularity is finer than an interleaving root decoder.
Update derive_port_granularity() to choose each port decoder's
granularity from the selector bits not already used by its ancestors.
For auto regions, compare the firmware-programmed granularity with the
derived value and reject a mismatch.
Originally-by: Robert Richter <rrichter@amd.com>
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 90 ++++++++++++++++++++++++++++++++++-----
1 file changed, 79 insertions(+), 11 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index b0061f03892a..871dedd37cc8 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1552,32 +1552,94 @@ static bool region_selectors_fit(struct cxl_port *port,
return true;
}
+/**
+ * cxl_region_is_mixed_gran() - Test for a mixed-granularity region
+ * @cxlr: region
+ *
+ * A region is mixed-granularity when an interleaving root decoder uses a
+ * larger granularity than the region.
+ *
+ * Return: true for a mixed-granularity region.
+ */
+static inline 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;
+}
+
/**
* derive_port_granularity() - Calculate the granularity for a port decoder
+ * @port: port being configured
* @cxlr: region under construction
+ * @accum: selectors used by the ancestor decoders
* @fanout: product of the ancestor switch ways
- * @ig: filled with the port decoder granularity
+ * @iw: port decoder interleave ways
+ * @ig: filled with the derived granularity
*
- * Preserve the existing parent-granularity times parent-ways recurrence in
- * terms of the region granularity and the fan-out above this port.
+ * Select the decoder granularity from the region selector bits not already
+ * used by its ancestors. Same-granularity regions allocate the lowest
+ * available selector bits first. Mixed-granularity regions allocate the
+ * highest available selector bits first so decoder granularities decrease
+ * from the root toward the endpoints.
*
- * Return: 0 on success.
+ * A passthrough decoder uses no selector bits and retains the granularity
+ * implied by the ancestor fan-out.
+ *
+ * Return: 0 on success, -ENXIO when no valid selector remains.
*/
-static int derive_port_granularity(struct cxl_region *cxlr, int fanout,
- int *ig)
+static int derive_port_granularity(struct cxl_port *port,
+ struct cxl_region *cxlr, u64 accum,
+ int fanout, int iw, int *ig)
{
struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
int root_iw = cxlrd->cxlsd.cxld.interleave_ways;
struct cxl_region_params *p = &cxlr->params;
+ u64 selector;
int sel_distance;
- sel_distance = is_power_of_2(root_iw) ? root_iw : root_iw / 3;
- sel_distance *= fanout;
- *ig = p->interleave_granularity * sel_distance;
+ selector = get_selector(p->interleave_ways,
+ p->interleave_granularity) & ~accum;
+
+ if (iw == 1) {
+ sel_distance = is_power_of_2(root_iw) ? root_iw : root_iw / 3;
+ sel_distance *= fanout;
+ *ig = p->interleave_granularity * sel_distance;
+ } else if (selector && cxl_region_is_mixed_gran(cxlr)) {
+ *ig = (1ULL << fls64(selector)) / iw;
+ } 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;
+ }
+
+ 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;
+ }
return 0;
}
+/**
+ * cxl_port_setup_targets() - Validate and program a port decoder
+ * @port: port being configured
+ * @cxlr: region under construction
+ * @cxled: endpoint decoder being attached
+ *
+ * Validate the decoder's selector placement and derive its interleave
+ * geometry. User-created regions program the derived values; auto regions
+ * validate the firmware-programmed values.
+ *
+ * Return: 0 on success, negative errno on invalid interleave geometry.
+ */
static int cxl_port_setup_targets(struct cxl_port *port,
struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled)
@@ -1635,7 +1697,7 @@ static int cxl_port_setup_targets(struct cxl_port *port,
goto add_target;
}
- rc = derive_port_granularity(cxlr, fanout, &ig);
+ rc = derive_port_granularity(port, cxlr, accum, fanout, iw, &ig);
if (rc)
return rc;
@@ -1650,8 +1712,14 @@ static int cxl_port_setup_targets(struct cxl_port *port,
}
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
+ if (iw > 1 && cxld->interleave_granularity != ig) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s: firmware ig %d != derived ig %d (iw %d)\n",
+ dev_name(port->uport_dev), dev_name(&port->dev),
+ cxld->interleave_granularity, ig, iw);
+ return -ENXIO;
+ }
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,
--
2.37.3
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 4/9] cxl/region: Account for mixed-granularity in position calculations
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
` (2 preceding siblings ...)
2026-07-30 22:20 ` [PATCH v3 3/9] cxl/region: Derive port granularity from " Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
2026-07-30 22:20 ` [PATCH v3 5/9] cxl/region: Allow mixed-granularity regions Alison Schofield
` (4 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
Endpoint positions identify the order in which a region routes
addresses to its endpoint decoders. The existing calculation assumes
the root target changes at every region position. In a
mixed-granularity region, one root target instead covers multiple
positions before the next root target is selected.
Add root_positions_per_target() to express that ratio and use it when
validating root targets, checking peer distance, and calculating
endpoint positions. Keep the existing position recurrence for
same-granularity regions and use each decoder level's granularity when
calculating positions for mixed-granularity regions.
Remove the temporary mixed-granularity reject now that endpoint
positions match the selector layout.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 109 +++++++++++++++++++++++++++++++-------
1 file changed, 89 insertions(+), 20 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 871dedd37cc8..938781c9bc5c 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1569,6 +1569,49 @@ static inline bool cxl_region_is_mixed_gran(struct cxl_region *cxlr)
cxld->interleave_granularity > cxlr->params.interleave_granularity;
}
+/**
+ * root_positions_per_target() - Count positions covered by a root target
+ * @cxlr: region
+ *
+ * In a mixed-granularity region, a root target covers
+ * ``root_granularity / region_granularity`` consecutive region positions.
+ * A same-granularity root covers one position.
+ *
+ * Return: the number of region positions covered by one root target.
+ */
+static inline int root_positions_per_target(struct cxl_region *cxlr)
+{
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
+
+ if (!cxl_region_is_mixed_gran(cxlr))
+ return 1;
+
+ return cxlrd->cxlsd.cxld.interleave_granularity /
+ cxlr->params.interleave_granularity;
+}
+
+/**
+ * peer_pos_distance() - Calculate the distance between same-dport endpoints
+ * @cxlr: region under construction
+ * @fanout: product of the ancestor switch ways
+ * @iw: port decoder interleave ways
+ *
+ * Mixed-granularity routing groups the endpoints below a dport into
+ * consecutive region positions. Same-granularity routing spaces them by
+ * the root ways and the switch fan-out above the port.
+ *
+ * Return: the distance in region positions.
+ */
+static int peer_pos_distance(struct cxl_region *cxlr, int fanout, int iw)
+{
+ int root_iw = cxlr->cxlrd->cxlsd.cxld.interleave_ways;
+
+ if (cxl_region_is_mixed_gran(cxlr))
+ return 1;
+
+ return root_iw * fanout * iw;
+}
+
/**
* derive_port_granularity() - Calculate the granularity for a port decoder
* @port: port being configured
@@ -1645,7 +1688,6 @@ static int cxl_port_setup_targets(struct cxl_port *port,
struct cxl_endpoint_decoder *cxled)
{
struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
- int root_iw = cxlrd->cxlsd.cxld.interleave_ways;
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);
@@ -1689,7 +1731,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,
- root_iw * fanout * iw);
+ peer_pos_distance(cxlr, fanout, iw));
if (rc)
return rc;
goto out_target_set;
@@ -1947,14 +1989,16 @@ 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 root_pos_per_target = root_positions_per_target(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",
+ if (dport != cxlrd->cxlsd.target[pos / root_pos_per_target % iw]) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s invalid target position for %s (positions per target %d)\n",
dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
- dev_name(&cxlrd->cxlsd.cxld.dev));
+ dev_name(&cxlrd->cxlsd.cxld.dev), root_pos_per_target);
return -ENXIO;
}
@@ -2053,7 +2097,7 @@ static int match_switch_decoder_by_range(struct device *dev,
}
static int find_pos_and_ways(struct cxl_port *port, struct range *range,
- int *pos, int *ways)
+ int *pos, int *ways, int *gran)
{
struct cxl_switch_decoder *cxlsd;
struct cxl_port *parent;
@@ -2074,6 +2118,7 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range,
}
cxlsd = to_cxl_switch_decoder(dev);
*ways = cxlsd->cxld.interleave_ways;
+ *gran = cxlsd->cxld.interleave_granularity;
for (int i = 0; i < *ways; i++) {
if (cxlsd->target[i] == port->parent_dport) {
@@ -2098,6 +2143,8 @@ 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
+ * @root_pos_per_target: region positions under one root target
+ * @region_gran: region interleave granularity
*
* The endpoint position is calculated by traversing the topology from
* the endpoint to the root decoder and iteratively applying this
@@ -2107,15 +2154,25 @@ 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 a mixed-granularity region, each interleaving level contributes its
+ * target index at the number of region positions covered by one of its
+ * targets:
+ *
+ * position += positions_per_target * target_index
+ *
+ * Switch positions-per-target is ``parent_gran / region_gran``. The root
+ * uses @root_pos_per_target.
+ *
* 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 root_pos_per_target, int region_gran)
{
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 parent_ways = 0, parent_pos = 0, parent_gran = 0, pos = 0;
int rc;
/*
@@ -2153,11 +2210,24 @@ static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
break;
rc = find_pos_and_ways(iter, hpa_range, &parent_pos,
- &parent_ways);
+ &parent_ways, &parent_gran);
if (rc)
return rc;
- pos = pos * parent_ways + parent_pos;
+ if (root_pos_per_target > 1 &&
+ is_cxl_root(parent_port_of(iter))) {
+ pos = pos + root_pos_per_target * parent_pos;
+ break;
+ }
+
+ /*
+ * Passthrough levels do not select a target and therefore do
+ * not contribute to the region position.
+ */
+ if (root_pos_per_target > 1)
+ pos = pos + (parent_gran / region_gran) * parent_pos;
+ else
+ pos = pos * parent_ways + parent_pos;
}
dev_dbg(&cxlmd->dev,
@@ -2171,12 +2241,15 @@ 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 root_pos_per_target = root_positions_per_target(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,
+ root_pos_per_target,
+ p->interleave_granularity);
/*
* Record that sorting failed, but still continue to calc
* cxled->pos so that cxl_calc_interleave_pos() emits its
@@ -2204,6 +2277,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 root_pos_per_target;
rc = check_interleave_cap(&cxled->cxld, p->interleave_ways,
p->interleave_granularity);
@@ -2236,14 +2310,6 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
- /*
- * Mixed-granularity position calculation is added by the next patch.
- * Reject it until then so this intermediate state remains bisectable.
- */
- 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);
@@ -2365,11 +2431,14 @@ 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.
*/
+ root_pos_per_target = root_positions_per_target(cxlr);
for (int i = 0; i < p->nr_targets; i++) {
struct cxl_endpoint_decoder *target = p->targets[i];
int test_pos;
- test_pos = cxl_calc_interleave_pos(target, &cxlr->hpa_range);
+ test_pos = cxl_calc_interleave_pos(target, &cxlr->hpa_range,
+ root_pos_per_target,
+ p->interleave_granularity);
dev_dbg(&target->cxld.dev,
"Test cxl_calc_interleave_pos(): %s test_pos:%d target->pos:%d\n",
(test_pos == target->pos) ? "success" : "fail",
--
2.37.3
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 5/9] cxl/region: Allow mixed-granularity regions
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
` (3 preceding siblings ...)
2026-07-30 22:20 ` [PATCH v3 4/9] cxl/region: Account for mixed-granularity in position calculations Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
2026-07-30 22:20 ` [PATCH v3 6/9] cxl/region: Enforce coarse-to-fine ordering for " Alison Schofield
` (3 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
The sysfs granularity rule required region_gran == root_gran whenever
the root decoder interleaves. That blocked the legal mixed-granularity
layouts permitted by CXL Spec 4.0 Section 9.13.1 and made every 6-way
and 12-way configuration in Tables 9-6, 9-7, and 9-8 impossible to
create. Auto regions skipped the sysfs path entirely and so had no
equivalent gate.
Replace the equality rule with is_ig_allowed(), which permits any
region granularity not greater than the root's, and apply it at attach
time so auto regions get the same rule. Add a ways*gran span identity
check for 3-way-family roots: a 3-way interleave consumes no HPA
selector bits, so selector containment cannot prove that the root and
region cover the same address span, and the identity restores that
constraint. Power-of-2 roots get the equivalent constraint from
selector containment for free.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 53 +++++++++++++++++++++++++++++++--------
1 file changed, 43 insertions(+), 10 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 938781c9bc5c..3919f64e2467 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -559,10 +559,27 @@ static ssize_t interleave_granularity_show(struct device *dev,
return sysfs_emit(buf, "%d\n", p->interleave_granularity);
}
+/**
+ * is_ig_allowed() - Check region granularity against the root decoder
+ * @cxlrd: root decoder
+ * @ig: proposed region granularity
+ *
+ * Return: true when the root does not interleave or @ig is no greater than
+ * the root decoder granularity.
+ */
+static inline bool is_ig_allowed(struct cxl_root_decoder *cxlrd, int ig)
+{
+ struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
+
+ if (cxld->interleave_ways <= 1)
+ return true;
+
+ return ig <= cxld->interleave_granularity;
+}
+
static int set_interleave_granularity(struct cxl_region *cxlr, int val)
{
struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
- struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
struct cxl_region_params *p = &cxlr->params;
int rc;
u16 ig;
@@ -571,15 +588,7 @@ static int set_interleave_granularity(struct cxl_region *cxlr, int val)
if (rc)
return rc;
- /*
- * When the host-bridge is interleaved, disallow region granularity !=
- * root granularity. Regions with a granularity less than the root
- * interleave result in needing multiple endpoints to support a single
- * slot in the interleave (possible to support in the future). Regions
- * with a granularity greater than the root interleave result in invalid
- * DPA translations (invalid to support).
- */
- if (cxld->interleave_ways > 1 && val != cxld->interleave_granularity)
+ if (!is_ig_allowed(cxlrd, val))
return -EINVAL;
lockdep_assert_held_write(&cxl_rwsem.region);
@@ -2278,6 +2287,8 @@ static int cxl_region_attach(struct cxl_region *cxlr,
struct cxl_dport *dport;
int rc = -ENXIO;
int root_pos_per_target;
+ int root_ways = cxlrd->cxlsd.cxld.interleave_ways;
+ int root_gran = cxlrd->cxlsd.cxld.interleave_granularity;
rc = check_interleave_cap(&cxled->cxld, p->interleave_ways,
p->interleave_granularity);
@@ -2310,6 +2321,28 @@ static int cxl_region_attach(struct cxl_region *cxlr,
return -ENXIO;
}
+ if (!is_ig_allowed(cxlrd, p->interleave_granularity)) {
+ dev_dbg(&cxlr->dev,
+ "ig %d incompatible with root ways %d ig %d\n",
+ p->interleave_granularity, root_ways, root_gran);
+ return -ENXIO;
+ }
+
+ /*
+ * A 3-way-family root contributes no selector bits, so selector
+ * validation cannot confirm that the root and region cover the same
+ * interleave span. Check the span explicitly.
+ */
+ if (!is_power_of_2(root_ways) &&
+ (u64)p->interleave_ways * p->interleave_granularity !=
+ (u64)root_ways * root_gran) {
+ dev_dbg(&cxlr->dev,
+ "region ways*gran (%d*%d) != root ways*gran (%d*%d)\n",
+ p->interleave_ways, p->interleave_granularity,
+ root_ways, root_gran);
+ return -ENXIO;
+ }
+
if (p->nr_targets >= p->interleave_ways) {
dev_dbg(&cxlr->dev, "region already has %d endpoints\n",
p->nr_targets);
--
2.37.3
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 6/9] cxl/region: Enforce coarse-to-fine ordering for mixed-granularity regions
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
` (4 preceding siblings ...)
2026-07-30 22:20 ` [PATCH v3 5/9] cxl/region: Allow mixed-granularity regions Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
2026-07-30 22:20 ` [PATCH v3 7/9] cxl/region: Fail region creation on position check mismatch Alison Schofield
` (2 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
With mixed granularity allowed, selector disjointness alone would
accept an arrangement where an interleaving level is coarser than one
above it. The position arithmetic cannot represent such a layout: it
assumes each level below the root refines the one above.
In a mixed-granularity region require each interleaving level to be
strictly finer than its nearest interleaving ancestor, and reject a
violation with -ENXIO in check_gran_ordering(), called from
cxl_port_setup_targets(). The value validated is the derived
granularity on the user path and the firmware-programmed granularity on
the auto path. Same-granularity regions are exempt; their levels
legitimately coarsen away from the root.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 48 +++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 3919f64e2467..b4e8ebe158f7 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1680,6 +1680,50 @@ static int derive_port_granularity(struct cxl_port *port,
return 0;
}
+/**
+ * check_gran_ordering() - Check mixed-granularity decoder ordering
+ * @port: port being configured
+ * @cxlr: region under construction
+ * @cxld: port decoder
+ * @accum: selectors used by the ancestor decoders
+ * @iw: port decoder interleave ways
+ * @ig: derived port decoder granularity
+ *
+ * The mixed-granularity position calculation requires each interleaving
+ * decoder below the root to use a smaller granularity than its nearest
+ * interleaving ancestor. Passthrough decoders do not participate.
+ *
+ * Auto regions validate the firmware-programmed granularity. User-created
+ * regions validate @ig.
+ *
+ * Return: 0 on success, -ENXIO on invalid ordering.
+ */
+static int check_gran_ordering(struct cxl_port *port, struct cxl_region *cxlr,
+ struct cxl_decoder *cxld, u64 accum, int iw,
+ int ig)
+{
+ struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
+ u64 val, ancestor_gran;
+
+ if (!cxl_region_is_mixed_gran(cxlr) || iw <= 1)
+ return 0;
+
+ val = test_bit(CXL_REGION_F_AUTO, &cxlr->flags) ?
+ cxld->interleave_granularity : ig;
+ ancestor_gran = accum ? (1ULL << __ffs64(accum)) :
+ cxlrd->cxlsd.cxld.interleave_granularity;
+
+ if (val >= ancestor_gran) {
+ dev_dbg(&cxlr->dev,
+ "%s:%s: iw %d ig %llu not finer than nearest interleaving ancestor (ig %llu); coarse-to-fine ordering required\n",
+ dev_name(port->uport_dev), dev_name(&port->dev),
+ iw, val, ancestor_gran);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
/**
* cxl_port_setup_targets() - Validate and program a port decoder
* @port: port being configured
@@ -1762,6 +1806,10 @@ static int cxl_port_setup_targets(struct cxl_port *port,
return rc;
}
+ rc = check_gran_ordering(port, cxlr, cxld, accum, iw, ig);
+ if (rc)
+ return rc;
+
if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
if (iw > 1 && cxld->interleave_granularity != ig) {
dev_dbg(&cxlr->dev,
--
2.37.3
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 7/9] cxl/region: Fail region creation on position check mismatch
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
` (5 preceding siblings ...)
2026-07-30 22:20 ` [PATCH v3 6/9] cxl/region: Enforce coarse-to-fine ordering for " Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
2026-07-30 22:40 ` sashiko-bot
2026-07-30 22:20 ` [PATCH v3 8/9] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
2026-07-30 22:20 ` [PATCH v3 9/9] Documentation/cxl: Describe multi-level interleave validation Alison Schofield
8 siblings, 1 reply; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
The position check in cxl_region_attach() only logged a mismatch
between the attach-order position and the auto-discovery calculator.
A mismatch means the same configuration would decode wrong when
presented as CXL_REGION_F_AUTO, yet region creation still succeeded.
Fail region creation on a mismatch, before the region reaches
CXL_CONFIG_ACTIVE, so a configuration that cannot round-trip through
auto-discovery cannot be committed.
Since cxl_region_setup_targets() has already programmed the topology
at this point, tear it down before returning so a later detach can
cleanly replace the endpoint.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
drivers/cxl/core/region.c | 46 +++++++++++++++++++--------------------
1 file changed, 23 insertions(+), 23 deletions(-)
diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index b4e8ebe158f7..62179d987e4b 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -2492,6 +2492,29 @@ static int cxl_region_attach(struct cxl_region *cxlr,
rc = cxl_region_setup_targets(cxlr);
if (rc)
return rc;
+
+ /*
+ * Verify that auto-discovery reconstructs the endpoint
+ * positions assigned while creating the region.
+ */
+ root_pos_per_target = root_positions_per_target(cxlr);
+ for (int i = 0; i < p->nr_targets; i++) {
+ struct cxl_endpoint_decoder *target = p->targets[i];
+ int test_pos;
+
+ test_pos = cxl_calc_interleave_pos(target,
+ &cxlr->hpa_range,
+ root_pos_per_target,
+ p->interleave_granularity);
+ if (test_pos != target->pos) {
+ dev_err(&target->cxld.dev,
+ "cxl_calc_interleave_pos() mismatch: test_pos:%d target->pos:%d\n",
+ test_pos, target->pos);
+ cxl_region_teardown_targets(cxlr);
+ return -ENXIO;
+ }
+ }
+
p->state = CXL_CONFIG_ACTIVE;
cxl_region_shared_upstream_bandwidth_update(cxlr);
}
@@ -2503,29 +2526,6 @@ static int cxl_region_attach(struct cxl_region *cxlr,
.end = p->res->end,
};
- if (p->nr_targets != p->interleave_ways)
- return 0;
-
- /*
- * Test the auto-discovery position calculator function
- * against this successfully created user-defined region.
- * A fail message here means that this interleave config
- * will fail when presented as CXL_REGION_F_AUTO.
- */
- root_pos_per_target = root_positions_per_target(cxlr);
- for (int i = 0; i < p->nr_targets; i++) {
- struct cxl_endpoint_decoder *target = p->targets[i];
- int test_pos;
-
- test_pos = cxl_calc_interleave_pos(target, &cxlr->hpa_range,
- root_pos_per_target,
- p->interleave_granularity);
- 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);
- }
-
return 0;
}
--
2.37.3
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v3 7/9] cxl/region: Fail region creation on position check mismatch
2026-07-30 22:20 ` [PATCH v3 7/9] cxl/region: Fail region creation on position check mismatch Alison Schofield
@ 2026-07-30 22:40 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-30 22:40 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] Duplicate region target attach causes state corruption and NULL pointer dereference.
--
--- Patch [7]: [PATCH v3 7/9] cxl/region: Fail region creation on position check mismatch ---
commit 4352ee883f0aeb726a532c5b7f4c7ec6075bf005
Author: Alison Schofield <alison.schofield@intel.com>
cxl/region: Fail region creation on position check mismatch
This commit fails region creation when there is a mismatch between the
attach-order position and the auto-discovery calculator. By tearing down
targets before returning, it ensures configurations that cannot round-trip
through auto-discovery are not committed to CXL_CONFIG_ACTIVE.
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index b4e8ebe158f7a..62179d987e4b5 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
> @@ -2492,6 +2492,29 @@ static int cxl_region_attach(struct cxl_region *cxlr,
> rc = cxl_region_setup_targets(cxlr);
> if (rc)
> return rc;
[Severity: High]
This is a pre-existing issue, but does a duplicate target attach cause a NULL
pointer dereference here?
If a user writes the same endpoint decoder twice to a region target position,
the first write succeeds. On the second write, cxl_region_validate_position()
returns 0 because p->targets[pos] == cxled.
We then proceed to cxl_region_attach_position() which fails during xa_insert()
with -EEXIST. Its error rollback path unconditionally calls
cxl_port_detach_region() on all upstream ports, wiping out the cxl_rr port
tracking and freeing it if empty.
However, p->targets[pos] still holds the decoder.
Once the remaining targets are attached, this cxl_region_setup_targets() call
iterates over p->targets and calls cxl_port_setup_targets(), which attempts
to load the freed cxl_rr:
drivers/cxl/core/region.c:cxl_port_setup_targets() {
...
struct cxl_region_ref *cxl_rr = cxl_rr_load(port, cxlr);
...
struct cxl_decoder *cxld = cxl_rr->decoder;
...
}
Will this dereference the freed cxl_rr? Should cxl_region_attach() instead
short-circuit when cxl_region_validate_position() indicates the target is
already attached?
> +
> + /*
> + * Verify that auto-discovery reconstructs the endpoint
> + * positions assigned while creating the region.
> + */
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1785444498.git.alison.schofield@intel.com?part=7
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 8/9] cxl/test: Add a topology to test mixed-granularity regions
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
` (6 preceding siblings ...)
2026-07-30 22:20 ` [PATCH v3 7/9] cxl/region: Fail region creation on position check mismatch Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
2026-07-30 22:20 ` [PATCH v3 9/9] Documentation/cxl: Describe multi-level interleave validation Alison Schofield
8 siblings, 0 replies; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 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 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.
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 | 498 ++++++++++++++++++++++++++++++++---
1 file changed, 460 insertions(+), 38 deletions(-)
diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index ef92dd35e030..a5c9374a7194 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)
@@ -122,15 +164,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 +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,
@@ -479,9 +592,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 +660,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 +700,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 +723,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 +839,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 +1268,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 +1412,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 +1513,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 +1917,183 @@ 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;
+
+ /* 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 = 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 +2102,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 +2258,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 +2273,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 +2353,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 +2366,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 +2382,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 +2435,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 +2468,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] 12+ messages in thread* [PATCH v3 9/9] Documentation/cxl: Describe multi-level interleave validation
2026-07-30 22:20 [PATCH v3 0/9] cxl: Support mixed-granularity region interleaves Alison Schofield
` (7 preceding siblings ...)
2026-07-30 22:20 ` [PATCH v3 8/9] cxl/test: Add a topology to test mixed-granularity regions Alison Schofield
@ 2026-07-30 22:20 ` Alison Schofield
8 siblings, 0 replies; 12+ messages in thread
From: Alison Schofield @ 2026-07-30 22:20 UTC (permalink / raw)
To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
Vishal Verma, Ira Weiny, Li Ming, Robert Richter
Cc: linux-cxl
CXL defines the legal ways and granularities for multi-level
interleaves, including configurations where the region granularity is
finer than the root decoder granularity.
Add a short guide that points to the controlling CXL specification
sections and summarizes where Linux validates and applies that
interleave geometry.
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 | 66 +++++++++++++++++++
2 files changed, 67 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..8f23ef6992b9
--- /dev/null
+++ b/Documentation/driver-api/cxl/linux/region-granularity.rst
@@ -0,0 +1,66 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+========================================
+Multi-Level Interleave Region Geometry
+========================================
+
+CXL regions may route an address through a root decoder and one or more
+switch decoders before reaching an endpoint decoder. Each interleaving
+decoder selects one of its targets from the region address. The decoder
+levels must use a compatible combination of interleave ways and
+granularities so that their routing decisions form one region interleave.
+
+The CXL specification defines the legal multi-level combinations and is
+the authority for the interleave geometry:
+
+* CXL 4.0 Section 9.13.1.1, ``Legal Interleaving Configurations``
+* CXL 4.0 Tables 9-6, 9-7, and 9-8
+* CXL 4.0 Figures 9-17 and 9-18
+
+Those sections cover both power-of-2 interleaves and the 3-way interleave
+family, including regions whose granularity is finer than the root decoder
+granularity.
+
+Linux implementation
+====================
+
+The CXL core validates region geometry while walking the decoder topology
+in ``drivers/cxl/core/region.c``.
+
+For each port decoder, Linux:
+
+* collects the HPA selector bits already used by the root and ancestor
+ switch decoders;
+* rejects overlapping selectors or selectors outside the region selector;
+* derives the port decoder granularity from the selector bits available at
+ that level;
+* validates firmware-programmed values for auto-discovered regions and
+ programs the derived values for user-created regions; and
+* checks that endpoint positions agree with the decoder target lists.
+
+Mixed-granularity regions require different endpoint position arithmetic
+because one decoder target may cover multiple region positions before the
+next target is selected. The driver expresses that as the number of region
+positions covered by one decoder target and uses it while reconstructing
+endpoint positions.
+
+For 3-way-family roots, selector bits alone do not describe the full
+interleave span. Linux therefore also checks that the root and region cover
+the same span.
+
+Implementation details
+======================
+
+The implementation is documented with the functions that enforce each
+part of the model:
+
+* :c:func:`get_selector`
+* :c:func:`get_parent_selectors`
+* :c:func:`derive_port_granularity`
+* :c:func:`root_positions_per_target`
+* :c:func:`cxl_calc_interleave_pos`
+* :c:func:`check_gran_ordering`
+* :c:func:`cxl_port_setup_targets`
+
+The region-level granularity and 3-way-family span checks are performed in
+``cxl_region_attach()``.
--
2.37.3
^ permalink raw reply related [flat|nested] 12+ messages in thread