diff --git a/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/DMAEngine_Data_Integrity_Validation.yaml b/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/DMAEngine_Data_Integrity_Validation.yaml new file mode 100644 index 00000000..b5aa70fc --- /dev/null +++ b/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/DMAEngine_Data_Integrity_Validation.yaml @@ -0,0 +1,21 @@ +metadata: + name: DMAEngine_Data_Integrity_Validation + format: "Lava-Test Test Definition 1.0" + description: "Run bounded DMAEngine memcpy operations with source and destination data verification" + os: + - linux + scope: + - functional + +params: + DMA_CHANNEL: "auto" + ITERATIONS: "20" + TIMEOUT_MS: "3000" + RUN_TIMEOUT_SECONDS: "90" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation + - ./run.sh --channel "${DMA_CHANNEL:-auto}" --iterations "${ITERATIONS:-20}" --timeout-ms "${TIMEOUT_MS:-3000}" --run-timeout "${RUN_TIMEOUT_SECONDS:-90}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh DMAEngine_Data_Integrity_Validation.res diff --git a/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/README.md b/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/README.md new file mode 100644 index 00000000..647109c0 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/README.md @@ -0,0 +1,37 @@ +# DMAEngine data-integrity validation + +`DMAEngine_Data_Integrity_Validation` uses the upstream kernel `dmatest` +driver to perform bounded DMA memcpy operations with verification enabled. +`dmatest` initializes source and destination buffers with different patterns, +executes DMA transfers at varied offsets and lengths, and verifies the copied +region, untouched bytes, byte ordering, and source-buffer integrity. + +This is stronger evidence of a working DMA data path than traffic counters +alone. It can expose DMA driver, mapping, cache-maintenance, or data-corruption +problems, but it does not identify CCI as the unique cause of a failure. + +## Options + +```text +--channel NAME|auto +--iterations N +--timeout-ms N +--run-timeout N +``` + +The equivalent LAVA environment variables are `DMA_CHANNEL`, `ITERATIONS`, +`TIMEOUT_MS`, and `RUN_TIMEOUT_SECONDS`. Command-line values take precedence. + +With `auto`, the suite selects the first idle channel accepted by `dmatest`. +Use an explicit channel when the platform reserves particular DMA channels for +validation. + +The suite skips when no suitable idle channel exists or `dmatest` is absent +for the running kernel. Modules found only under a different kernel tree are +not loaded. +It refuses to interrupt an existing dmatest run or configured test list. If it +loads the module, it unloads only that module. For a pre-existing idle module, +it restores scalar parameters after releasing the selected channel. + +Kernel log snapshots and the new dmatest result lines are retained for LAVA +triage. No package is installed at runtime. diff --git a/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/run.sh b/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/run.sh new file mode 100755 index 00000000..4ef6fecc --- /dev/null +++ b/Runner/suites/Kernel/Baseport/DMAEngine_Data_Integrity_Validation/run.sh @@ -0,0 +1,356 @@ +#!/bin/sh + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- 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" + +TESTNAME="DMAEngine_Data_Integrity_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +DMA_CHANNEL="${DMATEST_CHANNEL:-auto}" +DMA_ITERATIONS="${DMATEST_ITERATIONS:-20}" +DMA_TIMEOUT_MS="${DMATEST_TIMEOUT_MS:-3000}" +RUN_TIMEOUT_SECONDS="${DMATEST_RUN_TIMEOUT_SECONDS:-90}" + +DMATEST_DIR="/sys/module/dmatest/parameters" +LOADED_BY_TEST=0 +PARAMETERS_CHANGED=0 + +# usage +# Prints supported command-line options and returns success. +usage() { + printf '%s\n' "Usage: $0 [--channel NAME|auto] [--iterations N] [--timeout-ms N] [--run-timeout N]" +} + +# parse_args +# Parses command-line overrides and exits for help or invalid input. +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --channel) + [ "$#" -ge 2 ] || { + usage >&2 + exit 2 + } + DMA_CHANNEL="$2" + shift 2 + ;; + --iterations) + [ "$#" -ge 2 ] || { + usage >&2 + exit 2 + } + DMA_ITERATIONS="$2" + shift 2 + ;; + --timeout-ms) + [ "$#" -ge 2 ] || { + usage >&2 + exit 2 + } + DMA_TIMEOUT_MS="$2" + shift 2 + ;; + --run-timeout) + [ "$#" -ge 2 ] || { + usage >&2 + exit 2 + } + RUN_TIMEOUT_SECONDS="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + log_fail "Unknown argument: $1" + usage >&2 + exit 2 + ;; + esac + done +} + +# snapshot_parameters +# Saves the dmatest parameters changed by this suite as "name|value" records. Returns 0 +# when the parameters directory exists, otherwise 1. +snapshot_parameters() { + output_file="$1" + [ -d "$DMATEST_DIR" ] || return 1 + : >"$output_file" + + for parameter_name in timeout iterations threads_per_chan noverify verbose channel; do + parameter_path="$DMATEST_DIR/$parameter_name" + [ -r "$parameter_path" ] || continue + parameter_value=$(tr -d '\r\n' <"$parameter_path") + printf '%s|%s\n' "$parameter_name" "$parameter_value" >>"$output_file" + done + + return 0 +} + +# restore_parameters +# Restores previously saved dmatest scalar parameters. Returns success even if +# individual read-only parameters cannot be restored, after logging warnings. +# shellcheck disable=SC2317 +restore_parameters() { + snapshot_file="$1" + [ -r "$snapshot_file" ] || return 0 + [ -d "$DMATEST_DIR" ] || return 0 + + while IFS='|' read -r parameter_name parameter_value; do + [ -n "$parameter_name" ] || continue + parameter_path="$DMATEST_DIR/$parameter_name" + [ -w "$parameter_path" ] || continue + if ! printf '%s\n' "$parameter_value" >"$parameter_path" 2>/dev/null; then + log_warn "Could not restore dmatest parameter $parameter_name" + fi + done <"$snapshot_file" +} + +# cleanup +# Stops a test started by this suite, restores pre-existing parameter values, +# and unloads dmatest only when this suite loaded it. Returns success. +# shellcheck disable=SC2317 +cleanup() { + if [ "$PARAMETERS_CHANGED" -eq 1 ] && [ -w "$DMATEST_DIR/run" ]; then + printf '%s\n' 0 >"$DMATEST_DIR/run" 2>/dev/null || true + fi + + if [ "$LOADED_BY_TEST" -eq 1 ]; then + unload_kernel_module dmatest false || log_warn "Could not unload dmatest loaded by this suite" + else + restore_parameters "$SCRIPT_DIR/dmatest_parameters_before.log" + fi +} + +# configure_channel +# Requests one DMA channel from dmatest and verifies the accepted channel +# through the channel parameter. Returns 0 when accepted, otherwise 1. +configure_channel() { + channel_name="$1" + [ -n "$channel_name" ] || return 1 + + if ! printf '%s\n' "$channel_name" >"$DMATEST_DIR/channel" 2>/dev/null; then + return 1 + fi + + configured_channel=$(tr -d '\r\n' <"$DMATEST_DIR/channel" 2>/dev/null || true) + if [ "$configured_channel" = "$channel_name" ]; then + return 0 + fi + + return 1 +} + +# select_dma_channel +# Prints and configures the requested channel, or the first idle channel that +# dmatest accepts when auto-selection is requested. Returns 0 on success. +select_dma_channel() { + if [ "$DMA_CHANNEL" != "auto" ]; then + [ -d "/sys/class/dma/$DMA_CHANNEL" ] || return 1 + if [ -r "/sys/class/dma/$DMA_CHANNEL/in_use" ] && + [ "$(tr -d '[:space:]' <"/sys/class/dma/$DMA_CHANNEL/in_use")" != "0" ]; then + return 1 + fi + configure_channel "$DMA_CHANNEL" || return 1 + printf '%s\n' "$DMA_CHANNEL" + return 0 + fi + + for channel_dir in /sys/class/dma/*; do + [ -d "$channel_dir" ] || continue + channel_name=$(basename "$channel_dir") + if [ -r "$channel_dir/in_use" ] && + [ "$(tr -d '[:space:]' <"$channel_dir/in_use")" != "0" ]; then + continue + fi + if configure_channel "$channel_name"; then + printf '%s\n' "$channel_name" + return 0 + fi + done + + return 1 +} + +parse_args "$@" + +for numeric_value in "$DMA_ITERATIONS" "$DMA_TIMEOUT_MS" "$RUN_TIMEOUT_SECONDS"; do + case "$numeric_value" in + ''|*[!0-9]*|0) + log_fail "Iterations, timeout, and run-timeout values must be positive integers" + exit 2 + ;; + esac +done + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 +rm -f \ + "$SCRIPT_DIR/dmatest_parameters_before.log" \ + "$SCRIPT_DIR/dmatest_new.log" + +trap cleanup 0 +trap 'exit 130' 1 2 15 + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "Configuration: channel=$DMA_CHANNEL iterations=$DMA_ITERATIONS timeout_ms=$DMA_TIMEOUT_MS run_timeout_seconds=$RUN_TIMEOUT_SECONDS" + +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies awk grep sed wc tr basename sleep cp; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi + +if [ ! -d /sys/class/dma ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: the DMAEngine class is not available" +fi + +scan_dmesg_errors \ + "$SCRIPT_DIR/dmesg_before" \ + "dmatest|dmaengine|dma" \ + "No errors|0 failures" || true +baseline_lines=$(wc -l <"$SCRIPT_DIR/dmesg_before/dmesg_snapshot.log" 2>/dev/null || printf '%s\n' 0) + +if [ -d "$DMATEST_DIR" ]; then + snapshot_parameters "$SCRIPT_DIR/dmatest_parameters_before.log" || true + if [ -r "$DMATEST_DIR/run" ] && + grep -Eq '^(Y|1)$' "$DMATEST_DIR/run" 2>/dev/null; then + test_result_finish "SKIP" "$TESTNAME SKIP: an existing dmatest run is active" + fi + configured_channel=$(tr -d '\r\n' <"$DMATEST_DIR/channel" 2>/dev/null || true) + if [ -n "$configured_channel" ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: dmatest already has configured channels" + fi +else + module_path=$(find_kernel_module dmatest) + if [ -n "$module_path" ]; then + case "$module_path" in + "/lib/modules/$(uname -r)/"*) + ;; + *) + log_warn "Ignoring dmatest module from a non-running kernel tree: $module_path" + module_path="" + ;; + esac + fi + if [ -z "$module_path" ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: dmatest is neither built in nor available for the running kernel" + fi + if ! load_kernel_module "$module_path"; then + test_result_finish "FAIL" "$TESTNAME FAIL: the image-provided dmatest module could not be loaded" + fi + LOADED_BY_TEST=1 +fi + +if [ ! -d "$DMATEST_DIR" ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: dmatest parameters are unavailable after driver activation" +fi + +for required_parameter in run channel iterations timeout noverify; do + if [ ! -w "$DMATEST_DIR/$required_parameter" ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: dmatest parameter is not writable: $required_parameter" + fi +done + +printf '%s\n' "$DMA_ITERATIONS" >"$DMATEST_DIR/iterations" +printf '%s\n' "$DMA_TIMEOUT_MS" >"$DMATEST_DIR/timeout" +printf '%s\n' N >"$DMATEST_DIR/noverify" +if [ -w "$DMATEST_DIR/threads_per_chan" ]; then + printf '%s\n' 1 >"$DMATEST_DIR/threads_per_chan" +fi +if [ -w "$DMATEST_DIR/verbose" ]; then + printf '%s\n' 1 >"$DMATEST_DIR/verbose" +fi +PARAMETERS_CHANGED=1 + +if ! selected_channel=$(select_dma_channel); then + test_result_finish "SKIP" "$TESTNAME SKIP: no idle DMA memcpy channel was accepted by dmatest" +fi +log_pass "DMA channel selected for verified memcpy testing: $selected_channel" + +printf '%s\n' 1 >"$DMATEST_DIR/run" + +elapsed=0 +while [ "$elapsed" -lt "$RUN_TIMEOUT_SECONDS" ]; do + run_state=$(tr -d '[:space:]' <"$DMATEST_DIR/run" 2>/dev/null || true) + case "$run_state" in + N|0) + break + ;; + esac + sleep 1 + elapsed=$((elapsed + 1)) +done + +if [ "$elapsed" -ge "$RUN_TIMEOUT_SECONDS" ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: dmatest did not complete within $RUN_TIMEOUT_SECONDS seconds" +fi + +scan_dmesg_errors \ + "$SCRIPT_DIR/dmesg_after" \ + "dmatest|dmaengine|dma" \ + "No errors|0 failures" || true + +after_snapshot="$SCRIPT_DIR/dmesg_after/dmesg_snapshot.log" +after_lines=$(wc -l <"$after_snapshot" 2>/dev/null || printf '%s\n' 0) +if [ "$after_lines" -ge "$baseline_lines" ]; then + first_new_line=$((baseline_lines + 1)) + sed -n "${first_new_line},\$p" "$after_snapshot" >"$SCRIPT_DIR/dmatest_new.log" +else + cp "$after_snapshot" "$SCRIPT_DIR/dmatest_new.log" +fi + +summary_count=$(grep -c 'dmatest: .*summary' "$SCRIPT_DIR/dmatest_new.log" 2>/dev/null || true) +summary_count=${summary_count:-0} +if [ "$summary_count" -eq 0 ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: no new dmatest completion summary was captured" +fi + +if grep -Eq "dmatest: .*summary .* [1-9][0-9]* failures|mismatch|timed out|timeout|corrupt" \ + "$SCRIPT_DIR/dmatest_new.log"; then + test_result_finish "FAIL" "$TESTNAME FAIL: DMA data-integrity errors were reported" +fi + +if ! grep -Eq 'dmatest: .*summary .* 0 failures' "$SCRIPT_DIR/dmatest_new.log"; then + test_result_finish "FAIL" "$TESTNAME FAIL: dmatest summaries did not report zero failures" +fi + +while IFS= read -r result_line; do + log_info "[DMATEST] $result_line" +done <"$SCRIPT_DIR/dmatest_new.log" + +test_result_finish "PASS" "$TESTNAME PASS: channel=$selected_channel summaries=$summary_count" diff --git a/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/Interconnect_Provider_Validation.yaml b/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/Interconnect_Provider_Validation.yaml new file mode 100644 index 00000000..cf9b677c --- /dev/null +++ b/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/Interconnect_Provider_Validation.yaml @@ -0,0 +1,15 @@ +metadata: + name: Interconnect_Provider_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate Qualcomm ICC provider discovery, driver binding, synchronization, and kernel health" + os: + - linux + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh Interconnect_Provider_Validation.res diff --git a/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/README.md b/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/README.md new file mode 100644 index 00000000..4fb9b252 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/README.md @@ -0,0 +1,25 @@ +# Qualcomm interconnect provider validation + +`Interconnect_Provider_Validation` verifies that enabled Qualcomm Linux +interconnect providers described by the runtime device tree have corresponding +platform devices, bind to a driver, and reach synchronized state when the +kernel exposes `state_synced`. + +The test discovers providers dynamically through `#interconnect-cells`. It +does not assume a SoC name, provider address, driver name, or node count. + +## Result policy + +- `PASS`: every discovered provider has a bound driver and every exposed + `state_synced` attribute contains `1`. +- `FAIL`: an enabled provider is missing, unbound, unsynchronized, or has a + persistent ICC or NoC kernel error. +- `SKIP`: no applicable Qualcomm ICC provider exists. An individual + `state_synced` check is also skipped when that optional ABI is unavailable. + +Early `-EPROBE_DEFER` (`-517`) messages are not failures by themselves. Final +runtime binding is used to determine whether dependency resolution completed. + +The suite is read-only. It never writes `state_synced`, because writing that +attribute forcibly invokes the provider callback independently of consumer +state. diff --git a/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/run.sh b/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/run.sh new file mode 100755 index 00000000..7453830a --- /dev/null +++ b/Runner/suites/Kernel/Baseport/Interconnect_Provider_Validation/run.sh @@ -0,0 +1,147 @@ +#!/bin/sh + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- 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" +TESTNAME="Interconnect_Provider_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +# record_config +# Records one relevant kernel option. Returns 0 when enabled, 1 when explicitly +# disabled, and 2 when the running kernel configuration is unavailable. +record_config() { + config_name="$1" + if config_line=$(kernel_config_value "$config_name" 2>/dev/null); then + log_info "[ICC-CONFIG] $config_line" + case "$config_line" in + *=y|*=m) + return 0 + ;; + esac + return 1 + fi + + log_info "[ICC-CONFIG] $config_name is not visible in the running kernel configuration" + return 2 +} + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 +providers_file="$SCRIPT_DIR/icc_provider_nodes.log" +: >"$providers_file" + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" + +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies find grep sort readlink dirname basename tr awk sed mktemp; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi + +if ! dt_list_qcom_icc_provider_nodes >"$providers_file"; then + test_result_finish "SKIP" "$TESTNAME SKIP: no enabled Qualcomm ICC provider is present in the runtime device tree" +fi +if [ ! -s "$providers_file" ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: ICC provider discovery returned no readable provider list" +fi + +for config_name in CONFIG_INTERCONNECT CONFIG_INTERCONNECT_QCOM; do + record_config "$config_name" + config_rc=$? + if [ "$config_rc" -eq 0 ]; then + test_result_record "PASS" "$config_name is enabled" + elif [ "$config_rc" -eq 1 ]; then + test_result_record "FAIL" "$config_name is disabled for applicable ICC hardware" + else + test_result_record "SKIP" "$config_name could not be checked because the running kernel configuration is unavailable" + fi +done + +provider_count=0 +bound_count=0 +synced_count=0 +state_missing_count=0 + +while IFS= read -r node_dir; do + [ -n "$node_dir" ] || continue + provider_count=$((provider_count + 1)) + compatible=$(dt_property_text "$node_dir" compatible 2>/dev/null || printf '%s\n' unknown) + log_info "[ICC-DT] node=$node_dir compatible=$compatible" + + if ! device_dir=$(find_platform_device_for_dt_node "$node_dir"); then + test_result_record "FAIL" "ICC provider has no platform device: $node_dir" + continue + fi + + device_name=$(basename "$device_dir") + if driver_name=$(platform_device_driver_name "$device_dir"); then + bound_count=$((bound_count + 1)) + test_result_record "PASS" "ICC provider is bound: device=$device_name driver=$driver_name" + else + test_result_record "FAIL" "ICC provider platform device is unbound: $device_name" + continue + fi + + if [ -r "$device_dir/state_synced" ]; then + state_synced=$(tr -d '[:space:]' <"$device_dir/state_synced") + if [ "$state_synced" = "1" ]; then + synced_count=$((synced_count + 1)) + test_result_record "PASS" "ICC provider reached synchronized state: $device_name" + else + test_result_record "FAIL" "ICC provider did not reach synchronized state: device=$device_name state_synced=${state_synced:-unknown}" + fi + else + state_missing_count=$((state_missing_count + 1)) + test_result_record "SKIP" "state_synced is not exposed for ICC provider $device_name" + fi +done <"$providers_file" + +log_info "ICC provider totals: discovered=$provider_count bound=$bound_count synced=$synced_count state_not_exposed=$state_missing_count" + +scan_dmesg_errors \ + "$SCRIPT_DIR" \ + "interconnect|qnoc|icc|bcm_voter" \ + "-517|EPROBE_DEFER|deferred probe|probe deferral|interconnect provider registered|synced state" || true + +grep -Ei '(interconnect|qnoc|bcm_voter).*(fail|error|timed out|timeout|unavailable)' \ + "$SCRIPT_DIR/dmesg_snapshot.log" 2>/dev/null | + grep -Evi -- '-517|EPROBE_DEFER|deferred probe|probe deferral' \ + >"$SCRIPT_DIR/icc_runtime_errors.log" || true + +if [ -s "$SCRIPT_DIR/dmesg_errors.log" ] || + [ -s "$SCRIPT_DIR/icc_runtime_errors.log" ]; then + test_result_record "FAIL" "Persistent ICC or NoC errors were found in the captured kernel log" +fi + +test_result_finish diff --git a/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/Interconnect_Topology_Validation.yaml b/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/Interconnect_Topology_Validation.yaml new file mode 100644 index 00000000..822adbb6 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/Interconnect_Topology_Validation.yaml @@ -0,0 +1,15 @@ +metadata: + name: Interconnect_Topology_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate the Linux ICC debugfs node and link topology" + os: + - linux + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh Interconnect_Topology_Validation.res diff --git a/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/README.md b/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/README.md new file mode 100644 index 00000000..a62705e1 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/README.md @@ -0,0 +1,25 @@ +# Interconnect topology validation + +`Interconnect_Topology_Validation` validates the topology exported by the +Linux interconnect core through debugfs. It checks that the graph is complete, +contains nodes and links, has unique node IDs, has no dangling edge targets, +and agrees with the node count in `interconnect_summary`. + +The captured files are retained as `interconnect_graph.log` and +`interconnect_summary.log` for LAVA triage. + +## Requirements and result policy + +The kernel must expose: + +```text +/sys/kernel/debug/interconnect/interconnect_graph +/sys/kernel/debug/interconnect/interconnect_summary +``` + +The suite returns `SKIP` when Qualcomm ICC hardware is not applicable or when +debugfs is unavailable. Debugfs absence is not treated as a driver failure +because production images may disable it. + +The suite is read-only. The upstream writable ICC debugfs test client is not +used because arbitrary bandwidth requests can affect system stability. diff --git a/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/run.sh b/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/run.sh new file mode 100755 index 00000000..75a6fec6 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/Interconnect_Topology_Validation/run.sh @@ -0,0 +1,144 @@ +#!/bin/sh + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- 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" +TESTNAME="Interconnect_Topology_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 + +providers_file="$SCRIPT_DIR/icc_provider_nodes.log" +graph_file="$SCRIPT_DIR/interconnect_graph.log" +summary_file="$SCRIPT_DIR/interconnect_summary.log" +nodes_file="$SCRIPT_DIR/interconnect_graph_nodes.log" +endpoints_file="$SCRIPT_DIR/interconnect_graph_endpoints.log" +: >"$providers_file" + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" + +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies cp sed sort uniq grep awk wc find readlink dirname basename tr mktemp; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi + +if ! dt_list_qcom_icc_provider_nodes >"$providers_file"; then + test_result_finish "SKIP" "$TESTNAME SKIP: no enabled Qualcomm ICC provider is present in the runtime device tree" +fi +if [ ! -s "$providers_file" ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: ICC provider discovery returned no readable provider list" +fi + +if ! debugfs_dir=$(interconnect_debugfs_dir); then + test_result_finish "SKIP" "$TESTNAME SKIP: ICC debugfs summary and graph are not available" +fi + +if ! cp "$debugfs_dir/interconnect_graph" "$graph_file" || + ! cp "$debugfs_dir/interconnect_summary" "$summary_file"; then + test_result_finish "FAIL" "$TESTNAME FAIL: ICC debugfs files could not be captured" +fi + +sed -n 's/^[[:space:]]*"\([^"]*\)" \[label=.*/\1/p' "$graph_file" | + sort -u >"$nodes_file" + +{ + sed -n 's/^[[:space:]]*"\([^"]*\)" -> ".*/\1/p' "$graph_file" + sed -n 's/^[[:space:]]*"[^"]*" -> "\([^"]*\)".*/\1/p' "$graph_file" +} | sort -u >"$endpoints_file" + +provider_count=$(grep -c '^' "$providers_file" 2>/dev/null || true) +node_count=$(grep -c '^' "$nodes_file" 2>/dev/null || true) +edge_count=$(grep -c -- ' -> ' "$graph_file" 2>/dev/null || true) +provider_count=${provider_count:-0} +node_count=${node_count:-0} +edge_count=${edge_count:-0} +summary_node_count=$(awk ' + NR > 2 && $0 !~ /^[[:space:]][[:space:]]/ && NF >= 3 { + count++ + } + END { + print count + 0 + } +' "$summary_file") + +log_info "ICC topology totals: runtime_providers=$provider_count graph_nodes=$node_count graph_edges=$edge_count summary_nodes=$summary_node_count" + +if grep -q '^digraph {' "$graph_file" && grep -q '^}' "$graph_file"; then + test_result_record "PASS" "ICC graph has complete DOT framing" +else + test_result_record "FAIL" "ICC graph is incomplete or malformed" +fi + +if [ "$node_count" -gt 0 ]; then + test_result_record "PASS" "ICC graph contains registered nodes: count=$node_count" +else + test_result_record "FAIL" "ICC graph contains no registered nodes" +fi + +if [ "$edge_count" -gt 0 ]; then + test_result_record "PASS" "ICC graph contains topology links: count=$edge_count" +else + test_result_record "FAIL" "ICC graph contains no topology links" +fi + +dangling_count=0 +while IFS= read -r endpoint; do + [ -n "$endpoint" ] || continue + if ! grep -Fqx "$endpoint" "$nodes_file"; then + log_fail "ICC graph edge references an undeclared node: $endpoint" + dangling_count=$((dangling_count + 1)) + fi +done <"$endpoints_file" + +if [ "$dangling_count" -eq 0 ]; then + test_result_record "PASS" "ICC graph contains no dangling edge references" +else + test_result_record "FAIL" "ICC graph contains dangling edge references: count=$dangling_count" +fi + +duplicate_id_count=$(sed 's/:.*//' "$nodes_file" | sort | uniq -d | wc -l | awk '{print $1}') +if [ "$duplicate_id_count" -eq 0 ]; then + test_result_record "PASS" "ICC graph node IDs are unique" +else + test_result_record "FAIL" "ICC graph contains duplicate node IDs: count=$duplicate_id_count" +fi + +if [ "$summary_node_count" -eq "$node_count" ] && [ "$node_count" -gt 0 ]; then + test_result_record "PASS" "ICC summary and graph expose the same node count" +else + test_result_record "FAIL" "ICC summary and graph node counts differ: summary=$summary_node_count graph=$node_count" +fi + +test_result_finish diff --git a/Runner/suites/Kernel/Baseport/LLCC_Validation/LLCC_Validation.yaml b/Runner/suites/Kernel/Baseport/LLCC_Validation/LLCC_Validation.yaml new file mode 100644 index 00000000..00589d11 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/LLCC_Validation/LLCC_Validation.yaml @@ -0,0 +1,15 @@ +metadata: + name: LLCC_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate Qualcomm Last Level Cache Controller applicability, binding, and kernel health" + os: + - linux + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/LLCC_Validation + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh LLCC_Validation.res diff --git a/Runner/suites/Kernel/Baseport/LLCC_Validation/README.md b/Runner/suites/Kernel/Baseport/LLCC_Validation/README.md new file mode 100644 index 00000000..35a9b86d --- /dev/null +++ b/Runner/suites/Kernel/Baseport/LLCC_Validation/README.md @@ -0,0 +1,17 @@ +# Qualcomm LLCC validation + +`LLCC_Validation` checks runtime applicability and driver registration for the +Qualcomm Last Level Cache Controller. It dynamically discovers enabled +`qcom,*-llcc` nodes and verifies that their platform devices bind to the +upstream `qcom-llcc` driver. + +The test also records `CONFIG_QCOM_LLCC` when the running kernel configuration +is available and captures LLCC-related kernel errors through the shared dmesg +scanner. + +The suite does not assume that an LLCC performance-monitoring unit exists and +does not access LLCC registers. ECC reporting is covered separately by +`QCOM_EDAC_Validation` because some LLCC configurations intentionally disable +the EDAC child device. + +The suite returns `SKIP` when no enabled LLCC node is present. diff --git a/Runner/suites/Kernel/Baseport/LLCC_Validation/run.sh b/Runner/suites/Kernel/Baseport/LLCC_Validation/run.sh new file mode 100755 index 00000000..32def24c --- /dev/null +++ b/Runner/suites/Kernel/Baseport/LLCC_Validation/run.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- 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" +TESTNAME="LLCC_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 + +nodes_file="$SCRIPT_DIR/llcc_nodes.log" +: >"$nodes_file" + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" + +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies find grep sort readlink dirname basename tr sed mktemp; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi + +if ! dt_list_compatible_nodes \ + '(^|[[:space:]])qcom,[[:alnum:]_-]+-llcc([[:space:]]|$)' regex \ + >"$nodes_file"; then + test_result_finish "SKIP" "$TESTNAME SKIP: no enabled Qualcomm LLCC node is present in the runtime device tree" +fi + +if config_line=$(kernel_config_value CONFIG_QCOM_LLCC 2>/dev/null); then + log_info "[LLCC-CONFIG] $config_line" + case "$config_line" in + *=y|*=m) + test_result_record "PASS" "CONFIG_QCOM_LLCC is enabled" + ;; + *) + test_result_record "FAIL" "CONFIG_QCOM_LLCC is disabled for applicable LLCC hardware" + ;; + esac +else + log_info "CONFIG_QCOM_LLCC could not be checked because the running kernel configuration is unavailable" +fi + +node_count=0 +bound_count=0 +while IFS= read -r node_dir; do + [ -n "$node_dir" ] || continue + node_count=$((node_count + 1)) + compatible=$(dt_property_text "$node_dir" compatible 2>/dev/null || printf '%s\n' unknown) + log_info "[LLCC-DT] node=$node_dir compatible=$compatible" + + if ! device_dir=$(find_platform_device_for_dt_node "$node_dir"); then + test_result_record "FAIL" "LLCC node has no platform device: $node_dir" + continue + fi + + device_name=$(basename "$device_dir") + if driver_name=$(platform_device_driver_name "$device_dir"); then + bound_count=$((bound_count + 1)) + if [ "$driver_name" = "qcom-llcc" ]; then + test_result_record "PASS" "LLCC platform device is bound to qcom-llcc: $device_name" + else + test_result_record "FAIL" "LLCC platform device is bound to an unexpected driver: device=$device_name driver=$driver_name" + fi + else + test_result_record "FAIL" "LLCC platform device is unbound: $device_name" + fi +done <"$nodes_file" + +log_info "LLCC totals: discovered=$node_count bound=$bound_count" + +scan_dmesg_errors \ + "$SCRIPT_DIR" \ + "qcom-llcc|llcc" \ + "EDAC device registered|polling mode|-517|EPROBE_DEFER|deferred probe" || true + +grep -Ei '(qcom-llcc|llcc).*(fail|error|timed out|timeout|corrupt)' \ + "$SCRIPT_DIR/dmesg_snapshot.log" 2>/dev/null | + grep -Evi -- '-517|EPROBE_DEFER|deferred probe|correctable|corrected' \ + >"$SCRIPT_DIR/llcc_runtime_errors.log" || true + +if [ -s "$SCRIPT_DIR/dmesg_errors.log" ] || + [ -s "$SCRIPT_DIR/llcc_runtime_errors.log" ]; then + test_result_record "FAIL" "LLCC errors were found in the captured kernel log" +fi + +test_result_finish diff --git a/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/QCOM_BWMON_Validation.yaml b/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/QCOM_BWMON_Validation.yaml new file mode 100644 index 00000000..dcd1a49d --- /dev/null +++ b/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/QCOM_BWMON_Validation.yaml @@ -0,0 +1,18 @@ +metadata: + name: QCOM_BWMON_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate Qualcomm interconnect bandwidth-monitor binding and bounded runtime activity" + os: + - linux + scope: + - functional + +params: + LOAD_SECONDS: "10" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation + - ./run.sh --load-seconds "${LOAD_SECONDS:-10}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh QCOM_BWMON_Validation.res diff --git a/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/README.md b/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/README.md new file mode 100644 index 00000000..e14338ef --- /dev/null +++ b/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/README.md @@ -0,0 +1,27 @@ +# Qualcomm BWMON validation + +`QCOM_BWMON_Validation` validates enabled Qualcomm CPU and LLCC bandwidth +monitor nodes, their required `reg`, interrupt, interconnect, and OPP-table DT +resources, and binding to the upstream +`qcom-bwmon` driver. + +When an image-provided workload is available, the suite runs a bounded memory +load using `stress-ng`, `stressapptest`, or `dd`. It samples the ICC summary and +checks for a BWMON interrupt-count increase. These runtime observations are +supplemental: no interrupt delta or vote increase is not a failure because the +device may already be at its current maximum OPP or may not cross a programmed +threshold during the sample window. + +## Configuration + +```text +--load-seconds N +``` + +The equivalent LAVA environment variable is `LOAD_SECONDS`. The command-line +value takes precedence. + +The test does not require exact bandwidth values, force an ICC vote, alter an +OPP table, or install a benchmark package. Missing optional workload and +debugfs support produce skipped subchecks. An absent BWMON node skips the +suite, while an applicable but unbound or malformed node fails it. diff --git a/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/run.sh b/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/run.sh new file mode 100755 index 00000000..9f5bb93a --- /dev/null +++ b/Runner/suites/Kernel/Baseport/QCOM_BWMON_Validation/run.sh @@ -0,0 +1,316 @@ +#!/bin/sh + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- 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" +TESTNAME="QCOM_BWMON_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +LOAD_SECONDS="${BWMON_LOAD_SECONDS:-10}" +WORKLOAD_PID="" +WORKLOAD_NAME="" + +# usage +# Prints supported command-line options and returns success. +usage() { + printf '%s\n' "Usage: $0 [--load-seconds N]" +} + +# parse_args +# Parses command-line overrides and exits for help or invalid input. +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --load-seconds) + [ "$#" -ge 2 ] || { + usage >&2 + exit 2 + } + LOAD_SECONDS="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + log_fail "Unknown argument: $1" + usage >&2 + exit 2 + ;; + esac + done +} + +# cleanup +# Stops only the optional workload started by this suite. Returns success. +# shellcheck disable=SC2317 +cleanup() { + if [ -n "$WORKLOAD_PID" ] && kill -0 "$WORKLOAD_PID" 2>/dev/null; then + kill "$WORKLOAD_PID" 2>/dev/null || true + wait "$WORKLOAD_PID" 2>/dev/null || true + fi +} + +# start_memory_workload +# Starts one bounded image-provided memory workload and sets WORKLOAD_PID and +# WORKLOAD_NAME. Returns 0 when started, otherwise 1. +start_memory_workload() { + workload_log="$SCRIPT_DIR/bwmon_workload.log" + + if command -v stress-ng >/dev/null 2>&1; then + stress-ng \ + --vm 1 \ + --vm-bytes 64M \ + --verify \ + --timeout "${LOAD_SECONDS}s" \ + >"$workload_log" 2>&1 & + WORKLOAD_PID=$! + WORKLOAD_NAME="stress-ng" + return 0 + fi + + if command -v stressapptest >/dev/null 2>&1; then + stressapptest \ + -s "$LOAD_SECONDS" \ + -M 64 \ + -m 1 \ + >"$workload_log" 2>&1 & + WORKLOAD_PID=$! + WORKLOAD_NAME="stressapptest" + return 0 + fi + + if command -v dd >/dev/null 2>&1 && command -v date >/dev/null 2>&1; then + ( + end_time=$(($(date +%s) + LOAD_SECONDS)) + while [ "$(date +%s)" -lt "$end_time" ]; do + dd if=/dev/zero of=/dev/null bs=1M count=64 2>/dev/null + done + ) >"$workload_log" 2>&1 & + WORKLOAD_PID=$! + WORKLOAD_NAME="dd" + return 0 + fi + + return 1 +} + +# bwmon_irq_total +# Prints the sum of interrupt counters from lines containing BWMON or a bound +# BWMON device name. Returns 0 even when no matching interrupt label is found. +bwmon_irq_total() { + needle_file="$1" + awk -v needles="$needle_file" ' + BEGIN { + while ((getline line < needles) > 0) { + wanted[tolower(line)] = 1 + } + close(needles) + } + { + lower = tolower($0) + matched = index(lower, "bwmon") > 0 + for (needle in wanted) { + if (needle != "" && index(lower, needle) > 0) { + matched = 1 + } + } + if (!matched) { + next + } + for (field = 2; field <= NF; field++) { + if ($field ~ /^[0-9]+$/) { + total += $field + } else { + break + } + } + } + END { + print total + 0 + } + ' /proc/interrupts 2>/dev/null +} + +parse_args "$@" + +case "$LOAD_SECONDS" in + ''|*[!0-9]*|0) + log_fail "--load-seconds must be a positive integer" + exit 2 + ;; +esac + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 + +nodes_file="$SCRIPT_DIR/bwmon_nodes.log" +devices_file="$SCRIPT_DIR/bwmon_devices.log" +summary_samples="$SCRIPT_DIR/interconnect_summary_samples.log" +: >"$nodes_file" +: >"$devices_file" +: >"$summary_samples" + +trap cleanup 0 +trap 'exit 130' 1 2 15 + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "Configuration: load_seconds=$LOAD_SECONDS" + +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies awk grep find sort readlink dirname basename tr sed mktemp cat sleep; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi + +if ! dt_list_compatible_nodes \ + '(^|[[:space:]])qcom,([[:alnum:]_-]+-(cpu|llcc)-bwmon|msm8998-bwmon|sdm845-bwmon)([[:space:]]|$)' regex \ + >"$nodes_file"; then + test_result_finish "SKIP" "$TESTNAME SKIP: no enabled Qualcomm BWMON node is present in the runtime device tree" +fi + +if config_line=$(kernel_config_value CONFIG_QCOM_ICC_BWMON 2>/dev/null); then + log_info "[BWMON-CONFIG] $config_line" + case "$config_line" in + *=y|*=m) + test_result_record "PASS" "CONFIG_QCOM_ICC_BWMON is enabled" + ;; + *) + test_result_record "FAIL" "CONFIG_QCOM_ICC_BWMON is disabled for applicable BWMON hardware" + ;; + esac +else + log_info "CONFIG_QCOM_ICC_BWMON could not be checked because the running kernel configuration is unavailable" +fi + +while IFS= read -r node_dir; do + [ -n "$node_dir" ] || continue + compatible=$(dt_property_text "$node_dir" compatible 2>/dev/null || printf '%s\n' unknown) + log_info "[BWMON-DT] node=$node_dir compatible=$compatible" + + for property_name in reg interconnects interrupts operating-points-v2; do + if dt_node_has_property "$node_dir" "$property_name"; then + test_result_record "PASS" "BWMON node exposes required property $property_name" + else + test_result_record "FAIL" "BWMON node is missing required property $property_name: $node_dir" + fi + done + + if ! device_dir=$(find_platform_device_for_dt_node "$node_dir"); then + test_result_record "FAIL" "BWMON node has no platform device: $node_dir" + continue + fi + + device_name=$(basename "$device_dir") + printf '%s\n' "$device_name" >>"$devices_file" + if driver_name=$(platform_device_driver_name "$device_dir"); then + if [ "$driver_name" = "qcom-bwmon" ]; then + test_result_record "PASS" "BWMON platform device is bound to qcom-bwmon: $device_name" + else + test_result_record "FAIL" "BWMON platform device uses an unexpected driver: device=$device_name driver=$driver_name" + fi + else + test_result_record "FAIL" "BWMON platform device is unbound: $device_name" + fi +done <"$nodes_file" + +irq_before=$(bwmon_irq_total "$devices_file") + +if start_memory_workload; then + test_result_record "PASS" "Started bounded BWMON workload using $WORKLOAD_NAME" + + if debugfs_dir=$(interconnect_debugfs_dir); then + sample_index=0 + while [ "$sample_index" -lt 3 ]; do + printf '%s\n' "sample=$sample_index" >>"$summary_samples" + cat "$debugfs_dir/interconnect_summary" >>"$summary_samples" 2>/dev/null || true + sleep 1 + sample_index=$((sample_index + 1)) + done + else + test_result_record "SKIP" "ICC debugfs summary is unavailable for runtime vote observation" + fi + + wait "$WORKLOAD_PID" + workload_rc=$? + WORKLOAD_PID="" + if [ "$workload_rc" -eq 0 ]; then + test_result_record "PASS" "BWMON workload completed successfully" + else + test_result_record "FAIL" "BWMON workload failed: tool=$WORKLOAD_NAME status=$workload_rc" + fi + + irq_after=$(bwmon_irq_total "$devices_file") + irq_delta=$((irq_after - irq_before)) + if [ "$irq_delta" -gt 0 ]; then + test_result_record "PASS" "BWMON interrupt counters increased during load: delta=$irq_delta" + else + test_result_record "SKIP" "No BWMON interrupt delta was observed, the current OPP may not cross a threshold" + fi + + if [ -s "$summary_samples" ] && awk ' + $0 !~ /^[[:space:]][[:space:]]/ && NF >= 3 && + $(NF - 1) ~ /^[0-9]+$/ && $NF ~ /^[0-9]+$/ && + ($(NF - 1) > 0 || $NF > 0) { + found = 1 + } + END { + exit !found + } + ' "$summary_samples"; then + test_result_record "PASS" "ICC summary exposed at least one active bandwidth vote during load" + elif [ -s "$summary_samples" ]; then + test_result_record "SKIP" "ICC summary was readable but no non-zero vote was sampled" + fi +else + test_result_record "SKIP" "No supported image-provided memory workload is available" +fi + +scan_dmesg_errors \ + "$SCRIPT_DIR" \ + "qcom-bwmon|bwmon|interconnect|qnoc" \ + "-517|EPROBE_DEFER|deferred probe|No errors|0 failures" || true + +grep -Ei '(qcom-bwmon|bwmon|interconnect|qnoc).*(fail|error|timed out|timeout|unavailable)' \ + "$SCRIPT_DIR/dmesg_snapshot.log" 2>/dev/null | + grep -Evi -- '-517|EPROBE_DEFER|deferred probe|No errors|0 failures' \ + >"$SCRIPT_DIR/bwmon_runtime_errors.log" || true + +if [ -s "$SCRIPT_DIR/dmesg_errors.log" ] || + [ -s "$SCRIPT_DIR/bwmon_runtime_errors.log" ]; then + test_result_record "FAIL" "BWMON or interconnect errors were found in the captured kernel log" +fi + +test_result_finish diff --git a/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/QCOM_EDAC_Validation.yaml b/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/QCOM_EDAC_Validation.yaml new file mode 100644 index 00000000..0f4e1de6 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/QCOM_EDAC_Validation.yaml @@ -0,0 +1,19 @@ +metadata: + name: QCOM_EDAC_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate Qualcomm LLCC EDAC registration and correctable and uncorrectable error counters" + os: + - linux + scope: + - functional + +params: + STRICT_CE: "0" + OBSERVE_SECONDS: "2" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation + - ./run.sh --strict-ce "${STRICT_CE:-0}" --observe-seconds "${OBSERVE_SECONDS:-2}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh QCOM_EDAC_Validation.res diff --git a/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/README.md b/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/README.md new file mode 100644 index 00000000..b3587918 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/README.md @@ -0,0 +1,31 @@ +# Qualcomm LLCC EDAC validation + +`QCOM_EDAC_Validation` validates the upstream Qualcomm LLCC EDAC child device +and its read-only bank counters under: + +```text +/sys/devices/system/edac/qcom-llcc/ +``` + +The Qualcomm driver reports single-bit and double-bit errors for LLCC tag and +data RAM. Depending on platform configuration it operates through an ECC IRQ +or periodic polling. + +## Options + +```text +--strict-ce 0|1 +--observe-seconds N +``` + +The equivalent LAVA environment variables are `STRICT_CE` and +`OBSERVE_SECONDS`. Command-line values take precedence. + +Uncorrectable errors, increasing UE counters, missing counters, or an exposed +but unbound EDAC device fail the test. Existing or increasing CE counters are +warnings by default and failures with `--strict-ce 1`. + +The suite returns `SKIP` when the LLCC driver does not create an EDAC child. +This is valid on platforms where EDAC register access is intentionally +disabled. The test never injects errors and never writes EDAC or LLCC +registers. diff --git a/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/run.sh b/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/run.sh new file mode 100755 index 00000000..b11a016a --- /dev/null +++ b/Runner/suites/Kernel/Baseport/QCOM_EDAC_Validation/run.sh @@ -0,0 +1,270 @@ +#!/bin/sh + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- 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" +TESTNAME="QCOM_EDAC_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +STRICT_CE="${EDAC_STRICT_CE:-0}" +OBSERVE_SECONDS="${EDAC_OBSERVE_SECONDS:-2}" + +# usage +# Prints supported command-line options and returns success. +usage() { + printf '%s\n' "Usage: $0 [--strict-ce 0|1] [--observe-seconds N]" +} + +# parse_args +# Parses command-line overrides and exits for help or invalid input. +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --strict-ce) + [ "$#" -ge 2 ] || { + usage >&2 + exit 2 + } + STRICT_CE="$2" + shift 2 + ;; + --observe-seconds) + [ "$#" -ge 2 ] || { + usage >&2 + exit 2 + } + OBSERVE_SECONDS="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + log_fail "Unknown argument: $1" + usage >&2 + exit 2 + ;; + esac + done +} + +# snapshot_edac_counters +# Writes one "path|value" record for every Qualcomm LLCC EDAC CE and UE +# counter. Returns 0 when at least one readable counter is found, otherwise 1. +snapshot_edac_counters() { + output_file="$1" + found=0 + : >"$output_file" + + for counter_file in \ + /sys/devices/system/edac/qcom-llcc/*/ce_count \ + /sys/devices/system/edac/qcom-llcc/*/ue_count; do + [ -r "$counter_file" ] || continue + counter_value=$(tr -d '[:space:]' <"$counter_file") + case "$counter_value" in + ''|*[!0-9]*) + continue + ;; + esac + printf '%s|%s\n' "$counter_file" "$counter_value" >>"$output_file" + found=1 + done + + [ "$found" -eq 1 ] +} + +# counter_value_from_snapshot +# Prints a counter value from a snapshot and returns 0 when present, otherwise +# returns 1. +counter_value_from_snapshot() { + snapshot_file="$1" + counter_path="$2" + awk -F '|' -v path="$counter_path" '$1 == path { print $2; found=1; exit } END { exit !found }' "$snapshot_file" +} + +parse_args "$@" + +case "$STRICT_CE" in + 0|1) + ;; + *) + log_fail "--strict-ce must be 0 or 1" + exit 2 + ;; +esac + +case "$OBSERVE_SECONDS" in + ''|*[!0-9]*) + log_fail "--observe-seconds must be a non-negative integer" + exit 2 + ;; +esac + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 +rm -f \ + "$SCRIPT_DIR/edac_counters_before.log" \ + "$SCRIPT_DIR/edac_counters_after.log" + +baseline_file="$SCRIPT_DIR/edac_counters_before.log" +final_file="$SCRIPT_DIR/edac_counters_after.log" + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "Configuration: strict_ce=$STRICT_CE observe_seconds=$OBSERVE_SECONDS" + +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies awk grep tr sleep readlink basename; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi + +edac_platform_device="" +for candidate in /sys/bus/platform/devices/qcom_llcc_edac*; do + [ -d "$candidate" ] || continue + edac_platform_device="$candidate" + break +done + +if [ -z "$edac_platform_device" ] && [ ! -d /sys/devices/system/edac/qcom-llcc ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: Qualcomm LLCC EDAC is not exposed on this platform" +fi + +for config_name in CONFIG_EDAC CONFIG_EDAC_QCOM; do + if config_line=$(kernel_config_value "$config_name" 2>/dev/null); then + log_info "[EDAC-CONFIG] $config_line" + case "$config_line" in + *=y|*=m) + test_result_record "PASS" "$config_name is enabled" + ;; + *) + test_result_record "FAIL" "$config_name is disabled for an exposed Qualcomm EDAC device" + ;; + esac + else + log_info "$config_name could not be checked because the running kernel configuration is unavailable" + fi +done + +if [ -n "$edac_platform_device" ]; then + if driver_name=$(platform_device_driver_name "$edac_platform_device"); then + if [ "$driver_name" = "qcom_llcc_edac" ]; then + test_result_record "PASS" "Qualcomm LLCC EDAC platform device is bound" + else + test_result_record "FAIL" "Qualcomm LLCC EDAC device uses an unexpected driver: $driver_name" + fi + else + test_result_record "FAIL" "Qualcomm LLCC EDAC platform device is unbound" + fi +fi + +if [ ! -d /sys/devices/system/edac/qcom-llcc ]; then + test_result_record "FAIL" "Qualcomm LLCC EDAC sysfs hierarchy is missing" +elif snapshot_edac_counters "$baseline_file"; then + test_result_record "PASS" "Qualcomm LLCC EDAC counters are readable" +else + test_result_record "FAIL" "No readable Qualcomm LLCC EDAC bank counters were found" +fi + +if [ -s "$baseline_file" ]; then + while IFS='|' read -r counter_path counter_value; do + log_info "[EDAC-BASELINE] path=$counter_path value=$counter_value" + case "$counter_path" in + */ue_count) + if [ "$counter_value" -gt 0 ]; then + test_result_record "FAIL" "Uncorrectable EDAC errors are present: path=$counter_path count=$counter_value" + fi + ;; + */ce_count) + if [ "$counter_value" -gt 0 ] && [ "$STRICT_CE" -eq 1 ]; then + test_result_record "FAIL" "Correctable EDAC errors are present in strict mode: path=$counter_path count=$counter_value" + elif [ "$counter_value" -gt 0 ]; then + log_warn "Correctable EDAC errors are present at baseline: path=$counter_path count=$counter_value" + fi + ;; + esac + done <"$baseline_file" + + sleep "$OBSERVE_SECONDS" + snapshot_edac_counters "$final_file" || true + + while IFS='|' read -r counter_path baseline_value; do + final_value=$(counter_value_from_snapshot "$final_file" "$counter_path" 2>/dev/null || true) + if [ -z "$final_value" ]; then + test_result_record "FAIL" "EDAC counter disappeared during observation: $counter_path" + continue + fi + + delta=$((final_value - baseline_value)) + log_info "[EDAC-FINAL] path=$counter_path value=$final_value delta=$delta" + if [ "$delta" -lt 0 ]; then + test_result_record "FAIL" "EDAC counter decreased unexpectedly: $counter_path" + elif [ "$delta" -gt 0 ]; then + case "$counter_path" in + */ue_count) + test_result_record "FAIL" "New uncorrectable EDAC errors were reported: path=$counter_path delta=$delta" + ;; + */ce_count) + if [ "$STRICT_CE" -eq 1 ]; then + test_result_record "FAIL" "New correctable EDAC errors were reported in strict mode: path=$counter_path delta=$delta" + else + log_warn "New correctable EDAC errors were reported: path=$counter_path delta=$delta" + fi + ;; + esac + fi + done <"$baseline_file" +fi + +edac_dmesg_exclude="No errors|0 errors|0 failures|registered|polling mode|-517|EPROBE_DEFER|deferred probe" +if [ "$STRICT_CE" -eq 0 ]; then + edac_dmesg_exclude="$edac_dmesg_exclude|correctable|corrected" +fi + +scan_dmesg_errors \ + "$SCRIPT_DIR" \ + "EDAC|qcom_llcc_edac|qcom-llcc|llcc" \ + "$edac_dmesg_exclude" || true + +grep -Ei '(EDAC|ECC|qcom_llcc_edac|qcom-llcc|llcc).*(fail|error|uncorrectable|UE|corrupt)' \ + "$SCRIPT_DIR/dmesg_snapshot.log" 2>/dev/null | + grep -Evi "$edac_dmesg_exclude" \ + >"$SCRIPT_DIR/edac_runtime_errors.log" || true + +if [ -s "$SCRIPT_DIR/dmesg_errors.log" ] || + [ -s "$SCRIPT_DIR/edac_runtime_errors.log" ]; then + test_result_record "FAIL" "EDAC or LLCC errors were found in the captured kernel log" +fi + +test_result_finish diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index 3f20e736..da1202fc 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -14,6 +14,95 @@ log_error() { log "ERROR" "$@"; } log_skip() { log "SKIP" "$@"; } log_warn() { log "WARN" "$@"; } +# test_result_init +# Initializes shared testcase result state and removes any stale result file. +test_result_init() { + TEST_RESULT_NAME="$1" + TEST_RESULT_FILE="$2" + TEST_RESULT_PASS_COUNT=0 + TEST_RESULT_FAIL_COUNT=0 + TEST_RESULT_SKIP_COUNT=0 + + if [ -z "$TEST_RESULT_NAME" ] || [ -z "$TEST_RESULT_FILE" ]; then + log_error "test_result_init requires a test name and result file" + return 3 + fi + + rm -f "$TEST_RESULT_FILE" +} + +# test_result_record +# Logs one subcheck and updates the shared result counters. +test_result_record() { + test_result_status="$1" + test_result_message="$2" + + case "$test_result_status" in + PASS) + log_pass "$test_result_message" + TEST_RESULT_PASS_COUNT=$((TEST_RESULT_PASS_COUNT + 1)) + ;; + FAIL) + log_fail "$test_result_message" + TEST_RESULT_FAIL_COUNT=$((TEST_RESULT_FAIL_COUNT + 1)) + ;; + SKIP) + log_skip "$test_result_message" + TEST_RESULT_SKIP_COUNT=$((TEST_RESULT_SKIP_COUNT + 1)) + ;; + *) + log_error "Unsupported testcase result status: $test_result_status" + return 3 + ;; + esac +} + +# test_result_finish [PASS|FAIL|SKIP] [message] +# Writes the final result and exits successfully for LAVA result processing. +# Without an explicit status, failures take precedence, then passes, then skip. +test_result_finish() { + test_result_status="${1:-}" + test_result_message="${2:-}" + + if [ -z "$test_result_status" ]; then + if [ "$TEST_RESULT_FAIL_COUNT" -gt 0 ]; then + test_result_status="FAIL" + elif [ "$TEST_RESULT_PASS_COUNT" -gt 0 ]; then + test_result_status="PASS" + else + test_result_status="SKIP" + fi + fi + + case "$test_result_status" in + PASS|FAIL|SKIP) + ;; + *) + log_error "Unsupported final testcase result status: $test_result_status" + test_result_status="FAIL" + ;; + esac + + if [ -z "$test_result_message" ]; then + test_result_message="$TEST_RESULT_NAME $test_result_status: passed=$TEST_RESULT_PASS_COUNT failed=$TEST_RESULT_FAIL_COUNT skipped=$TEST_RESULT_SKIP_COUNT" + fi + + case "$test_result_status" in + PASS) + log_pass "$test_result_message" + ;; + FAIL) + log_fail "$test_result_message" + ;; + SKIP) + log_skip "$test_result_message" + ;; + esac + + printf '%s %s\n' "$TEST_RESULT_NAME" "$test_result_status" >"$TEST_RESULT_FILE" + exit 0 +} + # --- Kernel Log Collection --- get_kernel_log() { if command -v journalctl >/dev/null 2>&1; then @@ -50,7 +139,7 @@ find_kernel_module() { fi if [ -z "$module_path" ] && [ -d /lib/modules ]; then - log_warn "Module $module_name not found under /lib/modules/$kver, searching all module trees" + log_warn "Module $module_name not found under /lib/modules/$kver, searching all module trees" >&2 module_path=$( find /lib/modules -type f \ \( -name "${module_name}.ko" \ @@ -65,7 +154,7 @@ find_kernel_module() { found_version=${module_path#/lib/modules/} found_version=${found_version%%/*} if [ "$found_version" != "$kver" ]; then - log_warn "Found $module_name under kernel $found_version, running kernel is $kver" + log_warn "Found $module_name under kernel $found_version, running kernel is $kver" >&2 fi fi fi @@ -885,20 +974,47 @@ check_kernel_config() { ############################################################################### # kernel_config_value -# Prints the active CONFIG_NAME=value line from /proc/config.gz. Returns 0 -# when found, 1 when it is absent or the kernel config cannot be read, and 3 -# when the config name is omitted. +# Prints CONFIG_NAME=value from the running kernel configuration, including +# CONFIG_NAME=n for an explicitly disabled option. Returns 0 when found, 1 +# when unavailable or absent, and 3 when the name is omitted. ############################################################################### kernel_config_value() { config_name="$1" [ -n "$config_name" ] || return 3 - [ -r /proc/config.gz ] || return 1 - if command -v zgrep >/dev/null 2>&1; then - zgrep -m 1 -E "^${config_name}=" /proc/config.gz 2>/dev/null - else - gzip -dc /proc/config.gz 2>/dev/null | grep -m 1 -E "^${config_name}=" + if [ -r /proc/config.gz ]; then + if command -v zgrep >/dev/null 2>&1; then + config_line=$(zgrep -m 1 -E "^${config_name}=" /proc/config.gz 2>/dev/null || true) + config_disabled=$(zgrep -m 1 -E "^# ${config_name} is not set$" /proc/config.gz 2>/dev/null || true) + else + config_line=$(gzip -dc /proc/config.gz 2>/dev/null | grep -m 1 -E "^${config_name}=" || true) + config_disabled=$(gzip -dc /proc/config.gz 2>/dev/null | grep -m 1 -E "^# ${config_name} is not set$" || true) + fi + + if [ -n "$config_line" ]; then + printf '%s\n' "$config_line" + return 0 + fi + if [ -n "$config_disabled" ]; then + printf '%s=n\n' "$config_name" + return 0 + fi fi + + boot_config="/boot/config-$(uname -r 2>/dev/null)" + if [ -r "$boot_config" ]; then + config_line=$(grep -m 1 -E "^${config_name}=" "$boot_config" 2>/dev/null || true) + if [ -n "$config_line" ]; then + printf '%s\n' "$config_line" + return 0 + fi + if grep -q -E "^# ${config_name} is not set$" "$boot_config" 2>/dev/null; then + printf '%s=n\n' "$config_name" + return 0 + fi + fi + + return 1 } check_dt_nodes() { @@ -3223,41 +3339,164 @@ dt_has_remoteproc_fw() { } ############################################################################### -# dt_list_compatible_nodes +# dt_list_compatible_nodes [fixed|regex] # Prints one enabled device-tree node directory per line from both standard DT -# roots. Returns 0 when at least one node is found, 1 when none are found, -# and 3 when the compatible substring is omitted. +# 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. ############################################################################### dt_list_compatible_nodes() { - compatible="$1" - [ -n "$compatible" ] || return 3 + dtlc_pattern="$1" + dtlc_mode="${2:-fixed}" + [ -n "$dtlc_pattern" ] || return 3 + case "$dtlc_mode" in + fixed|regex) + ;; + *) + return 3 + ;; + esac - found=0 - previous_root="" - for dt_root in /proc/device-tree /sys/firmware/devicetree/base; do - [ -d "$dt_root" ] || continue - resolved_root=$(readlink -f "$dt_root" 2>/dev/null) - [ -n "$resolved_root" ] || resolved_root="$dt_root" - [ "$resolved_root" = "$previous_root" ] && continue - previous_root="$resolved_root" - - matches_file=$(mktemp "${TMPDIR:-/tmp}/dt-compatible.XXXXXX") || return 1 - grep -r -l -a -F "$compatible" "$dt_root" 2>/dev/null >"$matches_file" || true - while IFS= read -r compatible_file || [ -n "$compatible_file" ]; do - [ -n "$compatible_file" ] || continue - node_dir=$(dirname "$compatible_file") - [ -r "$node_dir/status" ] && IFS= read -r node_status <"$node_dir/status" - case "${node_status:-okay}" in + dtlc_found=0 + dtlc_previous_root="" + for dtlc_root in /proc/device-tree /sys/firmware/devicetree/base; do + [ -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 + if [ "$dtlc_mode" = "regex" ]; then + find "$dtlc_resolved_root" -type f -name compatible 2>/dev/null >"$dtlc_matches_file" + else + grep -r -l -a -F "$dtlc_pattern" "$dtlc_resolved_root" 2>/dev/null >"$dtlc_matches_file" || true + fi + + while IFS= read -r dtlc_compatible_file; do + [ "$(basename "$dtlc_compatible_file")" = "compatible" ] || continue + dtlc_node_dir=$(dirname "$dtlc_compatible_file") + dtlc_status=$(dt_property_text "$dtlc_node_dir" status 2>/dev/null || true) + case "$dtlc_status" in disabled|fail|failed) continue ;; esac - printf '%s\n' "$node_dir" - found=1 - done <"$matches_file" - rm -f "$matches_file" + 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 + 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" done - [ "$found" -eq 1 ] + + [ "$dtlc_found" -eq 1 ] +} + +# dt_list_qcom_icc_provider_nodes +# Prints enabled Qualcomm DT nodes exposing #interconnect-cells, excluding the +# virtual IPA providers intentionally ignored by the ICC core. Returns 0 when +# found, otherwise 1. +dt_list_qcom_icc_provider_nodes() { + qiccp_providers_file=$(mktemp "${TMPDIR:-/tmp}/qcom-icc-providers.XXXXXX") || { + return 1 + } + : >"$qiccp_providers_file" + + qiccp_previous_root="" + for qiccp_root in /proc/device-tree /sys/firmware/devicetree/base; do + [ -d "$qiccp_root" ] || continue + qiccp_resolved_root=$(readlink -f "$qiccp_root" 2>/dev/null || true) + [ -n "$qiccp_resolved_root" ] || qiccp_resolved_root="$qiccp_root" + [ "$qiccp_resolved_root" = "$qiccp_previous_root" ] && continue + qiccp_previous_root="$qiccp_resolved_root" + + find "$qiccp_resolved_root" -type f -name '#interconnect-cells' 2>/dev/null | + while IFS= read -r qiccp_cells_file; do + qiccp_node_dir=$(dirname "$qiccp_cells_file") + qiccp_status=$(dt_property_text "$qiccp_node_dir" status 2>/dev/null || true) + case "$qiccp_status" in + disabled|fail|failed) + continue + ;; + esac + qiccp_compatible=$(dt_property_text "$qiccp_node_dir" compatible 2>/dev/null || true) + case " $qiccp_compatible " in + *" qcom,sc7180-ipa-virt "*|\ + *" qcom,sc8180x-ipa-virt "*|\ + *" qcom,sdx55-ipa-virt "*|\ + *" qcom,sm8150-ipa-virt "*|\ + *" qcom,sm8250-ipa-virt "*) + continue + ;; + esac + if printf '%s\n' "$qiccp_compatible" | grep -Eq '(^|[[:space:]])qcom,'; then + readlink -f "$qiccp_node_dir" 2>/dev/null || printf '%s\n' "$qiccp_node_dir" + fi + done >>"$qiccp_providers_file" + done + + sort -u "$qiccp_providers_file" + if [ -s "$qiccp_providers_file" ]; then + qiccp_result=0 + else + qiccp_result=1 + fi + + rm -f "$qiccp_providers_file" + return "$qiccp_result" +} + +# find_platform_device_for_dt_node +# Prints the platform-device directory whose of_node link resolves to the DT +# node. Returns 0 when matched, 1 when unmatched, and 3 when omitted. +find_platform_device_for_dt_node() { + fpdd_node_dir="$1" + [ -n "$fpdd_node_dir" ] || return 3 + + fpdd_target_node=$(readlink -f "$fpdd_node_dir" 2>/dev/null || true) + [ -n "$fpdd_target_node" ] || fpdd_target_node="$fpdd_node_dir" + + for fpdd_of_node_link in /sys/bus/platform/devices/*/of_node; do + [ -e "$fpdd_of_node_link" ] || continue + fpdd_device_node=$(readlink -f "$fpdd_of_node_link" 2>/dev/null || true) + if [ "$fpdd_device_node" = "$fpdd_target_node" ]; then + dirname "$fpdd_of_node_link" + return 0 + fi + done + + return 1 +} + +# platform_device_driver_name +# Prints the bound platform-driver name. Returns 0 when bound, 1 when unbound, +# and 3 when the device directory is omitted. +platform_device_driver_name() { + pddn_device_dir="$1" + [ -n "$pddn_device_dir" ] || return 3 + [ -L "$pddn_device_dir/driver" ] || return 1 + + pddn_driver_path=$(readlink -f "$pddn_device_dir/driver" 2>/dev/null || true) + [ -n "$pddn_driver_path" ] || return 1 + basename "$pddn_driver_path" +} + +# interconnect_debugfs_dir +# Prints the ICC debugfs directory when its graph and summary are readable. +# Returns 0 when available, otherwise 1. +interconnect_debugfs_dir() { + for iccd_debug_root in /sys/kernel/debug /debug; do + iccd_candidate="$iccd_debug_root/interconnect" + if [ -r "$iccd_candidate/interconnect_summary" ] && + [ -r "$iccd_candidate/interconnect_graph" ]; then + printf '%s\n' "$iccd_candidate" + return 0 + fi + done + + return 1 } ############################################################################### @@ -4334,7 +4573,7 @@ scan_dmesg_errors() { # 1. Match lines with correct module and error pattern # 2. Exclude lines with harmless patterns (using dummy regulator etc) grep -iE "^\[[^]]+\][[:space:]]+($module_regex):.*($err_patterns)" "$DMESG_SNAPSHOT" \ - | grep -vEi "$exclude_regex" > "$DMESG_ERRORS" || true + | grep -vEi -- "$exclude_regex" > "$DMESG_ERRORS" || true cp "$DMESG_ERRORS" "$DMESG_HISTORY"