Hi Alison and Ben, Gentle ping on this patch. Could you please take a look when you have a time?
Thanks, Shaikh Kamaluddin On Sun, Sep 06, 2026 at 09:48:44AM +0530, Shaikh Kamaluddin wrote: > 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 <[email protected]> > Suggested-by: Alison Schofield <[email protected]> > Signed-off-by: Shaikh Kamaluddin <[email protected]> > --- > 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 >
