diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Basic_Validation/README.md b/Runner/suites/Connectivity/Ethernet/Ethernet_Basic_Validation/README.md index 860f0f15..44955dc3 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Basic_Validation/README.md +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Basic_Validation/README.md @@ -107,3 +107,12 @@ Ethernet_Basic_Validation.results.tsv ``` The overall testcase passes when at least one detected Ethernet interface completes link, IPv4, and ping validation. It fails when interfaces are testable but none passes. It skips when no interface can be tested, for example when every port is disconnected. + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Basic_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/Ethernet_Basic_Validation/run.sh index b68df82d..562118ad 100755 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Basic_Validation/run.sh +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Basic_Validation/run.sh @@ -50,6 +50,7 @@ fi TESTNAME="Ethernet_Basic_Validation" RES_FILE="./$TESTNAME.res" +ethv_failure_diagnostics_init "$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$/failure_diagnostics" test_path="$(find_test_case_by_name "$TESTNAME")" @@ -194,6 +195,7 @@ if ! ethv_is_positive_integer "$LINK_TIMEOUT_S" \ || ! ethv_is_positive_integer "$PING_RETRIES"; then log_fail "$TESTNAME FAIL: timeout, count, and retry values must be positive integers" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -218,6 +220,7 @@ ANY_FAIL=0 ANY_TESTED=0 for iface in $ETH_IFACES; do + ethv_failure_diagnostics_watch "$iface" driver="$(ethv_get_driver "$iface")" carrier="$(ethv_get_carrier "$iface")" operstate="$(ethv_get_operstate "$iface")" @@ -329,6 +332,7 @@ if [ "$ANY_PASS" -eq 1 ]; then echo "$TESTNAME PASS" >"$RES_FILE" elif [ "$ANY_TESTED" -eq 1 ] || [ "$ANY_FAIL" -eq 1 ]; then log_fail "$TESTNAME FAIL" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" else log_skip "$TESTNAME SKIP" diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Capability_Validation/README.md b/Runner/suites/Connectivity/Ethernet/Ethernet_Capability_Validation/README.md index 5bba5784..e0747f50 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Capability_Validation/README.md +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Capability_Validation/README.md @@ -62,7 +62,23 @@ shared discovery helper validates every detected physical Ethernet interface. ## Result +Driver statistics use the shared `lib_ethernet.sh` capture helper. It discovers +all unsigned counters reported by `ethtool -S`, without a driver-specific field +list. Full output and parsed values are retained under +`results/Ethernet_Capability_Validation/driver-*/`. Missing, unsupported, or +unusable optional driver statistics retain the existing `SKIP` policy for that +check and log the reason. No packages are installed. + ```text Ethernet_Capability_Validation.res Ethernet_Capability_Validation.results.tsv ``` + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Capability_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/Ethernet_Capability_Validation/run.sh index b521e921..66173fbc 100755 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Capability_Validation/run.sh +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Capability_Validation/run.sh @@ -50,6 +50,7 @@ fi TESTNAME="Ethernet_Capability_Validation" RES_FILE="./$TESTNAME.res" +ethv_failure_diagnostics_init "$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$/failure_diagnostics" test_path="$(find_test_case_by_name "$TESTNAME")" @@ -142,6 +143,7 @@ if ! ethv_is_boolean "$REQUIRE_FULL_DUPLEX" \ || ! ethv_is_uint "$MIN_LINK_SPEED_MBPS"; then log_fail "$TESTNAME FAIL: invalid configuration" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -165,6 +167,7 @@ if [ -z "$ETH_IFACES" ]; then fi for iface in $ETH_IFACES; do + ethv_failure_diagnostics_watch "$iface" driver="$(ethv_get_driver "$iface")" carrier="$(ethv_get_carrier "$iface")" speed="$(ethv_get_speed "$iface")" @@ -236,23 +239,21 @@ for iface in $ETH_IFACES; do "ethtool -k failed" fi - if ethtool -S "$iface" >/dev/null 2>&1; then - stat_count="$( - ethtool -S "$iface" 2>/dev/null \ - | awk -F: 'NF >= 2 {count++} END {print count + 0}' - )" + stats_prefix="$SCRIPT_DIR/results/$TESTNAME/driver-$$/$iface" + if ethv_capture_driver_statistics "$iface" "$stats_prefix"; then + stat_count="$(wc -l <"$stats_prefix.tsv" | tr -d '[:space:]')" ethv_record_result \ "$RESULT_TABLE" \ "$iface driver statistics" \ "PASS" \ - "$stat_count driver statistic(s) exposed" + "$stat_count driver statistic(s) exposed, artifact=$stats_prefix.tsv" else ethv_record_result \ "$RESULT_TABLE" \ "$iface driver statistics" \ "SKIP" \ - "driver does not support ethtool -S" + "$ETHV_DRIVER_STATS_REASON" fi if ethtool -a "$iface" >/dev/null 2>&1; then @@ -423,6 +424,7 @@ case "$OVERALL_RESULT" in ;; FAIL) log_fail "$TESTNAME FAIL" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" ;; *) diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Dual_Port_Validation/README.md b/Runner/suites/Connectivity/Ethernet/Ethernet_Dual_Port_Validation/README.md index 80400c3c..54dc0620 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Dual_Port_Validation/README.md +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Dual_Port_Validation/README.md @@ -65,3 +65,12 @@ The testcase skips when fewer than two physical Ethernet interfaces are present Ethernet_Dual_Port_Validation.res Ethernet_Dual_Port_Validation.results.tsv ``` + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Dual_Port_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/Ethernet_Dual_Port_Validation/run.sh index a2fd15d1..cca81ea8 100755 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Dual_Port_Validation/run.sh +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Dual_Port_Validation/run.sh @@ -50,6 +50,7 @@ fi TESTNAME="Ethernet_Dual_Port_Validation" RES_FILE="./$TESTNAME.res" +ethv_failure_diagnostics_init "$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$/failure_diagnostics" test_path="$(find_test_case_by_name "$TESTNAME")" @@ -208,6 +209,7 @@ if ! ethv_is_positive_integer "$LINK_TIMEOUT_S" \ || ! ethv_is_positive_integer "$PING_WAIT_S"; then log_fail "$TESTNAME FAIL: numeric configuration values must be positive integers" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -229,6 +231,7 @@ ETH_IFACE_LINES="$( IFACE_A="$(printf '%s\n' "$ETH_IFACE_LINES" | sed -n '1p')" IFACE_B="$(printf '%s\n' "$ETH_IFACE_LINES" | sed -n '2p')" +ethv_failure_diagnostics_watch "$IFACE_A" "$IFACE_B" if [ -z "$IFACE_A" ] || [ -z "$IFACE_B" ]; then ethv_record_result \ @@ -250,11 +253,13 @@ if [ "$IFACE_A" = "$IFACE_B" ]; then "the selected interface names are identical: $IFACE_A" ethv_print_summary "$RESULT_TABLE" "Ethernet Dual-Port Summary" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi for iface in "$IFACE_A" "$IFACE_B"; do + ethv_failure_diagnostics_watch "$iface" if [ ! -d "/sys/class/net/$iface" ]; then ethv_record_result \ "$RESULT_TABLE" \ @@ -302,6 +307,7 @@ done if [ "$(ethv_result_count "$RESULT_TABLE" FAIL)" -gt 0 ]; then ethv_print_summary "$RESULT_TABLE" "Ethernet Dual-Port Summary" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -426,6 +432,7 @@ case "$OVERALL_RESULT" in ;; FAIL) log_fail "$TESTNAME FAIL" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" ;; *) diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/README.md b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/README.md index a7d1d722..38723977 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/README.md +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/README.md @@ -26,6 +26,15 @@ testcase also correlates downstream TC956x driver functions with exported Ethernet interfaces and verifies that `TC956X_Firmware_PCIeBridge.bin` remains available from a standard firmware root. Images without QPS615 retain the generic Ethernet inventory behavior. +The QPS615 summary separates outermost switch roots from matching bridge +functions, so one switch exposing four `1179:0623` functions is counted once. + +When an enabled runtime DT node uses `pci1179,0623`, the inventory also +records the complete DT compatible, supply-property count, matching platform +device, and `pwrctrl-tc9563` binding. The platform driver is required only for +nodes that declare the public six-supply power-control contract. A partial +supply set is malformed, while integrations with no supply properties retain +their existing PCI runtime validation. For each expected Toshiba `1179:0220` Ethernet function, the live log and TSV include its PCI modalias, bound driver, exported netdevs, and availability of @@ -98,7 +107,22 @@ On QPS615 systems, inspect the correlated PCIe, driver, and netdev evidence: ```sh cat qps615_runtime/qps615_switches.log cat qps615_runtime/qps615_runtime.tsv +cat qps615_runtime/qps615_dt_platform.tsv ``` The QPS615 files are empty or contain no switch records when the capability is not present. That does not change the generic Ethernet result path. + +Use the focused `QPS615_Interface_Validation` suite for optional EFI FIT +selection, runtime device-tree, power-control, and detailed dynamic interface +checks. Use +`QPS615_Traffic_Validation` only with a confirmed external peer fixture. + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/run.sh index 49743c40..b38a88c8 100755 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/run.sh +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/run.sh @@ -50,6 +50,7 @@ fi TESTNAME="Ethernet_Inventory_Validation" RES_FILE="./$TESTNAME.res" +ethv_failure_diagnostics_init "$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$/failure_diagnostics" test_path="$(find_test_case_by_name "$TESTNAME")" @@ -141,7 +142,21 @@ case "$qps615_status" in "$RESULT_TABLE" \ "QPS615 PCIe topology and firmware" \ "PASS" \ - "bridge_functions=$QPS615_SWITCH_COUNT downstream=$QPS615_DOWNSTREAM_COUNT firmware=$QPS615_FIRMWARE_PATH" + "switches=$QPS615_SWITCH_COUNT bridge_functions=$QPS615_BRIDGE_COUNT downstream=$QPS615_DOWNSTREAM_COUNT firmware=$QPS615_FIRMWARE_PATH" + fi + + if [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + ethv_record_result \ + "$RESULT_TABLE" \ + "QPS615 PCIe power-control binding" \ + "FAIL" \ + "$QPS615_PWRCTRL_FAILURE_REASON" + elif [ "$QPS615_PWRCTRL_EXPECTED_COUNT" -gt 0 ]; then + ethv_record_result \ + "$RESULT_TABLE" \ + "QPS615 PCIe power-control binding" \ + "PASS" \ + "$QPS615_PWRCTRL_BOUND_COUNT supply-backed DT node(s) are bound to pwrctrl-tc9563" fi if [ -n "$QPS615_ETHERNET_FAILURE_REASON" ]; then @@ -212,6 +227,7 @@ else fi for iface in $ETH_IFACES; do + ethv_failure_diagnostics_watch "$iface" driver="$(ethv_get_driver "$iface")" module="$(ethv_get_driver_module "$iface")" device_path="$(ethv_get_device_path "$iface")" @@ -328,6 +344,7 @@ case "$OVERALL_RESULT" in ;; FAIL) log_fail "$TESTNAME FAIL" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" ;; *) diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Statistics_Validation/README.md b/Runner/suites/Connectivity/Ethernet/Ethernet_Statistics_Validation/README.md index 9b5fe87b..d87baa33 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Statistics_Validation/README.md +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Statistics_Validation/README.md @@ -67,7 +67,26 @@ Use `./run.sh` to validate every detected physical interface, or select one: ## Result +Optional driver statistics are captured before and after interface-bound +traffic, including failed ping attempts, through shared `lib_ethernet.sh` +helpers. All runtime counter names and exact unsigned values are retained in +`results/Ethernet_Statistics_Validation/driver-*/` as raw `.log`, parsed `.tsv`, +and comparison `_changes.tsv` files. A bounded comparison excerpt is logged +on stdout. Counter ordering, added/removed fields, and 64-bit values are handled +without fixed driver counter lists. These fields remain diagnostic because +their reset/gauge semantics are driver-specific. Existing standard packet, +byte, error, and optional drop thresholds determine the result unchanged. + ```text Ethernet_Statistics_Validation.res Ethernet_Statistics_Validation.results.tsv ``` + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Statistics_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/Ethernet_Statistics_Validation/run.sh index b823f0e1..9ba1fad3 100755 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Statistics_Validation/run.sh +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Statistics_Validation/run.sh @@ -50,6 +50,7 @@ fi TESTNAME="Ethernet_Statistics_Validation" RES_FILE="./$TESTNAME.res" +ethv_failure_diagnostics_init "$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$/failure_diagnostics" test_path="$(find_test_case_by_name "$TESTNAME")" @@ -219,6 +220,7 @@ for value in \ if ! ethv_is_uint "$value"; then log_fail "$TESTNAME FAIL: all numeric configuration values must be unsigned integers" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -229,6 +231,7 @@ if [ -n "$MAX_RX_DROP_DELTA" ] && [ -n "$MAX_TX_DROP_DELTA" ]; then || ! ethv_is_uint "$MAX_TX_DROP_DELTA"; then log_fail "$TESTNAME FAIL: drop thresholds must be unsigned integers" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -236,6 +239,7 @@ if [ -n "$MAX_RX_DROP_DELTA" ] && [ -n "$MAX_TX_DROP_DELTA" ]; then DROP_THRESHOLDS_ENABLED=1 elif [ -n "$MAX_RX_DROP_DELTA" ] || [ -n "$MAX_TX_DROP_DELTA" ]; then log_fail "$TESTNAME FAIL: both RX and TX drop thresholds must be configured together" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -259,6 +263,7 @@ if [ -z "$ETH_IFACES" ]; then fi for iface in $ETH_IFACES; do + ethv_failure_diagnostics_watch "$iface" log_info "---- Statistics validation for $iface ----" ethv_prepare_interface "$iface" "$LINK_TIMEOUT_S" "$IP_TIMEOUT_S" @@ -305,9 +310,24 @@ for iface in $ETH_IFACES; do rx_dropped_before="$(ethv_get_counter "$iface" rx_dropped)" tx_dropped_before="$(ethv_get_counter "$iface" tx_dropped)" + stats_prefix="$SCRIPT_DIR/results/$TESTNAME/driver-$$/$iface" + driver_stats_status=0 + ethv_capture_driver_statistics "$iface" "${stats_prefix}_before" || driver_stats_status=$? + log_info "$ETHV_DRIVER_STATS_REASON" + log_info "$iface baseline: rx_bytes=$rx_bytes_before tx_bytes=$tx_bytes_before rx_packets=$rx_packets_before tx_packets=$tx_packets_before rx_errors=$rx_errors_before tx_errors=$tx_errors_before rx_dropped=$rx_dropped_before tx_dropped=$tx_dropped_before" - if ! ethv_ping_interface "$iface" "$target" "$PING_COUNT" "$PING_WAIT_S"; then + traffic_status=0 + ethv_ping_interface "$iface" "$target" "$PING_COUNT" "$PING_WAIT_S" || traffic_status=$? + if [ "$driver_stats_status" -eq 0 ]; then + if ethv_capture_driver_statistics "$iface" "${stats_prefix}_after" && + ethv_compare_driver_statistics "${stats_prefix}_before.tsv" "${stats_prefix}_after.tsv" "${stats_prefix}_changes.tsv"; then + log_file_with_label "ETHERNET-DRIVER-COUNTERS-$iface" "${stats_prefix}_changes.tsv" 60 + else + log_warn "Optional driver-counter comparison is unavailable: $ETHV_DRIVER_STATS_REASON" + fi + fi + if [ "$traffic_status" -ne 0 ]; then ethv_record_result \ "$RESULT_TABLE" \ "$iface bounded traffic" \ @@ -398,16 +418,6 @@ for iface in $ETH_IFACES; do "rx_dropped_delta=$rx_dropped_delta tx_dropped_delta=$tx_dropped_delta thresholds=$MAX_RX_DROP_DELTA/$MAX_TX_DROP_DELTA" fi - if command -v ethtool >/dev/null 2>&1; then - log_info "[$iface ethtool statistics snapshot]" - ethtool -S "$iface" 2>/dev/null \ - | grep -iE \ - 'error|drop|miss|crc|overflow|underflow|timeout|collision|packet|byte' \ - | tail -n 200 \ - | while IFS= read -r line; do - [ -n "$line" ] && log_info "[$iface stats] $line" - done - fi done OVERALL_RESULT="$(ethv_compute_overall_result "$RESULT_TABLE")" @@ -421,6 +431,7 @@ case "$OVERALL_RESULT" in ;; FAIL) log_fail "$TESTNAME FAIL" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" ;; *) diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Suspend_Resume_Validation/README.md b/Runner/suites/Connectivity/Ethernet/Ethernet_Suspend_Resume_Validation/README.md index 04ae6259..f5e81833 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Suspend_Resume_Validation/README.md +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Suspend_Resume_Validation/README.md @@ -74,3 +74,12 @@ rtcwake.log dmesg/dmesg_snapshot.log dmesg/dmesg_errors.log ``` + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Suspend_Resume_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/Ethernet_Suspend_Resume_Validation/run.sh index 62e43755..0054a992 100755 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Suspend_Resume_Validation/run.sh +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Suspend_Resume_Validation/run.sh @@ -50,6 +50,7 @@ fi TESTNAME="Ethernet_Suspend_Resume_Validation" RES_FILE="./$TESTNAME.res" +ethv_failure_diagnostics_init "$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$/failure_diagnostics" test_path="$(find_test_case_by_name "$TESTNAME")" @@ -209,6 +210,7 @@ log_info "Configuration: enable=$ETH_SUSPEND_ENABLE interface=${ETH_SUSPEND_INTE if ! ethv_is_boolean "$ETH_SUSPEND_ENABLE"; then log_fail "$TESTNAME FAIL: ETH_SUSPEND_ENABLE must be 0 or 1" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -253,6 +255,7 @@ for value in \ if ! ethv_is_positive_integer "$value"; then log_fail "$TESTNAME FAIL: timeout, duration, and ping values must be positive integers" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -300,6 +303,7 @@ if [ -z "$ETH_IFACE" ] || [ ! -d "/sys/class/net/$ETH_IFACE" ]; then exit 0 fi +ethv_failure_diagnostics_watch "$ETH_IFACE" ethv_prepare_interface "$ETH_IFACE" "$LINK_TIMEOUT_S" "$IP_TIMEOUT_S" prepare_rc=$? @@ -335,6 +339,7 @@ if [ "$prepare_rc" -ne 0 ]; then case "$OVERALL_RESULT" in FAIL) + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" ;; *) @@ -383,6 +388,7 @@ if [ "$RTCWAKE_RC" -ne 0 ]; then "rtcwake failed with rc=$RTCWAKE_RC" ethv_print_summary "$RESULT_TABLE" "Ethernet Suspend/Resume Summary" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -401,6 +407,7 @@ if [ ! -d "/sys/class/net/$ETH_IFACE" ]; then "interface disappeared after resume" ethv_print_summary "$RESULT_TABLE" "Ethernet Suspend/Resume Summary" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -528,6 +535,7 @@ case "$OVERALL_RESULT" in ;; FAIL) log_fail "$TESTNAME FAIL" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" ;; *) diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Throughput_Validation/README.md b/Runner/suites/Connectivity/Ethernet/Ethernet_Throughput_Validation/README.md index 4defc881..74a9ee42 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Throughput_Validation/README.md +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Throughput_Validation/README.md @@ -126,3 +126,12 @@ Ethernet_Throughput_Validation.res Ethernet_Throughput_Validation.results.tsv iperf_logs/ ``` + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Throughput_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/Ethernet_Throughput_Validation/run.sh index 434e56c2..171bb95b 100755 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Throughput_Validation/run.sh +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Throughput_Validation/run.sh @@ -50,6 +50,7 @@ fi TESTNAME="Ethernet_Throughput_Validation" RES_FILE="./$TESTNAME.res" +ethv_failure_diagnostics_init "$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$/failure_diagnostics" test_path="$(find_test_case_by_name "$TESTNAME")" @@ -251,6 +252,7 @@ parse_args "$@" rm -f "$RES_FILE" "$RESULT_TABLE" mkdir -p "$LOG_DIR" || { + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 } @@ -416,6 +418,7 @@ for value in \ if ! ethv_is_positive_integer "$value"; then log_fail "$TESTNAME FAIL: timeout, port, duration, and stream values must be positive integers" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -428,6 +431,7 @@ for bool_value in \ if ! ethv_is_boolean "$bool_value"; then log_fail "$TESTNAME FAIL: enable controls must be 0 or 1" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -444,6 +448,7 @@ case "$ETH_IPERF_MODE" in external) if [ -z "$ETH_IPERF_SERVER" ]; then log_fail "$TESTNAME FAIL: external mode requires --iperf-server or ETH_IPERF_SERVER" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -452,6 +457,7 @@ case "$ETH_IPERF_MODE" in local) if [ -n "$ETH_IPERF_SERVER" ]; then log_fail "$TESTNAME FAIL: local mode cannot be combined with an external iperf3 server" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -459,6 +465,7 @@ case "$ETH_IPERF_MODE" in ;; *) log_fail "$TESTNAME FAIL: mode must be auto, external, or local" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 ;; @@ -472,6 +479,7 @@ for threshold_value in \ if ! ethv_is_nonnegative_decimal "$threshold_value"; then log_fail "$TESTNAME FAIL: throughput thresholds must be nonnegative decimal numbers" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -495,6 +503,7 @@ if [ -z "$ETH_IFACE" ] || [ ! -d "/sys/class/net/$ETH_IFACE" ]; then exit 0 fi +ethv_failure_diagnostics_watch "$ETH_IFACE" ethv_prepare_interface "$ETH_IFACE" "$LINK_TIMEOUT_S" "$IP_TIMEOUT_S" prepare_rc=$? @@ -530,6 +539,7 @@ if [ "$prepare_rc" -ne 0 ]; then case "$OVERALL_RESULT" in FAIL) + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" ;; *) @@ -547,6 +557,7 @@ if [ "$RESOLVED_IPERF_MODE" = "local" ]; then if ! start_local_iperf_server; then ethv_print_summary "$RESULT_TABLE" "Ethernet Throughput Summary" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" exit 0 fi @@ -629,6 +640,7 @@ case "$OVERALL_RESULT" in ;; FAIL) log_fail "$TESTNAME FAIL" + ethv_failure_diagnostics "$TESTNAME failure" echo "$TESTNAME FAIL" >"$RES_FILE" ;; *) diff --git a/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/QPS615_Interface_Validation.yaml b/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/QPS615_Interface_Validation.yaml new file mode 100644 index 00000000..62b3575e --- /dev/null +++ b/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/QPS615_Interface_Validation.yaml @@ -0,0 +1,15 @@ +metadata: + name: qps615-interface-validation + format: "Lava-Test Test Definition 1.0" + description: "Validate QPS615 runtime device-tree and PCIe topology, conditional pwrctrl-tc9563 binding, TC956x firmware and driver readiness, and dynamically discovered Ethernet interfaces on base and overlay images without inspecting EFI or changing target state." + os: + - linux + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh QPS615_Interface_Validation.res || true diff --git a/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/README.md b/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/README.md new file mode 100644 index 00000000..6c17affa --- /dev/null +++ b/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/README.md @@ -0,0 +1,177 @@ +# QPS615 interface validation + +`QPS615_Interface_Validation` performs read-only QPS615 interface bring-up by +default on both base and overlay images, without hardcoded PCI BDFs or +network-interface names. It correlates +enabled runtime device-tree nodes, the Toshiba `1179:0623` switch, downstream +`1179:0220` Ethernet functions, `tc956x_pcie_eth`, the registered +`tc956x_pci-eth` driver, and exported netdevs. An explicit option supports the +optional EFI boot selection on boards whose FIT integration requires it. This +firmware DT selection is unrelated to installing an image's overlay packages. + +The runtime device tree is authoritative when it declares +`compatible = "pci1179,0623"`. A declared node with no enumerated switch is a +bring-up failure. An enumerated switch without a matching runtime DT node is +still validated for compatibility with older integrations. + +The public qcom-next binding requires six supplies, `i2c-parent`, and +`resx-gpios` for the TC9563 PCI power-control flow. When a runtime node declares +any of those six supplies, the suite requires the complete supply set and the +node to bind to the `pwrctrl-tc9563` platform driver. Nodes that declare no +supplies remain valid for integrations such as the Talos staging topology that +do not instantiate this platform driver. + +## Run + +Default validation is read-only. For an explicit standalone module load and +driver/netdev readiness check, reuse the existing module-validation profile: + +```sh +cd Runner/suites/Kernel/Baseport/Module_Reload_Validation +./run.sh --module tc956x_pcie_eth --mode load_only +``` + +Run that command from the repository root. It supports both base and overlay +images without inspecting EFI, keeps initially active modules untouched, and +removes only the target module it loaded. See the module suite README for +prerequisites, restoration, artifacts, and result classification. Ordinary +unload/reload coverage remains available through its unchanged `basic` mode. + +```sh +cd Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation +./run.sh +``` + +Use this same command on base and overlay images. It does not inspect or modify +EFI and has no `staging` requirement. Runtime DT, PCI enumeration, driver binding +and netdev correlation determine readiness, regardless of package configuration +or how the running DT was loaded. A loaded `tc956x_pcie_eth` module alone is not +proof of an enumerated QPS615 or a usable Ethernet port. + +Address readiness prefers IPv4 and also accepts IPv6-only interfaces. IPv6 +discovery uses image-provided `ip`, prefers a global address over a link-local +address, and excludes tentative or duplicate-address-failed addresses. A +link-local address supports a same-link peer, it does not prove Internet access. +The interface diagnostic line reports both families. Address readiness is not +packet-transfer proof, run `QPS615_Traffic_Validation` for interface-bound ping, +packet loss and counter checks. No DHCP client or host-side command is started. + +### Optional FIT boot preparation + +Only when the board's bring-up procedure requires the `staging` FIT selection, +prepare the next boot explicitly. This is not required merely because an image +is base, overlay, or FIT-based: + +```sh +./run.sh --prepare-fit +# Existing commands remain supported: +./run.sh --prepare-overlay +``` + +`--prepare-overlay` is a compatibility alias for `--prepare-fit`. Neither option +selects, downloads, or installs overlay packages. `-h` and `--help` print usage. +There are no environment-variable equivalents for these options. + +This option requires image-provided `efivar`, mount utilities, and EFI runtime +services. If efivarfs is unmounted, it mounts the existing EFI variable-storage +directory and leaves it mounted. This follows the manual procedure: + +```sh +mount | grep efivar +# If no efivarfs mount is listed: +mount -t efivarfs none /sys/firmware/efi/efivars +``` + +An existing mount typically appears as +`none on /sys/firmware/efi/efivars type efivarfs (rw,relatime)`. +An existing unique `VendorDtbOverlays` variable supplies its GUID. +If a successful EFI inventory shows no +`VendorDtbOverlays`, explicit preparation creates it using the documented +QPS615 firmware GUID `882f8c2b-9646-435f-8de5-f208ff80c1bd`. Failed inventory, +ambiguous names, and unreadable variables prevent writes and report failure. + +If `staging` is not selected, this command writes and verifies it, records +`SKIP`, and asks +the operator to reboot manually. It never reboots the target. After reboot, +rerun the same command. An already selected FIT value followed by missing +runtime QPS615 DT and PCI evidence is then a failure, exposing a FIT image, +firmware, or boot-integration problem. +Firmware may report `staging,el2kvm` after reboot. A value already containing +`staging` is not rewritten, and the additional firmware selection is retained. + +Preparation never unmounts efivarfs. A newly created mount remains available +after success, write/read failures, or interruption. Cleanup only restores an +initially read-only mount after a temporary read-write remount, including on +failure or interruption. Initially read-write mounts remain read-write. +Read-only restoration failure makes preparation fail even if the EFI write +succeeded. + +The LAVA YAML intentionally runs only the read-only default command. EFI +mutation is not exposed as an automated parameter. Neither mode changes +network configuration, routes, or link state. + +## Validation and classification + +The suite validates: + +- enabled `pci1179,0623` DT nodes and their complete compatible strings; +- conditional `pwrctrl-tc9563` platform-device binding; +- PCI switch and downstream endpoint enumeration, reporting outermost + `1179:0623` switch roots separately from all matching bridge functions; +- `TC956X_Firmware_PCIeBridge.bin` provisioning; +- TC956x module and PCI-driver readiness; +- runtime DT Ethernet-port declarations and dynamic netdev correlation; +- optional non-mutating `ethtool` driver and link diagnostics; +- captured kernel health through the shared dmesg scanner. + +Carrier and IPv4 are readiness evidence and do not fail this suite because +they depend on external wiring and lab network configuration. + +- `PASS`: applicable DT, power-control, PCIe, firmware, driver, and netdev + checks are healthy. +- `FAIL`: DT discovery cannot complete, declared hardware does not enumerate, the supply-backed DT contract + is partial or unbound, an explicitly selected FIT value does not become + active, EFI preparation fails, required firmware or driver readiness is + broken, or an image-provided diagnostic tool fails on an interface. +- `SKIP`: no enabled QPS615 DT node or enumerated switch is present, no + Ethernet port is provisioned, a newly written FIT value requires reboot, or + optional carrier, IPv4, or `ethtool` evidence is absent. + +The suite installs no packages and is portable across Yocto, Debian, Ubuntu, +and CentOS. Evidence is retained under +`results/QPS615_Interface_Validation/run-*/`, including +`qps615_runtime/qps615_runtime.tsv`, `qps615_dt_nodes.log`, +`qps615_dt_platform.tsv`, `qps615_dt_scan.log`, PCI topology, +`ethtool`, and captured kernel logs. +Only explicit FIT preparation generates `qps615_overlay.tsv`, EFI diagnostics, +`before_qps615_overlay.tsv`, the original variable +inventory and printout, and mount/cleanup logs alongside the updated evidence. +Scratch files use the writable evidence directory, so an inherited missing +ADB `TMPDIR` such as `/data/local/tmp` cannot break EFI or DT discovery. A +discovery error is reported as `FAIL`, never as proof that QPS615 is absent. + +For the guide's one upstream and three downstream `0623` bridges, the runtime +summary reports `switches=1 bridge_functions=4`. Separate PCI hierarchies are +counted independently. A switch-root group includes any nested `0623` bridges, +so this count describes the discovered topology roots. + +## Public driver references + +- [qcom-next TC9563 binding](https://github.com/qualcomm-linux/kernel/blob/qcom-next/Documentation/devicetree/bindings/pci/toshiba%2Ctc9563.yaml) +- [qcom-next TC9563 power-control driver](https://github.com/qualcomm-linux/kernel/blob/qcom-next/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c) +- [kernel-topics QPS615 pull requests](https://github.com/search?q=repo%3Aqualcomm-linux%2Fkernel-topics+qps615&type=pullrequests) +- [QCS8550 RB5Gen2 base-DT integration](https://github.com/qualcomm-linux/kernel-topics/commit/33cae252054a694269f9dcd41b354063193c8f59) + +Use `QPS615_Traffic_Validation` for fixture-gated interface-bound ping and +counter movement. Use the existing generic Ethernet capability, throughput, +bidirectional, dual-port, and suspend/resume suites for their respective +end-use cases after selecting a QPS615-correlated interface. + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/run.sh new file mode 100755 index 00000000..92ef7863 --- /dev/null +++ b/Runner/suites/Connectivity/Ethernet/QPS615_Interface_Validation/run.sh @@ -0,0 +1,385 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# Read-only QPS615 PCIe Ethernet interface readiness validation. + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_system.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_ethernet.sh" + +TESTNAME="QPS615_Interface_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +PREPARE_OVERLAY=0 +SHOW_HELP=0 +RESULT_DIR="$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$$" +ethv_failure_diagnostics_init "$RESULT_DIR/failure_diagnostics" +QPS_RUNTIME_DIR="$RESULT_DIR/qps615_runtime" +AVAILABLE_INTERFACES="$RESULT_DIR/qps615_interfaces.log" + +# usage +# Print the command-line contract. stdout: usage text. return: 0. +# Side effects: none. +usage() { + printf '%s\n' \ + "Usage: ./run.sh [options]" \ + " --prepare-fit Select VendorDtbOverlays=staging for a board that requires it" \ + " --prepare-overlay Compatibility alias for --prepare-fit, not a package overlay" \ + " -h, --help" \ + "Default validation is identical on base and overlay images and does not inspect EFI." \ + "Optional FIT preparation never reboots the target or changes image packages." +} + +# parse_args +# Parse suite options into PREPARE_OVERLAY and SHOW_HELP. stdout: none. +# return: 0 on success or 2 for invalid input. Side effects: updates globals. +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --prepare-fit|--prepare-overlay) + PREPARE_OVERLAY=1 + shift + ;; + -h|--help) + SHOW_HELP=1 + shift + ;; + *) + log_error "Unknown argument: $1" + return 2 + ;; + esac + done +} + +if ! test_result_init "$TESTNAME" "$RES_FILE"; then + log_error "Could not initialize the result file: $RES_FILE" + exit 1 +fi + +if ! parse_args "$@"; then + usage >&2 + test_result_record "FAIL" "QPS615 interface command-line configuration is invalid" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +if [ "$SHOW_HELP" -eq 1 ]; then + usage + exit 0 +fi + +if ! CHECK_DEPS_RECOVER=0 CHECK_DEPS_NO_EXIT=1 check_dependencies \ + awk \ + basename \ + date \ + dirname \ + find \ + grep \ + mkdir \ + readlink \ + sed \ + tr \ + uname \ + wc; then + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: image-provided base utilities required for QPS615 discovery are unavailable" +fi + +if ! mkdir -p "$RESULT_DIR"; then + test_result_record "FAIL" "Could not create QPS615 evidence directory $RESULT_DIR" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "Evidence directory: $RESULT_DIR" +log_info "Configuration: prepare_fit=$PREPARE_OVERLAY applicability=runtime-dt-or-pci image_policy=base-and-overlay" + +if [ "$PREPARE_OVERLAY" -eq 1 ]; then + trap 'efi_restore_efivarfs_ro >>"$QPS_RUNTIME_DIR/qps615_efi_cleanup.log" 2>&1' EXIT + trap ' + test_result_record "FAIL" "QPS615 overlay preparation interrupted by SIGHUP" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + ' HUP + trap ' + test_result_record "FAIL" "QPS615 overlay preparation interrupted by SIGINT" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + ' INT + trap ' + test_result_record "FAIL" "QPS615 overlay preparation interrupted by SIGTERM" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + ' TERM + ethv_qps615_prepare_overlay "$QPS_RUNTIME_DIR" + prepare_status=$? + case "$prepare_status" in + 0) + log_info "Optional FIT boot selection is already configured, validating current runtime hardware" + ;; + 4) + test_result_record "SKIP" "$QPS615_OVERLAY_REASON" + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: QPS615 FIT selection was updated, reboot manually and rerun with --prepare-fit" + ;; + 2) + test_result_record \ + "FAIL" \ + "QPS615 FIT overlay preparation was explicitly requested but is unavailable: ${QPS615_OVERLAY_REASON:-unknown reason}" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + ;; + *) + test_result_record \ + "FAIL" \ + "QPS615 FIT overlay preparation failed: ${QPS615_OVERLAY_REASON:-unknown reason}" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + ;; + esac +fi + +ethv_qps615_collect_runtime "$QPS_RUNTIME_DIR" +qps_runtime_status=$? + +if [ -n "${QPS615_DT_FAILURE_REASON:-}" ]; then + test_result_record "FAIL" "$QPS615_DT_FAILURE_REASON" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +if [ "${QPS615_DT_NODE_COUNT:-0}" -gt 0 ]; then + test_result_record \ + "PASS" \ + "Found $QPS615_DT_NODE_COUNT enabled runtime DT node(s) compatible with pci1179,0623" +else + test_result_record \ + "SKIP" \ + "No enabled QPS615 runtime DT node was found, legacy PCI-only discovery remains supported" +fi + +if [ "${QPS615_PWRCTRL_EXPECTED_COUNT:-0}" -gt 0 ] && \ + [ "${QPS615_PWRCTRL_BOUND_COUNT:-0}" -eq "$QPS615_PWRCTRL_EXPECTED_COUNT" ]; then + test_result_record \ + "PASS" \ + "All $QPS615_PWRCTRL_BOUND_COUNT supply-backed QPS615 DT node(s) are bound to pwrctrl-tc9563" +elif [ "${QPS615_PWRCTRL_EXPECTED_COUNT:-0}" -eq 0 ]; then + test_result_record \ + "SKIP" \ + "No QPS615 DT node declares the supply-backed pwrctrl-tc9563 contract" +elif [ -n "${QPS615_PWRCTRL_FAILURE_REASON:-}" ]; then + test_result_record \ + "FAIL" \ + "$QPS615_PWRCTRL_FAILURE_REASON" + ethv_failure_diagnostics "$TESTNAME failure" +fi + +case "$qps_runtime_status" in + 0) + test_result_record \ + "PASS" \ + "QPS615 topology, firmware, driver, and netdev readiness are healthy: $QPS615_READINESS_SUMMARY" + ;; + 1) + test_result_record \ + "FAIL" \ + "QPS615 interface readiness failed: ${QPS615_FAILURE_REASON:-unknown runtime failure}" + ethv_failure_diagnostics "$TESTNAME failure" + ;; + 2) + if [ "$PREPARE_OVERLAY" -eq 1 ] && \ + [ "${QPS615_OVERLAY_STATE:-unavailable}" = "configured" ]; then + test_result_record \ + "FAIL" \ + "QPS615 FIT value is selected in EFI but no enabled pci1179,0623 DT node or switch 1179:0623 is active, reboot if the value was just selected or inspect the FIT image and boot firmware" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + fi + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: no enabled QPS615 DT node or enumerated switch 1179:0623 was discovered, verify board support, the active DT and PCIe enumeration on either base or overlay images" + ;; + 3) + test_result_record "FAIL" "QPS615 runtime discovery received an invalid path or result directory" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + ;; + *) + test_result_record "FAIL" "QPS615 runtime discovery returned unexpected status $qps_runtime_status" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + ;; +esac + +log_file_with_label "QPS615-RUNTIME" "$QPS_RUNTIME_DIR/qps615_runtime.tsv" 60 + +if [ -n "${QPS615_ETHERNET_SKIP_REASON:-}" ] && \ + [ "$qps_runtime_status" -eq 0 ]; then + test_result_record "SKIP" "$QPS615_ETHERNET_SKIP_REASON" + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: QPS615 is present but no Ethernet port is provisioned by the runtime device tree" +fi + +if ! ethv_qps615_list_netdevs \ + "$QPS_RUNTIME_DIR/qps615_runtime.tsv" >"$AVAILABLE_INTERFACES"; then + test_result_record \ + "FAIL" \ + "Could not parse QPS615 netdev correlation from $QPS_RUNTIME_DIR/qps615_runtime.tsv" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +available_count=$(wc -l <"$AVAILABLE_INTERFACES" | tr -d '[:space:]') +log_file_with_label "QPS615-INTERFACE" "$AVAILABLE_INTERFACES" 16 + +if [ "$available_count" -eq 0 ]; then + if [ "$qps_runtime_status" -eq 1 ]; then + test_result_finish "FAIL" + fi + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: QPS615 is present but no runtime-provisioned Ethernet netdev was discovered" +fi + +while IFS= read -r qps_iface; do + [ -n "$qps_iface" ] || continue + ethv_failure_diagnostics_watch "$qps_iface" + qps_driver=$(ethv_get_driver "$qps_iface") + qps_module=$(ethv_get_driver_module "$qps_iface") + qps_bus=$(ethv_get_bus_info "$qps_iface") + qps_carrier=$(ethv_get_carrier "$qps_iface") + qps_operstate=$(ethv_get_operstate "$qps_iface") + qps_speed=$(ethv_get_speed "$qps_iface") + qps_duplex=$(ethv_get_duplex "$qps_iface") + qps_ipv4=$(ethv_get_ipv4 "$qps_iface") + qps_ipv6_status=0 + qps_ipv6=$(ethv_get_ipv6 "$qps_iface") || qps_ipv6_status=$? + + log_info "[QPS615-INTERFACE] interface=$qps_iface driver=${qps_driver:-unknown} module=${qps_module:-built-in-or-unexposed} bus=${qps_bus:-unknown} carrier=$qps_carrier operstate=$qps_operstate speed=${qps_speed:-unknown} duplex=${qps_duplex:-unknown} ipv4=${qps_ipv4:-none} ipv6=${qps_ipv6:-none} ipv6_discovery_status=$qps_ipv6_status" + + if [ ! -d "/sys/class/net/$qps_iface" ]; then + test_result_record "FAIL" "QPS615 interface $qps_iface disappeared during validation" + ethv_failure_diagnostics "$TESTNAME failure" + continue + fi + + case "$qps_driver" in + tc956x*) + test_result_record \ + "PASS" \ + "QPS615 interface $qps_iface is dynamically correlated and bound to $qps_driver" + ;; + *) + test_result_record \ + "FAIL" \ + "QPS615 interface $qps_iface is not bound to a TC956x driver, observed=${qps_driver:-unbound}" + ethv_failure_diagnostics "$TESTNAME failure" + ;; + esac + + if [ "$qps_carrier" = "1" ]; then + test_result_record "PASS" "QPS615 interface $qps_iface reports link carrier" + else + test_result_record \ + "SKIP" \ + "QPS615 interface $qps_iface has no carrier, connect an external peer before traffic validation" + fi + + if ethv_valid_unicast_ipv4 "$qps_ipv4"; then + test_result_record "PASS" "QPS615 interface $qps_iface has configured IPv4 address $qps_ipv4" + elif [ "$qps_ipv6_status" -eq 0 ]; then + test_result_record "PASS" "QPS615 interface $qps_iface has ready IPv6 address $qps_ipv6" + elif [ "$qps_ipv6_status" -eq 2 ]; then + test_result_record "FAIL" "QPS615 interface $qps_iface has no usable IPv4 address and IPv6 discovery failed, verify ip -6 -o addr show dev $qps_iface and image IPv6 support" + ethv_failure_diagnostics "$TESTNAME failure" + else + test_result_record \ + "SKIP" \ + "QPS615 interface $qps_iface has no ready IPv4 or IPv6 address, configure DHCP/RA/static addressing before traffic validation, IPv6 discovery requires image-provided ip" + fi + + if command -v ethtool >/dev/null 2>&1; then + qps_ethtool_driver_log="$RESULT_DIR/${qps_iface}_ethtool_driver.log" + qps_ethtool_link_log="$RESULT_DIR/${qps_iface}_ethtool_link.log" + if ethtool -i "$qps_iface" >"$qps_ethtool_driver_log" 2>&1 && + ethtool "$qps_iface" >"$qps_ethtool_link_log" 2>&1; then + log_file_with_label "QPS615-ETHTOOL-DRIVER-$qps_iface" "$qps_ethtool_driver_log" 30 + log_file_with_label "QPS615-ETHTOOL-LINK-$qps_iface" "$qps_ethtool_link_log" 50 + test_result_record \ + "PASS" \ + "QPS615 interface $qps_iface supports ethtool driver and link inspection" + else + test_result_record \ + "FAIL" \ + "Image-provided ethtool could not inspect QPS615 interface $qps_iface, see $qps_ethtool_driver_log and $qps_ethtool_link_log" + ethv_failure_diagnostics "$TESTNAME failure" + fi + else + test_result_record \ + "SKIP" \ + "ethtool is not image-provided, QPS615 sysfs readiness was validated without optional ethtool diagnostics" + fi +done <"$AVAILABLE_INTERFACES" + +export KERNEL_LOG_JOURNAL_FALLBACK=1 +scan_dmesg_errors \ + "$RESULT_DIR/kernel" \ + 'tc956|qps615' \ + 'Link is Down|Link down|carrier lost|no carrier|deferred probe|EPROBE_DEFER' +qps_dmesg_status=$? + +if [ "${DMESG_ACCESS_STATUS:-unavailable}" != "available" ]; then + test_result_record \ + "SKIP" \ + "Kernel log access is unavailable for QPS615 readiness validation, artifact=$RESULT_DIR/kernel/dmesg_access.log" +elif [ "$qps_dmesg_status" -eq 0 ]; then + log_file_with_label "QPS615-KERNEL-ERROR" "$RESULT_DIR/kernel/dmesg_errors.log" 30 + test_result_record \ + "FAIL" \ + "QPS615 or TC956x probe errors were found in $RESULT_DIR/kernel/dmesg_errors.log" + ethv_failure_diagnostics "$TESTNAME failure" +else + test_result_record "PASS" "No non-benign QPS615 or TC956x errors were found in the captured kernel log" +fi + +test_result_finish diff --git a/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/QPS615_Traffic_Validation.yaml b/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/QPS615_Traffic_Validation.yaml new file mode 100644 index 00000000..e2baa8e0 --- /dev/null +++ b/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/QPS615_Traffic_Validation.yaml @@ -0,0 +1,22 @@ +metadata: + name: qps615-traffic-validation + format: "Lava-Test Test Definition 1.0" + description: "Validate fixture-gated QPS615 Ethernet traffic, packet-counter movement, and error-counter health on dynamically correlated TC956x interfaces." + os: + - linux + scope: + - functional + +params: + QPS615_TRAFFIC_FIXTURE: "0" + QPS615_INTERFACES: "" + QPS615_PEER: "" + QPS615_PING_COUNT: "10" + QPS615_PING_WAIT: "2" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation + - ./run.sh --fixture "${QPS615_TRAFFIC_FIXTURE}" --interfaces "${QPS615_INTERFACES}" --peer "${QPS615_PEER}" --ping-count "${QPS615_PING_COUNT}" --ping-wait "${QPS615_PING_WAIT}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh QPS615_Traffic_Validation.res || true diff --git a/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/README.md b/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/README.md new file mode 100644 index 00000000..7592674c --- /dev/null +++ b/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/README.md @@ -0,0 +1,142 @@ +# QPS615 traffic validation + +`QPS615_Traffic_Validation` provides the external-fixture part of the QPS615 +bring-up procedure. It reuses runtime PCIe, firmware, device-tree, driver, and +netdev correlation from the read-only interface test, then validates packet +transfer only after explicit operator opt-in. + +The suite never assumes PCI BDFs or interface names, never installs packages, +and never changes link state, addresses, DHCP, or routes. Prepare the selected +interfaces before running it. + +The same test runs on base and overlay images without inspecting EFI or changing +packages. Confirm that runtime QPS615 DT and PCI evidence is active. Only boards +whose bring-up procedure requires a different FIT boot selection need the +optional `QPS615_Interface_Validation/run.sh --prepare-fit` preparation and a +manual reboot. FIT boot preparation is not a general traffic prerequisite. +DT discovery errors fail rather than being classified as absent hardware. + +## Fixture preparation and run + +Connect the selected QPS615 port or ports to reachable peers. Configure link +carrier and addressing through the existing image network manager, DHCP, +IPv6 router advertisements, or static configuration. The suite does not start +DHCP or modify the network configuration. + +Without `--peer`, the suite selects the interface-specific IPv4 default gateway, +then an IPv6 default gateway only when no IPv4 gateway exists. It does not use +another interface's gateway, scan neighbours/subnets, or fall back to a public +Internet address. Gateway ping validates the path to the router, not a specific +host. Once a peer is selected, a failed ping is FAIL, not a reason to try IPv6 +or a different target. + +For a DHCP-addressed host, pass its stable DNS hostname instead of a changing +IP address. The DUT resolves it afresh using image-provided `getent` and the +system NSS configuration, preferring IPv4 to IPv6. Queries use `ahostsv4`, +then `hosts` for implementations without that database, then `ahostsv6` if +needed. Each query has a 10-second deadline plus watchdog cleanup time. +The first IPv4 result is preferred within mixed output, otherwise the first +IPv6 result is selected. No attempt is made to discover arbitrary host names. +DHCP-integrated DNS or existing mDNS/NSS registration must already work. +`.local` names require configured mDNS support, they are not assumed to work +merely because the host uses DHCP. No host-side command is needed for each run, +but the peer must answer ICMP echo requests. + +```sh +cd Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation +./run.sh --fixture --peer 192.0.2.2 +./run.sh --fixture --interfaces eth1,eth2 --peer 192.0.2.2 +./run.sh --fixture --interfaces all --peer 192.0.2.2 +# Automatic interface gateway, IPv4 preferred over IPv6 +./run.sh --fixture --interfaces enP1p5s0f0 +# Stable host name with a DHCP-assigned address +./run.sh --fixture --interfaces enP1p5s0f0 --peer lab-host.example.com +# Explicit IPv6 selects IPv6 even when IPv4 is available +./run.sh --fixture --interfaces enP1p5s0f0 --peer 2001:db8::2 +./run.sh --fixture --interfaces enP1p5s0f0 --peer fe80::1 +``` + +The interface names and peers above are examples, use runtime-discovered names +and reachable lab peers. Supply IPv6 literals without brackets or `%interface`. +The selected interface provides scope for link-local IPv6. IPv6 traffic requires +image-provided `ip` and either `ping6` or an IPv6-capable `ping -6`. IPv4 retains +the existing `ping` invocation and `ifconfig` fallback for explicit peers. +IPv4-mapped IPv6 addresses are rejected, supply their ordinary IPv4 form instead. +An explicit IPv6 peer is also how to exercise IPv6 on a dual-stack fixture while +leaving the normal IPv4-first default unchanged. + +Options: + +| Option | Default | Purpose | +|---|---:|---| +| `--fixture [0\|1]` | `0` | Confirm that selected ports have reachable peers | +| `--interfaces LIST` | automatic | Select a comma-separated subset or `all` | +| `--peer IP\|HOST` | interface gateway | Override with IPv4, IPv6 or a resolvable hostname | +| `--ping-count N` | `10` | Echo requests per interface, maximum `20` | +| `--ping-wait N` | `2` | Per-request wait in seconds, maximum `5` | + +Automatic interface selection is accepted only when exactly one QPS615 netdev +exists. Multiple interfaces require `--interfaces` because software cannot +infer the lab wiring. Running without `--fixture` performs discovery and +returns an actionable `SKIP` without transmitting traffic. + +The LAVA YAML exposes the same fixture, interface, peer, count, and wait +options. Its default `QPS615_TRAFFIC_FIXTURE=0` remains non-disruptive. A lab +job must set it to `1` and supply interface or peer overrides when automatic +discovery does not match the fixture wiring. + +## Validation and result policy + +Each selected interface must have carrier, a ready address in the selected family, a valid +peer, successful interface-bound ping with zero packet loss, increasing RX and +TX packet counters, and no growth in RX, TX, CRC, or carrier-error counters. +Once `--fixture` is selected, missing hardware, tools, carrier, address, +routing, hostname resolution, or counter evidence is `FAIL` because the requested data path cannot +be proven. This keeps missing image content separate from unsupported-platform +skips. + +Loopback, unspecified, multicast and all DUT-owned peer addresses are rejected, +including secondary addresses and addresses on other DUT ports. IPv6 addresses +are normalized before comparison so different textual spellings cannot bypass +the check. Tentative or duplicate-address-failed IPv6 addresses do not establish +readiness. Missing peer discovery never produces PASS. An explicit peer on a +directly connected subnet does not require a default gateway. + +The `[QPS615-PEER]` line reports the interface, selection source, IP family, +requested peer and resolved address. Resolver stdout/stderr and DUT address +inventory are retained under the per-interface `*_peer_*` artifacts. DNS/NSS +resolution may use the system management network, but the actual ping remains +bound to the selected QPS615 interface and uses the resolved numeric address. +Echo request/reply traffic proves DUT TX/RX, not independent host-initiated +connections or throughput. Physical port-to-port loopback is not implemented +by pinging an address owned by the DUT. + +Ping runs in the C locale with its native finite count and per-request wait. +Kernel logs are captured once through the shared diagnostic helper. Evidence +is retained under `results/QPS615_Traffic_Validation/run-*/`. + +When supported by image-provided `ethtool`, the suite also captures full +`ethtool -S` output and parsed counter inventories before and after traffic, +including failed ping attempts. Shared `lib_ethernet.sh` helpers match counters +by runtime name and retain exact unsigned values, including 64-bit counters. +The comparison records unchanged, changed, added, and missing fields, with a +bounded excerpt on stdout and complete `.log`/`.tsv` artifacts. Driver-specific +gauges, resets, and unknown fields are diagnostic only, not invented failure +thresholds. Missing or unsupported driver statistics do not weaken or replace +the standard packet/error checks above. + +This focused suite closes the interface-selection gap by transmitting only on +netdevs dynamically correlated with QPS615 PCI functions. Throughput, UDP, +bidirectional, dual-port, and suspend/resume coverage remains in the existing +generic Ethernet suites. Operators should pass one of the correlated netdevs +reported by this suite to those tests rather than duplicating their data paths +here. + +## Failure diagnostics + +Recorded failures automatically save read-only `ethtool` evidence for the +selected interfaces and replay bounded excerpts to stdout with `[ETH-DIAG]` +markers. No host commands, runtime package installation or new options are +required. The original verdict and cleanup behavior remain unchanged. +See [shared Ethernet failure diagnostics](../README_Ethernet_Diagnostics.md) +for collected queries, artifact locations, time bounds and unavailable-tool handling. diff --git a/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/run.sh new file mode 100755 index 00000000..6dc6ec36 --- /dev/null +++ b/Runner/suites/Connectivity/Ethernet/QPS615_Traffic_Validation/run.sh @@ -0,0 +1,496 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# Fixture-gated QPS615 Ethernet traffic validation. + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_ethernet.sh" + +TESTNAME="QPS615_Traffic_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +QPS615_TRAFFIC_FIXTURE=0 +QPS615_INTERFACES="" +QPS615_PEER="" +QPS615_PING_COUNT=10 +QPS615_PING_WAIT=2 +QPS615_ARGUMENT_ERROR="" +SHOW_HELP=0 +RESULT_DIR="$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$$" +ethv_failure_diagnostics_init "$RESULT_DIR/failure_diagnostics" +QPS_RUNTIME_DIR="$RESULT_DIR/qps615_runtime" +AVAILABLE_INTERFACES="$RESULT_DIR/qps615_interfaces.log" +SELECTED_INTERFACES="$RESULT_DIR/selected_interfaces.log" +QPS_TRAFFIC_PASS_COUNT=0 + +# usage +# Print the command-line contract. stdout: usage text. return: 0. +# Side effects: none. +usage() { + printf '%s\n' \ + "Usage: ./run.sh [options]" \ + " --fixture [0|1] Confirm reachable external Ethernet peers" \ + " --interfaces LIST Comma-separated QPS615 netdevs, or all" \ + " --peer IP|HOST Override interface gateway with IPv4, IPv6 or hostname" \ + " --ping-count N Echo requests per interface, default 10, maximum 20" \ + " --ping-wait N Per-request wait in seconds, default 2, maximum 5" \ + " -h, --help" \ + "QPS615 topology and eligible interface names are discovered at runtime." \ + "Automatic gateway and hostname selection prefer IPv4, then IPv6." \ + "Supply IPv6 literals without a %scope, the selected interface provides scope." +} + +# parse_args +# Parse traffic and fixture policy into QPS615_* globals. stdout: none. +# return: 0 on success or 2 for invalid input. Side effects: updates globals. +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --fixture) + if [ "$#" -ge 2 ]; then + case "$2" in + 0|1) + QPS615_TRAFFIC_FIXTURE="$2" + shift 2 + continue + ;; + esac + fi + QPS615_TRAFFIC_FIXTURE=1 + shift + ;; + --interfaces) + if ! ethv_require_option_value "$1" "$#"; then + QPS615_ARGUMENT_ERROR="--interfaces requires a value" + return 2 + fi + QPS615_INTERFACES="$2" + shift 2 + ;; + --peer) + if ! ethv_require_option_value "$1" "$#"; then + QPS615_ARGUMENT_ERROR="--peer requires a value" + return 2 + fi + QPS615_PEER="$2" + shift 2 + ;; + --ping-count) + if ! ethv_require_option_value "$1" "$#"; then + QPS615_ARGUMENT_ERROR="--ping-count requires a value" + return 2 + fi + QPS615_PING_COUNT="$2" + shift 2 + ;; + --ping-wait) + if ! ethv_require_option_value "$1" "$#"; then + QPS615_ARGUMENT_ERROR="--ping-wait requires a value" + return 2 + fi + QPS615_PING_WAIT="$2" + shift 2 + ;; + -h|--help) + SHOW_HELP=1 + shift + ;; + *) + QPS615_ARGUMENT_ERROR="unknown argument: $1" + return 2 + ;; + esac + done +} + +if ! test_result_init "$TESTNAME" "$RES_FILE"; then + log_error "Could not initialize the result file: $RES_FILE" + exit 1 +fi + +if ! parse_args "$@"; then + usage >&2 + test_result_record \ + "FAIL" \ + "QPS615 traffic command-line configuration is invalid, ${QPS615_ARGUMENT_ERROR:-review ./run.sh --help}" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +if [ "$SHOW_HELP" -eq 1 ]; then + usage + exit 0 +fi + +if ! ethv_validate_qps615_traffic_config \ + "$QPS615_TRAFFIC_FIXTURE" \ + "$QPS615_INTERFACES" \ + "$QPS615_PEER" \ + "$QPS615_PING_COUNT" \ + "$QPS615_PING_WAIT"; then + test_result_record \ + "FAIL" \ + "QPS615 traffic configuration is invalid, reason=${QPS615_CONFIG_ERROR:-invalid-input} fixture=$QPS615_TRAFFIC_FIXTURE interfaces=${QPS615_INTERFACES:-auto} peer=${QPS615_PEER:-auto} ping_count=$QPS615_PING_COUNT ping_wait=$QPS615_PING_WAIT" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +if ! CHECK_DEPS_RECOVER=0 CHECK_DEPS_NO_EXIT=1 check_dependencies \ + awk \ + basename \ + date \ + dirname \ + find \ + grep \ + mkdir \ + readlink \ + sed \ + sort \ + tr \ + uname \ + wc; then + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: image-provided base utilities required for QPS615 discovery are unavailable" +fi + +if ! mkdir -p "$RESULT_DIR"; then + test_result_record "FAIL" "Could not create QPS615 traffic evidence directory $RESULT_DIR" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "Evidence directory: $RESULT_DIR" +log_info "Configuration: fixture=$QPS615_TRAFFIC_FIXTURE interfaces=${QPS615_INTERFACES:-auto-unique} peer=${QPS615_PEER:-auto-gateway} ping_count=$QPS615_PING_COUNT ping_wait=${QPS615_PING_WAIT}s" +log_info "[QPS615-POLICY] topology=dynamic interface_policy=${QPS615_INTERFACES:-auto-unique} peer_policy=${QPS615_PEER:-auto-gateway} fixture_opt_in=$QPS615_TRAFFIC_FIXTURE" + +ethv_qps615_collect_runtime "$QPS_RUNTIME_DIR" +qps_runtime_status=$? +log_file_with_label "QPS615-RUNTIME" "$QPS_RUNTIME_DIR/qps615_runtime.tsv" 60 + +case "$qps_runtime_status" in + 0) + ;; + 2) + if [ "$QPS615_TRAFFIC_FIXTURE" -eq 1 ]; then + test_result_record \ + "FAIL" \ + "QPS615 traffic fixture was selected but no enabled QPS615 DT node or switch 1179:0623 was discovered, verify board support, the active DT and PCIe enumeration on either base or overlay images" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + fi + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: no enabled QPS615 DT node or enumerated switch 1179:0623 was discovered" + ;; + *) + test_result_record \ + "FAIL" \ + "QPS615 traffic prerequisites are not ready: ${QPS615_FAILURE_REASON:-runtime discovery failed}" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + ;; +esac + +if ! ethv_qps615_list_netdevs \ + "$QPS_RUNTIME_DIR/qps615_runtime.tsv" >"$AVAILABLE_INTERFACES"; then + test_result_record \ + "FAIL" \ + "Could not parse QPS615 netdev correlation from $QPS_RUNTIME_DIR/qps615_runtime.tsv" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +available_count=$(wc -l <"$AVAILABLE_INTERFACES" | tr -d '[:space:]') +log_file_with_label "QPS615-AVAILABLE-INTERFACE" "$AVAILABLE_INTERFACES" 16 + +if [ "$available_count" -eq 0 ]; then + if [ "$QPS615_TRAFFIC_FIXTURE" -eq 1 ]; then + test_result_record \ + "FAIL" \ + "QPS615 traffic fixture was selected but no traffic-capable QPS615 netdev is exposed: ${QPS615_ETHERNET_SKIP_REASON:-no correlated netdev}" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + fi + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: QPS615 is present but exposes no traffic-capable netdev" +fi + +if [ "$QPS615_TRAFFIC_FIXTURE" -ne 1 ]; then + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: external traffic was not authorized, rerun with --fixture only when selected ports have reachable peers" +fi + +if ! command -v ping >/dev/null 2>&1 && ! command -v ping6 >/dev/null 2>&1; then + test_result_record \ + "FAIL" \ + "QPS615 traffic was explicitly requested but neither ping nor ping6 is image-provided" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +if ! command -v ip >/dev/null 2>&1 && + ! command -v ifconfig >/dev/null 2>&1; then + test_result_record \ + "FAIL" \ + "QPS615 traffic needs image-provided ip for IPv4/IPv6 discovery, IPv4 also supports ifconfig" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +if [ -z "$QPS615_PEER" ] && ! command -v ip >/dev/null 2>&1; then + test_result_record \ + "FAIL" \ + "QPS615 automatic peer discovery requires image-provided ip, or provide --peer IP|HOST" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" +fi + +: >"$SELECTED_INTERFACES" +case "$QPS615_INTERFACES" in + "") + if [ "$available_count" -ne 1 ]; then + test_result_record \ + "FAIL" \ + "QPS615 exposes $available_count netdevs and the fixture was selected, identify fixture-connected ports with --interfaces" + ethv_failure_diagnostics "$TESTNAME failure" + test_result_finish "FAIL" + fi + sed -n '1p' "$AVAILABLE_INTERFACES" >"$SELECTED_INTERFACES" + selection_source="auto-unique" + ;; + all) + awk 'NF { print }' "$AVAILABLE_INTERFACES" >"$SELECTED_INTERFACES" + selection_source="explicit-all" + ;; + *) + printf '%s\n' "$QPS615_INTERFACES" \ + | tr ',' '\n' \ + | sort -u >"$SELECTED_INTERFACES" + selection_source="explicit-list" + ;; +esac + +log_info "[QPS615-SELECTION] source=$selection_source count=$(wc -l <"$SELECTED_INTERFACES" | tr -d '[:space:]') artifact=$SELECTED_INTERFACES" +log_file_with_label "QPS615-SELECTED-INTERFACE" "$SELECTED_INTERFACES" 16 + +while IFS= read -r qps_iface; do + [ -n "$qps_iface" ] || continue + ethv_failure_diagnostics_watch "$qps_iface" + + if ! grep -Fxq "$qps_iface" "$AVAILABLE_INTERFACES"; then + test_result_record \ + "FAIL" \ + "Requested interface $qps_iface is not correlated with a QPS615 Ethernet function" + ethv_failure_diagnostics "$TESTNAME failure" + continue + fi + + if [ ! -d "/sys/class/net/$qps_iface" ]; then + test_result_record "FAIL" "QPS615 interface $qps_iface disappeared before traffic validation" + ethv_failure_diagnostics "$TESTNAME failure" + continue + fi + + qps_carrier=$(ethv_get_carrier "$qps_iface") + qps_ipv4=$(ethv_get_ipv4 "$qps_iface") + log_info "[QPS615-TRAFFIC] interface=$qps_iface selection=$selection_source carrier=${qps_carrier:-unknown} ipv4=${qps_ipv4:-none} requested_peer=${QPS615_PEER:-auto-gateway}" + + if [ "$qps_carrier" != "1" ]; then + test_result_record \ + "FAIL" \ + "QPS615 interface $qps_iface has no carrier although the external fixture was confirmed" + ethv_failure_diagnostics "$TESTNAME failure" + continue + fi + if ! ethv_resolve_traffic_peer "$qps_iface" "$QPS615_PEER" "$RESULT_DIR/${qps_iface}_peer"; then + test_result_record \ + "FAIL" \ + "QPS615 peer discovery failed on $qps_iface: $ETHV_PEER_REASON" + ethv_failure_diagnostics "$TESTNAME failure" + continue + fi + qps_target="$ETHV_PEER_ADDRESS" + if [ "$ETHV_PEER_FAMILY" = 4 ] && ! ethv_valid_unicast_ipv4 "$qps_ipv4"; then + test_result_record "FAIL" "QPS615 interface $qps_iface has no usable IPv4 address for selected peer $qps_target, configure DHCP or static addressing" + ethv_failure_diagnostics "$TESTNAME failure" + continue + fi + log_info "[QPS615-PEER] interface=$qps_iface source=$ETHV_PEER_SOURCE family=IPv$ETHV_PEER_FAMILY requested=${QPS615_PEER:-auto} resolved_address=$qps_target artifact_prefix=$RESULT_DIR/${qps_iface}_peer" + + if ! ethv_counters_available \ + "$qps_iface" \ + rx_packets \ + tx_packets \ + rx_errors \ + tx_errors \ + rx_crc_errors \ + tx_carrier_errors; then + test_result_record \ + "FAIL" \ + "QPS615 interface $qps_iface does not expose usable $ETHV_COUNTER_FAILURE statistics before traffic" + ethv_failure_diagnostics "$TESTNAME failure" + continue + fi + + before_rx=$(ethv_get_counter "$qps_iface" rx_packets) + before_tx=$(ethv_get_counter "$qps_iface" tx_packets) + before_rx_errors=$(ethv_get_counter "$qps_iface" rx_errors) + before_tx_errors=$(ethv_get_counter "$qps_iface" tx_errors) + before_crc=$(ethv_get_counter "$qps_iface" rx_crc_errors) + before_carrier_errors=$(ethv_get_counter "$qps_iface" tx_carrier_errors) + + qps_driver_stats_status=0 + ethv_capture_driver_statistics "$qps_iface" "$RESULT_DIR/${qps_iface}_driver_before" || qps_driver_stats_status=$? + log_info "$ETHV_DRIVER_STATS_REASON" + + ping_log="$RESULT_DIR/${qps_iface}_ping.log" + if ethv_ping_interface \ + "$qps_iface" \ + "$qps_target" \ + "$QPS615_PING_COUNT" \ + "$QPS615_PING_WAIT" \ + "$ETHV_PEER_FAMILY" >"$ping_log" 2>&1; then + ping_status=0 + else + ping_status=$? + fi + + if [ "$qps_driver_stats_status" -eq 0 ]; then + if ethv_capture_driver_statistics "$qps_iface" "$RESULT_DIR/${qps_iface}_driver_after" && + ethv_compare_driver_statistics \ + "$RESULT_DIR/${qps_iface}_driver_before.tsv" \ + "$RESULT_DIR/${qps_iface}_driver_after.tsv" \ + "$RESULT_DIR/${qps_iface}_driver_changes.tsv"; then + log_file_with_label "QPS615-DRIVER-COUNTERS-$qps_iface" "$RESULT_DIR/${qps_iface}_driver_changes.tsv" 60 + else + log_warn "Optional driver-counter comparison is unavailable: $ETHV_DRIVER_STATS_REASON" + fi + fi + + if ! ethv_counters_available \ + "$qps_iface" \ + rx_packets \ + tx_packets \ + rx_errors \ + tx_errors \ + rx_crc_errors \ + tx_carrier_errors; then + log_file_with_label "QPS615-PING-$qps_iface" "$ping_log" 20 + test_result_record \ + "FAIL" \ + "QPS615 interface $qps_iface lost usable $ETHV_COUNTER_FAILURE statistics after traffic" + ethv_failure_diagnostics "$TESTNAME failure" + continue + fi + + after_rx=$(ethv_get_counter "$qps_iface" rx_packets) + after_tx=$(ethv_get_counter "$qps_iface" tx_packets) + after_rx_errors=$(ethv_get_counter "$qps_iface" rx_errors) + after_tx_errors=$(ethv_get_counter "$qps_iface" tx_errors) + after_crc=$(ethv_get_counter "$qps_iface" rx_crc_errors) + after_carrier_errors=$(ethv_get_counter "$qps_iface" tx_carrier_errors) + rx_delta=$(ethv_counter_delta "$before_rx" "$after_rx") + tx_delta=$(ethv_counter_delta "$before_tx" "$after_tx") + rx_error_delta=$(ethv_counter_delta "$before_rx_errors" "$after_rx_errors") + tx_error_delta=$(ethv_counter_delta "$before_tx_errors" "$after_tx_errors") + crc_delta=$(ethv_counter_delta "$before_crc" "$after_crc") + carrier_error_delta=$(ethv_counter_delta "$before_carrier_errors" "$after_carrier_errors") + + log_info "[QPS615-COUNTERS] interface=$qps_iface rx_packets_before=$before_rx rx_packets_after=$after_rx rx_packets_delta=$rx_delta tx_packets_before=$before_tx tx_packets_after=$after_tx tx_packets_delta=$tx_delta rx_errors_delta=$rx_error_delta tx_errors_delta=$tx_error_delta rx_crc_delta=$crc_delta tx_carrier_delta=$carrier_error_delta artifact=$ping_log" + log_file_with_label "QPS615-PING-$qps_iface" "$ping_log" 20 + + if [ "$ping_status" -ne 0 ]; then + test_result_record \ + "FAIL" \ + "QPS615 traffic to $qps_target failed on $qps_iface, rc=$ping_status artifact=$ping_log" + ethv_failure_diagnostics "$TESTNAME failure" + elif ! grep -Eq '(^|[[:space:],])0% packet loss' "$ping_log"; then + test_result_record \ + "FAIL" \ + "QPS615 traffic to $qps_target completed with packet loss on $qps_iface, artifact=$ping_log" + ethv_failure_diagnostics "$TESTNAME failure" + elif [ "$rx_delta" -le 0 ] || [ "$tx_delta" -le 0 ]; then + test_result_record \ + "FAIL" \ + "QPS615 ping succeeded on $qps_iface but counters did not prove bidirectional traffic, rx_delta=$rx_delta tx_delta=$tx_delta" + ethv_failure_diagnostics "$TESTNAME failure" + elif [ "$rx_error_delta" -ne 0 ] || [ "$tx_error_delta" -ne 0 ] || + [ "$crc_delta" -ne 0 ] || [ "$carrier_error_delta" -ne 0 ]; then + test_result_record \ + "FAIL" \ + "QPS615 traffic increased hardware error counters on $qps_iface" + ethv_failure_diagnostics "$TESTNAME failure" + else + test_result_record \ + "PASS" \ + "QPS615 interface $qps_iface transferred bidirectional traffic to $qps_target without packet loss or error-counter growth" + QPS_TRAFFIC_PASS_COUNT=$((QPS_TRAFFIC_PASS_COUNT + 1)) + fi +done <"$SELECTED_INTERFACES" + +export KERNEL_LOG_JOURNAL_FALLBACK=1 +scan_dmesg_errors \ + "$RESULT_DIR/kernel" \ + 'tc956|qps615|stmmac|phylink|mdio' \ + 'Link is Down|Link down|carrier lost|no carrier|deferred probe|EPROBE_DEFER' +qps_dmesg_status=$? + +if [ "${DMESG_ACCESS_STATUS:-unavailable}" != "available" ]; then + test_result_record \ + "SKIP" \ + "Kernel log access is unavailable for QPS615 traffic health validation, artifact=$RESULT_DIR/kernel/dmesg_access.log" +elif [ "$qps_dmesg_status" -eq 0 ]; then + log_file_with_label "QPS615-KERNEL-ERROR" "$RESULT_DIR/kernel/dmesg_errors.log" 30 + test_result_record \ + "FAIL" \ + "QPS615 or Ethernet kernel errors were detected after traffic, artifact=$RESULT_DIR/kernel/dmesg_errors.log" + ethv_failure_diagnostics "$TESTNAME failure" +else + test_result_record "PASS" "No QPS615 traffic-related kernel errors were found" +fi + +if [ "$TEST_RESULT_FAIL_COUNT" -eq 0 ] && [ "$QPS_TRAFFIC_PASS_COUNT" -eq 0 ]; then + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP: no selected QPS615 interface completed a traffic transfer" +fi + +test_result_finish diff --git a/Runner/suites/Connectivity/Ethernet/README_Ethernet_Diagnostics.md b/Runner/suites/Connectivity/Ethernet/README_Ethernet_Diagnostics.md new file mode 100644 index 00000000..3a153cbd --- /dev/null +++ b/Runner/suites/Connectivity/Ethernet/README_Ethernet_Diagnostics.md @@ -0,0 +1,63 @@ +# Ethernet failure diagnostics + +All Ethernet suites, including QPS615 interface and traffic validation, capture +read-only `ethtool` evidence on recorded failures. The shared implementation in +`Runner/utils/lib_ethernet.sh` uses the same image-provided tools on Yocto, +Debian, Ubuntu, CentOS and other distributions. No package is installed and no +new command-line, environment or YAML option is required. + +Evidence is retained under +`results//run--/failure_diagnostics/` in the suite directory. +Each selected interface has individual query logs and a `status.tsv` containing +the query, diagnostic status, command exit status and artifact path. A missing +tool or failure before any interface selection is explained in a top-level log. +The collector never guesses an unrelated management interface to interrogate. + +The `[ETH-DIAG]` stdout messages identify the interface, command, status and file. +Up to 80 lines from each query are replayed with the shared logging helper, +including an omitted-line count and full artifact path when truncated. +Repeated failures reuse the first capture for each interface. This avoids +repeatedly interrogating an unhealthy device and preserves the first snapshot. + +## Captured information + +| Query | Evidence | +|---|---| +| `ethtool IFACE` | Link state, speed, duplex, autonegotiation and advertised/partner modes | +| `ethtool -i IFACE` | Driver, firmware, driver version and bus information | +| `ethtool -S IFACE` | Driver packet, queue and error counters | +| `ethtool --phy-statistics IFACE` | PHY-specific counters when supported | +| `ethtool -k IFACE` | Checksum, segmentation and other offload features | +| `ethtool -g IFACE` | RX/TX ring sizes and limits | +| `ethtool -l IFACE` | Channel counts and limits | +| `ethtool -c IFACE` | Interrupt coalescing configuration | +| `ethtool -a IFACE` | Pause-frame and flow-control configuration | +| `ethtool -T IFACE` | Timestamping and PTP clock capabilities | +| `ethtool --show-eee IFACE` | Energy Efficient Ethernet state | +| `ethtool --show-fec IFACE` | Forward error correction configuration | +| `ethtool --show-priv-flags IFACE` | Driver-private flag names and current values | + +Unsupported options, driver limitations and permission failures retain their +exit status and output as `unavailable-or-error`. Diagnostic collection never +changes PASS/FAIL/SKIP, starts traffic, or changes the existing cleanup traps. +PASS and SKIP do not trigger this collector. Existing functional queries in +individual suites remain unchanged. + +Each query has a three-second deadline through the shared managed timeout. +After a timeout, remaining queries on that interface are skipped. A cumulative +60-second command-time budget bounds collection across interfaces. The final +query and watchdog cleanup can exceed that budget by up to five seconds, +excluding filesystem and stdout replay time. Skipped queries are listed in +`status.tsv` rather than silently omitted. + +The base query can expose the Wake-on-LAN SecureOn password. It is redacted before +any output is saved or replayed. Register/EEPROM/module dumps are not collected. +The collector never uses self-tests, cable tests, PHY identification, resets, +renegotiation or setting operations such as `-t`, `-p`, `-r`, `-s`, `-K`, `-G`, +`-L`, `-C`, `-A`, `--set-*` or `--reset`. These may disrupt the fixture or alter +the evidence needed to diagnose the original failure. + +## Public source references + +- [ethtool command manual](https://github.com/Distrotech/ethtool/blob/master/ethtool.8.in) +- [Linux ethtool netlink documentation](https://github.com/torvalds/linux/blob/master/Documentation/networking/ethtool-netlink.rst), covering link state, features, rings, channels, coalescing, pause, EEE, FEC, timestamps and statistics. Its WOL section also documents the confidential SecureOn field. diff --git a/Runner/suites/Kernel/Baseport/Module_Reload_Validation/Module_Reload_Validation_README.md b/Runner/suites/Kernel/Baseport/Module_Reload_Validation/Module_Reload_Validation_README.md index a97c4362..aeca4366 100644 --- a/Runner/suites/Kernel/Baseport/Module_Reload_Validation/Module_Reload_Validation_README.md +++ b/Runner/suites/Kernel/Baseport/Module_Reload_Validation/Module_Reload_Validation_README.md @@ -356,6 +356,39 @@ tc956x_pcie_eth This is useful on RB3Gen2-style platforms where Ethernet is provided by the TC956x PCIe Ethernet driver instead of `dwmac_qcom_eth`. +The existing `basic` mode continues to unload/reload the module. For QPS615 +standalone load/readiness validation on either a base or overlay image, use: + +```sh +./run.sh --module tc956x_pcie_eth --mode load_only +``` + +The profile name selects the workflow, not the module to insert. `load_only` +discovers QPS615 endpoints from runtime DT/PCI topology, resolves the module +through their `driver/module` links or running-kernel modalias database, and +captures `modinfo`. It does not change EFI selection, install packages, force +PCI binding, configure an IP address, or transmit traffic. Ethernet ports must +already be configured in runtime DT or bound by the running image. + +An initially loaded or built-in module is left untouched. Otherwise this mode +requires root, image-provided `modprobe` and `rmmod`, loads the discovered module, +and waits for correlated driver/netdev readiness. It then removes only the +target it loaded with non-forced `rmmod`. Dependency modules are preserved. +Use a maintenance window because removal takes newly created netdevs away. + +Missing QPS615 hardware or unconfigured Ethernet ports produce `SKIP`. Declared +but missing topology, ambiguous module metadata, failed load/binding, captured +kernel errors, and restoration failures produce `FAIL`. A loaded module alone +is never sufficient to pass. Carrier and traffic belong to the Ethernet suites. + +This mode performs one load/readiness check regardless of `--iterations`. +`--timeout-load` bounds module commands and `--timeout-settle` bounds readiness +polling. Evidence is retained in `results/Module_Reload_Validation/tc956x_pcie_eth/load_only/run-*/`, +including before/after runtime inventories and module command logs. Kernel +snapshot/error logs are captured after successful binding. The existing YAML +`PROFILE=tc956x_pcie_eth` and `MODE=load_only` parameters select the same path. +Default mode and other profiles are unchanged. + ### `ath11k_pci.profile` ath11k PCI Wi-Fi reload validation. diff --git a/Runner/suites/Kernel/Baseport/Module_Reload_Validation/profiles/tc956x_pcie_eth.profile b/Runner/suites/Kernel/Baseport/Module_Reload_Validation/profiles/tc956x_pcie_eth.profile index 660bbdf3..c681404c 100755 --- a/Runner/suites/Kernel/Baseport/Module_Reload_Validation/profiles/tc956x_pcie_eth.profile +++ b/Runner/suites/Kernel/Baseport/Module_Reload_Validation/profiles/tc956x_pcie_eth.profile @@ -1,5 +1,8 @@ #!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause +# Profile variables are consumed by the shared module-reload engine. +# shellcheck disable=SC2034 PROFILE_NAME="tc956x_pcie_eth" PROFILE_DESCRIPTION="TC956x PCIe Ethernet module reload validation" @@ -16,3 +19,12 @@ MODULE_LOAD_CMD="modprobe tc956x_pcie_eth" PROFILE_EXPECT_ABSENT_AFTER_UNLOAD="tc956x_pcie_eth" PROFILE_DEVICE_PATTERNS="/sys/class/net/*" PROFILE_SYSFS_PATTERNS="/sys/module/tc956x_pcie_eth" + +# profile_load_only +# Opt-in dynamic module load/readiness validation with target-only restoration. +# stdout: diagnostics. return: 0 ready, 1 failed, 2 unavailable. +profile_load_only() { + # shellcheck disable=SC1091 + . "$TOOLS/lib_ethernet.sh" + ethv_qps615_validate_module_load "$1" "$TIMEOUT_LOAD" "$TIMEOUT_SETTLE" +} diff --git a/Runner/suites/Kernel/Baseport/PCIe/README.md b/Runner/suites/Kernel/Baseport/PCIe/README.md index aec80452..083da86f 100644 --- a/Runner/suites/Kernel/Baseport/PCIe/README.md +++ b/Runner/suites/Kernel/Baseport/PCIe/README.md @@ -20,7 +20,7 @@ Unused PCIe bridge ports may legitimately report zero negotiated width. They are identified as inactive bridge ports, while a zero-width non-bridge endpoint remains a failure. -These checks help confirm that the PCIe root port is properly initialized and ready for use +These checks help confirm that the PCIe root port is properly initialized and ready for use When an enumerated Toshiba `1179:0623` switch identifies QPS615 hardware, the suite also validates the discovered downstream PCIe topology, TC956x Ethernet @@ -30,6 +30,18 @@ diagnosis but are owned by `Ethernet_Inventory_Validation`, so an Ethernet driver packaging or binding failure does not incorrectly fail the generic PCIe contract. Systems without QPS615 continue through the existing generic checks. +For enabled runtime DT nodes compatible with `pci1179,0623`, the PCIe suite +also validates the public supply-backed `pwrctrl-tc9563` platform-driver +contract. Nodes without supply properties remain on the legacy PCI runtime +path, while a partial six-supply set or an unbound supply-backed node fails. +Use `QPS615_Interface_Validation/run.sh --prepare-fit` only when a target +requires `VendorDtbOverlays=staging` before QPS615 can appear at runtime. +Normal QPS615 validation works on both base and overlay images without EFI +inspection. FIT boot selection is separate from image package configuration. +QPS615 diagnostics count outermost switch roots separately from all +`1179:0623` bridge functions. One upstream bridge and its three downstream +bridges report `switches=1 bridge_functions=4`. + The split follows the documented startup sequence. The QPS615 switch power, I2C initialization, firmware, PCIe link, and bridge enumeration establish the PCIe capability. The downstream `1179:0220` functions, `tc956x_pci-eth` driver @@ -60,7 +72,7 @@ cd Runner ## Prerequisites -1. `lspci` must be available on the target device +1. `lspci` must be available on the target device 2. PCIe interface must be exposed and initialized 3. Root access may be required depending on system configuration @@ -79,6 +91,7 @@ On QPS615 systems, inspect the retained topology evidence: ```sh cat qps615_runtime/qps615_switches.log cat qps615_runtime/qps615_runtime.tsv +cat qps615_runtime/qps615_dt_platform.tsv find /lib/firmware /usr/lib/firmware \ -name 'TC956X_Firmware_PCIeBridge.bin*' -print 2>/dev/null ``` diff --git a/Runner/suites/Kernel/Baseport/PCIe/run.sh b/Runner/suites/Kernel/Baseport/PCIe/run.sh index 23dbf6c8..983e8176 100755 --- a/Runner/suites/Kernel/Baseport/PCIe/run.sh +++ b/Runner/suites/Kernel/Baseport/PCIe/run.sh @@ -152,7 +152,17 @@ case "$qps615_status" in else test_result_record \ "PASS" \ - "QPS615 PCIe topology and firmware are healthy: bridge_functions=$QPS615_SWITCH_COUNT downstream=$QPS615_DOWNSTREAM_COUNT ethernet_devices=$QPS615_ETHERNET_DEVICE_COUNT firmware=$QPS615_FIRMWARE_PATH" + "QPS615 PCIe topology and firmware are healthy: switches=$QPS615_SWITCH_COUNT bridge_functions=$QPS615_BRIDGE_COUNT downstream=$QPS615_DOWNSTREAM_COUNT ethernet_devices=$QPS615_ETHERNET_DEVICE_COUNT firmware=$QPS615_FIRMWARE_PATH" + fi + + if [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + test_result_record \ + "FAIL" \ + "QPS615 PCIe power-control validation failed: $QPS615_PWRCTRL_FAILURE_REASON" + elif [ "$QPS615_PWRCTRL_EXPECTED_COUNT" -gt 0 ]; then + test_result_record \ + "PASS" \ + "All $QPS615_PWRCTRL_BOUND_COUNT supply-backed QPS615 DT node(s) are bound to pwrctrl-tc9563" fi if [ -n "$QPS615_ETHERNET_FAILURE_REASON" ]; then diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index 5050b592..25f8ec7e 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -3777,10 +3777,12 @@ dt_has_remoteproc_fw() { } ############################################################################### -# dt_list_compatible_nodes [fixed|regex] +# dt_list_compatible_nodes [fixed|regex] [dt-root] # Prints one enabled device-tree node directory per line from both standard DT # roots. The default mode performs a fixed substring match. Returns 0 when at -# least one node is found, 1 when none are found, and 3 for invalid arguments. +# least one node is found, 1 when none are found, 2 on discovery failure, and +# 3 for invalid arguments. Discovery failures are not proof of absent hardware. +# An explicit root limits discovery to that tree, useful for offline DT fixtures. ############################################################################### dt_list_compatible_nodes() { dtlc_pattern="$1" @@ -3795,7 +3797,7 @@ dt_list_compatible_nodes() { esac dtlc_found=0 - if [ -r "${DT_COMPATIBLE_INDEX:-}" ]; then + if [ -z "${3:-}" ] && [ -r "${DT_COMPATIBLE_INDEX:-}" ]; then if [ "$dtlc_mode" = "regex" ]; then awk -F '|' -v pattern="$dtlc_pattern" '$2 ~ pattern { print $1; found=1 } END { exit !found }' "$DT_COMPATIBLE_INDEX" else @@ -3804,18 +3806,32 @@ dt_list_compatible_nodes() { return $? fi dtlc_previous_root="" - for dtlc_root in /proc/device-tree /sys/firmware/devicetree/base; do + for dtlc_source in primary secondary; do + case "$dtlc_source" in + primary) + dtlc_root="${3:-/proc/device-tree}" + ;; + secondary) + [ -z "${3:-}" ] || continue + dtlc_root="/sys/firmware/devicetree/base" + ;; + esac [ -d "$dtlc_root" ] || continue dtlc_resolved_root=$(readlink -f "$dtlc_root" 2>/dev/null || true) [ -n "$dtlc_resolved_root" ] || dtlc_resolved_root="$dtlc_root" [ "$dtlc_resolved_root" = "$dtlc_previous_root" ] && continue dtlc_previous_root="$dtlc_resolved_root" - dtlc_matches_file=$(mktemp "${TMPDIR:-/tmp}/dt-compatible.XXXXXX") || return 1 + dtlc_matches_file=$(mktemp "${TMPDIR:-/tmp}/dt-compatible.XXXXXX") || return 2 + dtlc_scan_status=0 if [ "$dtlc_mode" = "regex" ]; then - find "$dtlc_resolved_root" -type f -name compatible 2>/dev/null >"$dtlc_matches_file" + find "$dtlc_resolved_root" -type f -name compatible >"$dtlc_matches_file" || dtlc_scan_status=2 else - grep -r -l -a -F "$dtlc_pattern" "$dtlc_resolved_root" 2>/dev/null >"$dtlc_matches_file" || true + grep -r -l -a -F "$dtlc_pattern" "$dtlc_resolved_root" >"$dtlc_matches_file" || dtlc_scan_status=$? + fi + if [ "$dtlc_scan_status" -gt 1 ]; then + rm -f "$dtlc_matches_file" + return 2 fi while IFS= read -r dtlc_compatible_file; do @@ -3828,13 +3844,23 @@ dt_list_compatible_nodes() { ;; esac if [ "$dtlc_mode" = "regex" ]; then - dtlc_text=$(dt_property_text "$dtlc_node_dir" compatible 2>/dev/null || true) - printf '%s\n' "$dtlc_text" | grep -Eq "$dtlc_pattern" || continue + if ! dtlc_text=$(dt_property_text "$dtlc_node_dir" compatible); then + dtlc_scan_status=2 + break + fi + dtlc_match_status=0 + printf '%s\n' "$dtlc_text" | grep -Eq "$dtlc_pattern" || dtlc_match_status=$? + if [ "$dtlc_match_status" -gt 1 ]; then + dtlc_scan_status=2 + break + fi + [ "$dtlc_match_status" -eq 0 ] || continue fi readlink -f "$dtlc_node_dir" 2>/dev/null || printf '%s\n' "$dtlc_node_dir" dtlc_found=1 done <"$dtlc_matches_file" rm -f "$dtlc_matches_file" + [ "$dtlc_scan_status" -le 1 ] || return 2 done [ "$dtlc_found" -eq 1 ] diff --git a/Runner/utils/lib_ethernet.sh b/Runner/utils/lib_ethernet.sh index b46c5f35..42a353cf 100755 --- a/Runner/utils/lib_ethernet.sh +++ b/Runner/utils/lib_ethernet.sh @@ -3,6 +3,176 @@ # SPDX-License-Identifier: BSD-3-Clause # Shared helpers for Ethernet functional validation. +QPS615_OVERLAY_NAME="VendorDtbOverlays" +QPS615_OVERLAY_VALUE="staging" +# QPS615 bring-up firmware namespace, used only for explicit first-time setup. +QPS615_OVERLAY_DEFAULT_GUID="882f8c2b-9646-435f-8de5-f208ff80c1bd" + +# ethv_failure_diagnostics_init +# Opt this process into Ethernet failure evidence without creating files. +# stdout: none. return: 0. Side effects: resets private diagnostic state. +ethv_failure_diagnostics_init() { + ETHV_FAILURE_DIR="$1" + ETHV_FAILURE_INTERFACES="" +} + +# ethv_failure_diagnostics_watch ... +# Register selected runtime interfaces, not unrelated management interfaces. +# stdout: none. return: 0. Side effects: accumulates safe netdev names. +ethv_failure_diagnostics_watch() { + for ethv_fdw_iface in "$@"; do + case "$ethv_fdw_iface" in + ""|.|..|-*|*[!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:-]*) + continue + ;; + esac + case " ${ETHV_FAILURE_INTERFACES:-} " in + *" $ethv_fdw_iface "*) + ;; + *) + ETHV_FAILURE_INTERFACES="${ETHV_FAILURE_INTERFACES:+$ETHV_FAILURE_INTERFACES }$ethv_fdw_iface" + ;; + esac + done +} + +# ethv_failure_diagnostics [reason] +# Save read-only ethtool failure evidence once per watched interface and print +# bounded excerpts. Three-second query deadlines, a 60-second cumulative +# command-time budget per suite, and no further queries after a device timeout. +# stdout: diagnostic output. return: always 0, never changes a test verdict. +# Side effects: retained per-query logs and status.tsv under ETHV_FAILURE_DIR. +# Subshell isolation preserves caller variables, umask and cleanup traps. +ethv_failure_diagnostics() ( + [ -n "${ETHV_FAILURE_DIR:-}" ] || exit 0 + umask 077 + if ! mkdir -p "$ETHV_FAILURE_DIR"; then + log_warn "[ETH-DIAG] Cannot create $ETHV_FAILURE_DIR, original failure is unchanged" + exit 0 + fi + if [ ! -f "$ETHV_FAILURE_DIR/context.log" ]; then + printf 'reason=%s\ninterfaces=%s\n' "${1:-Ethernet test failure}" \ + "${ETHV_FAILURE_INTERFACES:-none selected}" >"$ETHV_FAILURE_DIR/context.log" + log_info "[ETH-DIAG] Failure evidence: $ETHV_FAILURE_DIR" + log_file_with_label ETH-DIAG "$ETHV_FAILURE_DIR/context.log" 10 + fi + if [ -z "${ETHV_FAILURE_INTERFACES:-}" ]; then + if [ ! -f "$ETHV_FAILURE_DIR/no-interface.log" ]; then + printf '%s\n' 'No tested interface was selected before failure, no ethtool queries were run.' \ + >"$ETHV_FAILURE_DIR/no-interface.log" + log_file_with_label ETH-DIAG "$ETHV_FAILURE_DIR/no-interface.log" 10 + fi + exit 0 + fi + if ! command -v ethtool >/dev/null 2>&1; then + if [ ! -f "$ETHV_FAILURE_DIR/unavailable.log" ]; then + printf '%s\n' 'ethtool is not image-provided, provision it in the image for Ethernet failure diagnostics.' \ + >"$ETHV_FAILURE_DIR/unavailable.log" + log_file_with_label ETH-DIAG "$ETHV_FAILURE_DIR/unavailable.log" 10 + fi + exit 0 + fi + + ethv_fd_spent=0 + if [ -r "$ETHV_FAILURE_DIR/command-seconds" ]; then + read -r ethv_fd_spent <"$ETHV_FAILURE_DIR/command-seconds" + fi + case "$ethv_fd_spent" in + ''|*[!0-9]*) + log_warn "[ETH-DIAG] Invalid diagnostic budget state, no additional queries will run" + exit 0 + ;; + esac + for ethv_fd_iface in $ETHV_FAILURE_INTERFACES; do + ethv_fd_dir="$ETHV_FAILURE_DIR/$ethv_fd_iface" + [ ! -f "$ethv_fd_dir/complete" ] || continue + if ! mkdir -p "$ethv_fd_dir"; then + log_warn "[ETH-DIAG] Cannot create interface evidence directory $ethv_fd_dir" + continue + fi + printf 'query\tstatus\texit_status\tartifact\n' >"$ethv_fd_dir/status.tsv" + ethv_fd_stopped=0 + for ethv_fd_query in link driver statistics phy-statistics features rings channels coalesce pause timestamping eee fec private-flags; do + case "$ethv_fd_query" in + link) + set -- ethtool "$ethv_fd_iface" + ;; + driver) + set -- ethtool -i "$ethv_fd_iface" + ;; + statistics) + set -- ethtool -S "$ethv_fd_iface" + ;; + phy-statistics) + set -- ethtool --phy-statistics "$ethv_fd_iface" + ;; + features) + set -- ethtool -k "$ethv_fd_iface" + ;; + rings) + set -- ethtool -g "$ethv_fd_iface" + ;; + channels) + set -- ethtool -l "$ethv_fd_iface" + ;; + coalesce) + set -- ethtool -c "$ethv_fd_iface" + ;; + pause) + set -- ethtool -a "$ethv_fd_iface" + ;; + timestamping) + set -- ethtool -T "$ethv_fd_iface" + ;; + eee) + set -- ethtool --show-eee "$ethv_fd_iface" + ;; + fec) + set -- ethtool --show-fec "$ethv_fd_iface" + ;; + private-flags) + set -- ethtool --show-priv-flags "$ethv_fd_iface" + ;; + esac + ethv_fd_log="$ethv_fd_dir/$ethv_fd_query.log" + if [ "$ethv_fd_stopped" -eq 1 ] || [ "$ethv_fd_spent" -ge 60 ]; then + printf '%s\n' 'Not queried because the device timed out or the suite diagnostic command-time budget was exhausted.' >"$ethv_fd_log" + printf '%s\tskipped\t-\t%s\n' "$ethv_fd_query" "$ethv_fd_log" >>"$ethv_fd_dir/status.tsv" + continue + fi + ethv_fd_start=$(date +%s) || break + ethv_fd_rc=0 + ethv_fd_output=$(LC_ALL=C run_with_managed_timeout 3 "$ethv_fd_dir" ethtool-diagnostic "$@" 2>&1) || ethv_fd_rc=$? + ethv_fd_end=$(date +%s) || ethv_fd_end=$((ethv_fd_start + 5)) + ethv_fd_elapsed=$((ethv_fd_end - ethv_fd_start)) + [ "$ethv_fd_elapsed" -ge 0 ] || ethv_fd_elapsed=5 + ethv_fd_spent=$((ethv_fd_spent + ethv_fd_elapsed)) + printf '%s\n' "$ethv_fd_spent" >"$ETHV_FAILURE_DIR/command-seconds" + # Never persist or print the SecureOn password returned by a base + # ethtool query. Raw output is held only in this subshell variable. + if ! printf '%s\n' "$ethv_fd_output" | sed \ + -e 's/[Ss]ecure[Oo]n password:.*/SecureOn password: [redacted]/' \ + -e 's/sopass .*/sopass [redacted]/' >"$ethv_fd_log"; then + log_warn "[ETH-DIAG] Could not save redacted $ethv_fd_query output for $ethv_fd_iface" + continue + fi + ethv_fd_status=collected + if [ "$ethv_fd_rc" -eq 124 ]; then + ethv_fd_status=timeout + ethv_fd_stopped=1 + elif [ "$ethv_fd_rc" -ne 0 ]; then + ethv_fd_status=unavailable-or-error + fi + printf '%s\t%s\t%s\t%s\n' "$ethv_fd_query" "$ethv_fd_status" "$ethv_fd_rc" "$ethv_fd_log" >>"$ethv_fd_dir/status.tsv" + log_info "[ETH-DIAG] interface=$ethv_fd_iface query=$ethv_fd_query command=$* status=$ethv_fd_status rc=$ethv_fd_rc artifact=$ethv_fd_log" + log_file_with_label "ETH-DIAG-$ethv_fd_iface-$ethv_fd_query" "$ethv_fd_log" 80 + done + log_file_with_label "ETH-DIAG-$ethv_fd_iface-SUMMARY" "$ethv_fd_dir/status.tsv" 20 + : >"$ethv_fd_dir/complete" + done + exit 0 +) + # ethv_sanitize_text # Normalize text for a single-line, pipe-delimited result record. # stdout: sanitized text with repeated whitespace collapsed. @@ -44,6 +214,9 @@ ethv_record_result() { ;; FAIL) log_fail "$ethv_rr_check: $ethv_rr_details" + ethv_rr_log_status=$? + ethv_failure_diagnostics "$ethv_rr_check: $ethv_rr_details" + return "$ethv_rr_log_status" ;; SKIP) log_skip "$ethv_rr_check: $ethv_rr_details" @@ -258,11 +431,395 @@ ethv_get_physical_interfaces() { done } +# ethv_qps615_collect_overlay_state +# Inspect the dynamically discovered VendorDtbOverlays EFI variable for the +# QPS615 FIT compatibility value without changing target state. stdout: +# diagnostics, returned state is exported through QPS615_OVERLAY_*. +# return: 0 when staging is selected, 1 when absent or set to another value, +# 2 when EFI inspection is unavailable, and 3 for invalid arguments. Side +# effects: exports QPS615_OVERLAY_* state and refreshes retained evidence. +ethv_qps615_collect_overlay_state() { + ethv_qos_result_dir="${1:-}" + ethv_qos_list_log="$ethv_qos_result_dir/qps615_efi_variables.log" + ethv_qos_print_log="$ethv_qos_result_dir/qps615_overlay_print.log" + ethv_qos_diag_log="$ethv_qos_result_dir/qps615_overlay_diagnostics.log" + ethv_qos_state_file="$ethv_qos_result_dir/qps615_overlay.tsv" + + [ -n "$ethv_qos_result_dir" ] || return 3 + mkdir -p "$ethv_qos_result_dir" || return 3 + : >"$ethv_qos_list_log" || return 3 + : >"$ethv_qos_print_log" || return 3 + : >"$ethv_qos_diag_log" || return 3 + : >"$ethv_qos_state_file" || return 3 + + QPS615_OVERLAY_VARIABLE="" + QPS615_OVERLAY_STATE="unavailable" + QPS615_OVERLAY_REASON="EFI FIT overlay inspection is unavailable" + + for ethv_qos_helper in \ + efi_find_variable_by_name \ + efi_text_variable_matches \ + efi_mount_exists; do + if ! command -v "$ethv_qos_helper" >/dev/null 2>&1; then + QPS615_OVERLAY_REASON="required EFI helper is unavailable: $ethv_qos_helper" + break + fi + done + + if [ "$QPS615_OVERLAY_REASON" = "EFI FIT overlay inspection is unavailable" ]; then + for ethv_qos_command in efivar awk dirname grep mktemp od sed tr; do + if ! command -v "$ethv_qos_command" >/dev/null 2>&1; then + QPS615_OVERLAY_REASON="image-provided EFI inspection command is unavailable: $ethv_qos_command" + break + fi + done + fi + + if [ "$QPS615_OVERLAY_REASON" = "EFI FIT overlay inspection is unavailable" ]; then + if ! efi_mount_exists; then + QPS615_OVERLAY_REASON="efivarfs is not mounted at ${EFIVARFS_PATH:-/sys/firmware/efi/efivars}" + else + ethv_qos_lookup_status=0 + QPS615_OVERLAY_VARIABLE="$( + efi_find_variable_by_name \ + "$QPS615_OVERLAY_NAME" \ + "$ethv_qos_list_log" \ + 2>"$ethv_qos_diag_log" + )" || ethv_qos_lookup_status=$? + + if [ "$ethv_qos_lookup_status" -eq 2 ]; then + QPS615_OVERLAY_REASON="EFI variable listing failed, inspect $ethv_qos_list_log and EFI runtime permissions" + elif [ "$ethv_qos_lookup_status" -eq 1 ] && + ! grep -Eq -- "-$QPS615_OVERLAY_NAME$" "$ethv_qos_list_log"; then + QPS615_OVERLAY_STATE="absent" + QPS615_OVERLAY_REASON="$QPS615_OVERLAY_NAME is absent from the successfully read EFI inventory" + elif [ -z "$QPS615_OVERLAY_VARIABLE" ]; then + QPS615_OVERLAY_REASON="$QPS615_OVERLAY_NAME was not uniquely discovered in the EFI variable inventory" + else + ethv_qos_read_status=0 + efi_text_variable_matches \ + "$QPS615_OVERLAY_VARIABLE" \ + "$QPS615_OVERLAY_VALUE" \ + "$ethv_qos_print_log" || ethv_qos_read_status=$? + case "$ethv_qos_read_status" in + 0) + QPS615_OVERLAY_STATE="configured" + QPS615_OVERLAY_REASON="$QPS615_OVERLAY_VARIABLE contains $QPS615_OVERLAY_VALUE" + ;; + 1) + QPS615_OVERLAY_STATE="value-mismatch" + QPS615_OVERLAY_REASON="$QPS615_OVERLAY_VARIABLE does not contain the required QPS615 FIT value $QPS615_OVERLAY_VALUE" + ;; + *) + QPS615_OVERLAY_REASON="EFI variable $QPS615_OVERLAY_VARIABLE could not be inspected, see $ethv_qos_print_log" + ;; + esac + fi + fi + fi + + printf 'variable\t%s\nexpected_value\t%s\nstate\t%s\nreason\t%s\n' \ + "${QPS615_OVERLAY_VARIABLE:-unavailable}" \ + "$QPS615_OVERLAY_VALUE" \ + "$QPS615_OVERLAY_STATE" \ + "$QPS615_OVERLAY_REASON" >"$ethv_qos_state_file" + + export QPS615_OVERLAY_VARIABLE QPS615_OVERLAY_STATE + export QPS615_OVERLAY_REASON + log_info "[QPS615-FIT] state=$QPS615_OVERLAY_STATE variable=${QPS615_OVERLAY_VARIABLE:-unavailable} expected_value=$QPS615_OVERLAY_VALUE reason=$QPS615_OVERLAY_REASON" + + case "$QPS615_OVERLAY_STATE" in + configured) + return 0 + ;; + absent|value-mismatch) + return 1 + ;; + *) + return 2 + ;; + esac +} + +# ethv_qps615_prepare_overlay +# Explicitly select staging, mounting efivarfs when necessary and retaining it. +# Callers must arrange efi_restore_efivarfs_ro on exit and signals first. +# stdout: diagnostics. +# return: 0 when already selected, 1 on write or verification failure, +# 2 when EFI mutation is unavailable, 3 for invalid arguments, and 4 after a +# verified update requiring reboot. Side effects: retains new mounts, restores +# initially read-only mounts, and writes one EFI variable after operator opt-in. +ethv_qps615_prepare_overlay() { + ethv_qpo_result_dir="${1:-}" + + [ -n "$ethv_qpo_result_dir" ] || return 3 + mkdir -p "$ethv_qpo_result_dir" || return 3 + EFI_REMOUNT_LOG="$ethv_qpo_result_dir/qps615_efi_remount.log" + export EFI_REMOUNT_LOG + + for ethv_qpo_helper in \ + efi_ensure_mounted \ + efi_restore_efivarfs_ro \ + efi_write_text_variable; do + if ! command -v "$ethv_qpo_helper" >/dev/null 2>&1; then + QPS615_OVERLAY_REASON="required EFI mutation helper is unavailable: $ethv_qpo_helper" + export QPS615_OVERLAY_REASON + return 2 + fi + done + + for ethv_qpo_command in efivar awk cp dirname grep mktemp mount od sed sync tr; do + if ! command -v "$ethv_qpo_command" >/dev/null 2>&1; then + QPS615_OVERLAY_REASON="image-provided EFI mutation command is unavailable: $ethv_qpo_command" + export QPS615_OVERLAY_REASON + return 2 + fi + done + + ethv_qpo_status=0 + if efi_ensure_mounted "$ethv_qpo_result_dir/qps615_efi_mount.log"; then + ethv_qps615_update_overlay "$ethv_qpo_result_dir" || ethv_qpo_status=$? + else + QPS615_OVERLAY_REASON="Could not mount EFI variable storage at $EFIVARFS_PATH, check EFI runtime support and mount permissions" + ethv_qpo_status=1 + fi + if ! efi_restore_efivarfs_ro >>"$ethv_qpo_result_dir/qps615_efi_cleanup.log" 2>&1; then + QPS615_OVERLAY_REASON="${QPS615_OVERLAY_REASON:-Overlay preparation completed}, restoring EFI mount to read-only failed at $EFIVARFS_PATH, inspect qps615_efi_cleanup.log" + ethv_qpo_status=1 + fi + export QPS615_OVERLAY_REASON + return "$ethv_qpo_status" +} + +# ethv_qps615_update_overlay +# Update an accessible EFI overlay selection. Only the explicit preparation +# wrapper may call this helper. A successfully listed absent variable uses +# the documented QPS615 GUID. stdout: diagnostics. return: 0 already set, +# 4 written, 1 write failure, 2 inspection unavailable. Side effects: retains +# before/after evidence and persists staging without a newline. +ethv_qps615_update_overlay() { + ethv_qu_result_dir="$1" + ethv_qpo_state_status=0 + ethv_qps615_collect_overlay_state "$ethv_qu_result_dir" || ethv_qpo_state_status=$? + case "$ethv_qpo_state_status" in + 0) + return 0 + ;; + 2|3) + return 2 + ;; + esac + + for ethv_qu_before in qps615_overlay.tsv qps615_efi_variables.log qps615_overlay_print.log; do + if ! cp "$ethv_qu_result_dir/$ethv_qu_before" \ + "$ethv_qu_result_dir/before_$ethv_qu_before"; then + QPS615_OVERLAY_REASON="Could not preserve EFI state before updating $QPS615_OVERLAY_NAME" + return 1 + fi + done + if [ "$QPS615_OVERLAY_STATE" = "absent" ]; then + QPS615_OVERLAY_VARIABLE="$QPS615_OVERLAY_DEFAULT_GUID-$QPS615_OVERLAY_NAME" + log_info "Creating $QPS615_OVERLAY_VARIABLE using the QPS615 bring-up firmware namespace" + fi + + if ! efi_write_text_variable \ + "$QPS615_OVERLAY_VARIABLE" \ + "$QPS615_OVERLAY_VALUE" \ + "$ethv_qu_result_dir/qps615_overlay_write.log"; then + QPS615_OVERLAY_REASON="could not write $QPS615_OVERLAY_VALUE to $QPS615_OVERLAY_VARIABLE" + export QPS615_OVERLAY_REASON + return 1 + fi + + if ! ethv_qps615_collect_overlay_state "$ethv_qu_result_dir"; then + QPS615_OVERLAY_REASON="QPS615 FIT selection was written but EFI verification did not find $QPS615_OVERLAY_VALUE" + export QPS615_OVERLAY_REASON + return 1 + fi + + QPS615_OVERLAY_REASON="$QPS615_OVERLAY_VARIABLE was updated to $QPS615_OVERLAY_VALUE, reboot the target to load the QPS615 FIT device tree" + export QPS615_OVERLAY_REASON + return 4 +} + +# ethv_qps615_collect_dt_state +# Discover enabled QPS615-compatible runtime DT nodes and validate the +# supply-backed public pwrctrl-tc9563 contract when declared. Nodes without +# supplies remain valid for integration forms that do not use the platform +# power-control driver. stdout: diagnostics. return: 0 when DT evidence is healthy, +# 1 when a declared contract is malformed or unbound, 2 on discovery failure, +# and 3 for invalid arguments. Side effects: exports QPS615_DT_* and +# QPS615_PWRCTRL_* state. Scratch files use result-dir without changing TMPDIR +# outside the discovery call. Diagnostics are retained in qps615_dt_scan.log. +ethv_qps615_collect_dt_state() { + ethv_qds_result_dir="${1:-}" + ethv_qds_inventory="${2:-}" + ethv_qds_nodes="$ethv_qds_result_dir/qps615_dt_nodes.log" + ethv_qds_evidence="$ethv_qds_result_dir/qps615_dt_platform.tsv" + + [ -n "$ethv_qds_result_dir" ] && [ -n "$ethv_qds_inventory" ] || return 3 + mkdir -p "$ethv_qds_result_dir" || return 3 + : >"$ethv_qds_nodes" || return 3 + : >"$ethv_qds_evidence" || return 3 + + QPS615_DT_NODE_COUNT=0 + QPS615_DT_FAILURE_REASON="" + export QPS615_DT_FAILURE_REASON + QPS615_PWRCTRL_EXPECTED_COUNT=0 + QPS615_PWRCTRL_BOUND_COUNT=0 + QPS615_PWRCTRL_FAILURE_REASON="" + + ethv_qds_scan_status=0 + if command -v dt_list_compatible_nodes >/dev/null 2>&1; then + TMPDIR="$ethv_qds_result_dir" dt_list_compatible_nodes \ + '(^|[[:space:]])pci1179,0623([[:space:]]|$)' regex \ + >"$ethv_qds_nodes" 2>"$ethv_qds_result_dir/qps615_dt_scan.log" || ethv_qds_scan_status=$? + else + ethv_qds_scan_status=2 + fi + if [ "$ethv_qds_scan_status" -gt 1 ]; then + QPS615_DT_FAILURE_REASON="QPS615 runtime DT discovery failed with status $ethv_qds_scan_status, check DT access, image-provided discovery utilities, writable evidence storage and $ethv_qds_result_dir/qps615_dt_scan.log" + log_warn "$QPS615_DT_FAILURE_REASON" + return 2 + fi + + while IFS= read -r ethv_qds_node; do + [ -n "$ethv_qds_node" ] || continue + QPS615_DT_NODE_COUNT=$((QPS615_DT_NODE_COUNT + 1)) + ethv_qds_compatible="$( + dt_property_text "$ethv_qds_node" compatible 2>/dev/null || true + )" + ethv_qds_status="$( + dt_property_text "$ethv_qds_node" status 2>/dev/null || true + )" + [ -n "$ethv_qds_status" ] || ethv_qds_status="okay" + ethv_qds_supply_count=0 + + for ethv_qds_property in \ + vddc-supply \ + vdd18-supply \ + vdd09-supply \ + vddio1-supply \ + vddio2-supply \ + vddio18-supply; do + if [ -e "$ethv_qds_node/$ethv_qds_property" ]; then + ethv_qds_supply_count=$((ethv_qds_supply_count + 1)) + fi + done + + ethv_qds_i2c="absent" + ethv_qds_resx="absent" + ethv_qds_platform="unavailable" + ethv_qds_driver="unbound" + [ -e "$ethv_qds_node/i2c-parent" ] && ethv_qds_i2c="present" + [ -e "$ethv_qds_node/resx-gpios" ] && ethv_qds_resx="present" + + if [ "$ethv_qds_supply_count" -gt 0 ]; then + QPS615_PWRCTRL_EXPECTED_COUNT=$((QPS615_PWRCTRL_EXPECTED_COUNT + 1)) + if [ "$ethv_qds_supply_count" -ne 6 ]; then + ethv_qds_reason="QPS615 DT node $ethv_qds_node declares $ethv_qds_supply_count of 6 required power supplies, provide vddc, vdd18, vdd09, vddio1, vddio2, and vddio18 supplies" + if [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + QPS615_PWRCTRL_FAILURE_REASON="$QPS615_PWRCTRL_FAILURE_REASON, $ethv_qds_reason" + else + QPS615_PWRCTRL_FAILURE_REASON="$ethv_qds_reason" + fi + elif [ "$ethv_qds_i2c" != "present" ] || \ + [ "$ethv_qds_resx" != "present" ]; then + ethv_qds_reason="QPS615 DT node $ethv_qds_node declares all supplies but the power-control contract requires i2c-parent and resx-gpios, observed i2c_parent=$ethv_qds_i2c resx_gpios=$ethv_qds_resx" + if [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + QPS615_PWRCTRL_FAILURE_REASON="$QPS615_PWRCTRL_FAILURE_REASON, $ethv_qds_reason" + else + QPS615_PWRCTRL_FAILURE_REASON="$ethv_qds_reason" + fi + else + ethv_qds_platform="$( + find_platform_device_for_dt_node "$ethv_qds_node" \ + 2>/dev/null || true + )" + if [ -n "$ethv_qds_platform" ]; then + ethv_qds_driver="$( + platform_device_driver_name "$ethv_qds_platform" \ + 2>/dev/null || true + )" + [ -n "$ethv_qds_driver" ] || ethv_qds_driver="unbound" + fi + + if [ "$ethv_qds_driver" = "pwrctrl-tc9563" ]; then + QPS615_PWRCTRL_BOUND_COUNT=$((QPS615_PWRCTRL_BOUND_COUNT + 1)) + else + ethv_qds_reason="QPS615 DT node $ethv_qds_node requires pwrctrl-tc9563 but platform device ${ethv_qds_platform:-unavailable} is bound to $ethv_qds_driver" + if [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + QPS615_PWRCTRL_FAILURE_REASON="$QPS615_PWRCTRL_FAILURE_REASON, $ethv_qds_reason" + else + QPS615_PWRCTRL_FAILURE_REASON="$ethv_qds_reason" + fi + fi + fi + fi + + printf 'dt-node\t%s\tcompatible=%s\tstatus=%s\tsupplies=%s/6\ti2c_parent=%s\tresx_gpios=%s\tplatform_device=%s\tplatform_driver=%s\n' \ + "$ethv_qds_node" \ + "${ethv_qds_compatible:-unavailable}" \ + "$ethv_qds_status" \ + "$ethv_qds_supply_count" \ + "$ethv_qds_i2c" \ + "$ethv_qds_resx" \ + "$ethv_qds_platform" \ + "$ethv_qds_driver" >>"$ethv_qds_evidence" + printf 'dt-node\t%s\tcompatible=%s\tstatus=%s\tsupplies=%s/6\ti2c_parent=%s\tresx_gpios=%s\tplatform_device=%s\tplatform_driver=%s\n' \ + "$ethv_qds_node" \ + "${ethv_qds_compatible:-unavailable}" \ + "$ethv_qds_status" \ + "$ethv_qds_supply_count" \ + "$ethv_qds_i2c" \ + "$ethv_qds_resx" \ + "$ethv_qds_platform" \ + "$ethv_qds_driver" >>"$ethv_qds_inventory" + log_info "[QPS615-DT] node=$ethv_qds_node compatible=${ethv_qds_compatible:-unavailable} status=$ethv_qds_status supplies=$ethv_qds_supply_count/6 i2c_parent=$ethv_qds_i2c resx_gpios=$ethv_qds_resx platform_device=$ethv_qds_platform platform_driver=$ethv_qds_driver" + done <"$ethv_qds_nodes" + + printf 'dt-summary\tnodes=%s\tpwrctrl_expected=%s\tpwrctrl_bound=%s\tfailure=%s\n' \ + "$QPS615_DT_NODE_COUNT" \ + "$QPS615_PWRCTRL_EXPECTED_COUNT" \ + "$QPS615_PWRCTRL_BOUND_COUNT" \ + "${QPS615_PWRCTRL_FAILURE_REASON:-none}" >>"$ethv_qds_inventory" + + export QPS615_DT_NODE_COUNT QPS615_PWRCTRL_EXPECTED_COUNT + export QPS615_PWRCTRL_BOUND_COUNT QPS615_PWRCTRL_FAILURE_REASON + [ -z "$QPS615_PWRCTRL_FAILURE_REASON" ] +} + +# ethv_qps615_is_switch_root +# Identify an outermost 1179:0623 bridge by its sysfs ancestry. The caller +# has already verified this device's PCI ID. stdout: none. return: 0 for +# a root, 1 for a descendant bridge, 3 for invalid input. Side effects: none. +ethv_qps615_is_switch_root() { + ethv_qsr_path="${1:-}" + case "$ethv_qsr_path" in + /*) + ;; + *) + return 3 + ;; + esac + ethv_qsr_parent="${ethv_qsr_path%/*}" + while [ -n "$ethv_qsr_parent" ] && [ "$ethv_qsr_parent" != "/" ]; do + ethv_qsr_vendor=$(ethv_read_first_line "$ethv_qsr_parent/vendor" 2>/dev/null || true) + ethv_qsr_device=$(ethv_read_first_line "$ethv_qsr_parent/device" 2>/dev/null || true) + if [ "$ethv_qsr_vendor" = "0x1179" ] && [ "$ethv_qsr_device" = "0x0623" ]; then + return 1 + fi + ethv_qsr_parent="${ethv_qsr_parent%/*}" + done + return 0 +} + # ethv_qps615_collect_runtime [pci-devices-root] # Collect and validate the runtime QPS615/TC956x PCIe Ethernet topology. -# QPS615 applicability comes from the enumerated Toshiba 1179:0623 switch, -# not from an installed package or module alone. Exported QPS615_* values -# summarize the result for PCIe and Ethernet suite orchestration. +# Applicability comes from enabled pci1179,0623 runtime DT nodes or an +# enumerated Toshiba 1179:0623 switch, never an installed package or module +# alone. Exported QPS615_* values summarize the result for PCIe and Ethernet +# suite orchestration. # return: 0 when applicable and healthy, 1 when declared hardware is broken, # 2 when QPS615 is not present, and 3 for invalid arguments. ethv_qps615_collect_runtime() { @@ -277,6 +834,8 @@ ethv_qps615_collect_runtime() { : >"$ethv_qcr_switches" || return 1 QPS615_SWITCH_COUNT=0 + QPS615_BRIDGE_COUNT=0 + export QPS615_BRIDGE_COUNT QPS615_DOWNSTREAM_COUNT=0 QPS615_ETHERNET_DEVICE_COUNT=0 QPS615_ETHERNET_DECLARED_COUNT=0 @@ -295,15 +854,42 @@ ethv_qps615_collect_runtime() { QPS615_PCI_DRIVER_PATH="" QPS615_PCI_DRIVER_STATE="absent" QPS615_PCI_AUTOPROBE="unknown" + QPS615_DT_NODE_COUNT=0 + QPS615_DT_FAILURE_REASON="" + QPS615_PWRCTRL_EXPECTED_COUNT=0 + QPS615_PWRCTRL_BOUND_COUNT=0 + QPS615_PWRCTRL_FAILURE_REASON="" QPS615_TOPOLOGY_FAILURE_REASON="" QPS615_ETHERNET_FAILURE_REASON="" QPS615_ETHERNET_SKIP_REASON="" + QPS615_READINESS_SUMMARY="" QPS615_FAILURE_REASON="" + ethv_qcr_dt_status=0 + ethv_qps615_collect_dt_state \ + "$ethv_qcr_result_dir" \ + "$ethv_qcr_inventory" || ethv_qcr_dt_status=$? + if [ "$ethv_qcr_dt_status" -gt 1 ]; then + QPS615_FAILURE_REASON="${QPS615_DT_FAILURE_REASON:-Could not collect QPS615 DT evidence in $ethv_qcr_result_dir}" + QPS615_TOPOLOGY_FAILURE_REASON="$QPS615_FAILURE_REASON" + export QPS615_FAILURE_REASON QPS615_TOPOLOGY_FAILURE_REASON + return 1 + fi + log_info "QPS615 validation: scanning PCI devices under $ethv_qcr_pci_root for switch 1179:0623" if [ ! -d "$ethv_qcr_pci_root" ]; then - log_info "QPS615 validation is not applicable because the PCI sysfs inventory is not exposed" + if [ "$QPS615_DT_NODE_COUNT" -gt 0 ]; then + QPS615_TOPOLOGY_FAILURE_REASON="$QPS615_DT_NODE_COUNT enabled QPS615 DT node(s) were found but PCI sysfs is not exposed at $ethv_qcr_pci_root" + QPS615_FAILURE_REASON="$QPS615_TOPOLOGY_FAILURE_REASON" + if [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + QPS615_FAILURE_REASON="$QPS615_FAILURE_REASON, power-control failure: $QPS615_PWRCTRL_FAILURE_REASON" + fi + ethv_qcr_pci_missing_status=1 + else + log_info "QPS615 validation is not applicable because the PCI sysfs inventory is not exposed and no enabled QPS615 DT node was found" + ethv_qcr_pci_missing_status=2 + fi export QPS615_SWITCH_COUNT QPS615_DOWNSTREAM_COUNT QPS615_ETHERNET_DEVICE_COUNT export QPS615_ETHERNET_DECLARED_COUNT QPS615_DRIVER_DEVICE_COUNT export QPS615_DECLARED_DRIVER_DEVICE_COUNT QPS615_UNBOUND_ETHERNET_COUNT QPS615_NETDEV_COUNT @@ -313,9 +899,12 @@ ethv_qps615_collect_runtime() { export QPS615_MODULE_PORT_BDFS QPS615_MODULE_PORT_CONFIG export QPS615_PCI_DRIVER_PATH QPS615_PCI_DRIVER_STATE export QPS615_PCI_AUTOPROBE + export QPS615_DT_NODE_COUNT QPS615_PWRCTRL_EXPECTED_COUNT + export QPS615_PWRCTRL_BOUND_COUNT QPS615_PWRCTRL_FAILURE_REASON export QPS615_TOPOLOGY_FAILURE_REASON QPS615_ETHERNET_FAILURE_REASON - export QPS615_ETHERNET_SKIP_REASON QPS615_FAILURE_REASON - return 2 + export QPS615_ETHERNET_SKIP_REASON QPS615_READINESS_SUMMARY + export QPS615_FAILURE_REASON + return "$ethv_qcr_pci_missing_status" fi for ethv_qcr_device in "$ethv_qcr_pci_root"/*; do @@ -326,10 +915,19 @@ ethv_qps615_collect_runtime() { if [ "$ethv_qcr_vendor" = "0x1179" ] && \ [ "$ethv_qcr_id" = "0x0623" ]; then ethv_qcr_resolved="$(readlink -f "$ethv_qcr_device" 2>/dev/null || true)" - QPS615_SWITCH_COUNT=$((QPS615_SWITCH_COUNT + 1)) - if [ -n "$ethv_qcr_resolved" ]; then - printf '%s\n' "$ethv_qcr_resolved" >>"$ethv_qcr_switches" + QPS615_BRIDGE_COUNT=$((QPS615_BRIDGE_COUNT + 1)) + if [ -z "$ethv_qcr_resolved" ]; then + QPS615_TOPOLOGY_FAILURE_REASON="Cannot resolve QPS615 PCI sysfs path $ethv_qcr_device, rerun with readable PCI topology" + continue fi + printf 'bridge-function\t%s\t%s\t%s\t%s\n' \ + "${ethv_qcr_device##*/}" \ + "$ethv_qcr_vendor" \ + "$ethv_qcr_id" \ + "$ethv_qcr_resolved" >>"$ethv_qcr_inventory" + ethv_qps615_is_switch_root "$ethv_qcr_resolved" || continue + QPS615_SWITCH_COUNT=$((QPS615_SWITCH_COUNT + 1)) + printf '%s\n' "$ethv_qcr_resolved" >>"$ethv_qcr_switches" printf 'switch\t%s\t%s\t%s\t%s\n' \ "${ethv_qcr_device##*/}" \ "$ethv_qcr_vendor" \ @@ -339,8 +937,21 @@ ethv_qps615_collect_runtime() { fi done - if [ "$QPS615_SWITCH_COUNT" -eq 0 ]; then - log_info "QPS615 validation is not applicable because switch 1179:0623 was not enumerated" + printf 'topology-summary\tswitches=%s\tbridge_functions=%s\n' \ + "$QPS615_SWITCH_COUNT" "$QPS615_BRIDGE_COUNT" >>"$ethv_qcr_inventory" + + if [ "$QPS615_SWITCH_COUNT" -eq 0 ] && [ "$QPS615_BRIDGE_COUNT" -eq 0 ]; then + if [ "$QPS615_DT_NODE_COUNT" -gt 0 ]; then + QPS615_TOPOLOGY_FAILURE_REASON="$QPS615_DT_NODE_COUNT enabled QPS615 DT node(s) were found but switch 1179:0623 was not enumerated" + QPS615_FAILURE_REASON="$QPS615_TOPOLOGY_FAILURE_REASON" + if [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + QPS615_FAILURE_REASON="$QPS615_FAILURE_REASON, power-control failure: $QPS615_PWRCTRL_FAILURE_REASON" + fi + ethv_qcr_absent_status=1 + else + log_info "QPS615 validation is not applicable because switch 1179:0623 was not enumerated and no enabled QPS615 DT node was found" + ethv_qcr_absent_status=2 + fi export QPS615_SWITCH_COUNT QPS615_DOWNSTREAM_COUNT QPS615_ETHERNET_DEVICE_COUNT export QPS615_ETHERNET_DECLARED_COUNT QPS615_DRIVER_DEVICE_COUNT export QPS615_DECLARED_DRIVER_DEVICE_COUNT QPS615_UNBOUND_ETHERNET_COUNT QPS615_NETDEV_COUNT @@ -350,9 +961,12 @@ ethv_qps615_collect_runtime() { export QPS615_MODULE_PORT_BDFS QPS615_MODULE_PORT_CONFIG export QPS615_PCI_DRIVER_PATH QPS615_PCI_DRIVER_STATE export QPS615_PCI_AUTOPROBE + export QPS615_DT_NODE_COUNT QPS615_PWRCTRL_EXPECTED_COUNT + export QPS615_PWRCTRL_BOUND_COUNT QPS615_PWRCTRL_FAILURE_REASON export QPS615_TOPOLOGY_FAILURE_REASON QPS615_ETHERNET_FAILURE_REASON - export QPS615_ETHERNET_SKIP_REASON QPS615_FAILURE_REASON - return 2 + export QPS615_ETHERNET_SKIP_REASON QPS615_READINESS_SUMMARY + export QPS615_FAILURE_REASON + return "$ethv_qcr_absent_status" fi for ethv_qcr_device in "$ethv_qcr_pci_root"/*; do @@ -524,10 +1138,14 @@ ethv_qps615_collect_runtime() { log_warn "[QPS615-ETH] module=tc956x_pcie_eth was not found for the running kernel" fi - log_info "[QPS615] summary bridge_functions=$QPS615_SWITCH_COUNT downstream=$QPS615_DOWNSTREAM_COUNT ethernet_devices=$QPS615_ETHERNET_DEVICE_COUNT declared_ethernet_ports=$QPS615_ETHERNET_DECLARED_COUNT bound_ethernet_devices=$QPS615_DRIVER_DEVICE_COUNT bound_declared_ports=$QPS615_DECLARED_DRIVER_DEVICE_COUNT unbound_ethernet_devices=$QPS615_UNBOUND_ETHERNET_COUNT of_nodes=$QPS615_ETHERNET_OF_NODE_COUNT driver_overrides=$QPS615_ETHERNET_OVERRIDE_COUNT netdevs=$QPS615_NETDEV_COUNT declared_port_netdevs=$QPS615_DECLARED_NETDEV_COUNT artifact=$ethv_qcr_inventory" + log_info "[QPS615] summary switches=$QPS615_SWITCH_COUNT bridge_functions=$QPS615_BRIDGE_COUNT downstream=$QPS615_DOWNSTREAM_COUNT ethernet_devices=$QPS615_ETHERNET_DEVICE_COUNT declared_ethernet_ports=$QPS615_ETHERNET_DECLARED_COUNT bound_ethernet_devices=$QPS615_DRIVER_DEVICE_COUNT bound_declared_ports=$QPS615_DECLARED_DRIVER_DEVICE_COUNT unbound_ethernet_devices=$QPS615_UNBOUND_ETHERNET_COUNT of_nodes=$QPS615_ETHERNET_OF_NODE_COUNT driver_overrides=$QPS615_ETHERNET_OVERRIDE_COUNT netdevs=$QPS615_NETDEV_COUNT declared_port_netdevs=$QPS615_DECLARED_NETDEV_COUNT artifact=$ethv_qcr_inventory" - if [ "$QPS615_DOWNSTREAM_COUNT" -eq 0 ]; then + if [ -n "$QPS615_TOPOLOGY_FAILURE_REASON" ]; then + log_warn "$QPS615_TOPOLOGY_FAILURE_REASON" + elif [ "$QPS615_DOWNSTREAM_COUNT" -eq 0 ]; then QPS615_TOPOLOGY_FAILURE_REASON="QPS615 bridge is enumerated but exposes no downstream PCIe functions" + elif [ "$QPS615_DT_NODE_COUNT" -gt "$QPS615_SWITCH_COUNT" ]; then + QPS615_TOPOLOGY_FAILURE_REASON="$QPS615_DT_NODE_COUNT enabled QPS615 DT node(s) were found but only $QPS615_SWITCH_COUNT switch instance(s) enumerated" elif [ -z "$QPS615_FIRMWARE_PATH" ]; then QPS615_TOPOLOGY_FAILURE_REASON="required TC956X_Firmware_PCIeBridge.bin is not readable in a standard firmware root" fi @@ -560,12 +1178,31 @@ ethv_qps615_collect_runtime() { QPS615_ETHERNET_FAILURE_REASON="$QPS615_DRIVER_DEVICE_COUNT TC956x Ethernet function(s) are bound without an explicit runtime DT port declaration, but only $QPS615_NETDEV_COUNT netdev(s) are exposed" fi + QPS615_READINESS_SUMMARY="dt_nodes=$QPS615_DT_NODE_COUNT pwrctrl_expected=$QPS615_PWRCTRL_EXPECTED_COUNT pwrctrl_bound=$QPS615_PWRCTRL_BOUND_COUNT switches=$QPS615_SWITCH_COUNT ethernet_devices=$QPS615_ETHERNET_DEVICE_COUNT declared_ports=$QPS615_ETHERNET_DECLARED_COUNT bound_devices=$QPS615_DRIVER_DEVICE_COUNT bound_declared_ports=$QPS615_DECLARED_DRIVER_DEVICE_COUNT netdevs=$QPS615_NETDEV_COUNT module_state=$QPS615_MODULE_STATE pci_driver_state=$QPS615_PCI_DRIVER_STATE pci_autoprobe=$QPS615_PCI_AUTOPROBE" + if [ -n "$QPS615_TOPOLOGY_FAILURE_REASON" ]; then QPS615_FAILURE_REASON="$QPS615_TOPOLOGY_FAILURE_REASON" - else + if [ -n "$QPS615_ETHERNET_FAILURE_REASON" ]; then + QPS615_FAILURE_REASON="$QPS615_FAILURE_REASON, Ethernet readiness failure: $QPS615_ETHERNET_FAILURE_REASON" + elif [ -n "$QPS615_ETHERNET_SKIP_REASON" ]; then + QPS615_FAILURE_REASON="$QPS615_FAILURE_REASON, Ethernet provisioning note: $QPS615_ETHERNET_SKIP_REASON" + fi + elif [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + QPS615_FAILURE_REASON="$QPS615_PWRCTRL_FAILURE_REASON" + elif [ -n "$QPS615_ETHERNET_FAILURE_REASON" ]; then QPS615_FAILURE_REASON="$QPS615_ETHERNET_FAILURE_REASON" fi + if [ -n "$QPS615_FAILURE_REASON" ] && \ + [ -n "$QPS615_PWRCTRL_FAILURE_REASON" ] && \ + [ "$QPS615_FAILURE_REASON" != "$QPS615_PWRCTRL_FAILURE_REASON" ]; then + QPS615_FAILURE_REASON="$QPS615_FAILURE_REASON, power-control failure: $QPS615_PWRCTRL_FAILURE_REASON" + fi + + if [ -n "$QPS615_FAILURE_REASON" ]; then + QPS615_FAILURE_REASON="$QPS615_FAILURE_REASON, $QPS615_READINESS_SUMMARY" + fi + export QPS615_SWITCH_COUNT QPS615_DOWNSTREAM_COUNT QPS615_ETHERNET_DEVICE_COUNT export QPS615_ETHERNET_DECLARED_COUNT QPS615_DRIVER_DEVICE_COUNT export QPS615_DECLARED_DRIVER_DEVICE_COUNT QPS615_UNBOUND_ETHERNET_COUNT QPS615_NETDEV_COUNT @@ -575,12 +1212,279 @@ ethv_qps615_collect_runtime() { export QPS615_MODULE_PORT_BDFS QPS615_MODULE_PORT_CONFIG export QPS615_PCI_DRIVER_PATH QPS615_PCI_DRIVER_STATE export QPS615_PCI_AUTOPROBE + export QPS615_DT_NODE_COUNT QPS615_PWRCTRL_EXPECTED_COUNT + export QPS615_PWRCTRL_BOUND_COUNT QPS615_PWRCTRL_FAILURE_REASON export QPS615_TOPOLOGY_FAILURE_REASON QPS615_ETHERNET_FAILURE_REASON - export QPS615_ETHERNET_SKIP_REASON QPS615_FAILURE_REASON + export QPS615_ETHERNET_SKIP_REASON QPS615_READINESS_SUMMARY + export QPS615_FAILURE_REASON [ -z "$QPS615_FAILURE_REASON" ] } +# ethv_qps615_list_netdevs +# Read a QPS615 runtime TSV and print unique correlated netdev names. +# stdout: one netdev name per line in first-observed inventory order. +# return: 0 when the readable inventory is parsed, 1 when it is unavailable, +# or a nonzero awk status. Side effects: none. +ethv_qps615_list_netdevs() { + ethv_qln_inventory="${1:-}" + + [ -r "$ethv_qln_inventory" ] || return 1 + awk -F '\t' ' + $1 == "netdev" && $3 != "" && !seen[$3]++ { + print $3 + } + ' "$ethv_qln_inventory" +} + +# ethv_qps615_module_candidate [pci-root] +# Resolve a unique module from QPS615 endpoint driver links or the running +# image's modalias database. stdout: one module name. return: 0 unique, +# 1 absent/ambiguous/invalid metadata, 3 invalid input. Diagnostics: stderr. +ethv_qps615_module_candidate() { + ethv_qmc_inventory="${1:-}" + ethv_qmc_root="${2:-/sys/bus/pci/devices}" + [ -r "$ethv_qmc_inventory" ] || return 3 + ethv_qmc_modules="" + ethv_qmc_bdfs=$(awk -F '\t' '$1 == "ethernet-function" { print $2 }' "$ethv_qmc_inventory") || return 1 + for ethv_qmc_bdf in $ethv_qmc_bdfs; do + case "$ethv_qmc_bdf" in + ''|*[!0123456789abcdefABCDEF:.]*) + return 1 + ;; + esac + ethv_qmc_device="$ethv_qmc_root/$ethv_qmc_bdf" + if [ -L "$ethv_qmc_device/driver/module" ]; then + ethv_qmc_link=$(readlink -f "$ethv_qmc_device/driver/module") || return 1 + ethv_qmc_match="${ethv_qmc_link##*/}" + else + ethv_qmc_alias=$(ethv_read_first_line "$ethv_qmc_device/modalias") || return 1 + ethv_qmc_match=$(kernel_modules_for_modalias "$ethv_qmc_alias") || return 1 + fi + ethv_qmc_modules="$ethv_qmc_modules +$ethv_qmc_match" + done + ethv_qmc_unique=$(printf '%s\n' "$ethv_qmc_modules" | awk 'NF && !seen[$0]++') + case "$ethv_qmc_unique" in + ''|-*|*[!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-]*) + printf '%s\n' 'QPS615 endpoints did not resolve to one safe module name, inspect driver/module links and modalias metadata' >&2 + return 1 + ;; + esac + printf '%s\n' "$ethv_qmc_unique" +} + +# ethv_restore_owned_module +# Restore only a target module loaded by this test. Dependencies and initially +# loaded modules are preserved. stdout: diagnostics. return: 0 restored or +# untouched, 1 on failure. Side effects: bounded, non-forced target removal. +ethv_restore_owned_module() { + [ "$2" = 1 ] || return 0 + # Interrupted managed commands must stop before restoring the snapshot. + # Normal completion clears these PIDs in run_with_managed_timeout. + if [ -r "${MANAGED_TIMEOUT_SLEEP_PID_FILE:-}" ]; then + MANAGED_TIMEOUT_WATCHER_SLEEP_PID=$(cat "$MANAGED_TIMEOUT_SLEEP_PID_FILE") + fi + for ethv_rom_pid in "${MANAGED_TIMEOUT_CMD_PID:-}" \ + "${MANAGED_TIMEOUT_WATCHER_PID:-}" "${MANAGED_TIMEOUT_WATCHER_SLEEP_PID:-}"; do + case "$ethv_rom_pid" in + ''|*[!0-9]*|0|1) + continue + ;; + esac + kill -KILL "$ethv_rom_pid" 2>/dev/null || true + done + is_module_loaded "$1" || return 0 + log_info "Restoring initially unloaded module $1, preserving dependency modules" + # Do not use the generic unload helper's modprobe -r fallback, which can + # also remove dependency modules that were already active before the test. + if ! run_with_managed_timeout "$4" "$3" module-restore \ + rmmod "$1" >"$3/module_restore.log" 2>&1; then + log_error "Could not restore module $1, inspect $3/module_restore.log" + return 1 + fi + mrv_wait_module_state absent "$1" "$4" +} + +# ethv_qps615_validate_module_load [pci-root] +# Validate one runtime-resolved QPS615 module without cycling a module that +# was already loaded. Reuse shared discovery, module, wait, timeout and kernel +# log helpers. stdout: diagnostics. return: 0 ready, 1 failed, 2 unavailable. +# Side effects: load only if absent, restore the target on every exit/signal. +# Runs in a subshell so ownership and traps cannot affect other profiles. +ethv_qps615_validate_module_load() ( + ethv_qml_dir="${1:-}" + ethv_qml_timeout="${2:-30}" + ethv_qml_settle="${3:-20}" + ethv_qml_pci="${4:-/sys/bus/pci/devices}" + [ -n "$ethv_qml_dir" ] || exit 1 + ethv_is_uint "$ethv_qml_timeout" && [ "$ethv_qml_timeout" -gt 0 ] || exit 1 + ethv_is_uint "$ethv_qml_settle" && [ "$ethv_qml_settle" -gt 0 ] || exit 1 + mkdir -p "$ethv_qml_dir" || exit 1 + ethv_qml_dir=$(cd "$ethv_qml_dir" && pwd) || exit 1 + + ethv_qml_status=0 + ethv_qps615_collect_runtime "$ethv_qml_dir/before" "$ethv_qml_pci" || ethv_qml_status=$? + if [ "$ethv_qml_status" -eq 2 ]; then + log_skip "QPS615 module load validation requires runtime DT or PCI hardware, an installed module alone is not applicability evidence" + exit 2 + fi + if [ "$ethv_qml_status" -gt 2 ] || [ -n "${QPS615_TOPOLOGY_FAILURE_REASON:-}" ] || + [ -n "${QPS615_PWRCTRL_FAILURE_REASON:-}" ]; then + log_error "Cannot validate module loading before QPS615 topology and power-control are ready: ${QPS615_FAILURE_REASON:-discovery failed}" + exit 1 + fi + if [ "${QPS615_ETHERNET_DEVICE_COUNT:-0}" -eq 0 ]; then + log_skip "QPS615 exposes no Ethernet endpoints for module validation" + exit 2 + fi + if [ -n "${QPS615_ETHERNET_SKIP_REASON:-}" ]; then + log_skip "$QPS615_ETHERNET_SKIP_REASON, provision runtime Ethernet port configuration before testing module loading" + exit 2 + fi + if ! ethv_qps615_module_candidate "$ethv_qml_dir/before/qps615_runtime.tsv" "$ethv_qml_pci" \ + >"$ethv_qml_dir/module_candidate.log" 2>"$ethv_qml_dir/module_discovery.log"; then + log_error "Cannot uniquely resolve the QPS615 module from runtime endpoints, inspect $ethv_qml_dir/module_discovery.log and image modalias metadata" + exit 1 + fi + ethv_qml_module=$(ethv_read_first_line "$ethv_qml_dir/module_candidate.log") || exit 1 + ethv_qml_module=$(mrv_module_lsmod_name "$ethv_qml_module") || exit 1 + ethv_qml_origin=$(kernel_module_runtime_origin "$ethv_qml_module") || { + log_error "Runtime module metadata is unavailable for $ethv_qml_module" + exit 1 + } + ethv_qml_owned=0 + ethv_qml_exit=0 + trap ' + ethv_qml_exit=$? + trap - EXIT HUP INT TERM + if ! ethv_restore_owned_module "$ethv_qml_module" "$ethv_qml_owned" "$ethv_qml_dir" "$ethv_qml_timeout"; then + ethv_qml_exit=1 + fi + exit "$ethv_qml_exit" + ' EXIT + trap 'exit 1' HUP INT TERM + + log_info "QPS615 module selection: module=$ethv_qml_module origin=$ethv_qml_origin source=runtime-driver-or-modalias" + if command -v modinfo >/dev/null 2>&1; then + if ! run_with_managed_timeout "$ethv_qml_timeout" "$ethv_qml_dir" module-info \ + modinfo "$ethv_qml_module" >"$ethv_qml_dir/modinfo.log" 2>&1; then + log_error "modinfo failed for $ethv_qml_module, inspect $ethv_qml_dir/modinfo.log" + exit 1 + fi + fi + if [ "$ethv_qml_origin" = builtin ] || is_module_loaded "$ethv_qml_module"; then + log_info "QPS615 module is already active or built-in, no load or unload will be performed" + else + case "$ethv_qml_origin" in + module:*) + ethv_qml_path="${ethv_qml_origin#module:}" + ;; + *) + log_error "No image-provided module file was resolved for $ethv_qml_module" + exit 1 + ;; + esac + if ! command -v rmmod >/dev/null 2>&1 || + ! command -v modprobe >/dev/null 2>&1 || [ "$(id -u)" != 0 ]; then + log_skip "Loading $ethv_qml_module requires root and image-provided modprobe and rmmod for target-only restoration" + exit 2 + fi + if [ ! -r "$ethv_qml_path" ]; then + log_error "Running-kernel module $ethv_qml_path is not readable, provision the matching image module" + exit 1 + fi + # Record ownership before attempting the load so partial failures clean up. + ethv_qml_owned=1 + # Match the existing reload profile's modprobe workflow, allowing the + # running kernel's alias/dependency metadata to resolve prerequisites. + if ! run_with_managed_timeout "$ethv_qml_timeout" "$ethv_qml_dir" module-load \ + modprobe "$ethv_qml_module" >"$ethv_qml_dir/module_load.log" 2>&1; then + log_error "Loading $ethv_qml_module failed, inspect $ethv_qml_dir/module_load.log" + exit 1 + fi + if ! mrv_wait_module_state present "$ethv_qml_module" "$ethv_qml_settle"; then + log_error "Module $ethv_qml_module did not become loaded within ${ethv_qml_settle}s" + exit 1 + fi + fi + + ethv_qml_elapsed=0 + while :; do + ethv_qml_status=0 + ethv_qps615_collect_runtime "$ethv_qml_dir/after" "$ethv_qml_pci" || ethv_qml_status=$? + if [ "$ethv_qml_status" -eq 0 ] && [ "${QPS615_NETDEV_COUNT:-0}" -gt 0 ]; then + ethv_qml_bound=$(ethv_qps615_module_candidate "$ethv_qml_dir/after/qps615_runtime.tsv" "$ethv_qml_pci") || exit 1 + ethv_qml_bound=$(mrv_module_lsmod_name "$ethv_qml_bound") || exit 1 + if [ "$ethv_qml_bound" != "$ethv_qml_module" ]; then + log_error "QPS615 bound module changed from $ethv_qml_module to $ethv_qml_bound during validation" + exit 1 + fi + break + fi + if [ "$ethv_qml_elapsed" -ge "$ethv_qml_settle" ]; then + log_error "Module $ethv_qml_module is active but QPS615 driver/netdev binding is not ready: ${QPS615_FAILURE_REASON:-no correlated netdev}, inspect $ethv_qml_dir/after" + exit 1 + fi + sleep 1 + ethv_qml_elapsed=$((ethv_qml_elapsed + 1)) + done + ethv_qml_kernel_status=0 + scan_dmesg_errors "$ethv_qml_dir/kernel" 'tc956|qps615' \ + 'Link is Down|Link down|carrier lost|no carrier|deferred probe|EPROBE_DEFER' || ethv_qml_kernel_status=$? + if [ "${DMESG_ACCESS_STATUS:-unavailable}" = available ] && [ "$ethv_qml_kernel_status" -eq 0 ]; then + log_error "QPS615 kernel errors were captured in $ethv_qml_dir/kernel/dmesg_errors.log" + exit 1 + fi + log_info "QPS615 module $ethv_qml_module has runtime-correlated driver binding and netdev readiness, checking restoration before PASS, artifacts=$ethv_qml_dir" + exit 0 +) + +# ethv_validate_qps615_traffic_config +# Validate QPS615 traffic policy before runtime discovery. Empty interface +# and peer values select dynamic discovery. stdout: none. return: 0 when +# valid, 1 otherwise, or 3 for missing arguments. Side effects: exports +# QPS615_CONFIG_ERROR with an actionable reason. +ethv_validate_qps615_traffic_config() { + [ "$#" -ge 5 ] || return 3 + ethv_vqtc_fixture="$1" + ethv_vqtc_interfaces="$2" + ethv_vqtc_peer="$3" + ethv_vqtc_count="$4" + ethv_vqtc_wait="$5" + QPS615_CONFIG_ERROR="" + + if ! ethv_is_boolean "$ethv_vqtc_fixture"; then + QPS615_CONFIG_ERROR="fixture-must-be-0-or-1" + elif ! ethv_is_uint "$ethv_vqtc_count" || + ! ethv_is_uint "$ethv_vqtc_wait"; then + QPS615_CONFIG_ERROR="ping-count-and-wait-must-be-unsigned-integers" + elif ! awk \ + -v count="$ethv_vqtc_count" \ + -v wait_seconds="$ethv_vqtc_wait" ' + BEGIN { + exit !(count >= 1 && count <= 20 && + wait_seconds >= 1 && wait_seconds <= 5) + } + '; then + QPS615_CONFIG_ERROR="ping-count-or-wait-out-of-range" + elif [ -n "$ethv_vqtc_peer" ] && + ! ethv_valid_peer_spec "$ethv_vqtc_peer"; then + QPS615_CONFIG_ERROR="peer-must-be-unicast-ipv4-ipv6-or-hostname" + else + case "$ethv_vqtc_interfaces" in + ""|all) + ;; + ,*|*,|*,,*|*[!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:,-]*) + QPS615_CONFIG_ERROR="interface-list-is-malformed" + ;; + esac + fi + + export QPS615_CONFIG_ERROR + [ -z "$QPS615_CONFIG_ERROR" ] +} + # ethv_get_driver # Query the bound network driver through ethtool or the sysfs driver link. # stdout: driver name, or an empty line when unavailable. @@ -708,6 +1612,27 @@ ethv_get_ipv4() { | awk 'NR == 1 { split($4, a, "/"); print a[1] }' } +# ethv_get_ipv6 +# Discover a ready unicast IPv6 address, preferring global over link scope. +# stdout: address without prefix, or nothing. return: 0 found, 1 absent or +# ip unavailable, 2 query/parse failure. Side effects: none. +ethv_get_ipv6() { + command -v ip >/dev/null 2>&1 || return 1 + ethv_gi6_inventory=$(ip -6 -o addr show dev "$1" 2>/dev/null) || return 2 + ethv_gi6_address=$(printf '%s\n' "$ethv_gi6_inventory" | awk ' + / tentative| dadfailed/ { next } + $3 == "inet6" { + split($4, address, "/") + if ($0 ~ /scope global/) { print address[1]; found = 1; exit } + if (fallback == "") fallback = address[1] + } + END { if (!found && fallback != "") print fallback } + ') + [ -n "$ethv_gi6_address" ] || return 1 + ethv_valid_unicast_ipv6 "$ethv_gi6_address" || return 2 + printf '%s\n' "$ethv_gi6_address" +} + # ethv_valid_ipv4
# Validate an IPv4 address and reject unspecified and link-local addresses. # stdout: none. @@ -741,6 +1666,260 @@ ethv_valid_ipv4() { ' } +# ethv_valid_unicast_ipv4
+# Validate one dotted-decimal IPv4 peer address suitable for unicast traffic. +# Rejects unspecified, link-local, loopback, multicast, and 240/4 addresses. +# stdout: none. return: 0 for a usable unicast peer, nonzero otherwise. +# Side effects: none. +ethv_valid_unicast_ipv4() { + ethv_vui_addr="${1:-}" + + # The legacy is_valid_ipv4 helper only rejects empty/link-local values. + # Validate the actual dotted-decimal structure before numeric comparisons. + printf '%s\n' "$ethv_vui_addr" | awk -F. ' + NF != 4 { exit 1 } + { for (i = 1; i <= 4; i++) + if ($i !~ /^[0-9]+$/ || $i > 255 || + (length($i) > 1 && substr($i, 1, 1) == "0")) exit 1 } + END { if (NR != 1) exit 1 } + ' || return 1 + case "$ethv_vui_addr" in + 169.254.*) + return 1 + ;; + esac + ethv_vui_first_octet=${ethv_vui_addr%%.*} + [ "$ethv_vui_first_octet" -ge 1 ] && + [ "$ethv_vui_first_octet" -ne 127 ] && + [ "$ethv_vui_first_octet" -lt 224 ] +} + +# ethv_normalize_ipv6
+# Expand an unscoped IPv6 literal to eight lowercase zero-padded hex groups. +# Supports embedded dotted-decimal tails. stdout: normalized IPv6 address. +# return: 0 valid syntax, 1 invalid. Side effects: none. +ethv_normalize_ipv6() { + printf '%s\n' "${1:-}" | LC_ALL=C awk ' + { + value = tolower($0) + if (value !~ /:/ || value ~ /[^0-9a-f:.]/) exit 1 + if (value ~ /\./) { + tail = value + sub(/^.*:/, "", tail) + if (split(tail, octets, ".") != 4) exit 1 + for (i = 1; i <= 4; i++) + if (octets[i] !~ /^[0-9]+$/ || octets[i] > 255 || + (length(octets[i]) > 1 && substr(octets[i], 1, 1) == "0")) exit 1 + sub(/[^:]*$/, "", value) + value = value sprintf("%x:%x", octets[1] * 256 + octets[2], octets[3] * 256 + octets[4]) + } + gap = index(value, "::") + if (gap) { + left = substr(value, 1, gap - 1) + right = substr(value, gap + 2) + if (index(right, "::")) exit 1 + lc = left == "" ? 0 : split(left, lhs, ":") + rc = right == "" ? 0 : split(right, rhs, ":") + if (lc + rc >= 8) exit 1 + count = 0 + for (i = 1; i <= lc; i++) groups[++count] = lhs[i] + for (i = 1; i <= 8 - lc - rc; i++) groups[++count] = "0" + for (i = 1; i <= rc; i++) groups[++count] = rhs[i] + } else if (split(value, groups, ":") != 8) exit 1 + result = "" + for (i = 1; i <= 8; i++) { + if (length(groups[i]) < 1 || length(groups[i]) > 4 || + groups[i] !~ /^[0-9a-f]+$/) exit 1 + padded = "0000" groups[i] + result = result (i == 1 ? "" : ":") substr(padded, length(padded) - 3) + } + valid = 1 + } + END { if (NR != 1 || !valid) exit 1; print result } + ' +} + +# ethv_valid_unicast_ipv6
+# Reject unspecified, loopback, multicast and IPv4-mapped IPv6 peers. +# stdout: none. return: 0 usable unicast, 1 invalid. Side effects: none. +ethv_valid_unicast_ipv6() { + ethv_vu6_normal=$(ethv_normalize_ipv6 "${1:-}") || return 1 + case "$ethv_vu6_normal" in + 0000:0000:0000:0000:0000:0000:0000:0000|0000:0000:0000:0000:0000:0000:0000:0001|ff*|0000:0000:0000:0000:0000:ffff:*) + return 1 + ;; + esac + return 0 +} + +# ethv_valid_peer_spec +# Accept unicast IPv4/IPv6 or an ASCII DNS hostname, optionally absolute. +# IPv6 scope is supplied by the selected interface, not a percent suffix. +# Reject numeric address shorthand, options, URLs and malformed labels. +# stdout: none. return: 0 valid, 1 invalid. Side effects: none. +ethv_valid_peer_spec() { + case "${1:-}" in + *:*) + ethv_valid_unicast_ipv6 "$1" + ;; + ""|*[!0-9.]*) + printf '%s\n' "${1:-}" | LC_ALL=C awk ' + { + sub(/\.$/, "") + if (length($0) < 1 || length($0) > 253) exit 1 + count = split($0, labels, ".") + for (i = 1; i <= count; i++) { + if (length(labels[i]) < 1 || length(labels[i]) > 63 || + labels[i] !~ /^[A-Za-z0-9-]+$/ || + labels[i] ~ /^-/ || labels[i] ~ /-$/) exit 1 + } + } + END { if (NR != 1) exit 1 } + ' + ;; + *) + ethv_valid_unicast_ipv4 "$1" + ;; + esac +} + +# ethv_resolve_traffic_peer +# Select the interface gateway or resolve an explicit peer through system NSS. +# Each getent query is bounded to 10 seconds. Never probe other peer targets. +# stdout: none. return: 0 ready, 1 missing/unsafe peer or discovery failure. +# Side effects: writes discovery artifacts and sets ETHV_PEER_ADDRESS, +# ETHV_PEER_SOURCE, ETHV_PEER_FAMILY and ETHV_PEER_REASON. IPv4 is preferred. +# Does not change network configuration. +ethv_resolve_traffic_peer() { + ethv_rtp_iface="$1" + ethv_rtp_requested="$2" + ethv_rtp_prefix="$3" + ETHV_PEER_ADDRESS="" + ETHV_PEER_SOURCE="interface-gateway" + ETHV_PEER_FAMILY=4 + ETHV_PEER_REASON="" + export ETHV_PEER_ADDRESS ETHV_PEER_SOURCE ETHV_PEER_FAMILY ETHV_PEER_REASON + + if [ -z "$ethv_rtp_requested" ]; then + if ! ETHV_PEER_ADDRESS=$(ethv_get_default_gateway "$ethv_rtp_iface" 4); then + ETHV_PEER_REASON="IPv4 route discovery failed on $ethv_rtp_iface, verify image-provided ip and route access" + return 1 + fi + if [ -z "$ETHV_PEER_ADDRESS" ]; then + if ! ETHV_PEER_ADDRESS=$(ethv_get_default_gateway "$ethv_rtp_iface" 6); then + ETHV_PEER_REASON="No IPv4 gateway and IPv6 route discovery failed on $ethv_rtp_iface, configure a route or supply --peer" + return 1 + fi + ETHV_PEER_FAMILY=6 + fi + if [ -z "$ETHV_PEER_ADDRESS" ]; then + ETHV_PEER_REASON="No IPv4 or IPv6 default gateway on $ethv_rtp_iface, provision DHCP/RA/static routing or supply --peer with a reachable hostname or IP address" + return 1 + fi + elif ! ethv_valid_peer_spec "$ethv_rtp_requested"; then + ETHV_PEER_REASON="Invalid peer $ethv_rtp_requested, supply a unicast IPv4/IPv6 address or DNS hostname" + return 1 + elif ethv_valid_unicast_ipv4 "$ethv_rtp_requested"; then + ETHV_PEER_SOURCE="explicit-ipv4" + ETHV_PEER_ADDRESS="$ethv_rtp_requested" + elif ethv_valid_unicast_ipv6 "$ethv_rtp_requested"; then + ETHV_PEER_SOURCE="explicit-ipv6" + ETHV_PEER_FAMILY=6 + ETHV_PEER_ADDRESS="$ethv_rtp_requested" + else + ETHV_PEER_SOURCE="explicit-hostname" + if ! command -v getent >/dev/null 2>&1; then + ETHV_PEER_REASON="Hostname $ethv_rtp_requested requires image-provided getent and configured DNS/NSS, or supply --peer with an IP address" + return 1 + fi + for ethv_rtp_database in ahostsv4 hosts ahostsv6; do + ethv_rtp_status=0 + LC_ALL=C run_with_managed_timeout 10 "$(dirname "$ethv_rtp_prefix")" peer-resolution \ + getent "$ethv_rtp_database" "$ethv_rtp_requested" \ + >"${ethv_rtp_prefix}_${ethv_rtp_database}.log" \ + 2>"${ethv_rtp_prefix}_${ethv_rtp_database}.err" || ethv_rtp_status=$? + if [ "$ethv_rtp_status" -eq 124 ]; then + ETHV_PEER_REASON="Resolution of $ethv_rtp_requested timed out, verify DNS/NSS availability, artifact=${ethv_rtp_prefix}_${ethv_rtp_database}.err" + return 1 + fi + [ "$ethv_rtp_status" -eq 0 ] || continue + ETHV_PEER_FAMILY=4 + ETHV_PEER_ADDRESS=$(awk '$1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ { print $1; exit }' \ + "${ethv_rtp_prefix}_${ethv_rtp_database}.log") + if [ -z "$ETHV_PEER_ADDRESS" ] && [ "$ethv_rtp_database" != ahostsv4 ]; then + ETHV_PEER_ADDRESS=$(awk '$1 ~ /:/ { print $1; exit }' \ + "${ethv_rtp_prefix}_${ethv_rtp_database}.log") + ETHV_PEER_FAMILY=6 + fi + [ -z "$ETHV_PEER_ADDRESS" ] || break + done + if [ -z "$ETHV_PEER_ADDRESS" ]; then + ETHV_PEER_REASON="No IP result for $ethv_rtp_requested, verify DHCP DNS registration or configured mDNS/NSS support, artifacts=${ethv_rtp_prefix}_*.log" + return 1 + fi + fi + + if [ "$ETHV_PEER_FAMILY" = 6 ]; then + ethv_rtp_valid=0 + ethv_valid_unicast_ipv6 "$ETHV_PEER_ADDRESS" || ethv_rtp_valid=1 + else + ethv_rtp_valid=0 + ethv_valid_unicast_ipv4 "$ETHV_PEER_ADDRESS" || ethv_rtp_valid=1 + fi + if [ "$ethv_rtp_valid" -ne 0 ]; then + ETHV_PEER_REASON="Peer $ETHV_PEER_ADDRESS is not a usable unicast IPv$ETHV_PEER_FAMILY address, supply a reachable external peer" + return 1 + fi + + # Enumerate all addresses, including secondary addresses and other ports. + # A local-address ping must never stand in for traffic through the fixture. + if command -v ip >/dev/null 2>&1; then + if ! ip "-$ETHV_PEER_FAMILY" -o addr show >"${ethv_rtp_prefix}_addresses.log" 2>"${ethv_rtp_prefix}_addresses.err"; then + ETHV_PEER_REASON="Cannot inventory DUT IPv$ETHV_PEER_FAMILY addresses with ip, inspect ${ethv_rtp_prefix}_addresses.err" + return 1 + fi + ethv_rtp_local=$(awk '{ for (i = 1; i < NF; i++) if ($i == "inet" || $i == "inet6") { split($(i + 1), address, "/"); print address[1] } }' \ + "${ethv_rtp_prefix}_addresses.log") + if ! awk -v iface="$ethv_rtp_iface" ' + $2 == iface && $0 !~ / tentative| dadfailed/ { found = 1 } + END { exit !found } + ' "${ethv_rtp_prefix}_addresses.log"; then + ETHV_PEER_REASON="No ready IPv$ETHV_PEER_FAMILY address on $ethv_rtp_iface, configure DHCP/RA/static addressing and allow duplicate-address detection to complete" + return 1 + fi + elif [ "$ETHV_PEER_FAMILY" = 4 ] && command -v ifconfig >/dev/null 2>&1; then + if ! LC_ALL=C ifconfig -a >"${ethv_rtp_prefix}_addresses.log" 2>"${ethv_rtp_prefix}_addresses.err"; then + ETHV_PEER_REASON="Cannot inventory DUT IPv4 addresses with ifconfig, inspect ${ethv_rtp_prefix}_addresses.err" + return 1 + fi + ethv_rtp_local=$(awk '{ for (i = 1; i < NF; i++) if ($i == "inet") { value = $(i + 1); sub(/^addr:/, "", value); print value } }' \ + "${ethv_rtp_prefix}_addresses.log") + else + ETHV_PEER_REASON="Cannot inventory IPv$ETHV_PEER_FAMILY addresses, provision ip in the image, IPv4 also supports ifconfig" + return 1 + fi + if [ -z "$ethv_rtp_local" ]; then + ETHV_PEER_REASON="No DUT IP addresses could be parsed, inspect ${ethv_rtp_prefix}_addresses.log and configure the selected interface" + return 1 + fi + ethv_rtp_compare="$ETHV_PEER_ADDRESS" + if [ "$ETHV_PEER_FAMILY" = 6 ]; then + ethv_rtp_compare=$(ethv_normalize_ipv6 "$ETHV_PEER_ADDRESS") || return 1 + ethv_rtp_local=$(printf '%s\n' "$ethv_rtp_local" | while IFS= read -r ethv_rtp_address; do + ethv_normalize_ipv6 "$ethv_rtp_address" || exit 1 + done) || { + ETHV_PEER_REASON="Invalid IPv6 address inventory, inspect ${ethv_rtp_prefix}_addresses.log" + return 1 + } + fi + if printf '%s\n' "$ethv_rtp_local" | grep -Fxq "$ethv_rtp_compare"; then + ETHV_PEER_REASON="Peer $ETHV_PEER_ADDRESS belongs to the DUT and cannot validate external QPS615 traffic, select a nonlocal peer" + return 1 + fi + ETHV_PEER_REASON="Selected $ETHV_PEER_ADDRESS from $ETHV_PEER_SOURCE for $ethv_rtp_iface" + return 0 +} + # ethv_get_carrier # Read the kernel carrier state for an interface. # stdout: 1, 0, or unknown when the attribute cannot be read. @@ -937,14 +2116,23 @@ ethv_prepare_interface() { return 0 } -# ethv_get_default_gateway -# Find the first IPv4 default gateway routed through an interface. -# stdout: gateway IPv4 address, or no output when no route exists. -# return: status of the route and awk pipeline. +# ethv_get_default_gateway [4|6] +# Find the first default gateway routed through an interface, IPv4 by default. +# stdout: gateway address, or no output when no route exists. +# return: 0 on successful discovery, nonzero on invalid family or route error. ethv_get_default_gateway() { ethv_gdg_iface="${1:-}" - ip -4 route show default dev "$ethv_gdg_iface" 2>/dev/null \ + ethv_gdg_family="${2:-4}" + case "$ethv_gdg_family" in + 4|6) + ;; + *) + return 1 + ;; + esac + ethv_gdg_routes=$(ip "-$ethv_gdg_family" route show default dev "$ethv_gdg_iface" 2>/dev/null) || return 1 + printf '%s\n' "$ethv_gdg_routes" \ | awk 'NR == 1 { for (i = 1; i <= NF; i++) if ($i == "via") { print $(i + 1); exit } }' } @@ -971,22 +2159,155 @@ ethv_select_ping_target() { fi } -# ethv_ping_interface [count] [wait-seconds] +# ethv_ping_interface [count] [wait-seconds] [4|6] # Run ping with traffic explicitly bound to the requested interface. -# stdout/stderr: unmodified ping output. +# stdout/stderr: C-locale ping output for portable result parsing. # return: ping command exit status. ethv_ping_interface() { ethv_pi_iface="${1:-}" ethv_pi_target="${2:-}" ethv_pi_count="${3:-4}" ethv_pi_wait="${4:-2}" + ethv_pi_family="${5:-4}" - ping -I "$ethv_pi_iface" \ + case "$ethv_pi_family" in + 4|6) + ;; + *) + return 2 + ;; + esac + + if [ "$ethv_pi_family" = 6 ]; then + if command -v ping6 >/dev/null 2>&1; then + LC_ALL=C ping6 -n -I "$ethv_pi_iface" -c "$ethv_pi_count" -W "$ethv_pi_wait" "$ethv_pi_target" + else + LC_ALL=C ping -6 -n -I "$ethv_pi_iface" -c "$ethv_pi_count" -W "$ethv_pi_wait" "$ethv_pi_target" + fi + return $? + fi + + LC_ALL=C ping -I "$ethv_pi_iface" \ -c "$ethv_pi_count" \ -W "$ethv_pi_wait" \ "$ethv_pi_target" } +# ethv_capture_driver_statistics +# Capture every unsigned counter reported by the DUT's ethtool -S output. +# stdout: none. return: 0 parsed, 1 command/data failure, 2 optional capability +# absent, 3 invalid input. Side effects: writes .log/.tsv artifacts and exports +# ETHV_DRIVER_STATS_REASON. Never treats an unknown driver field as an error. +ethv_capture_driver_statistics() { + ethv_cds_iface="${1:-}" + ethv_cds_prefix="${2:-}" + ETHV_DRIVER_STATS_REASON="" + export ETHV_DRIVER_STATS_REASON + if [ -z "$ethv_cds_iface" ] || [ -z "$ethv_cds_prefix" ]; then + ETHV_DRIVER_STATS_REASON="Driver statistics require an interface and artifact prefix" + return 3 + fi + case "$ethv_cds_iface" in + *[!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:-]*) + ETHV_DRIVER_STATS_REASON="Invalid interface name for driver-statistics capture" + return 3 + ;; + esac + if ! mkdir -p "$(dirname "$ethv_cds_prefix")" || + ! { + : >"$ethv_cds_prefix.tsv" && + : >"$ethv_cds_prefix.log" + }; then + ETHV_DRIVER_STATS_REASON="Cannot write driver-statistics artifacts at $ethv_cds_prefix, provide a writable results directory" + return 1 + fi + if ! command -v ethtool >/dev/null 2>&1; then + ETHV_DRIVER_STATS_REASON="ethtool is not image-provided" + return 2 + fi + ethv_cds_status=0 + LC_ALL=C run_with_managed_timeout 10 "$(dirname "$ethv_cds_prefix")" driver-statistics \ + ethtool -S "$ethv_cds_iface" >"$ethv_cds_prefix.log" 2>&1 || ethv_cds_status=$? + if [ "$ethv_cds_status" -ne 0 ]; then + if [ "$ethv_cds_status" -ne 124 ] && + grep -Eiq 'operation not supported|not supported|no stats available' "$ethv_cds_prefix.log"; then + ETHV_DRIVER_STATS_REASON="Driver statistics are not supported on $ethv_cds_iface" + return 2 + fi + ETHV_DRIVER_STATS_REASON="ethtool -S failed on $ethv_cds_iface with status $ethv_cds_status, inspect $ethv_cds_prefix.log" + return 1 + fi + ethv_cds_status=0 + awk ' + /^[[:space:]]*NIC statistics:[[:space:]]*$/ || /^[[:space:]]*$/ { next } + { + colon = index($0, ":") + if (!colon) { bad=1; next } + name = substr($0, 1, colon-1) + value = substr($0, colon+1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", name) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) + if (name == "" || name ~ /[[:cntrl:]]/ || value !~ /^[0-9]+$/ || seen[name]++) { + bad=1 + next + } + printf "%s\t%s\n", name, value + count++ + } + END { if (bad) exit 1; if (!count) exit 2 } + ' "$ethv_cds_prefix.log" >"$ethv_cds_prefix.tsv" || ethv_cds_status=$? + if [ "$ethv_cds_status" -ne 0 ]; then + : >"$ethv_cds_prefix.tsv" + ETHV_DRIVER_STATS_REASON="No usable driver-statistics inventory on $ethv_cds_iface, inspect $ethv_cds_prefix.log" + return "$ethv_cds_status" + fi + ETHV_DRIVER_STATS_REASON="Driver statistics captured from $ethv_cds_iface in $ethv_cds_prefix.tsv" + return 0 +} + +# ethv_compare_driver_statistics +# Compare dynamically discovered counters by name, including additions and +# removals. Values are compared as strings to preserve full 64-bit precision. +# stdout: none. return: 0 compared, nonzero on invalid/unreadable evidence. +# Side effects: writes name, before, after, and change type to output-tsv. +# Exports ETHV_DRIVER_STATS_REASON when input/output evidence is unavailable. +# Driver fields can be gauges or reset-on-read, so this is diagnostic only. +ethv_compare_driver_statistics() { + if [ "$#" -ne 3 ] || [ ! -r "$1" ] || [ ! -r "$2" ]; then + ETHV_DRIVER_STATS_REASON="Driver-counter comparison requires readable before/after inventories and an output path" + export ETHV_DRIVER_STATS_REASON + return 1 + fi + if ! awk -F '\t' ' + FILENAME == ARGV[1] { before[$1]=$2; order[++count]=$1; next } + { + after[$1]=$2 + after_order[++after_count]=$1 + } + END { + for (i=1; i<=count; i++) { + name=order[i] + state="missing" + value="unavailable" + if (name in after) { + value=after[name] + state=("v" before[name] == "v" value) ? "unchanged" : "changed" + } + printf "%s\t%s\t%s\t%s\n", name, before[name], value, state + } + for (i=1; i<=after_count; i++) { + name=after_order[i] + if (!(name in before)) printf "%s\tunavailable\t%s\tadded\n", name, after[name] + } + } + ' "$1" "$2" >"$3"; then + ETHV_DRIVER_STATS_REASON="Cannot compare driver-counter inventories $1 and $2 into $3, check the retained files and output permissions" + export ETHV_DRIVER_STATS_REASON + return 1 + fi + return 0 +} + # ethv_get_counter # Read an unsigned standard network counter from sysfs. # stdout: counter value, or 0 when unavailable or malformed. @@ -1007,6 +2328,48 @@ ethv_get_counter() { printf '%s\n' 0 } +# ethv_counter_available +# Verify that a standard network counter is readable and contains an +# unsigned decimal value. stdout: none. return: 0 when usable and 1 when the +# interface, counter, or value is unavailable. Side effects: none. +ethv_counter_available() { + ethv_ca_iface="${1:-}" + ethv_ca_counter="${2:-}" + ethv_ca_file="/sys/class/net/$ethv_ca_iface/statistics/$ethv_ca_counter" + + [ -n "$ethv_ca_iface" ] && [ -n "$ethv_ca_counter" ] || return 1 + [ -r "$ethv_ca_file" ] || return 1 + if ! ethv_ca_value=$(ethv_read_first_line "$ethv_ca_file" 2>/dev/null); then + return 1 + fi + ethv_is_uint "$ethv_ca_value" +} + +# ethv_counters_available ... +# Verify a required set of standard network counters without emitting +# stdout. return: 0 when every counter is usable, 1 when any counter is +# unavailable, or 3 when no counter is supplied. Side effects: exports +# ETHV_COUNTER_FAILURE with the first unavailable counter name. +ethv_counters_available() { + ETHV_COUNTER_FAILURE="" + export ETHV_COUNTER_FAILURE + [ "$#" -gt 0 ] || return 3 + ethv_cas_iface="${1:-}" + shift + + [ -n "$ethv_cas_iface" ] && [ "$#" -gt 0 ] || return 3 + for ethv_cas_counter in "$@"; do + if ! ethv_counter_available "$ethv_cas_iface" "$ethv_cas_counter"; then + ETHV_COUNTER_FAILURE="$ethv_cas_counter" + export ETHV_COUNTER_FAILURE + return 1 + fi + done + + export ETHV_COUNTER_FAILURE + return 0 +} + # ethv_counter_delta # Calculate a monotonic unsigned counter delta. # stdout: after minus before, or -1 for invalid input or counter rollback. diff --git a/Runner/utils/lib_module_reload.sh b/Runner/utils/lib_module_reload.sh index 46693eea..0e05dcaf 100755 --- a/Runner/utils/lib_module_reload.sh +++ b/Runner/utils/lib_module_reload.sh @@ -1575,6 +1575,14 @@ mrv_run_one_profile() ( # shellcheck disable=SC1090 . "$profile_file" + # Optional load-only validation is owned by the profile and must preserve + # initially active modules. Existing reload profiles retain their old path. + if [ "${requested_mode:-$PROFILE_MODE_DEFAULT}" = "load_only" ] && + mrv_profile_override_defined profile_load_only; then + profile_load_only "$RESULT_ROOT/$PROFILE_NAME/load_only/run-$$" + exit $? + fi + mrv_profile_check "$requested_mode" check_rc=$? if [ "$check_rc" -eq 2 ]; then diff --git a/Runner/utils/lib_system.sh b/Runner/utils/lib_system.sh index 36b5f14b..d1792720 100755 --- a/Runner/utils/lib_system.sh +++ b/Runner/utils/lib_system.sh @@ -281,6 +281,27 @@ efi_install_restore_trap() { trap efi_restore_efivarfs_ro EXIT HUP INT TERM } +# efi_ensure_mounted +# Mount efivarfs only when initially absent at the existing EFIVARFS_PATH. +# stdout: diagnostics. return: 0 when mounted, 1 on failure. Side effects: +# leaves a newly created mount available after preparation completes. +efi_ensure_mounted() { + eem_log="${1:-}" + [ -n "$eem_log" ] || return 1 + if efi_mount_exists; then + return 0 + fi + if [ ! -d "$EFIVARFS_PATH" ]; then + log_warn "EFI variable storage directory is absent at $EFIVARFS_PATH, boot with EFI runtime services enabled" + return 1 + fi + log_info "Mounting efivarfs at $EFIVARFS_PATH, leaving it mounted after preparation" + if ! mount -t efivarfs none "$EFIVARFS_PATH" >>"$eem_log" 2>&1; then + return 1 + fi + efi_mount_exists +} + # Try to temporarily remount efivarfs read-write. # # Return 0 only if efivarfs becomes read-write. @@ -348,6 +369,8 @@ efi_variable_list_contains() { # $2 - file that receives efivar list output # Diagnostic output is written to stderr so command-substitution callers only # receive the variable name. +# Returns 0 for a unique match, 1 for absent or ambiguous names, and 2 when +# listing cannot complete. The retained log is authoritative only for 0 or 1. efi_find_variable_by_name() { efvbn_name="$1" efvbn_log_file="$2" @@ -356,17 +379,17 @@ efi_find_variable_by_name() { if [ -z "$efvbn_name" ] || [ -z "$efvbn_log_file" ]; then printf '%s\n' "efi_find_variable_by_name requires variable name and log file" >&2 - return 1 + return 2 fi if ! command -v efivar >/dev/null 2>&1; then printf '%s\n' "efivar command is unavailable" >&2 - return 1 + return 2 fi if ! efivar -l > "$efvbn_log_file" 2>&1; then printf '%s\n' "efivar could not list EFI variables" >&2 - return 1 + return 2 fi efvbn_matches="$(awk -v suffix="-$efvbn_name" ' @@ -390,6 +413,9 @@ efi_find_variable_by_name() { # $1 - EFI variable name in GUID-Name form # $2 - expected text payload, without a trailing newline # $3 - file that receives efivar print output +# Returns 0 for a match, 1 for a different payload, and 2 for a read or +# inspection error. Boolean callers retain their existing behavior. +# Scratch files use the caller's writable log directory, not inherited TMPDIR. efi_text_variable_matches() { etvm_var_name="$1" etvm_value="$2" @@ -400,28 +426,28 @@ efi_text_variable_matches() { if [ -z "$etvm_var_name" ] || [ -z "$etvm_value" ] || [ -z "$etvm_log_file" ]; then log_warn "efi_text_variable_matches requires variable name, value, and log file" - return 1 + return 2 fi if ! command -v efivar >/dev/null 2>&1; then log_warn "efivar command is unavailable" - return 1 + return 2 fi if ! efivar -n "$etvm_var_name" -p > "$etvm_log_file" 2>&1; then - return 1 + return 2 fi - etvm_data_file="$(mktemp "${TMPDIR:-/tmp}/efivar_payload.XXXXXX" 2>/dev/null || true)" + etvm_data_file="$(mktemp "$(dirname "$etvm_log_file")/efivar_payload.XXXXXX" 2>/dev/null || true)" if [ -z "$etvm_data_file" ]; then - log_warn "Could not create temporary EFI variable payload" - return 1 + log_warn "Could not create EFI payload beside $etvm_log_file, check directory permissions and free space" + return 2 fi if ! printf '%s' "$etvm_value" > "$etvm_data_file"; then log_warn "Could not write temporary EFI variable payload" rm -f "$etvm_data_file" - return 1 + return 2 fi etvm_expected_bytes="$(od -An -tx1 -v "$etvm_data_file" 2>/dev/null | tr '\n' ' ' | tr -s ' ' | sed 's/^ //; s/ $//')" @@ -429,7 +455,7 @@ efi_text_variable_matches() { if [ -z "$etvm_expected_bytes" ]; then log_warn "Could not derive EFI variable payload bytes" - return 1 + return 2 fi etvm_expected_pattern="$(printf '%s\n' "$etvm_expected_bytes" | sed 's/ /[[:space:]][[:space:]]*/g')" @@ -443,6 +469,7 @@ efi_text_variable_matches() { # $3 - file that receives efivar write and print output # The caller is responsible for arranging efi_restore_efivarfs_ro during # cleanup when efi_try_remount_rw changes the mount state. +# Scratch files use the caller's writable log directory, not inherited TMPDIR. efi_write_text_variable() { ewtv_var_name="$1" ewtv_value="$2" @@ -464,9 +491,9 @@ efi_write_text_variable() { return 1 fi - ewtv_data_file="$(mktemp "${TMPDIR:-/tmp}/efivar_payload.XXXXXX" 2>/dev/null || true)" + ewtv_data_file="$(mktemp "$(dirname "$ewtv_log_file")/efivar_payload.XXXXXX" 2>/dev/null || true)" if [ -z "$ewtv_data_file" ]; then - log_warn "Could not create temporary EFI variable payload" + log_warn "Could not create EFI payload beside $ewtv_log_file, check directory permissions and free space" return 1 fi