All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 0/5] CXL: Support region granularities finer than the root
@ 2026-10-13  4:44 Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 1/5] Documentation/cxl: Describe region granularity " Alison Schofield
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Alison Schofield @ 2026-10-13  4:44 UTC (permalink / raw)
  To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
	Vishal Verma, Ira Weiny, Li Ming, Robert Richter, Richard Cheng
  Cc: linux-cxl

*** Use to be cxl: Support mixed-granularity region interleaves *** 
Long long time ago, I can still remember when this issue first arose.
I simply called it mixed, because previously region and root decoders
had to match. Well, mixed was too simple and too broad. So rather than
continuing to qualify it, drop it and just state what the set actually
enables: region grans finer than root grans.

It's not just a new title. It reframes a lot of language throughout the
series, from the cover letter, commit messages, documentation, and
code naming.

Changes in v6:
Series wide:
- Reframe the series around what it enables: a region granularity finer
  than the root granularity, with monotonic refinement as the supported
  model. Drop the term "mixed-granularity", which read as support for
  arbitrary granularity arrangements. (Jonathan, Robert)
- Rebase onto the cxl_switch_port_setup_targets() refactor [1].
- Drop "cxl/region: Warn on user region position mismatch", merged in cxl/next
- Drop "cxl/region: Name the interleave locals in cxl_port_setup_targets()". 
Patch 1: Documentation/cxl: Describe region granularity finer than the root
- Separate what the CXL Spec permits from what Linux supports (Robert)
- Describe the resulting support rather than the prior restriction (Robert)
- Define region position by address translation (Robert)
- Define region granularity before the span equation (Robert)
- Condense the Mod3 section, keeping the annotated tables (Robert)
- Reword the refining and coarsening weight explanation
Patch 2: cxl/region: Generalize endpoint position mapping
- Pass the region to cxl_calc_interleave_pos() (Robert)
- Fold the per-level target lookup and weighting into cxl_port_interleave_pos() (Robert)
- Define region position by address translation in the kernel-doc (Robert)
- Drop the stale Normalized Addressing rationale (Robert)
- Name parent_pos in the commit message to match the code (Jonathan)
- Document the negative errno return of cxl_port_interleave_pos()
Patch 3: cxl/region: Support auto region granularity finer than the root
- Put peer distance walk and the iw/ig calculation in helpers (Robert)
- Return the peer distance of 1 explicitly for regions finer than the root,
  and explain why in the comment (Jonathan, Robert)
- Rename cxl_region_validate_interleave() to check_root_interleave() and
  move it next to check_interleave_cap() (Robert)
- Reword the span exemption comment to describe only the same-granularity
  power-of-two exception (Robert)
- Simplify the commit message description of the finer-than-region check (Robert)
- Describe the span identity as a requirement of the supported layouts (Robert)
Patch 4: cxl/region: Support user region granularity finer than the root
- Comment why a region granularity coarser than the root is rejected (Robert)
Patch 5: cxl/test: Add a topology for region granularity finer than the root
- Map the mock memdevs to online NUMA nodes, like other mock topologies
- Skip unpopulated entries in the finer-gran memdev cleanup loops
v5: https://lore.kernel.org/linux-cxl/cover.1788475206.git.alison.schofield@intel.com/


Begin Cover Letter:

Linux requires a region's interleave granularity to equal the interleave
granularity of its interleaving root decoder. This series relaxes that one
restriction: a region granularity may be finer than the root granularity,
provided the interleave levels between them refine monotonically toward
the endpoints.

This series does not add general support for every granularity arrangement
the CXL Specification permits. A region granularity coarser than the root
remains invalid, and configurations whose granularity refines and then
coarsens along the decoder path remain unsupported. Of the Mod3
configurations listed in CXL 4.0 Section 9.13.1.1, all are supported
except Table 9-6 row 6, the refine-then-coarsen 12-way composition.

The use case driving this series is 6- and 12-way regions behind a 3-way
host bridge interleave, CXL 4.0 Tables 9-6 and 9-7, which have no
same-granularity alternative. Power-of-two regions finer than the root
fall out of the same implementation.

Support applies to both firmware programmed auto regions and user created
regions.

Two prior proposals addressed parts of this gap:

AlisonS introduced position arithmetic and sysfs gating for auto and
user-created regions to support the 6- and 12-way interleave configurations
without a same-granularity alternative [2].

RobertR introduced an HPA selector-bit model for auto regions that allows
multi-level power-of-two interleaves regardless of granularity ordering [3].

Series structure in v6:
-----------------------
Patch 1 documents which region granularities Linux supports relative to the
root and the monotonic refinement model.

Patch 2 generalizes endpoint position calculation to account for granularity
changes between decoder levels.

Patch 3 enables auto regions with a granularity finer than the root and
validates their decoder geometry.

Patch 4 enables user created regions with a granularity finer than the root
by relaxing the existing root/region granularity restriction.

Patch 5 adds a cxl_test topology and coverage for region granularity finer
than the root.

Dependencies:
-------------
This series is built upon cxl/next, plus the refactor series [1].

The companion NDCTL patchset that allows these regions in 'cxl create-region'
and adds the unit test support is posted [4].


[1] https://lore.kernel.org/linux-cxl/cover.1791860305.git.alison.schofield@intel.com/
[2] https://lore.kernel.org/all/20250306232239.2609017-1-alison.schofield@intel.com/
[3] https://lore.kernel.org/all/20251028094754.72816-1-rrichter@amd.com/
[4] https://lore.kernel.org/linux-cxl/cover.1791863817.git.alison.schofield@intel.com/


Alison Schofield (5):
  Documentation/cxl: Describe region granularity finer than the root
  cxl/region: Generalize endpoint position mapping
  cxl/region: Support auto region granularity finer than the root
  cxl/region: Support user region granularity finer than the root
  cxl/test: Add a topology for region granularity finer than the root

 .../driver-api/cxl/linux/cxl-driver.rst       | 119 +++++
 drivers/cxl/core/region.c                     | 220 ++++++---
 tools/testing/cxl/test/cxl.c                  | 420 +++++++++++++++++-
 3 files changed, 681 insertions(+), 78 deletions(-)

-- 
2.37.3


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v6 1/5] Documentation/cxl: Describe region granularity finer than the root
  2026-10-13  4:44 [PATCH v6 0/5] CXL: Support region granularities finer than the root Alison Schofield
@ 2026-10-13  4:44 ` Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 2/5] cxl/region: Generalize endpoint position mapping Alison Schofield
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Alison Schofield @ 2026-10-13  4:44 UTC (permalink / raw)
  To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
	Vishal Verma, Ira Weiny, Li Ming, Robert Richter, Richard Cheng
  Cc: linux-cxl, Jonathan Cameron

Linux has required that a region's interleave granularity equal the
interleave granularity of its interleaving root decoder. Upcoming changes
lift that restriction for one configuration: a region granularity finer
than the root's, provided granularity refines monotonically from the root
toward the endpoints.

That 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 which region granularities Linux supports relative to the root,
and the monotonic refinement model for a region finer than the root.
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.

Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Assisted-by: LLM
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
 .../driver-api/cxl/linux/cxl-driver.rst       | 119 ++++++++++++++++++
 1 file changed, 119 insertions(+)

diff --git a/Documentation/driver-api/cxl/linux/cxl-driver.rst b/Documentation/driver-api/cxl/linux/cxl-driver.rst
index dd6dd17dc536..9c2fa4a23d47 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 `Finer-Than-Root Region Granularity`_.
+
 At Endpoint
 ~~~~~~~~~~~
 `Endpoint Decoders` are programmed similar to Host Bridge and Switch decoders,
@@ -619,6 +624,120 @@ 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.
 
+Finer-Than-Root Region Granularity
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The CXL Specification permits each decoder in a region's hierarchy to interleave
+at its own granularity, see CXL 4.0 Section 9.13.1.  Linux supports a subset of
+those configurations, defined by how the region's :code:`interleave_granularity`
+compares with the granularity of its interleaving root decoder:
+
+- Equal: granularity stays the same or coarsens at each level toward the
+  endpoints, as in `Cross-Link First`.
+- Finer: granularity stays the same or refines at each level toward the
+  endpoints.  This requires an interleaving root decoder.
+- Coarser: not supported.
+
+In both supported cases granularity changes monotonically across the decoders
+that route a request: the interleaving root, the host bridge, and any switches.
+Configurations whose granularity refines and then coarsens along that path are
+legal per the CXL Specification but are not supported by Linux.  The endpoint
+decoder is not part of that path.  It carries the region ways and granularity so
+that it can translate, as `At Endpoint`_ describes above.
+
+When the region granularity is finer than the root granularity, Linux programs
+each level below the root at its parent's granularity divided by its own ways,
+so granularity refines monotonically and the ordering follows from the region
+and root settings.  Reaching a fine granularity as early as possible remains
+available through `Cross-Link First`.  For an 8-way region at 1024 below a 2-way
+interleaving root decoder at 4096, where each host bridge interleaves across two
+root ports and each root port hosts a 2-way switch, Linux programs::
+
+  Level             Ways    Granularity
+  -----             ----    -----------
+  Root                 2         4096
+  Host bridge          2         2048
+  Switch               2         1024
+  Endpoint             8         1024
+
+An endpoint's *region position* is the interleave position that address
+translation applies at the region granularity: it is combined with a DPA to form
+the HPA, and removed from an HPA to recover the DPA.  It is also the index of
+the endpoint's region-granularity chunk within one full pass of the region
+interleave.  Each decoder level contributes the index of the target it routes
+the endpoint through, weighted by its granularity in region-granularity units::
+
+  position += target_position *
+              (decoder_granularity / region_granularity)
+
+That multiplier is the *weight* of a level: how many region positions pass
+between successive advances of its target index.  In the example above the root
+selects a host bridge every 4096 bytes, so it advances one target every four
+region positions, weight 4, while the switch advances one target every position,
+weight 1.  When granularity refines toward the endpoints, a level's weight is
+the product of the ways of the interleaving levels below it.  When it coarsens,
+as in `Cross-Link First`, the weight is the product of the ways of the levels
+above it, so the root has weight 1.
+
+The *region granularity* is the region's :code:`interleave_granularity`, the
+granularity at which its endpoints translate.  A region finer than the root
+must describe the same interleave *span* as the root decoder, the address range
+in which the interleave pattern completes one full pass::
+
+  root_ways * root_granularity == region_ways * region_granularity
+
+Linux rejects a region that does not describe that span.  A same-granularity
+region below a power-of-two root decoder is exempt: it may be wider than the
+root and repeat the root targets, so its span is a multiple of the root's.
+
+Mod3 Interleave Configurations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A 3-way, 6-way, or 12-way interleave, known as a Mod3 interleave, selects its
+target modulo 3 rather than from binary interleave selector bits alone, see CXL
+4.0 Section 9.13.1.1.  The cross-host bridge selection carries the factor of
+three, and any remaining x2 or x4 is binary interleave selection below it.  A
+6-way region at IGB across three host bridges is therefore a 3-way root
+interleave at 2*IGB with a 2-way interleave at IGB below it, both describing the
+same span::
+
+  3 * (2 * IGB) == 6 * IGB
+
+CXL 4.0 Section 9.13.1.1, Tables 9-6, 9-7, and 9-8, list the legal compositions
+for a 12-way, 6-way, and 3-way device-level interleave at IGB.  The
+specification is normative.  The 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 but is unsupported by Linux:
+from the root toward the endpoints its 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, so a
+12-way device-level interleave across three host bridges remains available.
+
 Example Configurations
 ======================
 .. toctree::
-- 
2.37.3


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v6 2/5] cxl/region: Generalize endpoint position mapping
  2026-10-13  4:44 [PATCH v6 0/5] CXL: Support region granularities finer than the root Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 1/5] Documentation/cxl: Describe region granularity " Alison Schofield
@ 2026-10-13  4:44 ` Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 3/5] cxl/region: Support auto region granularity finer than the root Alison Schofield
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Alison Schofield @ 2026-10-13  4:44 UTC (permalink / raw)
  To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
	Vishal Verma, Ira Weiny, Li Ming, Robert Richter, Richard Cheng
  Cc: linux-cxl, Jonathan Cameron

Endpoint position calculation assumes an interleaving root has the same
granularity as the region. Auto region assembly multiplies by each parent
decoder's ways, and user region creation selects the root target with
'pos % ways'. A region granularity finer than the root breaks that
assumption, since decoder granularity then changes between levels of the
hierarchy.

Weight each decoder level by its granularity in region-granularity units:

    position += parent_pos * (parent_granularity / region_granularity)

and use the same relationship to select the root target for user regions.
Pass the region to cxl_calc_interleave_pos() for the granularity and HPA
range it needs.

For currently supported regions the result is unchanged, except for a
region wider than a same-granularity Mod3 root, which the root span check
added with finer-than-root support rejects.

Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Assisted-by: LLM
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
 drivers/cxl/core/region.c | 104 +++++++++++++++++++++++++-------------
 1 file changed, 70 insertions(+), 34 deletions(-)

diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 50f4830c225d..35fefc33206d 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1864,9 +1864,20 @@ 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 root_pos = pos;
 	int rc;
 
-	if (dport != cxlrd->cxlsd.target[pos % iw]) {
+	/*
+	 * Root target selection advances once per root-granularity chunk, so
+	 * convert the region position to root-granularity units. A
+	 * non-interleaving root does not contribute to the region interleave,
+	 * so its advertised granularity must not scale the position.
+	 */
+	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));
@@ -1967,11 +1978,22 @@ 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)
+/*
+ * Return the region position offset that @port's slot in its parent decoder
+ * target list contributes: the slot index weighted by the parent decoder's
+ * granularity in region-granularity units.
+ *
+ * Returns the non-negative offset on success, or a negative errno if the parent
+ * decoder or @port's slot in its target list cannot be found.
+ */
+static int cxl_port_interleave_pos(struct cxl_port *port,
+				   struct cxl_region *cxlr)
 {
+	int region_gran = cxlr->params.interleave_granularity;
+	struct range *range = &cxlr->hpa_range;
 	struct cxl_switch_decoder *cxlsd;
 	struct cxl_port *parent;
+	int ways, gran, pos;
 	int rc = -ENXIO;
 
 	parent = parent_port_of(port);
@@ -1987,46 +2009,61 @@ 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;
+			pos = i;
 			rc = 0;
 			break;
 		}
 	}
-	if (rc)
+	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(dev));
+		return rc;
+	}
 
-	return rc;
+	return pos * (gran / region_gran);
 }
 
 /**
  * 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
+ * @cxled: endpoint decoder member of @cxlr
+ * @cxlr: region providing the translated HPA range and interleave granularity
  *
- * The endpoint position is calculated by traversing the topology from
- * the endpoint to the root decoder and iteratively applying this
- * calculation:
+ * An endpoint's region position is the interleave position that address
+ * translation applies at the region granularity. cxl_calculate_hpa_offset()
+ * combines it with a DPA offset to form the HPA offset, and
+ * cxl_calculate_position() recovers it from that HPA offset. It is also the
+ * index of the endpoint's region-granularity chunk within one full pass of the
+ * region interleave.
  *
- *    position = position * parent_ways + parent_pos;
+ * Each decoder level above the endpoint contributes the index of the target
+ * it routes the endpoint through, scaled by the weight of that level: how many
+ * region positions pass between successive advances of its target index. The
+ * position is calculated by traversing the topology from the endpoint to the
+ * root decoder and accumulating those contributions:
  *
- * ...where @position is inferred from switch and root decoder target lists.
+ *    position += parent_pos * (parent_granularity / region_granularity);
+ *
+ * ...where @parent_pos is the index of the endpoint's path in a switch or root
+ * decoder target list. A level that selects a single target contributes
+ * nothing.
  *
  * Return: position >= 0 on success
  *	   -ENXIO on failure
  */
 static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled,
-				   struct range *hpa_range)
+				   struct cxl_region *cxlr)
 {
 	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;
+	struct range *hpa_range = &cxlr->hpa_range;
+	int pos = 0;
 	int rc;
 
 	/*
@@ -2039,36 +2076,35 @@ 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 the region
+	 * granularity, so each host-bridge decoder interleaves at twice that.
+	 * The host-bridge decoders advance one target every two region
+	 * positions, weight 2, and the root decoder advances one target every
+	 * region position, weight 1.
 	 *
 	 * 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.
 	 */
 
-	/* Iterate from endpoint to root_port refining the position */
+	/* Iterate from endpoint to root_port accumulating the position */
 	for (iter = port; iter; iter = parent_port_of(iter)) {
 		if (is_cxl_root(iter))
 			break;
 
-		rc = find_pos_and_ways(iter, hpa_range, &parent_pos,
-				       &parent_ways);
-		if (rc)
+		rc = cxl_port_interleave_pos(iter, cxlr);
+		if (rc < 0)
 			return rc;
 
-		pos = pos * parent_ways + parent_pos;
+		pos += rc;
 	}
 
 	dev_dbg(&cxlmd->dev,
@@ -2087,7 +2123,7 @@ static int cxl_region_sort_targets(struct cxl_region *cxlr)
 	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);
 		/*
 		 * Record that sorting failed, but still continue to calc
 		 * cxled->pos so that cxl_calc_interleave_pos() emits its
@@ -2272,7 +2308,7 @@ static int cxl_region_attach(struct cxl_region *cxlr,
 		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);
 		if (test_pos != target->pos)
 			dev_warn(&cxlr->dev,
 				 "%s: position mismatch: calculated:%d assigned:%d\n",
-- 
2.37.3


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v6 3/5] cxl/region: Support auto region granularity finer than the root
  2026-10-13  4:44 [PATCH v6 0/5] CXL: Support region granularities finer than the root Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 1/5] Documentation/cxl: Describe region granularity " Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 2/5] cxl/region: Generalize endpoint position mapping Alison Schofield
@ 2026-10-13  4:44 ` Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 4/5] cxl/region: Support user " Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 5/5] cxl/test: Add a topology for " Alison Schofield
  4 siblings, 0 replies; 6+ messages in thread
From: Alison Schofield @ 2026-10-13  4:44 UTC (permalink / raw)
  To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
	Vishal Verma, Ira Weiny, Li Ming, Robert Richter, Richard Cheng
  Cc: linux-cxl, Jonathan Cameron

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 requires the interleaving root to have the same
granularity as the region, with each successive interleaving level becoming
coarser toward the endpoints. The driver cannot assemble a region whose
interleave granularity is finer than the granularity of its interleaving
root decoder.

Support auto regions with a granularity finer than the root, restricted to
configurations whose granularity refines monotonically toward the
endpoints. Derive each interleaving decoder's granularity from its parent:

        ig = parent_ig / iw

A same-granularity region keeps the existing derivation, which coarsens
toward the endpoints:

        ig = parent_ig * parent_iw

The topology determines each decoder's interleave ways, so the parent
granularity and the decoder's ways determine its granularity. Use that
relationship to validate the decoder geometry while assembling an auto
region. Only the interleaving levels are compared; a passthrough level,
iw == 1, keeps its granularity exemption.

Refuse a derived granularity finer than the region granularity. Each
interleaving level consumes part of the region interleave, and the deepest
interleaving level must not refine beyond the region granularity.

For the layouts Linux supports, require the root and region to interleave
over the same span, the address range in which the interleave pattern
completes one full pass:

        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.

Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Assisted-by: LLM
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
 drivers/cxl/core/region.c | 102 +++++++++++++++++++++++++++++++-------
 1 file changed, 85 insertions(+), 17 deletions(-)

diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index 35fefc33206d..ee450704af2c 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -1432,6 +1432,52 @@ static int check_interleave_cap(struct cxl_decoder *cxld, int iw, int ig)
 	return 0;
 }
 
+static int check_root_interleave(struct cxl_region *cxlr)
+{
+	struct cxl_decoder *cxld = &cxlr->cxlrd->cxlsd.cxld;
+	struct cxl_region_params *p = &cxlr->params;
+	int root_ig = cxld->interleave_granularity;
+	int root_iw = cxld->interleave_ways;
+
+	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;
+	}
+
+	/*
+	 * A same-granularity region below a power-of-two root may repeat
+	 * the root targets, spanning a multiple of the root span.
+	 */
+	if (is_power_of_2(root_iw) && p->interleave_granularity == root_ig)
+		return 0;
+
+	/* Span is one complete pass through the interleave pattern */
+	if (root_iw * root_ig != p->interleave_ways * p->interleave_granularity) {
+		dev_dbg(&cxlr->dev,
+			"region span %d (%d ways at %d) does not match root span %d (%d ways at %d)\n",
+			p->interleave_ways * p->interleave_granularity,
+			p->interleave_ways, p->interleave_granularity,
+			root_iw * root_ig, root_iw, root_ig);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+/* Region IG is finer than the IG of its interleaving root decoder */
+static bool cxl_region_is_finer_than_root(struct cxl_region *cxlr)
+{
+	struct cxl_decoder *cxld = &cxlr->cxlrd->cxlsd.cxld;
+
+	return cxld->interleave_ways > 1 &&
+	       cxld->interleave_granularity > cxlr->params.interleave_granularity;
+}
+
 /*
  * The "distance" between peer downstream ports represents which
  * endpoint positions in the region interleave a given port can
@@ -1449,6 +1495,14 @@ static int cxl_port_peer_distance(struct cxl_port *port, struct cxl_region *cxlr
 	struct cxl_port *iter = port;
 	int distance = 1;
 
+	/*
+	 * A finer-than-root region refines toward the endpoints, so peer
+	 * targets at the finest interleaving level occupy consecutive region
+	 * positions.
+	 */
+	if (cxl_region_is_finer_than_root(cxlr))
+		return 1;
+
 	do {
 		cxl_rr_iter = cxl_rr_load(iter, cxlr);
 		distance *= cxl_rr_iter->nr_targets;
@@ -1468,15 +1522,17 @@ static int cxl_port_parent_interleave(struct cxl_port *port, struct cxl_region *
 	int parent_iw, parent_ig;
 
 	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;
+		if (parent_iw > 1) {
+			parent_ig = cxlrd->cxlsd.cxld.interleave_granularity;
+		} else {
+			/*
+			 * A single-way root does not interleave, so its
+			 * granularity does not constrain the region interleave.
+			 * Use the region granularity instead.
+			 */
+			parent_ig = p->interleave_granularity;
+		}
 		/*
 		 * For purposes of address bit routing, use power-of-2 math for
 		 * switch ports.
@@ -1517,7 +1573,7 @@ static int cxl_port_calc_interleave(struct cxl_port *port, struct cxl_region *cx
 				    struct cxl_region_ref *cxl_rr, int *out_iw,
 				    int *out_ig)
 {
-	struct cxl_port *parent_port = to_cxl_port(port->dev.parent);
+	struct cxl_region_params *p = &cxlr->params;
 	struct cxl_decoder *cxld = cxl_rr->decoder;
 	int parent_iw, parent_ig, iw, ig, rc;
 	struct cxl_switch_decoder *cxlsd;
@@ -1533,16 +1589,24 @@ static int cxl_port_calc_interleave(struct cxl_port *port, struct cxl_region *cx
 		return -EINVAL;
 	}
 
-	/*
-	 * Interleave granularity is a multiple of @parent_port granularity.
-	 * Multiplier is the parent port interleave ways.
-	 */
-	ig = parent_ig * parent_iw;
+	/* Finer-than-root regions refine granularity toward the endpoints */
+	if (cxl_region_is_finer_than_root(cxlr)) {
+		ig = parent_ig / iw;
+
+		if (ig < p->interleave_granularity) {
+			dev_dbg(&cxlr->dev,
+				"%s:%s: granularity %d is finer than region granularity %d\n",
+				dev_name(port->uport_dev), dev_name(&port->dev),
+				ig, p->interleave_granularity);
+			return -ENXIO;
+		}
+	} else {
+		ig = parent_ig * parent_iw;
+	}
 
 	if (!granularity_valid(ig)) {
-		dev_dbg(&cxlr->dev,
-			"%s: invalid granularity calculation (%d * %d)\n",
-			dev_name(&parent_port->dev), parent_ig, parent_iw);
+		dev_dbg(&cxlr->dev, "%s:%s: invalid granularity: %d\n",
+			dev_name(port->uport_dev), dev_name(&port->dev), ig);
 		return -EINVAL;
 	}
 
@@ -2183,6 +2247,10 @@ static int cxl_region_attach(struct cxl_region *cxlr,
 		return -ENXIO;
 	}
 
+	rc = check_root_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] 6+ messages in thread

* [PATCH v6 4/5] cxl/region: Support user region granularity finer than the root
  2026-10-13  4:44 [PATCH v6 0/5] CXL: Support region granularities finer than the root Alison Schofield
                   ` (2 preceding siblings ...)
  2026-10-13  4:44 ` [PATCH v6 3/5] cxl/region: Support auto region granularity finer than the root Alison Schofield
@ 2026-10-13  4:44 ` Alison Schofield
  2026-10-13  4:44 ` [PATCH v6 5/5] cxl/test: Add a topology for " Alison Schofield
  4 siblings, 0 replies; 6+ messages in thread
From: Alison Schofield @ 2026-10-13  4:44 UTC (permalink / raw)
  To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
	Vishal Verma, Ira Weiny, Li Ming, Robert Richter, Richard Cheng
  Cc: linux-cxl, Jonathan Cameron

User region creation currently requires an interleaving root to have the
same granularity as the region.

That rejects a region granularity finer than the granularity of the
interleaving root decoder. 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.

Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Assisted-by: LLM
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
 drivers/cxl/core/region.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
index ee450704af2c..03472ed12e9d 100644
--- a/drivers/cxl/core/region.c
+++ b/drivers/cxl/core/region.c
@@ -569,15 +569,15 @@ static int set_interleave_granularity(struct cxl_region *cxlr, int val)
 		return -EINVAL;
 
 	/*
-	 * 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).
+	 * Region granularity coarser than the root produces invalid endpoint
+	 * address translations.
 	 */
-	if (cxld->interleave_ways > 1 && val != cxld->interleave_granularity)
+	if (cxld->interleave_ways > 1 && val > cxld->interleave_granularity) {
+		dev_dbg(&cxlr->dev,
+			"cannot set granularity to %d: exceeds root decoder granularity %d\n",
+			val, cxld->interleave_granularity);
 		return -EINVAL;
+	}
 
 	lockdep_assert_held_write(&cxl_rwsem.region);
 
-- 
2.37.3


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v6 5/5] cxl/test: Add a topology for region granularity finer than the root
  2026-10-13  4:44 [PATCH v6 0/5] CXL: Support region granularities finer than the root Alison Schofield
                   ` (3 preceding siblings ...)
  2026-10-13  4:44 ` [PATCH v6 4/5] cxl/region: Support user " Alison Schofield
@ 2026-10-13  4:44 ` Alison Schofield
  4 siblings, 0 replies; 6+ messages in thread
From: Alison Schofield @ 2026-10-13  4:44 UTC (permalink / raw)
  To: Davidlohr Bueso, Jonathan Cameron, Dave Jiang, Alison Schofield,
	Vishal Verma, Ira Weiny, Li Ming, Robert Richter, Richard Cheng
  Cc: linux-cxl

Add a cxl_test topology for testing regions whose granularity is finer
than the root granularity.

modprobe cxl_test finer_gran_regions=1 creates three host bridges, two
root ports per host bridge, one switch per root port, and two endpoints
per switch, for twelve endpoints total.

Selecting different endpoint combinations allows interleaving at the host
bridge, at the switch, or at both, without requiring separate topologies.

Three CFMWS windows are added:

1) A 2-way window at 4K over two host bridges, hosting an 8-way region
at 1K and a 4-way region at 2K.

2) A 3-way window at 512 over three host bridges, hosting a 6-way
region at 256: CXL 4.0 Table 9-7 rows 2 and 3.

3) A 3-way window at 1K over three host bridges, hosting a 12-way
region at 256: CXL 4.0 Table 9-6 row 7.

NULL-check the legacy topology arrays in the mock device and mock port
lookups, which are left empty when finer_gran_regions=1 selects the new
topology.

The default and existing special module params are unaffected.

Assisted-by: LLM
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
 tools/testing/cxl/test/cxl.c | 420 +++++++++++++++++++++++++++++++++--
 1 file changed, 400 insertions(+), 20 deletions(-)

diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c
index 3eb1051b1ee8..ffad0867f1df 100644
--- a/tools/testing/cxl/test/cxl.c
+++ b/tools/testing/cxl/test/cxl.c
@@ -19,6 +19,7 @@ static bool extended_linear_cache;
 static bool fail_autoassemble;
 static bool type2_test;
 static bool mock_zero_size_decoders;
+static bool finer_gran_regions;
 
 #define FAKE_QTG_ID	42
 
@@ -28,9 +29,33 @@ static bool mock_zero_size_decoders;
 #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)
 #define NR_CXL_TYPE2_ACCEL 1
 
+/*
+ * finer_gran_regions=1: three host bridges, two root ports each, and one
+ * switch per root port, reaching twelve endpoints. Two root ports per host
+ * bridge let a region interleave at the host bridge, at the switch, or at
+ * both, so a single topology covers the power-of-2 and 3-way-family region
+ * layouts that the CXL Specification permits.
+ */
+#define NR_CXL_FINER_GRAN_HB 3
+#define NR_CXL_FINER_GRAN_RPS_PER_HB 2
+#define NR_CXL_FINER_GRAN_RPS \
+	(NR_CXL_FINER_GRAN_HB * NR_CXL_FINER_GRAN_RPS_PER_HB)
+#define NR_CXL_FINER_GRAN_SWITCHES NR_CXL_FINER_GRAN_RPS
+#define NR_CXL_FINER_GRAN_DPORTS \
+	(NR_CXL_FINER_GRAN_SWITCHES * NR_CXL_SWITCH_PORTS)
+
+#define NR_DEFAULT_BRIDGES \
+	(NR_CXL_HOST_BRIDGES + NR_CXL_SINGLE_HOST + NR_CXL_RCH)
+#define NR_BRIDGES (NR_DEFAULT_BRIDGES + NR_CXL_FINER_GRAN_HB)
+
+/* CHBS index ranges within mock_cedt.chbs[] */
+#define CHBS_DEFAULT_START 0
+#define CHBS_DEFAULT_END (NR_DEFAULT_BRIDGES - 1)
+#define CHBS_FINER_GRAN_START NR_DEFAULT_BRIDGES
+#define CHBS_FINER_GRAN_END (NR_BRIDGES - 1)
+
 #define MOCK_AUTO_REGION_SIZE_DEFAULT SZ_512M
 static int mock_auto_region_size = MOCK_AUTO_REGION_SIZE_DEFAULT;
 
@@ -55,6 +80,12 @@ 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_finer_gran_hb[NR_CXL_FINER_GRAN_HB];
+static struct platform_device *cxl_finer_gran_root_port[NR_CXL_FINER_GRAN_RPS];
+static struct platform_device *cxl_finer_gran_uport[NR_CXL_FINER_GRAN_SWITCHES];
+static struct platform_device *cxl_finer_gran_dport[NR_CXL_FINER_GRAN_DPORTS];
+static struct platform_device *cxl_mem_finer_gran[NR_CXL_FINER_GRAN_DPORTS];
+
 /*
  * Decoder registry
  *
@@ -118,6 +149,19 @@ static struct acpi_device host_bridge[NR_BRIDGES] = {
 		.handle = &host_bridge[3],
 		.pnp.unique_id = "3",
 	},
+	/* Dedicated finer-than-root 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)
@@ -125,15 +169,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_finer_gran); i++)
+		if (cxl_mem_finer_gran[i] && dev == &cxl_mem_finer_gran[i]->dev)
+			return true;
+	if (cxl_acpi && dev == &cxl_acpi->dev)
 		return true;
 	return false;
 }
@@ -191,6 +238,18 @@ 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_cfmws cfmws;
+		u32 target[3];
+	} cfmws11;
 	struct {
 		struct acpi_cedt_cxims cxims;
 		u64 xormap_list[2];
@@ -235,6 +294,31 @@ static struct {
 		.uid = 3,
 		.cxl_version = ACPI_CEDT_CHBS_VERSION_CXL11,
 	},
+	/* Dedicated finer-than-root 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 = {
@@ -374,6 +458,51 @@ 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, },
+	},
+	.cfmws11 = {
+		.cfmws = {
+			.header = {
+				.type = ACPI_CEDT_TYPE_CFMWS,
+				.length = sizeof(mock_cedt.cfmws11),
+			},
+			.interleave_ways = 8,
+			.granularity = 2,
+			.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 = {
@@ -411,6 +540,10 @@ struct acpi_cedt_cfmws *mock_cfmws[] = {
 	[6] = &mock_cedt.cfmws6.cfmws,
 	[7] = &mock_cedt.cfmws7.cfmws,
 	[8] = &mock_cedt.cfmws8.cfmws,
+	/* Dedicated finer-than-root granularity topology */
+	[9] = &mock_cedt.cfmws9.cfmws,
+	[10] = &mock_cedt.cfmws10.cfmws,
+	[11] = &mock_cedt.cfmws11.cfmws,
 };
 
 static int cfmws_start;
@@ -419,6 +552,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_FINER_GRAN_ARRAY_START 9
+#define CFMWS_FINER_GRAN_ARRAY_END   11
 
 struct acpi_cedt_cxims *mock_cxims[1] = {
 	[0] = &mock_cedt.cxims0.cxims,
@@ -497,12 +632,26 @@ static void update_type2_cfmws(void)
 	memcpy(&mock_cedt.cfmws0.cfmws, &type2_cfmws0, sizeof(type2_cfmws0));
 }
 
+/* CHBS entries belonging to the topology this load selected */
+static void chbs_range(int *lo, int *hi)
+{
+	if (finer_gran_regions) {
+		*lo = CHBS_FINER_GRAN_START;
+		*hi = CHBS_FINER_GRAN_END;
+	} else {
+		*lo = CHBS_DEFAULT_START;
+		*hi = CHBS_DEFAULT_END;
+	}
+}
+
 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++) {
+	chbs_range(&chbs_lo, &chbs_hi);
+	for (i = chbs_lo; i <= chbs_hi; i++) {
 		struct acpi_cedt_chbs *chbs = &mock_cedt.chbs[i];
 		resource_size_t size;
 
@@ -563,12 +712,16 @@ 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;
+
+		chbs_range(&chbs_lo, &chbs_hi);
+		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++) {
@@ -592,13 +745,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_finer_gran_hb); i++)
+		if (cxl_finer_gran_hb[i] && dev == &cxl_finer_gran_hb[i]->dev)
 			return true;
 
 	return false;
@@ -612,27 +768,42 @@ 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_finer_gran_root_port); i++)
+		if (cxl_finer_gran_root_port[i] &&
+		    dev == &cxl_finer_gran_root_port[i]->dev)
+			return true;
+
+	for (i = 0; i < ARRAY_SIZE(cxl_finer_gran_uport); i++)
+		if (cxl_finer_gran_uport[i] &&
+		    dev == &cxl_finer_gran_uport[i]->dev)
+			return true;
+
+	for (i = 0; i < ARRAY_SIZE(cxl_finer_gran_dport); i++)
+		if (cxl_finer_gran_dport[i] &&
+		    dev == &cxl_finer_gran_dport[i]->dev)
 			return true;
 
 	if (is_cxl_memdev(dev))
@@ -703,7 +874,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)
@@ -1413,7 +1592,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;
 			}
@@ -1461,6 +1641,8 @@ static int mock_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm,
 
 	if (is_cxl_endpoint(port))
 		target_count = 0;
+	else if (finer_gran_regions && is_cxl_root(parent_port))
+		target_count = NR_CXL_FINER_GRAN_RPS_PER_HB;
 	else if (is_cxl_root(parent_port))
 		target_count = NR_CXL_ROOT_PORTS;
 	else
@@ -1560,6 +1742,24 @@ static int get_port_array(struct cxl_port *port,
 	struct platform_device **array;
 	int array_size;
 
+	if (finer_gran_regions) {
+		if (port->depth == 1) {
+			array_size = ARRAY_SIZE(cxl_finer_gran_root_port);
+			array = cxl_finer_gran_root_port;
+		} else if (port->depth == 2) {
+			array_size = ARRAY_SIZE(cxl_finer_gran_dport);
+			array = cxl_finer_gran_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);
@@ -1953,11 +2153,142 @@ static void cxl_single_topo_exit(void)
 	}
 }
 
+#define FINER_GRAN_RP_ID_BASE	(NR_MULTI_ROOT + NR_CXL_SINGLE_HOST)
+#define FINER_GRAN_USP_ID_BASE	FINER_GRAN_RP_ID_BASE
+#define FINER_GRAN_DSP_ID_BASE	(NR_MEM_MULTI + NR_MEM_SINGLE)
+#define FINER_GRAN_MEM_ID_BASE	(NR_MEM_MULTI + NR_MEM_SINGLE + NR_CXL_RCH)
+
+static void cxl_finer_gran_topo_exit(void)
+{
+	int i;
+
+	for (i = NR_CXL_FINER_GRAN_DPORTS - 1; i >= 0; i--) {
+		platform_device_unregister(cxl_finer_gran_dport[i]);
+		cxl_finer_gran_dport[i] = NULL;
+	}
+	for (i = NR_CXL_FINER_GRAN_SWITCHES - 1; i >= 0; i--) {
+		platform_device_unregister(cxl_finer_gran_uport[i]);
+		cxl_finer_gran_uport[i] = NULL;
+	}
+	for (i = NR_CXL_FINER_GRAN_RPS - 1; i >= 0; i--) {
+		platform_device_unregister(cxl_finer_gran_root_port[i]);
+		cxl_finer_gran_root_port[i] = NULL;
+	}
+	for (i = NR_CXL_FINER_GRAN_HB - 1; i >= 0; i--) {
+		struct platform_device *pdev = cxl_finer_gran_hb[i];
+
+		if (!pdev)
+			continue;
+		sysfs_remove_link(&pdev->dev.kobj, "physical_node");
+		platform_device_unregister(pdev);
+		cxl_finer_gran_hb[i] = NULL;
+	}
+}
+
+static __init int cxl_finer_gran_topo_init(void)
+{
+	int i, rc;
+
+	for (i = 0; i < NR_CXL_FINER_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_finer_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;
+	}
+
+	/* 2 root ports per host bridge */
+	for (i = 0; i < NR_CXL_FINER_GRAN_RPS; i++) {
+		struct platform_device *bridge =
+			cxl_finer_gran_hb[i / NR_CXL_FINER_GRAN_RPS_PER_HB];
+		struct platform_device *pdev;
+
+		pdev = platform_device_alloc("cxl_root_port",
+					     FINER_GRAN_RP_ID_BASE + i);
+		if (!pdev) {
+			rc = -ENOMEM;
+			goto err;
+		}
+		pdev->dev.parent = &bridge->dev;
+
+		rc = cxl_mock_platform_device_add(pdev,
+						  &cxl_finer_gran_root_port[i]);
+		if (rc)
+			goto err;
+	}
+
+	/* 1 switch uport per root port */
+	for (i = 0; i < NR_CXL_FINER_GRAN_SWITCHES; i++) {
+		struct platform_device *pdev;
+
+		pdev = platform_device_alloc("cxl_switch_uport",
+					     FINER_GRAN_USP_ID_BASE + i);
+		if (!pdev) {
+			rc = -ENOMEM;
+			goto err;
+		}
+		pdev->dev.parent = &cxl_finer_gran_root_port[i]->dev;
+
+		rc = cxl_mock_platform_device_add(pdev,
+						  &cxl_finer_gran_uport[i]);
+		if (rc)
+			goto err;
+	}
+
+	/* 2 dports per switch */
+	for (i = 0; i < NR_CXL_FINER_GRAN_DPORTS; i++) {
+		struct platform_device *uport =
+			cxl_finer_gran_uport[i / NR_CXL_SWITCH_PORTS];
+		struct platform_device *pdev;
+
+		pdev = platform_device_alloc("cxl_switch_dport",
+					     FINER_GRAN_DSP_ID_BASE + i);
+		if (!pdev) {
+			rc = -ENOMEM;
+			goto err;
+		}
+		pdev->dev.parent = &uport->dev;
+
+		rc = cxl_mock_platform_device_add(pdev,
+						  &cxl_finer_gran_dport[i]);
+		if (rc)
+			goto err;
+	}
+
+	return 0;
+err:
+	cxl_finer_gran_topo_exit();
+	return rc;
+}
+
 static void cxl_type3_mem_exit(void)
 {
 	struct platform_device *pdev;
 	int i;
 
+	if (finer_gran_regions) {
+		for (i = NR_CXL_FINER_GRAN_DPORTS - 1; i >= 0; i--) {
+			if (cxl_mem_finer_gran[i])
+				platform_device_unregister(cxl_mem_finer_gran[i]);
+		}
+		return;
+	}
+
 	for (i = ARRAY_SIZE(cxl_rcd) - 1; i >= 0; i--) {
 		pdev = cxl_rcd[i];
 		if (!pdev)
@@ -2030,10 +2361,44 @@ static int cxl_type2_mem_init(void)
 	return rc;
 }
 
+static int cxl_finer_gran_mem_init(void)
+{
+	int i, rc;
+
+	for (i = 0; i < NR_CXL_FINER_GRAN_DPORTS; i++) {
+		struct platform_device *dport = cxl_finer_gran_dport[i];
+		struct platform_device *pdev;
+
+		pdev = platform_device_alloc("cxl_mem",
+					     FINER_GRAN_MEM_ID_BASE + i);
+		if (!pdev) {
+			rc = -ENOMEM;
+			goto err;
+		}
+		pdev->dev.parent = &dport->dev;
+		set_dev_node(&pdev->dev, numa_map_to_online_node(i % 2));
+
+		rc = cxl_mock_platform_device_add(pdev, &cxl_mem_finer_gran[i]);
+		if (rc)
+			goto err;
+	}
+
+	return 0;
+err:
+	for (i = NR_CXL_FINER_GRAN_DPORTS - 1; i >= 0; i--) {
+		if (cxl_mem_finer_gran[i])
+			platform_device_unregister(cxl_mem_finer_gran[i]);
+	}
+	return rc;
+}
+
 static int cxl_type3_mem_init(void)
 {
 	int i, rc;
 
+	if (finer_gran_regions)
+		return cxl_finer_gran_mem_init();
+
 	for (i = 0; i < ARRAY_SIZE(cxl_mem); i++) {
 		struct platform_device *dport = cxl_switch_dport[i];
 		struct platform_device *pdev;
@@ -2148,6 +2513,8 @@ static bool __init have_multiple_modparms(void)
 		count++;
 	if (mock_zero_size_decoders)
 		count++;
+	if (finer_gran_regions)
+		count++;
 
 	/* Zero-size decoders require decoder[0] to preserve commit order. */
 	if (fail_autoassemble && mock_zero_size_decoders)
@@ -2421,6 +2788,11 @@ static int cxl_type3_topo_init(void)
 
 static void cxl_topo_exit(void)
 {
+	if (finer_gran_regions) {
+		cxl_finer_gran_topo_exit();
+		return;
+	}
+
 	if (type2_test) {
 		cxl_type2_topo_exit();
 		return;
@@ -2431,6 +2803,8 @@ static void cxl_topo_exit(void)
 
 static int cxl_topo_init(void)
 {
+	if (finer_gran_regions)
+		return cxl_finer_gran_topo_init();
 	if (type2_test)
 		return cxl_type2_topo_init();
 	return cxl_type3_topo_init();
@@ -2473,7 +2847,10 @@ static __init int cxl_test_init(void)
 	if (rc)
 		goto err_gen_pool_add;
 
-	if (interleave_arithmetic == 1) {
+	if (finer_gran_regions) {
+		cfmws_start = CFMWS_FINER_GRAN_ARRAY_START;
+		cfmws_end = CFMWS_FINER_GRAN_ARRAY_END;
+	} else if (interleave_arithmetic == 1) {
 		cfmws_start = CFMWS_XOR_ARRAY_START;
 		cfmws_end = CFMWS_XOR_ARRAY_END;
 	} else {
@@ -2563,6 +2940,9 @@ MODULE_PARM_DESC(type2_test, "Enable type 2 support testing");
 module_param(mock_zero_size_decoders, bool, 0444);
 MODULE_PARM_DESC(mock_zero_size_decoders,
 		 "Mock committed, locked, empty decoders under host-bridge0");
+module_param(finer_gran_regions, bool, 0444);
+MODULE_PARM_DESC(finer_gran_regions,
+		 "Topology for region granularities finer than the root");
 module_init(cxl_test_init);
 module_exit(cxl_test_exit);
 MODULE_LICENSE("GPL v2");
-- 
2.37.3


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-13  4:44 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13  4:44 [PATCH v6 0/5] CXL: Support region granularities finer than the root Alison Schofield
2026-10-13  4:44 ` [PATCH v6 1/5] Documentation/cxl: Describe region granularity " Alison Schofield
2026-10-13  4:44 ` [PATCH v6 2/5] cxl/region: Generalize endpoint position mapping Alison Schofield
2026-10-13  4:44 ` [PATCH v6 3/5] cxl/region: Support auto region granularity finer than the root Alison Schofield
2026-10-13  4:44 ` [PATCH v6 4/5] cxl/region: Support user " Alison Schofield
2026-10-13  4:44 ` [PATCH v6 5/5] cxl/test: Add a topology for " Alison Schofield

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.