From 70f0b25fe667a4ab8e005d2bebf4e9b36f533927 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Fri, 11 Sep 2026 23:48:13 +0530 Subject: [PATCH 1/5] utils: add shared bus validation helpers Provide capability-driven discovery, binding diagnostics, temporary evidence handling, and guarded UART, SPI, and CAN functional primitives for focused bus suites. Signed-off-by: Srikanth Muppandam --- Runner/utils/lib_bus.sh | 1662 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1662 insertions(+) create mode 100755 Runner/utils/lib_bus.sh diff --git a/Runner/utils/lib_bus.sh b/Runner/utils/lib_bus.sh new file mode 100755 index 00000000..75e8e069 --- /dev/null +++ b/Runner/utils/lib_bus.sh @@ -0,0 +1,1662 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# bus_validation_require_commands ... +# Verifies image-provided commands without invoking package recovery. +bus_validation_require_commands() { + birc_missing="" + + for birc_command in "$@"; do + if ! command -v "$birc_command" >/dev/null 2>&1; then + birc_missing="$birc_missing $birc_command" + fi + done + + if [ -n "$birc_missing" ]; then + log_warn "Required image-provided commands are unavailable:$birc_missing" + return 1 + fi + + return 0 +} + +# bus_validation_bool_true +# Returns success only for explicit affirmative fixture opt-in values. +bus_validation_bool_true() { + case "$1" in + 1|true|TRUE|yes|YES|on|ON) + return 0 + ;; + *) + return 1 + ;; + esac +} + +# bus_validation_ancestor_driver_name +# Prints the nearest bound ancestor driver for class devices such as TTYs. +bus_validation_ancestor_driver_name() { + bidn_object="$1" + [ -n "$bidn_object" ] || return 3 + + bidn_current=$(readlink -f "$bidn_object" 2>/dev/null || true) + [ -n "$bidn_current" ] || bidn_current="$bidn_object" + + while [ "$bidn_current" != "/" ] && [ -n "$bidn_current" ]; do + if [ -L "$bidn_current/driver" ]; then + bidn_driver=$(readlink -f "$bidn_current/driver" 2>/dev/null || true) + if [ -n "$bidn_driver" ]; then + bidn_driver_name=$(basename "$bidn_driver") + case "$bidn_driver_name" in + port) + # serial-core exposes a wrapper on the TTY port. + # Continue upward to report the hardware controller. + ;; + *) + printf '%s\n' "$bidn_driver_name" + return 0 + ;; + esac + fi + fi + bidn_parent=$(dirname "$bidn_current") + [ "$bidn_parent" != "$bidn_current" ] || break + bidn_current="$bidn_parent" + done + + return 1 +} + +# bus_validation_bound_driver_name +# Prints only the driver bound directly to the resolved bus or platform device. +bus_validation_bound_driver_name() { + bibdn_object="$1" + [ -n "$bibdn_object" ] || return 3 + + bibdn_resolved=$(readlink -f "$bibdn_object" 2>/dev/null || true) + [ -n "$bibdn_resolved" ] || bibdn_resolved="$bibdn_object" + [ -L "$bibdn_resolved/driver" ] || return 1 + bibdn_driver=$(readlink -f "$bibdn_resolved/driver" 2>/dev/null || true) + [ -n "$bibdn_driver" ] || return 1 + basename "$bibdn_driver" +} + +# bus_validation_of_node +# Prints the nearest resolved runtime device-tree node for a sysfs object. +bus_validation_of_node() { + bion_object="$1" + [ -n "$bion_object" ] || return 3 + + bion_current=$(readlink -f "$bion_object" 2>/dev/null || true) + [ -n "$bion_current" ] || bion_current="$bion_object" + + while [ "$bion_current" != "/" ] && [ -n "$bion_current" ]; do + if [ -L "$bion_current/of_node" ]; then + bion_node=$(readlink -f "$bion_current/of_node" 2>/dev/null || true) + if [ -n "$bion_node" ]; then + printf '%s\n' "$bion_node" + return 0 + fi + fi + bion_parent=$(dirname "$bion_current") + [ "$bion_parent" != "$bion_current" ] || break + bion_current="$bion_parent" + done + + return 1 +} + +# bus_validation_device_modalias +# Prints modalias evidence owned by the exact runtime device without requiring udev. +bus_validation_device_modalias() { + bim_object="$1" + [ -n "$bim_object" ] || return 3 + + bim_resolved=$(readlink -f "$bim_object" 2>/dev/null || true) + [ -n "$bim_resolved" ] || bim_resolved="$bim_object" + if [ -r "$bim_resolved/modalias" ]; then + tr -d '[:space:]' <"$bim_resolved/modalias" + return 0 + fi + if [ -r "$bim_resolved/uevent" ]; then + bim_value=$(awk -F= '$1 == "MODALIAS" { print $2; exit }' "$bim_resolved/uevent") + if [ -n "$bim_value" ]; then + printf '%s\n' "$bim_value" + return 0 + fi + fi + + return 1 +} + +# bus_validation_module_candidates +# Prints module aliases when modprobe metadata is available in the image. +bus_validation_module_candidates() { + bimc_modalias="$1" + [ -n "$bimc_modalias" ] || return 3 + command -v modprobe >/dev/null 2>&1 || return 2 + + modprobe -R "$bimc_modalias" 2>/dev/null | + awk 'NF && !seen[$0]++ { printf "%s%s", separator, $0; separator="," } END { if (separator != "") print "" }' +} + +# bus_validation_module_origins +# Prints module:origin pairs, distinguishing built-in, loaded, and file-backed support. +bus_validation_module_origins() { + bimo_modules="$1" + [ -n "$bimo_modules" ] || return 3 + command -v modinfo >/dev/null 2>&1 || return 2 + + bimo_output="" + bimo_remaining_modules=$bimo_modules + while [ -n "$bimo_remaining_modules" ]; do + case "$bimo_remaining_modules" in + *,*) + bimo_module=${bimo_remaining_modules%%,*} + bimo_remaining_modules=${bimo_remaining_modules#*,} + ;; + *) + bimo_module=$bimo_remaining_modules + bimo_remaining_modules="" + ;; + esac + [ -n "$bimo_module" ] || continue + bimo_origin=$(modinfo -F filename "$bimo_module" 2>/dev/null || true) + if [ -z "$bimo_origin" ] && [ -d "/sys/module/$bimo_module" ]; then + bimo_origin=loaded + fi + [ -n "$bimo_origin" ] || bimo_origin=unknown + bimo_output="${bimo_output}${bimo_output:+,}$bimo_module:$bimo_origin" + done + + [ -n "$bimo_output" ] || return 1 + printf '%s\n' "$bimo_output" +} + +# bus_validation_capture_driver_names +# Retains registered driver names without following driver-directory contents. +bus_validation_capture_driver_names() { + bicdn_directory="$1" + bicdn_output="$2" + [ -n "$bicdn_directory" ] && [ -n "$bicdn_output" ] || return 3 + + : >"$bicdn_output" + [ -d "$bicdn_directory" ] || return 1 + for bicdn_driver in "$bicdn_directory"/*; do + [ -e "$bicdn_driver" ] || continue + basename "$bicdn_driver" + done | sort -u >"$bicdn_output" + + [ -s "$bicdn_output" ] +} + +# bus_validation_waiting_for_supplier +# Prints the nearest waiting_for_supplier value, or unknown when not exposed. +bus_validation_waiting_for_supplier() { + biwfs_object="$1" + [ -n "$biwfs_object" ] || return 3 + + biwfs_current=$(readlink -f "$biwfs_object" 2>/dev/null || true) + [ -n "$biwfs_current" ] || biwfs_current="$biwfs_object" + + while [ "$biwfs_current" != "/" ] && [ -n "$biwfs_current" ]; do + if [ -r "$biwfs_current/waiting_for_supplier" ]; then + tr -d '[:space:]' <"$biwfs_current/waiting_for_supplier" + return 0 + fi + biwfs_parent=$(dirname "$biwfs_current") + [ "$biwfs_parent" != "$biwfs_current" ] || break + biwfs_current="$biwfs_parent" + done + + printf '%s\n' unknown + return 1 +} + +# bus_validation_find_runtime_by_of_node ... +# Prints the first runtime object whose nearest of_node matches the DT node. +bus_validation_find_runtime_by_of_node() { + bifr_node="$1" + shift + [ -n "$bifr_node" ] || return 3 + + bifr_target=$(readlink -f "$bifr_node" 2>/dev/null || true) + [ -n "$bifr_target" ] || bifr_target="$bifr_node" + + for bifr_pattern in "$@"; do + for bifr_object in $bifr_pattern; do + [ -e "$bifr_object" ] || continue + bifr_runtime_node=$(bus_validation_of_node "$bifr_object" 2>/dev/null || true) + if [ "$bifr_runtime_node" = "$bifr_target" ]; then + readlink -f "$bifr_object" 2>/dev/null || printf '%s\n' "$bifr_object" + return 0 + fi + done + done + + return 1 +} + +# bus_validation_dt_nodes +# Lists enabled DT nodes whose basename matches the supplied extended regex. +bus_validation_dt_nodes() { + bidn_root="$1" + bidn_regex="$2" + bidn_output="$3" + [ -d "$bidn_root" ] && [ -n "$bidn_regex" ] && [ -n "$bidn_output" ] || return 3 + + : >"$bidn_output" + find "$bidn_root" -type d 2>/dev/null | + while IFS= read -r bidn_node; do + bidn_name=$(basename "$bidn_node") + printf '%s\n' "$bidn_name" | grep -Eq "$bidn_regex" || continue + dt_node_enabled "$bidn_node" || continue + printf '%s\n' "$bidn_node" + done | + sort -u >"$bidn_output" + + [ -s "$bidn_output" ] +} + +# uart_validate_runtime +# Correlates enabled UART DT nodes with platform drivers, TTYs, and serdev consumers. +uart_validate_runtime() { + ucri_result_dir="$1" + [ -n "$ucri_result_dir" ] || return 3 + mkdir -p "$ucri_result_dir" || return 1 + + ucri_evidence="$ucri_result_dir/uart_evidence.tsv" + ucri_dt_nodes="$ucri_result_dir/uart_dt_nodes.log" + ucri_ttys="$ucri_result_dir/uart_ttys.tsv" + ucri_dt_root=$(dt_runtime_root 2>/dev/null || true) + ucri_tty_root="${UART_SYS_CLASS_TTY_ROOT:-/sys/class/tty}" + ucri_serial_roots="${UART_SYS_BUS_SERIAL_ROOTS:-/sys/bus/serial /sys/bus/serial-base}" + ucri_registered_drivers="$ucri_result_dir/uart_registered_drivers.log" + ucri_failures=0 + ucri_dt_count=0 + ucri_runtime_count=0 + + printf 'kind\tobject\tdriver\tof_node\tconsumer\tdetails\n' >"$ucri_evidence" + printf 'tty\tdriver\tof_node\tconsole\tdevice\n' >"$ucri_ttys" + : >"$ucri_dt_nodes" + : >"$ucri_registered_drivers" + + for ucri_serial_root in $ucri_serial_roots; do + bus_validation_capture_driver_names "$ucri_serial_root/drivers" "$ucri_result_dir/.uart_drivers.tmp" || true + if [ -s "$ucri_result_dir/.uart_drivers.tmp" ]; then + cat "$ucri_result_dir/.uart_drivers.tmp" >>"$ucri_registered_drivers" + fi + done + if sort -u "$ucri_registered_drivers" >"$ucri_result_dir/.uart_drivers.sorted"; then + mv "$ucri_result_dir/.uart_drivers.sorted" "$ucri_registered_drivers" + else + rm -f "$ucri_result_dir/.uart_drivers.sorted" + fi + rm -f "$ucri_result_dir/.uart_drivers.tmp" + + if [ -d "$ucri_tty_root" ]; then + for ucri_tty_path in "$ucri_tty_root"/*; do + [ -e "$ucri_tty_path" ] || continue + [ -e "$ucri_tty_path/device" ] || continue + ucri_tty=$(basename "$ucri_tty_path") + ucri_device=$(readlink -f "$ucri_tty_path/device" 2>/dev/null || true) + case "$ucri_device" in + /sys/devices/virtual/*) + continue + ;; + esac + ucri_driver=$(bus_validation_ancestor_driver_name "$ucri_tty_path/device" 2>/dev/null || true) + [ -n "$ucri_driver" ] || continue + ucri_devnode="${UART_DEV_ROOT:-/dev}/$ucri_tty" + ucri_devnode_state=missing + if [ -c "$ucri_devnode" ]; then + ucri_devnode_state=character-device + fi + ucri_of_node=$(bus_validation_of_node "$ucri_tty_path/device" 2>/dev/null || true) + ucri_console=no + if [ -r /proc/consoles ] && awk -v tty="$ucri_tty" '$1 == tty { found=1 } END { exit !found }' /proc/consoles; then + ucri_console=yes + fi + printf '%s\t%s\t%s\t%s\t%s\n' \ + "$ucri_tty" "$ucri_driver" "${ucri_of_node:-unknown}" "$ucri_console" "${ucri_device:-unknown}" >>"$ucri_ttys" + log_info "[UART-TTY] tty=$ucri_tty driver=$ucri_driver devnode=$ucri_devnode_state console=$ucri_console of_node=${ucri_of_node:-unknown}" + ucri_runtime_count=$((ucri_runtime_count + 1)) + if [ "$ucri_devnode_state" != "character-device" ]; then + log_fail "[UART-FAIL] object=$ucri_tty expected=character-device observed=$ucri_devnode_state path=$ucri_devnode artifact=$ucri_evidence" + ucri_failures=$((ucri_failures + 1)) + fi + done + fi + + if [ -n "$ucri_dt_root" ]; then + bus_validation_dt_nodes "$ucri_dt_root" '^(serial|uart)@' "$ucri_dt_nodes" || true + fi + + while IFS= read -r ucri_node; do + [ -n "$ucri_node" ] || continue + ucri_dt_count=$((ucri_dt_count + 1)) + ucri_compatible=$(dt_property_text "$ucri_node" compatible 2>/dev/null || printf '%s\n' unknown) + ucri_device_dir=$(find_platform_device_for_dt_node "$ucri_node" 2>/dev/null || true) + ucri_driver="" + if [ -n "$ucri_device_dir" ]; then + ucri_driver=$(platform_device_driver_name "$ucri_device_dir" 2>/dev/null || true) + fi + + ucri_tty_names=$(awk -F '\t' -v node="$ucri_node" 'NR > 1 && $3 == node { printf "%s%s", separator, $1; separator="," } END { if (separator != "") print "" }' "$ucri_ttys") + ucri_serdev_names="" + ucri_serdev_bound=0 + for ucri_serial_root in $ucri_serial_roots; do + if [ -d "$ucri_serial_root/devices" ]; then + for ucri_serdev in "$ucri_serial_root"/devices/*; do + [ -e "$ucri_serdev" ] || continue + ucri_serdev_node=$(bus_validation_of_node "$ucri_serdev" 2>/dev/null || true) + case "$ucri_serdev_node" in + "$ucri_node"/*) + ucri_serdev_name=$(basename "$ucri_serdev") + ucri_serdev_driver=$(bus_validation_bound_driver_name "$ucri_serdev" 2>/dev/null || true) + ucri_serdev_names="${ucri_serdev_names}${ucri_serdev_names:+,}$ucri_serdev_name:${ucri_serdev_driver:-unbound}" + if [ -n "$ucri_serdev_driver" ]; then + ucri_serdev_bound=$((ucri_serdev_bound + 1)) + fi + ;; + esac + done + fi + done + + log_info "[UART-DT] node=$ucri_node compatible=$ucri_compatible device=${ucri_device_dir:-missing} driver=${ucri_driver:-unbound} ttys=${ucri_tty_names:-none} serdev=${ucri_serdev_names:-none}" + printf 'controller\t%s\t%s\t%s\t%s\tcompatible=%s serdev=%s\n' \ + "$ucri_node" "${ucri_driver:-unbound}" "$ucri_node" \ + "${ucri_tty_names:-${ucri_serdev_names:-none}}" "$ucri_compatible" "${ucri_serdev_names:-none}" >>"$ucri_evidence" + + if [ -z "$ucri_device_dir" ]; then + log_fail "[UART-FAIL] object=$ucri_node expected=runtime-platform-device observed=missing artifact=$ucri_evidence" + ucri_failures=$((ucri_failures + 1)) + elif [ -z "$ucri_driver" ]; then + ucri_waiting=$(bus_validation_waiting_for_supplier "$ucri_device_dir" 2>/dev/null || true) + ucri_modalias=$(bus_validation_device_modalias "$ucri_device_dir" 2>/dev/null || true) + ucri_candidates=$(bus_validation_module_candidates "$ucri_modalias" 2>/dev/null || true) + ucri_origins=$(bus_validation_module_origins "$ucri_candidates" 2>/dev/null || true) + log_fail "[UART-FAIL] object=$(basename "$ucri_device_dir") expected=bound-controller-driver observed=unbound waiting_for_supplier=${ucri_waiting:-unknown} modalias=${ucri_modalias:-unknown} candidates=${ucri_candidates:-unresolved} origins=${ucri_origins:-unknown} evidence=$ucri_evidence registered_drivers=$ucri_registered_drivers" + ucri_failures=$((ucri_failures + 1)) + elif [ -z "$ucri_tty_names" ] && [ "$ucri_serdev_bound" -eq 0 ]; then + log_fail "[UART-FAIL] object=$(basename "$ucri_device_dir") expected=TTY-or-bound-serdev-consumer observed=none driver=$ucri_driver artifact=$ucri_evidence" + ucri_failures=$((ucri_failures + 1)) + fi + done <"$ucri_dt_nodes" + + log_info "UART runtime summary: dt_controllers=$ucri_dt_count physical_ttys=$ucri_runtime_count failures=$ucri_failures artifact=$ucri_evidence" + + if [ "$ucri_dt_count" -eq 0 ] && [ "$ucri_runtime_count" -eq 0 ]; then + return 2 + fi + [ "$ucri_failures" -eq 0 ] || return 1 + return 0 +} + +# spi_validate_runtime +# Correlates enabled SPI controllers and children with masters, devices, and drivers. +spi_validate_runtime() { + scri_result_dir="$1" + [ -n "$scri_result_dir" ] || return 3 + mkdir -p "$scri_result_dir" || return 1 + + scri_evidence="$scri_result_dir/spi_evidence.tsv" + scri_dt_nodes="$scri_result_dir/spi_dt_controllers.log" + scri_dt_root=$(dt_runtime_root 2>/dev/null || true) + scri_master_root="${SPI_SYS_CLASS_MASTER_ROOT:-/sys/class/spi_master}" + scri_bus_root="${SPI_SYS_BUS_ROOT:-/sys/bus/spi}" + scri_failures=0 + scri_controller_count=0 + scri_master_count=0 + scri_device_count=0 + scri_registered_drivers="$scri_result_dir/spi_registered_drivers.log" + + printf 'kind\tobject\tdriver\tof_node\tmodalias\tfrequency_hex\tmode\terrors\ttimedout\truntime_pm\twaiting_for_supplier\n' >"$scri_evidence" + : >"$scri_dt_nodes" + bus_validation_capture_driver_names "$scri_bus_root/drivers" "$scri_registered_drivers" || true + + if [ -n "$scri_dt_root" ]; then + bus_validation_dt_nodes "$scri_dt_root" '^(spi|qspi)@' "$scri_dt_nodes" || true + fi + + if [ -d "$scri_master_root" ]; then + for scri_master in "$scri_master_root"/spi*; do + [ -e "$scri_master" ] || continue + scri_master_name=$(basename "$scri_master") + scri_driver=$(bus_validation_bound_driver_name "$scri_master/device" 2>/dev/null || true) + scri_node=$(bus_validation_of_node "$scri_master/device" 2>/dev/null || true) + scri_errors=$(cat "$scri_master/statistics/errors" 2>/dev/null || printf '%s\n' unavailable) + scri_timeouts=$(cat "$scri_master/statistics/timedout" 2>/dev/null || printf '%s\n' unavailable) + scri_runtime_pm=$(cat "$scri_master/device/power/runtime_status" 2>/dev/null || printf '%s\n' unavailable) + log_info "[SPI-MASTER] master=$scri_master_name driver=${scri_driver:-unbound} runtime_pm=$scri_runtime_pm errors=$scri_errors timedout=$scri_timeouts of_node=${scri_node:-unknown}" + printf 'master\t%s\t%s\t%s\t-\t-\t-\t%s\t%s\t%s\t-\n' \ + "$scri_master_name" "${scri_driver:-unbound}" "${scri_node:-unknown}" \ + "$scri_errors" "$scri_timeouts" "$scri_runtime_pm" >>"$scri_evidence" + scri_master_count=$((scri_master_count + 1)) + if [ -z "$scri_driver" ]; then + log_fail "[SPI-FAIL] object=$scri_master_name expected=bound-master-driver observed=unbound artifact=$scri_evidence" + scri_failures=$((scri_failures + 1)) + fi + done + fi + + while IFS= read -r scri_controller; do + [ -n "$scri_controller" ] || continue + scri_controller_count=$((scri_controller_count + 1)) + scri_compatible=$(dt_property_text "$scri_controller" compatible 2>/dev/null || printf '%s\n' unknown) + scri_master_match=$(bus_validation_find_runtime_by_of_node \ + "$scri_controller" "$scri_master_root/spi*" 2>/dev/null || true) + log_info "[SPI-DT] controller=$scri_controller compatible=$scri_compatible master=${scri_master_match:-missing}" + if [ -z "$scri_master_match" ]; then + scri_platform=$(find_platform_device_for_dt_node "$scri_controller" 2>/dev/null || true) + scri_platform_driver="" + if [ -n "$scri_platform" ]; then + scri_platform_driver=$(platform_device_driver_name "$scri_platform" 2>/dev/null || true) + fi + log_fail "[SPI-FAIL] object=$scri_controller expected=runtime-spi-master observed=missing platform_device=${scri_platform:-missing} driver=${scri_platform_driver:-unbound} artifact=$scri_evidence" + scri_failures=$((scri_failures + 1)) + fi + done <"$scri_dt_nodes" + + if [ -d "$scri_bus_root/devices" ]; then + for scri_device in "$scri_bus_root"/devices/spi*.*; do + [ -e "$scri_device" ] || continue + scri_device_name=$(basename "$scri_device") + scri_driver=$(bus_validation_bound_driver_name "$scri_device" 2>/dev/null || true) + scri_node=$(bus_validation_of_node "$scri_device" 2>/dev/null || true) + scri_modalias=$(bus_validation_device_modalias "$scri_device" 2>/dev/null || true) + scri_frequency=unknown + scri_mode=unknown + if [ -n "$scri_node" ] && [ -r "$scri_node/spi-max-frequency" ]; then + scri_frequency=$(dt_property_hex "$scri_node" spi-max-frequency 2>/dev/null || printf '%s\n' unknown) + fi + if [ -n "$scri_node" ]; then + scri_mode=mode0 + [ -e "$scri_node/spi-cpol" ] && scri_mode="${scri_mode},cpol" + [ -e "$scri_node/spi-cpha" ] && scri_mode="${scri_mode},cpha" + [ -e "$scri_node/spi-cs-high" ] && scri_mode="${scri_mode},cs-high" + [ -e "$scri_node/spi-lsb-first" ] && scri_mode="${scri_mode},lsb-first" + [ -e "$scri_node/spi-3wire" ] && scri_mode="${scri_mode},3wire" + fi + scri_waiting=$(bus_validation_waiting_for_supplier "$scri_device" 2>/dev/null || true) + scri_errors=$(cat "$scri_device/statistics/errors" 2>/dev/null || printf '%s\n' unavailable) + scri_timeouts=$(cat "$scri_device/statistics/timedout" 2>/dev/null || printf '%s\n' unavailable) + scri_runtime_pm=$(cat "$scri_device/power/runtime_status" 2>/dev/null || printf '%s\n' unavailable) + log_info "[SPI-DEVICE] device=$scri_device_name driver=${scri_driver:-unbound} modalias=${scri_modalias:-unknown} max_frequency_hex=$scri_frequency mode=$scri_mode runtime_pm=$scri_runtime_pm errors=$scri_errors timedout=$scri_timeouts waiting_for_supplier=${scri_waiting:-unknown} of_node=${scri_node:-unknown}" + printf 'device\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$scri_device_name" "${scri_driver:-unbound}" "${scri_node:-unknown}" \ + "${scri_modalias:-unknown}" "$scri_frequency" "$scri_mode" \ + "$scri_errors" "$scri_timeouts" "$scri_runtime_pm" \ + "${scri_waiting:-unknown}" >>"$scri_evidence" + scri_device_count=$((scri_device_count + 1)) + + if [ -z "$scri_driver" ]; then + scri_candidates=$(bus_validation_module_candidates "$scri_modalias" 2>/dev/null || true) + scri_origins=$(bus_validation_module_origins "$scri_candidates" 2>/dev/null || true) + log_fail "[SPI-FAIL] object=$scri_device_name expected=bound-client-driver observed=unbound waiting_for_supplier=${scri_waiting:-unknown} modalias=${scri_modalias:-unknown} candidates=${scri_candidates:-unresolved} origins=${scri_origins:-unknown} evidence=$scri_evidence registered_drivers=$scri_registered_drivers" + scri_failures=$((scri_failures + 1)) + fi + done + fi + + while IFS= read -r scri_controller; do + [ -n "$scri_controller" ] || continue + for scri_child in "$scri_controller"/*; do + [ -d "$scri_child" ] || continue + [ -r "$scri_child/reg" ] || continue + dt_node_enabled "$scri_child" || continue + scri_runtime_child=$(bus_validation_find_runtime_by_of_node \ + "$scri_child" "$scri_bus_root/devices/spi*.*" 2>/dev/null || true) + if [ -z "$scri_runtime_child" ]; then + scri_child_compatible=$(dt_property_text "$scri_child" compatible 2>/dev/null || printf '%s\n' unknown) + log_fail "[SPI-FAIL] object=$scri_child expected=runtime-spi-device observed=missing compatible=$scri_child_compatible artifact=$scri_evidence" + scri_failures=$((scri_failures + 1)) + fi + done + done <"$scri_dt_nodes" + + log_info "SPI runtime summary: dt_controllers=$scri_controller_count masters=$scri_master_count devices=$scri_device_count failures=$scri_failures artifact=$scri_evidence" + + if [ "$scri_controller_count" -eq 0 ] && [ "$scri_master_count" -eq 0 ]; then + return 2 + fi + [ "$scri_failures" -eq 0 ] || return 1 + return 0 +} + +# i3c_validate_runtime +# Validates Linux-visible I3C objects and otherwise records firmware-side evidence. +i3c_validate_runtime() { + icri_result_dir="$1" + [ -n "$icri_result_dir" ] || return 3 + mkdir -p "$icri_result_dir" || return 1 + + icri_evidence="$icri_result_dir/i3c_evidence.tsv" + icri_dt_nodes="$icri_result_dir/i3c_dt_nodes.log" + icri_remoteprocs="$icri_result_dir/i3c_remoteproc_evidence.tsv" + icri_dt_root=$(dt_runtime_root 2>/dev/null || true) + icri_bus_root="${I3C_SYS_BUS_ROOT:-/sys/bus/i3c}" + icri_class_root="${I3C_SYS_CLASS_ROOT:-/sys/class/i3c-master}" + icri_failures=0 + icri_dt_count=0 + icri_runtime_count=0 + icri_master_count=0 + icri_device_count=0 + icri_registered_drivers="$icri_result_dir/i3c_registered_drivers.log" + + printf 'kind\tobject\tdriver\tof_node\tmodalias\twaiting_for_supplier\tdetails\n' >"$icri_evidence" + printf 'remoteproc\tname\tstate\tfirmware\n' >"$icri_remoteprocs" + : >"$icri_dt_nodes" + bus_validation_capture_driver_names "$icri_bus_root/drivers" "$icri_registered_drivers" || true + + if [ -n "$icri_dt_root" ]; then + bus_validation_dt_nodes "$icri_dt_root" '^i3c@' "$icri_dt_nodes" || true + fi + + while IFS= read -r icri_node; do + [ -n "$icri_node" ] || continue + icri_dt_count=$((icri_dt_count + 1)) + done <"$icri_dt_nodes" + + if [ -d "$icri_bus_root/devices" ]; then + for icri_object in "$icri_bus_root"/devices/*; do + [ -e "$icri_object" ] || continue + icri_name=$(basename "$icri_object") + icri_node=$(bus_validation_of_node "$icri_object" 2>/dev/null || true) + icri_resolved=$(readlink -f "$icri_object" 2>/dev/null || true) + [ -n "$icri_resolved" ] || icri_resolved="$icri_object" + if [ -r "$icri_resolved/current_master" ] || [ -r "$icri_resolved/i3c_scl_frequency" ]; then + icri_parent=$(dirname "$icri_resolved") + icri_driver=$(bus_validation_bound_driver_name "$icri_parent" 2>/dev/null || true) + icri_mode=$(cat "$icri_resolved/mode" 2>/dev/null || printf '%s\n' unknown) + icri_scl=$(cat "$icri_resolved/i3c_scl_frequency" 2>/dev/null || printf '%s\n' unknown) + log_info "[I3C-MASTER] master=$icri_name controller_driver=${icri_driver:-unbound} mode=$icri_mode i3c_scl_hz=$icri_scl of_node=${icri_node:-unknown}" + printf 'master\t%s\t%s\t%s\t-\t-\tmode=%s i3c_scl_hz=%s\n' \ + "$icri_name" "${icri_driver:-unbound}" "${icri_node:-unknown}" \ + "$icri_mode" "$icri_scl" >>"$icri_evidence" + icri_master_count=$((icri_master_count + 1)) + icri_runtime_count=$((icri_runtime_count + 1)) + if [ -z "$icri_driver" ]; then + log_fail "[I3C-FAIL] object=$icri_name expected=bound-parent-controller-driver observed=unbound parent=$icri_parent evidence=$icri_evidence registered_drivers=$icri_registered_drivers" + icri_failures=$((icri_failures + 1)) + fi + continue + fi + + icri_driver=$(bus_validation_bound_driver_name "$icri_object" 2>/dev/null || true) + icri_modalias=$(bus_validation_device_modalias "$icri_object" 2>/dev/null || true) + icri_waiting=$(bus_validation_waiting_for_supplier "$icri_object" 2>/dev/null || true) + icri_pid=$(cat "$icri_resolved/pid" 2>/dev/null || printf '%s\n' unknown) + icri_dcr=$(cat "$icri_resolved/dcr" 2>/dev/null || printf '%s\n' unknown) + icri_address=$(cat "$icri_resolved/dynamic_address" 2>/dev/null || printf '%s\n' unknown) + log_info "[I3C-DEVICE] device=$icri_name driver=${icri_driver:-unbound} pid=$icri_pid dcr=$icri_dcr dynamic_address=$icri_address modalias=${icri_modalias:-unknown} waiting_for_supplier=${icri_waiting:-unknown} of_node=${icri_node:-unknown}" + printf 'device\t%s\t%s\t%s\t%s\t%s\tpid=%s dcr=%s dynamic_address=%s\n' \ + "$icri_name" "${icri_driver:-unbound}" "${icri_node:-unknown}" \ + "${icri_modalias:-unknown}" "${icri_waiting:-unknown}" \ + "$icri_pid" "$icri_dcr" "$icri_address" >>"$icri_evidence" + icri_device_count=$((icri_device_count + 1)) + icri_runtime_count=$((icri_runtime_count + 1)) + if [ -z "$icri_driver" ]; then + icri_candidates=$(bus_validation_module_candidates "$icri_modalias" 2>/dev/null || true) + icri_origins=$(bus_validation_module_origins "$icri_candidates" 2>/dev/null || true) + log_fail "[I3C-FAIL] object=$icri_name expected=bound-runtime-driver observed=unbound waiting_for_supplier=${icri_waiting:-unknown} candidates=${icri_candidates:-unresolved} origins=${icri_origins:-unknown} evidence=$icri_evidence registered_drivers=$icri_registered_drivers" + icri_failures=$((icri_failures + 1)) + fi + done + fi + + if [ -d "$icri_class_root" ]; then + for icri_master in "$icri_class_root"/*; do + [ -e "$icri_master" ] || continue + icri_master_name=$(basename "$icri_master") + log_info "[I3C-MASTER] master=$icri_master_name path=$(readlink -f "$icri_master" 2>/dev/null || printf '%s' "$icri_master")" + done + fi + + for icri_remoteproc in /sys/class/remoteproc/remoteproc*; do + [ -d "$icri_remoteproc" ] || continue + icri_rp_name=$(cat "$icri_remoteproc/name" 2>/dev/null || printf '%s\n' unknown) + printf '%s\n' "$icri_rp_name" | grep -Eqi 'adsp|slpi|ssc|sensor' || continue + icri_rp_state=$(cat "$icri_remoteproc/state" 2>/dev/null || printf '%s\n' unknown) + icri_rp_firmware=$(cat "$icri_remoteproc/firmware" 2>/dev/null || printf '%s\n' unknown) + printf '%s\t%s\t%s\t%s\n' \ + "$(basename "$icri_remoteproc")" "$icri_rp_name" "$icri_rp_state" "$icri_rp_firmware" >>"$icri_remoteprocs" + log_info "[I3C-INDIRECT] remoteproc=$(basename "$icri_remoteproc") name=$icri_rp_name state=$icri_rp_state firmware=$icri_rp_firmware" + done + + if [ "$icri_dt_count" -gt 0 ] && [ "$icri_master_count" -eq 0 ]; then + log_fail "[I3C-FAIL] object=runtime-i3c expected=Linux-runtime-master observed=none dt_nodes=$icri_dt_count artifact=$icri_evidence" + icri_failures=$((icri_failures + 1)) + fi + + log_info "I3C runtime summary: dt_controllers=$icri_dt_count masters=$icri_master_count devices=$icri_device_count failures=$icri_failures artifact=$icri_evidence indirect_evidence=$icri_remoteprocs" + + [ "$icri_failures" -eq 0 ] || return 1 + if [ "$icri_dt_count" -eq 0 ] && [ "$icri_runtime_count" -eq 0 ]; then + return 2 + fi + return 0 +} + +# can_validate_runtime +# Correlates CAN DT declarations with SocketCAN interfaces and parent drivers. +can_validate_runtime() { + ccri_result_dir="$1" + [ -n "$ccri_result_dir" ] || return 3 + mkdir -p "$ccri_result_dir" || return 1 + + ccri_evidence="$ccri_result_dir/can_evidence.tsv" + ccri_dt_nodes="$ccri_result_dir/can_dt_nodes.log" + ccri_ip_log="$ccri_result_dir/can_ip_details.log" + ccri_dt_root=$(dt_runtime_root 2>/dev/null || true) + ccri_net_root="${CAN_SYS_CLASS_NET_ROOT:-/sys/class/net}" + ccri_spi_root="${SPI_SYS_BUS_ROOT:-/sys/bus/spi}" + ccri_platform_root="${PLATFORM_SYS_BUS_ROOT:-/sys/bus/platform}" + ccri_failures=0 + ccri_dt_count=0 + ccri_interface_count=0 + ccri_registered_drivers="$ccri_result_dir/can_parent_registered_drivers.log" + + printf 'kind\tobject\tdriver\tparent\tof_node\toperstate\tcan_state\tbitrate\tdbitrate\tctrlmode\tberr_tx\tberr_rx\trx_errors\ttx_errors\n' >"$ccri_evidence" + : >"$ccri_dt_nodes" + : >"$ccri_ip_log" + bus_validation_capture_driver_names "$ccri_spi_root/drivers" "$ccri_result_dir/.can_spi_drivers.tmp" || true + bus_validation_capture_driver_names "$ccri_platform_root/drivers" "$ccri_result_dir/.can_platform_drivers.tmp" || true + cat "$ccri_result_dir/.can_spi_drivers.tmp" "$ccri_result_dir/.can_platform_drivers.tmp" 2>/dev/null | + sort -u >"$ccri_registered_drivers" + rm -f "$ccri_result_dir/.can_spi_drivers.tmp" "$ccri_result_dir/.can_platform_drivers.tmp" + + if [ -n "$ccri_dt_root" ]; then + bus_validation_dt_nodes "$ccri_dt_root" '^can(@|$)' "$ccri_dt_nodes" || true + fi + + if [ -d "$ccri_net_root" ]; then + for ccri_iface_path in "$ccri_net_root"/*; do + [ -e "$ccri_iface_path/type" ] || continue + ccri_type=$(tr -d '[:space:]' <"$ccri_iface_path/type") + [ "$ccri_type" = "280" ] || continue + ccri_iface=$(basename "$ccri_iface_path") + if [ ! -e "$ccri_iface_path/device" ]; then + log_info "[CAN-IFACE] interface=$ccri_iface kind=virtual-or-software-only action=ignored" + continue + fi + ccri_driver=$(bus_validation_bound_driver_name "$ccri_iface_path/device" 2>/dev/null || true) + ccri_parent=$(readlink -f "$ccri_iface_path/device" 2>/dev/null || true) + ccri_node=$(bus_validation_of_node "$ccri_iface_path/device" 2>/dev/null || true) + ccri_state=$(cat "$ccri_iface_path/operstate" 2>/dev/null || printf '%s\n' unknown) + ccri_rx_errors=$(cat "$ccri_iface_path/statistics/rx_errors" 2>/dev/null || printf '%s\n' unknown) + ccri_tx_errors=$(cat "$ccri_iface_path/statistics/tx_errors" 2>/dev/null || printf '%s\n' unknown) + ccri_can_state=unknown + ccri_bitrate=unknown + ccri_dbitrate=not-configured + ccri_ctrlmode=unknown + ccri_berr_tx=unknown + ccri_berr_rx=unknown + if command -v ip >/dev/null 2>&1; then + ccri_ip_tmp="$ccri_result_dir/.can_ip_$ccri_iface.log" + if ip -details -statistics link show dev "$ccri_iface" >"$ccri_ip_tmp" 2>&1; then + printf '===== %s =====\n' "$ccri_iface" >>"$ccri_ip_log" + cat "$ccri_ip_tmp" >>"$ccri_ip_log" + ccri_can_state=$(awk '/can (<[^>]*> )?state / { for (i = 1; i <= NF; i++) if ($i == "state") { print $(i + 1); exit } }' "$ccri_ip_tmp") + ccri_bitrate=$(awk '/bitrate[[:space:]]+[0-9]+/ { for (i = 1; i <= NF; i++) if ($i == "bitrate") { print $(i + 1); exit } }' "$ccri_ip_tmp") + ccri_dbitrate=$(awk '/dbitrate[[:space:]]+[0-9]+/ { for (i = 1; i <= NF; i++) if ($i == "dbitrate") { print $(i + 1); exit } }' "$ccri_ip_tmp") + ccri_ctrlmode=$(awk ' + match($0, /can <[^>]*>/) { + value = substr($0, RSTART + 5, RLENGTH - 6) + print value + exit + } + ' "$ccri_ip_tmp") + ccri_berr_tx=$(awk '/berr-counter/ { for (i = 1; i <= NF; i++) if ($i == "tx") { print $(i + 1); exit } }' "$ccri_ip_tmp" | tr -d ')') + ccri_berr_rx=$(awk '/berr-counter/ { for (i = 1; i <= NF; i++) if ($i == "rx") { print $(i + 1); exit } }' "$ccri_ip_tmp" | tr -d ')') + [ -n "$ccri_can_state" ] || ccri_can_state=unknown + [ -n "$ccri_bitrate" ] || ccri_bitrate=not-configured + [ -n "$ccri_dbitrate" ] || ccri_dbitrate=not-configured + [ -n "$ccri_ctrlmode" ] || ccri_ctrlmode=none-reported + [ -n "$ccri_berr_tx" ] || ccri_berr_tx=unknown + [ -n "$ccri_berr_rx" ] || ccri_berr_rx=unknown + else + log_warn "[CAN-IFACE] interface=$ccri_iface diagnostic=ip-details-query-failed artifact=$ccri_ip_tmp" + fi + rm -f "$ccri_ip_tmp" + fi + log_info "[CAN-IFACE] interface=$ccri_iface driver=${ccri_driver:-unbound} operstate=$ccri_state can_state=$ccri_can_state bitrate=$ccri_bitrate dbitrate=$ccri_dbitrate ctrlmode=$ccri_ctrlmode berr_tx=$ccri_berr_tx berr_rx=$ccri_berr_rx rx_errors=$ccri_rx_errors tx_errors=$ccri_tx_errors parent=${ccri_parent:-unknown} of_node=${ccri_node:-unknown}" + printf 'interface\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$ccri_iface" "${ccri_driver:-unbound}" "${ccri_parent:-unknown}" \ + "${ccri_node:-unknown}" "$ccri_state" "$ccri_can_state" "$ccri_bitrate" \ + "$ccri_dbitrate" "$ccri_ctrlmode" "$ccri_berr_tx" "$ccri_berr_rx" \ + "$ccri_rx_errors" "$ccri_tx_errors" >>"$ccri_evidence" + ccri_interface_count=$((ccri_interface_count + 1)) + if [ -z "$ccri_driver" ]; then + log_fail "[CAN-FAIL] object=$ccri_iface expected=bound-CAN-driver observed=unbound parent=${ccri_parent:-unknown} artifact=$ccri_evidence" + ccri_failures=$((ccri_failures + 1)) + fi + case "$ccri_can_state" in + BUS-OFF|ERROR-PASSIVE|ERROR-WARNING) + log_fail "[CAN-FAIL] object=$ccri_iface expected=healthy-CAN-state observed=$ccri_can_state berr_tx=$ccri_berr_tx berr_rx=$ccri_berr_rx artifact=$ccri_ip_log" + ccri_failures=$((ccri_failures + 1)) + ;; + esac + done + fi + + while IFS= read -r ccri_node; do + [ -n "$ccri_node" ] || continue + ccri_dt_count=$((ccri_dt_count + 1)) + ccri_compatible=$(dt_property_text "$ccri_node" compatible 2>/dev/null || printf '%s\n' unknown) + ccri_runtime=$(bus_validation_find_runtime_by_of_node \ + "$ccri_node" "$ccri_spi_root/devices/*" "$ccri_platform_root/devices/*" 2>/dev/null || true) + ccri_driver="" + ccri_netdevs="" + if [ -n "$ccri_runtime" ]; then + ccri_driver=$(bus_validation_bound_driver_name "$ccri_runtime" 2>/dev/null || true) + if [ -d "$ccri_runtime/net" ]; then + for ccri_netdev in "$ccri_runtime"/net/*; do + [ -e "$ccri_netdev" ] || continue + ccri_netdevs="${ccri_netdevs}${ccri_netdevs:+,}$(basename "$ccri_netdev")" + done + fi + fi + log_info "[CAN-DT] node=$ccri_node compatible=$ccri_compatible runtime=${ccri_runtime:-missing} driver=${ccri_driver:-unbound} interfaces=${ccri_netdevs:-none}" + printf 'declaration\t%s\t%s\t%s\t%s\t%s\t-\t-\t-\t-\t-\t-\t-\t-\n' \ + "$ccri_node" "${ccri_driver:-unbound}" "${ccri_runtime:-missing}" \ + "$ccri_node" "interfaces=${ccri_netdevs:-none}" >>"$ccri_evidence" + + if [ -z "$ccri_runtime" ]; then + log_fail "[CAN-FAIL] object=$ccri_node expected=runtime-CAN-device observed=missing compatible=$ccri_compatible artifact=$ccri_evidence" + ccri_failures=$((ccri_failures + 1)) + elif [ -z "$ccri_driver" ]; then + ccri_waiting=$(bus_validation_waiting_for_supplier "$ccri_runtime" 2>/dev/null || true) + ccri_modalias=$(bus_validation_device_modalias "$ccri_runtime" 2>/dev/null || true) + ccri_candidates=$(bus_validation_module_candidates "$ccri_modalias" 2>/dev/null || true) + ccri_origins=$(bus_validation_module_origins "$ccri_candidates" 2>/dev/null || true) + log_fail "[CAN-FAIL] object=$(basename "$ccri_runtime") expected=bound-CAN-driver observed=unbound waiting_for_supplier=${ccri_waiting:-unknown} modalias=${ccri_modalias:-unknown} candidates=${ccri_candidates:-unresolved} origins=${ccri_origins:-unknown} evidence=$ccri_evidence registered_drivers=$ccri_registered_drivers" + ccri_failures=$((ccri_failures + 1)) + elif [ -z "$ccri_netdevs" ]; then + log_fail "[CAN-FAIL] object=$(basename "$ccri_runtime") expected=SocketCAN-interface observed=none driver=$ccri_driver artifact=$ccri_evidence" + ccri_failures=$((ccri_failures + 1)) + fi + done <"$ccri_dt_nodes" + + log_info "CAN runtime summary: dt_devices=$ccri_dt_count interfaces=$ccri_interface_count failures=$ccri_failures artifact=$ccri_evidence ip_details=$ccri_ip_log" + + if [ "$ccri_dt_count" -eq 0 ] && [ "$ccri_interface_count" -eq 0 ]; then + return 2 + fi + [ "$ccri_failures" -eq 0 ] || return 1 + return 0 +} + +# bus_validation_suite_run +# Runs the common read-only evidence, result, artifact, and kernel-health flow. +bus_validation_suite_run() { + bisr_bus="$1" + bisr_test_name="$2" + bisr_result_file="$3" + bisr_script_dir="$4" + [ -n "$bisr_bus" ] && [ -n "$bisr_test_name" ] && [ -n "$bisr_result_file" ] && [ -n "$bisr_script_dir" ] || return 3 + + bisr_result_dir="$bisr_script_dir/.${bisr_test_name}.work.$$" + + case "$bisr_bus" in + uart) + bisr_intent="correlating enabled DT controllers, platform drivers, TTY devices, consoles, and serdev consumers" + bisr_success="UART controllers and runtime consumers are functionally ready" + bisr_failure="UART validation found missing or unbound declared capabilities" + bisr_absent="no enabled or runtime-visible UART capability is present" + bisr_dmesg_regex='qcom_geni_serial|msm_serial|serial|tty' + bisr_dmesg_label="UART" + ;; + spi) + bisr_intent="correlating enabled DT controllers and children with runtime masters, devices, modaliases, and bound drivers" + bisr_success="SPI controllers, masters, and client drivers are functionally ready" + bisr_failure="SPI validation found missing or unbound declared capabilities" + bisr_absent="no enabled or runtime-visible SPI capability is present" + bisr_dmesg_regex='spi_geni_qcom|spi_qup|spi' + bisr_dmesg_label="SPI" + ;; + i3c) + bisr_intent="checking direct Linux runtime evidence and recording ADSP, SLPI, or SSC remoteproc state only as indirect evidence" + bisr_success="Linux-visible I3C runtime objects are bound and internally consistent" + bisr_failure="Declared or runtime-visible I3C capability is malformed or unbound" + bisr_absent="no direct Linux I3C interface is exposed, firmware-side remoteproc evidence cannot prove the physical I3C transport" + bisr_dmesg_regex='i3c|qup|ssc|slpi' + bisr_dmesg_label="I3C or QUP" + ;; + can) + bisr_intent="correlating enabled CAN DT devices with parent drivers, SocketCAN interfaces, state, and error counters" + bisr_success="CAN controllers and SocketCAN interfaces are functionally ready" + bisr_failure="CAN validation found missing drivers or SocketCAN interfaces" + bisr_absent="no enabled or runtime-visible physical CAN capability is present" + bisr_dmesg_regex='mcp251|can|spi' + bisr_dmesg_label="CAN or parent-SPI" + ;; + *) + log_error "Unsupported bus evidence type: $bisr_bus" + return 3 + ;; + esac + + test_result_init "$bisr_test_name" "$bisr_result_file" || return 1 + if ! mkdir -p "$bisr_result_dir"; then + test_result_finish "FAIL" "$bisr_test_name FAIL: cannot create temporary evidence directory" + fi + trap 'rm -rf "$bisr_result_dir"' EXIT HUP INT TERM + + log_info "--------------------------------------------------------------------------" + log_info "Starting $bisr_test_name" + log_info "${bisr_dmesg_label} validation: $bisr_intent" + + if ! bus_validation_require_commands awk basename cat dirname find grep mkdir mv od readlink rm sort tr; then + test_result_finish "SKIP" "$bisr_test_name SKIP: required image-provided base utilities are unavailable" + fi + + case "$bisr_bus" in + uart) + uart_validate_runtime "$bisr_result_dir" + ;; + spi) + spi_validate_runtime "$bisr_result_dir" + ;; + i3c) + i3c_validate_runtime "$bisr_result_dir" + ;; + can) + can_validate_runtime "$bisr_result_dir" + ;; + esac + bisr_evidence_status=$? + + case "$bisr_evidence_status" in + 0) + test_result_record "PASS" "$bisr_success" + ;; + 1) + test_result_record "FAIL" "$bisr_failure, detailed per-object diagnostics were emitted in the live log" + ;; + 2) + test_result_finish "SKIP" "$bisr_test_name SKIP: $bisr_absent" + ;; + *) + test_result_record "FAIL" "$bisr_dmesg_label validation helper returned unexpected status $bisr_evidence_status" + ;; + esac + + if command -v dmesg >/dev/null 2>&1; then + scan_dmesg_errors \ + "$bisr_result_dir" \ + "$bisr_dmesg_regex" \ + 'dummy regulator|supply [^ ]+ not found|using dummy regulator' || true + if [ -s "$bisr_result_dir/dmesg_errors.log" ]; then + test_result_record "FAIL" "$bisr_dmesg_label kernel errors were captured, detailed diagnostics were emitted in the live log" + elif [ -s "$bisr_result_dir/dmesg_snapshot.log" ]; then + test_result_record "PASS" "No persistent $bisr_dmesg_label kernel errors were found" + else + test_result_record "SKIP" "Kernel log access is unavailable for $bisr_dmesg_label health validation" + fi + else + test_result_record "SKIP" "The image does not provide dmesg for $bisr_dmesg_label health validation" + fi + + test_result_finish +} + +# uart_device_is_console +# Returns success when the TTY is an active or boot-configured kernel console. +uart_device_is_console() { + udic_tty="$1" + [ -n "$udic_tty" ] || return 3 + + if [ -r /proc/consoles ] && + awk -v tty="$udic_tty" '$1 == tty { found=1 } END { exit !found }' /proc/consoles; then + return 0 + fi + if [ -r /proc/cmdline ] && + grep -Eq "(^|[[:space:]])console=${udic_tty}([,[:space:]]|$)" /proc/cmdline; then + return 0 + fi + + return 1 +} + +# uart_loopback_device_use_reason +# Prints the first current-process or userspace FD owner that makes a TTY unsafe. +uart_loopback_device_use_reason() { + uldur_device="$1" + uldur_target=$(readlink -f "$uldur_device" 2>/dev/null || true) + [ -n "$uldur_target" ] || return 2 + + for uldur_fd in 0 1 2; do + uldur_fd_target=$(readlink -f "/proc/self/fd/$uldur_fd" 2>/dev/null || true) + if [ "$uldur_fd_target" = "$uldur_target" ]; then + printf 'test-process-fd-%s\n' "$uldur_fd" + return 0 + fi + done + + for uldur_process in /proc/[0-9]*; do + [ -d "$uldur_process/fd" ] || continue + uldur_pid=${uldur_process#/proc/} + [ "$uldur_pid" = "$$" ] && continue + for uldur_fd_path in "$uldur_process/fd"/*; do + [ -e "$uldur_fd_path" ] || continue + uldur_fd_target=$(readlink -f "$uldur_fd_path" 2>/dev/null || true) + [ "$uldur_fd_target" = "$uldur_target" ] || continue + uldur_fd_number=${uldur_fd_path##*/} + uldur_comm=$(cat "$uldur_process/comm" 2>/dev/null || printf '%s' unknown) + printf 'process-pid-%s-comm-%s-fd-%s\n' \ + "$uldur_pid" "$uldur_comm" "$uldur_fd_number" + return 0 + done + done + + return 1 +} + +# uart_loopback_select_device +# Prints the first runtime-discovered physical, accessible, non-console TTY. +uart_loopback_select_device() { + ulsd_tty_root="${UART_SYS_CLASS_TTY_ROOT:-/sys/class/tty}" + ulsd_dev_root="${UART_DEV_ROOT:-/dev}" + ulsd_candidate="" + ulsd_count=0 + + [ -d "$ulsd_tty_root" ] || return 2 + for ulsd_tty_path in "$ulsd_tty_root"/*; do + [ -e "$ulsd_tty_path/device" ] || continue + ulsd_tty=$(basename "$ulsd_tty_path") + ulsd_device=$(readlink -f "$ulsd_tty_path/device" 2>/dev/null || true) + case "$ulsd_device" in + /sys/devices/virtual/*) + continue + ;; + esac + ulsd_of_node=$(bus_validation_of_node "$ulsd_tty_path/device" 2>/dev/null || true) + [ -n "$ulsd_of_node" ] || continue + ulsd_devnode="$ulsd_dev_root/$ulsd_tty" + [ -c "$ulsd_devnode" ] || continue + if [ ! -r "$ulsd_devnode" ] || [ ! -w "$ulsd_devnode" ]; then + continue + fi + uart_device_is_console "$ulsd_tty" && continue + ulsd_candidate="$ulsd_devnode" + ulsd_count=$((ulsd_count + 1)) + done + + [ "$ulsd_count" -eq 0 ] && return 2 + [ "$ulsd_count" -eq 1 ] || return 1 + printf '%s\n' "$ulsd_candidate" + return 0 +} + +# uart_loopback_select_bauds +# Prints the conservative portable baud default for a selected runtime UART. +uart_loopback_select_bauds() { + ulsb_device="$1" + [ -n "$ulsb_device" ] || return 3 + + printf '%s\n' '115200' +} + +UART_LOOPBACK_DEVICE="" +UART_LOOPBACK_STATE="" +UART_LOOPBACK_READER_PID="" +UART_LOOPBACK_WATCHER_PID="" + +# uart_loopback_cleanup +# Stops an active reader and restores the exact saved termios state. +uart_loopback_cleanup() { + ulc_status=0 + + if [ -n "$UART_LOOPBACK_READER_PID" ]; then + kill "$UART_LOOPBACK_READER_PID" 2>/dev/null || true + wait "$UART_LOOPBACK_READER_PID" 2>/dev/null || true + UART_LOOPBACK_READER_PID="" + fi + if [ -n "$UART_LOOPBACK_WATCHER_PID" ]; then + kill "$UART_LOOPBACK_WATCHER_PID" 2>/dev/null || true + wait "$UART_LOOPBACK_WATCHER_PID" 2>/dev/null || true + UART_LOOPBACK_WATCHER_PID="" + fi + + if [ -n "$UART_LOOPBACK_DEVICE" ] && [ -n "$UART_LOOPBACK_STATE" ]; then + if ! stty -F "$UART_LOOPBACK_DEVICE" "$UART_LOOPBACK_STATE" 2>/dev/null; then + log_fail "[UART-RESTORE] device=$UART_LOOPBACK_DEVICE expected=saved-termios-state observed=restore-failed" + ulc_status=1 + else + log_info "[UART-RESTORE] device=$UART_LOOPBACK_DEVICE state=restored" + fi + fi + + UART_LOOPBACK_DEVICE="" + UART_LOOPBACK_STATE="" + return "$ulc_status" +} + +# uart_loopback_validate [payload-bytes] +# Runs exact-payload external loopback on an explicitly selected non-console TTY. +uart_loopback_validate() { + ulv_device="$1" + ulv_baud="$2" + ulv_timeout="$3" + ulv_result_dir="$4" + ulv_payload_bytes="${5:-4096}" + [ -n "$ulv_device" ] && [ -n "$ulv_baud" ] && [ -n "$ulv_timeout" ] && [ -n "$ulv_result_dir" ] || return 3 + + case "$ulv_baud:$ulv_timeout:$ulv_payload_bytes" in + *[!0-9:]*|:*|*:) + return 3 + ;; + esac + + mkdir -p "$ulv_result_dir" || return 1 + ulv_log="$ulv_result_dir/uart_loopback_${ulv_baud}.log" + ulv_tx="$ulv_result_dir/uart_loopback_${ulv_baud}_tx.bin" + ulv_rx="$ulv_result_dir/uart_loopback_${ulv_baud}_rx.bin" + ulv_tty=$(basename "$ulv_device") + : >"$ulv_log" + rm -f "$ulv_tx" "$ulv_rx" + + if [ ! -c "$ulv_device" ]; then + log_fail "[UART-LOOPBACK] device=$ulv_device expected=character-device observed=missing-or-wrong-type artifact=$ulv_log" + return 1 + fi + if [ ! -r "$ulv_device" ] || [ ! -w "$ulv_device" ]; then + log_fail "[UART-LOOPBACK] device=$ulv_device expected=read-write-access observed=permission-denied artifact=$ulv_log" + return 1 + fi + if uart_device_is_console "$ulv_tty"; then + log_info "[UART-LOOPBACK] device=$ulv_device state=not-runnable reason=kernel-console action=refused artifact=$ulv_log" + return 2 + fi + ulv_use_reason=$(uart_loopback_device_use_reason "$ulv_device" 2>/dev/null || true) + if [ -n "$ulv_use_reason" ]; then + log_info "[UART-LOOPBACK] device=$ulv_device state=not-runnable reason=active-terminal-or-userspace-owner owner=$ulv_use_reason action=refused artifact=$ulv_log" + return 2 + fi + + UART_LOOPBACK_DEVICE="$ulv_device" + UART_LOOPBACK_STATE=$(stty -F "$ulv_device" -g 2>/dev/null || true) + if [ -z "$UART_LOOPBACK_STATE" ]; then + log_fail "[UART-LOOPBACK] device=$ulv_device expected=readable-termios-state observed=unavailable artifact=$ulv_log" + UART_LOOPBACK_DEVICE="" + return 1 + fi + + if ! stty -F "$ulv_device" "$ulv_baud" raw -echo cs8 -cstopb -parenb -ixon -ixoff -crtscts 2>>"$ulv_log"; then + log_fail "[UART-LOOPBACK] device=$ulv_device expected=baud-$ulv_baud-configuration observed=stty-failed artifact=$ulv_log" + uart_loopback_cleanup || true + return 1 + fi + + if ! awk -v bytes="$ulv_payload_bytes" ' + BEGIN { + pattern = "QLI_UART_" + for (i = 0; i < bytes; i++) { + printf "%s", substr(pattern, (i % length(pattern)) + 1, 1) + } + } + ' >"$ulv_tx"; then + log_fail "[UART-LOOPBACK] device=$ulv_device expected=generated-$ulv_payload_bytes-byte-payload observed=generation-failed artifact=$ulv_log" + uart_loopback_cleanup || true + return 1 + fi + ulv_length=$(wc -c <"$ulv_tx" | tr -d '[:space:]') + printf 'device=%s\nbaud=%s\ntimeout=%s\npayload_bytes=%s\n' \ + "$ulv_device" "$ulv_baud" "$ulv_timeout" "$ulv_length" >>"$ulv_log" + stty -F "$ulv_device" -a >>"$ulv_log" 2>&1 || true + + dd if="$ulv_device" of="$ulv_rx" bs=1 count="$ulv_length" >>"$ulv_log" 2>&1 & + UART_LOOPBACK_READER_PID=$! + ( + sleep "$ulv_timeout" + kill "$UART_LOOPBACK_READER_PID" 2>/dev/null || true + ) & + UART_LOOPBACK_WATCHER_PID=$! + sleep 1 + + # shellcheck disable=SC2016 + if ! run_with_timeout "$ulv_timeout" sh -c 'dd if="$1" of="$2" bs=4096' sh "$ulv_tx" "$ulv_device" >>"$ulv_log" 2>&1; then + log_fail "[UART-LOOPBACK] device=$ulv_device expected=bounded-write observed=write-failed-or-timeout artifact=$ulv_log" + uart_loopback_cleanup || true + return 1 + fi + + if ! wait "$UART_LOOPBACK_READER_PID"; then + UART_LOOPBACK_READER_PID="" + log_fail "[UART-LOOPBACK] device=$ulv_device expected=$ulv_length-received-bytes observed=reader-failed-or-timeout artifact=$ulv_log" + uart_loopback_cleanup || true + return 1 + fi + UART_LOOPBACK_READER_PID="" + kill "$UART_LOOPBACK_WATCHER_PID" 2>/dev/null || true + wait "$UART_LOOPBACK_WATCHER_PID" 2>/dev/null || true + UART_LOOPBACK_WATCHER_PID="" + + if ! cmp -s "$ulv_tx" "$ulv_rx"; then + ulv_received=$(od -An -v -tx1 "$ulv_rx" 2>/dev/null | tr -d ' \n') + ulv_expected=$(od -An -v -tx1 "$ulv_tx" 2>/dev/null | tr -d ' \n') + log_fail "[UART-LOOPBACK] device=$ulv_device expected_hex=$ulv_expected observed_hex=${ulv_received:-empty} artifact=$ulv_log" + uart_loopback_cleanup || true + return 1 + fi + + if ! uart_loopback_cleanup; then + return 1 + fi + + log_info "[UART-LOOPBACK] device=$ulv_device baud=$ulv_baud bytes=$ulv_length format=8N1 flow_control=off payload=verified artifact=$ulv_log" + return 0 +} + +# spi_loopback_select_device +# Prints the first accessible image-provided spidev character device. +spi_loopback_select_device() { + slsd_dev_root="${SPI_DEV_ROOT:-/dev}" + slsd_candidate="" + slsd_count=0 + + for slsd_device in "$slsd_dev_root"/spidev*; do + [ -c "$slsd_device" ] || continue + if [ ! -r "$slsd_device" ] || [ ! -w "$slsd_device" ]; then + continue + fi + slsd_candidate="$slsd_device" + slsd_count=$((slsd_count + 1)) + done + + [ "$slsd_count" -eq 0 ] && return 2 + [ "$slsd_count" -eq 1 ] || return 1 + printf '%s\n' "$slsd_candidate" + return 0 +} + +# spi_loopback_select_type +# Requires an explicit loopback type because sysfs does not expose fixture wiring. +spi_loopback_select_type() { + slst_device="$1" + [ -n "$slst_device" ] || return 3 + + return 2 +} + +# spi_loopback_validate [mode] [internal|external] +# Runs spidev_test and requires exact TX, RX, and requested payload byte equality. +spi_loopback_validate() { + slv_device="$1" + slv_speed="$2" + slv_bits="$3" + slv_payload="$4" + slv_timeout="$5" + slv_result_dir="$6" + slv_mode="${7:-0}" + slv_loopback_type="${8:-external}" + [ -n "$slv_device" ] && [ -n "$slv_speed" ] && [ -n "$slv_bits" ] && [ -n "$slv_payload" ] && [ -n "$slv_timeout" ] && [ -n "$slv_result_dir" ] || return 3 + + case "$slv_speed:$slv_bits:$slv_timeout" in + *[!0-9:]*|:*|*:) + return 3 + ;; + esac + case "$slv_mode" in + 0|1|2|3) + ;; + *) + return 3 + ;; + esac + case "$slv_loopback_type" in + internal|external) + ;; + *) + return 3 + ;; + esac + + mkdir -p "$slv_result_dir" || return 1 + slv_case="mode${slv_mode}_${slv_loopback_type}" + slv_log="$slv_result_dir/spi_loopback_${slv_case}.log" + slv_tx_file="$slv_result_dir/spi_loopback_${slv_case}_tx.bin" + slv_rx_file="$slv_result_dir/spi_loopback_${slv_case}_rx.bin" + slv_spi_name=$(basename "$slv_device") + slv_sysfs_device="${SPI_SYS_BUS_ROOT:-/sys/bus/spi}/devices/$slv_spi_name" + slv_errors_before="" + slv_timeouts_before="" + : >"$slv_log" + rm -f "$slv_rx_file" + printf '%s' "$slv_payload" >"$slv_tx_file" + + if [ ! -c "$slv_device" ]; then + log_fail "[SPI-LOOPBACK] device=$slv_device expected=spidev-character-device observed=missing-or-wrong-type artifact=$slv_log" + return 1 + fi + if ! command -v spidev_test >/dev/null 2>&1; then + log_info "[SPI-LOOPBACK] device=$slv_device state=not-runnable reason=spidev_test-not-provided" + return 2 + fi + + if [ -r "$slv_sysfs_device/statistics/errors" ]; then + slv_errors_before=$(tr -d '[:space:]' <"$slv_sysfs_device/statistics/errors") + case "$slv_errors_before" in + ''|*[!0-9]*) + log_fail "[SPI-LOOPBACK] device=$slv_device expected=numeric-errors-counter observed=${slv_errors_before:-empty} artifact=$slv_log" + return 1 + ;; + esac + fi + if [ -r "$slv_sysfs_device/statistics/timedout" ]; then + slv_timeouts_before=$(tr -d '[:space:]' <"$slv_sysfs_device/statistics/timedout") + case "$slv_timeouts_before" in + ''|*[!0-9]*) + log_fail "[SPI-LOOPBACK] device=$slv_device expected=numeric-timeout-counter observed=${slv_timeouts_before:-empty} artifact=$slv_log" + return 1 + ;; + esac + fi + log_info "[SPI-LOOPBACK] device=$slv_device mode=$slv_mode loopback=$slv_loopback_type errors_before=${slv_errors_before:-unavailable} timedout_before=${slv_timeouts_before:-unavailable} statistics=$slv_sysfs_device/statistics" + + set -- spidev_test -D "$slv_device" -s "$slv_speed" -b "$slv_bits" \ + -p "$slv_payload" -o "$slv_rx_file" -v + case "$slv_mode" in + 1) + set -- "$@" -H + ;; + 2) + set -- "$@" -O + ;; + 3) + set -- "$@" -H -O + ;; + esac + if [ "$slv_loopback_type" = "internal" ]; then + set -- "$@" -l + fi + + if ! run_with_timeout_log "$slv_timeout" "$slv_log" "$@"; then + log_file_with_label "SPI-LOOPBACK" "$slv_log" + log_fail "[SPI-LOOPBACK] device=$slv_device expected=successful-bounded-transfer observed=command-failed-or-timeout artifact=$slv_log" + return 1 + fi + + log_file_with_label "SPI-LOOPBACK" "$slv_log" + if [ ! -f "$slv_rx_file" ]; then + log_fail "[SPI-LOOPBACK] device=$slv_device expected=fresh-RX-artifact observed=missing artifact=$slv_log" + return 1 + fi + if ! cmp -s "$slv_tx_file" "$slv_rx_file"; then + slv_expected=$(od -An -v -tx1 "$slv_tx_file" | tr -d ' \n' | tr '[:upper:]' '[:lower:]') + slv_received=$(od -An -v -tx1 "$slv_rx_file" | tr -d ' \n' | tr '[:upper:]' '[:lower:]') + log_fail "[SPI-LOOPBACK] device=$slv_device expected_hex=$slv_expected observed_hex=${slv_received:-empty} artifact=$slv_log" + return 1 + fi + + slv_errors_after="" + slv_timeouts_after="" + if [ -r "$slv_sysfs_device/statistics/errors" ]; then + slv_errors_after=$(tr -d '[:space:]' <"$slv_sysfs_device/statistics/errors") + case "$slv_errors_after" in + ''|*[!0-9]*) + log_fail "[SPI-LOOPBACK] device=$slv_device expected=numeric-errors-counter observed=${slv_errors_after:-empty} artifact=$slv_log" + return 1 + ;; + esac + fi + if [ -r "$slv_sysfs_device/statistics/timedout" ]; then + slv_timeouts_after=$(tr -d '[:space:]' <"$slv_sysfs_device/statistics/timedout") + case "$slv_timeouts_after" in + ''|*[!0-9]*) + log_fail "[SPI-LOOPBACK] device=$slv_device expected=numeric-timeout-counter observed=${slv_timeouts_after:-empty} artifact=$slv_log" + return 1 + ;; + esac + fi + log_info "[SPI-LOOPBACK] device=$slv_device mode=$slv_mode loopback=$slv_loopback_type errors_after=${slv_errors_after:-unavailable} timedout_after=${slv_timeouts_after:-unavailable}" + if [ -n "$slv_errors_before" ] && [ -n "$slv_errors_after" ] && + [ "$slv_errors_after" -gt "$slv_errors_before" ] 2>/dev/null; then + log_fail "[SPI-LOOPBACK] device=$slv_device expected=no-new-controller-errors observed=errors-$slv_errors_before-to-$slv_errors_after artifact=$slv_log" + return 1 + fi + if [ -n "$slv_timeouts_before" ] && [ -n "$slv_timeouts_after" ] && + [ "$slv_timeouts_after" -gt "$slv_timeouts_before" ] 2>/dev/null; then + log_fail "[SPI-LOOPBACK] device=$slv_device expected=no-new-controller-timeouts observed=timedout-$slv_timeouts_before-to-$slv_timeouts_after artifact=$slv_log" + return 1 + fi + + log_info "[SPI-LOOPBACK] device=$slv_device mode=$slv_mode loopback=$slv_loopback_type speed_hz=$slv_speed bits=$slv_bits bytes=$(printf '%s' "$slv_payload" | wc -c | tr -d '[:space:]') payload=verified artifact=$slv_log" + return 0 +} + +CAN_LOOPBACK_INTERFACE="" +CAN_LOOPBACK_INITIAL_MODE="off" +CAN_LOOPBACK_INITIAL_BITRATE="" +CAN_LOOPBACK_INITIAL_DBITRATE="" +CAN_LOOPBACK_INITIAL_FD="off" +CAN_LOOPBACK_TEST_MODE="classic" +CAN_LOOPBACK_CHANGED=0 +CAN_LOOPBACK_CANDUMP_PID="" + +# can_internal_loopback_interface_is_ready +# Returns success only for a physical SocketCAN interface that is down in sysfs. +can_internal_loopback_interface_is_ready() { + cili_interface="$1" + cili_net_root="${CAN_SYS_CLASS_NET_ROOT:-/sys/class/net}" + cili_path="$cili_net_root/$cili_interface" + + [ -d "$cili_path" ] || return 2 + [ -r "$cili_path/type" ] || return 1 + [ "$(tr -d '[:space:]' <"$cili_path/type")" = "280" ] || return 1 + [ -e "$cili_path/device" ] || return 1 + [ "$(cat "$cili_path/operstate" 2>/dev/null || true)" = "down" ] || return 1 + return 0 +} + +# can_internal_loopback_select_interface +# Prints the unique down physical CAN interface that may be configured safely. +can_internal_loopback_select_interface() { + cilsi_mode="$1" + cilsi_net_root="${CAN_SYS_CLASS_NET_ROOT:-/sys/class/net}" + cilsi_candidate="" + cilsi_count=0 + [ "$cilsi_mode" = "auto" ] || [ "$cilsi_mode" = "classic" ] || [ "$cilsi_mode" = "fd" ] || return 3 + [ -d "$cilsi_net_root" ] || return 2 + + for cilsi_path in "$cilsi_net_root"/*; do + cilsi_interface=$(basename "$cilsi_path") + can_internal_loopback_interface_is_ready "$cilsi_interface" || continue + cilsi_candidate="$cilsi_interface" + cilsi_count=$((cilsi_count + 1)) + done + + [ "$cilsi_count" -eq 0 ] && return 2 + [ "$cilsi_count" -eq 1 ] || return 1 + printf '%s\n' "$cilsi_candidate" + return 0 +} + +# can_internal_loopback_select_modes +# Prints the automatic classic and CAN FD functional matrix. +can_internal_loopback_select_modes() { + cilsm_interface="$1" + [ -n "$cilsm_interface" ] || return 3 + + cilsm_path="${CAN_SYS_CLASS_NET_ROOT:-/sys/class/net}/$cilsm_interface" + [ -d "$cilsm_path" ] || return 2 + printf '%s\n' 'classic,fd' +} + +# can_internal_loopback_cleanup +# Stops candump, returns the interface down, and restores its loopback flag. +can_internal_loopback_cleanup() { + cilc_status=0 + + if [ -n "$CAN_LOOPBACK_CANDUMP_PID" ]; then + kill "$CAN_LOOPBACK_CANDUMP_PID" 2>/dev/null || true + wait "$CAN_LOOPBACK_CANDUMP_PID" 2>/dev/null || true + CAN_LOOPBACK_CANDUMP_PID="" + fi + + if [ -n "$CAN_LOOPBACK_INTERFACE" ]; then + if ! ip link set dev "$CAN_LOOPBACK_INTERFACE" down >/dev/null 2>&1; then + log_fail "[CAN-RESTORE] interface=$CAN_LOOPBACK_INTERFACE expected=administratively-down observed=down-failed" + cilc_status=1 + fi + if [ "$CAN_LOOPBACK_CHANGED" -eq 1 ]; then + set -- ip link set dev "$CAN_LOOPBACK_INTERFACE" type can + if [ -n "$CAN_LOOPBACK_INITIAL_BITRATE" ]; then + set -- "$@" bitrate "$CAN_LOOPBACK_INITIAL_BITRATE" + fi + if [ "$CAN_LOOPBACK_INITIAL_FD" = "on" ] && [ -n "$CAN_LOOPBACK_INITIAL_DBITRATE" ]; then + set -- "$@" dbitrate "$CAN_LOOPBACK_INITIAL_DBITRATE" fd on + elif [ "$CAN_LOOPBACK_TEST_MODE" = "fd" ]; then + set -- "$@" fd off + fi + set -- "$@" loopback "$CAN_LOOPBACK_INITIAL_MODE" + if ! "$@" >/dev/null 2>&1; then + log_fail "[CAN-RESTORE] interface=$CAN_LOOPBACK_INTERFACE expected=original-CAN-configuration observed=restore-failed bitrate=${CAN_LOOPBACK_INITIAL_BITRATE:-unset} dbitrate=${CAN_LOOPBACK_INITIAL_DBITRATE:-unset} fd=$CAN_LOOPBACK_INITIAL_FD loopback=$CAN_LOOPBACK_INITIAL_MODE" + cilc_status=1 + else + log_info "[CAN-RESTORE] interface=$CAN_LOOPBACK_INTERFACE admin_state=down bitrate=${CAN_LOOPBACK_INITIAL_BITRATE:-test-default-retained} dbitrate=${CAN_LOOPBACK_INITIAL_DBITRATE:-unset} fd=$CAN_LOOPBACK_INITIAL_FD loopback=$CAN_LOOPBACK_INITIAL_MODE" + fi + fi + fi + + CAN_LOOPBACK_INTERFACE="" + CAN_LOOPBACK_INITIAL_MODE="off" + CAN_LOOPBACK_INITIAL_BITRATE="" + CAN_LOOPBACK_INITIAL_DBITRATE="" + CAN_LOOPBACK_INITIAL_FD="off" + CAN_LOOPBACK_TEST_MODE="classic" + CAN_LOOPBACK_CHANGED=0 + return "$cilc_status" +} + +# can_internal_loopback_validate +# Configures a selected, initially down CAN interface and restores recoverable state. +can_internal_loopback_validate() { + cilv_interface="$1" + cilv_mode="$2" + cilv_timeout="$3" + cilv_result_dir="$4" + cilv_requested_bitrate="$5" + cilv_requested_dbitrate="$6" + [ -n "$cilv_interface" ] && [ -n "$cilv_mode" ] && [ -n "$cilv_timeout" ] && [ -n "$cilv_result_dir" ] && [ -n "$cilv_requested_bitrate" ] && [ -n "$cilv_requested_dbitrate" ] || return 3 + + for cilv_numeric in \ + "$cilv_timeout" \ + "$cilv_requested_bitrate" \ + "$cilv_requested_dbitrate"; do + case "$cilv_numeric" in + ''|*[!0-9]*|0) + return 3 + ;; + esac + done + case "$cilv_mode" in + classic|fd) + ;; + *) + return 3 + ;; + esac + + cilv_iface_path="${CAN_SYS_CLASS_NET_ROOT:-/sys/class/net}/$cilv_interface" + mkdir -p "$cilv_result_dir" || return 1 + cilv_snapshot="$cilv_result_dir/can_loopback_initial_state_${cilv_mode}.log" + cilv_rx_log="$cilv_result_dir/can_loopback_rx_${cilv_mode}.log" + cilv_stats_log="$cilv_result_dir/can_loopback_statistics_${cilv_mode}.log" + : >"$cilv_rx_log" + + if [ ! -d "$cilv_iface_path" ] || [ ! -r "$cilv_iface_path/type" ]; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=runtime-interface observed=missing artifact=$cilv_snapshot" + return 1 + fi + cilv_type=$(tr -d '[:space:]' <"$cilv_iface_path/type") + if [ "$cilv_type" != "280" ] || [ ! -e "$cilv_iface_path/device" ]; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=physical-SocketCAN-interface observed=type-$cilv_type-or-software-only artifact=$cilv_snapshot" + return 1 + fi + + if ! ip -details link show dev "$cilv_interface" >"$cilv_snapshot" 2>&1; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=readable-CAN-configuration observed=ip-query-failed artifact=$cilv_snapshot" + return 1 + fi + cilv_flags=$(ip -o link show dev "$cilv_interface" 2>/dev/null | sed -n 's/.*<\([^>]*\)>.*/\1/p') + if printf '%s\n' "$cilv_flags" | tr ',' '\n' | grep -qx UP; then + log_info "[CAN-LOOPBACK] interface=$cilv_interface state=not-runnable reason=interface-is-already-up action=refused" + return 2 + fi + + CAN_LOOPBACK_INITIAL_BITRATE=$(awk ' + /bitrate[[:space:]]+[0-9]+/ { + for (i = 1; i <= NF; i++) { + if ($i == "bitrate") { + print $(i + 1) + exit + } + } + } + ' "$cilv_snapshot") + CAN_LOOPBACK_INITIAL_DBITRATE=$(awk ' + /dbitrate[[:space:]]+[0-9]+/ { + for (i = 1; i <= NF; i++) { + if ($i == "dbitrate") { + print $(i + 1) + exit + } + } + } + ' "$cilv_snapshot") + + CAN_LOOPBACK_INITIAL_MODE=off + if grep -Eq '<[^>]*LOOPBACK([^A-Z-]|[>,])' "$cilv_snapshot"; then + CAN_LOOPBACK_INITIAL_MODE=on + fi + CAN_LOOPBACK_INITIAL_FD=off + if grep -Eq '<[^>]*FD([^A-Z-]|[>,])' "$cilv_snapshot"; then + CAN_LOOPBACK_INITIAL_FD=on + fi + if [ "$cilv_mode" = "fd" ]; then + cilv_frames='5A5##1514C492D43414E4644' + cilv_markers='5A5##1514C492D43414E4644' + cilv_frame_count=1 + else + cilv_frames='5A5#514C4943414E 1ABCDE#45585443414E' + cilv_markers='5A5#514C4943414E 001ABCDE#45585443414E' + cilv_frame_count=2 + fi + + cilv_rx_packets_before=$(cat "$cilv_iface_path/statistics/rx_packets" 2>/dev/null || printf '%s\n' unknown) + cilv_tx_packets_before=$(cat "$cilv_iface_path/statistics/tx_packets" 2>/dev/null || printf '%s\n' unknown) + cilv_rx_errors_before=$(cat "$cilv_iface_path/statistics/rx_errors" 2>/dev/null || printf '%s\n' unknown) + cilv_tx_errors_before=$(cat "$cilv_iface_path/statistics/tx_errors" 2>/dev/null || printf '%s\n' unknown) + for cilv_counter in \ + "$cilv_rx_packets_before" \ + "$cilv_tx_packets_before" \ + "$cilv_rx_errors_before" \ + "$cilv_tx_errors_before"; do + case "$cilv_counter" in + unknown) + ;; + ''|*[!0-9]*) + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=numeric-sysfs-counter observed=${cilv_counter:-empty} artifact=$cilv_stats_log" + return 1 + ;; + esac + done + printf 'before rx_packets=%s tx_packets=%s rx_errors=%s tx_errors=%s\n' \ + "$cilv_rx_packets_before" "$cilv_tx_packets_before" \ + "$cilv_rx_errors_before" "$cilv_tx_errors_before" >"$cilv_stats_log" + + CAN_LOOPBACK_INTERFACE="$cilv_interface" + CAN_LOOPBACK_TEST_MODE="$cilv_mode" + if [ "$cilv_mode" = "fd" ]; then + if ! ip link set dev "$cilv_interface" type can \ + bitrate "$cilv_requested_bitrate" \ + dbitrate "$cilv_requested_dbitrate" \ + fd on \ + loopback on; then + log_info "[CAN-LOOPBACK] interface=$cilv_interface state=not-runnable reason=CAN-FD-configuration-rejected bitrate=$cilv_requested_bitrate dbitrate=$cilv_requested_dbitrate artifact=$cilv_snapshot" + can_internal_loopback_cleanup || true + return 2 + fi + elif ! ip link set dev "$cilv_interface" type can \ + bitrate "$cilv_requested_bitrate" \ + loopback on; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=classic-CAN-configuration observed=ip-command-failed bitrate=$cilv_requested_bitrate artifact=$cilv_snapshot" + can_internal_loopback_cleanup || true + return 1 + fi + CAN_LOOPBACK_CHANGED=1 + cilv_dbitrate_display=not-applicable + if [ "$cilv_mode" = "fd" ]; then + cilv_dbitrate_display="$cilv_requested_dbitrate" + fi + log_info "[CAN-LOOPBACK] interface=$cilv_interface mode=$cilv_mode configured_bitrate=$cilv_requested_bitrate configured_dbitrate=$cilv_dbitrate_display loopback=on" + if ! ip link set dev "$cilv_interface" up; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=administratively-up observed=ip-command-failed bitrate=$cilv_requested_bitrate artifact=$cilv_snapshot" + can_internal_loopback_cleanup || true + return 1 + fi + + candump -L -n "$cilv_frame_count" -T "$((cilv_timeout * 1000))" "$cilv_interface" >"$cilv_rx_log" 2>&1 & + CAN_LOOPBACK_CANDUMP_PID=$! + sleep 1 + for cilv_frame in $cilv_frames; do + log_info "[CAN-LOOPBACK] interface=$cilv_interface mode=$cilv_mode action=transmit frame=$cilv_frame" + if ! run_with_timeout "$cilv_timeout" cansend "$cilv_interface" "$cilv_frame"; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=frame-transmit observed=cansend-failed-or-timeout frame=$cilv_frame artifact=$cilv_rx_log" + can_internal_loopback_cleanup || true + return 1 + fi + done + if ! wait "$CAN_LOOPBACK_CANDUMP_PID"; then + CAN_LOOPBACK_CANDUMP_PID="" + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=$cilv_frame_count-received-loopback-frames observed=candump-failed-or-timeout frames=$cilv_frames artifact=$cilv_rx_log" + can_internal_loopback_cleanup || true + return 1 + fi + CAN_LOOPBACK_CANDUMP_PID="" + + cilv_normalized=$(tr -d ' .\t\r\n' <"$cilv_rx_log" | tr '[:lower:]' '[:upper:]') + for cilv_marker in $cilv_markers; do + if ! printf '%s\n' "$cilv_normalized" | grep -Fq "$cilv_marker"; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected_frame=$cilv_marker observed_log=$cilv_rx_log artifact=$cilv_rx_log" + can_internal_loopback_cleanup || true + return 1 + fi + done + + cilv_rx_packets_after=$(cat "$cilv_iface_path/statistics/rx_packets" 2>/dev/null || printf '%s\n' unknown) + cilv_tx_packets_after=$(cat "$cilv_iface_path/statistics/tx_packets" 2>/dev/null || printf '%s\n' unknown) + cilv_rx_errors_after=$(cat "$cilv_iface_path/statistics/rx_errors" 2>/dev/null || printf '%s\n' unknown) + cilv_tx_errors_after=$(cat "$cilv_iface_path/statistics/tx_errors" 2>/dev/null || printf '%s\n' unknown) + for cilv_counter in \ + "$cilv_rx_packets_after" \ + "$cilv_tx_packets_after" \ + "$cilv_rx_errors_after" \ + "$cilv_tx_errors_after"; do + case "$cilv_counter" in + unknown) + ;; + ''|*[!0-9]*) + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=numeric-sysfs-counter observed=${cilv_counter:-empty} artifact=$cilv_stats_log" + can_internal_loopback_cleanup || true + return 1 + ;; + esac + done + printf 'after rx_packets=%s tx_packets=%s rx_errors=%s tx_errors=%s\n' \ + "$cilv_rx_packets_after" "$cilv_tx_packets_after" \ + "$cilv_rx_errors_after" "$cilv_tx_errors_after" >>"$cilv_stats_log" + ip -details -statistics link show dev "$cilv_interface" >>"$cilv_stats_log" 2>&1 || true + + if ! can_internal_loopback_cleanup; then + return 1 + fi + + if [ "$cilv_rx_packets_before" != "unknown" ] && [ "$cilv_rx_packets_after" != "unknown" ] && + [ $((cilv_rx_packets_after - cilv_rx_packets_before)) -lt "$cilv_frame_count" ]; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=rx-packet-increase-at-least-$cilv_frame_count observed=$cilv_rx_packets_before-to-$cilv_rx_packets_after artifact=$cilv_stats_log" + return 1 + fi + if [ "$cilv_tx_packets_before" != "unknown" ] && [ "$cilv_tx_packets_after" != "unknown" ] && + [ $((cilv_tx_packets_after - cilv_tx_packets_before)) -lt "$cilv_frame_count" ]; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=tx-packet-increase-at-least-$cilv_frame_count observed=$cilv_tx_packets_before-to-$cilv_tx_packets_after artifact=$cilv_stats_log" + return 1 + fi + if [ "$cilv_rx_errors_before" != "unknown" ] && [ "$cilv_rx_errors_after" != "unknown" ] && + [ "$cilv_rx_errors_after" -gt "$cilv_rx_errors_before" ] 2>/dev/null; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=no-new-rx-errors observed=$cilv_rx_errors_before-to-$cilv_rx_errors_after artifact=$cilv_stats_log" + return 1 + fi + if [ "$cilv_tx_errors_before" != "unknown" ] && [ "$cilv_tx_errors_after" != "unknown" ] && + [ "$cilv_tx_errors_after" -gt "$cilv_tx_errors_before" ] 2>/dev/null; then + log_fail "[CAN-LOOPBACK] interface=$cilv_interface expected=no-new-tx-errors observed=$cilv_tx_errors_before-to-$cilv_tx_errors_after artifact=$cilv_stats_log" + return 1 + fi + + log_info "[CAN-LOOPBACK] interface=$cilv_interface mode=$cilv_mode bitrate=$cilv_requested_bitrate dbitrate=$cilv_dbitrate_display frames=$cilv_frame_count payloads=verified rx_packets=$cilv_rx_packets_before-to-$cilv_rx_packets_after tx_packets=$cilv_tx_packets_before-to-$cilv_tx_packets_after rx_errors=$cilv_rx_errors_before-to-$cilv_rx_errors_after tx_errors=$cilv_tx_errors_before-to-$cilv_tx_errors_after artifacts=$cilv_rx_log,$cilv_stats_log" + return 0 +} From 1504ae3d9823df6280cc53f7f5b695e9a8adf006 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Fri, 11 Sep 2026 23:48:46 +0530 Subject: [PATCH 2/5] uart: add readiness and guarded loopback tests Discover runtime UART consumers dynamically and provide fixture-gated payload loopback with serial-console and active-terminal ownership protection. Signed-off-by: Srikanth Muppandam --- .../UART_Loopback_Validation/README.md | 59 +++++ .../UART_Loopback_Validation.yaml | 22 ++ .../Baseport/UART_Loopback_Validation/run.sh | 236 ++++++++++++++++++ .../Kernel/Baseport/UART_Validation/README.md | 42 ++++ .../UART_Validation/UART_Validation.yaml | 15 ++ .../Kernel/Baseport/UART_Validation/run.sh | 64 +++++ 6 files changed, 438 insertions(+) create mode 100644 Runner/suites/Kernel/Baseport/UART_Loopback_Validation/README.md create mode 100755 Runner/suites/Kernel/Baseport/UART_Loopback_Validation/UART_Loopback_Validation.yaml create mode 100755 Runner/suites/Kernel/Baseport/UART_Loopback_Validation/run.sh create mode 100644 Runner/suites/Kernel/Baseport/UART_Validation/README.md create mode 100755 Runner/suites/Kernel/Baseport/UART_Validation/UART_Validation.yaml create mode 100755 Runner/suites/Kernel/Baseport/UART_Validation/run.sh diff --git a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/README.md b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/README.md new file mode 100644 index 00000000..20ffcf13 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/README.md @@ -0,0 +1,59 @@ +# UART loopback validation + +`UART_Loopback_Validation` verifies exact payload transfer through an +auto-detected or explicitly selected UART with an external TX-to-RX loopback +fixture. + +The suite refuses active kernel consoles, snapshots the complete termios state, +uses bounded reads and writes, compares a 4 KiB transmitted and received +payload, and restores the saved state after every baud case. When exactly one +accessible DT-backed physical non-console UART exists, it is selected +automatically. Generic legacy `ttyS*` entries without runtime DT ownership are +ignored. Multiple eligible UARTs require an explicit override to avoid guessing +which port has the loopback fixture. + +Before changing termios, the suite also compares the selected device with its +own stdin, stdout, and stderr and scans current userspace file-descriptor +owners. A serial shell, getty, or another process holding that UART produces a +safe `SKIP` with the dynamically discovered owner PID, command, and FD in the +live log. + +Automatic baud selection uses the portable `115200` default. Specify any +additional board-qualified baud rates explicitly with `--baud` or +`UART_BAUDS`. + +## Run + +```sh +cd Runner/suites/Kernel/Baseport/UART_Loopback_Validation +./run.sh --fixture +``` + +Optional settings: + +```sh +./run.sh --fixture --device /dev/ttyHS1 --baud 115200,921600,3000000 --payload-bytes 4096 --timeout 15 +``` + +Use `--device` or `UART_DEVICE` to override automatic discovery. CLI options +take precedence over environment variables. Other environment variables are +`UART_BAUDS`, `UART_PAYLOAD_BYTES`, `UART_LOOPBACK_TIMEOUT`, and +`UART_LOOPBACK_FIXTURE`. The legacy single-value `UART_BAUD` environment +variable remains supported. + +An external TX-to-RX loopback fixture must be explicitly enabled with +`--fixture` or `UART_LOOPBACK_FIXTURE=1`. This prevents an unqualified test +job from transmitting on an auto-detected UART. + +Temporary payload and command evidence is removed when the run finishes. The +live log retains the bounded per-case failure analysis. + +## Result policy + +- `PASS`: the exact payload is received and termios restoration succeeds. +- `FAIL`: the selected device is unsafe or invalid, configuration or transfer + fails, bytes differ, a timeout occurs, or state restoration fails. +- `SKIP`: no unique safe UART is discovered and no override is provided, or + required image utilities are absent. + +Reference: [Qualcomm Linux UART guide](https://docs.qualcomm.com/doc/80-70023-8/topic/uart.html) diff --git a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/UART_Loopback_Validation.yaml b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/UART_Loopback_Validation.yaml new file mode 100755 index 00000000..6e911b0c --- /dev/null +++ b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/UART_Loopback_Validation.yaml @@ -0,0 +1,22 @@ +metadata: + name: UART_Loopback_Validation + format: "Lava-Test Test Definition 1.0" + description: "Run bounded exact-payload loopback on an auto-detected or selected safe UART" + os: + - linux + scope: + - functional + +params: + UART_DEVICE: "" + UART_BAUDS: "auto" + UART_PAYLOAD_BYTES: "4096" + UART_LOOPBACK_TIMEOUT: "10" + UART_LOOPBACK_FIXTURE: "0" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/UART_Loopback_Validation + - UART_DEVICE="${UART_DEVICE}" UART_BAUDS="${UART_BAUDS}" UART_PAYLOAD_BYTES="${UART_PAYLOAD_BYTES}" UART_LOOPBACK_TIMEOUT="${UART_LOOPBACK_TIMEOUT}" UART_LOOPBACK_FIXTURE="${UART_LOOPBACK_FIXTURE}" ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh UART_Loopback_Validation.res diff --git a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/run.sh b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/run.sh new file mode 100755 index 00000000..580f33d6 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/run.sh @@ -0,0 +1,236 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_bus.sh" +TESTNAME="UART_Loopback_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +RESULT_DIR="$SCRIPT_DIR/.${TESTNAME}.work.$$" +UART_DEVICE="${UART_DEVICE:-}" +UART_BAUDS="${UART_BAUDS:-${UART_BAUD:-auto}}" +UART_PAYLOAD_BYTES="${UART_PAYLOAD_BYTES:-4096}" +UART_LOOPBACK_TIMEOUT="${UART_LOOPBACK_TIMEOUT:-10}" +UART_LOOPBACK_FIXTURE="${UART_LOOPBACK_FIXTURE:-0}" + +cleanup() { + uart_loopback_cleanup >/dev/null 2>&1 || true + rm -rf "$RESULT_DIR" +} + +usage() { + cat <&2 + exit 2 +fi +if ! validate_config; then + usage >&2 + exit 2 +fi + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 +trap cleanup EXIT HUP INT TERM + +if ! mkdir -p "$RESULT_DIR"; then + test_result_finish "FAIL" "$TESTNAME FAIL: cannot create temporary evidence directory" +fi + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +if ! bus_validation_require_commands awk basename cmp dd grep mkdir od readlink rm sh sleep stty tr wc; then + test_result_finish "SKIP" "$TESTNAME SKIP: required image-provided UART loopback utilities are unavailable" +fi +if ! bus_validation_bool_true "$UART_LOOPBACK_FIXTURE"; then + test_result_finish "SKIP" "$TESTNAME SKIP: external UART loopback fixture was not explicitly enabled, use --fixture or UART_LOOPBACK_FIXTURE=1" +fi + +UART_SELECTION_SOURCE=override +if [ -z "$UART_DEVICE" ]; then + UART_SELECTION_SOURCE=auto + UART_DEVICE=$(uart_loopback_select_device 2>/dev/null) + UART_SELECTION_STATUS=$? + if [ "$UART_SELECTION_STATUS" -eq 1 ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: multiple eligible UART devices were detected, select one with --device or UART_DEVICE" + fi +fi +if [ -z "$UART_DEVICE" ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: no accessible DT-backed physical non-console UART was detected" +fi +if [ "$UART_BAUDS" = "auto" ]; then + UART_BAUDS=$(uart_loopback_select_bauds "$UART_DEVICE") + UART_BAUD_SELECTION_STATUS=$? + if [ "$UART_BAUD_SELECTION_STATUS" -ne 0 ] || [ -z "$UART_BAUDS" ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: could not derive a baud matrix for $UART_DEVICE" + fi +fi +log_info "Configuration: device=$UART_DEVICE selection=$UART_SELECTION_SOURCE bauds=$UART_BAUDS payload_bytes=$UART_PAYLOAD_BYTES timeout=${UART_LOOPBACK_TIMEOUT}s fixture=external-confirmed" + +log_info "UART loopback: validating exact data transfer across the selected baud matrix with termios restoration after every case" +UART_REMAINING_BAUDS=$UART_BAUDS +while [ -n "$UART_REMAINING_BAUDS" ]; do + case "$UART_REMAINING_BAUDS" in + *,*) + UART_BAUD=${UART_REMAINING_BAUDS%%,*} + UART_REMAINING_BAUDS=${UART_REMAINING_BAUDS#*,} + ;; + *) + UART_BAUD=$UART_REMAINING_BAUDS + UART_REMAINING_BAUDS="" + ;; + esac + log_info "---- UART functional case: device=$UART_DEVICE baud=$UART_BAUD bytes=$UART_PAYLOAD_BYTES ----" + uart_loopback_validate \ + "$UART_DEVICE" \ + "$UART_BAUD" \ + "$UART_LOOPBACK_TIMEOUT" \ + "$RESULT_DIR" \ + "$UART_PAYLOAD_BYTES" + loopback_status=$? + case "$loopback_status" in + 0) + test_result_record "PASS" "UART exact payload transfer passed on $UART_DEVICE at $UART_BAUD baud" + ;; + 1) + test_result_record "FAIL" "UART loopback failed on $UART_DEVICE at $UART_BAUD baud, see $RESULT_DIR/uart_loopback_${UART_BAUD}.log" + ;; + 2) + test_result_record "SKIP" "UART loopback at $UART_BAUD baud is not runnable on $UART_DEVICE, the preceding UART-LOOPBACK line identifies the console or active owner" + ;; + *) + test_result_record "FAIL" "UART loopback helper returned unexpected status $loopback_status at $UART_BAUD baud" + ;; + esac +done + +test_result_finish diff --git a/Runner/suites/Kernel/Baseport/UART_Validation/README.md b/Runner/suites/Kernel/Baseport/UART_Validation/README.md new file mode 100644 index 00000000..c8d2e446 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/UART_Validation/README.md @@ -0,0 +1,42 @@ +# UART validation + +`UART_Validation` performs read-only functional-readiness validation of enabled +UART controllers and their Linux runtime consumers. It does not assume a fixed +serial-engine address, alias, TTY number, board, or SoC. + +The suite correlates: + +- Enabled runtime DT nodes named `serial@...` or `uart@...` +- Platform-device and driver binding +- Physical TTY class devices +- Active serial-console ownership from `/proc/consoles` +- Bound serdev consumers such as Bluetooth devices + +An enabled controller is healthy when it has a bound controller driver and is +consumed by either a TTY or a bound serdev child. A serdev-owned controller is +not incorrectly required to expose a user-accessible TTY. + +## Run + +```sh +cd Runner/suites/Kernel/Baseport/UART_Validation +./run.sh +``` + +## Result policy + +- `PASS`: discovered UART controllers and consumers are consistently bound. +- `FAIL`: an enabled controller lacks its platform device, driver, TTY, or + bound serdev consumer, or persistent UART kernel errors are present. +- `SKIP`: no enabled or runtime-visible UART capability exists. + +## Diagnostics + +The suite emits bounded per-object binding, consumer, and kernel-health +diagnostics to stdout. Temporary evidence is removed when the run finishes. + +Physical data transfer is intentionally kept out of this evidence suite. +Loopback must use an explicitly selected non-console UART and must restore its +termios state. + +Reference: [Qualcomm Linux UART guide](https://docs.qualcomm.com/doc/80-70023-8/topic/uart.html) diff --git a/Runner/suites/Kernel/Baseport/UART_Validation/UART_Validation.yaml b/Runner/suites/Kernel/Baseport/UART_Validation/UART_Validation.yaml new file mode 100755 index 00000000..2b33f125 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/UART_Validation/UART_Validation.yaml @@ -0,0 +1,15 @@ +metadata: + name: UART_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate UART controller, TTY, console, and serdev runtime readiness" + os: + - linux + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/UART_Validation + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh UART_Validation.res diff --git a/Runner/suites/Kernel/Baseport/UART_Validation/run.sh b/Runner/suites/Kernel/Baseport/UART_Validation/run.sh new file mode 100755 index 00000000..e9a38c2f --- /dev/null +++ b/Runner/suites/Kernel/Baseport/UART_Validation/run.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_bus.sh" +TESTNAME="UART_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +usage() { + printf 'Usage: %s [--help]\n' "$0" +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + *) + log_error "Unknown argument: $1" + usage >&2 + return 2 + ;; + esac + done +} + +parse_args "$@" || exit $? +bus_validation_suite_run uart "$TESTNAME" "$RES_FILE" "$SCRIPT_DIR" From 5484c2f1975635c1d50ff656bc55f85e12716cf3 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Fri, 11 Sep 2026 23:49:17 +0530 Subject: [PATCH 3/5] spi: add readiness and fixture loopback tests Validate SPI runtime binding dynamically and provide explicit internal or fixture-confirmed external transfer coverage without driver-name assumptions. Signed-off-by: Srikanth Muppandam --- .../SPI_Loopback_Validation/README.md | 52 ++++ .../SPI_Loopback_Validation.yaml | 25 ++ .../Baseport/SPI_Loopback_Validation/run.sh | 269 ++++++++++++++++++ .../Kernel/Baseport/SPI_Validation/README.md | 38 +++ .../SPI_Validation/SPI_Validation.yaml | 15 + .../Kernel/Baseport/SPI_Validation/run.sh | 64 +++++ 6 files changed, 463 insertions(+) create mode 100644 Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/README.md create mode 100755 Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/SPI_Loopback_Validation.yaml create mode 100755 Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/run.sh create mode 100644 Runner/suites/Kernel/Baseport/SPI_Validation/README.md create mode 100755 Runner/suites/Kernel/Baseport/SPI_Validation/SPI_Validation.yaml create mode 100755 Runner/suites/Kernel/Baseport/SPI_Validation/run.sh diff --git a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/README.md b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/README.md new file mode 100644 index 00000000..01321c67 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/README.md @@ -0,0 +1,52 @@ +# SPI loopback validation + +`SPI_Loopback_Validation` runs `spidev_test` against an auto-detected or +explicitly selected `/dev/spidevX.Y` node. The kernel does not expose a +portable runtime indicator for whether controller-internal loopback is +supported, so select `internal` only when the target's documented controller +supports it. External mode requires a MOSI-to-MISO loopback fixture. + +The suite validates command status and compares the exact requested, TX, and RX +byte sequences across SPI modes 0, 1, 2, and 3 by default. It never creates +spidev nodes, changes DT configuration, installs tools, or takes control of a +production SPI client. A unique accessible +`spidev` node is selected automatically. Multiple nodes require an explicit +override so the suite does not guess which device has the loopback fixture. +When the running kernel exposes standard SPI device statistics, the suite also +requires the transfer not to increment the `errors` or `timedout` counters. + +Use `--loopback internal` for a documented controller-internal loopback path, +or `--loopback external --fixture` for a wired fixture. Automatic mode skips +instead of inferring loopback capability from a driver name. + +## Run + +```sh +cd Runner/suites/Kernel/Baseport/SPI_Loopback_Validation +./run.sh --loopback internal +``` + +Optional settings: + +```sh +./run.sh --device /dev/spidev0.0 --speed 5000000 --bits 8 --modes 0,1,2,3 --loopback external --fixture --timeout 15 +``` + +Use `--device` or `SPI_DEVICE` to override automatic discovery. `SPI_MODES`, +`SPI_LOOPBACK_TYPE`, and `SPI_LOOPBACK_FIXTURE` control the functional matrix. +CLI options take precedence over environment variables. + +Temporary transfer evidence is removed when the run finishes. The live log +retains the bounded per-case failure analysis. + +## Result policy + +- `PASS`: requested, TX, and RX bytes match exactly without new SPI error or + timeout counters when those counters are exposed. +- `FAIL`: the selected node is invalid, the command fails or times out, output + cannot be validated, the received bytes differ, or an exposed SPI error or + timeout counter increases during the transfer. +- `SKIP`: no unique accessible `spidev` node is discovered and no override is + provided, or `spidev_test` is not provided by the image. + +Reference: [Qualcomm Linux SPI guide](https://docs.qualcomm.com/doc/80-70023-8/topic/spi.html) diff --git a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/SPI_Loopback_Validation.yaml b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/SPI_Loopback_Validation.yaml new file mode 100755 index 00000000..21d3b9aa --- /dev/null +++ b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/SPI_Loopback_Validation.yaml @@ -0,0 +1,25 @@ +metadata: + name: SPI_Loopback_Validation + format: "Lava-Test Test Definition 1.0" + description: "Run bounded exact-byte loopback on an auto-detected or selected spidev fixture" + os: + - linux + scope: + - functional + +params: + SPI_DEVICE: "" + SPI_SPEED_HZ: "1000000" + SPI_BITS_PER_WORD: "8" + SPI_MODES: "0,1,2,3" + SPI_LOOPBACK_TYPE: "auto" + SPI_LOOPBACK_PAYLOAD: "QLI_SPI_LOOPBACK" + SPI_LOOPBACK_TIMEOUT: "10" + SPI_LOOPBACK_FIXTURE: "0" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/SPI_Loopback_Validation + - SPI_DEVICE="${SPI_DEVICE}" SPI_SPEED_HZ="${SPI_SPEED_HZ}" SPI_BITS_PER_WORD="${SPI_BITS_PER_WORD}" SPI_MODES="${SPI_MODES}" SPI_LOOPBACK_TYPE="${SPI_LOOPBACK_TYPE}" SPI_LOOPBACK_PAYLOAD="${SPI_LOOPBACK_PAYLOAD}" SPI_LOOPBACK_TIMEOUT="${SPI_LOOPBACK_TIMEOUT}" SPI_LOOPBACK_FIXTURE="${SPI_LOOPBACK_FIXTURE}" ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh SPI_Loopback_Validation.res diff --git a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/run.sh b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/run.sh new file mode 100755 index 00000000..dfa26071 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/run.sh @@ -0,0 +1,269 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_bus.sh" +TESTNAME="SPI_Loopback_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +RESULT_DIR="$SCRIPT_DIR/.${TESTNAME}.work.$$" +SPI_DEVICE="${SPI_DEVICE:-}" +SPI_SPEED_HZ="${SPI_SPEED_HZ:-1000000}" +SPI_BITS_PER_WORD="${SPI_BITS_PER_WORD:-8}" +SPI_MODES="${SPI_MODES:-${SPI_MODE:-0,1,2,3}}" +SPI_LOOPBACK_TYPE="${SPI_LOOPBACK_TYPE:-auto}" +SPI_LOOPBACK_PAYLOAD="${SPI_LOOPBACK_PAYLOAD:-QLI_SPI_LOOPBACK}" +SPI_LOOPBACK_TIMEOUT="${SPI_LOOPBACK_TIMEOUT:-10}" +SPI_LOOPBACK_FIXTURE="${SPI_LOOPBACK_FIXTURE:-0}" + +cleanup() { + rm -rf "$RESULT_DIR" +} + +usage() { + cat <&2 + exit 2 +fi +if ! validate_config; then + usage >&2 + exit 2 +fi + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 +trap cleanup EXIT HUP INT TERM + +if ! mkdir -p "$RESULT_DIR"; then + test_result_finish "FAIL" "$TESTNAME FAIL: cannot create temporary evidence directory" +fi + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +if ! bus_validation_require_commands basename cat cmp dirname grep mkdir od readlink rm tr wc; then + test_result_finish "SKIP" "$TESTNAME SKIP: required image-provided SPI loopback utilities are unavailable" +fi + +SPI_SELECTION_SOURCE=override +if [ -z "$SPI_DEVICE" ]; then + SPI_SELECTION_SOURCE=auto + SPI_DEVICE=$(spi_loopback_select_device 2>/dev/null) + SPI_SELECTION_STATUS=$? + if [ "$SPI_SELECTION_STATUS" -eq 1 ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: multiple accessible spidev nodes were detected, select one with --device or SPI_DEVICE" + fi +fi +if [ -z "$SPI_DEVICE" ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: no accessible image-provided spidev node was detected" +fi +if [ "$SPI_LOOPBACK_TYPE" = "auto" ]; then + SPI_LOOPBACK_TYPE=$(spi_loopback_select_type "$SPI_DEVICE") + SPI_LOOPBACK_SELECTION_STATUS=$? + if [ "$SPI_LOOPBACK_SELECTION_STATUS" -ne 0 ] || [ -z "$SPI_LOOPBACK_TYPE" ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: the loopback type is not observable at runtime, select --loopback internal or --loopback external" + fi +fi +case "$SPI_LOOPBACK_TYPE" in + internal|external) + ;; + *) + test_result_finish "FAIL" "$TESTNAME FAIL: unsupported loopback type $SPI_LOOPBACK_TYPE" + ;; +esac +if [ "$SPI_LOOPBACK_TYPE" = "external" ] && ! bus_validation_bool_true "$SPI_LOOPBACK_FIXTURE"; then + test_result_finish "SKIP" "$TESTNAME SKIP: external SPI loopback fixture was not explicitly enabled, use --fixture or SPI_LOOPBACK_FIXTURE=1" +fi +log_info "Configuration: device=$SPI_DEVICE selection=$SPI_SELECTION_SOURCE speed_hz=$SPI_SPEED_HZ bits=$SPI_BITS_PER_WORD modes=$SPI_MODES loopback=$SPI_LOOPBACK_TYPE timeout=${SPI_LOOPBACK_TIMEOUT}s" + +log_info "SPI loopback: validating exact transfers across the requested CPOL and CPHA mode matrix" +SPI_REMAINING_MODES=$SPI_MODES +while [ -n "$SPI_REMAINING_MODES" ]; do + case "$SPI_REMAINING_MODES" in + *,*) + SPI_MODE=${SPI_REMAINING_MODES%%,*} + SPI_REMAINING_MODES=${SPI_REMAINING_MODES#*,} + ;; + *) + SPI_MODE=$SPI_REMAINING_MODES + SPI_REMAINING_MODES="" + ;; + esac + log_info "---- SPI functional case: device=$SPI_DEVICE mode=$SPI_MODE loopback=$SPI_LOOPBACK_TYPE ----" + spi_loopback_validate \ + "$SPI_DEVICE" \ + "$SPI_SPEED_HZ" \ + "$SPI_BITS_PER_WORD" \ + "$SPI_LOOPBACK_PAYLOAD" \ + "$SPI_LOOPBACK_TIMEOUT" \ + "$RESULT_DIR" \ + "$SPI_MODE" \ + "$SPI_LOOPBACK_TYPE" + loopback_status=$? + case "$loopback_status" in + 0) + test_result_record "PASS" "SPI exact transfer passed on $SPI_DEVICE in mode $SPI_MODE with $SPI_LOOPBACK_TYPE loopback" + ;; + 1) + test_result_record "FAIL" "SPI loopback failed on $SPI_DEVICE in mode $SPI_MODE, see $RESULT_DIR/spi_loopback_mode${SPI_MODE}_${SPI_LOOPBACK_TYPE}.log" + ;; + 2) + test_result_finish "SKIP" "$TESTNAME SKIP: spidev_test is not provided by the image" + ;; + *) + test_result_record "FAIL" "SPI loopback helper returned unexpected status $loopback_status in mode $SPI_MODE" + ;; + esac +done + +test_result_finish diff --git a/Runner/suites/Kernel/Baseport/SPI_Validation/README.md b/Runner/suites/Kernel/Baseport/SPI_Validation/README.md new file mode 100644 index 00000000..c832b1ce --- /dev/null +++ b/Runner/suites/Kernel/Baseport/SPI_Validation/README.md @@ -0,0 +1,38 @@ +# SPI validation + +`SPI_Validation` validates the functional readiness of enabled SPI controllers +and child devices using Linux SPI masters, runtime client devices, modalias +evidence, and bound drivers. Discovery is based on runtime DT and sysfs instead +of fixed bus or chip-select numbers. + +The suite reports each master and client with its driver, runtime DT node, +modalias, `spi-max-frequency` property bytes, SPI mode flags, runtime power +state, framework error and timeout counters, and supplier-wait state when +available. These fields are diagnostic evidence and optional kernel ABI is not +treated as a failure. + +## Run + +```sh +cd Runner/suites/Kernel/Baseport/SPI_Validation +./run.sh +``` + +## Result policy + +- `PASS`: enabled controllers expose runtime masters and declared clients are + bound. +- `FAIL`: an enabled controller lacks a runtime master, a declared child is + absent, or a runtime client is unbound. +- `SKIP`: no enabled or runtime-visible SPI capability exists. + +## Diagnostics + +The suite emits bounded per-object binding and kernel-health diagnostics to +stdout. Temporary evidence is removed when the run finishes. + +The suite never creates `spidev` nodes, changes DT configuration, probes +arbitrary chip-selects, or installs diagnostic utilities. Fixture-controlled +SPI loopback belongs in a separate functional suite. + +Reference: [Qualcomm Linux SPI guide](https://docs.qualcomm.com/doc/80-70023-8/topic/spi.html) diff --git a/Runner/suites/Kernel/Baseport/SPI_Validation/SPI_Validation.yaml b/Runner/suites/Kernel/Baseport/SPI_Validation/SPI_Validation.yaml new file mode 100755 index 00000000..38700d55 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/SPI_Validation/SPI_Validation.yaml @@ -0,0 +1,15 @@ +metadata: + name: SPI_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate SPI controller, master, client, and driver runtime readiness" + os: + - linux + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/SPI_Validation + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh SPI_Validation.res diff --git a/Runner/suites/Kernel/Baseport/SPI_Validation/run.sh b/Runner/suites/Kernel/Baseport/SPI_Validation/run.sh new file mode 100755 index 00000000..4e686608 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/SPI_Validation/run.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_bus.sh" +TESTNAME="SPI_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +usage() { + printf 'Usage: %s [--help]\n' "$0" +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + *) + log_error "Unknown argument: $1" + usage >&2 + return 2 + ;; + esac + done +} + +parse_args "$@" || exit $? +bus_validation_suite_run spi "$TESTNAME" "$RES_FILE" "$SCRIPT_DIR" From 50cddfaceb85d2a0afc9375ddd0563f64bf36bb8 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Fri, 11 Sep 2026 23:49:29 +0530 Subject: [PATCH 4/5] i3c: add direct Linux runtime validation Validate Linux-visible I3C bindings while keeping remoteproc evidence explicitly indirect when firmware owns the transport. Signed-off-by: Srikanth Muppandam --- .../I3C_Validation/I3C_Validation.yaml | 15 +++++ .../Kernel/Baseport/I3C_Validation/README.md | 33 ++++++++++ .../Kernel/Baseport/I3C_Validation/run.sh | 64 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100755 Runner/suites/Kernel/Baseport/I3C_Validation/I3C_Validation.yaml create mode 100644 Runner/suites/Kernel/Baseport/I3C_Validation/README.md create mode 100755 Runner/suites/Kernel/Baseport/I3C_Validation/run.sh diff --git a/Runner/suites/Kernel/Baseport/I3C_Validation/I3C_Validation.yaml b/Runner/suites/Kernel/Baseport/I3C_Validation/I3C_Validation.yaml new file mode 100755 index 00000000..33bfcb4a --- /dev/null +++ b/Runner/suites/Kernel/Baseport/I3C_Validation/I3C_Validation.yaml @@ -0,0 +1,15 @@ +metadata: + name: I3C_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate direct Linux I3C evidence without treating subsystem health as transport proof" + os: + - linux + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/I3C_Validation + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh I3C_Validation.res diff --git a/Runner/suites/Kernel/Baseport/I3C_Validation/README.md b/Runner/suites/Kernel/Baseport/I3C_Validation/README.md new file mode 100644 index 00000000..ada508eb --- /dev/null +++ b/Runner/suites/Kernel/Baseport/I3C_Validation/README.md @@ -0,0 +1,33 @@ +# I3C validation + +The Qualcomm Linux interface guide states that current I3C sensor use cases +run through aDSP, SLPI, or SDC firmware and do not expose a Linux I3C use case. +`I3C_Validation` therefore does not claim that a healthy sensor +subsystem proves the physical I3C transport. + +The suite checks direct Linux evidence when a future or custom image exposes +it through runtime DT, `/sys/bus/i3c`, or `/sys/class/i3c-master`. I3C masters +are correlated with their parent controller driver, while I3C peripheral +devices require their own direct driver binding. It also records ADSP, SLPI, +SSC, or sensor remoteproc state as explicitly indirect diagnostic evidence. + +## Run + +```sh +cd Runner/suites/Kernel/Baseport/I3C_Validation +./run.sh +``` + +## Result policy + +- `PASS`: direct Linux-visible I3C objects exist and are bound consistently. +- `FAIL`: Linux declares or exposes I3C objects that are missing or unbound. +- `SKIP`: no direct Linux I3C interface exists. Running firmware or sensors + remain indirect evidence and do not convert this result to `PASS`. + +## Diagnostics + +The suite emits bounded direct-I3C and indirect remoteproc evidence to stdout. +Temporary evidence is removed when the run finishes. + +Reference: [Qualcomm Linux I3C guide](https://docs.qualcomm.com/doc/80-70023-8/topic/i3c.html) diff --git a/Runner/suites/Kernel/Baseport/I3C_Validation/run.sh b/Runner/suites/Kernel/Baseport/I3C_Validation/run.sh new file mode 100755 index 00000000..e645f3ef --- /dev/null +++ b/Runner/suites/Kernel/Baseport/I3C_Validation/run.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_bus.sh" +TESTNAME="I3C_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +usage() { + printf 'Usage: %s [--help]\n' "$0" +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + *) + log_error "Unknown argument: $1" + usage >&2 + return 2 + ;; + esac + done +} + +parse_args "$@" || exit $? +bus_validation_suite_run i3c "$TESTNAME" "$RES_FILE" "$SCRIPT_DIR" From e4b0e6efd0771281e31579358588a7cffec35320 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Fri, 11 Sep 2026 23:49:45 +0530 Subject: [PATCH 5/5] bus: harden UART, SPI, I3C, and CAN validation Retain visible per-run evidence, improve dynamic UART and Bluetooth diagnostics, and add exact controller-internal UART loopback through a state-restoring TIOCM_LOOP helper.\n\nValidate all safe UART, SPI, and CAN internal-loopback candidates sequentially while keeping external fixture selection explicit. Keep every suite image-provided-only and align the YAML and README contracts with the runtime behavior. Signed-off-by: Srikanth Muppandam --- .../CAN_Internal_Loopback_Validation.yaml | 22 + .../README.md | 63 ++ .../CAN_Internal_Loopback_Validation/run.sh | 232 ++++++ .../CAN_Validation/CAN_Validation.yaml | 15 + .../Kernel/Baseport/CAN_Validation/README.md | 39 ++ .../Kernel/Baseport/CAN_Validation/run.sh | 64 ++ .../Kernel/Baseport/I3C_Validation/README.md | 2 +- .../SPI_Loopback_Validation/README.md | 23 +- .../SPI_Loopback_Validation.yaml | 2 +- .../Baseport/SPI_Loopback_Validation/run.sh | 126 ++-- .../Kernel/Baseport/SPI_Validation/README.md | 2 +- .../UART_Loopback_Validation/README.md | 96 ++- .../UART_Loopback_Validation.yaml | 13 +- .../Baseport/UART_Loopback_Validation/run.sh | 292 ++++++-- .../Kernel/Baseport/UART_Validation/README.md | 14 +- Runner/utils/lib_bus.sh | 663 ++++++++++++++---- Runner/utils/uart_tiocm_loopback.py | 289 ++++++++ 17 files changed, 1686 insertions(+), 271 deletions(-) create mode 100755 Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/CAN_Internal_Loopback_Validation.yaml create mode 100644 Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/README.md create mode 100755 Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/run.sh create mode 100755 Runner/suites/Kernel/Baseport/CAN_Validation/CAN_Validation.yaml create mode 100644 Runner/suites/Kernel/Baseport/CAN_Validation/README.md create mode 100755 Runner/suites/Kernel/Baseport/CAN_Validation/run.sh create mode 100755 Runner/utils/uart_tiocm_loopback.py diff --git a/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/CAN_Internal_Loopback_Validation.yaml b/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/CAN_Internal_Loopback_Validation.yaml new file mode 100755 index 00000000..96171601 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/CAN_Internal_Loopback_Validation.yaml @@ -0,0 +1,22 @@ +metadata: + name: CAN_Internal_Loopback_Validation + format: "Lava-Test Test Definition 1.0" + description: "Run transactional exact-frame loopback on all safe auto-detected or one selected CAN interface" + os: + - linux + scope: + - functional + +params: + CAN_INTERFACE: "" + CAN_LOOPBACK_MODE: "auto" + CAN_BITRATE: "500000" + CAN_DBITRATE: "2000000" + CAN_LOOPBACK_TIMEOUT: "10" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation + - CAN_INTERFACE="${CAN_INTERFACE}" CAN_LOOPBACK_MODE="${CAN_LOOPBACK_MODE}" CAN_BITRATE="${CAN_BITRATE}" CAN_DBITRATE="${CAN_DBITRATE}" CAN_LOOPBACK_TIMEOUT="${CAN_LOOPBACK_TIMEOUT}" ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh CAN_Internal_Loopback_Validation.res diff --git a/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/README.md b/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/README.md new file mode 100644 index 00000000..691c26cd --- /dev/null +++ b/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/README.md @@ -0,0 +1,63 @@ +# CAN internal loopback validation + +`CAN_Internal_Loopback_Validation` sends and receives exact SocketCAN frames +through an auto-detected or explicitly selected physical CAN controller in +internal-loopback mode. + +For safety, the suite requires each interface to be initially down. It configures +each selected interface with a default 500 kbit/s arbitration bitrate and a +2 Mbit/s CAN FD data bitrate. An active CAN interface is never taken over +automatically. Every physical interface meeting these readiness conditions is +selected automatically and validated sequentially. + +The suite restores the interface to down and restores the original bitrate, +data bitrate, CAN FD, and loopback settings when they were initially exposed. +Linux does not provide a portable operation to return a previously unconfigured +physical CAN interface to an unconfigured timing state. In that case the test +leaves the interface down with the test bitrate configured and reports that +state in stdout. + +Classic mode validates both an 11-bit identifier and a 29-bit extended +identifier. Automatic mode attempts a CAN-FD frame using the default data +bitrate and records a clean skip when the controller rejects CAN FD. Every case +requires RX and TX packet counters to increase and error counters not to +increase when sysfs exposes those counters. + +## Prepare and run + +Run automatic CAN loopback with the default bitrates: + +```sh +cd Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation +./run.sh +``` + +Override the selected interface or timing when required: + +```sh +./run.sh --interface can0 --mode auto --bitrate 500000 --dbitrate 2000000 +``` + +Use `--interface` or `CAN_INTERFACE` to override automatic discovery. +`CAN_BITRATE` and `CAN_DBITRATE` override the timing defaults. CLI options take +precedence over environment variables. + +With no override, the suite discovers and sequentially validates every down +physical CAN interface. Use `--interface` or `CAN_INTERFACE` to restrict +validation to one interface. The test uses image-provided `ip`, `candump`, and +`cansend` only and skips cleanly when those optional utilities are absent. + +Command and frame evidence is retained under +`results/CAN_Internal_Loopback_Validation/run-*//`. The +live log retains the per-object failure analysis needed for remote debugging. + +## Result policy + +- `PASS`: all applicable exact frames are observed, packet counters increase, + error counters remain stable, and state restoration succeeds. +- `FAIL`: the selected object is not physical CAN hardware, configuration, + transfer, frame validation, or restoration fails. +- `SKIP`: no ready physical interface is discovered, required image-provided + tools are absent, or CAN FD is unavailable during automatic mode. + +Reference: [Qualcomm Linux CAN guide](https://docs.qualcomm.com/doc/80-70023-8/topic/can.html) diff --git a/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/run.sh b/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/run.sh new file mode 100755 index 00000000..f453e2de --- /dev/null +++ b/Runner/suites/Kernel/Baseport/CAN_Internal_Loopback_Validation/run.sh @@ -0,0 +1,232 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_bus.sh" +TESTNAME="CAN_Internal_Loopback_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +RESULT_DIR="$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$$" +CAN_INTERFACE="${CAN_INTERFACE:-}" +CAN_LOOPBACK_MODE="${CAN_LOOPBACK_MODE:-auto}" +CAN_BITRATE="${CAN_BITRATE:-500000}" +CAN_DBITRATE="${CAN_DBITRATE:-2000000}" +CAN_LOOPBACK_TIMEOUT="${CAN_LOOPBACK_TIMEOUT:-10}" + +cleanup() { + can_internal_loopback_cleanup >/dev/null 2>&1 || true +} + +usage() { + cat <&2 + exit 2 +fi +if ! validate_config; then + usage >&2 + exit 2 +fi + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 +trap cleanup EXIT HUP INT TERM + +if ! mkdir -p "$RESULT_DIR"; then + test_result_finish "FAIL" "$TESTNAME FAIL: cannot create retained evidence directory $RESULT_DIR" +fi + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "Evidence directory: $RESULT_DIR" +CAN_SELECTION_SOURCE=override +CAN_SELECTED_INTERFACES="$RESULT_DIR/can_selected_interfaces.log" +: >"$CAN_SELECTED_INTERFACES" +if [ -n "$CAN_INTERFACE" ]; then + if ! can_internal_loopback_interface_is_ready "$CAN_INTERFACE"; then + test_result_finish "FAIL" "$TESTNAME FAIL: requested interface $CAN_INTERFACE is not a down physical SocketCAN interface" + fi + printf '%s\n' "$CAN_INTERFACE" >"$CAN_SELECTED_INTERFACES" +else + CAN_SELECTION_SOURCE=auto-all + can_internal_loopback_list_interfaces "$CAN_LOOPBACK_MODE" >"$CAN_SELECTED_INTERFACES" 2>/dev/null + CAN_SELECTION_STATUS=$? + if [ "$CAN_SELECTION_STATUS" -eq 3 ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: unsupported CAN loopback mode $CAN_LOOPBACK_MODE" + fi +fi +if [ ! -s "$CAN_SELECTED_INTERFACES" ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: no down physical CAN interface was detected" +fi +if ! bus_validation_require_commands awk candump cansend cat grep ip mkdir sed sleep tr; then + test_result_finish "SKIP" "$TESTNAME SKIP: image-provided ip or CAN-utils are unavailable" +fi +log_info "CAN loopback: validating standard, extended, and available FD frame paths while checking packet and error-counter deltas" +CAN_SELECTED_COUNT=$(awk 'NF { count++ } END { print count + 0 }' "$CAN_SELECTED_INTERFACES") +log_info "CAN selection: source=$CAN_SELECTION_SOURCE selected_interfaces=$CAN_SELECTED_COUNT list=$CAN_SELECTED_INTERFACES" +while IFS= read -r CAN_INTERFACE; do + [ -n "$CAN_INTERFACE" ] || continue + CAN_INTERFACE_RESULT_DIR="$RESULT_DIR/$CAN_INTERFACE" + if ! mkdir -p "$CAN_INTERFACE_RESULT_DIR"; then + test_result_record "FAIL" "CAN evidence directory could not be created for $CAN_INTERFACE at $CAN_INTERFACE_RESULT_DIR" + continue + fi + log_info "[CAN-SELECTION] interface=$CAN_INTERFACE source=$CAN_SELECTION_SOURCE" + CAN_LOOPBACK_MODES=$CAN_LOOPBACK_MODE + if [ "$CAN_LOOPBACK_MODE" = "auto" ]; then + CAN_LOOPBACK_MODES=$(can_internal_loopback_select_modes "$CAN_INTERFACE") + CAN_MODE_SELECTION_STATUS=$? + if [ "$CAN_MODE_SELECTION_STATUS" -ne 0 ] || [ -z "$CAN_LOOPBACK_MODES" ]; then + test_result_record "FAIL" "Could not derive loopback modes for $CAN_INTERFACE" + continue + fi + fi + log_info "Configuration: interface=$CAN_INTERFACE selection=$CAN_SELECTION_SOURCE modes=$CAN_LOOPBACK_MODES bitrate=$CAN_BITRATE dbitrate=$CAN_DBITRATE timeout=${CAN_LOOPBACK_TIMEOUT}s evidence=$CAN_INTERFACE_RESULT_DIR" + CAN_REMAINING_MODES=$CAN_LOOPBACK_MODES + while [ -n "$CAN_REMAINING_MODES" ]; do + case "$CAN_REMAINING_MODES" in + *,*) + CAN_MODE=${CAN_REMAINING_MODES%%,*} + CAN_REMAINING_MODES=${CAN_REMAINING_MODES#*,} + ;; + *) + CAN_MODE=$CAN_REMAINING_MODES + CAN_REMAINING_MODES="" + ;; + esac + log_info "---- CAN functional case: interface=$CAN_INTERFACE mode=$CAN_MODE ----" + can_internal_loopback_validate \ + "$CAN_INTERFACE" \ + "$CAN_MODE" \ + "$CAN_LOOPBACK_TIMEOUT" \ + "$CAN_INTERFACE_RESULT_DIR" \ + "$CAN_BITRATE" \ + "$CAN_DBITRATE" + loopback_status=$? + case "$loopback_status" in + 0) + test_result_record "PASS" "CAN $CAN_MODE internal loopback frames and counter deltas passed on $CAN_INTERFACE" + ;; + 1) + test_result_record "FAIL" "CAN $CAN_MODE internal loopback failed on $CAN_INTERFACE, see $CAN_INTERFACE_RESULT_DIR/can_loopback_rx_${CAN_MODE}.log and $CAN_INTERFACE_RESULT_DIR/can_loopback_statistics_${CAN_MODE}.log" + ;; + 2) + if [ "$CAN_LOOPBACK_MODE" = "fd" ]; then + test_result_record "FAIL" "CAN FD was explicitly requested but $CAN_INTERFACE rejected bitrate=$CAN_BITRATE dbitrate=$CAN_DBITRATE configuration" + else + test_result_record "SKIP" "$CAN_INTERFACE does not accept the automatic $CAN_MODE configuration" + fi + ;; + *) + test_result_record "FAIL" "CAN loopback helper returned unexpected status $loopback_status for mode $CAN_MODE" + ;; + esac + done +done <"$CAN_SELECTED_INTERFACES" + +test_result_finish diff --git a/Runner/suites/Kernel/Baseport/CAN_Validation/CAN_Validation.yaml b/Runner/suites/Kernel/Baseport/CAN_Validation/CAN_Validation.yaml new file mode 100755 index 00000000..38a7aa27 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/CAN_Validation/CAN_Validation.yaml @@ -0,0 +1,15 @@ +metadata: + name: CAN_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate CAN controller binding and SocketCAN runtime readiness" + os: + - linux + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/CAN_Validation + - ./run.sh || true + - $REPO_PATH/Runner/utils/send-to-lava.sh CAN_Validation.res diff --git a/Runner/suites/Kernel/Baseport/CAN_Validation/README.md b/Runner/suites/Kernel/Baseport/CAN_Validation/README.md new file mode 100644 index 00000000..0ffbae1a --- /dev/null +++ b/Runner/suites/Kernel/Baseport/CAN_Validation/README.md @@ -0,0 +1,39 @@ +# CAN validation + +`CAN_Validation` validates CAN hardware readiness from runtime DT and SocketCAN +interfaces. It does not assume `can0`, a fixed SPI bus, chip select, board, SoC, +or a single valid CAN controller driver. + +For each capability, the suite reports the parent runtime device, bound driver, +DT identity, SocketCAN interface, administrative state, CAN protocol state, +classic and CAN FD bitrates, controller mode, bus error counters, and network +RX/TX error counters. Detailed `ip` output is retained when the image provides +the tool. + +## Run + +```sh +cd Runner/suites/Kernel/Baseport/CAN_Validation +./run.sh +``` + +## Result policy + +- `PASS`: every discovered CAN declaration has a bound runtime device and + SocketCAN interface. +- `FAIL`: a declared CAN device is missing, unbound, lacks its SocketCAN + interface, reports a current error-warning, error-passive, or bus-off state, + or persistent CAN or parent-SPI kernel errors are present. +- `SKIP`: no enabled or runtime-visible CAN capability exists. + +## Diagnostics + +The suite emits bounded per-device binding, SocketCAN, and kernel-health +diagnostics to stdout. Evidence is retained under +`results/CAN_Validation/run-*/`. + +Internal and external traffic validation are intentionally separate because +they change interface state and require either controlled loopback or external +fixtures with reliable cleanup. + +Reference: [Qualcomm Linux CAN guide](https://docs.qualcomm.com/doc/80-70023-8/topic/can.html) diff --git a/Runner/suites/Kernel/Baseport/CAN_Validation/run.sh b/Runner/suites/Kernel/Baseport/CAN_Validation/run.sh new file mode 100755 index 00000000..552a43b8 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/CAN_Validation/run.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_bus.sh" +TESTNAME="CAN_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +usage() { + printf 'Usage: %s [--help]\n' "$0" +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + *) + log_error "Unknown argument: $1" + usage >&2 + return 2 + ;; + esac + done +} + +parse_args "$@" || exit $? +bus_validation_suite_run can "$TESTNAME" "$RES_FILE" "$SCRIPT_DIR" diff --git a/Runner/suites/Kernel/Baseport/I3C_Validation/README.md b/Runner/suites/Kernel/Baseport/I3C_Validation/README.md index ada508eb..c0b6f669 100644 --- a/Runner/suites/Kernel/Baseport/I3C_Validation/README.md +++ b/Runner/suites/Kernel/Baseport/I3C_Validation/README.md @@ -28,6 +28,6 @@ cd Runner/suites/Kernel/Baseport/I3C_Validation ## Diagnostics The suite emits bounded direct-I3C and indirect remoteproc evidence to stdout. -Temporary evidence is removed when the run finishes. +Evidence is retained under `results/I3C_Validation/run-*/`. Reference: [Qualcomm Linux I3C guide](https://docs.qualcomm.com/doc/80-70023-8/topic/i3c.html) diff --git a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/README.md b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/README.md index 01321c67..23e686df 100644 --- a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/README.md +++ b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/README.md @@ -9,9 +9,9 @@ supports it. External mode requires a MOSI-to-MISO loopback fixture. The suite validates command status and compares the exact requested, TX, and RX byte sequences across SPI modes 0, 1, 2, and 3 by default. It never creates spidev nodes, changes DT configuration, installs tools, or takes control of a -production SPI client. A unique accessible -`spidev` node is selected automatically. Multiple nodes require an explicit -override so the suite does not guess which device has the loopback fixture. +production SPI client. Internal mode selects every accessible `spidev` node. +External mode requires a unique node or an explicit override so the suite does +not guess which device has the loopback fixture. When the running kernel exposes standard SPI device statistics, the suite also requires the transfer not to increment the `errors` or `timedout` counters. @@ -32,11 +32,15 @@ Optional settings: ./run.sh --device /dev/spidev0.0 --speed 5000000 --bits 8 --modes 0,1,2,3 --loopback external --fixture --timeout 15 ``` -Use `--device` or `SPI_DEVICE` to override automatic discovery. `SPI_MODES`, -`SPI_LOOPBACK_TYPE`, and `SPI_LOOPBACK_FIXTURE` control the functional matrix. -CLI options take precedence over environment variables. +Use `--device` or `SPI_DEVICE` to override automatic discovery. Internal mode +sequentially validates every accessible spidev node when no override is given. +External mode requires a unique node because runtime discovery cannot infer +which device has the fixture return path. `SPI_MODES`, `SPI_LOOPBACK_TYPE`, and +`SPI_LOOPBACK_FIXTURE` control the functional matrix. CLI options take +precedence over environment variables. -Temporary transfer evidence is removed when the run finishes. The live log +Transfer evidence is retained under +`results/SPI_Loopback_Validation/run-*//`. The live log retains the bounded per-case failure analysis. ## Result policy @@ -46,7 +50,8 @@ retains the bounded per-case failure analysis. - `FAIL`: the selected node is invalid, the command fails or times out, output cannot be validated, the received bytes differ, or an exposed SPI error or timeout counter increases during the transfer. -- `SKIP`: no unique accessible `spidev` node is discovered and no override is - provided, or `spidev_test` is not provided by the image. +- `SKIP`: no accessible `spidev` node is discovered, external mode finds an + ambiguous set without an override, or `spidev_test` is not provided by the + image. Reference: [Qualcomm Linux SPI guide](https://docs.qualcomm.com/doc/80-70023-8/topic/spi.html) diff --git a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/SPI_Loopback_Validation.yaml b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/SPI_Loopback_Validation.yaml index 21d3b9aa..b5680293 100755 --- a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/SPI_Loopback_Validation.yaml +++ b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/SPI_Loopback_Validation.yaml @@ -1,7 +1,7 @@ metadata: name: SPI_Loopback_Validation format: "Lava-Test Test Definition 1.0" - description: "Run bounded exact-byte loopback on an auto-detected or selected spidev fixture" + description: "Run bounded exact-byte loopback on safe auto-detected or selected spidev devices" os: - linux scope: diff --git a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/run.sh b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/run.sh index dfa26071..bb7c6fac 100755 --- a/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/run.sh +++ b/Runner/suites/Kernel/Baseport/SPI_Loopback_Validation/run.sh @@ -40,7 +40,7 @@ fi TESTNAME="SPI_Loopback_Validation" RES_FILE="$SCRIPT_DIR/$TESTNAME.res" -RESULT_DIR="$SCRIPT_DIR/.${TESTNAME}.work.$$" +RESULT_DIR="$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$$" SPI_DEVICE="${SPI_DEVICE:-}" SPI_SPEED_HZ="${SPI_SPEED_HZ:-1000000}" SPI_BITS_PER_WORD="${SPI_BITS_PER_WORD:-8}" @@ -50,10 +50,6 @@ SPI_LOOPBACK_PAYLOAD="${SPI_LOOPBACK_PAYLOAD:-QLI_SPI_LOOPBACK}" SPI_LOOPBACK_TIMEOUT="${SPI_LOOPBACK_TIMEOUT:-10}" SPI_LOOPBACK_FIXTURE="${SPI_LOOPBACK_FIXTURE:-0}" -cleanup() { - rm -rf "$RESULT_DIR" -} - usage() { cat <"$SPI_SELECTED_DEVICES" +if [ -n "$SPI_DEVICE" ]; then + printf '%s\n' "$SPI_DEVICE" >"$SPI_SELECTED_DEVICES" +elif [ "$SPI_LOOPBACK_TYPE" = "internal" ]; then + SPI_SELECTION_SOURCE=auto-all + spi_loopback_list_devices >"$SPI_SELECTED_DEVICES" 2>/dev/null + SPI_SELECTION_STATUS=$? +else SPI_SELECTION_SOURCE=auto - SPI_DEVICE=$(spi_loopback_select_device 2>/dev/null) + SPI_SELECTED_DEVICE=$(spi_loopback_select_device 2>/dev/null) SPI_SELECTION_STATUS=$? if [ "$SPI_SELECTION_STATUS" -eq 1 ]; then test_result_finish "SKIP" "$TESTNAME SKIP: multiple accessible spidev nodes were detected, select one with --device or SPI_DEVICE" fi + if [ "$SPI_SELECTION_STATUS" -eq 0 ] && [ -n "$SPI_SELECTED_DEVICE" ]; then + printf '%s\n' "$SPI_SELECTED_DEVICE" >"$SPI_SELECTED_DEVICES" + fi fi -if [ -z "$SPI_DEVICE" ]; then +if [ ! -s "$SPI_SELECTED_DEVICES" ]; then test_result_finish "SKIP" "$TESTNAME SKIP: no accessible image-provided spidev node was detected" fi if [ "$SPI_LOOPBACK_TYPE" = "auto" ]; then - SPI_LOOPBACK_TYPE=$(spi_loopback_select_type "$SPI_DEVICE") + SPI_LOOPBACK_TYPE=$(spi_loopback_select_type auto) SPI_LOOPBACK_SELECTION_STATUS=$? if [ "$SPI_LOOPBACK_SELECTION_STATUS" -ne 0 ] || [ -z "$SPI_LOOPBACK_TYPE" ]; then test_result_finish "SKIP" "$TESTNAME SKIP: the loopback type is not observable at runtime, select --loopback internal or --loopback external" @@ -224,46 +231,63 @@ esac if [ "$SPI_LOOPBACK_TYPE" = "external" ] && ! bus_validation_bool_true "$SPI_LOOPBACK_FIXTURE"; then test_result_finish "SKIP" "$TESTNAME SKIP: external SPI loopback fixture was not explicitly enabled, use --fixture or SPI_LOOPBACK_FIXTURE=1" fi -log_info "Configuration: device=$SPI_DEVICE selection=$SPI_SELECTION_SOURCE speed_hz=$SPI_SPEED_HZ bits=$SPI_BITS_PER_WORD modes=$SPI_MODES loopback=$SPI_LOOPBACK_TYPE timeout=${SPI_LOOPBACK_TIMEOUT}s" +SPI_SELECTED_COUNT=$(awk 'NF { count++ } END { print count + 0 }' "$SPI_SELECTED_DEVICES") +log_info "SPI selection: source=$SPI_SELECTION_SOURCE selected_devices=$SPI_SELECTED_COUNT list=$SPI_SELECTED_DEVICES" log_info "SPI loopback: validating exact transfers across the requested CPOL and CPHA mode matrix" -SPI_REMAINING_MODES=$SPI_MODES -while [ -n "$SPI_REMAINING_MODES" ]; do - case "$SPI_REMAINING_MODES" in - *,*) - SPI_MODE=${SPI_REMAINING_MODES%%,*} - SPI_REMAINING_MODES=${SPI_REMAINING_MODES#*,} - ;; - *) - SPI_MODE=$SPI_REMAINING_MODES - SPI_REMAINING_MODES="" - ;; - esac - log_info "---- SPI functional case: device=$SPI_DEVICE mode=$SPI_MODE loopback=$SPI_LOOPBACK_TYPE ----" - spi_loopback_validate \ - "$SPI_DEVICE" \ - "$SPI_SPEED_HZ" \ - "$SPI_BITS_PER_WORD" \ - "$SPI_LOOPBACK_PAYLOAD" \ - "$SPI_LOOPBACK_TIMEOUT" \ - "$RESULT_DIR" \ - "$SPI_MODE" \ - "$SPI_LOOPBACK_TYPE" - loopback_status=$? - case "$loopback_status" in - 0) - test_result_record "PASS" "SPI exact transfer passed on $SPI_DEVICE in mode $SPI_MODE with $SPI_LOOPBACK_TYPE loopback" - ;; - 1) - test_result_record "FAIL" "SPI loopback failed on $SPI_DEVICE in mode $SPI_MODE, see $RESULT_DIR/spi_loopback_mode${SPI_MODE}_${SPI_LOOPBACK_TYPE}.log" - ;; - 2) - test_result_finish "SKIP" "$TESTNAME SKIP: spidev_test is not provided by the image" - ;; - *) - test_result_record "FAIL" "SPI loopback helper returned unexpected status $loopback_status in mode $SPI_MODE" - ;; - esac -done +while IFS= read -r SPI_DEVICE; do + [ -n "$SPI_DEVICE" ] || continue + SPI_DEVICE_NAME=$(basename "$SPI_DEVICE") + SPI_DEVICE_RESULT_DIR="$RESULT_DIR/$SPI_DEVICE_NAME" + if ! mkdir -p "$SPI_DEVICE_RESULT_DIR"; then + test_result_record "FAIL" "SPI evidence directory could not be created for $SPI_DEVICE at $SPI_DEVICE_RESULT_DIR" + continue + fi + log_info "[SPI-SELECTION] device=$SPI_DEVICE source=$SPI_SELECTION_SOURCE loopback=$SPI_LOOPBACK_TYPE" + if [ ! -c "$SPI_DEVICE" ] || [ ! -r "$SPI_DEVICE" ] || [ ! -w "$SPI_DEVICE" ]; then + log_fail "[SPI-LOOPBACK] device=$SPI_DEVICE expected=accessible-spidev-character-device observed=missing-wrong-type-or-inaccessible selection=$SPI_SELECTION_SOURCE" + test_result_record "FAIL" "Selected SPI loopback device $SPI_DEVICE is not an accessible image-provided spidev node" + continue + fi + log_info "Configuration: device=$SPI_DEVICE selection=$SPI_SELECTION_SOURCE speed_hz=$SPI_SPEED_HZ bits=$SPI_BITS_PER_WORD modes=$SPI_MODES loopback=$SPI_LOOPBACK_TYPE timeout=${SPI_LOOPBACK_TIMEOUT}s evidence=$SPI_DEVICE_RESULT_DIR" + SPI_REMAINING_MODES=$SPI_MODES + while [ -n "$SPI_REMAINING_MODES" ]; do + case "$SPI_REMAINING_MODES" in + *,*) + SPI_MODE=${SPI_REMAINING_MODES%%,*} + SPI_REMAINING_MODES=${SPI_REMAINING_MODES#*,} + ;; + *) + SPI_MODE=$SPI_REMAINING_MODES + SPI_REMAINING_MODES="" + ;; + esac + log_info "---- SPI functional case: device=$SPI_DEVICE mode=$SPI_MODE loopback=$SPI_LOOPBACK_TYPE ----" + spi_loopback_validate \ + "$SPI_DEVICE" \ + "$SPI_SPEED_HZ" \ + "$SPI_BITS_PER_WORD" \ + "$SPI_LOOPBACK_PAYLOAD" \ + "$SPI_LOOPBACK_TIMEOUT" \ + "$SPI_DEVICE_RESULT_DIR" \ + "$SPI_MODE" \ + "$SPI_LOOPBACK_TYPE" + loopback_status=$? + case "$loopback_status" in + 0) + test_result_record "PASS" "SPI exact transfer passed on $SPI_DEVICE in mode $SPI_MODE with $SPI_LOOPBACK_TYPE loopback" + ;; + 1) + test_result_record "FAIL" "SPI loopback failed on $SPI_DEVICE in mode $SPI_MODE, see $SPI_DEVICE_RESULT_DIR/spi_loopback_mode${SPI_MODE}_${SPI_LOOPBACK_TYPE}.log" + ;; + 2) + test_result_finish "SKIP" "$TESTNAME SKIP: spidev_test is not provided by the image" + ;; + *) + test_result_record "FAIL" "SPI loopback helper returned unexpected status $loopback_status in mode $SPI_MODE" + ;; + esac + done +done <"$SPI_SELECTED_DEVICES" test_result_finish diff --git a/Runner/suites/Kernel/Baseport/SPI_Validation/README.md b/Runner/suites/Kernel/Baseport/SPI_Validation/README.md index c832b1ce..0b511387 100644 --- a/Runner/suites/Kernel/Baseport/SPI_Validation/README.md +++ b/Runner/suites/Kernel/Baseport/SPI_Validation/README.md @@ -29,7 +29,7 @@ cd Runner/suites/Kernel/Baseport/SPI_Validation ## Diagnostics The suite emits bounded per-object binding and kernel-health diagnostics to -stdout. Temporary evidence is removed when the run finishes. +stdout. Evidence is retained under `results/SPI_Validation/run-*/`. The suite never creates `spidev` nodes, changes DT configuration, probes arbitrary chip-selects, or installs diagnostic utilities. Fixture-controlled diff --git a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/README.md b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/README.md index 20ffcf13..1706fef5 100644 --- a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/README.md +++ b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/README.md @@ -1,16 +1,19 @@ # UART loopback validation `UART_Loopback_Validation` verifies exact payload transfer through an -auto-detected or explicitly selected UART with an external TX-to-RX loopback -fixture. +auto-detected or explicitly selected UART. It supports controller-internal +loopback through `TIOCM_LOOP` and an external return path provided by TX-to-RX +wiring or a USB-to-UART peer that echoes the transmitted bytes. The suite refuses active kernel consoles, snapshots the complete termios state, uses bounded reads and writes, compares a 4 KiB transmitted and received -payload, and restores the saved state after every baud case. When exactly one -accessible DT-backed physical non-console UART exists, it is selected -automatically. Generic legacy `ttyS*` entries without runtime DT ownership are -ignored. Multiple eligible UARTs require an explicit override to avoid guessing -which port has the loopback fixture. +payload, exercises requested baud and character-width combinations, and +restores the saved state after every case. In internal mode, every accessible, +unused, DT-backed physical non-console UART is selected and validated +sequentially. In external mode, automatic selection still requires exactly one +eligible UART because CI cannot infer which physical port is connected to the +intended fixture or peer. Generic legacy `ttyS*` entries without runtime DT +ownership are ignored. Before changing termios, the suite also compares the selected device with its own stdin, stdout, and stderr and scans current userspace file-descriptor @@ -29,31 +32,86 @@ cd Runner/suites/Kernel/Baseport/UART_Loopback_Validation ./run.sh --fixture ``` +This runs external loopback and therefore requires a confirmed return path, +such as TX-to-RX wiring or a USB-to-UART peer echo process. The `--fixture` +option confirms that this external path exists; it does not create an echo. +For the controller-internal test equivalent to `msm_uart_test -l`, run: + +```sh +./run.sh --loopback internal +``` + +Internal mode uses image-provided `python3` to issue `TIOCMGET` and `TIOCMSET`, +enable `TIOCM_LOOP`, configure and transfer through one open file descriptor, +and restore the original modem-control bits and termios state. It skips cleanly +when Python or the UART driver's loopback ioctl is unavailable. + +For migration from `msm_uart_test`, the focused loopback suite also accepts +`-l`, `-d`, `-b`, `-s`, `-f`, and `-c`. For example: + +```sh +./run.sh -l -d /dev/ttyHS2 -b 115200 -s 400 -f 1 -c 8 +``` + +The legacy nominal mode is not copied because it does not verify received +data. The interactive echo mode is also excluded from unattended CI because +success depends on an external peer and manual interruption. + +`UART_DEVICE` defaults to empty, causing `run.sh` to discover the device from +runtime sysfs and device-tree evidence. Internal mode tests all eligible +devices sequentially. Setting `UART_DEVICE` or `--device` restricts either mode +to that one device. Baud, data bits, flow control, payload size, and timeout +retain their documented defaults. + Optional settings: ```sh -./run.sh --fixture --device /dev/ttyHS1 --baud 115200,921600,3000000 --payload-bytes 4096 --timeout 15 +./run.sh --fixture --baud 115200,921600,3000000 \ + --data-bits 7,8 --flow-control none --payload-bytes 4096 --timeout 15 ``` +Use `--device /dev/` to restrict internal mode to one UART, or +when external mode finds multiple safe UARTs and the return path is attached to +a specific port. + Use `--device` or `UART_DEVICE` to override automatic discovery. CLI options take precedence over environment variables. Other environment variables are -`UART_BAUDS`, `UART_PAYLOAD_BYTES`, `UART_LOOPBACK_TIMEOUT`, and -`UART_LOOPBACK_FIXTURE`. The legacy single-value `UART_BAUD` environment -variable remains supported. +`UART_BAUDS`, `UART_DATA_BITS`, `UART_FLOW_CONTROL`, `UART_PAYLOAD_BYTES`, +`UART_LOOPBACK_TYPE`, `UART_LOOPBACK_TIMEOUT`, and `UART_LOOPBACK_FIXTURE`. The +legacy single-value `UART_BAUD` environment variable remains supported. + +An external return path must be explicitly confirmed with `--fixture` or +`UART_LOOPBACK_FIXTURE=1`. This prevents an unqualified test job from +transmitting on an auto-detected UART. Internal mode does not require this flag. + +The default is 8 data bits with no hardware flow control. Character widths +5, 6, 7, and 8 are available through `--data-bits`. Selecting +`--flow-control rtscts` additionally requires RTS-to-CTS wiring in the fixture. +The suite does not require the proprietary `msm_uart_test` binary. Its probe, +configuration, bounded transfer, exact-data checks, and internal ioctl mode are +implemented using repository helpers and image-provided utilities. + +Payload and command evidence is retained under +`results/UART_Loopback_Validation/run-*/`. Each selected UART has a separate +device-named subdirectory so evidence cannot be overwritten by a later case. The +live log records TX and RX byte counts, checksums, bounded 32-byte hexadecimal +previews, exact byte-for-byte comparison status, restoration evidence, and the +complete payload artifact paths. -An external TX-to-RX loopback fixture must be explicitly enabled with -`--fixture` or `UART_LOOPBACK_FIXTURE=1`. This prevents an unqualified test -job from transmitting on an auto-detected UART. +Key live-log markers are: -Temporary payload and command evidence is removed when the run finishes. The -live log retains the bounded per-case failure analysis. +- `[UART-PAYLOAD]`: direction, byte count, checksum, bounded preview, and file +- `[UART-INTERNAL] state=payload`: SHA-256 proof for internal TX and RX data +- `[UART-PROOF]`: final byte-for-byte comparison and complete artifact paths +- `[UART-RESTORE]`: final termios restoration result ## Result policy -- `PASS`: the exact payload is received and termios restoration succeeds. +- `PASS`: the exact payload is received and all changed state is restored. - `FAIL`: the selected device is unsafe or invalid, configuration or transfer fails, bytes differ, a timeout occurs, or state restoration fails. -- `SKIP`: no unique safe UART is discovered and no override is provided, or - required image utilities are absent. +- `SKIP`: no safe UART is discovered, external mode has ambiguous multiple + candidates without an override, or required utilities or the requested + internal-loopback ioctl are absent. Reference: [Qualcomm Linux UART guide](https://docs.qualcomm.com/doc/80-70023-8/topic/uart.html) diff --git a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/UART_Loopback_Validation.yaml b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/UART_Loopback_Validation.yaml index 6e911b0c..0f2bb814 100755 --- a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/UART_Loopback_Validation.yaml +++ b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/UART_Loopback_Validation.yaml @@ -1,22 +1,31 @@ metadata: name: UART_Loopback_Validation format: "Lava-Test Test Definition 1.0" - description: "Run bounded exact-payload loopback on an auto-detected or selected safe UART" + description: > + Run bounded internal or external exact-payload loopback on dynamically + selected safe UARTs and retain per-device TX/RX proof and restoration evidence os: - linux scope: - functional params: + # Empty tests all eligible UARTs in internal mode and requires a unique UART in external mode. UART_DEVICE: "" + # Auto uses the portable 115200 default. Jobs may override a target-qualified matrix. UART_BAUDS: "auto" + UART_DATA_BITS: "8" + UART_FLOW_CONTROL: "none" + # Internal uses TIOCM_LOOP. External requires UART_LOOPBACK_FIXTURE=1 and a return path. + UART_LOOPBACK_TYPE: "external" UART_PAYLOAD_BYTES: "4096" UART_LOOPBACK_TIMEOUT: "10" + # Confirms external TX/RX wiring or a USB-to-UART peer echo. Ignored by internal mode. UART_LOOPBACK_FIXTURE: "0" run: steps: - REPO_PATH=$PWD - cd Runner/suites/Kernel/Baseport/UART_Loopback_Validation - - UART_DEVICE="${UART_DEVICE}" UART_BAUDS="${UART_BAUDS}" UART_PAYLOAD_BYTES="${UART_PAYLOAD_BYTES}" UART_LOOPBACK_TIMEOUT="${UART_LOOPBACK_TIMEOUT}" UART_LOOPBACK_FIXTURE="${UART_LOOPBACK_FIXTURE}" ./run.sh || true + - UART_DEVICE="${UART_DEVICE}" UART_BAUDS="${UART_BAUDS}" UART_DATA_BITS="${UART_DATA_BITS}" UART_FLOW_CONTROL="${UART_FLOW_CONTROL}" UART_LOOPBACK_TYPE="${UART_LOOPBACK_TYPE}" UART_PAYLOAD_BYTES="${UART_PAYLOAD_BYTES}" UART_LOOPBACK_TIMEOUT="${UART_LOOPBACK_TIMEOUT}" UART_LOOPBACK_FIXTURE="${UART_LOOPBACK_FIXTURE}" ./run.sh || true - $REPO_PATH/Runner/utils/send-to-lava.sh UART_Loopback_Validation.res diff --git a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/run.sh b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/run.sh index 580f33d6..d5000578 100755 --- a/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/run.sh +++ b/Runner/suites/Kernel/Baseport/UART_Loopback_Validation/run.sh @@ -40,16 +40,18 @@ fi TESTNAME="UART_Loopback_Validation" RES_FILE="$SCRIPT_DIR/$TESTNAME.res" -RESULT_DIR="$SCRIPT_DIR/.${TESTNAME}.work.$$" +RESULT_DIR="$SCRIPT_DIR/results/$TESTNAME/run-$(date '+%Y%m%d-%H%M%S')-$$" UART_DEVICE="${UART_DEVICE:-}" UART_BAUDS="${UART_BAUDS:-${UART_BAUD:-auto}}" +UART_DATA_BITS="${UART_DATA_BITS:-8}" +UART_FLOW_CONTROL="${UART_FLOW_CONTROL:-none}" +UART_LOOPBACK_TYPE="${UART_LOOPBACK_TYPE:-external}" UART_PAYLOAD_BYTES="${UART_PAYLOAD_BYTES:-4096}" UART_LOOPBACK_TIMEOUT="${UART_LOOPBACK_TIMEOUT:-10}" UART_LOOPBACK_FIXTURE="${UART_LOOPBACK_FIXTURE:-0}" cleanup() { uart_loopback_cleanup >/dev/null 2>&1 || true - rm -rf "$RESULT_DIR" } usage() { @@ -57,31 +59,63 @@ usage() { Usage: $0 [options] --device PATH Override auto-detection with a non-console UART device --baud LIST Comma-separated baud rates, default: $UART_BAUDS + --data-bits LIST Comma-separated character widths (5,6,7,8), default: $UART_DATA_BITS + --flow-control TYPE none or rtscts, default: $UART_FLOW_CONTROL + --loopback TYPE internal or external, default: $UART_LOOPBACK_TYPE --payload-bytes N Bytes transferred at each baud, default: $UART_PAYLOAD_BYTES --timeout SECONDS Read and write timeout, default: $UART_LOOPBACK_TIMEOUT - --fixture Confirm that an external TX-to-RX loopback fixture is connected + --fixture Confirm external TX/RX wiring or a USB-to-UART peer echo -h, --help Show this help -Environment equivalents: UART_DEVICE, UART_BAUDS, UART_PAYLOAD_BYTES, - UART_LOOPBACK_TIMEOUT, UART_LOOPBACK_FIXTURE. UART_BAUD remains a compatible - single-value fallback. +Legacy-compatible aliases: -l selects internal loopback, -d maps to --device, + -b maps to --baud, -s maps to --payload-bytes, -c maps to --data-bits, and + -f accepts 0 for none or 1 for rtscts flow control. + +Environment equivalents: UART_DEVICE, UART_BAUDS, UART_DATA_BITS, + UART_FLOW_CONTROL, UART_LOOPBACK_TYPE, UART_PAYLOAD_BYTES, UART_LOOPBACK_TIMEOUT, + UART_LOOPBACK_FIXTURE. UART_BAUD remains a compatible single-value fallback. EOF } parse_args() { while [ "$#" -gt 0 ]; do case "$1" in - --device) + --device|-d) [ "$#" -ge 2 ] || return 2 UART_DEVICE="$2" shift 2 ;; - --baud) + --baud|-b) [ "$#" -ge 2 ] || return 2 UART_BAUDS="$2" shift 2 ;; - --payload-bytes) + --data-bits|-c) + [ "$#" -ge 2 ] || return 2 + UART_DATA_BITS="$2" + shift 2 + ;; + --flow-control|-f) + [ "$#" -ge 2 ] || return 2 + case "$2" in + 0) + UART_FLOW_CONTROL=none + ;; + 1) + UART_FLOW_CONTROL=rtscts + ;; + *) + UART_FLOW_CONTROL="$2" + ;; + esac + shift 2 + ;; + --loopback) + [ "$#" -ge 2 ] || return 2 + UART_LOOPBACK_TYPE="$2" + shift 2 + ;; + --payload-bytes|-s) [ "$#" -ge 2 ] || return 2 UART_PAYLOAD_BYTES="$2" shift 2 @@ -95,6 +129,10 @@ parse_args() { UART_LOOPBACK_FIXTURE=1 shift ;; + -l) + UART_LOOPBACK_TYPE=internal + shift + ;; -h|--help) usage exit 0 @@ -148,6 +186,53 @@ validate_config() { esac done fi + + case "$UART_DATA_BITS" in + ''|,*|*,|*,,*) + log_error "UART data-bit list is empty or contains an empty element: $UART_DATA_BITS" + return 2 + ;; + esac + validate_uart_remaining_data_bits=$UART_DATA_BITS + while [ -n "$validate_uart_remaining_data_bits" ]; do + case "$validate_uart_remaining_data_bits" in + *,*) + validate_uart_data_bits=${validate_uart_remaining_data_bits%%,*} + validate_uart_remaining_data_bits=${validate_uart_remaining_data_bits#*,} + ;; + *) + validate_uart_data_bits=$validate_uart_remaining_data_bits + validate_uart_remaining_data_bits="" + ;; + esac + case "$validate_uart_data_bits" in + 5|6|7|8) + ;; + *) + log_error "UART data bits must be one of 5, 6, 7, or 8: $validate_uart_data_bits" + return 2 + ;; + esac + done + + case "$UART_FLOW_CONTROL" in + none|rtscts) + ;; + *) + log_error "UART flow control must be none or rtscts: $UART_FLOW_CONTROL" + return 2 + ;; + esac + + case "$UART_LOOPBACK_TYPE" in + internal|external) + ;; + *) + log_error "UART loopback type must be internal or external: $UART_LOOPBACK_TYPE" + return 2 + ;; + esac + } if ! parse_args "$@"; then @@ -163,74 +248,157 @@ test_result_init "$TESTNAME" "$RES_FILE" || exit 1 trap cleanup EXIT HUP INT TERM if ! mkdir -p "$RESULT_DIR"; then - test_result_finish "FAIL" "$TESTNAME FAIL: cannot create temporary evidence directory" + test_result_finish "FAIL" "$TESTNAME FAIL: cannot create retained evidence directory $RESULT_DIR" fi log_info "--------------------------------------------------------------------------" log_info "Starting $TESTNAME" -if ! bus_validation_require_commands awk basename cmp dd grep mkdir od readlink rm sh sleep stty tr wc; then +log_info "Evidence directory: $RESULT_DIR" +if ! bus_validation_require_commands awk basename cksum cmp grep mkdir od readlink rm sh sleep stty tr wc; then test_result_finish "SKIP" "$TESTNAME SKIP: required image-provided UART loopback utilities are unavailable" fi -if ! bus_validation_bool_true "$UART_LOOPBACK_FIXTURE"; then +if [ "$UART_LOOPBACK_TYPE" = external ] && + ! bus_validation_bool_true "$UART_LOOPBACK_FIXTURE"; then test_result_finish "SKIP" "$TESTNAME SKIP: external UART loopback fixture was not explicitly enabled, use --fixture or UART_LOOPBACK_FIXTURE=1" fi +if [ "$UART_LOOPBACK_TYPE" = external ] && + ! bus_validation_require_commands dd; then + test_result_finish "SKIP" "$TESTNAME SKIP: dd is unavailable for external UART loopback" +fi +if [ "$UART_LOOPBACK_TYPE" = internal ] && + ! bus_validation_require_commands python3; then + test_result_finish "SKIP" "$TESTNAME SKIP: python3 is unavailable for TIOCM_LOOP validation" +fi +if [ "$UART_LOOPBACK_TYPE" = internal ] && + [ ! -r "$TOOLS/uart_tiocm_loopback.py" ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: internal loopback helper is unavailable at $TOOLS/uart_tiocm_loopback.py" +fi +log_info "UART discovery: evaluating runtime DT-backed physical TTYs for access, console use, active owners, driver binding, and runtime PM" +if ! uart_loopback_report_candidates "$RESULT_DIR/uart_candidates.tsv"; then + log_warn "UART candidate diagnostics could not be completed, continuing with capability-driven selection" +fi + +UART_SELECTED_DEVICES="$RESULT_DIR/uart_selected_devices.log" +: >"$UART_SELECTED_DEVICES" UART_SELECTION_SOURCE=override -if [ -z "$UART_DEVICE" ]; then - UART_SELECTION_SOURCE=auto - UART_DEVICE=$(uart_loopback_select_device 2>/dev/null) +if [ -n "$UART_DEVICE" ]; then + printf '%s\n' "$UART_DEVICE" >"$UART_SELECTED_DEVICES" +elif [ "$UART_LOOPBACK_TYPE" = internal ]; then + UART_SELECTION_SOURCE=auto-all + uart_loopback_list_devices >"$UART_SELECTED_DEVICES" 2>/dev/null + UART_SELECTION_STATUS=$? + if [ "$UART_SELECTION_STATUS" -ne 0 ]; then + if [ "$UART_SELECTION_STATUS" -ne 2 ]; then + test_result_finish "FAIL" "$TESTNAME FAIL: UART device discovery failed with status $UART_SELECTION_STATUS" + fi + fi +else + UART_SELECTION_SOURCE=auto-unique + UART_SELECTED_DEVICE=$(uart_loopback_select_device 2>/dev/null) UART_SELECTION_STATUS=$? if [ "$UART_SELECTION_STATUS" -eq 1 ]; then - test_result_finish "SKIP" "$TESTNAME SKIP: multiple eligible UART devices were detected, select one with --device or UART_DEVICE" + test_result_finish "SKIP" "$TESTNAME SKIP: multiple unused DT-backed non-console UART devices were detected, CI cannot infer which port has the external return path, select one with --device or UART_DEVICE" + fi + if [ "$UART_SELECTION_STATUS" -eq 0 ] && [ -n "$UART_SELECTED_DEVICE" ]; then + printf '%s\n' "$UART_SELECTED_DEVICE" >"$UART_SELECTED_DEVICES" fi fi -if [ -z "$UART_DEVICE" ]; then - test_result_finish "SKIP" "$TESTNAME SKIP: no accessible DT-backed physical non-console UART was detected" +if [ ! -s "$UART_SELECTED_DEVICES" ]; then + test_result_finish "SKIP" "$TESTNAME SKIP: no accessible unused DT-backed physical non-console UART was detected" fi -if [ "$UART_BAUDS" = "auto" ]; then - UART_BAUDS=$(uart_loopback_select_bauds "$UART_DEVICE") - UART_BAUD_SELECTION_STATUS=$? - if [ "$UART_BAUD_SELECTION_STATUS" -ne 0 ] || [ -z "$UART_BAUDS" ]; then - test_result_finish "FAIL" "$TESTNAME FAIL: could not derive a baud matrix for $UART_DEVICE" - fi +UART_SELECTED_COUNT=$(awk 'NF { count++ } END { print count + 0 }' "$UART_SELECTED_DEVICES") +UART_FIXTURE_STATE=not-required +if [ "$UART_LOOPBACK_TYPE" = external ]; then + UART_FIXTURE_STATE=external-confirmed fi -log_info "Configuration: device=$UART_DEVICE selection=$UART_SELECTION_SOURCE bauds=$UART_BAUDS payload_bytes=$UART_PAYLOAD_BYTES timeout=${UART_LOOPBACK_TIMEOUT}s fixture=external-confirmed" - -log_info "UART loopback: validating exact data transfer across the selected baud matrix with termios restoration after every case" -UART_REMAINING_BAUDS=$UART_BAUDS -while [ -n "$UART_REMAINING_BAUDS" ]; do - case "$UART_REMAINING_BAUDS" in - *,*) - UART_BAUD=${UART_REMAINING_BAUDS%%,*} - UART_REMAINING_BAUDS=${UART_REMAINING_BAUDS#*,} - ;; - *) - UART_BAUD=$UART_REMAINING_BAUDS - UART_REMAINING_BAUDS="" - ;; - esac - log_info "---- UART functional case: device=$UART_DEVICE baud=$UART_BAUD bytes=$UART_PAYLOAD_BYTES ----" - uart_loopback_validate \ - "$UART_DEVICE" \ - "$UART_BAUD" \ - "$UART_LOOPBACK_TIMEOUT" \ - "$RESULT_DIR" \ - "$UART_PAYLOAD_BYTES" - loopback_status=$? - case "$loopback_status" in - 0) - test_result_record "PASS" "UART exact payload transfer passed on $UART_DEVICE at $UART_BAUD baud" - ;; - 1) - test_result_record "FAIL" "UART loopback failed on $UART_DEVICE at $UART_BAUD baud, see $RESULT_DIR/uart_loopback_${UART_BAUD}.log" - ;; - 2) - test_result_record "SKIP" "UART loopback at $UART_BAUD baud is not runnable on $UART_DEVICE, the preceding UART-LOOPBACK line identifies the console or active owner" - ;; - *) - test_result_record "FAIL" "UART loopback helper returned unexpected status $loopback_status at $UART_BAUD baud" - ;; - esac -done +log_info "UART selection: source=$UART_SELECTION_SOURCE selected_devices=$UART_SELECTED_COUNT list=$UART_SELECTED_DEVICES" +while IFS= read -r UART_SELECTED_LOG_DEVICE; do + [ -n "$UART_SELECTED_LOG_DEVICE" ] || continue + log_info "[UART-SELECTION] device=$UART_SELECTED_LOG_DEVICE source=$UART_SELECTION_SOURCE loopback=$UART_LOOPBACK_TYPE" +done <"$UART_SELECTED_DEVICES" + +log_info "UART loopback: validating each selected device sequentially across the requested baud and character-width matrix" +UART_REQUESTED_BAUDS=$UART_BAUDS +while IFS= read -r UART_DEVICE; do + [ -n "$UART_DEVICE" ] || continue + UART_DEVICE_NAME=$(basename "$UART_DEVICE") + UART_DEVICE_RESULT_DIR="$RESULT_DIR/$UART_DEVICE_NAME" + if ! mkdir -p "$UART_DEVICE_RESULT_DIR"; then + test_result_record "FAIL" "UART evidence directory could not be created for $UART_DEVICE at $UART_DEVICE_RESULT_DIR" + continue + fi + + UART_DEVICE_BAUDS=$UART_REQUESTED_BAUDS + if [ "$UART_DEVICE_BAUDS" = "auto" ]; then + UART_DEVICE_BAUDS=$(uart_loopback_select_bauds "$UART_DEVICE") + UART_BAUD_SELECTION_STATUS=$? + if [ "$UART_BAUD_SELECTION_STATUS" -ne 0 ] || [ -z "$UART_DEVICE_BAUDS" ]; then + test_result_record "FAIL" "Could not derive a baud matrix for $UART_DEVICE" + continue + fi + fi + log_info "Configuration: device=$UART_DEVICE selection=$UART_SELECTION_SOURCE loopback=$UART_LOOPBACK_TYPE bauds=$UART_DEVICE_BAUDS data_bits=$UART_DATA_BITS flow_control=$UART_FLOW_CONTROL payload_bytes=$UART_PAYLOAD_BYTES timeout=${UART_LOOPBACK_TIMEOUT}s fixture=$UART_FIXTURE_STATE evidence=$UART_DEVICE_RESULT_DIR" + + UART_REMAINING_BAUDS=$UART_DEVICE_BAUDS + while [ -n "$UART_REMAINING_BAUDS" ]; do + case "$UART_REMAINING_BAUDS" in + *,*) + UART_BAUD=${UART_REMAINING_BAUDS%%,*} + UART_REMAINING_BAUDS=${UART_REMAINING_BAUDS#*,} + ;; + *) + UART_BAUD=$UART_REMAINING_BAUDS + UART_REMAINING_BAUDS="" + ;; + esac + UART_REMAINING_DATA_BITS=$UART_DATA_BITS + while [ -n "$UART_REMAINING_DATA_BITS" ]; do + case "$UART_REMAINING_DATA_BITS" in + *,*) + UART_CASE_DATA_BITS=${UART_REMAINING_DATA_BITS%%,*} + UART_REMAINING_DATA_BITS=${UART_REMAINING_DATA_BITS#*,} + ;; + *) + UART_CASE_DATA_BITS=$UART_REMAINING_DATA_BITS + UART_REMAINING_DATA_BITS="" + ;; + esac + UART_CASE_ID="${UART_BAUD}_${UART_CASE_DATA_BITS}bit_${UART_FLOW_CONTROL}_${UART_LOOPBACK_TYPE}" + UART_CASE_LOG="$UART_DEVICE_RESULT_DIR/uart_loopback_${UART_CASE_ID}.log" + log_info "---- UART functional case: device=$UART_DEVICE loopback=$UART_LOOPBACK_TYPE baud=$UART_BAUD data_bits=$UART_CASE_DATA_BITS flow_control=$UART_FLOW_CONTROL bytes=$UART_PAYLOAD_BYTES ----" + uart_loopback_validate \ + "$UART_DEVICE" \ + "$UART_BAUD" \ + "$UART_LOOPBACK_TIMEOUT" \ + "$UART_DEVICE_RESULT_DIR" \ + "$UART_PAYLOAD_BYTES" \ + "$UART_CASE_DATA_BITS" \ + "$UART_FLOW_CONTROL" \ + "$UART_LOOPBACK_TYPE" \ + "$TOOLS/uart_tiocm_loopback.py" + loopback_status=$? + case "$loopback_status" in + 0) + test_result_record "PASS" "UART $UART_LOOPBACK_TYPE exact payload transfer passed on $UART_DEVICE at $UART_BAUD baud with $UART_CASE_DATA_BITS data bits and $UART_FLOW_CONTROL flow control" + ;; + 1) + uart_loopback_capture_failure_diagnostics \ + "$UART_DEVICE" \ + "$UART_DEVICE_RESULT_DIR" \ + "$UART_CASE_ID" || + log_warn "UART failure diagnostics could not be completed for $UART_DEVICE" + test_result_record "FAIL" "UART $UART_LOOPBACK_TYPE loopback failed on $UART_DEVICE at $UART_BAUD baud with $UART_CASE_DATA_BITS data bits and $UART_FLOW_CONTROL flow control, see $UART_CASE_LOG and preceding UART-RESTORE diagnostics" + ;; + 2) + test_result_record "SKIP" "UART $UART_LOOPBACK_TYPE loopback at $UART_BAUD baud is not runnable on $UART_DEVICE, the preceding UART-LOOPBACK line identifies the reason" + ;; + *) + test_result_record "FAIL" "UART loopback helper returned unexpected status $loopback_status on $UART_DEVICE at $UART_BAUD baud with $UART_CASE_DATA_BITS data bits" + ;; + esac + done + done +done <"$UART_SELECTED_DEVICES" test_result_finish diff --git a/Runner/suites/Kernel/Baseport/UART_Validation/README.md b/Runner/suites/Kernel/Baseport/UART_Validation/README.md index c8d2e446..e27e0678 100644 --- a/Runner/suites/Kernel/Baseport/UART_Validation/README.md +++ b/Runner/suites/Kernel/Baseport/UART_Validation/README.md @@ -27,13 +27,23 @@ cd Runner/suites/Kernel/Baseport/UART_Validation - `PASS`: discovered UART controllers and consumers are consistently bound. - `FAIL`: an enabled controller lacks its platform device, driver, TTY, or - bound serdev consumer, or persistent UART kernel errors are present. + bound serdev consumer, a Bluetooth serdev lacks its HCI adapter, an available + HCI control probe fails, or persistent UART kernel errors are present. - `SKIP`: no enabled or runtime-visible UART capability exists. ## Diagnostics The suite emits bounded per-object binding, consumer, and kernel-health -diagnostics to stdout. Temporary evidence is removed when the run finishes. +diagnostics to stdout. Evidence is retained under +`results/UART_Validation/run-*/`. + +Bluetooth-owned UARTs are validated without opening their TTY directly. The +suite correlates each bound Bluetooth serdev consumer with its runtime HCI +adapter and runs a bounded `btmgmt`, `hciconfig`, or `bluetoothctl` control +probe when one is image-provided. A declared and bound Bluetooth serdev with no +corresponding HCI adapter fails. If no control utility is available, the bound +serdev-to-HCI registration remains the kernel transport evidence and the +missing optional probe is reported in stdout. Physical data transfer is intentionally kept out of this evidence suite. Loopback must use an explicitly selected non-console UART and must restore its diff --git a/Runner/utils/lib_bus.sh b/Runner/utils/lib_bus.sh index 75e8e069..a0b27bcd 100755 --- a/Runner/utils/lib_bus.sh +++ b/Runner/utils/lib_bus.sh @@ -49,8 +49,8 @@ bus_validation_ancestor_driver_name() { if [ -n "$bidn_driver" ]; then bidn_driver_name=$(basename "$bidn_driver") case "$bidn_driver_name" in - port) - # serial-core exposes a wrapper on the TTY port. + port|ctrl) + # serial-core exposes wrappers around the controller. # Continue upward to report the hardware controller. ;; *) @@ -259,6 +259,97 @@ bus_validation_dt_nodes() { [ -s "$bidn_output" ] } +# uart_validate_bluetooth_transport +# Correlates a Bluetooth serdev UART with HCI and runs a bounded control probe. +uart_validate_bluetooth_transport() { + uvbt_uart_node="$1" + uvbt_result_dir="$2" + [ -n "$uvbt_uart_node" ] && [ -n "$uvbt_result_dir" ] || return 3 + + uvbt_log="$uvbt_result_dir/uart_bluetooth_hci.log" + uvbt_class_root="${UART_SYS_CLASS_BLUETOOTH_ROOT:-/sys/class/bluetooth}" + uvbt_matches=0 + uvbt_failures=0 + : >>"$uvbt_log" + + log_info "[UART-BT] validation=starting uart_node=$uvbt_uart_node intent=correlate-serdev-to-HCI-and-run-bounded-control-probe" + for uvbt_hci_path in "$uvbt_class_root"/hci*; do + [ -e "$uvbt_hci_path/device" ] || continue + uvbt_hci=$(basename "$uvbt_hci_path") + uvbt_hci_node=$(bus_validation_of_node "$uvbt_hci_path/device" 2>/dev/null || true) + case "$uvbt_hci_node" in + "$uvbt_uart_node"|"$uvbt_uart_node"/*) + ;; + *) + continue + ;; + esac + + uvbt_matches=$((uvbt_matches + 1)) + uvbt_device=$(readlink -f "$uvbt_hci_path/device" 2>/dev/null || true) + uvbt_driver=$(bus_validation_ancestor_driver_name "$uvbt_hci_path/device" 2>/dev/null || true) + uvbt_address=$(cat "$uvbt_hci_path/address" 2>/dev/null || printf '%s\n' unavailable) + uvbt_name=$(cat "$uvbt_hci_path/name" 2>/dev/null || printf '%s\n' unavailable) + uvbt_probe=none + uvbt_probe_status=unavailable + uvbt_probe_output="$uvbt_result_dir/uart_bluetooth_${uvbt_hci}_probe.log" + : >"$uvbt_probe_output" + + if command -v btmgmt >/dev/null 2>&1; then + uvbt_probe=btmgmt + uvbt_index=${uvbt_hci#hci} + if run_with_timeout 5 btmgmt --index "$uvbt_index" info >"$uvbt_probe_output" 2>&1 && + [ -s "$uvbt_probe_output" ]; then + uvbt_probe_status=pass + else + uvbt_probe_status=fail + uvbt_failures=$((uvbt_failures + 1)) + fi + elif command -v hciconfig >/dev/null 2>&1; then + uvbt_probe=hciconfig + if run_with_timeout 5 hciconfig -a "$uvbt_hci" >"$uvbt_probe_output" 2>&1 && + [ -s "$uvbt_probe_output" ]; then + uvbt_probe_status=pass + else + uvbt_probe_status=fail + uvbt_failures=$((uvbt_failures + 1)) + fi + elif command -v bluetoothctl >/dev/null 2>&1; then + uvbt_probe=bluetoothctl + if run_with_timeout 5 bluetoothctl show "$uvbt_address" >"$uvbt_probe_output" 2>&1 && + grep -q 'Controller' "$uvbt_probe_output"; then + uvbt_probe_status=pass + else + uvbt_probe_status=diagnostic-unavailable + fi + fi + + printf 'uart_node=%s hci=%s device=%s driver=%s address=%s name=%s probe=%s status=%s artifact=%s\n' \ + "$uvbt_uart_node" "$uvbt_hci" "${uvbt_device:-unknown}" \ + "${uvbt_driver:-unbound}" "$uvbt_address" "$uvbt_name" \ + "$uvbt_probe" "$uvbt_probe_status" "$uvbt_probe_output" >>"$uvbt_log" + + case "$uvbt_probe_status" in + pass) + log_info "[UART-BT] uart_node=$uvbt_uart_node hci=$uvbt_hci driver=${uvbt_driver:-unbound} address=$uvbt_address probe=$uvbt_probe status=pass artifact=$uvbt_probe_output" + ;; + fail) + log_fail "[UART-BT-FAIL] uart_node=$uvbt_uart_node hci=$uvbt_hci expected=successful-HCI-control-probe observed=$uvbt_probe-failed artifact=$uvbt_probe_output" + ;; + *) + log_warn "[UART-BT] uart_node=$uvbt_uart_node hci=$uvbt_hci driver=${uvbt_driver:-unbound} address=$uvbt_address probe=$uvbt_probe status=$uvbt_probe_status kernel_transport=registered artifact=$uvbt_probe_output" + ;; + esac + done + + if [ "$uvbt_matches" -eq 0 ]; then + log_fail "[UART-BT-FAIL] uart_node=$uvbt_uart_node expected=HCI-adapter-for-bound-Bluetooth-serdev observed=none artifact=$uvbt_log" + return 1 + fi + [ "$uvbt_failures" -eq 0 ] || return 1 + return 0 +} + # uart_validate_runtime # Correlates enabled UART DT nodes with platform drivers, TTYs, and serdev consumers. uart_validate_runtime() { @@ -276,6 +367,7 @@ uart_validate_runtime() { ucri_failures=0 ucri_dt_count=0 ucri_runtime_count=0 + ucri_bt_transport_count=0 printf 'kind\tobject\tdriver\tof_node\tconsumer\tdetails\n' >"$ucri_evidence" printf 'tty\tdriver\tof_node\tconsole\tdevice\n' >"$ucri_ttys" @@ -283,17 +375,17 @@ uart_validate_runtime() { : >"$ucri_registered_drivers" for ucri_serial_root in $ucri_serial_roots; do - bus_validation_capture_driver_names "$ucri_serial_root/drivers" "$ucri_result_dir/.uart_drivers.tmp" || true - if [ -s "$ucri_result_dir/.uart_drivers.tmp" ]; then - cat "$ucri_result_dir/.uart_drivers.tmp" >>"$ucri_registered_drivers" + bus_validation_capture_driver_names "$ucri_serial_root/drivers" "$ucri_result_dir/uart_drivers_tmp.log" || true + if [ -s "$ucri_result_dir/uart_drivers_tmp.log" ]; then + cat "$ucri_result_dir/uart_drivers_tmp.log" >>"$ucri_registered_drivers" fi done - if sort -u "$ucri_registered_drivers" >"$ucri_result_dir/.uart_drivers.sorted"; then - mv "$ucri_result_dir/.uart_drivers.sorted" "$ucri_registered_drivers" + if sort -u "$ucri_registered_drivers" >"$ucri_result_dir/uart_drivers_sorted.log"; then + mv "$ucri_result_dir/uart_drivers_sorted.log" "$ucri_registered_drivers" else - rm -f "$ucri_result_dir/.uart_drivers.sorted" + rm -f "$ucri_result_dir/uart_drivers_sorted.log" fi - rm -f "$ucri_result_dir/.uart_drivers.tmp" + rm -f "$ucri_result_dir/uart_drivers_tmp.log" if [ -d "$ucri_tty_root" ]; then for ucri_tty_path in "$ucri_tty_root"/*; do @@ -346,6 +438,7 @@ uart_validate_runtime() { ucri_tty_names=$(awk -F '\t' -v node="$ucri_node" 'NR > 1 && $3 == node { printf "%s%s", separator, $1; separator="," } END { if (separator != "") print "" }' "$ucri_ttys") ucri_serdev_names="" ucri_serdev_bound=0 + ucri_bluetooth_serdev=0 for ucri_serial_root in $ucri_serial_roots; do if [ -d "$ucri_serial_root/devices" ]; then for ucri_serdev in "$ucri_serial_root"/devices/*; do @@ -358,6 +451,11 @@ uart_validate_runtime() { ucri_serdev_names="${ucri_serdev_names}${ucri_serdev_names:+,}$ucri_serdev_name:${ucri_serdev_driver:-unbound}" if [ -n "$ucri_serdev_driver" ]; then ucri_serdev_bound=$((ucri_serdev_bound + 1)) + case "$ucri_serdev_driver:$ucri_serdev_name" in + *hci*|*bluetooth*|*btqca*) + ucri_bluetooth_serdev=1 + ;; + esac fi ;; esac @@ -384,9 +482,17 @@ uart_validate_runtime() { log_fail "[UART-FAIL] object=$(basename "$ucri_device_dir") expected=TTY-or-bound-serdev-consumer observed=none driver=$ucri_driver artifact=$ucri_evidence" ucri_failures=$((ucri_failures + 1)) fi + + if [ "$ucri_bluetooth_serdev" -eq 1 ]; then + if uart_validate_bluetooth_transport "$ucri_node" "$ucri_result_dir"; then + ucri_bt_transport_count=$((ucri_bt_transport_count + 1)) + else + ucri_failures=$((ucri_failures + 1)) + fi + fi done <"$ucri_dt_nodes" - log_info "UART runtime summary: dt_controllers=$ucri_dt_count physical_ttys=$ucri_runtime_count failures=$ucri_failures artifact=$ucri_evidence" + log_info "UART runtime summary: dt_controllers=$ucri_dt_count physical_ttys=$ucri_runtime_count bluetooth_transports=$ucri_bt_transport_count failures=$ucri_failures artifact=$ucri_evidence" if [ "$ucri_dt_count" -eq 0 ] && [ "$ucri_runtime_count" -eq 0 ]; then return 2 @@ -662,11 +768,11 @@ can_validate_runtime() { printf 'kind\tobject\tdriver\tparent\tof_node\toperstate\tcan_state\tbitrate\tdbitrate\tctrlmode\tberr_tx\tberr_rx\trx_errors\ttx_errors\n' >"$ccri_evidence" : >"$ccri_dt_nodes" : >"$ccri_ip_log" - bus_validation_capture_driver_names "$ccri_spi_root/drivers" "$ccri_result_dir/.can_spi_drivers.tmp" || true - bus_validation_capture_driver_names "$ccri_platform_root/drivers" "$ccri_result_dir/.can_platform_drivers.tmp" || true - cat "$ccri_result_dir/.can_spi_drivers.tmp" "$ccri_result_dir/.can_platform_drivers.tmp" 2>/dev/null | + bus_validation_capture_driver_names "$ccri_spi_root/drivers" "$ccri_result_dir/can_spi_drivers_tmp.log" || true + bus_validation_capture_driver_names "$ccri_platform_root/drivers" "$ccri_result_dir/can_platform_drivers_tmp.log" || true + cat "$ccri_result_dir/can_spi_drivers_tmp.log" "$ccri_result_dir/can_platform_drivers_tmp.log" 2>/dev/null | sort -u >"$ccri_registered_drivers" - rm -f "$ccri_result_dir/.can_spi_drivers.tmp" "$ccri_result_dir/.can_platform_drivers.tmp" + rm -f "$ccri_result_dir/can_spi_drivers_tmp.log" "$ccri_result_dir/can_platform_drivers_tmp.log" if [ -n "$ccri_dt_root" ]; then bus_validation_dt_nodes "$ccri_dt_root" '^can(@|$)' "$ccri_dt_nodes" || true @@ -695,7 +801,7 @@ can_validate_runtime() { ccri_berr_tx=unknown ccri_berr_rx=unknown if command -v ip >/dev/null 2>&1; then - ccri_ip_tmp="$ccri_result_dir/.can_ip_$ccri_iface.log" + ccri_ip_tmp="$ccri_result_dir/can_ip_${ccri_iface}_tmp.log" if ip -details -statistics link show dev "$ccri_iface" >"$ccri_ip_tmp" 2>&1; then printf '===== %s =====\n' "$ccri_iface" >>"$ccri_ip_log" cat "$ccri_ip_tmp" >>"$ccri_ip_log" @@ -798,7 +904,7 @@ bus_validation_suite_run() { bisr_script_dir="$4" [ -n "$bisr_bus" ] && [ -n "$bisr_test_name" ] && [ -n "$bisr_result_file" ] && [ -n "$bisr_script_dir" ] || return 3 - bisr_result_dir="$bisr_script_dir/.${bisr_test_name}.work.$$" + bisr_result_dir="$bisr_script_dir/results/$bisr_test_name/run-$(date '+%Y%m%d-%H%M%S')-$$" case "$bisr_bus" in uart) @@ -841,12 +947,12 @@ bus_validation_suite_run() { test_result_init "$bisr_test_name" "$bisr_result_file" || return 1 if ! mkdir -p "$bisr_result_dir"; then - test_result_finish "FAIL" "$bisr_test_name FAIL: cannot create temporary evidence directory" + test_result_finish "FAIL" "$bisr_test_name FAIL: cannot create retained evidence directory $bisr_result_dir" fi - trap 'rm -rf "$bisr_result_dir"' EXIT HUP INT TERM log_info "--------------------------------------------------------------------------" log_info "Starting $bisr_test_name" + log_info "Evidence directory: $bisr_result_dir" log_info "${bisr_dmesg_label} validation: $bisr_intent" if ! bus_validation_require_commands awk basename cat dirname find grep mkdir mv od readlink rm sort tr; then @@ -955,39 +1061,188 @@ uart_loopback_device_use_reason() { return 1 } -# uart_loopback_select_device -# Prints the first runtime-discovered physical, accessible, non-console TTY. -uart_loopback_select_device() { - ulsd_tty_root="${UART_SYS_CLASS_TTY_ROOT:-/sys/class/tty}" - ulsd_dev_root="${UART_DEV_ROOT:-/dev}" - ulsd_candidate="" - ulsd_count=0 - - [ -d "$ulsd_tty_root" ] || return 2 - for ulsd_tty_path in "$ulsd_tty_root"/*; do - [ -e "$ulsd_tty_path/device" ] || continue - ulsd_tty=$(basename "$ulsd_tty_path") - ulsd_device=$(readlink -f "$ulsd_tty_path/device" 2>/dev/null || true) - case "$ulsd_device" in +# uart_loopback_list_devices +# Prints every runtime-discovered physical, accessible, unused non-console TTY. +uart_loopback_list_devices() { + ulld_tty_root="${UART_SYS_CLASS_TTY_ROOT:-/sys/class/tty}" + ulld_dev_root="${UART_DEV_ROOT:-/dev}" + ulld_count=0 + + [ -d "$ulld_tty_root" ] || return 2 + for ulld_tty_path in "$ulld_tty_root"/*; do + [ -e "$ulld_tty_path/device" ] || continue + ulld_tty=$(basename "$ulld_tty_path") + ulld_device=$(readlink -f "$ulld_tty_path/device" 2>/dev/null || true) + case "$ulld_device" in /sys/devices/virtual/*) continue ;; esac - ulsd_of_node=$(bus_validation_of_node "$ulsd_tty_path/device" 2>/dev/null || true) - [ -n "$ulsd_of_node" ] || continue - ulsd_devnode="$ulsd_dev_root/$ulsd_tty" - [ -c "$ulsd_devnode" ] || continue - if [ ! -r "$ulsd_devnode" ] || [ ! -w "$ulsd_devnode" ]; then + ulld_of_node=$(bus_validation_of_node "$ulld_tty_path/device" 2>/dev/null || true) + [ -n "$ulld_of_node" ] || continue + ulld_devnode="$ulld_dev_root/$ulld_tty" + [ -c "$ulld_devnode" ] || continue + if [ ! -r "$ulld_devnode" ] || [ ! -w "$ulld_devnode" ]; then + continue + fi + uart_device_is_console "$ulld_tty" && continue + if uart_loopback_device_use_reason "$ulld_devnode" >/dev/null 2>&1; then continue fi - uart_device_is_console "$ulsd_tty" && continue - ulsd_candidate="$ulsd_devnode" - ulsd_count=$((ulsd_count + 1)) + printf '%s\n' "$ulld_devnode" + ulld_count=$((ulld_count + 1)) done - [ "$ulsd_count" -eq 0 ] && return 2 + [ "$ulld_count" -gt 0 ] || return 2 + return 0 +} + +# uart_loopback_select_device +# Prints the unique eligible UART and rejects ambiguous multi-device selection. +uart_loopback_select_device() { + ulsd_candidates=$(uart_loopback_list_devices) + ulsd_status=$? + [ "$ulsd_status" -eq 0 ] || return "$ulsd_status" + + ulsd_count=$(printf '%s\n' "$ulsd_candidates" | awk 'NF { count++ } END { print count + 0 }') [ "$ulsd_count" -eq 1 ] || return 1 - printf '%s\n' "$ulsd_candidate" + printf '%s\n' "$ulsd_candidates" + return 0 +} + +# uart_loopback_report_candidates +# Logs why each DT-backed physical TTY is eligible or rejected for loopback. +uart_loopback_report_candidates() { + ulrc_output="$1" + ulrc_tty_root="${UART_SYS_CLASS_TTY_ROOT:-/sys/class/tty}" + ulrc_dev_root="${UART_DEV_ROOT:-/dev}" + [ -n "$ulrc_output" ] || return 3 + + printf 'tty\tdevnode\tdriver\tof_node\tconsole\taccess\towner\truntime_pm\teligible\n' >"$ulrc_output" || return 1 + [ -d "$ulrc_tty_root" ] || return 2 + + for ulrc_tty_path in "$ulrc_tty_root"/*; do + [ -e "$ulrc_tty_path/device" ] || continue + ulrc_tty=$(basename "$ulrc_tty_path") + ulrc_device=$(readlink -f "$ulrc_tty_path/device" 2>/dev/null || true) + case "$ulrc_device" in + /sys/devices/virtual/*) + continue + ;; + esac + ulrc_of_node=$(bus_validation_of_node "$ulrc_tty_path/device" 2>/dev/null || true) + [ -n "$ulrc_of_node" ] || continue + ulrc_driver=$(bus_validation_ancestor_driver_name "$ulrc_tty_path/device" 2>/dev/null || true) + ulrc_devnode="$ulrc_dev_root/$ulrc_tty" + ulrc_console=no + uart_device_is_console "$ulrc_tty" && ulrc_console=yes + ulrc_access=unavailable + if [ -c "$ulrc_devnode" ] && [ -r "$ulrc_devnode" ] && [ -w "$ulrc_devnode" ]; then + ulrc_access=read-write + fi + ulrc_owner=$(uart_loopback_device_use_reason "$ulrc_devnode" 2>/dev/null || true) + [ -n "$ulrc_owner" ] || ulrc_owner=none + ulrc_runtime_pm=$(cat "$ulrc_tty_path/device/power/runtime_status" 2>/dev/null || printf '%s\n' unavailable) + ulrc_eligible=yes + if [ "$ulrc_console" = yes ]; then + ulrc_eligible=no-console + elif [ "$ulrc_access" != read-write ]; then + ulrc_eligible=no-access + elif [ "$ulrc_owner" != none ]; then + ulrc_eligible=no-active-owner + fi + + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$ulrc_tty" "$ulrc_devnode" "${ulrc_driver:-unbound}" "$ulrc_of_node" \ + "$ulrc_console" "$ulrc_access" "$ulrc_owner" "$ulrc_runtime_pm" \ + "$ulrc_eligible" >>"$ulrc_output" + log_info "[UART-DISCOVERY] device=$ulrc_devnode driver=${ulrc_driver:-unbound} console=$ulrc_console access=$ulrc_access owner=$ulrc_owner runtime_pm=$ulrc_runtime_pm eligible=$ulrc_eligible of_node=$ulrc_of_node" + done + + return 0 +} + +# uart_loopback_capture_failure_diagnostics +# Retains bounded UART, Bluetooth-topology, rfkill, and kernel-error evidence. +uart_loopback_capture_failure_diagnostics() { + ulfd_device="$1" + ulfd_result_dir="$2" + ulfd_case="$3" + [ -n "$ulfd_device" ] && [ -n "$ulfd_result_dir" ] && [ -n "$ulfd_case" ] || return 3 + + mkdir -p "$ulfd_result_dir" || return 1 + ulfd_tty=$(basename "$ulfd_device") + ulfd_tty_path="${UART_SYS_CLASS_TTY_ROOT:-/sys/class/tty}/$ulfd_tty" + ulfd_output="$ulfd_result_dir/uart_failure_${ulfd_case}.log" + ulfd_bt_class_root="${UART_SYS_CLASS_BLUETOOTH_ROOT:-/sys/class/bluetooth}" + ulfd_rfkill_class_root="${UART_SYS_CLASS_RFKILL_ROOT:-/sys/class/rfkill}" + ulfd_sysfs_device=$(readlink -f "$ulfd_tty_path/device" 2>/dev/null || true) + ulfd_of_node=$(bus_validation_of_node "$ulfd_tty_path/device" 2>/dev/null || true) + ulfd_driver=$(bus_validation_ancestor_driver_name "$ulfd_tty_path/device" 2>/dev/null || true) + ulfd_runtime_pm=$(cat "$ulfd_tty_path/device/power/runtime_status" 2>/dev/null || printf '%s\n' unavailable) + ulfd_owner=$(uart_loopback_device_use_reason "$ulfd_device" 2>/dev/null || true) + [ -n "$ulfd_owner" ] || ulfd_owner=none + ulfd_console=no + uart_device_is_console "$ulfd_tty" && ulfd_console=yes + + printf 'device=%s\nsysfs_device=%s\ndriver=%s\nof_node=%s\nconsole=%s\nowner=%s\nruntime_pm=%s\n' \ + "$ulfd_device" "${ulfd_sysfs_device:-unknown}" "${ulfd_driver:-unbound}" \ + "${ulfd_of_node:-unknown}" "$ulfd_console" "$ulfd_owner" \ + "$ulfd_runtime_pm" >"$ulfd_output" + log_info "[UART-DIAG] device=$ulfd_device driver=${ulfd_driver:-unbound} console=$ulfd_console owner=$ulfd_owner runtime_pm=$ulfd_runtime_pm of_node=${ulfd_of_node:-unknown} artifact=$ulfd_output" + + ulfd_bt_count=0 + ulfd_bt_related=0 + for ulfd_hci in "$ulfd_bt_class_root"/hci*; do + [ -e "$ulfd_hci/device" ] || continue + ulfd_bt_count=$((ulfd_bt_count + 1)) + ulfd_hci_device=$(readlink -f "$ulfd_hci/device" 2>/dev/null || true) + ulfd_hci_node=$(bus_validation_of_node "$ulfd_hci/device" 2>/dev/null || true) + ulfd_hci_driver=$(bus_validation_ancestor_driver_name "$ulfd_hci/device" 2>/dev/null || true) + ulfd_relation=distinct + case "$ulfd_hci_node" in + "$ulfd_of_node"|"$ulfd_of_node"/*) + if [ -n "$ulfd_of_node" ]; then + ulfd_relation=same-uart-controller + ulfd_bt_related=1 + fi + ;; + esac + printf 'bluetooth=%s device=%s driver=%s of_node=%s relation=%s\n' \ + "$(basename "$ulfd_hci")" "${ulfd_hci_device:-unknown}" \ + "${ulfd_hci_driver:-unbound}" "${ulfd_hci_node:-unknown}" \ + "$ulfd_relation" >>"$ulfd_output" + log_info "[UART-BLUETOOTH] adapter=$(basename "$ulfd_hci") driver=${ulfd_hci_driver:-unbound} relation=$ulfd_relation of_node=${ulfd_hci_node:-unknown}" + done + + ulfd_rfkill_count=0 + for ulfd_rfkill in "$ulfd_rfkill_class_root"/rfkill*; do + [ -r "$ulfd_rfkill/type" ] || continue + ulfd_rfkill_type=$(tr -d '[:space:]' <"$ulfd_rfkill/type") + [ "$ulfd_rfkill_type" = bluetooth ] || continue + ulfd_rfkill_count=$((ulfd_rfkill_count + 1)) + ulfd_rfkill_soft=$(cat "$ulfd_rfkill/soft" 2>/dev/null || printf '%s\n' unavailable) + ulfd_rfkill_hard=$(cat "$ulfd_rfkill/hard" 2>/dev/null || printf '%s\n' unavailable) + printf 'rfkill=%s soft=%s hard=%s\n' \ + "$(basename "$ulfd_rfkill")" "$ulfd_rfkill_soft" "$ulfd_rfkill_hard" >>"$ulfd_output" + log_info "[UART-BLUETOOTH] rfkill=$(basename "$ulfd_rfkill") soft=$ulfd_rfkill_soft hard=$ulfd_rfkill_hard" + done + + if [ "$ulfd_bt_related" -eq 1 ]; then + log_warn "[UART-RECOVERY] Bluetooth shares the selected UART controller, automatic disable is refused because the port ownership and restoration contract is not safe" + elif [ "$ulfd_bt_count" -gt 0 ] || [ "$ulfd_rfkill_count" -gt 0 ]; then + log_info "[UART-RECOVERY] Bluetooth runtime evidence is present on a distinct controller, action=none reason=not-related-to-selected-uart" + else + log_info "[UART-RECOVERY] Bluetooth runtime evidence is absent, action=none reason=not-applicable" + fi + + ulfd_kernel_dir="$ulfd_result_dir/kernel" + mkdir -p "$ulfd_kernel_dir" || return 1 + scan_dmesg_errors \ + "$ulfd_kernel_dir" \ + 'qcom_geni_serial|msm_serial|uart|serial|tty|hci_uart|bluetooth|btqca|qca' \ + 'dummy regulator|supply [^ ]+ not found|using dummy regulator' || true + log_info "[UART-DIAG] kernel evidence retained under $ulfd_kernel_dir" return 0 } @@ -1004,6 +1259,25 @@ UART_LOOPBACK_DEVICE="" UART_LOOPBACK_STATE="" UART_LOOPBACK_READER_PID="" UART_LOOPBACK_WATCHER_PID="" +UART_LOOPBACK_LOG="" + +# uart_loopback_log_payload +# Logs bounded payload proof while retaining the complete payload artifact. +uart_loopback_log_payload() { + ullp_direction="$1" + ullp_file="$2" + [ -n "$ullp_direction" ] && [ -r "$ullp_file" ] || return 3 + + ullp_bytes=$(wc -c <"$ullp_file" 2>/dev/null | tr -d '[:space:]') + ullp_checksum=$(cksum "$ullp_file" 2>/dev/null | awk '{ print $1 }') + ullp_preview=$(od -An -N 32 -v -tx1 "$ullp_file" 2>/dev/null | tr -d ' \n') + ullp_preview_bytes="${ullp_bytes:-0}" + if [ "$ullp_preview_bytes" -gt 32 ] 2>/dev/null; then + ullp_preview_bytes=32 + fi + log_info "[UART-PAYLOAD] direction=$ullp_direction bytes=${ullp_bytes:-0} cksum=${ullp_checksum:-unavailable} preview_hex=${ullp_preview:-empty} preview_bytes=$ullp_preview_bytes artifact=$ullp_file" + return 0 +} # uart_loopback_cleanup # Stops an active reader and restores the exact saved termios state. @@ -1022,27 +1296,41 @@ uart_loopback_cleanup() { fi if [ -n "$UART_LOOPBACK_DEVICE" ] && [ -n "$UART_LOOPBACK_STATE" ]; then - if ! stty -F "$UART_LOOPBACK_DEVICE" "$UART_LOOPBACK_STATE" 2>/dev/null; then - log_fail "[UART-RESTORE] device=$UART_LOOPBACK_DEVICE expected=saved-termios-state observed=restore-failed" - ulc_status=1 - else + if ulc_restore_error=$(stty -F "$UART_LOOPBACK_DEVICE" "$UART_LOOPBACK_STATE" 2>&1); then log_info "[UART-RESTORE] device=$UART_LOOPBACK_DEVICE state=restored" + else + ulc_current_state=$(stty -F "$UART_LOOPBACK_DEVICE" -g 2>/dev/null || true) + if [ -n "$UART_LOOPBACK_LOG" ]; then + printf '%s\n' "$ulc_restore_error" >>"$UART_LOOPBACK_LOG" + fi + ulc_restore_error=$(printf '%s' "$ulc_restore_error" | tr '\n' ' ') + if [ "$ulc_current_state" = "$UART_LOOPBACK_STATE" ]; then + log_warn "[UART-RESTORE] device=$UART_LOOPBACK_DEVICE state=verified-restored stty_status=nonzero stty_error=${ulc_restore_error:-none}" + else + log_fail "[UART-RESTORE] device=$UART_LOOPBACK_DEVICE expected_state=$UART_LOOPBACK_STATE observed_state=${ulc_current_state:-unavailable} observed=restore-failed stty_error=${ulc_restore_error:-none}" + ulc_status=1 + fi fi fi UART_LOOPBACK_DEVICE="" UART_LOOPBACK_STATE="" + UART_LOOPBACK_LOG="" return "$ulc_status" } -# uart_loopback_validate [payload-bytes] -# Runs exact-payload external loopback on an explicitly selected non-console TTY. +# uart_loopback_validate [payload-bytes] [data-bits] [flow-control] [loopback-type] [internal-helper] +# Runs exact-payload internal or external loopback on a selected non-console TTY. uart_loopback_validate() { ulv_device="$1" ulv_baud="$2" ulv_timeout="$3" ulv_result_dir="$4" ulv_payload_bytes="${5:-4096}" + ulv_data_bits="${6:-8}" + ulv_flow_control="${7:-none}" + ulv_loopback_type="${8:-external}" + ulv_internal_helper="${9:-}" [ -n "$ulv_device" ] && [ -n "$ulv_baud" ] && [ -n "$ulv_timeout" ] && [ -n "$ulv_result_dir" ] || return 3 case "$ulv_baud:$ulv_timeout:$ulv_payload_bytes" in @@ -1050,11 +1338,40 @@ uart_loopback_validate() { return 3 ;; esac + case "$ulv_data_bits" in + 5|6|7|8) + ;; + *) + return 3 + ;; + esac + case "$ulv_flow_control" in + none) + ulv_flow_flag="-crtscts" + ;; + rtscts) + ulv_flow_flag="crtscts" + ;; + *) + return 3 + ;; + esac + case "$ulv_loopback_type" in + internal) + [ -n "$ulv_internal_helper" ] && [ -r "$ulv_internal_helper" ] || return 3 + ;; + external) + ;; + *) + return 3 + ;; + esac mkdir -p "$ulv_result_dir" || return 1 - ulv_log="$ulv_result_dir/uart_loopback_${ulv_baud}.log" - ulv_tx="$ulv_result_dir/uart_loopback_${ulv_baud}_tx.bin" - ulv_rx="$ulv_result_dir/uart_loopback_${ulv_baud}_rx.bin" + ulv_case="${ulv_baud}_${ulv_data_bits}bit_${ulv_flow_control}_${ulv_loopback_type}" + ulv_log="$ulv_result_dir/uart_loopback_${ulv_case}.log" + ulv_tx="$ulv_result_dir/uart_loopback_${ulv_case}_tx.bin" + ulv_rx="$ulv_result_dir/uart_loopback_${ulv_case}_rx.bin" ulv_tty=$(basename "$ulv_device") : >"$ulv_log" rm -f "$ulv_tx" "$ulv_rx" @@ -1068,108 +1385,198 @@ uart_loopback_validate() { return 1 fi if uart_device_is_console "$ulv_tty"; then - log_info "[UART-LOOPBACK] device=$ulv_device state=not-runnable reason=kernel-console action=refused artifact=$ulv_log" + log_info "[UART-LOOPBACK] device=$ulv_device state=not-runnable reason=kernel-console action=refused" return 2 fi ulv_use_reason=$(uart_loopback_device_use_reason "$ulv_device" 2>/dev/null || true) if [ -n "$ulv_use_reason" ]; then - log_info "[UART-LOOPBACK] device=$ulv_device state=not-runnable reason=active-terminal-or-userspace-owner owner=$ulv_use_reason action=refused artifact=$ulv_log" + log_info "[UART-LOOPBACK] device=$ulv_device state=not-runnable reason=active-terminal-or-userspace-owner owner=$ulv_use_reason action=refused" return 2 fi UART_LOOPBACK_DEVICE="$ulv_device" + UART_LOOPBACK_LOG="$ulv_log" UART_LOOPBACK_STATE=$(stty -F "$ulv_device" -g 2>/dev/null || true) if [ -z "$UART_LOOPBACK_STATE" ]; then log_fail "[UART-LOOPBACK] device=$ulv_device expected=readable-termios-state observed=unavailable artifact=$ulv_log" UART_LOOPBACK_DEVICE="" return 1 fi + log_info "[UART-STATE] device=$ulv_device phase=pre-config termios=snapshot-captured" - if ! stty -F "$ulv_device" "$ulv_baud" raw -echo cs8 -cstopb -parenb -ixon -ixoff -crtscts 2>>"$ulv_log"; then - log_fail "[UART-LOOPBACK] device=$ulv_device expected=baud-$ulv_baud-configuration observed=stty-failed artifact=$ulv_log" - uart_loopback_cleanup || true - return 1 + ulv_controller_loopback=not-requested + if [ "$ulv_loopback_type" = internal ]; then + ulv_controller_loopback=requested + log_info "[UART-STATE] device=$ulv_device phase=configuration-pending owner=internal-helper baud=$ulv_baud format=${ulv_data_bits}N1 flow_control=$ulv_flow_control controller_loopback=$ulv_controller_loopback" + else + if ! stty -F "$ulv_device" "$ulv_baud" raw -echo "cs$ulv_data_bits" \ + -cstopb -parenb -ixon -ixoff "$ulv_flow_flag" clocal cread 2>>"$ulv_log"; then + log_fail "[UART-LOOPBACK] device=$ulv_device expected=termios-configuration observed=stty-failed baud=$ulv_baud data_bits=$ulv_data_bits flow_control=$ulv_flow_control artifact=$ulv_log" + uart_loopback_cleanup || true + return 1 + fi + log_info "[UART-STATE] device=$ulv_device phase=configured baud=$ulv_baud format=${ulv_data_bits}N1 flow_control=$ulv_flow_control controller_loopback=$ulv_controller_loopback" fi - if ! awk -v bytes="$ulv_payload_bytes" ' - BEGIN { - pattern = "QLI_UART_" - for (i = 0; i < bytes; i++) { - printf "%s", substr(pattern, (i % length(pattern)) + 1, 1) + case "$ulv_data_bits" in + 5) + ulv_character_range=31 + ;; + 6) + ulv_character_range=63 + ;; + 7) + ulv_character_range=127 + ;; + 8) + ulv_character_range=0 + ;; + esac + ulv_generation_failed=0 + if [ "$ulv_character_range" -eq 0 ]; then + LC_ALL=C awk -v bytes="$ulv_payload_bytes" ' + BEGIN { + pattern = "QLI_UART_" + for (i = 0; i < bytes; i++) { + printf "%s", substr(pattern, (i % length(pattern)) + 1, 1) + } } - } - ' >"$ulv_tx"; then + ' >"$ulv_tx" || ulv_generation_failed=1 + else + LC_ALL=C awk -v bytes="$ulv_payload_bytes" -v range="$ulv_character_range" ' + BEGIN { + for (i = 0; i < bytes; i++) { + printf "%c", 1 + (i % range) + } + } + ' >"$ulv_tx" || ulv_generation_failed=1 + fi + if [ "$ulv_generation_failed" -ne 0 ]; then log_fail "[UART-LOOPBACK] device=$ulv_device expected=generated-$ulv_payload_bytes-byte-payload observed=generation-failed artifact=$ulv_log" uart_loopback_cleanup || true return 1 fi ulv_length=$(wc -c <"$ulv_tx" | tr -d '[:space:]') - printf 'device=%s\nbaud=%s\ntimeout=%s\npayload_bytes=%s\n' \ - "$ulv_device" "$ulv_baud" "$ulv_timeout" "$ulv_length" >>"$ulv_log" + uart_loopback_log_payload tx "$ulv_tx" || true + printf 'device=%s\nloopback=%s\nbaud=%s\ndata_bits=%s\nflow_control=%s\ntimeout=%s\npayload_bytes=%s\n' \ + "$ulv_device" "$ulv_loopback_type" "$ulv_baud" "$ulv_data_bits" "$ulv_flow_control" \ + "$ulv_timeout" "$ulv_length" >>"$ulv_log" stty -F "$ulv_device" -a >>"$ulv_log" 2>&1 || true - dd if="$ulv_device" of="$ulv_rx" bs=1 count="$ulv_length" >>"$ulv_log" 2>&1 & - UART_LOOPBACK_READER_PID=$! - ( - sleep "$ulv_timeout" - kill "$UART_LOOPBACK_READER_PID" 2>/dev/null || true - ) & - UART_LOOPBACK_WATCHER_PID=$! - sleep 1 - - # shellcheck disable=SC2016 - if ! run_with_timeout "$ulv_timeout" sh -c 'dd if="$1" of="$2" bs=4096' sh "$ulv_tx" "$ulv_device" >>"$ulv_log" 2>&1; then - log_fail "[UART-LOOPBACK] device=$ulv_device expected=bounded-write observed=write-failed-or-timeout artifact=$ulv_log" - uart_loopback_cleanup || true - return 1 - fi - - if ! wait "$UART_LOOPBACK_READER_PID"; then + if [ "$ulv_loopback_type" = internal ]; then + ulv_internal_log="$ulv_result_dir/uart_internal_${ulv_case}.log" + ulv_outer_timeout=$((ulv_timeout + 5)) + log_info "[UART-INTERNAL] device=$ulv_device phase=enable-request ioctl=TIOCM_LOOP timeout=${ulv_timeout}s artifact=$ulv_internal_log" + run_with_timeout "$ulv_outer_timeout" \ + python3 "$ulv_internal_helper" \ + --device "$ulv_device" \ + --tx-file "$ulv_tx" \ + --rx-file "$ulv_rx" \ + --timeout "$ulv_timeout" \ + --baud "$ulv_baud" \ + --data-bits "$ulv_data_bits" \ + --flow-control "$ulv_flow_control" >"$ulv_internal_log" 2>&1 + ulv_internal_status=$? + log_file_with_label "UART-INTERNAL" "$ulv_internal_log" + case "$ulv_internal_status" in + 0) + ;; + 2) + log_info "[UART-LOOPBACK] device=$ulv_device state=not-runnable reason=internal-loopback-unsupported action=skipped artifact=$ulv_internal_log" + if ! uart_loopback_cleanup; then + return 1 + fi + return 2 + ;; + *) + log_fail "[UART-LOOPBACK] device=$ulv_device expected=verified-TIOCM_LOOP-transfer observed=internal-helper-failed status=$ulv_internal_status artifact=$ulv_internal_log" + uart_loopback_cleanup || true + return 1 + ;; + esac + else + dd if="$ulv_device" of="$ulv_rx" bs=1 count="$ulv_length" >>"$ulv_log" 2>&1 & + UART_LOOPBACK_READER_PID=$! + log_info "[UART-IO] device=$ulv_device phase=reader-started pid=$UART_LOOPBACK_READER_PID expected_bytes=$ulv_length timeout=${ulv_timeout}s" + ( + sleep "$ulv_timeout" + kill "$UART_LOOPBACK_READER_PID" 2>/dev/null || true + ) & + UART_LOOPBACK_WATCHER_PID=$! + sleep 1 + + # shellcheck disable=SC2016 + if ! run_with_timeout "$ulv_timeout" sh -c 'dd if="$1" of="$2" bs=4096' sh "$ulv_tx" "$ulv_device" >>"$ulv_log" 2>&1; then + uart_loopback_log_payload rx "$ulv_rx" || true + log_fail "[UART-LOOPBACK] device=$ulv_device expected=bounded-write observed=write-failed-or-timeout artifact=$ulv_log" + uart_loopback_cleanup || true + return 1 + fi + log_info "[UART-IO] device=$ulv_device phase=write-complete bytes=$ulv_length" + + if ! wait "$UART_LOOPBACK_READER_PID"; then + UART_LOOPBACK_READER_PID="" + ulv_received_bytes=$(wc -c <"$ulv_rx" 2>/dev/null | tr -d '[:space:]') + uart_loopback_log_payload rx "$ulv_rx" || true + log_fail "[UART-LOOPBACK] device=$ulv_device expected=${ulv_length}-received-bytes observed=reader-failed-or-timeout received_bytes=${ulv_received_bytes:-0} fixture=external-tx-rx-loopback-not-observed artifact=$ulv_log" + uart_loopback_cleanup || true + return 1 + fi UART_LOOPBACK_READER_PID="" - log_fail "[UART-LOOPBACK] device=$ulv_device expected=$ulv_length-received-bytes observed=reader-failed-or-timeout artifact=$ulv_log" - uart_loopback_cleanup || true - return 1 + kill "$UART_LOOPBACK_WATCHER_PID" 2>/dev/null || true + wait "$UART_LOOPBACK_WATCHER_PID" 2>/dev/null || true + UART_LOOPBACK_WATCHER_PID="" + ulv_received_bytes=$(wc -c <"$ulv_rx" 2>/dev/null | tr -d '[:space:]') + log_info "[UART-IO] device=$ulv_device phase=read-complete expected_bytes=$ulv_length received_bytes=${ulv_received_bytes:-0}" fi - UART_LOOPBACK_READER_PID="" - kill "$UART_LOOPBACK_WATCHER_PID" 2>/dev/null || true - wait "$UART_LOOPBACK_WATCHER_PID" 2>/dev/null || true - UART_LOOPBACK_WATCHER_PID="" + + uart_loopback_log_payload rx "$ulv_rx" || true if ! cmp -s "$ulv_tx" "$ulv_rx"; then - ulv_received=$(od -An -v -tx1 "$ulv_rx" 2>/dev/null | tr -d ' \n') - ulv_expected=$(od -An -v -tx1 "$ulv_tx" 2>/dev/null | tr -d ' \n') - log_fail "[UART-LOOPBACK] device=$ulv_device expected_hex=$ulv_expected observed_hex=${ulv_received:-empty} artifact=$ulv_log" + ulv_first_mismatch=$(cmp -l "$ulv_tx" "$ulv_rx" 2>/dev/null | awk 'NR == 1 { printf "offset=%s expected_octal=%s observed_octal=%s", $1, $2, $3; exit }') + log_fail "[UART-LOOPBACK] device=$ulv_device expected=byte-for-byte-match observed=mismatch first_difference=${ulv_first_mismatch:-size-or-content-mismatch} tx_artifact=$ulv_tx rx_artifact=$ulv_rx" uart_loopback_cleanup || true return 1 fi + log_info "[UART-PROOF] device=$ulv_device comparison=byte-for-byte result=match bytes=$ulv_length tx_artifact=$ulv_tx rx_artifact=$ulv_rx" if ! uart_loopback_cleanup; then return 1 fi - log_info "[UART-LOOPBACK] device=$ulv_device baud=$ulv_baud bytes=$ulv_length format=8N1 flow_control=off payload=verified artifact=$ulv_log" + log_info "[UART-LOOPBACK] device=$ulv_device loopback=$ulv_loopback_type baud=$ulv_baud bytes=$ulv_length format=${ulv_data_bits}N1 flow_control=$ulv_flow_control payload=verified artifact=$ulv_log" return 0 } -# spi_loopback_select_device -# Prints the first accessible image-provided spidev character device. -spi_loopback_select_device() { - slsd_dev_root="${SPI_DEV_ROOT:-/dev}" - slsd_candidate="" - slsd_count=0 +# spi_loopback_list_devices +# Prints every accessible image-provided spidev character device. +spi_loopback_list_devices() { + slld_dev_root="${SPI_DEV_ROOT:-/dev}" + slld_count=0 - for slsd_device in "$slsd_dev_root"/spidev*; do - [ -c "$slsd_device" ] || continue - if [ ! -r "$slsd_device" ] || [ ! -w "$slsd_device" ]; then + for slld_device in "$slld_dev_root"/spidev*; do + [ -c "$slld_device" ] || continue + if [ ! -r "$slld_device" ] || [ ! -w "$slld_device" ]; then continue fi - slsd_candidate="$slsd_device" - slsd_count=$((slsd_count + 1)) + printf '%s\n' "$slld_device" + slld_count=$((slld_count + 1)) done - [ "$slsd_count" -eq 0 ] && return 2 + [ "$slld_count" -gt 0 ] || return 2 + return 0 +} + +# spi_loopback_select_device +# Prints the unique accessible spidev device and rejects ambiguous selection. +spi_loopback_select_device() { + slsd_candidates=$(spi_loopback_list_devices) + slsd_status=$? + [ "$slsd_status" -eq 0 ] || return "$slsd_status" + + slsd_count=$(printf '%s\n' "$slsd_candidates" | awk 'NF { count++ } END { print count + 0 }') [ "$slsd_count" -eq 1 ] || return 1 - printf '%s\n' "$slsd_candidate" + printf '%s\n' "$slsd_candidates" return 0 } @@ -1352,26 +1759,36 @@ can_internal_loopback_interface_is_ready() { return 0 } +# can_internal_loopback_list_interfaces +# Prints every down physical CAN interface that may be configured safely. +can_internal_loopback_list_interfaces() { + cilli_mode="$1" + cilli_net_root="${CAN_SYS_CLASS_NET_ROOT:-/sys/class/net}" + cilli_count=0 + [ "$cilli_mode" = "auto" ] || [ "$cilli_mode" = "classic" ] || [ "$cilli_mode" = "fd" ] || return 3 + [ -d "$cilli_net_root" ] || return 2 + + for cilli_path in "$cilli_net_root"/*; do + cilli_interface=$(basename "$cilli_path") + can_internal_loopback_interface_is_ready "$cilli_interface" || continue + printf '%s\n' "$cilli_interface" + cilli_count=$((cilli_count + 1)) + done + + [ "$cilli_count" -gt 0 ] || return 2 + return 0 +} + # can_internal_loopback_select_interface -# Prints the unique down physical CAN interface that may be configured safely. +# Prints the unique eligible CAN interface and rejects ambiguous selection. can_internal_loopback_select_interface() { - cilsi_mode="$1" - cilsi_net_root="${CAN_SYS_CLASS_NET_ROOT:-/sys/class/net}" - cilsi_candidate="" - cilsi_count=0 - [ "$cilsi_mode" = "auto" ] || [ "$cilsi_mode" = "classic" ] || [ "$cilsi_mode" = "fd" ] || return 3 - [ -d "$cilsi_net_root" ] || return 2 - - for cilsi_path in "$cilsi_net_root"/*; do - cilsi_interface=$(basename "$cilsi_path") - can_internal_loopback_interface_is_ready "$cilsi_interface" || continue - cilsi_candidate="$cilsi_interface" - cilsi_count=$((cilsi_count + 1)) - done + cilsi_candidates=$(can_internal_loopback_list_interfaces "$1") + cilsi_status=$? + [ "$cilsi_status" -eq 0 ] || return "$cilsi_status" - [ "$cilsi_count" -eq 0 ] && return 2 + cilsi_count=$(printf '%s\n' "$cilsi_candidates" | awk 'NF { count++ } END { print count + 0 }') [ "$cilsi_count" -eq 1 ] || return 1 - printf '%s\n' "$cilsi_candidate" + printf '%s\n' "$cilsi_candidates" return 0 } diff --git a/Runner/utils/uart_tiocm_loopback.py b/Runner/utils/uart_tiocm_loopback.py new file mode 100755 index 00000000..aad63ab7 --- /dev/null +++ b/Runner/utils/uart_tiocm_loopback.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +"""Run a bounded UART controller-internal loopback through TIOCM_LOOP.""" + +import argparse +import array +import errno +import fcntl +import hashlib +import os +import select +import signal +import sys +import termios +import time + + +TIOCMGET = getattr(termios, "TIOCMGET", 0x5415) +TIOCMSET = getattr(termios, "TIOCMSET", 0x5418) +TIOCM_LOOP = getattr(termios, "TIOCM_LOOP", 0x8000) +UNSUPPORTED_ERRNOS = { + errno.EINVAL, + errno.ENOSYS, + errno.ENOTTY, + errno.EOPNOTSUPP, +} + + +class InterruptedRun(Exception): + """Raised when the timeout wrapper asks the helper to terminate.""" + + +class UnsupportedOperation(Exception): + """Raised when the runtime cannot provide a requested UART operation.""" + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--device", required=True) + parser.add_argument("--tx-file", required=True) + parser.add_argument("--rx-file", required=True) + parser.add_argument("--timeout", required=True, type=int) + parser.add_argument("--baud", required=True, type=int) + parser.add_argument("--data-bits", required=True, type=int) + parser.add_argument("--flow-control", choices=("none", "rtscts"), required=True) + return parser.parse_args() + + +def get_modem_bits(fd): + bits = array.array("i", [0]) + fcntl.ioctl(fd, TIOCMGET, bits, True) + return bits[0] + + +def set_modem_bits(fd, value): + bits = array.array("i", [value]) + fcntl.ioctl(fd, TIOCMSET, bits) + + +def interrupted(_signum, _frame): + raise InterruptedRun("terminated-by-timeout-wrapper") + + +def log_payload(direction, payload): + print( + "state=payload direction=%s bytes=%d sha256=%s preview_hex=%s preview_bytes=%d" + % ( + direction, + len(payload), + hashlib.sha256(payload).hexdigest(), + payload[:32].hex(), + min(len(payload), 32), + ) + ) + + +def configure_uart(fd, baud, data_bits, flow_control): + speed = getattr(termios, "B%d" % baud, None) + if speed is None: + raise UnsupportedOperation("baud-%d-not-exposed-by-python-termios" % baud) + + character_sizes = { + 5: termios.CS5, + 6: termios.CS6, + 7: termios.CS7, + 8: termios.CS8, + } + if data_bits not in character_sizes: + raise UnsupportedOperation("data-bits-%d-unsupported" % data_bits) + + hardware_flow = getattr(termios, "CRTSCTS", None) + if flow_control == "rtscts" and hardware_flow is None: + raise UnsupportedOperation("CRTSCTS-not-exposed-by-python-termios") + + original_attributes = termios.tcgetattr(fd) + attributes = original_attributes[:] + attributes[6] = original_attributes[6][:] + attributes[0] = termios.IGNPAR + attributes[1] = 0 + attributes[2] &= ~(termios.CSIZE | termios.PARENB | termios.CSTOPB) + attributes[2] |= character_sizes[data_bits] | termios.CLOCAL | termios.CREAD + if hardware_flow is not None: + if flow_control == "rtscts": + attributes[2] |= hardware_flow + else: + attributes[2] &= ~hardware_flow + attributes[3] = 0 + attributes[4] = speed + attributes[5] = speed + attributes[6][termios.VMIN] = 0 + attributes[6][termios.VTIME] = 0 + termios.tcsetattr(fd, termios.TCSANOW, attributes) + + current_flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, current_flags & ~os.O_NONBLOCK) + return original_attributes + + +def transfer(fd, payload, timeout_seconds): + received = bytearray() + written = 0 + deadline = time.monotonic() + timeout_seconds + + while written < len(payload) or len(received) < len(payload): + remaining = deadline - time.monotonic() + if remaining <= 0: + raise TimeoutError( + "transfer-timeout written=%d received=%d expected=%d" + % (written, len(received), len(payload)) + ) + + write_set = [fd] if written < len(payload) else [] + readable, writable, _exceptional = select.select( + [fd], write_set, [], remaining + ) + + if fd in writable: + try: + count = os.write(fd, payload[written : written + 256]) + except BlockingIOError: + count = 0 + if count > 0: + written += count + + if fd in readable: + try: + chunk = os.read(fd, len(payload) - len(received)) + except BlockingIOError: + chunk = b"" + if chunk: + received.extend(chunk) + + return bytes(received), written + + +def main(): + args = parse_args() + if args.timeout <= 0: + print("state=invalid-argument reason=timeout-must-be-positive") + return 1 + + signal.signal(signal.SIGTERM, interrupted) + signal.signal(signal.SIGINT, interrupted) + + fd = None + original_bits = None + original_termios = None + restore_required = False + received = b"" + status = 1 + + try: + with open(args.tx_file, "rb") as tx_stream: + payload = tx_stream.read() + if not payload: + raise ValueError("transmit-payload-is-empty") + log_payload("tx", payload) + + fd = os.open( + args.device, + os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK | getattr(os, "O_CLOEXEC", 0), + ) + try: + original_bits = get_modem_bits(fd) + except OSError as exc: + if exc.errno in UNSUPPORTED_ERRNOS: + print( + "state=unsupported operation=TIOCMGET errno=%d error=%s" + % (exc.errno, exc.strerror) + ) + status = 2 + else: + raise + else: + preexisting = "yes" if original_bits & TIOCM_LOOP else "no" + print( + "state=precheck modem_bits=0x%x loop_preexisting=%s" + % (original_bits, preexisting) + ) + try: + set_modem_bits(fd, original_bits | TIOCM_LOOP) + restore_required = True + enabled_bits = get_modem_bits(fd) + except OSError as exc: + if exc.errno in UNSUPPORTED_ERRNOS: + print( + "state=unsupported operation=TIOCM_LOOP errno=%d error=%s" + % (exc.errno, exc.strerror) + ) + status = 2 + else: + raise + else: + readback = "set" if enabled_bits & TIOCM_LOOP else "not-exposed" + print( + "state=enabled modem_bits=0x%x loop_readback=%s" + % (enabled_bits, readback) + ) + original_termios = configure_uart( + fd, + args.baud, + args.data_bits, + args.flow_control, + ) + print( + "state=configured baud=%d data_bits=%d flow_control=%s blocking=yes" + % (args.baud, args.data_bits, args.flow_control) + ) + termios.tcflush(fd, termios.TCIOFLUSH) + received, written = transfer(fd, payload, args.timeout) + if received != payload: + raise RuntimeError( + "payload-mismatch written=%d received=%d expected=%d" + % (written, len(received), len(payload)) + ) + print( + "state=transfer-complete written=%d received=%d comparison=byte-for-byte-match" + % (written, len(received)) + ) + status = 0 + except UnsupportedOperation as exc: + print("state=unsupported operation=termios error=%s" % str(exc)) + status = 2 + except (InterruptedRun, OSError, RuntimeError, TimeoutError, ValueError) as exc: + print("state=failed error=%s" % str(exc)) + status = 1 + finally: + log_payload("rx", received) + try: + with open(args.rx_file, "wb") as rx_stream: + rx_stream.write(received) + except OSError as exc: + print("state=failed operation=write-rx-artifact error=%s" % str(exc)) + status = 1 + + if fd is not None and original_bits is not None and restore_required: + try: + set_modem_bits(fd, original_bits) + restored_bits = get_modem_bits(fd) + if (restored_bits & TIOCM_LOOP) != (original_bits & TIOCM_LOOP): + raise RuntimeError( + "TIOCM_LOOP-restore-readback-mismatch expected=0x%x observed=0x%x" + % (original_bits, restored_bits) + ) + print( + "state=restored original_modem_bits=0x%x observed_modem_bits=0x%x loop_bit=matched" + % (original_bits, restored_bits) + ) + except (OSError, RuntimeError) as exc: + print("state=restore-failed error=%s" % str(exc)) + status = 1 + + if fd is not None and original_termios is not None: + try: + termios.tcsetattr(fd, termios.TCSANOW, original_termios) + print("state=termios-restored verification=tcsetattr-success") + except OSError as exc: + print("state=termios-restore-failed error=%s" % str(exc)) + status = 1 + + if fd is not None: + os.close(fd) + + return status + + +if __name__ == "__main__": + sys.exit(main())