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