From 880d095da69805a289787076e6919a33448c3f39 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Fri, 4 Sep 2026 22:50:53 +0530 Subject: [PATCH 1/2] utils: add capability-driven hardware helpers Add shared runtime inventory and validation primitives for I2C, PCIe, USB, device-tree health, firmware discovery, and QRTR topology. Keep capability detection dynamic across platforms and expose bounded diagnostics for consumer suites. Signed-off-by: Srikanth Muppandam --- Runner/utils/functestlib.sh | 1084 ++++++++++++++++++++++++++++++++++- 1 file changed, 1078 insertions(+), 6 deletions(-) diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index 907c89d5..a5ae8867 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -295,6 +295,8 @@ check_dependencies() { # Support both: # check_dependencies date awk sed # check_dependencies "$deps" where deps="date awk sed" + # Set CHECK_DEPS_RECOVER=0 for image-validation suites that must never + # install packages while running on the target. if [ "$#" -eq 1 ]; then # Split the single string into args # shellcheck disable=SC2086 @@ -316,7 +318,8 @@ check_dependencies() { continue fi - if command -v pkg_check_dependencies_recover_enabled >/dev/null 2>&1; then + if [ "${CHECK_DEPS_RECOVER:-1}" = "1" ] && + command -v pkg_check_dependencies_recover_enabled >/dev/null 2>&1; then if pkg_check_dependencies_recover_enabled; then if pkg_ensure_command "$cmd"; then if command -v "$cmd" >/dev/null 2>&1; then @@ -3613,6 +3616,8 @@ dt_build_runtime_indexes() { -name '#clock-cells' -o \ -name '#reset-cells' -o \ -name regulator-name -o \ + -name '#power-domain-cells' -o \ + -name '#cooling-cells' -o \ -name '#mbox-cells' -o \ -name '#interconnect-cells' \ \) 2>/dev/null | @@ -3670,6 +3675,7 @@ dt_hw_capability_parse_args() { return 3 ;; esac + shift done DTRHC_AREAS=$(printf '%s' "$DTRHC_AREAS" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]') [ -n "$DTRHC_AREAS" ] || return 3 @@ -3840,6 +3846,51 @@ platform_device_driver_name() { basename "$pddn_driver_path" } +# kernel_modules_for_modalias +# Prints kernel module candidates resolved from the running image's alias +# database. Returns 0 when candidates are found, 1 when no alias matches, 2 +# when alias resolution is unavailable, and 3 for an empty modalias. +kernel_modules_for_modalias() { + kmfm_modalias="$1" + [ -n "$kmfm_modalias" ] || return 3 + command -v modprobe >/dev/null 2>&1 || return 2 + + kmfm_modules=$(modprobe -R "$kmfm_modalias" 2>/dev/null | awk 'NF && !seen[$0]++') + [ -n "$kmfm_modules" ] || return 1 + printf '%s\n' "$kmfm_modules" +} + +# kernel_module_runtime_origin +# Prints builtin, loaded, or the module file reported by the running image. +# Returns 1 when the optional runtime metadata is unavailable. +kernel_module_runtime_origin() { + kmro_module="$1" + [ -n "$kmro_module" ] || return 3 + + if command -v modinfo >/dev/null 2>&1; then + kmro_filename=$(modinfo -F filename "$kmro_module" 2>/dev/null || true) + case "$kmro_filename" in + '(builtin)') + printf '%s\n' "builtin" + return 0 + ;; + '') + ;; + *) + printf '%s\n' "module:$kmro_filename" + return 0 + ;; + esac + fi + + if is_module_loaded "$kmro_module"; then + printf '%s\n' "loaded" + return 0 + fi + + return 1 +} + # interconnect_debugfs_dir # Prints the ICC debugfs directory when its graph and summary are readable. # Returns 0 when available, otherwise 1. @@ -4185,6 +4236,927 @@ dt_validate_usb_host_runtime() { test_result_record "SKIP" "USB host controller runtime evidence is not exposed" } +############################################################################### +# i2c_collect_runtime_inventory +# Correlates enabled Qualcomm I2C controllers with runtime adapters and clients. +# Returns 0 for healthy applicable hardware, 1 for inconsistent runtime state, +# 2 when I2C is not exposed, and 3 for invalid arguments. +############################################################################### +i2c_collect_runtime_inventory() { + icri_result_dir="$1" + I2C_RUNTIME_CONTROLLER_COUNT=0 + I2C_RUNTIME_ADAPTER_COUNT=0 + I2C_RUNTIME_CLIENT_COUNT=0 + I2C_RUNTIME_BOUND_CLIENT_COUNT=0 + I2C_RUNTIME_WAITING_CLIENT_COUNT=0 + I2C_RUNTIME_REGISTERED_DRIVER_COUNT=0 + I2C_RUNTIME_FAILURE_REASON="" + icri_controller_unbound=0 + icri_declared_client_unbound=0 + icri_seen_adapters="" + icri_unbound_clients="" + icri_waiting_clients="" + + [ -n "$icri_result_dir" ] || return 3 + mkdir -p "$icri_result_dir" || return 1 + : >"$icri_result_dir/i2c_controllers.tsv" + : >"$icri_result_dir/i2c_adapters.tsv" + : >"$icri_result_dir/i2c_clients.tsv" + : >"$icri_result_dir/i2c_registered_drivers.log" + + for icri_driver_path in /sys/bus/i2c/drivers/*; do + [ -d "$icri_driver_path" ] || continue + I2C_RUNTIME_REGISTERED_DRIVER_COUNT=$((I2C_RUNTIME_REGISTERED_DRIVER_COUNT + 1)) + basename "$icri_driver_path" >>"$icri_result_dir/i2c_registered_drivers.log" + done + log_info "I2C driver registry: registered=$I2C_RUNTIME_REGISTERED_DRIVER_COUNT artifact=$icri_result_dir/i2c_registered_drivers.log" + + log_info "I2C validation: correlating enabled Qualcomm controllers, adapters, clients, and bound drivers" + + if dt_runtime_root >/dev/null 2>&1; then + if dt_list_compatible_nodes \ + 'qcom,(geni-i2c|i2c-geni)' \ + regex >"$icri_result_dir/i2c_controller_nodes.log"; then + while IFS= read -r icri_node; do + [ -n "$icri_node" ] || continue + I2C_RUNTIME_CONTROLLER_COUNT=$((I2C_RUNTIME_CONTROLLER_COUNT + 1)) + icri_compatible=$(dt_property_text "$icri_node" compatible 2>/dev/null || true) + icri_device="" + icri_driver="" + + if icri_device=$(find_platform_device_for_dt_node "$icri_node" 2>/dev/null); then + icri_driver=$(platform_device_driver_name "$icri_device" 2>/dev/null || true) + fi + + printf '%s\tcompatible=%s\tdevice=%s\tdriver=%s\n' \ + "$icri_node" \ + "${icri_compatible:-unknown}" \ + "${icri_device##*/}" \ + "${icri_driver:-unbound}" >>"$icri_result_dir/i2c_controllers.tsv" + log_info "[I2C-CONTROLLER] node=$icri_node device=${icri_device##*/} driver=${icri_driver:-unbound}" + + if [ -z "$icri_device" ] || [ -z "$icri_driver" ]; then + icri_controller_unbound=$((icri_controller_unbound + 1)) + fi + done <"$icri_result_dir/i2c_controller_nodes.log" + fi + fi + + for icri_adapter in \ + /sys/class/i2c-adapter/i2c-* \ + /sys/bus/i2c/devices/i2c-*; do + [ -d "$icri_adapter" ] || continue + icri_adapter_id=${icri_adapter##*/} + case " $icri_seen_adapters " in + *" $icri_adapter_id "*) + continue + ;; + esac + icri_seen_adapters="$icri_seen_adapters $icri_adapter_id" + I2C_RUNTIME_ADAPTER_COUNT=$((I2C_RUNTIME_ADAPTER_COUNT + 1)) + icri_adapter_name=$(cat "$icri_adapter/name" 2>/dev/null || true) + icri_adapter_path=$(readlink -f "$icri_adapter/device" 2>/dev/null || true) + icri_adapter_driver=$(platform_device_driver_name "$icri_adapter/device" 2>/dev/null || true) + icri_devnode="absent" + [ -c "/dev/$icri_adapter_id" ] && icri_devnode="present" + + printf '%s\tname=%s\tdriver=%s\tdevnode=%s\tpath=%s\n' \ + "$icri_adapter_id" \ + "${icri_adapter_name:-unknown}" \ + "${icri_adapter_driver:-framework-or-unexposed}" \ + "$icri_devnode" \ + "${icri_adapter_path:-unknown}" >>"$icri_result_dir/i2c_adapters.tsv" + log_info "[I2C-ADAPTER] adapter=$icri_adapter_id name=${icri_adapter_name:-unknown} driver=${icri_adapter_driver:-framework-or-unexposed} devnode=$icri_devnode" + done + + for icri_client in /sys/bus/i2c/devices/[0-9]*-[0-9a-fA-F]*; do + [ -d "$icri_client" ] || continue + I2C_RUNTIME_CLIENT_COUNT=$((I2C_RUNTIME_CLIENT_COUNT + 1)) + icri_client_name=$(cat "$icri_client/name" 2>/dev/null || true) + icri_client_driver=$(platform_device_driver_name "$icri_client" 2>/dev/null || true) + icri_client_of_node=$(readlink -f "$icri_client/of_node" 2>/dev/null || true) + icri_client_modalias=$(cat "$icri_client/modalias" 2>/dev/null || true) + icri_client_waiting=$(cat "$icri_client/waiting_for_supplier" 2>/dev/null || true) + icri_client_modules="" + icri_client_module_state="" + icri_client_dt_status="" + icri_client_dt_children=0 + icri_client_resources="" + icri_client_channels=0 + if [ -n "$icri_client_modalias" ] && + icri_client_module_lines=$(kernel_modules_for_modalias "$icri_client_modalias" 2>/dev/null); then + icri_client_modules=$(printf '%s\n' "$icri_client_module_lines" | tr '\n' ',' | sed 's/,$//') + for icri_client_module in $icri_client_module_lines; do + icri_client_origin=$(kernel_module_runtime_origin "$icri_client_module" 2>/dev/null || true) + [ -n "$icri_client_origin" ] || icri_client_origin="alias-resolved" + if [ -n "$icri_client_module_state" ]; then + icri_client_module_state="$icri_client_module_state,$icri_client_module=$icri_client_origin" + else + icri_client_module_state="$icri_client_module=$icri_client_origin" + fi + done + fi + if [ -n "$icri_client_of_node" ]; then + icri_client_dt_status=$(dt_property_text "$icri_client_of_node" status 2>/dev/null || true) + [ -n "$icri_client_dt_status" ] || icri_client_dt_status="okay-default" + icri_client_dt_children=$( + find "$icri_client_of_node" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | + wc -l | + tr -d '[:space:]' + ) + for icri_client_property in \ + vdd-supply reset-gpios resets clocks power-domains; do + [ -e "$icri_client_of_node/$icri_client_property" ] || continue + if [ -n "$icri_client_resources" ]; then + icri_client_resources="$icri_client_resources,$icri_client_property" + else + icri_client_resources="$icri_client_property" + fi + done + fi + [ -n "$icri_client_resources" ] || icri_client_resources="none-exposed" + for icri_client_channel in "$icri_client"/channel-*; do + [ -L "$icri_client_channel" ] || continue + icri_client_channels=$((icri_client_channels + 1)) + done + + if [ -n "$icri_client_driver" ]; then + I2C_RUNTIME_BOUND_CLIENT_COUNT=$((I2C_RUNTIME_BOUND_CLIENT_COUNT + 1)) + elif [ -n "$icri_client_of_node" ]; then + icri_declared_client_unbound=$((icri_declared_client_unbound + 1)) + icri_unbound_clients="$icri_unbound_clients ${icri_client##*/}(${icri_client_name:-unknown})" + case "$icri_client_waiting" in + 1|Y|y|yes|true) + I2C_RUNTIME_WAITING_CLIENT_COUNT=$((I2C_RUNTIME_WAITING_CLIENT_COUNT + 1)) + icri_waiting_clients="$icri_waiting_clients ${icri_client##*/}(${icri_client_name:-unknown})" + ;; + esac + log_warn "[I2C-UNBOUND] client=${icri_client##*/} name=${icri_client_name:-unknown} dt_status=$icri_client_dt_status waiting_for_supplier=${icri_client_waiting:-unexposed} module_state=${icri_client_module_state:-unresolved} dt_children=$icri_client_dt_children runtime_channels=$icri_client_channels resources=$icri_client_resources" + fi + + printf '%s\tname=%s\tdriver=%s\tmodalias=%s\tmodule_candidates=%s\tmodule_state=%s\twaiting_for_supplier=%s\tdt_status=%s\tdt_children=%s\truntime_channels=%s\tresources=%s\tof_node=%s\n' \ + "${icri_client##*/}" \ + "${icri_client_name:-unknown}" \ + "${icri_client_driver:-unbound}" \ + "${icri_client_modalias:-unexposed}" \ + "${icri_client_modules:-unresolved}" \ + "${icri_client_module_state:-unresolved}" \ + "${icri_client_waiting:-unexposed}" \ + "${icri_client_dt_status:-unknown}" \ + "$icri_client_dt_children" \ + "$icri_client_channels" \ + "$icri_client_resources" \ + "${icri_client_of_node:-none}" >>"$icri_result_dir/i2c_clients.tsv" + log_info "[I2C-CLIENT] client=${icri_client##*/} name=${icri_client_name:-unknown} driver=${icri_client_driver:-unbound} modalias=${icri_client_modalias:-unexposed} module_candidates=${icri_client_modules:-unresolved} waiting_for_supplier=${icri_client_waiting:-unexposed} of_node=${icri_client_of_node:-none}" + done + + log_info "I2C runtime summary: controllers=$I2C_RUNTIME_CONTROLLER_COUNT adapters=$I2C_RUNTIME_ADAPTER_COUNT clients=$I2C_RUNTIME_CLIENT_COUNT bound_clients=$I2C_RUNTIME_BOUND_CLIENT_COUNT waiting_for_supplier=$I2C_RUNTIME_WAITING_CLIENT_COUNT artifact=$icri_result_dir/i2c_adapters.tsv" + + if [ "$I2C_RUNTIME_CONTROLLER_COUNT" -eq 0 ] && + [ "$I2C_RUNTIME_ADAPTER_COUNT" -eq 0 ]; then + return 2 + fi + + if [ "$I2C_RUNTIME_CONTROLLER_COUNT" -gt 0 ] && + [ "$I2C_RUNTIME_ADAPTER_COUNT" -eq 0 ]; then + I2C_RUNTIME_FAILURE_REASON="enabled Qualcomm I2C controllers expose no runtime adapters" + elif [ "$icri_controller_unbound" -gt 0 ]; then + I2C_RUNTIME_FAILURE_REASON="$icri_controller_unbound enabled Qualcomm I2C controller(s) have no bound platform driver" + elif [ "$I2C_RUNTIME_WAITING_CLIENT_COUNT" -gt 0 ]; then + I2C_RUNTIME_FAILURE_REASON="$I2C_RUNTIME_WAITING_CLIENT_COUNT DT-declared I2C client(s) are waiting for unresolved suppliers:${icri_waiting_clients}" + elif [ "$icri_declared_client_unbound" -gt 0 ]; then + I2C_RUNTIME_FAILURE_REASON="$icri_declared_client_unbound DT-declared I2C client(s) are unbound:${icri_unbound_clients}" + fi + + [ -z "$I2C_RUNTIME_FAILURE_REASON" ] || return 1 + return 0 +} + +############################################################################### +# i2c_run_legacy_test +# Runs the image-provided i2c-msm-test compatibility path and verifies both its +# status and transfer markers. Returns 0 for success, 1 for a failed transfer, +# 2 when the tool is absent, 3 for invalid arguments, and 4 without an adapter. +############################################################################### +i2c_run_legacy_test() { + irlt_result_dir="$1" + irlt_requested_adapter="$2" + irlt_timeout="$3" + I2C_LEGACY_SELECTED_ADAPTER="" + + [ -n "$irlt_result_dir" ] && [ -n "$irlt_requested_adapter" ] || return 3 + case "$irlt_timeout" in + ''|*[!0-9]*|0) + return 3 + ;; + esac + + command -v i2c-msm-test >/dev/null 2>&1 || return 2 + + case "$irlt_requested_adapter" in + auto) + for irlt_devnode in /dev/i2c-*; do + [ -c "$irlt_devnode" ] || continue + I2C_LEGACY_SELECTED_ADAPTER="$irlt_devnode" + break + done + ;; + /dev/i2c-*) + I2C_LEGACY_SELECTED_ADAPTER="$irlt_requested_adapter" + ;; + *) + I2C_LEGACY_SELECTED_ADAPTER="/dev/i2c-$irlt_requested_adapter" + ;; + esac + + [ -n "$I2C_LEGACY_SELECTED_ADAPTER" ] && + [ -c "$I2C_LEGACY_SELECTED_ADAPTER" ] || return 4 + + log_info "I2C functional target: adapter=$I2C_LEGACY_SELECTED_ADAPTER command=i2c-msm-test timeout=${irlt_timeout}s" + if ! run_with_timeout_log \ + "$irlt_timeout" \ + "$irlt_result_dir/i2c_msm_test.log" \ + i2c-msm-test -v -D "$I2C_LEGACY_SELECTED_ADAPTER" -l; then + log_file_with_label "I2C-LEGACY" "$irlt_result_dir/i2c_msm_test.log" + return 1 + fi + + if ! grep -q 'Reading' "$irlt_result_dir/i2c_msm_test.log" || + ! grep -q 'ret:1' "$irlt_result_dir/i2c_msm_test.log"; then + log_file_with_label "I2C-LEGACY" "$irlt_result_dir/i2c_msm_test.log" + return 1 + fi + + return 0 +} + +############################################################################### +# pcie_collect_runtime_health +# Captures PCI device link, MSI, driver, and runtime-power evidence without +# changing link or power state. Returns 0 when inventory is readable, 1 when +# malformed, 2 when PCI is absent, and 3 for invalid arguments. +############################################################################### +pcie_collect_runtime_health() { + pcrh_result_dir="$1" + PCIE_RUNTIME_DEVICE_COUNT=0 + PCIE_RUNTIME_LINK_COUNT=0 + PCIE_RUNTIME_MSI_DEVICE_COUNT=0 + PCIE_RUNTIME_POWER_COUNT=0 + PCIE_RUNTIME_INACTIVE_PORT_COUNT=0 + PCIE_RUNTIME_FAILURE_REASON="" + pcrh_invalid_link_count=0 + + [ -n "$pcrh_result_dir" ] || return 3 + mkdir -p "$pcrh_result_dir" || return 1 + : >"$pcrh_result_dir/pcie_runtime.tsv" + + log_info "PCIe runtime validation: collecting driver, link, MSI, and runtime-power evidence" + + for pcrh_device in /sys/bus/pci/devices/*; do + [ -d "$pcrh_device" ] || continue + PCIE_RUNTIME_DEVICE_COUNT=$((PCIE_RUNTIME_DEVICE_COUNT + 1)) + pcrh_vendor=$(cat "$pcrh_device/vendor" 2>/dev/null || true) + pcrh_device_id=$(cat "$pcrh_device/device" 2>/dev/null || true) + pcrh_class=$(cat "$pcrh_device/class" 2>/dev/null || true) + pcrh_driver=$(platform_device_driver_name "$pcrh_device" 2>/dev/null || true) + pcrh_current_speed=$(cat "$pcrh_device/current_link_speed" 2>/dev/null || true) + pcrh_max_speed=$(cat "$pcrh_device/max_link_speed" 2>/dev/null || true) + pcrh_current_width=$(cat "$pcrh_device/current_link_width" 2>/dev/null || true) + pcrh_max_width=$(cat "$pcrh_device/max_link_width" 2>/dev/null || true) + pcrh_runtime_status=$(cat "$pcrh_device/power/runtime_status" 2>/dev/null || true) + pcrh_msi_count=0 + pcrh_link_state="unexposed" + + if [ -d "$pcrh_device/msi_irqs" ]; then + pcrh_msi_count=$(find "$pcrh_device/msi_irqs" -mindepth 1 -maxdepth 1 2>/dev/null | wc -l | tr -d '[:space:]') + fi + case "$pcrh_msi_count" in + ''|*[!0-9]*) + pcrh_msi_count=0 + ;; + esac + if [ "$pcrh_msi_count" -gt 0 ]; then + PCIE_RUNTIME_MSI_DEVICE_COUNT=$((PCIE_RUNTIME_MSI_DEVICE_COUNT + 1)) + fi + + if [ -n "$pcrh_current_speed" ] || [ -n "$pcrh_current_width" ]; then + PCIE_RUNTIME_LINK_COUNT=$((PCIE_RUNTIME_LINK_COUNT + 1)) + pcrh_link_state="up" + case "$pcrh_current_width" in + 0|0x|0X) + case "$pcrh_class" in + 0x0604*|0604*) + pcrh_link_state="inactive-bridge-port" + PCIE_RUNTIME_INACTIVE_PORT_COUNT=$((PCIE_RUNTIME_INACTIVE_PORT_COUNT + 1)) + ;; + *) + pcrh_link_state="invalid-zero-width" + pcrh_invalid_link_count=$((pcrh_invalid_link_count + 1)) + ;; + esac + ;; + esac + fi + [ -n "$pcrh_runtime_status" ] && PCIE_RUNTIME_POWER_COUNT=$((PCIE_RUNTIME_POWER_COUNT + 1)) + + printf '%s\tvendor=%s\tdevice=%s\tclass=%s\tdriver=%s\tcurrent_speed=%s\tmax_speed=%s\tcurrent_width=%s\tmax_width=%s\tlink_state=%s\tmsi_irqs=%s\truntime_status=%s\n' \ + "${pcrh_device##*/}" \ + "${pcrh_vendor:-unknown}" \ + "${pcrh_device_id:-unknown}" \ + "${pcrh_class:-unknown}" \ + "${pcrh_driver:-unbound}" \ + "${pcrh_current_speed:-unexposed}" \ + "${pcrh_max_speed:-unexposed}" \ + "${pcrh_current_width:-unexposed}" \ + "${pcrh_max_width:-unexposed}" \ + "$pcrh_link_state" \ + "$pcrh_msi_count" \ + "${pcrh_runtime_status:-unexposed}" >>"$pcrh_result_dir/pcie_runtime.tsv" + log_info "[PCIE] bdf=${pcrh_device##*/} id=${pcrh_vendor:-unknown}:${pcrh_device_id:-unknown} class=${pcrh_class:-unknown} driver=${pcrh_driver:-unbound} link=${pcrh_current_speed:-unexposed}/x${pcrh_current_width:-unexposed} link_state=$pcrh_link_state max=${pcrh_max_speed:-unexposed}/x${pcrh_max_width:-unexposed} msi_irqs=$pcrh_msi_count runtime_status=${pcrh_runtime_status:-unexposed}" + done + + if [ "$PCIE_RUNTIME_DEVICE_COUNT" -eq 0 ]; then + return 2 + fi + + if [ "$pcrh_invalid_link_count" -gt 0 ]; then + # Exported result for suite orchestration after this helper returns. + # shellcheck disable=SC2034 + PCIE_RUNTIME_FAILURE_REASON="$pcrh_invalid_link_count non-bridge PCIe device(s) report zero negotiated link width" + return 1 + fi + + log_info "PCIe runtime summary: devices=$PCIE_RUNTIME_DEVICE_COUNT links=$PCIE_RUNTIME_LINK_COUNT inactive_bridge_ports=$PCIE_RUNTIME_INACTIVE_PORT_COUNT msi_devices=$PCIE_RUNTIME_MSI_DEVICE_COUNT power_nodes=$PCIE_RUNTIME_POWER_COUNT artifact=$pcrh_result_dir/pcie_runtime.tsv" + return 0 +} + +############################################################################### +# usb_collect_host_inventory +# Captures USB root hubs, connected devices, interfaces, speeds, drivers, and +# runtime-power state. Returns 0 for host runtime, 1 for malformed host state, +# 2 when host mode is inactive, and 3 for invalid arguments. +############################################################################### +usb_collect_host_inventory() { + uchi_result_dir="$1" + USB_RUNTIME_ROOT_HUB_COUNT=0 + USB_RUNTIME_DEVICE_COUNT=0 + USB_RUNTIME_INTERFACE_COUNT=0 + USB_RUNTIME_BOUND_INTERFACE_COUNT=0 + USB_RUNTIME_FAILURE_REASON="" + + [ -n "$uchi_result_dir" ] || return 3 + mkdir -p "$uchi_result_dir" || return 1 + : >"$uchi_result_dir/usb_host_runtime.tsv" + + log_info "USB host validation: collecting root-hub, device, interface, speed, driver, and runtime-power evidence" + + for uchi_hub in /sys/bus/usb/devices/usb*; do + [ -d "$uchi_hub" ] || continue + USB_RUNTIME_ROOT_HUB_COUNT=$((USB_RUNTIME_ROOT_HUB_COUNT + 1)) + uchi_speed=$(cat "$uchi_hub/speed" 2>/dev/null || true) + uchi_runtime=$(cat "$uchi_hub/power/runtime_status" 2>/dev/null || true) + uchi_path=$(readlink -f "$uchi_hub" 2>/dev/null || true) + printf 'root-hub\t%s\tspeed=%s\truntime_status=%s\tpath=%s\n' \ + "${uchi_hub##*/}" \ + "${uchi_speed:-unknown}" \ + "${uchi_runtime:-unexposed}" \ + "${uchi_path:-unknown}" >>"$uchi_result_dir/usb_host_runtime.tsv" + log_info "[USB-ROOT] hub=${uchi_hub##*/} speed=${uchi_speed:-unknown} runtime_status=${uchi_runtime:-unexposed} path=${uchi_path:-unknown}" + done + + if [ "$USB_RUNTIME_ROOT_HUB_COUNT" -eq 0 ]; then + for uchi_role in /sys/class/usb_role/*/role /sys/class/typec/*/data_role; do + [ -r "$uchi_role" ] || continue + uchi_role_value=$(tr -d '[:space:]' <"$uchi_role" 2>/dev/null) + [ -n "$uchi_role_value" ] || continue + log_info "USB host mode is inactive: role_node=$uchi_role role=$uchi_role_value" + return 2 + done + # Exported result for suite orchestration after this helper returns. + # shellcheck disable=SC2034 + USB_RUNTIME_FAILURE_REASON="no USB root hubs are exposed and no inactive device role could be confirmed" + return 1 + fi + + for uchi_device in /sys/bus/usb/devices/*-*; do + [ -d "$uchi_device" ] || continue + case "${uchi_device##*/}" in + *:*) + continue + ;; + esac + [ -r "$uchi_device/idVendor" ] || continue + + USB_RUNTIME_DEVICE_COUNT=$((USB_RUNTIME_DEVICE_COUNT + 1)) + uchi_vendor=$(cat "$uchi_device/idVendor" 2>/dev/null || true) + uchi_product_id=$(cat "$uchi_device/idProduct" 2>/dev/null || true) + uchi_product=$(tr -d '\000' <"$uchi_device/product" 2>/dev/null || true) + uchi_speed=$(cat "$uchi_device/speed" 2>/dev/null || true) + uchi_runtime=$(cat "$uchi_device/power/runtime_status" 2>/dev/null || true) + log_info "[USB-DEVICE] device=${uchi_device##*/} id=${uchi_vendor:-unknown}:${uchi_product_id:-unknown} product=${uchi_product:-unknown} speed_mbps=${uchi_speed:-unknown} runtime_status=${uchi_runtime:-unexposed}" + + for uchi_interface in "$uchi_device":*; do + [ -d "$uchi_interface" ] || continue + [ -r "$uchi_interface/bInterfaceClass" ] || continue + USB_RUNTIME_INTERFACE_COUNT=$((USB_RUNTIME_INTERFACE_COUNT + 1)) + uchi_class=$(cat "$uchi_interface/bInterfaceClass" 2>/dev/null || true) + uchi_driver=$(platform_device_driver_name "$uchi_interface" 2>/dev/null || true) + [ -n "$uchi_driver" ] && USB_RUNTIME_BOUND_INTERFACE_COUNT=$((USB_RUNTIME_BOUND_INTERFACE_COUNT + 1)) + printf 'interface\t%s\tclass=%s\tdriver=%s\tparent=%s\n' \ + "${uchi_interface##*/}" \ + "${uchi_class:-unknown}" \ + "${uchi_driver:-unbound}" \ + "${uchi_device##*/}" >>"$uchi_result_dir/usb_host_runtime.tsv" + log_info "[USB-INTERFACE] interface=${uchi_interface##*/} class=${uchi_class:-unknown} driver=${uchi_driver:-unbound}" + done + done + + log_info "USB host summary: root_hubs=$USB_RUNTIME_ROOT_HUB_COUNT devices=$USB_RUNTIME_DEVICE_COUNT interfaces=$USB_RUNTIME_INTERFACE_COUNT bound_interfaces=$USB_RUNTIME_BOUND_INTERFACE_COUNT artifact=$uchi_result_dir/usb_host_runtime.tsv" + return 0 +} + +############################################################################### +# usb_validate_hid_runtime +# Validates binding for connected USB HID class interfaces. Returns 0 when all +# discovered interfaces are bound, 1 when any is unbound, 2 when absent, and 3 +# for invalid arguments. +############################################################################### +usb_validate_hid_runtime() { + uvhr_result_dir="$1" + USB_HID_INTERFACE_COUNT=0 + USB_HID_BOUND_COUNT=0 + USB_HID_UNBOUND_COUNT=0 + + [ -n "$uvhr_result_dir" ] || return 3 + mkdir -p "$uvhr_result_dir" || return 1 + : >"$uvhr_result_dir/usb_hid_runtime.tsv" + + log_info "USB HID validation: checking class interfaces and kernel-driver binding" + + for uvhr_class_file in /sys/bus/usb/devices/*:*/bInterfaceClass; do + [ -r "$uvhr_class_file" ] || continue + [ "$(cat "$uvhr_class_file" 2>/dev/null)" = "03" ] || continue + + uvhr_interface=$(dirname "$uvhr_class_file") + uvhr_interface_name=${uvhr_interface##*/} + uvhr_device_name=${uvhr_interface_name%%:*} + uvhr_device="/sys/bus/usb/devices/$uvhr_device_name" + uvhr_driver=$(platform_device_driver_name "$uvhr_interface" 2>/dev/null || true) + uvhr_vendor=$(cat "$uvhr_device/idVendor" 2>/dev/null || true) + uvhr_product_id=$(cat "$uvhr_device/idProduct" 2>/dev/null || true) + uvhr_product=$(tr -d '\000' <"$uvhr_device/product" 2>/dev/null || true) + USB_HID_INTERFACE_COUNT=$((USB_HID_INTERFACE_COUNT + 1)) + + if [ -n "$uvhr_driver" ]; then + USB_HID_BOUND_COUNT=$((USB_HID_BOUND_COUNT + 1)) + else + USB_HID_UNBOUND_COUNT=$((USB_HID_UNBOUND_COUNT + 1)) + fi + + printf '%s\tdevice=%s\tid=%s:%s\tproduct=%s\tdriver=%s\n' \ + "$uvhr_interface_name" \ + "$uvhr_device_name" \ + "${uvhr_vendor:-unknown}" \ + "${uvhr_product_id:-unknown}" \ + "${uvhr_product:-unknown}" \ + "${uvhr_driver:-unbound}" >>"$uvhr_result_dir/usb_hid_runtime.tsv" + log_info "[USB-HID] interface=$uvhr_interface_name device=$uvhr_device_name id=${uvhr_vendor:-unknown}:${uvhr_product_id:-unknown} product=${uvhr_product:-unknown} driver=${uvhr_driver:-unbound}" + done + + [ "$USB_HID_INTERFACE_COUNT" -gt 0 ] || return 2 + [ "$USB_HID_UNBOUND_COUNT" -eq 0 ] || return 1 + return 0 +} + +############################################################################### +# usb_validate_mass_storage_runtime +# Validates USB mass-storage binding and block nodes, then optionally performs +# a non-destructive 512-byte read. Returns 0 for healthy devices, 1 for a +# runtime failure, 2 when no fixture is present, and 3 for invalid arguments. +############################################################################### +usb_validate_mass_storage_runtime() { + uvms_result_dir="$1" + uvms_read_verify="$2" + uvms_wait_seconds="$3" + USB_MSD_DEVICE_COUNT=0 + USB_MSD_FAILURE_COUNT=0 + + [ -n "$uvms_result_dir" ] || return 3 + case "$uvms_read_verify" in + 0|1) + ;; + *) + return 3 + ;; + esac + case "$uvms_wait_seconds" in + ''|*[!0-9]*) + return 3 + ;; + esac + + mkdir -p "$uvms_result_dir" || return 1 + : >"$uvms_result_dir/usb_msd_runtime.tsv" + uvms_device_file="$uvms_result_dir/usb_msd_devices.list" + : >"$uvms_device_file" + + log_info "USB mass-storage validation: checking class binding, block-device creation, and optional read access" + + for uvms_class_file in /sys/bus/usb/devices/*:*/bInterfaceClass; do + [ -r "$uvms_class_file" ] || continue + [ "$(cat "$uvms_class_file" 2>/dev/null)" = "08" ] || continue + uvms_interface_name=$(basename "$(dirname "$uvms_class_file")") + uvms_device_name=${uvms_interface_name%%:*} + if ! grep -Fqx "$uvms_device_name" "$uvms_device_file"; then + printf '%s\n' "$uvms_device_name" >>"$uvms_device_file" + fi + done + + while IFS= read -r uvms_device_name; do + [ -n "$uvms_device_name" ] || continue + USB_MSD_DEVICE_COUNT=$((USB_MSD_DEVICE_COUNT + 1)) + uvms_device="/sys/bus/usb/devices/$uvms_device_name" + uvms_vendor=$(cat "$uvms_device/idVendor" 2>/dev/null || true) + uvms_product_id=$(cat "$uvms_device/idProduct" 2>/dev/null || true) + uvms_product=$(tr -d '\000' <"$uvms_device/product" 2>/dev/null || true) + uvms_driver="" + uvms_block_list="" + + for uvms_interface in "$uvms_device":*; do + [ -d "$uvms_interface" ] || continue + [ "$(cat "$uvms_interface/bInterfaceClass" 2>/dev/null)" = "08" ] || continue + uvms_driver=$(platform_device_driver_name "$uvms_interface" 2>/dev/null || true) + + if [ -z "$uvms_driver" ]; then + break + fi + + uvms_waited=0 + while [ "$uvms_waited" -le "$uvms_wait_seconds" ]; do + uvms_block_list="$({ + for uvms_block_path in \ + "$uvms_interface"/host*/target*/*/block/* \ + "$uvms_interface"/host*/target*/*/*/block/* \ + "$uvms_interface"/host*/target*/block/*; do + [ -e "$uvms_block_path" ] || continue + basename "$uvms_block_path" + done + } | sort -u)" + [ -n "$uvms_block_list" ] && break + [ "$uvms_waited" -eq "$uvms_wait_seconds" ] && break + sleep 1 + uvms_waited=$((uvms_waited + 1)) + done + break + done + + printf '%s\tid=%s:%s\tproduct=%s\tdriver=%s\tblocks=%s\n' \ + "$uvms_device_name" \ + "${uvms_vendor:-unknown}" \ + "${uvms_product_id:-unknown}" \ + "${uvms_product:-unknown}" \ + "${uvms_driver:-unbound}" \ + "${uvms_block_list:-none}" >>"$uvms_result_dir/usb_msd_runtime.tsv" + log_info "[USB-MSD] device=$uvms_device_name id=${uvms_vendor:-unknown}:${uvms_product_id:-unknown} product=${uvms_product:-unknown} driver=${uvms_driver:-unbound} blocks=${uvms_block_list:-none}" + + if [ -z "$uvms_driver" ]; then + USB_MSD_FAILURE_COUNT=$((USB_MSD_FAILURE_COUNT + 1)) + log_warn "USB mass-storage interface is unbound: device=$uvms_device_name" + continue + fi + + if [ -z "$uvms_block_list" ]; then + USB_MSD_FAILURE_COUNT=$((USB_MSD_FAILURE_COUNT + 1)) + log_warn "USB mass-storage device has no block device after ${uvms_wait_seconds}s: device=$uvms_device_name driver=$uvms_driver" + continue + fi + + if [ "$uvms_read_verify" -eq 1 ]; then + for uvms_block in $uvms_block_list; do + if [ ! -b "/dev/$uvms_block" ]; then + USB_MSD_FAILURE_COUNT=$((USB_MSD_FAILURE_COUNT + 1)) + log_warn "USB mass-storage block node is missing: /dev/$uvms_block" + continue + fi + + log_info "USB mass-storage read validation: device=/dev/$uvms_block bytes=512" + if ! dd \ + if="/dev/$uvms_block" \ + of=/dev/null \ + bs=512 \ + count=1 >"$uvms_result_dir/read_${uvms_block}.log" 2>&1; then + USB_MSD_FAILURE_COUNT=$((USB_MSD_FAILURE_COUNT + 1)) + log_file_with_label "USB-MSD-READ" "$uvms_result_dir/read_${uvms_block}.log" + fi + done + fi + done <"$uvms_device_file" + + [ "$USB_MSD_DEVICE_COUNT" -gt 0 ] || return 2 + [ "$USB_MSD_FAILURE_COUNT" -eq 0 ] || return 1 + return 0 +} + +############################################################################### +# dt_validate_pmic_glink_ucsi +# Validates the Qualcomm PMIC GLINK parent, UCSI auxiliary driver, and Type-C +# runtime exposure only when an enabled PMIC GLINK DT node declares support. +############################################################################### +dt_validate_pmic_glink_ucsi() { + dtvpgu_result_dir="$1" + dtvpgu_nodes_file="$dtvpgu_result_dir/pmic_glink_nodes.log" + dtvpgu_parent_count=0 + dtvpgu_aux_count=0 + dtvpgu_typec_count=0 + dtvpgu_role_invalid_count=0 + + [ -n "$dtvpgu_result_dir" ] || return 3 + : >"$dtvpgu_nodes_file" + + log_info "PMIC GLINK/UCSI validation: checking enabled DT parents, auxiliary binding, Type-C ports, and role state" + + if ! dt_list_compatible_nodes \ + '(^|[[:space:]])qcom,([^[:space:]]+-)?pmic-glink([[:space:]]|$)' \ + regex >"$dtvpgu_nodes_file"; then + test_result_record "SKIP" "Qualcomm PMIC GLINK UCSI capability is not enabled in the runtime device tree" + return 0 + fi + + while IFS= read -r dtvpgu_node; do + [ -n "$dtvpgu_node" ] || continue + dtvpgu_parent_count=$((dtvpgu_parent_count + 1)) + dtvpgu_compatible="$(dt_property_text "$dtvpgu_node" compatible 2>/dev/null || true)" + log_info "[PMIC-GLINK-DT] node=$dtvpgu_node compatible=${dtvpgu_compatible:-unknown}" + + if dtvpgu_device="$(find_platform_device_for_dt_node "$dtvpgu_node" 2>/dev/null)" && + dtvpgu_driver="$(platform_device_driver_name "$dtvpgu_device" 2>/dev/null)"; then + test_result_record "PASS" "PMIC GLINK parent is bound: device=$(basename "$dtvpgu_device") driver=$dtvpgu_driver" + else + test_result_record "FAIL" "Enabled PMIC GLINK node has no bound runtime platform driver: ${dtvpgu_node##*/}" + fi + done <"$dtvpgu_nodes_file" + + for dtvpgu_aux in /sys/bus/auxiliary/devices/pmic_glink.ucsi.*; do + [ -d "$dtvpgu_aux" ] || continue + dtvpgu_aux_count=$((dtvpgu_aux_count + 1)) + if [ -L "$dtvpgu_aux/driver" ]; then + dtvpgu_aux_driver="$(basename "$(readlink -f "$dtvpgu_aux/driver" 2>/dev/null)" 2>/dev/null || true)" + log_info "[PMIC-GLINK-UCSI] auxiliary=${dtvpgu_aux##*/} driver=${dtvpgu_aux_driver:-unknown}" + case "$dtvpgu_aux_driver" in + *pmic_glink_ucsi) + test_result_record "PASS" "PMIC GLINK UCSI auxiliary device is bound: device=${dtvpgu_aux##*/} driver=$dtvpgu_aux_driver" + ;; + *) + test_result_record "FAIL" "PMIC GLINK UCSI auxiliary device has unexpected driver: device=${dtvpgu_aux##*/} driver=${dtvpgu_aux_driver:-unknown}" + ;; + esac + else + log_info "[PMIC-GLINK-UCSI] auxiliary=${dtvpgu_aux##*/} driver=unbound" + test_result_record "FAIL" "PMIC GLINK UCSI auxiliary device is present but unbound: ${dtvpgu_aux##*/}" + fi + done + + if [ "$dtvpgu_aux_count" -eq 0 ]; then + test_result_record "FAIL" "PMIC GLINK is declared but no UCSI auxiliary device is exposed" + elif [ "$dtvpgu_aux_count" -lt "$dtvpgu_parent_count" ]; then + test_result_record "FAIL" "Only $dtvpgu_aux_count UCSI auxiliary device(s) are exposed for $dtvpgu_parent_count PMIC GLINK parent(s)" + fi + + : >"$dtvpgu_result_dir/typec_ports.log" + for dtvpgu_port in /sys/class/typec/port*; do + [ -d "$dtvpgu_port" ] || continue + case "${dtvpgu_port##*/}" in + *-partner*) + continue + ;; + esac + dtvpgu_port_path="$(readlink -f "$dtvpgu_port" 2>/dev/null || true)" + case "$dtvpgu_port_path" in + *pmic_glink.ucsi.*) + ;; + *) + continue + ;; + esac + + dtvpgu_typec_count=$((dtvpgu_typec_count + 1)) + dtvpgu_power_role="$(cat "$dtvpgu_port/power_role" 2>/dev/null || true)" + dtvpgu_data_role="$(cat "$dtvpgu_port/data_role" 2>/dev/null || true)" + printf '%s\tpower_role=%s\tdata_role=%s\tpath=%s\n' \ + "${dtvpgu_port##*/}" \ + "${dtvpgu_power_role:-unknown}" \ + "${dtvpgu_data_role:-unknown}" \ + "${dtvpgu_port_path:-unknown}" >>"$dtvpgu_result_dir/typec_ports.log" + log_info "[PMIC-GLINK-UCSI] port=${dtvpgu_port##*/} power_role=${dtvpgu_power_role:-unknown} data_role=${dtvpgu_data_role:-unknown}" + + case "$dtvpgu_power_role" in + source|sink|*'[source]'*|*'[sink]'*) + ;; + *) + dtvpgu_role_invalid_count=$((dtvpgu_role_invalid_count + 1)) + continue + ;; + esac + case "$dtvpgu_data_role" in + host|device|*'[host]'*|*'[device]'*) + ;; + *) + dtvpgu_role_invalid_count=$((dtvpgu_role_invalid_count + 1)) + ;; + esac + done + + if [ "$dtvpgu_typec_count" -eq 0 ]; then + test_result_record "FAIL" "PMIC GLINK UCSI is declared but no UCSI-backed Type-C ports are exposed" + elif [ "$dtvpgu_role_invalid_count" -gt 0 ]; then + test_result_record "FAIL" "PMIC GLINK UCSI has $dtvpgu_role_invalid_count Type-C port(s) with unreadable or invalid role state" + else + test_result_record "PASS" "PMIC GLINK UCSI exposes $dtvpgu_typec_count Type-C port(s) with valid power and data roles" + fi +} + +############################################################################### +# dt_validate_thermal_runtime +# Validates readable and plausible thermal-zone data when thermal zones are +# declared. Cooling devices are required only when a cooling provider exists. +############################################################################### +dt_validate_thermal_runtime() { + dtvtr_root="$1" + dtvtr_result_dir="$2" + dtvtr_zone_file="$dtvtr_result_dir/thermal_zones.log" + dtvtr_cooling_file="$dtvtr_result_dir/cooling_devices.log" + dtvtr_zone_count=0 + dtvtr_readable_count=0 + dtvtr_unreadable_count=0 + dtvtr_invalid_count=0 + dtvtr_cooling_count=0 + dtvtr_cooling_invalid_count=0 + dtvtr_declared_count=0 + + [ -d "$dtvtr_root" ] && [ -n "$dtvtr_result_dir" ] || return 3 + + log_info "Thermal validation: checking enabled DT thermal zones, runtime temperatures, and cooling-device state" + + if [ ! -d "$dtvtr_root/thermal-zones" ]; then + test_result_record "SKIP" "Runtime device tree does not declare thermal zones" + return 0 + fi + + for dtvtr_declared in "$dtvtr_root/thermal-zones"/*; do + [ -d "$dtvtr_declared" ] || continue + dt_node_enabled "$dtvtr_declared" || continue + dtvtr_declared_count=$((dtvtr_declared_count + 1)) + done + + if [ "$dtvtr_declared_count" -eq 0 ]; then + test_result_record "SKIP" "Runtime device tree has no enabled thermal zones" + return 0 + fi + + : >"$dtvtr_zone_file" + for dtvtr_zone in /sys/class/thermal/thermal_zone*; do + [ -d "$dtvtr_zone" ] || continue + dtvtr_zone_count=$((dtvtr_zone_count + 1)) + dtvtr_type="$(cat "$dtvtr_zone/type" 2>/dev/null || true)" + dtvtr_temp="$(cat "$dtvtr_zone/temp" 2>/dev/null || true)" + printf '%s\ttype=%s\ttemp_mC=%s\n' \ + "${dtvtr_zone##*/}" \ + "${dtvtr_type:-unknown}" \ + "${dtvtr_temp:-unreadable}" >>"$dtvtr_zone_file" + log_info "[THERMAL] zone=${dtvtr_zone##*/} type=${dtvtr_type:-unknown} temp_mC=${dtvtr_temp:-unreadable}" + + if [ -z "$dtvtr_type" ]; then + dtvtr_invalid_count=$((dtvtr_invalid_count + 1)) + continue + fi + + if [ -z "$dtvtr_temp" ]; then + dtvtr_unreadable_count=$((dtvtr_unreadable_count + 1)) + continue + fi + + case "$dtvtr_temp" in + -*) + dtvtr_temp_digits=${dtvtr_temp#-} + ;; + *) + dtvtr_temp_digits=$dtvtr_temp + ;; + esac + + case "$dtvtr_temp_digits" in + ''|*[!0-9]*) + dtvtr_invalid_count=$((dtvtr_invalid_count + 1)) + ;; + *) + if [ "$dtvtr_temp" -lt -100000 ] || [ "$dtvtr_temp" -gt 250000 ]; then + dtvtr_invalid_count=$((dtvtr_invalid_count + 1)) + else + dtvtr_readable_count=$((dtvtr_readable_count + 1)) + fi + ;; + esac + done + + if [ "$dtvtr_zone_count" -eq 0 ]; then + test_result_record "FAIL" "Thermal zones are declared but no runtime thermal zones are exposed" + elif [ "$dtvtr_invalid_count" -gt 0 ]; then + test_result_record "FAIL" "Thermal runtime has $dtvtr_invalid_count malformed or implausible zone(s) out of $dtvtr_zone_count" + elif [ "$dtvtr_readable_count" -eq 0 ]; then + test_result_record "FAIL" "Thermal zones are exposed but none currently provide a readable temperature" + else + test_result_record "PASS" "Thermal runtime exposes $dtvtr_readable_count readable zone(s) with plausible temperatures" + if [ "$dtvtr_unreadable_count" -gt 0 ]; then + test_result_record "SKIP" "$dtvtr_unreadable_count optional or aggregate thermal zone(s) do not currently expose temperature data" + fi + fi + + : >"$dtvtr_cooling_file" + for dtvtr_cooling in /sys/class/thermal/cooling_device*; do + [ -d "$dtvtr_cooling" ] || continue + dtvtr_cooling_count=$((dtvtr_cooling_count + 1)) + dtvtr_cooling_type="$(cat "$dtvtr_cooling/type" 2>/dev/null || true)" + dtvtr_cooling_state="$(cat "$dtvtr_cooling/cur_state" 2>/dev/null || true)" + dtvtr_cooling_max="$(cat "$dtvtr_cooling/max_state" 2>/dev/null || true)" + printf '%s\ttype=%s\tstate=%s\tmax_state=%s\n' \ + "${dtvtr_cooling##*/}" \ + "${dtvtr_cooling_type:-unknown}" \ + "${dtvtr_cooling_state:-unknown}" \ + "${dtvtr_cooling_max:-unknown}" >>"$dtvtr_cooling_file" + log_info "[THERMAL] cooling=${dtvtr_cooling##*/} type=${dtvtr_cooling_type:-unknown} state=${dtvtr_cooling_state:-unknown} max_state=${dtvtr_cooling_max:-unknown}" + + dtvtr_cooling_valid=1 + case "$dtvtr_cooling_state" in + ''|*[!0-9]*) + dtvtr_cooling_valid=0 + ;; + esac + case "$dtvtr_cooling_max" in + ''|*[!0-9]*) + dtvtr_cooling_valid=0 + ;; + esac + + if [ "$dtvtr_cooling_valid" -eq 0 ] || + [ "$dtvtr_cooling_state" -gt "$dtvtr_cooling_max" ]; then + dtvtr_cooling_invalid_count=$((dtvtr_cooling_invalid_count + 1)) + fi + done + + if dt_list_enabled_property_nodes "$dtvtr_root" '#cooling-cells' >/dev/null 2>&1; then + if [ "$dtvtr_cooling_count" -gt 0 ]; then + if [ "$dtvtr_cooling_invalid_count" -eq 0 ]; then + test_result_record "PASS" "Thermal runtime exposes $dtvtr_cooling_count valid cooling device(s)" + else + test_result_record "FAIL" "Thermal runtime has $dtvtr_cooling_invalid_count malformed cooling device(s) out of $dtvtr_cooling_count" + fi + else + test_result_record "FAIL" "Cooling providers are declared but no runtime cooling devices are exposed" + fi + else + test_result_record "SKIP" "Runtime device tree does not declare a cooling-device provider" + fi +} + +############################################################################### +# dt_capture_power_runtime +# Retains optional regulator and generic power-domain debugfs summaries. Their +# absence is not a failure because production images may disable debugfs. +############################################################################### +dt_capture_power_runtime() { + dtcpr_result_dir="$1" + dtcpr_regulator="/sys/kernel/debug/regulator/regulator_summary" + dtcpr_genpd="/sys/kernel/debug/pm_genpd/pm_genpd_summary" + + [ -n "$dtcpr_result_dir" ] || return 3 + + log_info "Power evidence validation: checking optional regulator and generic power-domain debugfs summaries" + + if [ -r "$dtcpr_regulator" ]; then + if cp "$dtcpr_regulator" "$dtcpr_result_dir/regulator_summary.log"; then + dtcpr_regulator_lines=$(wc -l <"$dtcpr_result_dir/regulator_summary.log" | tr -d '[:space:]') + log_info "[POWER] regulator_summary source=$dtcpr_regulator lines=${dtcpr_regulator_lines:-0} artifact=$dtcpr_result_dir/regulator_summary.log" + test_result_record "PASS" "Regulator runtime summary was captured" + else + test_result_record "FAIL" "Regulator runtime summary is readable but could not be captured" + fi + else + test_result_record "SKIP" "Regulator debugfs summary is not exposed" + fi + + if [ -r "$dtcpr_genpd" ]; then + if cp "$dtcpr_genpd" "$dtcpr_result_dir/power_domain_summary.log"; then + dtcpr_genpd_lines=$(wc -l <"$dtcpr_result_dir/power_domain_summary.log" | tr -d '[:space:]') + log_info "[POWER] power_domain_summary source=$dtcpr_genpd lines=${dtcpr_genpd_lines:-0} artifact=$dtcpr_result_dir/power_domain_summary.log" + test_result_record "PASS" "Generic power-domain runtime summary was captured" + else + test_result_record "FAIL" "Generic power-domain summary is readable but could not be captured" + fi + else + test_result_record "SKIP" "Generic power-domain debugfs summary is not exposed" + fi +} + ############################################################################### # dt_validate_tee_runtime # Distinguishes declared OP-TEE from a generic TEE device, which is commonly @@ -4385,7 +5357,7 @@ dt_validate_runtime_hardware_capabilities() { if dt_hw_capability_area_enabled "health"; then scan_dmesg_errors \ "$dtrhc_result_dir" \ - 'of|device.tree|devicetree|qcom.*(smmu|pcie|ufs|sdhci|dwc3|usb|ethqos|dpu|mdss)' \ + 'of|device.tree|devicetree|qcom.*(smmu|pcie|ufs|sdhci|dwc3|usb|ethqos|dpu|mdss)|pmic.gl|ucsi|thermal|tsens|rpmh|regulator|power.domain' \ '-517|EPROBE_DEFER|deferred probe|dummy regulator|supply [^ ]+ not found' || true log_info "Captured DT and controller kernel-health snapshot" fi @@ -4542,11 +5514,13 @@ dt_validate_runtime_hardware_capabilities() { dt_validate_property_provider "$dtrhc_root" "Clock" "#clock-cells" "$dtrhc_result_dir" dt_validate_property_provider "$dtrhc_root" "Reset" "#reset-cells" "$dtrhc_result_dir" dt_validate_property_provider "$dtrhc_root" "Regulator" "regulator-name" "$dtrhc_result_dir" + dt_validate_property_provider "$dtrhc_root" "Power domain" "#power-domain-cells" "$dtrhc_result_dir" dt_validate_property_provider "$dtrhc_root" "Mailbox" "#mbox-cells" "$dtrhc_result_dir" dt_validate_property_provider "$dtrhc_root" "Interconnect" "#interconnect-cells" "$dtrhc_result_dir" dt_validate_node_inventory "RPMh" 'qcom,.*rpmh' "$dtrhc_result_dir" dt_validate_platform_capability "LLCC" 'qcom,.*llcc' "$dtrhc_result_dir" dt_validate_platform_capability "SMMU" 'qcom,.*smmu-500' "$dtrhc_result_dir" + dt_capture_power_runtime "$dtrhc_result_dir" dt_summary_end_area fi @@ -4554,6 +5528,7 @@ dt_validate_runtime_hardware_capabilities() { dt_summary_begin_area "$dtrhc_result_dir" "USB" dt_validate_platform_capability "USB" 'qcom,.*(dwc3|usb)' "$dtrhc_result_dir" dt_validate_usb_host_runtime + dt_validate_pmic_glink_ucsi "$dtrhc_result_dir" dt_validate_runtime_path "USB gadget controller" '/sys/class/udc/*' dt_summary_end_area fi @@ -4614,6 +5589,7 @@ dt_validate_runtime_hardware_capabilities() { if dt_hw_capability_area_enabled "health"; then dt_summary_begin_area "$dtrhc_result_dir" "Kernel health" + dt_validate_thermal_runtime "$dtrhc_root" "$dtrhc_result_dir" if [ -s "$dtrhc_result_dir/dmesg_errors.log" ]; then test_result_record "FAIL" "Relevant DT or hardware-controller errors were found in the captured kernel log" else @@ -4655,15 +5631,29 @@ list_remoteproc_instances() { ############################################################################### # find_image_firmware -# Prints the first matching image-provided firmware path under the standard -# firmware roots, accepting uncompressed, .xz, and .zst files. Returns 0 on a -# match, 1 when no asset is exposed, and 3 when no name is supplied. +# Prints the first matching image-provided firmware path under the standard or +# running-kernel firmware roots, accepting uncompressed, .xz, and .zst files. +# Returns 0 on a match, 1 when no asset is exposed, and 3 when no name is +# supplied. ############################################################################### find_image_firmware() { firmware_name="$1" + firmware_release=$(uname -r 2>/dev/null || true) [ -n "$firmware_name" ] || return 3 - for firmware_root in /lib/firmware /usr/lib/firmware; do + for firmware_root in \ + "/lib/firmware/$firmware_release" \ + "/usr/lib/firmware/$firmware_release" \ + /lib/firmware \ + /usr/lib/firmware; do + [ -n "$firmware_release" ] || { + case "$firmware_root" in + /lib/firmware/|/usr/lib/firmware/) + continue + ;; + esac + } + [ -d "$firmware_root" ] || continue for firmware_path in \ "$firmware_root/$firmware_name" \ "$firmware_root/$firmware_name.xz" \ @@ -5907,6 +6897,88 @@ minkipc_prepare_test_packages() { return 0 } +# qrtr_runtime_present +# Reports whether the running kernel exposes QRTR transport evidence. Installed +# tools or modules that are not loaded are not treated as hardware evidence. +qrtr_runtime_present() { + if [ -d /sys/bus/qrtr ] || [ -r /proc/net/qrtr ] || [ -d /sys/module/qrtr ]; then + return 0 + fi + + if is_module_loaded qrtr; then + return 0 + fi + + return 1 +} + +# qrtr_capture_topology [timeout-seconds] +# Runs one bounded, read-only qrtr-lookup inventory and validates its tabular +# header. Returns 0 for a valid snapshot, 1 for a broken query, 2 when QRTR or +# qrtr-lookup is unavailable, and 3 for invalid arguments. +qrtr_capture_topology() { + qct_output_file="$1" + qct_timeout="${2:-${QRTR_LOOKUP_TIMEOUT:-10}}" + qct_lookup_bin="${QRTR_LOOKUP_BIN:-qrtr-lookup}" + + [ -n "$qct_output_file" ] || return 3 + case "$qct_timeout" in + ''|*[!0-9]*|0) + return 3 + ;; + esac + + qrtr_runtime_present || return 2 + command -v "$qct_lookup_bin" >/dev/null 2>&1 || return 2 + + qct_output_dir=$(dirname "$qct_output_file") + mkdir -p "$qct_output_dir" || return 1 + rm -f "$qct_output_file" + + if ! run_with_timeout_log \ + "$qct_timeout" \ + "$qct_output_file" \ + "$qct_lookup_bin"; then + return 1 + fi + + if ! awk ' + NR == 1 && $1 == "Service" && $2 == "Version" && + $3 == "Instance" && $4 == "Node" && $5 == "Port" { + valid=1 + } + END { exit !valid } + ' "$qct_output_file"; then + return 1 + fi + + return 0 +} + +# qrtr_topology_has_service [version] [instance] +# Matches a qrtr-lookup row. Empty version or instance arguments act as +# wildcards, allowing each consumer to enforce only its documented contract. +qrtr_topology_has_service() { + qths_file="$1" + qths_service="$2" + qths_version="${3:-}" + qths_instance="${4:-}" + + [ -r "$qths_file" ] && [ -n "$qths_service" ] || return 3 + + awk \ + -v service="$qths_service" \ + -v version="$qths_version" \ + -v instance="$qths_instance" ' + NR > 1 && $1 == service && + (version == "" || $2 == version) && + (instance == "" || $3 == instance) { + found=1 + } + END { exit !found } + ' "$qths_file" +} + ############################################################################### # Qualcomm RMTFS runtime helpers ############################################################################### From 609f02ee929dca273c1fcf260a014a9802aa5d6b Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Fri, 4 Sep 2026 22:51:17 +0530 Subject: [PATCH 2/2] pcie: validate QPS615 runtime topology Extend generic PCIe health collection and Ethernet inventory with dynamically discovered QPS615 bridge, firmware, endpoint, driver, and netdev evidence. Preserve the generic paths when the switch is absent and report unbound endpoints with actionable diagnostics. Signed-off-by: Srikanth Muppandam --- .../Ethernet_Inventory_Validation.yaml | 2 +- .../Ethernet_Inventory_Validation/README.md | 50 ++- .../Ethernet_Inventory_Validation/run.sh | 92 ++++- Runner/suites/Kernel/Baseport/PCIe/PCIe.yaml | 6 +- Runner/suites/Kernel/Baseport/PCIe/README.md | 116 +++++-- Runner/suites/Kernel/Baseport/PCIe/run.sh | 198 +++++++++-- Runner/utils/lib_ethernet.sh | 323 ++++++++++++++++++ 7 files changed, 700 insertions(+), 87 deletions(-) diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/Ethernet_Inventory_Validation.yaml b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/Ethernet_Inventory_Validation.yaml index 31718105..94a8844a 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/Ethernet_Inventory_Validation.yaml +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/Ethernet_Inventory_Validation.yaml @@ -1,7 +1,7 @@ metadata: name: ethernet-inventory-validation format: "Lava-Test Test Definition 1.0" - description: "Inventory physical Ethernet interfaces, MAC drivers, PHY identity, device-tree identity, queue topology, and platform bindings without changing network configuration." + description: "Inventory Ethernet interfaces, drivers, PHY and queue topology, including runtime-gated QPS615 readiness, without changing network configuration." os: - linux scope: diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/README.md b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/README.md index 7b628d0f..a7d1d722 100644 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/README.md +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/README.md @@ -21,6 +21,38 @@ For each interface, the testcase reports and validates: The testcase does not bring interfaces up, run DHCP, change link settings, or transmit traffic. +When the runtime PCI topology contains a QPS615 switch (`1179:0623`), the +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. + +For each expected Toshiba `1179:0220` Ethernet function, the live log and TSV +include its PCI modalias, bound driver, exported netdevs, and availability of +the running-kernel `tc956x_pcie_eth` module. They also report whether that +module registered its `tc956x_pci-eth` PCI driver and expose the current +`tc956x_eth_ports_bdf` value, PCI driver-autoprobe state, per-function driver +override, runtime power state, and runtime OF node as diagnostic evidence. The +driver's all-zero BDF array selects its built-in defaults and is not itself a +failure. An enumerated Ethernet function that remains unbound is a real +Ethernet failure only when its runtime OF node declares a platform Ethernet +port through the current `phy-reset-gpios` property or the legacy +`qcom,phy-rst-gpio` property. Bare embedded PCI functions can be enumerated for +IOMMU/topology description even when no MAC/PHY port is provisioned, and are +reported as SKIP. The PCIe suite continues to evaluate switch topology and +firmware independently. + +This classification follows the Qualcomm Linux PCIe and Ethernet guides: the +QPS615 MAC driver and kernel configuration are enabled by default, and the +supported Ethernet interfaces are expected to activate during startup once the +QPS615 driver is loaded and the PCIe link is established. Link carrier and the +optional AQR PHY are not required by this inventory-only testcase. + +References: + +- [Qualcomm Linux PCIe guide](https://docs.qualcomm.com/doc/80-70023-8/topic/pcie.html#pcie-software-support-feature-for-qps615) +- [Qualcomm Linux Ethernet bring-up guide](https://docs.qualcomm.com/bundle/publicresource/80-70023-26/topics/bring_up-ethernet.md) + ## Prerequisites and infrastructure - No cable, peer, DHCP service, or Internet connection is required. @@ -52,9 +84,21 @@ Example: ## Result -```text -Ethernet_Inventory_Validation.res -Ethernet_Inventory_Validation.results.tsv +Check the final result and per-check summary: + +```sh +cat Ethernet_Inventory_Validation.res +cat Ethernet_Inventory_Validation.results.tsv ``` The testcase passes when at least one physical Ethernet interface is enumerated with a bound driver and valid RX/TX queue entries. Optional OF and PHY properties are reported as SKIP when the corresponding bus or driver does not expose them. + +On QPS615 systems, inspect the correlated PCIe, driver, and netdev evidence: + +```sh +cat qps615_runtime/qps615_switches.log +cat qps615_runtime/qps615_runtime.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. diff --git a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/run.sh b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/run.sh index 9467d055..49743c40 100755 --- a/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/run.sh +++ b/Runner/suites/Connectivity/Ethernet/Ethernet_Inventory_Validation/run.sh @@ -109,6 +109,7 @@ if ! CHECK_DEPS_NO_EXIT=1 check_dependencies \ grep \ cat \ find \ + head \ readlink \ uname; then @@ -123,6 +124,58 @@ fi log_info "Platform Details: machine='${PLATFORM_MACHINE:-unknown}' target='${PLATFORM_TARGET:-unknown}' kernel='$(uname -r 2>/dev/null || echo unknown)' arch='$(uname -m 2>/dev/null || echo unknown)'" +QPS615_RESULT_DIR="./qps615_runtime" +ethv_qps615_collect_runtime "$QPS615_RESULT_DIR" +qps615_status=$? + +case "$qps615_status" in + 0|1) + if [ -n "$QPS615_TOPOLOGY_FAILURE_REASON" ]; then + ethv_record_result \ + "$RESULT_TABLE" \ + "QPS615 PCIe topology and firmware" \ + "FAIL" \ + "$QPS615_TOPOLOGY_FAILURE_REASON" + else + ethv_record_result \ + "$RESULT_TABLE" \ + "QPS615 PCIe topology and firmware" \ + "PASS" \ + "bridge_functions=$QPS615_SWITCH_COUNT downstream=$QPS615_DOWNSTREAM_COUNT firmware=$QPS615_FIRMWARE_PATH" + fi + + if [ -n "$QPS615_ETHERNET_FAILURE_REASON" ]; then + ethv_record_result \ + "$RESULT_TABLE" \ + "QPS615 Ethernet endpoint readiness" \ + "FAIL" \ + "$QPS615_ETHERNET_FAILURE_REASON" + elif [ -n "$QPS615_ETHERNET_SKIP_REASON" ]; then + ethv_record_result \ + "$RESULT_TABLE" \ + "QPS615 Ethernet endpoint readiness" \ + "SKIP" \ + "$QPS615_ETHERNET_SKIP_REASON" + else + ethv_record_result \ + "$RESULT_TABLE" \ + "QPS615 Ethernet endpoint readiness" \ + "PASS" \ + "devices=$QPS615_ETHERNET_DEVICE_COUNT declared_ports=$QPS615_ETHERNET_DECLARED_COUNT bound_declared_ports=$QPS615_DECLARED_DRIVER_DEVICE_COUNT declared_port_netdevs=$QPS615_DECLARED_NETDEV_COUNT module_state=$QPS615_MODULE_STATE pci_driver_state=$QPS615_PCI_DRIVER_STATE module_path=${QPS615_MODULE_PATH:-built-in-or-unexposed}" + fi + ;; + 2) + log_info "QPS615 PCIe switch is not present, preserving generic Ethernet inventory validation" + ;; + *) + ethv_record_result \ + "$RESULT_TABLE" \ + "QPS615 runtime collection" \ + "FAIL" \ + "runtime topology collection could not complete" + ;; +esac + if [ -n "$ETH_INTERFACE" ]; then ETH_IFACES="$ETH_INTERFACE" else @@ -130,19 +183,34 @@ else fi if [ -z "$ETH_IFACES" ]; then - ethv_record_result \ - "$RESULT_TABLE" \ - "Physical Ethernet inventory" \ - "SKIP" \ - "no physical Ethernet interfaces are currently enumerated" - - ethv_print_summary "$RESULT_TABLE" "Ethernet Inventory Summary" - echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 + case "$qps615_status" in + 0) + if [ -n "$QPS615_ETHERNET_SKIP_REASON" ]; then + ethv_record_result \ + "$RESULT_TABLE" \ + "Physical Ethernet inventory" \ + "SKIP" \ + "no runtime-provisioned Ethernet interface is present" + else + ethv_record_result \ + "$RESULT_TABLE" \ + "Physical Ethernet inventory" \ + "FAIL" \ + "QPS615 reported $QPS615_NETDEV_COUNT netdev(s), but physical Ethernet discovery returned none" + fi + ;; + 2) + ethv_record_result \ + "$RESULT_TABLE" \ + "Physical Ethernet inventory" \ + "SKIP" \ + "no physical Ethernet interfaces are currently enumerated" + ;; + esac +else + log_info "Detected physical Ethernet interfaces: $(printf '%s' "$ETH_IFACES" | tr '\n' ' ')" fi -log_info "Detected physical Ethernet interfaces: $(printf '%s' "$ETH_IFACES" | tr '\n' ' ')" - for iface in $ETH_IFACES; do driver="$(ethv_get_driver "$iface")" module="$(ethv_get_driver_module "$iface")" @@ -242,7 +310,7 @@ log_info "---- Ethernet-related kernel messages ----" if command -v get_kernel_log >/dev/null 2>&1; then get_kernel_log 2>/dev/null \ | grep -iE \ - 'ethqos|stmmac|dwmac|gmac|emac|phylink|mdio|sgmii|hsgmii|2500base|rgmii|qca808|marvell|aquantia|aqr|dp83867|ptp' \ + 'ethqos|stmmac|dwmac|gmac|emac|tc956|qps615|phylink|mdio|sgmii|hsgmii|2500base|rgmii|qca808|marvell|aquantia|aqr|dp83867|ptp' \ | tail -n 200 \ | while IFS= read -r line; do [ -n "$line" ] && log_info "[eth-kernel] $line" diff --git a/Runner/suites/Kernel/Baseport/PCIe/PCIe.yaml b/Runner/suites/Kernel/Baseport/PCIe/PCIe.yaml index e9ec8674..80e54073 100644 --- a/Runner/suites/Kernel/Baseport/PCIe/PCIe.yaml +++ b/Runner/suites/Kernel/Baseport/PCIe/PCIe.yaml @@ -1,16 +1,18 @@ metadata: name: pcio format: "Lava-Test Test Definition 1.0" - description: "Test basic PCIe features" + description: "Test basic PCIe features and capability-gated QPS615 topology and firmware readiness, with downstream Ethernet diagnostics" os: - linux scope: - functional +params: + PCIE_DMESG_STRICT: "0" + run: steps: - REPO_PATH=$PWD - cd Runner/suites/Kernel/Baseport/PCIe - ./run.sh || true - $REPO_PATH/Runner/utils/send-to-lava.sh PCIe.res || true - diff --git a/Runner/suites/Kernel/Baseport/PCIe/README.md b/Runner/suites/Kernel/Baseport/PCIe/README.md index 437316d9..aec80452 100644 --- a/Runner/suites/Kernel/Baseport/PCIe/README.md +++ b/Runner/suites/Kernel/Baseport/PCIe/README.md @@ -1,47 +1,99 @@ # PCIe Validation Test -© Qualcomm Technologies, Inc. and/or its subsidiaries. -SPDX-License-Identifier: BSD-3-Clause## Overview +Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +SPDX-License-Identifier: BSD-3-Clause + +## Overview + This test case validates the PCIe interface on the target device by checking for the presence of key PCIe attributes using the `lspci -vvv` command. It ensures that the PCIe subsystem is correctly enumerated and functional + ### The test checks for: + - Presence of **Device Tree Node** - Availability of **PCIe Capabilities** - Binding of a **Kernel Driver** +- Per-device negotiated and maximum link speed and width +- MSI/MSI-X runtime IRQ exposure +- Runtime power state +- PCIe controller, link-training, and uncorrected AER kernel errors + +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 + +When an enumerated Toshiba `1179:0623` switch identifies QPS615 hardware, the +suite also validates the discovered downstream PCIe topology, TC956x Ethernet +function enumeration, and the image-provided +`TC956X_Firmware_PCIeBridge.bin`. Driver and netdev readiness are logged for +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. + +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 +binding, and exported netdevs establish Ethernet readiness. This keeps a +downstream MAC/PHY problem visible without misclassifying it as a failed PCIe +switch topology. + +References: + +- [Qualcomm Linux PCIe guide](https://docs.qualcomm.com/doc/80-70023-8/topic/pcie.html#pcie-software-support-feature-for-qps615) +- [Qualcomm Linux Ethernet bring-up guide](https://docs.qualcomm.com/bundle/publicresource/80-70023-26/topics/bring_up-ethernet.md) + ## Usage -### Instructions: -1. **Copy the test suite to the target device** using `scp` or any preferred method. -2. **Navigate to the test directory** on the target device. -3. **Run the test script** using the test runner or directly. ---- -### Quick Example -```bash -git clone -cd -scp -r common Runner user@target_device_ip: -ssh user@target_device_ip -cd /Runner && ./run-test.sh PCIe + +Run the suite directly on the target: + +```sh +cd Runner/suites/Kernel/Baseport/PCIe +./run.sh +``` + +It can also be launched through the repository runner: + +```sh +cd Runner +./run-test.sh PCIe ``` ---- -### Prerequisites + +## Prerequisites + 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 ---- + ## Result Format -Test result will be saved in `PCIe.res` as: -## Output -A .res file is generated in the same directory: -`PCIe PASS` OR `PCIe FAIL` -## Sample Log + +Check the final result: + +```sh +cat PCIe.res +``` + +The file contains `PCIe PASS`, `PCIe FAIL`, or `PCIe SKIP`. + +On QPS615 systems, inspect the retained topology evidence: + +```sh +cat qps615_runtime/qps615_switches.log +cat qps615_runtime/qps615_runtime.tsv +find /lib/firmware /usr/lib/firmware \ + -name 'TC956X_Firmware_PCIeBridge.bin*' -print 2>/dev/null +``` + +Generic PCIe runtime and kernel-health evidence is retained in: + +```sh +cat pcie_runtime/pcie_runtime.tsv +cat pcie_runtime/dmesg_errors.log +``` + +Kernel-log findings are advisory by default because the boot log can contain +history from unused ports or previously detached endpoints. To make matching +PCIe controller, link, or uncorrected AER errors fail the suite: + +```sh +PCIE_DMESG_STRICT=1 ./run.sh ``` -[INFO] 1980-01-06 00:43:54 - ----------------------------------------------------------------------------------------- -[INFO] 1980-01-06 00:43:54 - -------------------Starting PCIe Testcase---------------------------- -[INFO] 1980-01-06 00:43:54 - === Test Initialization === -[INFO] 1980-01-06 00:43:54 - Checking if required tools are available -[INFO] 1980-01-06 00:43:54 - Running PCIe -[INFO] 1980-01-06 00:43:54 - DT node is present -[INFO] 1980-01-06 00:43:54 - Yes, 'Capabilities:' is found -[INFO] 1980-01-06 00:43:54 - Driver is loaded -[PASS] 1980-01-06 00:43:54 - PCIe : Test Passed -``` \ No newline at end of file diff --git a/Runner/suites/Kernel/Baseport/PCIe/run.sh b/Runner/suites/Kernel/Baseport/PCIe/run.sh index fb2a75e9..23dbf6c8 100755 --- a/Runner/suites/Kernel/Baseport/PCIe/run.sh +++ b/Runner/suites/Kernel/Baseport/PCIe/run.sh @@ -1,11 +1,15 @@ #!/bin/sh - # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause -# Robustly find and source init_env -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# ---------- 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" @@ -19,58 +23,178 @@ if [ -z "$INIT_ENV" ]; then exit 1 fi -# Only source if not already loaded (idempotent) -if [ -z "$__INIT_ENV_LOADED" ]; then +# 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 -# Always source functestlib.sh, using $TOOLS exported by init_env -# shellcheck disable=SC1090,SC1091 + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 . "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_ethernet.sh" TESTNAME="PCIe" -test_path=$(find_test_case_by_name "$TESTNAME") -cd "$test_path" || exit 1 -res_file="./$TESTNAME.res" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" +QPS615_RESULT_DIR="$SCRIPT_DIR/qps615_runtime" +PCIE_RESULT_DIR="$SCRIPT_DIR/pcie_runtime" +PCIE_DMESG_STRICT="${PCIE_DMESG_STRICT:-0}" + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 -log_info "-----------------------------------------------------------------------------------------" -log_info "-------------------Starting $TESTNAME Testcase----------------------------" -log_info "=== Test Initialization ===" -log_info "Checking if required tools are available" +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME Testcase" -check_dependencies "lspci" -log_info "Running PCIe " -lspci_output=$(lspci -vvv) +case "$PCIE_DMESG_STRICT" in + 0|1) + ;; + *) + log_warn "Invalid PCIE_DMESG_STRICT='$PCIE_DMESG_STRICT', using 0" + PCIE_DMESG_STRICT=0 + ;; +esac -missing="" +log_info "Configuration: PCIE_DMESG_STRICT=$PCIE_DMESG_STRICT" -if echo "$lspci_output" | grep -q "Device tree node:"; then - log_info "DT node is present" +if ! CHECK_DEPS_RECOVER=0 CHECK_DEPS_NO_EXIT=1 check_dependencies \ + awk \ + basename \ + cat \ + find \ + grep \ + head \ + lspci \ + mkdir \ + readlink \ + rm \ + tr \ + uname; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi + +if ! lspci_output="$(lspci -vvv 2>&1)"; then + test_result_finish "FAIL" "$TESTNAME FAIL: lspci inventory command failed" +fi + +pcie_dt_association=0 +for pcie_of_node in /sys/bus/pci/devices/*/of_node; do + [ -e "$pcie_of_node" ] || continue + pcie_dt_association=1 + break +done + +if printf '%s\n' "$lspci_output" | grep -q "Device tree node:" || + [ "$pcie_dt_association" -eq 1 ]; then + test_result_record "PASS" "PCIe inventory exposes device-tree node information" else - log_warn "DT node is not present" - missing=1 + test_result_record "SKIP" "PCIe device-tree association is not exposed by pciutils or sysfs" fi -if echo "$lspci_output" | grep -q "Capabilities:"; then - log_info "Yes, 'Capabilities:' is found" +if printf '%s\n' "$lspci_output" | grep -q "Capabilities:"; then + test_result_record "PASS" "PCIe capabilities are exposed" else - log_warn "No, 'Capabilities:' is missing" - missing=1 + test_result_record "FAIL" "PCIe capabilities are not exposed" fi -if echo "$lspci_output" | grep -q "Kernel driver in use:"; then - log_info "Driver is loaded" +if printf '%s\n' "$lspci_output" | grep -q "Kernel driver in use:"; then + test_result_record "PASS" "At least one PCIe device has a bound kernel driver" else - log_warn "Driver is not loaded" - missing=1 + test_result_record "FAIL" "No bound PCIe kernel driver was reported" fi -if [ -z "$missing" ]; then - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$res_file" - exit 0 +pcie_collect_runtime_health "$PCIE_RESULT_DIR" +pcie_runtime_status=$? + +case "$pcie_runtime_status" in + 0) + test_result_record \ + "PASS" \ + "PCIe runtime inventory is readable: devices=$PCIE_RUNTIME_DEVICE_COUNT link_records=$PCIE_RUNTIME_LINK_COUNT power_records=$PCIE_RUNTIME_POWER_COUNT" + if [ "$PCIE_RUNTIME_INACTIVE_PORT_COUNT" -gt 0 ]; then + test_result_record \ + "SKIP" \ + "$PCIE_RUNTIME_INACTIVE_PORT_COUNT unused PCIe bridge port(s) currently have no negotiated downstream link" + fi + if [ "$PCIE_RUNTIME_MSI_DEVICE_COUNT" -gt 0 ]; then + test_result_record \ + "PASS" \ + "PCIe MSI/MSI-X runtime evidence is present for $PCIE_RUNTIME_MSI_DEVICE_COUNT device(s)" + else + test_result_record "SKIP" "No PCIe MSI/MSI-X IRQ directories are exposed" + fi + ;; + 1) + test_result_record \ + "FAIL" \ + "PCIe runtime health validation failed: ${PCIE_RUNTIME_FAILURE_REASON:-malformed sysfs evidence}" + ;; + 2) + test_result_record "FAIL" "lspci reports PCI devices but PCI sysfs inventory is empty" + ;; + *) + test_result_record "FAIL" "PCIe runtime health inventory could not complete" + ;; +esac + +ethv_qps615_collect_runtime "$QPS615_RESULT_DIR" +qps615_status=$? + +case "$qps615_status" in + 0|1) + if [ -n "$QPS615_TOPOLOGY_FAILURE_REASON" ]; then + test_result_record \ + "FAIL" \ + "QPS615 PCIe topology or firmware validation failed: $QPS615_TOPOLOGY_FAILURE_REASON" + 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" + fi + + if [ -n "$QPS615_ETHERNET_FAILURE_REASON" ]; then + test_result_record \ + "SKIP" \ + "QPS615 Ethernet readiness is reported by Ethernet_Inventory_Validation: $QPS615_ETHERNET_FAILURE_REASON" + elif [ -n "$QPS615_ETHERNET_SKIP_REASON" ]; then + test_result_record \ + "SKIP" \ + "QPS615 Ethernet is not runtime-provisioned: $QPS615_ETHERNET_SKIP_REASON" + else + log_info "QPS615 Ethernet evidence: devices=$QPS615_ETHERNET_DEVICE_COUNT declared_ports=$QPS615_ETHERNET_DECLARED_COUNT bound_declared_ports=$QPS615_DECLARED_DRIVER_DEVICE_COUNT declared_port_netdevs=$QPS615_DECLARED_NETDEV_COUNT module_state=$QPS615_MODULE_STATE module_path=${QPS615_MODULE_PATH:-built-in-or-unexposed}" + fi + ;; + 2) + log_info "QPS615 PCIe switch is not present, preserving generic PCIe validation" + ;; + 3) + test_result_record \ + "FAIL" \ + "QPS615 topology validation could not complete because its arguments or runtime paths were invalid" + ;; + *) + test_result_record "FAIL" "QPS615 topology validation could not complete" + ;; +esac + +log_info "PCIe kernel-health validation: capturing link-training, controller, and AER errors" +scan_dmesg_errors \ + "$PCIE_RESULT_DIR" \ + 'pcieport.*|qcom-pcie.*|pci.*' \ + 'AER: Corrected|corrected error|deferred probe|EPROBE_DEFER|using dummy regulator|supply [^ ]+ not found' +pcie_dmesg_status=$? + +if [ ! -s "$PCIE_RESULT_DIR/dmesg_snapshot.log" ]; then + test_result_record "SKIP" "Kernel log access is unavailable for PCIe health validation" +elif [ "$pcie_dmesg_status" -eq 0 ] && [ "$PCIE_DMESG_STRICT" -eq 1 ]; then + test_result_record "FAIL" "PCIe link, controller, or uncorrected AER errors were found in $PCIE_RESULT_DIR/dmesg_errors.log" +elif [ "$pcie_dmesg_status" -eq 0 ]; then + test_result_record "SKIP" "PCIe kernel errors were retained as advisory evidence, set PCIE_DMESG_STRICT=1 to gate them" else - log_fail "$TESTNAME : Test Failed" - echo "$TESTNAME FAIL" > "$res_file" - exit 1 + test_result_record "PASS" "No non-benign PCIe controller or AER errors were found in the captured kernel log" fi + +test_result_finish diff --git a/Runner/utils/lib_ethernet.sh b/Runner/utils/lib_ethernet.sh index 7d16f851..b46c5f35 100755 --- a/Runner/utils/lib_ethernet.sh +++ b/Runner/utils/lib_ethernet.sh @@ -258,6 +258,329 @@ ethv_get_physical_interfaces() { done } +# 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. +# 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() { + ethv_qcr_result_dir="${1:-}" + ethv_qcr_pci_root="${2:-/sys/bus/pci/devices}" + ethv_qcr_inventory="$ethv_qcr_result_dir/qps615_runtime.tsv" + ethv_qcr_switches="$ethv_qcr_result_dir/qps615_switches.log" + + [ -n "$ethv_qcr_result_dir" ] || return 3 + mkdir -p "$ethv_qcr_result_dir" || return 1 + : >"$ethv_qcr_inventory" || return 1 + : >"$ethv_qcr_switches" || return 1 + + QPS615_SWITCH_COUNT=0 + QPS615_DOWNSTREAM_COUNT=0 + QPS615_ETHERNET_DEVICE_COUNT=0 + QPS615_ETHERNET_DECLARED_COUNT=0 + QPS615_DRIVER_DEVICE_COUNT=0 + QPS615_DECLARED_DRIVER_DEVICE_COUNT=0 + QPS615_UNBOUND_ETHERNET_COUNT=0 + QPS615_NETDEV_COUNT=0 + QPS615_DECLARED_NETDEV_COUNT=0 + QPS615_ETHERNET_OF_NODE_COUNT=0 + QPS615_ETHERNET_OVERRIDE_COUNT=0 + QPS615_FIRMWARE_PATH="" + QPS615_MODULE_PATH="" + QPS615_MODULE_STATE="absent" + QPS615_MODULE_PORT_BDFS="" + QPS615_MODULE_PORT_CONFIG="unavailable" + QPS615_PCI_DRIVER_PATH="" + QPS615_PCI_DRIVER_STATE="absent" + QPS615_PCI_AUTOPROBE="unknown" + QPS615_TOPOLOGY_FAILURE_REASON="" + QPS615_ETHERNET_FAILURE_REASON="" + QPS615_ETHERNET_SKIP_REASON="" + QPS615_FAILURE_REASON="" + + 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" + 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 + export QPS615_DECLARED_NETDEV_COUNT + export QPS615_ETHERNET_OF_NODE_COUNT QPS615_ETHERNET_OVERRIDE_COUNT + export QPS615_FIRMWARE_PATH QPS615_MODULE_PATH QPS615_MODULE_STATE + export QPS615_MODULE_PORT_BDFS QPS615_MODULE_PORT_CONFIG + export QPS615_PCI_DRIVER_PATH QPS615_PCI_DRIVER_STATE + export QPS615_PCI_AUTOPROBE + export QPS615_TOPOLOGY_FAILURE_REASON QPS615_ETHERNET_FAILURE_REASON + export QPS615_ETHERNET_SKIP_REASON QPS615_FAILURE_REASON + return 2 + fi + + for ethv_qcr_device in "$ethv_qcr_pci_root"/*; do + [ -d "$ethv_qcr_device" ] || continue + ethv_qcr_vendor="$(ethv_read_first_line "$ethv_qcr_device/vendor" 2>/dev/null || true)" + ethv_qcr_id="$(ethv_read_first_line "$ethv_qcr_device/device" 2>/dev/null || true)" + + 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" + fi + printf 'switch\t%s\t%s\t%s\t%s\n' \ + "${ethv_qcr_device##*/}" \ + "$ethv_qcr_vendor" \ + "$ethv_qcr_id" \ + "${ethv_qcr_resolved:-unresolved}" >>"$ethv_qcr_inventory" + log_info "[QPS615] switch=${ethv_qcr_device##*/} vendor=$ethv_qcr_vendor device=$ethv_qcr_id path=${ethv_qcr_resolved:-unresolved}" + fi + done + + if [ "$QPS615_SWITCH_COUNT" -eq 0 ]; then + log_info "QPS615 validation is not applicable because switch 1179:0623 was not enumerated" + 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 + export QPS615_DECLARED_NETDEV_COUNT + export QPS615_ETHERNET_OF_NODE_COUNT QPS615_ETHERNET_OVERRIDE_COUNT + export QPS615_FIRMWARE_PATH QPS615_MODULE_PATH QPS615_MODULE_STATE + export QPS615_MODULE_PORT_BDFS QPS615_MODULE_PORT_CONFIG + export QPS615_PCI_DRIVER_PATH QPS615_PCI_DRIVER_STATE + export QPS615_PCI_AUTOPROBE + export QPS615_TOPOLOGY_FAILURE_REASON QPS615_ETHERNET_FAILURE_REASON + export QPS615_ETHERNET_SKIP_REASON QPS615_FAILURE_REASON + return 2 + fi + + for ethv_qcr_device in "$ethv_qcr_pci_root"/*; do + [ -d "$ethv_qcr_device" ] || continue + ethv_qcr_resolved="$(readlink -f "$ethv_qcr_device" 2>/dev/null || true)" + ethv_qcr_is_downstream=0 + + while IFS= read -r ethv_qcr_switch_path; do + [ -n "$ethv_qcr_switch_path" ] || continue + case "$ethv_qcr_resolved" in + "$ethv_qcr_switch_path"/*) + ethv_qcr_is_downstream=1 + break + ;; + esac + done <"$ethv_qcr_switches" + + [ "$ethv_qcr_is_downstream" -eq 1 ] || continue + QPS615_DOWNSTREAM_COUNT=$((QPS615_DOWNSTREAM_COUNT + 1)) + + ethv_qcr_driver="" + if [ -L "$ethv_qcr_device/driver" ]; then + ethv_qcr_driver="$(basename "$(readlink -f "$ethv_qcr_device/driver" 2>/dev/null)" 2>/dev/null || true)" + fi + ethv_qcr_vendor="$(ethv_read_first_line "$ethv_qcr_device/vendor" 2>/dev/null || true)" + ethv_qcr_id="$(ethv_read_first_line "$ethv_qcr_device/device" 2>/dev/null || true)" + printf 'downstream\t%s\t%s\t%s\t%s\t%s\n' \ + "${ethv_qcr_device##*/}" \ + "${ethv_qcr_vendor:-unknown}" \ + "${ethv_qcr_id:-unknown}" \ + "${ethv_qcr_driver:-unbound}" \ + "${ethv_qcr_resolved:-unknown}" >>"$ethv_qcr_inventory" + log_info "[QPS615] downstream=${ethv_qcr_device##*/} vendor=${ethv_qcr_vendor:-unknown} device=${ethv_qcr_id:-unknown} driver=${ethv_qcr_driver:-unbound}" + + if [ "$ethv_qcr_vendor" = "0x1179" ] && \ + [ "$ethv_qcr_id" = "0x0220" ]; then + QPS615_ETHERNET_DEVICE_COUNT=$((QPS615_ETHERNET_DEVICE_COUNT + 1)) + ethv_qcr_modalias="$(ethv_read_first_line "$ethv_qcr_device/modalias" 2>/dev/null || true)" + ethv_qcr_driver_override="$(ethv_read_first_line "$ethv_qcr_device/driver_override" 2>/dev/null || true)" + ethv_qcr_of_node="$(readlink -f "$ethv_qcr_device/of_node" 2>/dev/null || true)" + ethv_qcr_runtime_status="$(ethv_read_first_line "$ethv_qcr_device/power/runtime_status" 2>/dev/null || true)" + ethv_qcr_port_declaration="absent" + case "$ethv_qcr_driver_override" in + ''|'(null)') + ethv_qcr_driver_override="none" + ;; + *) + QPS615_ETHERNET_OVERRIDE_COUNT=$((QPS615_ETHERNET_OVERRIDE_COUNT + 1)) + ;; + esac + if [ -n "$ethv_qcr_of_node" ]; then + QPS615_ETHERNET_OF_NODE_COUNT=$((QPS615_ETHERNET_OF_NODE_COUNT + 1)) + if [ -e "$ethv_qcr_of_node/phy-reset-gpios" ] || \ + [ -e "$ethv_qcr_of_node/qcom,phy-rst-gpio" ]; then + ethv_qcr_port_declaration="declared" + QPS615_ETHERNET_DECLARED_COUNT=$((QPS615_ETHERNET_DECLARED_COUNT + 1)) + fi + fi + printf 'ethernet-function\t%s\tdriver=%s\tmodalias=%s\tdriver_override=%s\tof_node=%s\tport_configuration=%s\truntime_status=%s\n' \ + "${ethv_qcr_device##*/}" \ + "${ethv_qcr_driver:-unbound}" \ + "${ethv_qcr_modalias:-unavailable}" \ + "$ethv_qcr_driver_override" \ + "${ethv_qcr_of_node:-unavailable}" \ + "$ethv_qcr_port_declaration" \ + "${ethv_qcr_runtime_status:-unknown}" >>"$ethv_qcr_inventory" + log_info "[QPS615-ETH] function=${ethv_qcr_device##*/} driver=${ethv_qcr_driver:-unbound} port_configuration=$ethv_qcr_port_declaration driver_override=$ethv_qcr_driver_override of_node=${ethv_qcr_of_node:-unavailable} runtime_status=${ethv_qcr_runtime_status:-unknown} modalias=${ethv_qcr_modalias:-unavailable}" + + case "$ethv_qcr_driver" in + tc956x*) + QPS615_DRIVER_DEVICE_COUNT=$((QPS615_DRIVER_DEVICE_COUNT + 1)) + if [ "$ethv_qcr_port_declaration" = "declared" ]; then + QPS615_DECLARED_DRIVER_DEVICE_COUNT=$((QPS615_DECLARED_DRIVER_DEVICE_COUNT + 1)) + fi + for ethv_qcr_netdev in "$ethv_qcr_device"/net/*; do + [ -d "$ethv_qcr_netdev" ] || continue + QPS615_NETDEV_COUNT=$((QPS615_NETDEV_COUNT + 1)) + if [ "$ethv_qcr_port_declaration" = "declared" ]; then + QPS615_DECLARED_NETDEV_COUNT=$((QPS615_DECLARED_NETDEV_COUNT + 1)) + fi + printf 'netdev\t%s\t%s\n' \ + "${ethv_qcr_device##*/}" \ + "${ethv_qcr_netdev##*/}" >>"$ethv_qcr_inventory" + log_info "[QPS615] ethernet_function=${ethv_qcr_device##*/} netdev=${ethv_qcr_netdev##*/}" + done + ;; + "") + QPS615_UNBOUND_ETHERNET_COUNT=$((QPS615_UNBOUND_ETHERNET_COUNT + 1)) + ;; + *) + QPS615_UNBOUND_ETHERNET_COUNT=$((QPS615_UNBOUND_ETHERNET_COUNT + 1)) + log_warn "[QPS615-ETH] function=${ethv_qcr_device##*/} is bound to unexpected driver=$ethv_qcr_driver" + ;; + esac + fi + done + + QPS615_FIRMWARE_PATH="$(find_image_firmware TC956X_Firmware_PCIeBridge.bin 2>/dev/null || true)" + if [ -n "$QPS615_FIRMWARE_PATH" ]; then + log_info "[QPS615] firmware=$QPS615_FIRMWARE_PATH" + else + log_warn "[QPS615] firmware TC956X_Firmware_PCIeBridge.bin was not found" + fi + + ethv_qcr_module_candidate="$(find_kernel_module tc956x_pcie_eth 2>/dev/null | awk '/^\// { print; exit }' || true)" + ethv_qcr_running_kernel="$(uname -r 2>/dev/null || true)" + case "$ethv_qcr_module_candidate" in + "/lib/modules/$ethv_qcr_running_kernel/"*) + QPS615_MODULE_PATH="$ethv_qcr_module_candidate" + QPS615_MODULE_STATE="available" + ;; + "") + ;; + *) + log_warn "[QPS615-ETH] tc956x_pcie_eth exists only outside the running-kernel module tree: $ethv_qcr_module_candidate" + ;; + esac + + if [ -d /sys/module/tc956x_pcie_eth ]; then + QPS615_MODULE_STATE="loaded-or-built-in" + fi + + if [ -r /sys/module/tc956x_pcie_eth/parameters/tc956x_eth_ports_bdf ]; then + QPS615_MODULE_PORT_BDFS="$( + ethv_read_first_line \ + /sys/module/tc956x_pcie_eth/parameters/tc956x_eth_ports_bdf \ + 2>/dev/null || true + )" + ethv_qcr_nonzero_bdfs="$( + printf '%s' "$QPS615_MODULE_PORT_BDFS" | + tr -d '0,[:space:]' + )" + if [ -n "$QPS615_MODULE_PORT_BDFS" ] && \ + [ -z "$ethv_qcr_nonzero_bdfs" ]; then + QPS615_MODULE_PORT_CONFIG="driver-defaults" + elif [ -n "$QPS615_MODULE_PORT_BDFS" ]; then + QPS615_MODULE_PORT_CONFIG="configured" + fi + printf 'module-configuration\ttc956x_eth_ports_bdf=%s\tstate=%s\n' \ + "${QPS615_MODULE_PORT_BDFS:-unreadable}" \ + "$QPS615_MODULE_PORT_CONFIG" >>"$ethv_qcr_inventory" + log_info "[QPS615-ETH] module_parameter=tc956x_eth_ports_bdf value=${QPS615_MODULE_PORT_BDFS:-unreadable} state=$QPS615_MODULE_PORT_CONFIG" + fi + + if [ -d /sys/bus/pci/drivers/tc956x_pci-eth ]; then + QPS615_PCI_DRIVER_PATH="/sys/bus/pci/drivers/tc956x_pci-eth" + QPS615_PCI_DRIVER_STATE="registered" + log_info "[QPS615-ETH] pci_driver=tc956x_pci-eth state=registered path=$QPS615_PCI_DRIVER_PATH" + elif [ "$QPS615_MODULE_STATE" = "loaded-or-built-in" ]; then + log_warn "[QPS615-ETH] module is loaded but PCI driver tc956x_pci-eth is not registered" + fi + printf 'pci-driver\tname=tc956x_pci-eth\tstate=%s\tpath=%s\n' \ + "$QPS615_PCI_DRIVER_STATE" \ + "${QPS615_PCI_DRIVER_PATH:-unavailable}" >>"$ethv_qcr_inventory" + + QPS615_PCI_AUTOPROBE="$( + ethv_read_first_line /sys/bus/pci/drivers_autoprobe 2>/dev/null || true + )" + [ -n "$QPS615_PCI_AUTOPROBE" ] || QPS615_PCI_AUTOPROBE="unknown" + printf 'pci-autoprobe\tstate=%s\n' \ + "$QPS615_PCI_AUTOPROBE" >>"$ethv_qcr_inventory" + log_info "[QPS615-ETH] pci_drivers_autoprobe=$QPS615_PCI_AUTOPROBE" + + if [ -n "$QPS615_MODULE_PATH" ]; then + log_info "[QPS615-ETH] module=tc956x_pcie_eth state=$QPS615_MODULE_STATE path=$QPS615_MODULE_PATH" + elif [ "$QPS615_MODULE_STATE" = "loaded-or-built-in" ]; then + log_info "[QPS615-ETH] module=tc956x_pcie_eth state=$QPS615_MODULE_STATE path=built-in-or-unexposed" + else + 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" + + if [ "$QPS615_DOWNSTREAM_COUNT" -eq 0 ]; then + QPS615_TOPOLOGY_FAILURE_REASON="QPS615 bridge is enumerated but exposes no downstream PCIe functions" + elif [ -z "$QPS615_FIRMWARE_PATH" ]; then + QPS615_TOPOLOGY_FAILURE_REASON="required TC956X_Firmware_PCIeBridge.bin is not readable in a standard firmware root" + fi + + if [ "$QPS615_ETHERNET_DEVICE_COUNT" -eq 0 ]; then + QPS615_ETHERNET_SKIP_REASON="QPS615 is enumerated but exposes no Toshiba 1179:0220 Ethernet functions" + elif [ "$QPS615_ETHERNET_DECLARED_COUNT" -eq 0 ] && \ + [ "$QPS615_DRIVER_DEVICE_COUNT" -eq 0 ]; then + QPS615_ETHERNET_SKIP_REASON="$QPS615_ETHERNET_DEVICE_COUNT embedded Ethernet function(s) are enumerated, but no runtime DT port configuration declares them for Ethernet use" + elif [ "$QPS615_DECLARED_DRIVER_DEVICE_COUNT" -lt "$QPS615_ETHERNET_DECLARED_COUNT" ]; then + if [ "$QPS615_PCI_AUTOPROBE" = "0" ]; then + QPS615_ETHERNET_FAILURE_REASON="$QPS615_ETHERNET_DECLARED_COUNT declared QPS615 Ethernet port(s) are not ready because PCI driver autoprobe is disabled" + elif [ "$QPS615_ETHERNET_OVERRIDE_COUNT" -gt 0 ]; then + QPS615_ETHERNET_FAILURE_REASON="$QPS615_ETHERNET_DECLARED_COUNT declared QPS615 Ethernet port(s) are not ready and $QPS615_ETHERNET_OVERRIDE_COUNT function(s) have a driver override" + elif [ "$QPS615_MODULE_STATE" = "loaded-or-built-in" ]; then + if [ "$QPS615_PCI_DRIVER_STATE" != "registered" ]; then + QPS615_ETHERNET_FAILURE_REASON="$QPS615_ETHERNET_DECLARED_COUNT declared QPS615 Ethernet port(s) are not bound, tc956x_pcie_eth is loaded but PCI driver tc956x_pci-eth is not registered" + else + QPS615_ETHERNET_FAILURE_REASON="$QPS615_ETHERNET_DECLARED_COUNT declared QPS615 Ethernet port(s) did not bind after automatic matching, PCI driver tc956x_pci-eth is registered and endpoint probe did not complete" + fi + elif [ -n "$QPS615_MODULE_PATH" ]; then + QPS615_ETHERNET_FAILURE_REASON="$QPS615_ETHERNET_DECLARED_COUNT declared QPS615 Ethernet port(s) are not bound although module $QPS615_MODULE_PATH is available" + else + QPS615_ETHERNET_FAILURE_REASON="$QPS615_ETHERNET_DECLARED_COUNT declared QPS615 Ethernet port(s) are not bound and tc956x_pcie_eth is not available for the running kernel" + fi + elif [ "$QPS615_DECLARED_NETDEV_COUNT" -lt "$QPS615_DECLARED_DRIVER_DEVICE_COUNT" ]; then + QPS615_ETHERNET_FAILURE_REASON="$QPS615_DECLARED_DRIVER_DEVICE_COUNT declared TC956x Ethernet port(s) are bound but only $QPS615_DECLARED_NETDEV_COUNT netdev(s) are exposed" + elif [ "$QPS615_ETHERNET_DECLARED_COUNT" -eq 0 ] && \ + [ "$QPS615_NETDEV_COUNT" -lt "$QPS615_DRIVER_DEVICE_COUNT" ]; then + 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 + + if [ -n "$QPS615_TOPOLOGY_FAILURE_REASON" ]; then + QPS615_FAILURE_REASON="$QPS615_TOPOLOGY_FAILURE_REASON" + else + QPS615_FAILURE_REASON="$QPS615_ETHERNET_FAILURE_REASON" + 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 + export QPS615_DECLARED_NETDEV_COUNT + export QPS615_ETHERNET_OF_NODE_COUNT QPS615_ETHERNET_OVERRIDE_COUNT + export QPS615_FIRMWARE_PATH QPS615_MODULE_PATH QPS615_MODULE_STATE + export QPS615_MODULE_PORT_BDFS QPS615_MODULE_PORT_CONFIG + export QPS615_PCI_DRIVER_PATH QPS615_PCI_DRIVER_STATE + export QPS615_PCI_AUTOPROBE + export QPS615_TOPOLOGY_FAILURE_REASON QPS615_ETHERNET_FAILURE_REASON + export QPS615_ETHERNET_SKIP_REASON QPS615_FAILURE_REASON + + [ -z "$QPS615_FAILURE_REASON" ] +} + # ethv_get_driver # Query the bound network driver through ethtool or the sysfs driver link. # stdout: driver name, or an empty line when unavailable.