linux-cxl.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [ndctl PATCH] cxl/test: Add ELC MCE alias retirement tests
@ 2026-09-06  4:18 Shaikh Kamaluddin
  0 siblings, 0 replies; only message in thread
From: Shaikh Kamaluddin @ 2026-09-06  4:18 UTC (permalink / raw)
  To: linux-cxl, nvdimm
  Cc: dave.jiang, dave, jonathan.cameron, alison.schofield,
	vishal.l.verma, ira.weiny, dan.j.williams, benjamin.cheatham,
	Shaikh Kamaluddin

ELC alias retirement depends on the error severity and on whether
vendor-specific MCE rules consider the reported address usable. Add
regression coverage for these decisions to cxl-elc.sh.

Inject MCE records through the mce-inject debugfs interface and verify
the resulting bank status and alias-offlining messages in dmesg. Test
corrected and uncorrected errors on Intel, and exercise the poison,
deferred, and legacy bank-4 cases required by AMD address validation.

Derive the ELC SPA and its alias from cxl-list output, select offline
test addresses from lsmem, and discover available MCA banks dynamically.
Use a distinct SPA for each case and clean up modules loaded by the test.

Suggested-by: Ben Cheatham <benjamin.cheatham@amd.com>
Suggested-by: Alison Schofield <alison.schofield@intel.com>
Signed-off-by: Shaikh Kamaluddin <shaikhkamal2012@gmail.com>
---
Testing:
The test was run under virtme-ng with cxl_test configured for Extended
Linear Cache operation:

Intel, GenuineIntel:
--------------------

1. vng -v -r ./arch/x86/boot/bzImage --disable-kvm --qemu-opts='-cpu Skylake-Server-v4,+mce,+mca -m 4G -machine q35,cxl=on -object memory-backend-ram,id=cxl-mem0,size=512M -device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.0 -device cxl-rp,port=0,bus=cxl.0,id=root_port0,chassis=0,slot=0 -device cxl-type3,bus=root_port0,volatile-memdev=cxl-mem0,id=cxl-mem-device0 -M cxl-fmw.0.targets.0=cxl.0,cxl-fmw.0.size=512M'

2 . Go to ndctl-main directoy with this patches 

env NDCTL="$PWD/build/ndctl/ndctl" 
DAXCTL="$PWD/build/daxctl/daxctl" 
CXL="$PWD/build/cxl/cxl" 
./test/cxl-elc.sh

RESULT:
--------

PASS Intel corrected: alias no
PASS Intel uncorrected: alias yes
CXL ELC MCE validation passed for GenuineIntel: 2 cases
-----------------------------------------------------------------

AMD, AuthenticAMD legacy MCA: 
-----------------------------

1. vng -v -r ./arch/x86/boot/bzImage --disable-kvm --qemu-opts='-cpu EPYC-Milan -m 4G -machine q35,cxl=on -object memory-backend-ram,id=cxl-mem0,size=512M -device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.0 -device cxl-rp,port=0,bus=cxl.0,id=root_port0,chassis=0,slot=0 -device cxl-type3,bus=root_port0,volatile-memdev=cxl-mem0,id=cxl-mem-device0 -M cxl-fmw.0.targets.0=cxl.0,cxl-fmw.0.size=512M'

2. Go to ndctl-main directoy with this patches  
env NDCTL="$PWD/build/ndctl/ndctl" 
DAXCTL="$PWD/build/daxctl/daxctl" 
CXL="$PWD/build/cxl/cxl" 
./test/cxl-elc.sh

RESULT:
---------
PASS AMD plain corrected: alias no
PASS AMD plain uncorrected: alias no
PASS AMD poison-only (invalid encoding): alias no
PASS AMD deferred without poison: alias no
PASS AMD deferred with poison: alias yes
PASS AMD uncorrected with poison: alias yes
PASS AMD bank-4 non-memory poison (invalid encoding): alias no
PASS AMD legacy bank-4 corrected DRAM ECC: alias no
CXL ELC MCE validation passed for AuthenticAMD: 8 cases

 test/cxl-elc.sh | 313 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 312 insertions(+), 1 deletion(-)

diff --git a/test/cxl-elc.sh b/test/cxl-elc.sh
index cfa09cb..bd600b4 100755
--- a/test/cxl-elc.sh
+++ b/test/cxl-elc.sh
@@ -8,6 +8,30 @@ rc=77
 
 set -ex
 
+mce_inject_loaded=0
+
+cleanup()
+{
+	local status=$?
+	local cleanup_status=0
+
+	trap - EXIT ERR
+	set +e
+
+	if ((mce_inject_loaded)); then
+		modprobe -r mce-inject || cleanup_status=1
+	fi
+
+	modprobe -r cxl_test || cleanup_status=1
+
+	if ((status == 0 && cleanup_status != 0)); then
+		status=1
+	fi
+
+	exit "$status"
+}
+
+trap cleanup EXIT
 trap 'err $LINENO' ERR
 
 check_prereq "jq"
@@ -85,10 +109,297 @@ compare_bases()
 	((cxlrd_hpa == ep1_hpa - ep1_size)) || err "$LINENO"
 }
 
