diff --git a/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/DeviceTree_HW_Capability_Validation.yaml b/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/DeviceTree_HW_Capability_Validation.yaml new file mode 100755 index 00000000..8eb025a9 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/DeviceTree_HW_Capability_Validation.yaml @@ -0,0 +1,17 @@ +metadata: + name: DeviceTree_HW_Capability_Validation + format: "Lava-Test Test Definition 1.0" + description: "Validate runtime device-tree identity, enabled hardware controllers, driver binding, and kernel health" + os: + - linux + scope: + - functional +params: + area: all + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation + - ./run.sh --area "${area:-all}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh DeviceTree_HW_Capability_Validation.res diff --git a/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/README.md b/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/README.md new file mode 100644 index 00000000..27934a08 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/README.md @@ -0,0 +1,96 @@ +# Device Tree Hardware Capability Validation + +## Overview + +`DeviceTree_HW_Capability_Validation` validates the active runtime device tree, +not a DTB stored on the root filesystem. It is designed for Qualcomm Linux +Developer Kits and Evaluation Kits with differing board revisions, FIT DTB +selection, and overlays. + +The suite discovers enabled hardware controllers from the runtime `compatible` +properties. It does not hardcode board names, network-interface names, or +attached peripherals. + +## Validation scope + +| Area selector | Validation | +| --- | --- | +| `identity` | Runtime DT root, `compatible`, optional `model`, root address and size cells, and a profile identifier derived from the first compatible string | +| `boot` | Optional `/chosen` `stdout-path` and `bootargs`, including stdout target resolution through `/aliases` | +| `cpu-memory` | Enabled DT CPU nodes cover kernel-exposed CPUs; memory nodes expose `reg`; reserved-memory children expose `reg` or `size`; declared NUMA node identifiers are inventoried | +| `interrupts` | Readable `/proc/interrupts`, interrupt-controller providers, and GIC, PDC, and GPIO structural or runtime evidence as appropriate | +| `fabric` | Clock, reset, regulator, mailbox, interconnect, RPMh, LLCC, and SMMU provider structure with platform-driver evidence where applicable | +| `storage` | Enabled UFS, SDHCI, and SPI controller bindings; SPI-NOR DT inventory; optional NVMe and MTD runtime evidence | +| `usb` | Enabled USB controller binding plus optional host and gadget-controller runtime evidence, including role-state reporting when host mode is inactive | +| `pcie` | Enabled PCIe controller binding plus optional endpoint enumeration evidence | +| `network` | Enabled Ethernet controller binding, non-virtual network-runtime evidence, and separate Wi-Fi/Bluetooth DT inventory without requiring an external module or link | +| `multimedia` | Display and GPU binding, audio and camera DT inventory, and optional DRM and ALSA runtime evidence | +| `remoteproc` | Enabled Qualcomm remoteproc inventory, memory-region and firmware-name evidence, firmware provisioning status, and runtime instance correlation | +| `security` | OP-TEE, SCM, TPM, and KVM EL2 DT inventory with optional TEE, TPM, and KVM runtime evidence, distinguishing a likely Qualcomm TEE flow from declared OP-TEE | +| `health` | Captured relevant DT and controller probe failures before and after validation | + +Existing focused suites remain responsible for active functional testing. For +example, PCIe endpoint enumeration, Ethernet traffic, audio capture, +interconnect voting, and EDAC counter behavior are not duplicated here. + +## Result policy + +- **PASS**: Required DT structure is present and a discovered controller is bound. +- **SKIP**: The runtime tree does not expose an optional capability, remoteproc + firmware is not provisioned, an external fixture or USB role is inactive, a + framework provider has no direct runtime binding, or a required base utility + is absent. +- **FAIL**: Required DT identity, CPU, or memory data is invalid; a hardware + controller that requires an MMIO register range lacks `reg`; or the captured + kernel log reports relevant failures. Framework children and firmware nodes + without direct platform-device representation are reported as **SKIP**. + +An attached USB device, PCIe endpoint, Ethernet cable, display, camera, or +wireless module is not required. Those are fixture-level validations. + +The profile identifier is runtime-derived from the first compatible string. +This keeps the baseline valid for all supported `meta-qcom` machines and their +overlay variants without tying the suite to a Yocto `MACHINE` name. Platform +profiles can later add stricter requirements after target evidence is collected. + +## Usage + +Run directly on the target from the suite directory: + +```sh +./run.sh +``` + +By default all areas run. To shorten an investigation, select one area or a +comma-separated set. The accepted area names correspond to the table above: + +```sh +./run.sh --area interrupts +./run.sh --area storage,usb,pcie +./run.sh --area remoteproc,security +./run.sh --list-areas +``` + +The suite builds compatible, provider-property, and platform-device indexes +once at startup, then reuses them for each selected area. This avoids repeated +full device-tree and platform-bus scans. It deliberately does not run checks in +parallel because they update one result file and one set of result counters. +The final output includes a compact per-area pass, fail, and skip table; full +per-node details remain in the artifacts. + +The result file is `DeviceTree_HW_Capability_Validation.res`. DT node lists and +captured kernel-log artifacts, including `dmesg_snapshot.log` and +`dmesg_errors.log`, are retained in: + +```text +results/DeviceTree_HW_Capability_Validation/ +``` + +This directory also contains `dt_compatible_index.log`, +`dt_property_index.log`, `platform_device_index.log`, and +`dt_area_summary.tsv`. + +## LAVA + +Use `DeviceTree_HW_Capability_Validation.yaml`. The definition invokes the +suite and uploads its result file through the repository LAVA helper. diff --git a/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/run.sh b/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/run.sh new file mode 100755 index 00000000..1e6d5246 --- /dev/null +++ b/Runner/suites/Kernel/Baseport/DeviceTree_HW_Capability_Validation/run.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +TESTNAME="DeviceTree_HW_Capability_Validation" +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" + +RESULT_DIR="$SCRIPT_DIR/results/$TESTNAME" + +dt_hw_capability_parse_args "$@" +parse_status=$? +if [ "$parse_status" -ne 0 ]; then + if [ "$parse_status" -eq 2 ]; then + exit 0 + fi + echo "[ERROR] Usage: ./run.sh [--area all|identity,boot,cpu-memory,interrupts,fabric,storage,usb,pcie,network,multimedia,remoteproc,security,health] [--list-areas]" >&2 + exit 2 +fi + +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 + +if ! mkdir -p "$RESULT_DIR"; then + test_result_finish "FAIL" "$TESTNAME FAIL: cannot create result directory $RESULT_DIR" +fi + +# DT helper discovery uses mktemp. Keep all temporary files in the testcase +# directory instead of inheriting a target-specific TMPDIR. +TMPDIR="$SCRIPT_DIR" + +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies \ + awk basename cp date dirname dmesg find grep mkdir mktemp readlink rm sed sort tr wc; then + test_result_finish "SKIP" "$TESTNAME SKIP: required base utilities are unavailable" +fi + +if ! DT_ROOT=$(dt_runtime_root); then + test_result_finish "SKIP" "$TESTNAME SKIP: runtime device tree is not exposed" +fi + +log_info "--------------------------------------------------------------------------" +log_info "Starting $TESTNAME" +log_info "Validation areas: $DTRHC_AREAS" + +if ! dt_validate_runtime_hardware_capabilities "$DT_ROOT" "$RESULT_DIR"; then + test_result_finish "FAIL" "$TESTNAME FAIL: DT capability validation could not complete" +fi + +test_result_finish diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index da1202fc..76da20d6 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -3357,6 +3357,14 @@ dt_list_compatible_nodes() { esac dtlc_found=0 + if [ -r "${DT_COMPATIBLE_INDEX:-}" ]; then + if [ "$dtlc_mode" = "regex" ]; then + awk -F '|' -v pattern="$dtlc_pattern" '$2 ~ pattern { print $1; found=1 } END { exit !found }' "$DT_COMPATIBLE_INDEX" + else + awk -F '|' -v pattern="$dtlc_pattern" 'index($2, pattern) { print $1; found=1 } END { exit !found }' "$DT_COMPATIBLE_INDEX" + fi + return $? + fi dtlc_previous_root="" for dtlc_root in /proc/device-tree /sys/firmware/devicetree/base; do [ -d "$dtlc_root" ] || continue @@ -3394,6 +3402,159 @@ dt_list_compatible_nodes() { [ "$dtlc_found" -eq 1 ] } +############################################################################### +# dt_build_runtime_indexes +# Builds reusable enabled-compatible, provider-property, and platform-device +# indexes. The files are retained with the suite artifacts for diagnosis. +############################################################################### +dt_build_runtime_indexes() { + dbri_root="$1" + dbri_result_dir="$2" + dbri_compatible_file="$dbri_result_dir/dt_compatible_index.log" + dbri_property_file="$dbri_result_dir/dt_property_index.log" + dbri_platform_file="$dbri_result_dir/platform_device_index.log" + [ -d "$dbri_root" ] && [ -n "$dbri_result_dir" ] || return 3 + + : >"$dbri_compatible_file" + : >"$dbri_property_file" + : >"$dbri_platform_file" + + find "$dbri_root" -type f \( \ + -name compatible -o \ + -name numa-node-id -o \ + -name interrupt-controller -o \ + -name '#clock-cells' -o \ + -name '#reset-cells' -o \ + -name regulator-name -o \ + -name '#mbox-cells' -o \ + -name '#interconnect-cells' \ + \) 2>/dev/null | + while IFS= read -r dbri_path; do + dbri_node=$(dirname "$dbri_path") + dt_node_enabled "$dbri_node" || continue + dbri_property=$(basename "$dbri_path") + if [ "$dbri_property" = "compatible" ]; then + dbri_text=$(dt_property_text "$dbri_node" compatible 2>/dev/null || true) + [ -n "$dbri_text" ] || continue + printf '%s|%s\n' "$dbri_node" "$dbri_text" >>"$dbri_compatible_file" + else + printf '%s|%s\n' "$dbri_property" "$dbri_node" >>"$dbri_property_file" + fi + done + + for dbri_of_node in /sys/bus/platform/devices/*/of_node; do + [ -e "$dbri_of_node" ] || continue + dbri_node=$(readlink -f "$dbri_of_node" 2>/dev/null || true) + [ -n "$dbri_node" ] || continue + dbri_device=$(dirname "$dbri_of_node") + printf '%s|%s\n' "$dbri_node" "$dbri_device" >>"$dbri_platform_file" + done + + DT_COMPATIBLE_INDEX="$dbri_compatible_file" + DT_PROPERTY_INDEX="$dbri_property_file" + DT_PLATFORM_DEVICE_INDEX="$dbri_platform_file" + export DT_COMPATIBLE_INDEX DT_PROPERTY_INDEX DT_PLATFORM_DEVICE_INDEX + log_info "Runtime DT indexes: compatible=$(wc -l <"$dbri_compatible_file" | tr -d '[:space:]') property=$(wc -l <"$dbri_property_file" | tr -d '[:space:]') platform=$(wc -l <"$dbri_platform_file" | tr -d '[:space:]')" +} + +############################################################################### +# dt_hw_capability_parse_args [--area ] [--list-areas] [--help] +# Validates suite area selection and stores the comma-separated selection in +# DTRHC_AREAS. Returns 2 after printing informational output. +############################################################################### +dt_hw_capability_parse_args() { + DTRHC_AREAS="all" + while [ "$#" -gt 0 ]; do + case "$1" in + --area) + shift + [ "$#" -gt 0 ] || return 3 + DTRHC_AREAS="$1" + ;; + --list-areas) + printf '%s\n' "identity boot cpu-memory interrupts fabric storage usb pcie network multimedia remoteproc security health" + return 2 + ;; + --help|-h) + printf '%s\n' "Usage: ./run.sh [--area all|identity,boot,cpu-memory,interrupts,fabric,storage,usb,pcie,network,multimedia,remoteproc,security,health] [--list-areas]" + return 2 + ;; + *) + return 3 + ;; + esac + done + DTRHC_AREAS=$(printf '%s' "$DTRHC_AREAS" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]') + [ -n "$DTRHC_AREAS" ] || return 3 + dthc_remaining=$DTRHC_AREAS + while [ -n "$dthc_remaining" ]; do + dthc_area=${dthc_remaining%%,*} + case "$dthc_area" in + all|identity|boot|cpu-memory|interrupts|fabric|storage|usb|pcie|network|multimedia|remoteproc|security|health) + ;; + *) + return 3 + ;; + esac + [ "$dthc_remaining" = "$dthc_area" ] && break + dthc_remaining=${dthc_remaining#*,} + done + export DTRHC_AREAS +} + +# dt_hw_capability_area_enabled +# Returns success when all areas or the supplied area was selected. +dt_hw_capability_area_enabled() { + dthcae_area="$1" + case ",${DTRHC_AREAS:-all}," in + *,all,*|*,$dthcae_area,*) + return 0 + ;; + esac + return 1 +} + +# dt_summary_begin_area +# Snapshots shared result counters before one validation area runs. +dt_summary_begin_area() { + DTSUMMARY_RESULT_DIR="$1" + DTSUMMARY_AREA="$2" + DTSUMMARY_PASS_BEFORE=$TEST_RESULT_PASS_COUNT + DTSUMMARY_FAIL_BEFORE=$TEST_RESULT_FAIL_COUNT + DTSUMMARY_SKIP_BEFORE=$TEST_RESULT_SKIP_COUNT + [ -n "$DTSUMMARY_RESULT_DIR" ] && [ -n "$DTSUMMARY_AREA" ] || return 3 +} + +# dt_summary_end_area +# Appends the selected area's result delta to the retained summary artifact. +dt_summary_end_area() { + dtsummary_file="$DTSUMMARY_RESULT_DIR/dt_area_summary.tsv" + dtsummary_pass=$((TEST_RESULT_PASS_COUNT - DTSUMMARY_PASS_BEFORE)) + dtsummary_fail=$((TEST_RESULT_FAIL_COUNT - DTSUMMARY_FAIL_BEFORE)) + dtsummary_skip=$((TEST_RESULT_SKIP_COUNT - DTSUMMARY_SKIP_BEFORE)) + if [ "$dtsummary_fail" -gt 0 ]; then + dtsummary_result="FAIL" + elif [ "$dtsummary_pass" -gt 0 ]; then + dtsummary_result="PASS" + else + dtsummary_result="SKIP" + fi + printf '%s\t%s\t%s\t%s\t%s\n' "$DTSUMMARY_AREA" "$dtsummary_result" "$dtsummary_pass" "$dtsummary_fail" "$dtsummary_skip" >>"$dtsummary_file" +} + +# dt_summary_print +# Prints a compact area-level summary from the retained TSV artifact. +dt_summary_print() { + dtsummary_file="$1/dt_area_summary.tsv" + [ -r "$dtsummary_file" ] || return 1 + log_info "Device Tree Capability Summary" + printf '%-26s %-6s %5s %5s %5s\n' "Area" "Result" "Pass" "Fail" "Skip" + dtsummary_tab=$(printf '\t') + while IFS="$dtsummary_tab" read -r dtsummary_area dtsummary_result dtsummary_pass dtsummary_fail dtsummary_skip; do + printf '%-26s %-6s %5s %5s %5s\n' "$dtsummary_area" "$dtsummary_result" "$dtsummary_pass" "$dtsummary_fail" "$dtsummary_skip" + done <"$dtsummary_file" +} + # dt_list_qcom_icc_provider_nodes # Prints enabled Qualcomm DT nodes exposing #interconnect-cells, excluding the # virtual IPA providers intentionally ignored by the ICC core. Returns 0 when @@ -3458,6 +3619,15 @@ find_platform_device_for_dt_node() { fpdd_target_node=$(readlink -f "$fpdd_node_dir" 2>/dev/null || true) [ -n "$fpdd_target_node" ] || fpdd_target_node="$fpdd_node_dir" + if [ -r "${DT_PLATFORM_DEVICE_INDEX:-}" ]; then + fpdd_index_device=$(awk -F '|' -v node="$fpdd_target_node" '$1 == node { print $2; exit }' "$DT_PLATFORM_DEVICE_INDEX") + if [ -n "$fpdd_index_device" ]; then + printf '%s\n' "$fpdd_index_device" + return 0 + fi + return 1 + fi + for fpdd_of_node_link in /sys/bus/platform/devices/*/of_node; do [ -e "$fpdd_of_node_link" ] || continue fpdd_device_node=$(readlink -f "$fpdd_of_node_link" 2>/dev/null || true) @@ -3539,6 +3709,737 @@ dt_property_text() { tr '\000' ' ' <"$node_dir/$property" | tr -cd '[:print:] \n' | sed 's/[[:space:]]*$//' } +############################################################################### +# dt_runtime_root +# Prints the resolved runtime device-tree root, preferring sysfs over procfs. +############################################################################### +dt_runtime_root() { + dtrr_candidate="" + + for dtrr_candidate in /sys/firmware/devicetree/base /proc/device-tree; do + [ -d "$dtrr_candidate" ] || continue + dtrr_resolved=$(readlink -f "$dtrr_candidate" 2>/dev/null || true) + if [ -n "$dtrr_resolved" ] && [ -d "$dtrr_resolved" ]; then + printf '%s\n' "$dtrr_resolved" + else + printf '%s\n' "$dtrr_candidate" + fi + return 0 + done + + return 1 +} + +############################################################################### +# dt_node_enabled +# Returns success unless the node status explicitly disables or fails the node. +############################################################################### +dt_node_enabled() { + dtne_node_dir="$1" + [ -n "$dtne_node_dir" ] || return 3 + dtne_status=$(dt_property_text "$dtne_node_dir" status 2>/dev/null || true) + + case "$dtne_status" in + disabled|fail|failed) + return 1 + ;; + esac + + return 0 +} + +############################################################################### +# dt_count_enabled_cpu_nodes +# Prints the number of enabled CPU nodes described by the supplied DT root. +############################################################################### +dt_count_enabled_cpu_nodes() { + dtcec_root="$1" + dtcec_count=0 + [ -d "$dtcec_root/cpus" ] || { + printf '%s\n' 0 + return 1 + } + + for dtcec_type_file in "$dtcec_root"/cpus/*/device_type; do + [ -r "$dtcec_type_file" ] || continue + dtcec_type=$(tr -d '\000[:space:]' <"$dtcec_type_file" 2>/dev/null) + [ "$dtcec_type" = "cpu" ] || continue + dtcec_node=$(dirname "$dtcec_type_file") + dt_node_enabled "$dtcec_node" || continue + dtcec_count=$((dtcec_count + 1)) + done + + printf '%s\n' "$dtcec_count" + [ "$dtcec_count" -gt 0 ] +} + +############################################################################### +# dt_count_kernel_cpu_nodes +# Prints the number of CPU directories exposed by the running kernel. +############################################################################### +dt_count_kernel_cpu_nodes() { + find /sys/devices/system/cpu -maxdepth 1 -type d -name 'cpu[0-9]*' 2>/dev/null | + wc -l | + tr -d '[:space:]' +} + +############################################################################### +# dt_list_enabled_memory_nodes +# Prints enabled memory nodes directly below the supplied DT root. +############################################################################### +dt_list_enabled_memory_nodes() { + dtlem_root="$1" + dtlem_found=0 + [ -d "$dtlem_root" ] || return 3 + + for dtlem_node in "$dtlem_root"/memory@* "$dtlem_root"/memory; do + [ -d "$dtlem_node" ] || continue + dt_node_enabled "$dtlem_node" || continue + printf '%s\n' "$dtlem_node" + dtlem_found=1 + done + + [ "$dtlem_found" -eq 1 ] +} + +############################################################################### +# dt_list_enabled_reserved_memory_nodes +# Prints enabled reserved-memory child nodes from the supplied DT root. +############################################################################### +dt_list_enabled_reserved_memory_nodes() { + dtlrm_root="$1" + dtlrm_found=0 + dtlrm_reserved_root="$dtlrm_root/reserved-memory" + [ -d "$dtlrm_reserved_root" ] || return 1 + + for dtlrm_node in "$dtlrm_reserved_root"/*; do + [ -d "$dtlrm_node" ] || continue + dt_node_enabled "$dtlrm_node" || continue + printf '%s\n' "$dtlrm_node" + dtlrm_found=1 + done + + [ "$dtlrm_found" -eq 1 ] +} + +############################################################################### +# dt_profile_id +# Prints a stable profile identifier derived from the first root compatible. +############################################################################### +dt_profile_id() { + dtpi_root="$1" + dtpi_first_compatible="" + [ -d "$dtpi_root" ] || return 3 + + if [ -r "$dtpi_root/compatible" ]; then + dtpi_first_compatible=$(tr '\000' '\n' <"$dtpi_root/compatible" 2>/dev/null | sed -n '1p') + fi + [ -n "$dtpi_first_compatible" ] || return 1 + + printf '%s\n' "$dtpi_first_compatible" | + tr '[:upper:]' '[:lower:]' | + tr ',.-' '___' | + tr -cd '[:alnum:]_' +} + +############################################################################### +# dt_list_enabled_property_nodes +# Prints enabled DT node directories that expose the exact property name. +############################################################################### +dt_list_enabled_property_nodes() { + dtlep_root="$1" + dtlep_property="$2" + dtlep_file="" + [ -d "$dtlep_root" ] && [ -n "$dtlep_property" ] || return 3 + + if [ -r "${DT_PROPERTY_INDEX:-}" ]; then + awk -F '|' -v property="$dtlep_property" '$1 == property { print $2; found=1 } END { exit !found }' "$DT_PROPERTY_INDEX" + return $? + fi + + dtlep_file=$(mktemp "${TMPDIR:-/tmp}/dt-property.XXXXXX") || return 1 + find "$dtlep_root" -type f -name "$dtlep_property" 2>/dev/null | + while IFS= read -r dtlep_property_file; do + dtlep_node=$(dirname "$dtlep_property_file") + dt_node_enabled "$dtlep_node" || continue + printf '%s\n' "$dtlep_node" >>"$dtlep_file" + done + + if [ -s "$dtlep_file" ]; then + sort -u "$dtlep_file" + rm -f "$dtlep_file" + return 0 + fi + + rm -f "$dtlep_file" + return 1 +} + +############################################################################### +# dt_validate_property_provider