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 59569569e27457b8c0e9479b035c0779af19d197 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Fri, 4 Sep 2026 22:51:07 +0530 Subject: [PATCH 2/2] buses: add capability-driven I2C validation Correlate enabled I2C controllers, runtime adapters, DT clients, alias-resolved driver support, and actual bindings without board-specific mappings. Preserve the optional legacy path and print per-object diagnostics for malformed or unbound clients. Add distro-aware i2c-tools recovery for supported host distributions, read-only adapter capability checks, and explicitly requested address scan, register read, and transactional write-and-restore operations with bounded execution and validation. Signed-off-by: Srikanth Muppandam --- Runner/config/pkg_command_map.conf | 28 + .../suites/Kernel/Baseport/Buses/Buses.yaml | 24 +- Runner/suites/Kernel/Baseport/Buses/README.md | 106 ++++ Runner/suites/Kernel/Baseport/Buses/run.sh | 542 +++++++++++++++++- Runner/utils/functestlib.sh | 434 ++++++++++++++ Runner/utils/lib_pkg_provider.sh | 48 +- 6 files changed, 1146 insertions(+), 36 deletions(-) create mode 100644 Runner/suites/Kernel/Baseport/Buses/README.md diff --git a/Runner/config/pkg_command_map.conf b/Runner/config/pkg_command_map.conf index 24ad7bcf..315702de 100755 --- a/Runner/config/pkg_command_map.conf +++ b/Runner/config/pkg_command_map.conf @@ -20,6 +20,34 @@ # # Do not assume command name equals package name. +# --------------------------------------------------------------------------- +# I2C userspace diagnostics. +# +# The Buses suite uses only read-only adapter listing. i2cget, i2cset, and +# i2cdump are mapped for explicit focused tests, but are never run automatically +# because arbitrary device accesses can disturb hardware. +# --------------------------------------------------------------------------- + +debian:package-set:i2c-tools=i2c-tools +ubuntu:package-set:i2c-tools=i2c-tools +centos:package-set:i2c-tools=i2c-tools + +debian:i2cdetect=i2c-tools +debian:i2cget=i2c-tools +debian:i2cset=i2c-tools +debian:i2cdump=i2c-tools +debian:i2ctransfer=i2c-tools +ubuntu:i2cdetect=i2c-tools +ubuntu:i2cget=i2c-tools +ubuntu:i2cset=i2c-tools +ubuntu:i2cdump=i2c-tools +ubuntu:i2ctransfer=i2c-tools +centos:i2cdetect=i2c-tools +centos:i2cget=i2c-tools +centos:i2cset=i2c-tools +centos:i2cdump=i2c-tools +centos:i2ctransfer=i2c-tools + # --------------------------------------------------------------------------- # Common apt provider mappings. # Applies to Debian/Ubuntu apt-based rootfs. diff --git a/Runner/suites/Kernel/Baseport/Buses/Buses.yaml b/Runner/suites/Kernel/Baseport/Buses/Buses.yaml index 076bd9bc..35ce2ff8 100644 --- a/Runner/suites/Kernel/Baseport/Buses/Buses.yaml +++ b/Runner/suites/Kernel/Baseport/Buses/Buses.yaml @@ -1,16 +1,34 @@ metadata: - name: buses + name: Buses format: "Lava-Test Test Definition 1.0" - description: "Test I2C busses" + description: "Validate I2C runtime state, adapter capabilities, and explicitly selected transfers" os: - linux scope: - functional +params: + I2C_LEGACY_TEST_ENABLE: "auto" + I2C_TEST_ADAPTER: "auto" + I2C_TEST_TIMEOUT: "15" + I2C_DMESG_STRICT: "0" + I2C_TOOLS_TIMEOUT: "10" + I2C_SCAN_ENABLE: "0" + I2C_SCAN_MODE: "quick" + I2C_READ_ADDRESS: "" + I2C_READ_REGISTER: "" + I2C_READ_MODE: "b" + I2C_READ_EXPECTED: "" + I2C_READ_MASK: "" + I2C_WRITE_ADDRESS: "" + I2C_WRITE_REGISTER: "" + I2C_WRITE_VALUE: "" + I2C_WRITE_MODE: "b" + I2C_ALLOW_WRITE: "0" + run: steps: - REPO_PATH=$PWD - cd Runner/suites/Kernel/Baseport/Buses - ./run.sh || true - $REPO_PATH/Runner/utils/send-to-lava.sh Buses.res - diff --git a/Runner/suites/Kernel/Baseport/Buses/README.md b/Runner/suites/Kernel/Baseport/Buses/README.md new file mode 100644 index 00000000..24ae6395 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/Buses/README.md @@ -0,0 +1,106 @@ +# I2C Buses Validation + +This suite performs a read-only, capability-driven I2C runtime validation. It +correlates enabled Qualcomm GENI I2C device-tree controllers with kernel +adapters and registered clients, reports driver binding, dynamically resolves +client modaliases against the running image, and retains bounded inventory and +kernel-health artifacts. It does not use board-specific client-to-driver +mappings. + +The default run does not scan arbitrary bus addresses or read device registers. +Those operations can disturb devices whose protocols are not known to the +test. When the image provides `i2c-msm-test`, the compatibility path runs +automatically against the selected character device. It can be disabled or +explicitly required through configuration. + +On Debian, Ubuntu, and CentOS, the suite recovers the standard `i2c-tools` +package after I2C applicability is established. The default path uses +`i2cdetect -l` and `i2cdetect -F` to list adapters and query controller +functionality without probing peripheral addresses. Yocto and qcom-distro +remain image-managed and do not install packages. The Qualcomm-specific +`i2c-msm-test` utility remains optional and image-provided on every distro. + +## Run + +```sh +cd Runner/suites/Kernel/Baseport/Buses +./run.sh +``` + +Require legacy functional validation on the first exposed character device: + +```sh +./run.sh --legacy-test +``` + +Select a validated adapter and timeout explicitly: + +```sh +./run.sh --legacy-test --adapter 0 --timeout 20 +``` + +Equivalent environment variables are: + +```sh +I2C_LEGACY_TEST_ENABLE=1 I2C_TEST_ADAPTER=0 I2C_TEST_TIMEOUT=20 I2C_DMESG_STRICT=1 ./run.sh +``` + +## Explicit i2c-tools operations + +The following operations can affect attached devices and never run by default. +The suite auto-selects the adapter only when exactly one I2C character adapter +exists. Select it explicitly on multi-adapter systems and obtain addresses and +registers from the board or peripheral documentation. + +Scan one adapter using SMBus quick-write probes: + +```sh +./run.sh --adapter 1 --scan --scan-mode quick +``` + +Quick-write probing can corrupt some EEPROM-style devices. + +Use SMBus receive-byte probes instead when the selected devices require them: + +```sh +./run.sh --adapter 1 --scan --scan-mode read +``` + +Receive-byte probing can also confuse devices or leave them in an unexpected +state. Use either scan mode only when the selected bus topology is understood. + +Read a byte register and optionally validate selected bits: + +```sh +./run.sh --adapter 1 --read 0x50 0x00 --read-mode b \ + --expected 0x42 --mask 0xff +``` + +Write a documented scratch register, verify the value, and restore its original +contents: + +```sh +./run.sh --adapter 1 --write 0x50 0x10 0x5a --write-mode b --allow-write +``` + +Register writes require explicit confirmation. The suite reads the original +value first, rejects a test value equal to the original value, verifies the +write, restores the original value, and verifies restoration. Restoration +cannot undo device behavior triggered immediately by a write, so only a +documented test or scratch register should be selected. + +## Results + +- `PASS`: applicable controllers, adapters, and declared clients have a + consistent runtime state, adapter capability queries pass, and every + explicitly requested scan or register transaction succeeds. +- `FAIL`: an enabled controller lacks a runtime adapter or driver, a declared + client is unbound or waiting for an unresolved supplier, an installed + diagnostic fails, an explicit scan or read fails, or a write cannot be + verified and restored. +- `SKIP`: I2C is not exposed, or an optional tool or functional test is not + available. + +Artifacts are retained under `results/Buses/`, including controller, adapter, +client, registered-driver, modalias, module-origin, supplier-wait, DT-resource, +and mux-channel evidence for diagnosing unbound clients. diff --git a/Runner/suites/Kernel/Baseport/Buses/run.sh b/Runner/suites/Kernel/Baseport/Buses/run.sh index d6bc13e9..a76fd231 100755 --- a/Runner/suites/Kernel/Baseport/Buses/run.sh +++ b/Runner/suites/Kernel/Baseport/Buses/run.sh @@ -1,10 +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)" +# 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" @@ -18,37 +23,524 @@ 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_pkg_provider.sh" TESTNAME="Buses" -test_path=$(find_test_case_by_name "$TESTNAME") -cd "$test_path" || exit 1 -# shellcheck disable=SC2034 -res_file="./$TESTNAME.res" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" +RESULT_DIR="$SCRIPT_DIR/results/$TESTNAME" + +I2C_LEGACY_TEST_ENABLE="${I2C_LEGACY_TEST_ENABLE:-auto}" +I2C_TEST_ADAPTER="${I2C_TEST_ADAPTER:-auto}" +I2C_TEST_TIMEOUT="${I2C_TEST_TIMEOUT:-15}" +I2C_DMESG_STRICT="${I2C_DMESG_STRICT:-0}" +I2C_SCAN_ENABLE="${I2C_SCAN_ENABLE:-0}" +I2C_SCAN_MODE="${I2C_SCAN_MODE:-quick}" +I2C_READ_ADDRESS="${I2C_READ_ADDRESS:-}" +I2C_READ_REGISTER="${I2C_READ_REGISTER:-}" +I2C_READ_MODE="${I2C_READ_MODE:-b}" +I2C_READ_EXPECTED="${I2C_READ_EXPECTED:-}" +I2C_READ_MASK="${I2C_READ_MASK:-}" +I2C_WRITE_ADDRESS="${I2C_WRITE_ADDRESS:-}" +I2C_WRITE_REGISTER="${I2C_WRITE_REGISTER:-}" +I2C_WRITE_VALUE="${I2C_WRITE_VALUE:-}" +I2C_WRITE_MODE="${I2C_WRITE_MODE:-b}" +I2C_ALLOW_WRITE="${I2C_ALLOW_WRITE:-0}" +I2C_TOOLS_TIMEOUT="${I2C_TOOLS_TIMEOUT:-10}" + +usage() { + cat <<'EOF' +Usage: ./run.sh [options] + +Options: + --legacy-test Run image-provided i2c-msm-test after inventory. + --adapter BUS Use /dev/i2c-BUS or an explicit /dev/i2c-* path. + --timeout SECONDS Legacy command timeout, default: 15. + --tools-timeout SEC i2c-tools command timeout, default: 10. + --scan Scan addresses on the explicitly selected adapter. + --scan-mode MODE quick or read, default: quick. + --read ADDRESS REG Read one explicitly selected register. + --read-mode MODE b or w, default: b. + --expected VALUE Expected read value. + --mask VALUE Mask applied to expected read comparison. + --write ADDRESS REG VALUE + Transactionally write, read back, and restore a register. + --write-mode MODE b or w, default: b. + --allow-write Confirm the selected register is safe to modify. + -h, --help Show this help. + +Environment: + I2C_LEGACY_TEST_ENABLE=auto|0|1 + I2C_TEST_ADAPTER=auto|BUS|/dev/i2c-BUS + I2C_TEST_TIMEOUT=SECONDS + I2C_DMESG_STRICT=0|1 + I2C_SCAN_ENABLE=0|1 + I2C_SCAN_MODE=quick|read + I2C_READ_ADDRESS, I2C_READ_REGISTER, I2C_READ_MODE, I2C_READ_EXPECTED, + I2C_READ_MASK, I2C_WRITE_ADDRESS, I2C_WRITE_REGISTER, I2C_WRITE_VALUE, + I2C_WRITE_MODE, I2C_ALLOW_WRITE=0|1, I2C_TOOLS_TIMEOUT=SECONDS + +Scanning and register operations auto-select a unique character adapter when +possible. Use --adapter when multiple adapters exist. Writes also require +--allow-write and always attempt to restore the original value. +EOF +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --legacy-test) + I2C_LEGACY_TEST_ENABLE=1 + shift + ;; + --adapter) + [ "$#" -ge 2 ] || return 1 + I2C_TEST_ADAPTER="$2" + shift 2 + ;; + --timeout) + [ "$#" -ge 2 ] || return 1 + I2C_TEST_TIMEOUT="$2" + shift 2 + ;; + --tools-timeout) + [ "$#" -ge 2 ] || return 1 + I2C_TOOLS_TIMEOUT="$2" + shift 2 + ;; + --scan) + I2C_SCAN_ENABLE=1 + shift + ;; + --scan-mode) + [ "$#" -ge 2 ] || return 1 + I2C_SCAN_MODE="$2" + shift 2 + ;; + --read) + [ "$#" -ge 3 ] || return 1 + I2C_READ_ADDRESS="$2" + I2C_READ_REGISTER="$3" + shift 3 + ;; + --read-mode) + [ "$#" -ge 2 ] || return 1 + I2C_READ_MODE="$2" + shift 2 + ;; + --expected) + [ "$#" -ge 2 ] || return 1 + I2C_READ_EXPECTED="$2" + shift 2 + ;; + --mask) + [ "$#" -ge 2 ] || return 1 + I2C_READ_MASK="$2" + shift 2 + ;; + --write) + [ "$#" -ge 4 ] || return 1 + I2C_WRITE_ADDRESS="$2" + I2C_WRITE_REGISTER="$3" + I2C_WRITE_VALUE="$4" + shift 4 + ;; + --write-mode) + [ "$#" -ge 2 ] || return 1 + I2C_WRITE_MODE="$2" + shift 2 + ;; + --allow-write) + I2C_ALLOW_WRITE=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + log_error "Unknown argument: $1" + return 1 + ;; + esac + done +} + +# cleanup +# Restores a register value when an interrupted write left restoration pending. +cleanup() { + if ! i2c_tools_restore_pending_write; then + log_fail "I2C emergency register restoration failed" + return 1 + fi +} + +# handle_signal +# Restores pending state before terminating after a signal. +handle_signal() { + cleanup || true + exit 1 +} + +parse_args "$@" || { + usage >&2 + exit 2 +} + +case "$I2C_LEGACY_TEST_ENABLE" in + auto|0|1) + ;; + *) + log_warn "Invalid I2C_LEGACY_TEST_ENABLE='$I2C_LEGACY_TEST_ENABLE', using auto" + I2C_LEGACY_TEST_ENABLE=auto + ;; +esac + +case "$I2C_DMESG_STRICT" in + 0|1) + ;; + *) + log_warn "Invalid I2C_DMESG_STRICT='$I2C_DMESG_STRICT', using 0" + I2C_DMESG_STRICT=0 + ;; +esac + +case "$I2C_TEST_TIMEOUT" in + ''|*[!0-9]*|0) + log_warn "Invalid I2C_TEST_TIMEOUT='$I2C_TEST_TIMEOUT', using 15" + I2C_TEST_TIMEOUT=15 + ;; +esac + +case "$I2C_TOOLS_TIMEOUT" in + ''|*[!0-9]*|0) + log_error "I2C_TOOLS_TIMEOUT must be a positive integer" + exit 2 + ;; +esac + +case "$I2C_SCAN_ENABLE:$I2C_ALLOW_WRITE" in + 0:0|0:1|1:0|1:1) + ;; + *) + log_error "I2C_SCAN_ENABLE and I2C_ALLOW_WRITE must be 0 or 1" + exit 2 + ;; +esac + +case "$I2C_SCAN_MODE" in + quick|read) + ;; + *) + log_error "I2C_SCAN_MODE must be quick or read" + exit 2 + ;; +esac + +case "$I2C_READ_MODE:$I2C_WRITE_MODE" in + b:b|b:w|w:b|w:w) + ;; + *) + log_error "I2C read and write modes must be b or w" + exit 2 + ;; +esac + +if [ -n "$I2C_READ_ADDRESS$I2C_READ_REGISTER" ] && + { [ -z "$I2C_READ_ADDRESS" ] || [ -z "$I2C_READ_REGISTER" ]; }; then + log_error "Both I2C_READ_ADDRESS and I2C_READ_REGISTER are required" + exit 2 +fi + +if [ -z "$I2C_READ_ADDRESS" ] && + { [ -n "$I2C_READ_EXPECTED" ] || [ -n "$I2C_READ_MASK" ]; }; then + log_error "I2C expected value and mask require an explicit register read" + exit 2 +fi + +if [ -n "$I2C_READ_MASK" ] && [ -z "$I2C_READ_EXPECTED" ]; then + log_error "I2C_READ_MASK requires I2C_READ_EXPECTED" + exit 2 +fi + +if [ -n "$I2C_WRITE_ADDRESS$I2C_WRITE_REGISTER$I2C_WRITE_VALUE" ] && + { [ -z "$I2C_WRITE_ADDRESS" ] || [ -z "$I2C_WRITE_REGISTER" ] || [ -z "$I2C_WRITE_VALUE" ]; }; then + log_error "I2C write address, register, and value are all required" + exit 2 +fi + +if [ -n "$I2C_WRITE_ADDRESS" ] && [ "$I2C_ALLOW_WRITE" -ne 1 ]; then + log_error "Register writes require --allow-write or I2C_ALLOW_WRITE=1" + exit 2 +fi + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 +trap cleanup EXIT +trap handle_signal HUP INT TERM + +if ! mkdir -p "$RESULT_DIR"; then + test_result_finish "FAIL" "$TESTNAME FAIL: cannot create result directory $RESULT_DIR" +fi + +TMPDIR="$SCRIPT_DIR" + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME I2C Testcase" +log_info "Configuration: legacy_test=$I2C_LEGACY_TEST_ENABLE adapter=$I2C_TEST_ADAPTER timeout=${I2C_TEST_TIMEOUT}s tools_timeout=${I2C_TOOLS_TIMEOUT}s scan=$I2C_SCAN_ENABLE scan_mode=$I2C_SCAN_MODE read=${I2C_READ_ADDRESS:-disabled}:${I2C_READ_REGISTER:-disabled}:$I2C_READ_MODE write=${I2C_WRITE_ADDRESS:-disabled}:${I2C_WRITE_REGISTER:-disabled}:$I2C_WRITE_MODE allow_write=$I2C_ALLOW_WRITE dmesg_strict=$I2C_DMESG_STRICT" + +if ! CHECK_DEPS_RECOVER=0 CHECK_DEPS_NO_EXIT=1 check_dependencies \ + awk \ + basename \ + cat \ + dirname \ + find \ + grep \ + mkdir \ + mktemp \ + readlink \ + rm \ + sed \ + sort \ + tr \ + wc; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi -log_info "-----------------------------------------------------------------------------------------" -log_info "-------------------Starting $TESTNAME Testcase----------------------------" -log_info "=== Test Initialization ===" +i2c_collect_runtime_inventory "$RESULT_DIR" +i2c_inventory_status=$? -log_info "Checking if dependency binary is available" -check_dependencies i2c-msm-test +case "$i2c_inventory_status" in + 0) + test_result_record \ + "PASS" \ + "I2C runtime is healthy: controllers=$I2C_RUNTIME_CONTROLLER_COUNT adapters=$I2C_RUNTIME_ADAPTER_COUNT clients=$I2C_RUNTIME_CLIENT_COUNT bound_clients=$I2C_RUNTIME_BOUND_CLIENT_COUNT" + ;; + 1) + test_result_record \ + "FAIL" \ + "I2C runtime validation failed: ${I2C_RUNTIME_FAILURE_REASON:-inconsistent controller, adapter, or client state}" + ;; + 2) + test_result_finish "SKIP" "$TESTNAME SKIP: I2C is not exposed by the runtime device tree or kernel" + ;; + *) + test_result_finish "FAIL" "$TESTNAME FAIL: I2C runtime inventory could not complete" + ;; +esac -log_info "running i2c binary" -output=$(i2c-msm-test -v -D /dev/i2c-0 -l | grep "ret:1") +i2c_devnode_count=0 +for i2c_devnode in /dev/i2c-*; do + [ -c "$i2c_devnode" ] || continue + i2c_devnode_count=$((i2c_devnode_count + 1)) + log_info "[I2C-DEVNODE] path=$i2c_devnode" +done -if echo "$output" | grep -q "Reading"; then - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$res_file" - exit 0 +if [ "$i2c_devnode_count" -gt 0 ]; then + test_result_record "PASS" "I2C character devices are exposed: count=$i2c_devnode_count" else - log_fail "$TESTNAME : Test Failed" - echo "$TESTNAME FAIL" > "$res_file" - exit 1 + test_result_record "SKIP" "I2C adapters are present but CONFIG_I2C_CHARDEV runtime nodes are not exposed" +fi + +i2c_tools_explicit=0 +if [ "$I2C_SCAN_ENABLE" -eq 1 ] || + [ -n "$I2C_READ_ADDRESS" ] || + [ -n "$I2C_WRITE_ADDRESS" ]; then + i2c_tools_explicit=1 +fi + +if [ "$i2c_tools_explicit" -eq 1 ] && [ "$I2C_TEST_ADAPTER" = "auto" ]; then + I2C_TEST_ADAPTER=$(i2c_tools_select_adapter) + i2c_adapter_selection_status=$? + case "$i2c_adapter_selection_status" in + 0) + log_info "I2C functional adapter auto-selected: i2c-$I2C_TEST_ADAPTER" + ;; + 1) + test_result_finish "FAIL" "$TESTNAME FAIL: multiple I2C character adapters are available, select one with --adapter or I2C_TEST_ADAPTER" + ;; + *) + test_result_finish "FAIL" "$TESTNAME FAIL: no I2C character adapter is available for the explicitly requested operation" + ;; + esac fi + +pkg_ensure_host_distro_package_set_present i2c-tools +i2c_tools_package_status=$? + +case "$i2c_tools_package_status" in + 0) + log_info "I2C userspace package set is ready" + ;; + 1) + if [ "$i2c_tools_explicit" -eq 1 ]; then + log_warn "i2c-tools package recovery failed on $(pkg_detect_os_id), explicit operations will verify the required commands directly" + else + test_result_record "SKIP" "I2C userspace diagnostics are unavailable because i2c-tools package recovery failed on $(pkg_detect_os_id)" + fi + ;; + 2) + log_info "I2C package recovery is not applicable, using image-provided tools" + ;; + *) + test_result_record "FAIL" "I2C package preparation returned unexpected status $i2c_tools_package_status" + ;; +esac + +if [ "$i2c_tools_package_status" -ne 1 ]; then + log_info "I2C userspace validation: listing adapters and querying adapter functionality without transferring device data" + i2c_tools_validate_adapters "$RESULT_DIR" "$I2C_TOOLS_TIMEOUT" + i2c_tools_status=$? + case "$i2c_tools_status" in + 0) + test_result_record "PASS" "i2c-tools validated $I2C_TOOLS_CAPABILITY_COUNT adapter functionality report(s)" + ;; + 1) + test_result_record "FAIL" "i2c-tools adapter listing or functionality query failed, see $RESULT_DIR/i2cdetect_*.log" + ;; + 2) + test_result_record "SKIP" "i2cdetect or an I2C character adapter is unavailable" + ;; + *) + test_result_record "FAIL" "I2C adapter functionality helper returned unexpected status $i2c_tools_status" + ;; + esac +fi + +if [ "$I2C_SCAN_ENABLE" -eq 1 ]; then + log_warn "I2C address scan was explicitly requested for adapter=$I2C_TEST_ADAPTER mode=$I2C_SCAN_MODE" + i2c_tools_scan_adapter \ + "$I2C_TEST_ADAPTER" \ + "$I2C_SCAN_MODE" \ + "$I2C_TOOLS_TIMEOUT" \ + "$RESULT_DIR" + i2c_scan_status=$? + case "$i2c_scan_status" in + 0) + test_result_record "PASS" "Explicit I2C $I2C_SCAN_MODE address scan completed on adapter $I2C_TEST_ADAPTER" + ;; + 1) + test_result_record "FAIL" "Explicit I2C address scan failed on adapter $I2C_TEST_ADAPTER" + ;; + 2) + test_result_record "FAIL" "Explicit I2C address scan requires i2cdetect and an accessible adapter" + ;; + *) + test_result_record "FAIL" "Explicit I2C address scan configuration is invalid" + ;; + esac +fi + +if [ -n "$I2C_READ_ADDRESS" ]; then + log_warn "I2C register read was explicitly requested for adapter=$I2C_TEST_ADAPTER address=$I2C_READ_ADDRESS register=$I2C_READ_REGISTER mode=$I2C_READ_MODE" + i2c_tools_read_register \ + "$I2C_TEST_ADAPTER" \ + "$I2C_READ_ADDRESS" \ + "$I2C_READ_REGISTER" \ + "$I2C_READ_MODE" \ + "$I2C_TOOLS_TIMEOUT" \ + "$RESULT_DIR" \ + "$I2C_READ_EXPECTED" \ + "$I2C_READ_MASK" + i2c_read_status=$? + case "$i2c_read_status" in + 0) + test_result_record "PASS" "Explicit I2C register read completed: adapter=$I2C_TEST_ADAPTER address=$I2C_READ_ADDRESS register=$I2C_READ_REGISTER value=$I2C_TOOLS_LAST_READ" + ;; + 1) + test_result_record "FAIL" "Explicit I2C register read or expected-value comparison failed" + ;; + 2) + test_result_record "FAIL" "Explicit I2C register read requires i2cget and an accessible adapter" + ;; + *) + test_result_record "FAIL" "Explicit I2C register read configuration is invalid" + ;; + esac +fi + +if [ -n "$I2C_WRITE_ADDRESS" ]; then + log_warn "I2C transactional register write was explicitly authorized for adapter=$I2C_TEST_ADAPTER address=$I2C_WRITE_ADDRESS register=$I2C_WRITE_REGISTER mode=$I2C_WRITE_MODE" + i2c_tools_write_restore_register \ + "$I2C_TEST_ADAPTER" \ + "$I2C_WRITE_ADDRESS" \ + "$I2C_WRITE_REGISTER" \ + "$I2C_WRITE_VALUE" \ + "$I2C_WRITE_MODE" \ + "$I2C_TOOLS_TIMEOUT" \ + "$RESULT_DIR" + i2c_write_status=$? + case "$i2c_write_status" in + 0) + test_result_record "PASS" "Explicit I2C register write was read back and the original value was restored" + ;; + 1) + test_result_record "FAIL" "Explicit I2C register write, read-back, or restoration failed" + ;; + 2) + test_result_record "FAIL" "Explicit I2C register write requires i2cget, i2cset, and an accessible adapter" + ;; + *) + test_result_record "FAIL" "Explicit I2C register write configuration is invalid" + ;; + esac +fi + +if [ "$I2C_LEGACY_TEST_ENABLE" != 0 ]; then + log_info "I2C functional validation: running the optional image-provided i2c-msm-test path" + i2c_run_legacy_test "$RESULT_DIR" "$I2C_TEST_ADAPTER" "$I2C_TEST_TIMEOUT" + i2c_legacy_status=$? + case "$i2c_legacy_status" in + 0) + test_result_record "PASS" "Legacy I2C functional validation completed on $I2C_LEGACY_SELECTED_ADAPTER" + ;; + 1) + test_result_record "FAIL" "i2c-msm-test failed, timed out, or omitted its required transfer markers" + ;; + 2) + test_result_record "SKIP" "Legacy I2C validation is unavailable because i2c-msm-test is not provided by the image" + ;; + 4) + if [ "$I2C_LEGACY_TEST_ENABLE" = "1" ]; then + test_result_record "FAIL" "Legacy I2C functional test has no usable adapter: requested=$I2C_TEST_ADAPTER" + else + test_result_record "SKIP" "Automatic legacy I2C validation found no usable character-device adapter" + fi + ;; + *) + test_result_record "FAIL" "Legacy I2C functional validation received invalid configuration" + ;; + esac +else + test_result_record "SKIP" "Legacy i2c-msm-test was disabled by configuration" +fi + +log_info "I2C kernel-health validation: capturing controller errors without changing bus state" +scan_dmesg_errors \ + "$RESULT_DIR" \ + 'geni_i2c.*|i2c_qcom_geni.*|i2c.*' \ + 'deferred probe|EPROBE_DEFER|using dummy regulator|supply [^ ]+ not found' +i2c_dmesg_status=$? + +if [ ! -s "$RESULT_DIR/dmesg_snapshot.log" ]; then + test_result_record "SKIP" "Kernel log access is unavailable for I2C health validation" +elif [ "$i2c_dmesg_status" -eq 0 ] && [ "$I2C_DMESG_STRICT" -eq 1 ]; then + test_result_record "FAIL" "I2C-related kernel errors were found in $RESULT_DIR/dmesg_errors.log" +elif [ "$i2c_dmesg_status" -eq 0 ]; then + test_result_record "SKIP" "I2C kernel errors were retained as advisory evidence, set I2C_DMESG_STRICT=1 to gate them" +else + test_result_record "PASS" "No non-benign I2C controller 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 a5ae8867..414f8bbf 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -4432,6 +4432,440 @@ i2c_collect_runtime_inventory() { return 0 } +############################################################################### +# I2C userspace-tool validation helpers. +############################################################################### +# i2c_tools_select_adapter +# Prints the unique exposed I2C character adapter number. +i2c_tools_select_adapter() { + itsa_dev_root="${I2C_DEV_ROOT:-/dev}" + itsa_selected="" + itsa_count=0 + + for itsa_devnode in "$itsa_dev_root"/i2c-*; do + [ -c "$itsa_devnode" ] || continue + itsa_bus=${itsa_devnode##*-} + case "$itsa_bus" in + ''|*[!0-9]*) + continue + ;; + esac + itsa_selected=$itsa_bus + itsa_count=$((itsa_count + 1)) + done + + [ "$itsa_count" -gt 0 ] || return 2 + [ "$itsa_count" -eq 1 ] || return 1 + printf '%s\n' "$itsa_selected" +} + +# i2c_tools_adapter_number +# Normalizes BUS or /dev/i2c-BUS and verifies that its character device exists. +i2c_tools_adapter_number() { + itan_adapter="$1" + itan_dev_root="${I2C_DEV_ROOT:-/dev}" + + case "$itan_adapter" in + "$itan_dev_root"/i2c-*) + itan_bus=${itan_adapter##*-} + ;; + /dev/i2c-*) + itan_bus=${itan_adapter##*-} + ;; + *) + itan_bus=$itan_adapter + ;; + esac + + case "$itan_bus" in + ''|*[!0-9]*) + return 3 + ;; + esac + + [ -c "$itan_dev_root/i2c-$itan_bus" ] || return 2 + printf '%s\n' "$itan_bus" +} + +# i2c_tools_value_decimal +# Converts a validated decimal or hexadecimal i2c-tools argument to decimal. +i2c_tools_value_decimal() { + itvd_value="$1" + + case "$itvd_value" in + 0x[0-9a-fA-F]*) + itvd_digits=${itvd_value#0x} + case "$itvd_digits" in + ''|*[!0-9a-fA-F]*) + return 3 + ;; + esac + ;; + ''|*[!0-9]*) + return 3 + ;; + esac + + printf '%d\n' "$itvd_value" 2>/dev/null +} + +# i2c_tools_value_in_range +# Validates a decimal or hexadecimal value against inclusive decimal bounds. +i2c_tools_value_in_range() { + itvir_value="$1" + itvir_minimum="$2" + itvir_maximum="$3" + itvir_decimal=$(i2c_tools_value_decimal "$itvir_value") || return 3 + + [ "$itvir_decimal" -ge "$itvir_minimum" ] 2>/dev/null && + [ "$itvir_decimal" -le "$itvir_maximum" ] 2>/dev/null +} + +# i2c_tools_validate_adapters +# Lists adapters and queries each exposed character adapter's functionality. +i2c_tools_validate_adapters() { + itva_result_dir="$1" + itva_timeout="$2" + itva_dev_root="${I2C_DEV_ROOT:-/dev}" + I2C_TOOLS_CAPABILITY_COUNT=0 + I2C_TOOLS_CAPABILITY_FAILURES=0 + + [ -n "$itva_result_dir" ] || return 3 + case "$itva_timeout" in + ''|*[!0-9]*|0) + return 3 + ;; + esac + command -v i2cdetect >/dev/null 2>&1 || return 2 + mkdir -p "$itva_result_dir" || return 1 + + if ! run_with_timeout_log \ + "$itva_timeout" \ + "$itva_result_dir/i2cdetect_list.log" \ + i2cdetect -l; then + log_file_with_label "I2C-TOOLS-LIST" "$itva_result_dir/i2cdetect_list.log" + log_fail "[I2C-TOOLS] expected=adapter-list observed=command-failed-or-timeout artifact=$itva_result_dir/i2cdetect_list.log" + return 1 + fi + if [ ! -s "$itva_result_dir/i2cdetect_list.log" ]; then + log_fail "[I2C-TOOLS] expected=nonempty-adapter-list observed=empty artifact=$itva_result_dir/i2cdetect_list.log" + return 1 + fi + log_file_with_label "I2C-TOOLS-LIST" "$itva_result_dir/i2cdetect_list.log" + + for itva_devnode in "$itva_dev_root"/i2c-*; do + [ -c "$itva_devnode" ] || continue + itva_bus=${itva_devnode##*-} + case "$itva_bus" in + ''|*[!0-9]*) + continue + ;; + esac + I2C_TOOLS_CAPABILITY_COUNT=$((I2C_TOOLS_CAPABILITY_COUNT + 1)) + itva_log="$itva_result_dir/i2cdetect_functionality_${itva_bus}.log" + if run_with_timeout_log "$itva_timeout" "$itva_log" i2cdetect -F "$itva_bus" && + grep -Eq 'I2C|SMBus' "$itva_log"; then + log_info "[I2C-TOOLS] adapter=i2c-$itva_bus functionality=readable artifact=$itva_log" + else + I2C_TOOLS_CAPABILITY_FAILURES=$((I2C_TOOLS_CAPABILITY_FAILURES + 1)) + log_file_with_label "I2C-TOOLS-F$itva_bus" "$itva_log" + log_fail "[I2C-TOOLS] adapter=i2c-$itva_bus expected=readable-functionality observed=command-failed-or-timeout artifact=$itva_log" + fi + done + + [ "$I2C_TOOLS_CAPABILITY_COUNT" -gt 0 ] || return 2 + [ "$I2C_TOOLS_CAPABILITY_FAILURES" -eq 0 ] +} + +# i2c_tools_scan_adapter +# Runs an explicitly requested address scan and retains the complete matrix. +i2c_tools_scan_adapter() { + itsa_adapter="$1" + itsa_mode="$2" + itsa_timeout="$3" + itsa_result_dir="$4" + + [ -n "$itsa_result_dir" ] || return 3 + case "$itsa_timeout" in + ''|*[!0-9]*|0) + return 3 + ;; + esac + case "$itsa_mode" in + quick|read) + ;; + *) + return 3 + ;; + esac + command -v i2cdetect >/dev/null 2>&1 || return 2 + itsa_bus=$(i2c_tools_adapter_number "$itsa_adapter") || return $? + mkdir -p "$itsa_result_dir" || return 1 + itsa_log="$itsa_result_dir/i2cdetect_scan_${itsa_bus}_${itsa_mode}.log" + + set -- i2cdetect -y "$itsa_bus" + if [ "$itsa_mode" = "read" ]; then + set -- i2cdetect -r -y "$itsa_bus" + fi + if ! run_with_timeout_log "$itsa_timeout" "$itsa_log" "$@"; then + log_file_with_label "I2C-SCAN" "$itsa_log" + log_fail "[I2C-SCAN] adapter=i2c-$itsa_bus mode=$itsa_mode expected=completed-scan observed=command-failed-or-timeout artifact=$itsa_log" + return 1 + fi + if ! grep -Eq '(^|[[:space:]])0[[:space:]]+1[[:space:]]+2' "$itsa_log"; then + log_file_with_label "I2C-SCAN" "$itsa_log" + log_fail "[I2C-SCAN] adapter=i2c-$itsa_bus mode=$itsa_mode expected=address-matrix observed=malformed-output artifact=$itsa_log" + return 1 + fi + + log_file_with_label "I2C-SCAN" "$itsa_log" + log_info "[I2C-SCAN] adapter=i2c-$itsa_bus mode=$itsa_mode state=completed artifact=$itsa_log" + return 0 +} + +# i2c_tools_read_register
[expected] [mask] +# Reads one explicitly selected register and optionally validates masked data. +i2c_tools_read_register() { + itrr_adapter="$1" + itrr_address="$2" + itrr_register="$3" + itrr_mode="$4" + itrr_timeout="$5" + itrr_result_dir="$6" + itrr_expected="${7:-}" + itrr_mask="${8:-}" + I2C_TOOLS_LAST_READ="" + + [ -n "$itrr_result_dir" ] || return 3 + case "$itrr_mode" in + b|w) + ;; + *) + return 3 + ;; + esac + case "$itrr_timeout" in + ''|*[!0-9]*|0) + return 3 + ;; + esac + i2c_tools_value_in_range "$itrr_address" 3 119 || return 3 + i2c_tools_value_in_range "$itrr_register" 0 255 || return 3 + if [ -n "$itrr_expected" ]; then + if [ "$itrr_mode" = "w" ]; then + i2c_tools_value_in_range "$itrr_expected" 0 65535 || return 3 + else + i2c_tools_value_in_range "$itrr_expected" 0 255 || return 3 + fi + fi + if [ -n "$itrr_mask" ]; then + if [ "$itrr_mode" = "w" ]; then + i2c_tools_value_in_range "$itrr_mask" 0 65535 || return 3 + else + i2c_tools_value_in_range "$itrr_mask" 0 255 || return 3 + fi + fi + command -v i2cget >/dev/null 2>&1 || return 2 + itrr_bus=$(i2c_tools_adapter_number "$itrr_adapter") || return $? + mkdir -p "$itrr_result_dir" || return 1 + itrr_log="$itrr_result_dir/i2cget_${itrr_bus}_${itrr_address}_${itrr_register}_${itrr_mode}.log" + + if ! run_with_timeout_log \ + "$itrr_timeout" \ + "$itrr_log" \ + i2cget -y "$itrr_bus" "$itrr_address" "$itrr_register" "$itrr_mode"; then + log_file_with_label "I2C-READ" "$itrr_log" + log_fail "[I2C-READ] adapter=i2c-$itrr_bus address=$itrr_address register=$itrr_register mode=$itrr_mode expected=successful-read observed=command-failed-or-timeout artifact=$itrr_log" + return 1 + fi + + I2C_TOOLS_LAST_READ=$(awk '/^0x[0-9a-fA-F]+$/ { value=$0 } END { print value }' "$itrr_log") + i2c_tools_value_decimal "$I2C_TOOLS_LAST_READ" >/dev/null || { + log_file_with_label "I2C-READ" "$itrr_log" + log_fail "[I2C-READ] adapter=i2c-$itrr_bus address=$itrr_address register=$itrr_register expected=numeric-value observed=${I2C_TOOLS_LAST_READ:-missing} artifact=$itrr_log" + return 1 + } + + if [ -n "$itrr_expected" ]; then + itrr_actual_decimal=$(i2c_tools_value_decimal "$I2C_TOOLS_LAST_READ") || return 1 + itrr_expected_decimal=$(i2c_tools_value_decimal "$itrr_expected") || return 3 + if [ -n "$itrr_mask" ]; then + itrr_mask_decimal=$(i2c_tools_value_decimal "$itrr_mask") || return 3 + elif [ "$itrr_mode" = "w" ]; then + itrr_mask_decimal=65535 + else + itrr_mask_decimal=255 + fi + if [ $((itrr_actual_decimal & itrr_mask_decimal)) -ne $((itrr_expected_decimal & itrr_mask_decimal)) ]; then + log_fail "[I2C-READ] adapter=i2c-$itrr_bus address=$itrr_address register=$itrr_register mask=$itrr_mask_decimal expected=$itrr_expected observed=$I2C_TOOLS_LAST_READ artifact=$itrr_log" + return 1 + fi + fi + + log_info "[I2C-READ] adapter=i2c-$itrr_bus address=$itrr_address register=$itrr_register mode=$itrr_mode value=$I2C_TOOLS_LAST_READ artifact=$itrr_log" + return 0 +} + +I2C_TOOLS_RESTORE_PENDING=0 +I2C_TOOLS_RESTORE_ADAPTER="" +I2C_TOOLS_RESTORE_ADDRESS="" +I2C_TOOLS_RESTORE_REGISTER="" +I2C_TOOLS_RESTORE_VALUE="" +I2C_TOOLS_RESTORE_MODE="" +I2C_TOOLS_RESTORE_TIMEOUT="" +I2C_TOOLS_RESTORE_RESULT_DIR="" + +# i2c_tools_restore_pending_write +# Restores the original register value after an interrupted transactional write. +i2c_tools_restore_pending_write() { + [ "$I2C_TOOLS_RESTORE_PENDING" -eq 1 ] || return 0 + if ! i2c_tools_write_register_value \ + "$I2C_TOOLS_RESTORE_ADAPTER" \ + "$I2C_TOOLS_RESTORE_ADDRESS" \ + "$I2C_TOOLS_RESTORE_REGISTER" \ + "$I2C_TOOLS_RESTORE_VALUE" \ + "$I2C_TOOLS_RESTORE_MODE" \ + "$I2C_TOOLS_RESTORE_TIMEOUT" \ + "$I2C_TOOLS_RESTORE_RESULT_DIR/i2cset_emergency_restore.log"; then + return 1 + fi + if ! i2c_tools_read_register \ + "$I2C_TOOLS_RESTORE_ADAPTER" \ + "$I2C_TOOLS_RESTORE_ADDRESS" \ + "$I2C_TOOLS_RESTORE_REGISTER" \ + "$I2C_TOOLS_RESTORE_MODE" \ + "$I2C_TOOLS_RESTORE_TIMEOUT" \ + "$I2C_TOOLS_RESTORE_RESULT_DIR" \ + "$I2C_TOOLS_RESTORE_VALUE"; then + return 1 + fi + I2C_TOOLS_RESTORE_PENDING=0 + return 0 +} + +# i2c_tools_write_register_value
+# Writes one validated register value for transactional helper use. +i2c_tools_write_register_value() { + itwrv_adapter="$1" + itwrv_address="$2" + itwrv_register="$3" + itwrv_value="$4" + itwrv_mode="$5" + itwrv_timeout="$6" + itwrv_log="$7" + + case "$itwrv_mode" in + b|w) + ;; + *) + return 3 + ;; + esac + case "$itwrv_timeout" in + ''|*[!0-9]*|0) + return 3 + ;; + esac + i2c_tools_value_in_range "$itwrv_address" 3 119 || return 3 + i2c_tools_value_in_range "$itwrv_register" 0 255 || return 3 + if [ "$itwrv_mode" = "w" ]; then + i2c_tools_value_in_range "$itwrv_value" 0 65535 || return 3 + else + i2c_tools_value_in_range "$itwrv_value" 0 255 || return 3 + fi + command -v i2cset >/dev/null 2>&1 || return 2 + itwrv_bus=$(i2c_tools_adapter_number "$itwrv_adapter") || return $? + + run_with_timeout_log \ + "$itwrv_timeout" \ + "$itwrv_log" \ + i2cset -y "$itwrv_bus" "$itwrv_address" "$itwrv_register" "$itwrv_value" "$itwrv_mode" +} + +# i2c_tools_write_restore_register
+# Writes, reads back, restores, and verifies one explicitly selected register. +i2c_tools_write_restore_register() { + itwrr_adapter="$1" + itwrr_address="$2" + itwrr_register="$3" + itwrr_value="$4" + itwrr_mode="$5" + itwrr_timeout="$6" + itwrr_result_dir="$7" + + [ -n "$itwrr_result_dir" ] || return 3 + command -v i2cget >/dev/null 2>&1 || return 2 + command -v i2cset >/dev/null 2>&1 || return 2 + mkdir -p "$itwrr_result_dir" || return 1 + + i2c_tools_read_register \ + "$itwrr_adapter" "$itwrr_address" "$itwrr_register" \ + "$itwrr_mode" "$itwrr_timeout" "$itwrr_result_dir" + itwrr_read_status=$? + [ "$itwrr_read_status" -eq 0 ] || return "$itwrr_read_status" + itwrr_original=$I2C_TOOLS_LAST_READ + itwrr_original_decimal=$(i2c_tools_value_decimal "$itwrr_original") || return 1 + itwrr_requested_decimal=$(i2c_tools_value_decimal "$itwrr_value") || return 3 + if [ "$itwrr_original_decimal" -eq "$itwrr_requested_decimal" ]; then + log_fail "[I2C-WRITE] address=$itwrr_address register=$itwrr_register expected=different-test-value observed=request-equals-original-$itwrr_original" + return 3 + fi + + I2C_TOOLS_RESTORE_PENDING=1 + I2C_TOOLS_RESTORE_ADAPTER=$itwrr_adapter + I2C_TOOLS_RESTORE_ADDRESS=$itwrr_address + I2C_TOOLS_RESTORE_REGISTER=$itwrr_register + I2C_TOOLS_RESTORE_VALUE=$itwrr_original + I2C_TOOLS_RESTORE_MODE=$itwrr_mode + I2C_TOOLS_RESTORE_TIMEOUT=$itwrr_timeout + I2C_TOOLS_RESTORE_RESULT_DIR=$itwrr_result_dir + + itwrr_write_log="$itwrr_result_dir/i2cset_test_write.log" + if ! i2c_tools_write_register_value \ + "$itwrr_adapter" "$itwrr_address" "$itwrr_register" \ + "$itwrr_value" "$itwrr_mode" "$itwrr_timeout" "$itwrr_write_log"; then + log_file_with_label "I2C-WRITE" "$itwrr_write_log" + if i2c_tools_restore_pending_write; then + log_info "[I2C-RESTORE] original value restored after test-write failure" + else + log_fail "[I2C-RESTORE] expected=$itwrr_original observed=emergency-restore-failed" + fi + return 1 + fi + + if ! i2c_tools_read_register \ + "$itwrr_adapter" "$itwrr_address" "$itwrr_register" \ + "$itwrr_mode" "$itwrr_timeout" "$itwrr_result_dir" \ + "$itwrr_value"; then + if i2c_tools_restore_pending_write; then + log_info "[I2C-RESTORE] original value restored after read-back failure" + else + log_fail "[I2C-RESTORE] expected=$itwrr_original observed=emergency-restore-failed" + fi + return 1 + fi + + itwrr_restore_log="$itwrr_result_dir/i2cset_restore.log" + if ! i2c_tools_write_register_value \ + "$itwrr_adapter" "$itwrr_address" "$itwrr_register" \ + "$itwrr_original" "$itwrr_mode" "$itwrr_timeout" "$itwrr_restore_log"; then + log_file_with_label "I2C-RESTORE" "$itwrr_restore_log" + log_fail "[I2C-RESTORE] address=$itwrr_address register=$itwrr_register expected=$itwrr_original observed=restore-write-failed artifact=$itwrr_restore_log" + return 1 + fi + if ! i2c_tools_read_register \ + "$itwrr_adapter" "$itwrr_address" "$itwrr_register" \ + "$itwrr_mode" "$itwrr_timeout" "$itwrr_result_dir" \ + "$itwrr_original"; then + log_fail "[I2C-RESTORE] address=$itwrr_address register=$itwrr_register expected=$itwrr_original observed=restore-verification-failed" + return 1 + fi + + I2C_TOOLS_RESTORE_PENDING=0 + log_info "[I2C-WRITE] adapter=$itwrr_adapter address=$itwrr_address register=$itwrr_register test_value=$itwrr_value original=$itwrr_original state=write-readback-restored" + return 0 +} + ############################################################################### # i2c_run_legacy_test # Runs the image-provided i2c-msm-test compatibility path and verifies both its diff --git a/Runner/utils/lib_pkg_provider.sh b/Runner/utils/lib_pkg_provider.sh index 2b76102d..9033b9a0 100755 --- a/Runner/utils/lib_pkg_provider.sh +++ b/Runner/utils/lib_pkg_provider.sh @@ -518,15 +518,14 @@ pkg_overlay_requested_from_args() { [ "$overlay_requested" -eq 1 ] } -# Return success when optional overlay package-set recovery is applicable. +# Return success when host-distro package recovery is applicable. # -# Yocto/qcom-distro and other embedded images must not regress. They should keep -# their existing image-provided packages and skip optional overlay package-set -# recovery unless explicitly supported later. -pkg_optional_package_set_supported_os() { - optional_os_id="$1" +# Yocto/qcom-distro and other embedded images must not regress. They keep their +# image-provided packages unless an exact OS-specific policy is added later. +pkg_package_recovery_supported_os() { + recovery_os_id="$1" - case "$optional_os_id" in + case "$recovery_os_id" in debian|ubuntu|centos) return 0 ;; @@ -536,6 +535,11 @@ pkg_optional_package_set_supported_os() { esac } +# Return success when optional overlay package-set recovery is applicable. +pkg_optional_package_set_supported_os() { + pkg_package_recovery_supported_os "$1" +} + # Ensure an optional package-set only when overlay mode is explicitly requested. # # This is used by tests that support both: @@ -2317,6 +2321,35 @@ pkg_ensure_required_package_set_present() { pkg_verify_package_set_installed "$perps_set" } +# Ensure a mapped required package set on supported general-purpose distros. +# +# Return values: +# 0 - package set is ready +# 1 - mapped package recovery failed +# 2 - OS is image-managed or no exact OS mapping exists +pkg_ensure_host_distro_package_set_present() { + pehdps_set="$1" + pehdps_os_id="$(pkg_detect_os_id)" + pehdps_map_file="$(pkg_resolve_path "$PKG_PACKAGE_MAP")" + + [ -n "$pehdps_set" ] || return 1 + + if ! pkg_package_recovery_supported_os "$pehdps_os_id"; then + pkg_log_info "Package-set recovery is not enabled for image-managed OS, set=$pehdps_set os=$pehdps_os_id" + return 2 + fi + + if [ ! -r "$pehdps_map_file" ] || + ! pkg_lookup_key_in_map \ + "$pehdps_map_file" \ + "${pehdps_os_id}:package-set:${pehdps_set}" >/dev/null 2>&1; then + pkg_log_info "Package-set recovery skipped, no exact OS mapping for set=$pehdps_set os=$pehdps_os_id" + return 2 + fi + + pkg_ensure_required_package_set_present "$pehdps_set" +} + # Avoid package-manager/network work when an optional package set is complete. pkg_ensure_optional_package_set_present() { peops_set="$1" @@ -2374,4 +2407,3 @@ pkg_package_has_file_matching() { return 1 } -