+mce_skip()
+{
+	echo "SKIP CXL ELC MCE subtest: $*"
+	return 0
+}
+
+dmesg_count()
+{
+	dmesg | grep -F -c -- "$1" || true
+}
+
+wait_for_dmesg()
+{
+	local pattern="$1"
+	local previous="$2"
+	local count
+
+	for _ in {1..50}; do
+		count="$(dmesg_count "$pattern")"
+		((count > previous)) && return 0
+		sleep 0.1
+	done
+
+	return 1
+}
+
+address_is_offline()
+{
+	local address="$1"
+	local range start end
+
+	while read -r range; do
+		[[ "$range" == *-* ]] || continue
+		start=$(( ${range%%-*} ))
+		end=$(( ${range##*-} ))
+		((address >= start && address <= end)) && return 0
+	done < <(echo "$lsmem_json" | jq -r \
+		'.memory[] | select((.state | ascii_downcase) == "offline") |
+		 .range')
+
+	return 1
+}
+
+find_offline_spas()
+{
+	local nr_spas="$1"
+	local range start end low high candidate alias spa_string alias_string
+	local page_size region_end cache_end i
+	local stride=0x10000
+
+	lsmem_json="$(lsmem --json --bytes --output RANGE,STATE)"
+	page_size="$(getconf PAGESIZE)"
+	region_end=$((region_hpa + region_size))
+	cache_end=$((region_hpa + elc_size))
+	inject_spas=()
+	alias_spas=()
+
+	while read -r range; do
+		[[ "$range" == *-* ]] || continue
+		start=$(( ${range%%-*} ))
+		end=$(( ${range##*-} ))
+
+		low=$start
+		((low < region_hpa)) && low=$region_hpa
+		high=$((end + 1))
+		((high > cache_end)) && high=$cache_end
+		((low < high)) || continue
+
+		# Stay away from the first pages of the hotplugged range.
+		candidate=$((low + stride))
+		candidate=$((
+			(candidate + page_size - 1) / page_size * page_size
+		))
+		inject_spas=()
+		alias_spas=()
+		for ((i = 0; i < nr_spas; i++)); do
+			candidate=$((candidate + (i > 0 ? stride : 0)))
+			alias=$((candidate + elc_size))
+			((candidate < high && alias < region_end)) || break
+			address_is_offline "$alias" || break
+			printf -v spa_string '0x%x' "$candidate"
+			printf -v alias_string '0x%x' "$alias"
+			inject_spas+=("$spa_string")
+			alias_spas+=("$alias_string")
+		done
+		((${#inject_spas[@]} == nr_spas)) || continue
+		return 0
+	done < <(echo "$lsmem_json" | jq -r \
+		'.memory[] | select((.state | ascii_downcase) == "offline") |
+		 .range')
+
+	return 1
+}
+
+discover_mca_banks()
+{
+	local path name
+
+	for path in \
+		/sys/devices/system/machinecheck/machinecheck0/bank[0-9]*; do
+		[[ -e "$path" ]] || continue
+		name="${path##*/}"
+		printf '%s\n' "${name#bank}"
+	done | sort -n -u
+}
+
+has_bank()
+{
+	local wanted="$1"
+	local bank
+
+	for bank in "${mca_banks[@]}"; do
+		[[ "$bank" == "$wanted" ]] && return 0
+	done
+
+	return 1
+}
+
+select_mce_parameters()
+{
+	local bank
+
+	vendor="$(awk '/^vendor_id/{ print $3; exit }' /proc/cpuinfo)"
+	readarray -t mca_banks < <(discover_mca_banks)
+	((${#mca_banks[@]})) || return 1
+
+	case "$vendor" in
+	GenuineIntel)
+		test_bank="${mca_banks[-1]}"
+		test_case_count=2
+		;;
+	AuthenticAMD)
+		# The usable-address corrected-error case is the legacy bank-4 DRAM
+		# ECC encoding (XEC 8). It is not valid for an SMCA bank.
+		grep -qw smca /proc/cpuinfo && return 1
+		has_bank 4 || return 1
+		non_b4_bank=
+		for bank in "${mca_banks[@]}"; do
+			[[ "$bank" == 4 ]] || non_b4_bank="$bank"
+		done
+		[[ -n "$non_b4_bank" ]] || return 1
+		test_case_count=8
+		;;
+	*)
+		return 1
+		;;
+	esac
+}
+
+inject_mce()
+{
+	local status="$1"
+	local bank="$2"
+	local mce_inject=/sys/kernel/debug/mce-inject
+
+	echo sw > "$mce_inject/flags"
+	echo "$status" > "$mce_inject/status"
+	echo 0x80 > "$mce_inject/misc"
+	echo "$inject_spa" > "$mce_inject/addr"
+	echo "$bank" > "$mce_inject/bank"
+}
+
+run_mce_case()
+{
+	local name="$1"
+	local status="$2"
+	local bank="$3"
+	local expect_alias="$4"
+	local index="$5"
+	local alias_msg bank_msg status_tag
+	local alias_before bank_before
+
+	inject_spa="${inject_spas[$index]}"
+	alias_spa="${alias_spas[$index]}"
+	alias_msg="Offlining aliased SPA address0: $alias_spa"
+	status_tag="${status#0x}"
+	bank_msg="Bank $bank: $status_tag"
+	alias_before="$(dmesg_count "$alias_msg")"
+	bank_before="$(dmesg_count "$bank_msg")"
+
+	inject_mce "$status" "$bank"
+	wait_for_dmesg "$bank_msg" "$bank_before" || err "$LINENO"
+
+	if [[ "$expect_alias" == "yes" ]]; then
+		wait_for_dmesg "$alias_msg" "$alias_before" || err "$LINENO"
+	else
+		(($(dmesg_count "$alias_msg") == alias_before)) || \
+			err "$LINENO"
+	fi
+
+	echo "PASS $name: SPA $inject_spa, alias $alias_spa, bank $bank, alias $expect_alias"
+}
+
+run_intel_mce_cases()
+{
+	run_mce_case "Intel corrected" 0x9c00000000000080 \
+		"$test_bank" no 0
+	run_mce_case "Intel uncorrected" 0xbc00000000000080 \
+		"$test_bank" yes 1
+}
+
+run_amd_mce_cases()
+{
+	# Mirror the full AMD matrix documented with the kernel fix. The two
+	# poison-only records have UC=0 and Deferred=0, so they are not valid
+	# architectural records. Keep them as software-injection robustness
+	# coverage and expect the corrected-error filter to reject them.
+	run_mce_case "AMD plain corrected" 0x9c00000000000080 \
+		"$non_b4_bank" no 0
+	run_mce_case "AMD plain uncorrected" 0xbc00000000000080 \
+		"$non_b4_bank" no 1
+	run_mce_case "AMD poison-only (invalid encoding)" \
+		0x9c00080000000080 "$non_b4_bank" no 2
+	run_mce_case "AMD deferred without poison" 0x9c00100000000080 \
+		"$non_b4_bank" no 3
+	run_mce_case "AMD deferred with poison" 0x9c00180000000080 \
+		"$non_b4_bank" yes 4
+	run_mce_case "AMD uncorrected with poison" 0xbc00080000000080 \
+		"$non_b4_bank" yes 5
+	run_mce_case "AMD bank-4 non-memory poison (invalid encoding)" \
+		0x9c00080000000080 4 no 6
+	run_mce_case "AMD legacy bank-4 corrected DRAM ECC" \
+		0x9c00000000080000 4 no 7
+}
+
+test_mce_alias()
+{
+	local region_json
+
+	[[ "$(uname -m)" == "x86_64" ]] || {
+		mce_skip "x86_64 is required"
+		return 0
+	}
+	command -v lsmem >/dev/null 2>&1 || {
+		mce_skip "lsmem is unavailable"
+		return 0
+	}
+
+	region_json="$(echo "$json" | jq -c --arg region "$region" \
+		'.[] | select(.region == $region)')"
+	region_hpa="$(echo "$region_json" | jq -r '.resource // empty')"
+	region_size="$(echo "$region_json" | jq -r '.size // empty')"
+	elc_size="$(echo "$region_json" | jq -r \
+		'.extended_linear_cache_size // empty')"
+
+	[[ "$region_hpa" =~ ^[0-9]+$ ]] || err "$LINENO"
+	[[ "$region_size" =~ ^[0-9]+$ ]] || err "$LINENO"
+	[[ "$elc_size" =~ ^[0-9]+$ ]] || err "$LINENO"
+	((elc_size > 0x10000 && region_size >= elc_size * 2)) || \
+		err "$LINENO"
+
+	select_mce_parameters || {
+		mce_skip "no supported MCA bank configuration for $vendor"
+		return 0
+	}
+
+	if [[ ! -d /sys/module/mce_inject ]]; then
+		modprobe mce-inject || {
+			mce_skip "mce-inject module is unavailable"
+			return 0
+		}
+		mce_inject_loaded=1
+	fi
+	[[ -d /sys/kernel/debug/mce-inject ]] || {
+		mce_skip "mce-inject debugfs interface is unavailable"
+		return 0
+	}
+
+	find_offline_spas "$test_case_count" || {
+		mce_skip "offline lsmem range lacks enough ELC test addresses"
+		return 0
+	}
+
+	case "$vendor" in
+	GenuineIntel)
+		run_intel_mce_cases
+		;;
+	AuthenticAMD)
+		run_amd_mce_cases
+		;;
+	esac
+
+	echo "CXL ELC MCE validation passed for $vendor: $test_case_count cases"
+}
+
 find_region
 retrieve_info
 compare_sizes
 compare_bases
 
+# Check before injecting the expected machine-check messages.
+check_dmesg "$LINENO"
+test_mce_alias
 check_dmesg "$LINENO"
-modprobe -r cxl_test

base-commit: 15e932c4e1318a9608ad9b799ad83a32a8b5970d
-- 
2.43.0


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-09-06  4:19 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-06  4:18 [ndctl PATCH] cxl/test: Add ELC MCE alias retirement tests Shaikh Kamaluddin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).