Exercise mixed-granularity region configurations against the
mixed_gran_regions=1 cxl_test topology.

The mixed_gran_regions topology presents two root decoders over a
12-endpoint pool:
  - 2-way @ 4KB across 2 host bridges
  - 3-way @ 512B 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 selector walk and 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 <[email protected]>
---
 test/cxl-region-mixed.sh | 297 +++++++++++++++++++++++++++++++++++++++
 test/meson.build         |   2 +
 2 files changed, 299 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..ffc3f86919af
--- /dev/null
+++ b/test/cxl-region-mixed.sh
@@ -0,0 +1,297 @@
+#!/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
+rc=1
+
+expect_fail() {
+       "$@" || true
+}
+
+# Test mixed-granularity region configurations
+#
+# Topology: modprobe cxl_test mixed_gran_regions=1
+#   - 2-way pmem @ 4KB across 2 HBs (cfmws9)
+#   - 3-way pmem @ 512B across 3 HBs (cfmws10)
+#   with 12 endpoints total.
+#
+# Cases (in execution order). Positive cases also 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.
+#
+# Positive:
+#   1. 3-way same-gran on 3-way root, all switches passthrough
+#   2. 4-way p2 mixed-gran on 2-way root @ 4KB
+#   3. 4-way mixed-gran, L2 switches passthrough on 2-way root
+#   4. 6-way mixed-gran on 3-way root @ 512B
+#   5. 8-way multi-level mixed-gran on 2-way root @ 4KB
+# Negative:
+#   6. 6-way same-gran on 3-way root
+#   7. 8-way @ 512B on 2-way root @ 4KB
+#   8. region_gran > root_gran rejected at sysfs write
+
+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"
+}
+
+find_x2_root() {
+       decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+               select(.pmem_capable == true) |
+               select(.nr_targets == 2) |
+               select(.interleave_granularity == 4096) |
+               .decoder")
+       [[ $decoder ]] || err "$LINENO"
+}
+
+find_x3_root() {
+       decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+               select(.pmem_capable == true) |
+               select(.nr_targets == 3) |
+               select(.interleave_granularity == 512) |
+               .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"
+}
+
+# Sanity check on the mixed_gran_regions topology.
+test_mix_gran_topology_sanity() {
+       local nr_mem
+       local x2 x3
+
+       nr_mem=$($CXL list -b cxl_test -M | jq length)
+       [[ $nr_mem -eq 12 ]] || {
+               echo "expected 12 memdevs, got $nr_mem" >&2
+               err "$LINENO"
+       }
+
+       x2=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+               select(.pmem_capable == true) |
+               select(.nr_targets == 2) |
+               select(.interleave_granularity == 4096) | .decoder")
+       [[ $x2 ]] || {
+               echo "expected x2 @ 4KB pmem root decoder" >&2
+               err "$LINENO"
+       }
+
+       x3=$($CXL list -b cxl_test -D -d root | jq -r ".[] |
+               select(.pmem_capable == true) |
+               select(.nr_targets == 3) |
+               select(.interleave_granularity == 512) | .decoder")
+       [[ $x3 ]] || {
+               echo "expected x3 @ 512B pmem root decoder" >&2
+               err "$LINENO"
+       }
+}
+
+# 3-way root setup: discover the 3 ports beneath it and the memdevs
+# visible under each. Each HB hosts a 3-level switch tree (L1 + L2)
+# but for the 3-way tests only L1 (or no level) interleaves; tests
+# select endpoints accordingly.
+setup_x3_topology() {
+       find_x3_root
+
+       port_dev0=$(target_port "$decoder" 0)
+       port_dev1=$(target_port "$decoder" 1)
+       port_dev2=$(target_port "$decoder" 2)
+
+       readarray -t hb0 < <("$CXL" list -M -p "$port_dev0" | jq -r 
'.[].memdev')
+       readarray -t hb1 < <("$CXL" list -M -p "$port_dev1" | jq -r 
'.[].memdev')
+       readarray -t hb2 < <("$CXL" list -M -p "$port_dev2" | jq -r 
'.[].memdev')
+}
+
+# 2-way root setup: discover the 2 ports beneath the x2 @ 4KB pmem
+# root and the memdevs visible under each. Used by all 2-way root
+# tests including multi-level cases.
+setup_x2_topology() {
+       find_x2_root
+
+       root_ig=$(cat "/sys/bus/cxl/devices/$decoder/interleave_granularity")
+       port_dev0=$(target_port "$decoder" 0)
+       port_dev1=$(target_port "$decoder" 1)
+
+       readarray -t hb0 < <("$CXL" list -M -p "$port_dev0" | jq -r 
'.[].memdev')
+       readarray -t hb1 < <("$CXL" list -M -p "$port_dev1" | jq -r 
'.[].memdev')
+}
+
+# Positive: 3-way same-gran on 3-way root, all switches passthrough
+setup_x3_same_gran_passthrough() {
+       setup_x3_topology
+
+       memdevs="${hb0[0]} ${hb1[0]} ${hb2[0]}"
+       ways=3
+       region_ig=512
+}
+
+# Positive: 4-way p2 mixed-gran on 2-way root @ 4KB.
+setup_x4_p2_mixed_gran() {
+       setup_x2_topology
+
+       region_ig=$((root_ig / 2))
+       [[ $region_ig -ge 256 ]] || err "$LINENO"
+
+       memdevs="${hb0[0]} ${hb0[1]} ${hb1[0]} ${hb1[1]}"
+       ways=4
+}
+
+# Positive: 4-way mixed-gran, L2 switches passthrough on 2-way root.
+setup_x4_l2_passthrough() {
+       setup_x2_topology
+
+       region_ig=2048
+
+       local -a l2_ports_hb0 l2_ports_hb1
+       readarray -t l2_ports_hb0 < <("$CXL" list -P -p "$port_dev0" |
+               jq -r '.. | objects | select(.depth == 3) | .host')
+       readarray -t l2_ports_hb1 < <("$CXL" list -P -p "$port_dev1" |
+               jq -r '.. | objects | select(.depth == 3) | .host')
+
+       local m0 m1 m2 m3
+       m0=$("$CXL" list -M -p "${l2_ports_hb0[0]}" | jq -r ".[0].memdev")
+       m1=$("$CXL" list -M -p "${l2_ports_hb0[1]}" | jq -r ".[0].memdev")
+       m2=$("$CXL" list -M -p "${l2_ports_hb1[0]}" | jq -r ".[0].memdev")
+       m3=$("$CXL" list -M -p "${l2_ports_hb1[1]}" | jq -r ".[0].memdev")
+       memdevs="$m0 $m1 $m2 $m3"
+       ways=4
+}
+
+# Positive: 6-way mixed-gran on 3-way root @ 512B.
+# Section 9.13.1.1 Table 9-7 row 2: 2-way HB beneath each root target
+setup_x6_mixed_gran() {
+       setup_x3_topology
+
+       memdevs="${hb0[0]} ${hb0[1]} ${hb1[0]} ${hb1[1]} ${hb2[0]} ${hb2[1]}"
+       ways=6
+       region_ig=256
+}
+
+# Positive: 8-way multi-level mixed-gran on 2-way root @ 4KB.
+# Root @ 4KB, L1 @ 2KB, L2 @ 1KB. Positions 0..3 under HB[0], 4..7
+# under HB[1]. Memdev order follows position math requirements.
+setup_x8_multi_level() {
+       setup_x2_topology
+
+       region_ig=$((root_ig / 4))
+       [[ $region_ig -ge 256 ]] || err "$LINENO"
+
+       local -a l2_ports_hb0 l2_ports_hb1
+       readarray -t l2_ports_hb0 < <("$CXL" list -P -p "$port_dev0" |
+               jq -r '.. | objects | select(.depth == 3) | .host')
+       readarray -t l2_ports_hb1 < <("$CXL" list -P -p "$port_dev1" |
+               jq -r '.. | objects | select(.depth == 3) | .host')
+
+       local -a br0 br1 br2 br3
+       readarray -t br0 < <("$CXL" list -M -p "${l2_ports_hb0[0]}" | jq -r 
'.[].memdev')
+       readarray -t br1 < <("$CXL" list -M -p "${l2_ports_hb0[1]}" | jq -r 
'.[].memdev')
+       readarray -t br2 < <("$CXL" list -M -p "${l2_ports_hb1[0]}" | jq -r 
'.[].memdev')
+       readarray -t br3 < <("$CXL" list -M -p "${l2_ports_hb1[1]}" | jq -r 
'.[].memdev')
+
+       memdevs="${br0[0]} ${br1[0]} ${br0[1]} ${br1[1]} ${br2[0]} ${br3[0]} 
${br2[1]} ${br3[1]}"
+       ways=8
+}
+
+# Negative: 6-way same-gran on 3-way root
+# 6*512 != 3*512 — span identity violated
+setup_x6_same_gran_span_violation() {
+       setup_x3_topology
+
+       memdevs="${hb0[0]} ${hb0[1]} ${hb1[0]} ${hb1[1]} ${hb2[0]} ${hb2[1]}"
+       ways=6
+       region_ig=512
+}
+
+# Negative: 8-way @ 512B on 2-way root @ 4KB.
+# Selector walk rejects: root selector bit lands outside the region
+# selector mask (containment fails).
+setup_gran_too_small() {
+       setup_x2_topology
+
+       region_ig=512
+       memdevs="${hb0[*]} ${hb1[*]}"
+       ways=8
+}
+
+# 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_x2_root
+
+       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"
+}
+
+# Execution
+
+test_mix_gran_topology_sanity
+
+setup_x3_same_gran_passthrough
+create_region
+replay_regions || err "$LINENO"
+$CXL destroy-region -f -b cxl_test "$region"
+
+setup_x4_p2_mixed_gran
+create_region
+replay_regions || err "$LINENO"
+$CXL destroy-region -f -b cxl_test "$region"
+
+setup_x4_l2_passthrough
+create_region
+replay_regions || err "$LINENO"
+$CXL destroy-region -f -b cxl_test "$region"
+
+setup_x6_mixed_gran
+create_region
+replay_regions || err "$LINENO"
+$CXL destroy-region -f -b cxl_test "$region"
+
+setup_x8_multi_level
+create_region
+replay_regions || err "$LINENO"
+$CXL destroy-region -f -b cxl_test "$region"
+
+setup_x6_same_gran_span_violation
+region=$(expect_fail "$CXL" create-region -d "$decoder" -w "$ways" \
+       -g "$region_ig" -m "$memdevs" | jq -r ".region")
+[[ ! $region ]] || err "$LINENO"
+
+setup_gran_too_small
+region=$(expect_fail "$CXL" create-region -d "$decoder" -w "$ways" \
+       -g "$region_ig" -m "$memdevs" | jq -r ".region")
+[[ ! $region ]] || err "$LINENO"
+
+test_ig_gt_root_rejected
+
+check_dmesg "$LINENO"
+
+modprobe -r cxl_test
diff --git a/test/meson.build b/test/meson.build
index e0e2193bfd51..67eb815db529 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -171,6 +171,7 @@ cxl_translate = find_program('cxl-translate.sh')
 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_region_mixed = find_program('cxl-region-mixed.sh')
 
 tests = [
   [ 'libndctl',               libndctl,                  'ndctl' ],
@@ -207,6 +208,7 @@ tests = [
   [ 'cxl-elc.sh',             cxl_elc,            'cxl'   ],
   [ 'cxl-dax-hmem.sh',        cxl_dax_hmem,       'cxl'   ],
   [ 'cxl-region-replay.sh',   cxl_region_replay,  'cxl'   ],
+  [ 'cxl-region-mixed.sh',    cxl_region_mixed,   'cxl'   ],
 ]
 
 if get_option('destructive').enabled()
-- 
2.37.3


Reply via email to