* [ndctl PATCH v2 0/3] cxl: support mixed-granularity regions
@ 2026-09-03 21:34 Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 1/3] cxl/region: round default region size within root decoder extent Alison Schofield
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Alison Schofield @ 2026-09-03 21:34 UTC (permalink / raw)
To: nvdimm, linux-cxl; +Cc: Alison Schofield
This is an update to a series[1] that was previously sent out without a
cover letter. In v2 a fixup prerequisite patch is added and the tests
are expanded based on the kernel series review feedback [2].
[1] https://lore.kernel.org/all/fa5c109f08824180f58341ebd9055545a2ff3142.1780099216.git.alison.schofield@intel.com/
[2] https://lore.kernel.org/all/cover.1787255388.git.alison.schofield@intel.com/
Changes in v2:
Patch 1: cxl/region: round default region size within root decoder extent
- New patch to fixup the ALIGN_DOWN usage that doesn't always work
Patch 2: cxl/region: allow mixed-granularity regions
- Reject a granularity that is not power of 2
- Update man page -g section
Patch 3: test/cxl-region-mixed.sh: test mixed-granularity region creation
- Cover the third pmem window, a 3-way at 1KB across 3 HBs
- Add two test cases, a 6-way and 12-way, for more spec defined coverage
- Select endpoints by walking hierarchy instead of handbuilding lists per case
- Assert 2 switches below each HB and 2 EP below each switch
- Require negative test cases to fail at first target write
- Skip rather than fail when kernel has no registry
- Drop selector walk language to align with kernel language
The base commit is the current ndctl pending branch.
Alison Schofield (3):
cxl/region: round default region size within root decoder extent
cxl/region: allow mixed-granularity regions
test/cxl-region-mixed.sh: test mixed-granularity region creation
Documentation/cxl/cxl-create-region.txt | 21 +-
cxl/region.c | 39 ++-
test/cxl-region-mixed.sh | 413 ++++++++++++++++++++++++
test/meson.build | 2 +
4 files changed, 462 insertions(+), 13 deletions(-)
create mode 100644 test/cxl-region-mixed.sh
base-commit: ca9a1d69dc8894836999042ae6793fd7698b78fd
--
2.37.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* [ndctl PATCH v2 1/3] cxl/region: round default region size within root decoder extent
2026-09-03 21:34 [ndctl PATCH v2 0/3] cxl: support mixed-granularity regions Alison Schofield
@ 2026-09-03 21:34 ` Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 2/3] cxl/region: allow mixed-granularity regions Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 3/3] test/cxl-region-mixed.sh: test mixed-granularity region creation Alison Schofield
2 siblings, 0 replies; 4+ messages in thread
From: Alison Schofield @ 2026-09-03 21:34 UTC (permalink / raw)
To: nvdimm, linux-cxl; +Cc: Alison Schofield
The kernel requires a region size to be a multiple of SZ_256M * ways. The
cxl-cli command, cxl create-region, accepts an explicit size with -s, or
derives the size using ALIGN_DOWN(). The derived size is not always valid.
The calculation can fail in two ways:
1) When the region interleave ways has a factor of three, SZ_256M * ways is
not a power of two, so ALIGN_DOWN() is not valid for the calculation. The
result depends on the root decoder extent and can appear correct by chance.
For example, a 6-way region with a 3G extent produces the expected 3G size,
while a 12-way region with the same extent is trimmed to 1G.
2) At 16 ways, SZ_256M * ways overflows the int used for the multiplication
and evaluates to zero, so the trim returns zero.
Compute the product in u64 to avoid the overflow and round down by division
so that the derived size is a multiple of SZ_256M * ways. Return -ENOSPC
when the root decoder window is smaller than SZ_256M * ways.
This was found while adding 12-way region test coverage.
Fixes: 2904504721a6 ("cxl/region: Trim region size by max available extent")
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
cxl/region.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/cxl/region.c b/cxl/region.c
index 85d4d9bb54f2..9d727216b9b8 100644
--- a/cxl/region.c
+++ b/cxl/region.c
@@ -682,8 +682,18 @@ static int create_region(struct cxl_ctx *ctx, int *count,
return -ENOSPC;
}
- if (size > max_extent)
- size = ALIGN_DOWN(max_extent, SZ_256M * p->ways);
+ if (size > max_extent) {
+ u64 min_size = (u64)SZ_256M * p->ways;
+
+ size = (max_extent / min_size) * min_size;
+ if (!size) {
+ log_err(&rl,
+ "%s: max available space (%#lx) is less than the minimum %d-way region size (%#lx)\n",
+ cxl_decoder_get_devname(p->root_decoder),
+ max_extent, p->ways, min_size);
+ return -ENOSPC;
+ }
+ }
if (p->mode == CXL_DECODER_MODE_PMEM) {
region = cxl_decoder_create_pmem_region(p->root_decoder);
--
2.37.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [ndctl PATCH v2 2/3] cxl/region: allow mixed-granularity regions
2026-09-03 21:34 [ndctl PATCH v2 0/3] cxl: support mixed-granularity regions Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 1/3] cxl/region: round default region size within root decoder extent Alison Schofield
@ 2026-09-03 21:34 ` Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 3/3] test/cxl-region-mixed.sh: test mixed-granularity region creation Alison Schofield
2 siblings, 0 replies; 4+ messages in thread
From: Alison Schofield @ 2026-09-03 21:34 UTC (permalink / raw)
To: nvdimm, linux-cxl; +Cc: Alison Schofield
cxl_region_determine_granularity() rejects any user-supplied granularity
that does not exactly match the root decoder's granularity, making it
impossible to create mixed-granularity regions from userspace even when the
kernel driver accepts them.
Relax the check to allow region_gran <= root_gran. This function only needs
to reject what it can determine on its own is always wrong - a region
granularity greater than the root decoder's. The kernel does the rest of
the validation.
Reject a granularity that is not a power of two, or is below 256, since the
previous exact-match test that screened those out implicitly is now gone.
Update the cxl-create-region man page to document the new behavior.
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
Documentation/cxl/cxl-create-region.txt | 21 ++++++++++++++++-----
cxl/region.c | 25 +++++++++++++++++++------
2 files changed, 35 insertions(+), 11 deletions(-)
diff --git a/Documentation/cxl/cxl-create-region.txt b/Documentation/cxl/cxl-create-region.txt
index b244af60b8a6..d5998ad725ee 100644
--- a/Documentation/cxl/cxl-create-region.txt
+++ b/Documentation/cxl/cxl-create-region.txt
@@ -93,11 +93,22 @@ include::bus-option.txt[]
-g::
--granularity=::
- The interleave granularity for the new region. Must match the selected
- root decoder's (if provided) granularity. If the root decoder is
- interleaved across more than one host-bridge then this value must match
- that granularity. Otherwise, for non-interleaved decode windows, any
- granularity can be specified as long as all devices support that setting.
+ The interleave granularity for the new region. A granularity coarser
+ than the root decoder's is never legal. If the root decoder is
+ interleaved across more than one host bridge, then for a given number
+ of ways at most two granularities are legal:
+
+ * "root_ways * root_granularity / ways", which creates a
+ mixed-granularity region. The root interleaves at its own coarser
+ granularity and the levels below it, host bridges and switches,
+ interleave at the finer region granularity.
+ * The root decoder granularity itself, which repeats the root
+ interleave pattern across a wider region. This is available only
+ when the root decoder ways are a power of two.
+
+ The two coincide when the region ways equal the root decoder ways. For
+ non-interleaved root decoders, any granularity can be specified as
+ long as all devices support that setting.
-d::
--decoder=::
diff --git a/cxl/region.c b/cxl/region.c
index 9d727216b9b8..de9f487ddff9 100644
--- a/cxl/region.c
+++ b/cxl/region.c
@@ -620,6 +620,18 @@ static int cxl_region_determine_granularity(struct cxl_region *region,
if (!p->granularity)
return granularity;
+ /*
+ * The CXL specification defines interleave granularities of 256 through
+ * 16K in powers of two. Reject anything else here, rather than leave the
+ * user with a bare EINVAL from the kernel.
+ */
+ if (p->granularity < 256 || !is_power_of_2(p->granularity)) {
+ log_err(&rl,
+ "%s: granularity (%d) must be a power of 2, 256 or greater\n",
+ devname, p->granularity);
+ return -EINVAL;
+ }
+
ways = cxl_decoder_get_interleave_ways(p->root_decoder);
if (ways == 0 || ways == -1) {
log_err(&rl, "%s: unable to determine root decoder ways\n",
@@ -632,15 +644,16 @@ static int cxl_region_determine_granularity(struct cxl_region *region,
return p->granularity;
/*
- * For ways > 1, only allow the same granularity as the selected
- * root decoder
+ * For ways > 1, allow any region granularity up to and including the
+ * root decoder granularity. A finer region granularity produces a
+ * mixed-granularity configuration and a coarser one is always invalid.
*/
- if (p->granularity == granularity)
- return granularity;
+ if (p->granularity <= granularity)
+ return p->granularity;
log_err(&rl,
- "%s: For an x%d root, only root decoder granularity (%d) permitted\n",
- devname, ways, granularity);
+ "%s: region granularity (%d) cannot exceed root decoder granularity (%d)\n",
+ devname, p->granularity, granularity);
return -EINVAL;
}
--
2.37.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [ndctl PATCH v2 3/3] test/cxl-region-mixed.sh: test mixed-granularity region creation
2026-09-03 21:34 [ndctl PATCH v2 0/3] cxl: support mixed-granularity regions Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 1/3] cxl/region: round default region size within root decoder extent Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 2/3] cxl/region: allow mixed-granularity regions Alison Schofield
@ 2026-09-03 21:34 ` Alison Schofield
2 siblings, 0 replies; 4+ messages in thread
From: Alison Schofield @ 2026-09-03 21:34 UTC (permalink / raw)
To: nvdimm, linux-cxl; +Cc: Alison Schofield
Exercise mixed-granularity region configurations against the
mixed_gran_regions=1 cxl_test topology.
The mixed_gran_regions topology presents three root decoders over a
12-endpoint pool:
- 2-way @ 4KB across 2 host bridges
- 3-way @ 512B across 3 host bridges
- 3-way @ 1KB across 3 host bridges
Positive cases run replay_regions to round-trip through the auto
create path. The auto path reads decoder values back from the
topology and runs them through the same position arithmetic the
user-create path uses. A region that creates successfully but
fails replay usually indicates a position arithmetic bug, so replay
coverage is the strongest correctness check we have at this layer.
The test cases are listed near the top of the script.
Assisted-by: Claude Opus 4.7
Signed-off-by: Alison Schofield <alison.schofield@intel.com>
---
test/cxl-region-mixed.sh | 413 +++++++++++++++++++++++++++++++++++++++
test/meson.build | 2 +
2 files changed, 415 insertions(+)
create mode 100644 test/cxl-region-mixed.sh
diff --git a/test/cxl-region-mixed.sh b/test/cxl-region-mixed.sh
new file mode 100644
index 000000000000..ea208b7e048e
--- /dev/null
+++ b/test/cxl-region-mixed.sh
@@ -0,0 +1,413 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026 Intel Corporation. All rights reserved.
+
+# shellcheck disable=SC1091,SC2034
+. "$(dirname "$0")"/common
+rc=77
+set -ex
+trap 'err $LINENO' ERR
+check_prereq "jq"
+
+modinfo cxl_test | grep -q '^parm:.*mixed_gran_regions' ||
+ do_skip "cxl_test mixed_gran_regions module param not available"
+
+modprobe -r cxl_test
+modprobe cxl_test mixed_gran_regions=1
+
+# Replay support is exposed by cxl_acpi after cxl_test loads. Without it
+# replay_regions() would unbind and rebind cxl_acpi anyway and the test
+# would fail rather than skip.
+if [ ! -e /sys/bus/platform/devices/cxl_acpi.0/decoder_reset_preserve_registry ]; then
+ do_skip "test requires decoder registry replay support"
+fi
+
+rc=1
+
+expect_fail() {
+ "$@" || true
+}
+
+# Test mixed-granularity region configurations
+#
+# Topology: modprobe cxl_test mixed_gran_regions=1
+# Three host bridges, two root ports each, one switch per root port,
+# and two endpoints per switch, for twelve endpoints total. A region
+# interleaves at the host bridge, at the switch, or at both, depending
+# on which endpoints it is built from.
+#
+# Three pmem windows:
+# - 2-way @ 4096 across 2 host bridges
+# - 3-way @ 512 across 3 host bridges
+# - 3-way @ 1024 across 3 host bridges
+#
+# Cases (in execution order). Positive cases assert the interleave
+# programmed into the host bridge and switch decoders below the region,
+# then replay through the auto-create path to round-trip the
+# configuration; a region that creates but fails replay indicates a
+# position bug. Negative cases verify rejection at the first target
+# write, which is where the kernel applies the span identity.
+#
+# Positive:
+# 1. 3-way same-gran @512, 3-way root, every level below passthrough
+# 2. 4-way mixed-gran @2048, 2-way root, switch interleaves
+# 3. 4-way mixed-gran @2048, 2-way root, host bridge interleaves
+# 4. 6-way mixed-gran @256, 3-way root, CXL 4.0 Table 9-7 row 3
+# 5. 6-way mixed-gran @256, 3-way root, CXL 4.0 Table 9-7 row 2
+# 6. 8-way mixed-gran @1024, 2-way root, both levels interleave
+# 7. 12-way mixed-gran @256, 3-way root, CXL 4.0 Table 9-6 row 7
+# Negative:
+# 8. 6-way same-gran on 3-way root
+# 9. 8-way @512 on 2-way root @4096
+# 10. region_gran > root_gran rejected at sysfs write
+#
+# Table 9-6 row 6 has no case here. It is the one legal composition Linux
+# does not support, and a user cannot ask for it: each level below the
+# root takes its parent granularity divided by its own ways, so row 7 is
+# what gets programmed for a 12-way region on a 3-way root at 4*IGB.
+
+create_region() {
+ [[ $decoder && $ways && $region_ig && $memdevs ]] || err "$LINENO"
+ region=$($CXL create-region -d "$decoder" -w "$ways" -g "$region_ig" \
+ -m "$memdevs" | jq -r ".region")
+ [[ $region ]] || err "$LINENO"
+}
+
+# Locate a pmem root decoder by its ways and granularity.
+# Usage: find_root <ways> <granularity>
+find_root() {
+ decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+ select(.pmem_capable == true) |
+ select(.nr_targets == $1) |
+ select(.interleave_granularity == $2) |
+ .decoder")
+ [[ $decoder ]] || err "$LINENO"
+}
+
+# Return the port device that backs the given target position of a root
+# decoder. Usage: target_port <decoder> <position>
+target_port() {
+ "$CXL" list -T -d "$1" |
+ jq -r ".[] | .targets | .[] | select(.position == $2) | .target"
+}
+
+# Return the switch ports below a host bridge port. Host bridge ports are
+# depth 1 and the switches below them are depth 2.
+# Usage: switch_ports <host_bridge_port>
+switch_ports() {
+ "$CXL" list -P -p "$1" |
+ jq -r '.. | objects | select(.depth == 2) | .host' | sort -V
+}
+
+# Return the memdevs below a port. Usage: port_memdevs <port>
+port_memdevs() {
+ "$CXL" list -M -p "$1" | jq -r '.[].memdev' | sort -V
+}
+
+# Build a position-ordered memdev list and set $ways to match.
+# Usage: select_memdevs <nr_host_bridges> <switches_per_hb> <endpoints_per_switch>
+#
+# Walk the root decoder targets in position order, then the switches below
+# each host bridge, then the endpoints below each switch, taking the
+# requested count at every level. That walk visits endpoints in region
+# position order, coarsest level first, which is the order create-region
+# assigns positions in. Taking fewer than the available count at a level
+# leaves that level passthrough, which is how a case selects whether the
+# host bridge or the switch does the interleaving.
+#
+# Endpoint selection has to go through the hierarchy: memdev names are
+# assigned in probe order and say nothing about topology.
+select_memdevs() {
+ local nr_hb=$1 nr_sw=$2 nr_ep=$3
+ local -a mems=()
+ local hb sw mem
+ local i nsw nep
+
+ # Force each case to restate what it expects below the root
+ hb_iw='' hb_ig='' sw_iw='' sw_ig=''
+
+ for ((i = 0; i < nr_hb; i++)); do
+ hb=$(target_port "$decoder" "$i")
+ [[ $hb ]] || err "$LINENO"
+
+ nsw=0
+ while read -r sw; do
+ [[ $nsw -lt $nr_sw ]] || break
+
+ nep=0
+ while read -r mem; do
+ [[ $nep -lt $nr_ep ]] || break
+ mems+=("$mem")
+ nep=$((nep + 1))
+ done < <(port_memdevs "$sw")
+ [[ $nep -eq $nr_ep ]] || err "$LINENO"
+
+ nsw=$((nsw + 1))
+ done < <(switch_ports "$hb")
+ [[ $nsw -eq $nr_sw ]] || err "$LINENO"
+ done
+
+ memdevs="${mems[*]}"
+ ways=$((nr_hb * nr_sw * nr_ep))
+ sel_hb=$nr_hb
+ sel_sw=$nr_sw
+}
+
+# Return the decoders at @1 that are attached to $region, one per line.
+# Host bridge ports are depth 1 and the switches below them are depth 2.
+#
+# "-d switch" is required, not just tidy: an endpoint's decoders are
+# reported inside the array of the port above it, so without the filter
+# a depth 2 switch port hands back its own decoder plus the endpoint
+# decoders below it.
+# Usage: region_decoders <depth>
+region_decoders() {
+ "$CXL" list -b cxl_test -P -D -d switch | jq -r --argjson depth "$1" \
+ --arg region "$region" '.. | objects | select(.depth? == $depth) |
+ to_entries[] | select(.key | startswith("decoders:")) |
+ .value[] | select(.region? == $region) | .decoder'
+}
+
+# Assert the interleave programmed at one level below the root.
+# Usage: check_level <depth> <nr_decoders> <ways> <granularity>
+check_level() {
+ local depth=$1 nr=$2 exp_iw=$3 exp_ig=$4
+ local -a decoders=()
+ local dec iw ig
+
+ mapfile -t decoders < <(region_decoders "$depth" | sort -V)
+ [[ ${#decoders[@]} -eq $nr ]] || {
+ echo "depth $depth: expected $nr decoders, got ${#decoders[@]}" >&2
+ err "$LINENO"
+ }
+
+ for dec in "${decoders[@]}"; do
+ iw=$(cat "/sys/bus/cxl/devices/$dec/interleave_ways")
+ ig=$(cat "/sys/bus/cxl/devices/$dec/interleave_granularity")
+ [[ $iw -eq $exp_iw && $ig -eq $exp_ig ]] || {
+ echo "$dec: expected iw $exp_iw ig $exp_ig, got iw $iw ig $ig" >&2
+ err "$LINENO"
+ }
+ done
+}
+
+# Assert the interleave the region programmed into the host bridge and
+# switch decoders beneath it.
+#
+# The region attributes and the replay signature both describe the region
+# as a whole, so neither says anything about how the interleave was
+# divided up on the way down to the endpoints. A divisor that is wrong
+# the same way everywhere satisfies both. These are the numbers CXL 4.0
+# Section 9.13.1.1 tabulates, taken from the case comments rather than
+# recomputed here.
+check_region_decoders() {
+ [[ $hb_iw && $hb_ig && $sw_iw && $sw_ig ]] || err "$LINENO"
+
+ check_level 1 "$sel_hb" "$hb_iw" "$hb_ig"
+ check_level 2 "$((sel_hb * sel_sw))" "$sw_iw" "$sw_ig"
+}
+
+# Sanity check on the mixed_gran_regions topology.
+test_mix_gran_topology_sanity() {
+ local nr_mem hb nr_sw nr_ep
+
+ nr_mem=$($CXL list -b cxl_test -M | jq length)
+ [[ $nr_mem -eq 12 ]] || {
+ echo "expected 12 memdevs, got $nr_mem" >&2
+ err "$LINENO"
+ }
+
+ # One root decoder per window
+ find_root 2 4096
+ find_root 3 512
+ find_root 3 1024
+
+ # Two switches below each host bridge, two endpoints below each
+ # switch. Without two switches the host bridge could never
+ # interleave and cases 3, 5, and 7 would silently test something
+ # else.
+ hb=$(target_port "$decoder" 0)
+ nr_sw=$(switch_ports "$hb" | wc -l)
+ [[ $nr_sw -eq 2 ]] || {
+ echo "expected 2 switches below $hb, got $nr_sw" >&2
+ err "$LINENO"
+ }
+ nr_ep=$(port_memdevs "$(switch_ports "$hb" | head -1)" | wc -l)
+ [[ $nr_ep -eq 2 ]] || {
+ echo "expected 2 endpoints below the switch, got $nr_ep" >&2
+ err "$LINENO"
+ }
+}
+
+# Positive: 3-way same-gran on the 3-way root, every level below
+# passthrough. One endpoint per host bridge.
+setup_x3_same_gran_passthrough() {
+ find_root 3 512
+ select_memdevs 3 1 1
+ region_ig=512
+ hb_iw=1 hb_ig=512
+ sw_iw=1 sw_ig=512
+}
+
+# Positive: 4-way mixed-gran on the 2-way root @4096, switch interleaves.
+# Both endpoints below one switch per host bridge, so the host bridge
+# passes through at 4096 and the switch interleaves 2-way at 2048.
+setup_x4_switch_interleave() {
+ find_root 2 4096
+ select_memdevs 2 1 2
+ region_ig=2048
+ hb_iw=1 hb_ig=4096
+ sw_iw=2 sw_ig=2048
+}
+
+# Positive: 4-way mixed-gran on the 2-way root @4096, host bridge
+# interleaves. One endpoint below each of the two switches per host
+# bridge, so the host bridge interleaves 2-way at 2048 and the switch
+# passes through.
+setup_x4_hb_interleave() {
+ find_root 2 4096
+ select_memdevs 2 2 1
+ region_ig=2048
+ hb_iw=2 hb_ig=2048
+ sw_iw=1 sw_ig=2048
+}
+
+# Positive: 6-way mixed-gran on the 3-way root @512.
+# CXL 4.0 Section 9.13.1.1 Table 9-7 row 3: host bridge passthrough,
+# switch 2-way at the region granularity.
+setup_x6_switch_interleave() {
+ find_root 3 512
+ select_memdevs 3 1 2
+ region_ig=256
+ hb_iw=1 hb_ig=512
+ sw_iw=2 sw_ig=256
+}
+
+# Positive: 6-way mixed-gran on the 3-way root @512.
+# CXL 4.0 Section 9.13.1.1 Table 9-7 row 2: host bridge 2-way at the
+# region granularity, switch passthrough.
+setup_x6_hb_interleave() {
+ find_root 3 512
+ select_memdevs 3 2 1
+ region_ig=256
+ hb_iw=2 hb_ig=256
+ sw_iw=1 sw_ig=256
+}
+
+# Positive: 8-way mixed-gran on the 2-way root @4096, interleaving at
+# every level. Root @4096, host bridge 2-way @2048, switch 2-way @1024.
+setup_x8_all_levels() {
+ find_root 2 4096
+ select_memdevs 2 2 2
+ region_ig=1024
+ hb_iw=2 hb_ig=2048
+ sw_iw=2 sw_ig=1024
+}
+
+# Positive: 12-way mixed-gran on the 3-way root @1024, interleaving at
+# every level. CXL 4.0 Section 9.13.1.1 Table 9-6 row 7: root 3-way
+# @4*IGB, host bridge 2-way @2*IGB, switch 2-way @IGB. This is the
+# composition Linux supports in place of the unsupported row 6.
+setup_x12_row7() {
+ find_root 3 1024
+ select_memdevs 3 2 2
+ region_ig=256
+ hb_iw=2 hb_ig=512
+ sw_iw=2 sw_ig=256
+}
+
+# Negative: 6-way same-gran on the 3-way root.
+# 6*512 != 3*512 — span identity violated
+setup_x6_same_gran_span_violation() {
+ find_root 3 512
+ select_memdevs 3 1 2
+ region_ig=512
+}
+
+# Negative: 8-way @512 on the 2-way root @4096.
+# 8*512 != 2*4096 — span identity violated
+setup_gran_too_small() {
+ find_root 2 4096
+ select_memdevs 2 2 2
+ region_ig=512
+}
+
+# Negative: region_gran > root_gran rejected at sysfs write.
+# Bypasses CLI to confirm the kernel gate, not the CLI, is the rejector.
+test_ig_gt_root_rejected() {
+ find_root 2 4096
+
+ root_ig=$(cat "/sys/bus/cxl/devices/$decoder/interleave_granularity")
+ bad_ig=$((root_ig * 2))
+
+ region=$(cat "/sys/bus/cxl/devices/$decoder/create_pmem_region")
+ echo "$region" >"/sys/bus/cxl/devices/$decoder/create_pmem_region"
+
+ if echo "$bad_ig" >"/sys/bus/cxl/devices/$region/interleave_granularity" \
+ 2>/dev/null; then
+ err "$LINENO"
+ fi
+
+ echo "$region" >"/sys/bus/cxl/devices/$decoder/delete_region"
+}
+
+# Create, replay, and destroy a region. Usage: run_positive <setup_func>
+run_positive() {
+ "$1"
+ create_region
+ check_region_decoders
+ replay_regions || err "$LINENO"
+ $CXL destroy-region -f -b cxl_test "$region"
+}
+
+# Confirm a configuration is rejected, and that the kernel is what
+# rejected it. Usage: run_negative <setup_func>
+#
+# Exit status alone cannot tell a real rejection from a case that never
+# reached the kernel. create-region reserves DPA on each endpoint as it
+# goes and only deletes the region when a target write fails, so the
+# reservation made for target 0 outlives the case; enough negative cases
+# sharing an endpoint and create-region starts failing on the size it
+# cannot determine instead, which looks the same from here. Require the
+# failure to come from the first target write, and require no region to
+# be left behind.
+run_negative() {
+ local out nr_regions
+
+ "$1"
+ out=$(expect_fail "$CXL" create-region -d "$decoder" -w "$ways" \
+ -g "$region_ig" -m "$memdevs" 2>&1)
+ grep -q "failed to set target0" <<<"$out" || {
+ echo "expected rejection at the first target write, got:" >&2
+ echo "$out" >&2
+ err "$LINENO"
+ }
+
+ nr_regions=$($CXL list -b cxl_test -R | jq -r '.[].region' | wc -l)
+ [[ $nr_regions -eq 0 ]] || {
+ echo "rejected create left $nr_regions region(s) behind" >&2
+ err "$LINENO"
+ }
+ region=''
+}
+
+# Execution
+
+test_mix_gran_topology_sanity
+
+run_positive setup_x3_same_gran_passthrough
+run_positive setup_x4_switch_interleave
+run_positive setup_x4_hb_interleave
+run_positive setup_x6_switch_interleave
+run_positive setup_x6_hb_interleave
+run_positive setup_x8_all_levels
+run_positive setup_x12_row7
+
+run_negative setup_x6_same_gran_span_violation
+run_negative setup_gran_too_small
+
+test_ig_gt_root_rejected
+
+check_dmesg "$LINENO"
+
+modprobe -r cxl_test
diff --git a/test/meson.build b/test/meson.build
index 07c8618cbc6e..492e5b062eb5 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -173,6 +173,7 @@ cxl_elc = find_program('cxl-elc.sh')
cxl_dax_hmem = find_program('cxl-dax-hmem.sh')
cxl_region_replay = find_program('cxl-region-replay.sh')
cxl_type2 = find_program('cxl-type2.sh')
+cxl_region_mixed = find_program('cxl-region-mixed.sh')
tests = [
[ 'libndctl', libndctl, 'ndctl' ],
@@ -211,6 +212,7 @@ tests = [
[ 'cxl-dax-hmem.sh', cxl_dax_hmem, 'cxl' ],
[ 'cxl-region-replay.sh', cxl_region_replay, 'cxl' ],
[ 'cxl-type2.sh', cxl_type2, 'cxl' ],
+ [ 'cxl-region-mixed.sh', cxl_region_mixed, 'cxl' ],
]
if get_option('destructive').enabled()
--
2.37.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-03 21:34 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 21:34 [ndctl PATCH v2 0/3] cxl: support mixed-granularity regions Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 1/3] cxl/region: round default region size within root decoder extent Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 2/3] cxl/region: allow mixed-granularity regions Alison Schofield
2026-09-03 21:34 ` [ndctl PATCH v2 3/3] test/cxl-region-mixed.sh: test mixed-granularity region creation Alison Schofield
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox